diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 06be59d510..0000000000 --- a/.eslintignore +++ /dev/null @@ -1,16 +0,0 @@ -!.meta - -# Protected or generated -/.appends -/.github -/.vscode - -# Binaries -/bin - -# Configuration -/config - -# Typings -/exercises/**/global.d.ts -/exercises/**/env.d.ts diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 16287ebc2c..b0c1effe76 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,7 +5,7 @@ updates: - package-ecosystem: 'github-actions' directory: '/' schedule: - interval: 'daily' + interval: 'monthly' labels: - 'dependencies :arrow_up_small:' - 'github_actions' @@ -17,7 +17,7 @@ updates: directory: '/' # Check the npm registry for updates every day (weekdays) schedule: - interval: 'daily' + interval: 'monthly' labels: - 'dependencies :arrow_up_small:' - 'javascript' diff --git a/.github/labels.yml b/.github/labels.yml index f529049027..775388e833 100644 --- a/.github/labels.yml +++ b/.github/labels.yml @@ -86,6 +86,30 @@ description: "Work on Test Runners" color: "ffffff" +# The `x:rep/` labels describe the amount of reputation to award +# +# For more information on reputation and how these labels should be used, +# check out https://exercism.org/docs/using/product/reputation +- name: "x:rep/tiny" + description: "Tiny amount of reputation" + color: "ffffff" + +- name: "x:rep/small" + description: "Small amount of reputation" + color: "ffffff" + +- name: "x:rep/medium" + description: "Medium amount of reputation" + color: "ffffff" + +- name: "x:rep/large" + description: "Large amount of reputation" + color: "ffffff" + +- name: "x:rep/massive" + description: "Massive amount of reputation" + color: "ffffff" + # The `x:size/` labels describe the expected amount of work for a contributor - name: "x:size/tiny" description: "Tiny amount of work" @@ -133,16 +157,16 @@ description: "Work on Documentation" color: "ffffff" -# This label can be added to accept PRs as part of Hacktoberfest -- name: "hacktoberfest-accepted" - description: "Make this PR count for hacktoberfest" - color: "ff7518" - # This Exercism-wide label is added to all automatically created pull requests that help migrate/prepare a track for Exercism v3 - name: "v3-migration 🤖" description: "Preparing for Exercism v3" color: "e99695" +# This Exercism-wide label can be used to bulk-close issues in preparation for pausing community contributions +- name: "paused" + description: "Work paused until further notice" + color: "e4e669" + # ----------------------------------------------------------------------------------------- # # These are the repository-specific labels that augment the Exercise-wide labels defined in # # https://github.com/exercism/org-wide-files/blob/main/global-files/.github/labels.yml. # diff --git a/.github/org-wide-files-config.toml b/.github/org-wide-files-config.toml new file mode 100644 index 0000000000..577840e35d --- /dev/null +++ b/.github/org-wide-files-config.toml @@ -0,0 +1,2 @@ +[configlet] +fmt = true diff --git a/.github/pr-commenter.yml b/.github/pr-commenter.yml deleted file mode 100644 index 29441a8251..0000000000 --- a/.github/pr-commenter.yml +++ /dev/null @@ -1,68 +0,0 @@ -comment: - on-update: edit - glob-options: - dot: true - - header: | - ### Dear {{ prAuthor }} - Thank you for contributing to the JavaScript track on Exercism! 💙 - You will see some automated feedback below 🤖. It would be great if you can make sure your PR covers those points. This will save your reviewer some time and your change can be merged quicker. - - footer: | - - Automated comment created by [PR Commenter](https://github.com/exercism/pr-commenter-action) 🤖. - - snippets: - - id: concept-changes - files: - - 'exercises/concept/*/.docs/introduction.md' - - 'concepts/*/introduction.md' - - 'concepts/*/about.md' - body: | - - 📜 The following files usually contain very similar content. - - `concepts//about.md` - - `concepts//introduction.md` - - `exercises/concept//.docs/introduction.md` - - Please check whether the changes you made to one of these also need to be applied to the others. - - - id: exemplar-or-stub-changes - files: - - 'exercises/concept/*/.meta/*.js' - - any: - - 'exercises/concept/*/*.js' - - '!exercises/concept/*/*.spec.js' - body: | - - 🧦 If you changed the function signature or the JSDoc comment in the exemplar file (`.meta/exemplar.js`) or the stub file (`.js`), make sure the change is applied to both files. - - - id: problem-spec-related - files: - - 'exercises/practice/*/*.spec.js' - - 'exercises/practice/*/.docs/instructions.md' - body: | - - ⬆️ The instructions and test cases for many practice exercises originate in the Exercism-wide [problem-specifications repo](https://github.com/exercism/problem-specifications). If the exercise you changed is listed there in the [exercises folder](https://github.com/exercism/problem-specifications/tree/main/exercises), please consider the following. - - Improvements to the `instructions.md` file should be made in the problem-spec repo so that all tracks can benefit. - You can open a PR there instead. - - If you want to add some language specific information, use the `instructions.append.md` file (see [Practice Exercise Docs](https://exercism.org/docs/building/tracks/practice-exercises)). - - Test cases selected in the `.meta/tests.toml` file need to be implemented in the `.spec.js` file according to the specification in [`canonical-data.json`](https://github.com/exercism/problem-specifications#test-data-canonical-datajson). - - - id: note-for-contributors - files: - - '*' - - '**/*' - body: | - - ✍️ If your PR is not related to an existing issue (and is not self-explaining like a typo fix), please make sure the description explains why the change you made is necessary. - - - 🔤 If your PR fixes an easy to identify typo, if would be great if you could check for that typo in the whole repo. For example, if you found `Unicdoe`, use "replace all" in your editor (or command line magic) to fix it consistently. - - - id: note-for-maintainers - files: - - '*' - - '**/*' - body: | - ### Dear Reviewer/Maintainer - - 📏 Make sure you set the appropriate **`x:size` label** for the PR. (This also works after merging, in case you forgot about it.) - - - 🔍 **Don't be too nit-picky.** If the PR is a clear improvement compared to the status quo, it should be approved as clear signal this is good to be merged even if the minor comments you might have are not addressed by the contributor. Further improvement ideas can be captured in issues (if important enough) and implemented via additional PRs. - - - 🤔 After reviewing the diff in the "Files changed" section, take a moment to think about whether there are **changes missing** from the diff. Does something need to be adjusted in other places so the code or content stays consistent? diff --git a/.github/workflows/action-format.yml b/.github/workflows/action-format.yml index 43024ad33d..38462cba30 100644 --- a/.github/workflows/action-format.yml +++ b/.github/workflows/action-format.yml @@ -3,20 +3,21 @@ name: 'javascript / format' on: issue_comment: types: [created] + workflow_dispatch: jobs: format: name: 'Format code' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/format') steps: - name: 'Post acknowledgement that it will format code' continue-on-error: true # Never fail the build if this fails - uses: actions/github-script@e3cbab99d3a9b271e1b79fc96d103a4a5534998c + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | - github.issues.createComment({ + github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, @@ -34,7 +35,7 @@ jobs: id: fork_status run: | IS_FORK="$(jq '.head.repo.fork' "/tmp/pr.json")" - echo "::set-output name=fork::$IS_FORK" + echo "fork=$IS_FORK" >> "$GITHUB_OUTPUT" - name: 'Setup SSH deploy key' if: steps.fork_status.outputs.fork == 'false' @@ -60,12 +61,18 @@ jobs: git clone $HEAD_REPO . git checkout -b "$HEAD_REF" "origin/$HEAD_REF" - - name: Use Node.js LTS (16.x) - uses: actions/setup-node@1f8c6b94b26d0feae1e387ca63ccbdc44d27b561 + - name: Enable corepack to fix https://github.com/actions/setup-node/pull/901 + run: corepack enable pnpm + + - name: Use Node.js LTS (22.x) + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 with: - node-version: '16' + node-version: 22.x + cache: 'pnpm' + - name: Install project development dependencies - run: npm install --no-save + run: corepack pnpm install --no-save + - name: 'Format code' run: ./bin/format.sh @@ -88,11 +95,11 @@ jobs: - name: 'Post acknowledgement that it has formatted the code' continue-on-error: true # Never fail the build if this fails - uses: actions/github-script@e3cbab99d3a9b271e1b79fc96d103a4a5534998c + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | - github.issues.createComment({ + github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, @@ -102,11 +109,11 @@ jobs: - name: 'Post reminder to trigger build manually' continue-on-error: true # Never fail the build if this fails if: steps.fork_status.outputs.fork == 'true' - uses: actions/github-script@e3cbab99d3a9b271e1b79fc96d103a4a5534998c + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | - github.issues.createComment({ + github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, diff --git a/.github/workflows/action-sync.yml b/.github/workflows/action-sync.yml index e01863b34e..5f8de73c82 100644 --- a/.github/workflows/action-sync.yml +++ b/.github/workflows/action-sync.yml @@ -3,20 +3,21 @@ name: 'javascript / sync' on: issue_comment: types: [created] + workflow_dispatch: jobs: format: name: 'Sync all exercises' - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/sync') steps: - name: 'Post acknowledgement that it will sync exercises' continue-on-error: true # Never fail the build if this fails - uses: actions/github-script@e3cbab99d3a9b271e1b79fc96d103a4a5534998c + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | - github.issues.createComment({ + github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, @@ -34,7 +35,7 @@ jobs: id: fork_status run: | IS_FORK="$(jq '.head.repo.fork' "/tmp/pr.json")" - echo "::set-output name=fork::$IS_FORK" + echo "fork=$IS_FORK" >> "$GITHUB_OUTPUT" - name: 'Setup SSH deploy key' if: steps.fork_status.outputs.fork == 'false' @@ -61,9 +62,11 @@ jobs: git checkout -b "$HEAD_REF" "origin/$HEAD_REF" - name: 'Install dependencies' - run: yarn install + run: | + corepack enable pnpm + corepack pnpm install - name: 'Sync exercises' - run: npx babel-node scripts/sync + run: corepack pnpm node scripts/sync.mjs - name: 'Commit changes' run: | @@ -84,11 +87,11 @@ jobs: - name: 'Post acknowledgement that it has synced the code' continue-on-error: true # Never fail the build if this fails - uses: actions/github-script@e3cbab99d3a9b271e1b79fc96d103a4a5534998c + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | - github.issues.createComment({ + github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, @@ -98,11 +101,11 @@ jobs: - name: 'Post reminder to trigger build manually' continue-on-error: true # Never fail the build if this fails if: steps.fork_status.outputs.fork == 'true' - uses: actions/github-script@e3cbab99d3a9b271e1b79fc96d103a4a5534998c + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | - github.issues.createComment({ + github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, diff --git a/.github/workflows/ci.js.yml b/.github/workflows/ci.js.yml index 02dcfd051c..d623c9d4ed 100644 --- a/.github/workflows/ci.js.yml +++ b/.github/workflows/ci.js.yml @@ -6,40 +6,49 @@ name: javascript / main on: push: branches: [main] + workflow_dispatch: jobs: precheck: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - - name: Use Node.js LTS (16.x) - uses: actions/setup-node@1f8c6b94b26d0feae1e387ca63ccbdc44d27b561 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 + - name: Enable corepack to fix https://github.com/actions/setup-node/pull/901 + run: corepack enable pnpm + + - name: Use Node.js LTS (22.x) + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 with: - node-version: '16' + node-version: 22.x + cache: 'pnpm' - name: Install project dependencies - run: npm ci + run: corepack pnpm install --frozen-lockfile - name: Run exercism/javascript ci precheck (checks config, lint code, and runs tests) for all exercises - run: npx babel-node scripts/ci-check + run: corepack pnpm node scripts/ci-check.mjs ci: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 strategy: matrix: - node-version: ['16', '17'] + node-version: [22.x] steps: - - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 + - name: Enable corepack to fix https://github.com/actions/setup-node/pull/901 + run: corepack enable pnpm + - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@1f8c6b94b26d0feae1e387ca63ccbdc44d27b561 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 with: node-version: ${{ matrix.node-version }} + cache: 'pnpm' - name: Install project dependencies - run: npm ci + run: corepack pnpm install --frozen-lockfile - name: Run exercism/javascript ci (checks config, lint code, and runs tests) for all exercises - run: npx babel-node scripts/ci + run: corepack pnpm node scripts/ci.mjs diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 1f1c9725cc..4ad26b8320 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -16,7 +16,7 @@ on: jobs: analyze: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 strategy: fail-fast: false @@ -29,11 +29,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -44,7 +44,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v1 + uses: github/codeql-action/autobuild@v3 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -58,4 +58,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/configlet.yml b/.github/workflows/configlet.yml index 0fcdaeaedb..c730b58201 100644 --- a/.github/workflows/configlet.yml +++ b/.github/workflows/configlet.yml @@ -1,18 +1,17 @@ -# This workflow will do a fetch the latest configlet binary and lint this repository. +name: Configlet -name: configlet +on: + pull_request: + push: + branches: + - main + workflow_dispatch: -on: [push, pull_request] +permissions: + contents: read jobs: - lint: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - - - name: Fetch configlet - uses: exercism/github-actions/configlet-ci@main - - - name: Configlet Linter - run: configlet lint + configlet: + uses: exercism/github-actions/.github/workflows/configlet.yml@main + with: + fmt: true diff --git a/.github/workflows/no-important-files-changed.yml b/.github/workflows/no-important-files-changed.yml new file mode 100644 index 0000000000..812e912966 --- /dev/null +++ b/.github/workflows/no-important-files-changed.yml @@ -0,0 +1,23 @@ +name: No important files changed + +on: + pull_request_target: + types: [opened] + branches: [main] + paths: + - "exercises/concept/**" + - "exercises/practice/**" + - "!exercises/*/*/.approaches/**" + - "!exercises/*/*/.articles/**" + - "!exercises/*/*/.docs/**" + - "!exercises/*/*/.meta/**" + +permissions: + pull-requests: write + +jobs: + check: + uses: exercism/github-actions/.github/workflows/check-no-important-files-changed.yml@main + with: + repository: ${{ github.event.pull_request.head.repo.owner.login }}/${{ github.event.pull_request.head.repo.name }} + ref: ${{ github.head_ref }} diff --git a/.github/workflows/pause-community-contributions.yml b/.github/workflows/pause-community-contributions.yml new file mode 100644 index 0000000000..77636a2eb4 --- /dev/null +++ b/.github/workflows/pause-community-contributions.yml @@ -0,0 +1,25 @@ +name: Pause Community Contributions + +on: + issues: + types: + - opened + pull_request_target: + types: + - opened + paths-ignore: + - 'exercises/*/*/.approaches/**' + - 'exercises/*/*/.articles/**' + +permissions: + issues: write + pull-requests: write + +jobs: + pause: + if: github.repository_owner == 'exercism' # Stops this job from running on forks + uses: exercism/github-actions/.github/workflows/community-contributions.yml@main + with: + forum_category: javascript + secrets: + github_membership_token: ${{ secrets.COMMUNITY_CONTRIBUTIONS_WORKFLOW_TOKEN }} diff --git a/.github/workflows/ping-cross-track-maintainers-team.yml b/.github/workflows/ping-cross-track-maintainers-team.yml new file mode 100644 index 0000000000..b6ec9c5662 --- /dev/null +++ b/.github/workflows/ping-cross-track-maintainers-team.yml @@ -0,0 +1,16 @@ +name: Ping cross-track maintainers team + +on: + pull_request_target: + types: + - opened + +permissions: + pull-requests: write + +jobs: + ping: + if: github.repository_owner == 'exercism' # Stops this job from running on forks + uses: exercism/github-actions/.github/workflows/ping-cross-track-maintainers-team.yml@main + secrets: + github_membership_token: ${{ secrets.COMMUNITY_CONTRIBUTIONS_WORKFLOW_TOKEN }} diff --git a/.github/workflows/pr-comment.yml b/.github/workflows/pr-comment.yml deleted file mode 100644 index 321bbae256..0000000000 --- a/.github/workflows/pr-comment.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: PR Comment - -on: [pull_request_target] - -jobs: - pr-comment: - runs-on: ubuntu-latest - steps: - - uses: exercism/pr-commenter-action@3706a61d7c6cf1a77db576b9a4c0e7046ae78f1a - with: - github-token: '${{ github.token }}' - config-file: '.github/pr-commenter.yml' - template-variables: | - { - "prAuthor": "${{ github.event.pull_request.user.login }}" - } diff --git a/.github/workflows/pr.ci.js.yml b/.github/workflows/pr.ci.js.yml index 6d5f4e5464..67572e1739 100644 --- a/.github/workflows/pr.ci.js.yml +++ b/.github/workflows/pr.ci.js.yml @@ -7,50 +7,71 @@ on: pull_request jobs: precheck: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Checkout PR - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 + with: + fetch-depth: ${{ github.event_name == 'pull_request' && 2 || 0 }} + + - name: Get changed files + id: changed-files + run: | + if ${{ github.event_name == 'pull_request' }}; then + echo "changed_files=$(git diff --name-only --diff-filter=ACM -r HEAD^1 HEAD | xargs)" >> $GITHUB_OUTPUT + else + echo "changed_files=$(git diff --name-only --diff-filter=ACM ${{ github.event.before }} ${{ github.event.after }} | xargs)" >> $GITHUB_OUTPUT + fi - - name: Use Node.js LTS (16.x) - uses: actions/setup-node@1f8c6b94b26d0feae1e387ca63ccbdc44d27b561 + - name: Enable corepack to fix https://github.com/actions/setup-node/pull/901 + run: corepack enable pnpm + + - name: Use Node.js LTS (22.x) + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 with: - node-version: '16' + node-version: 22.x + cache: 'pnpm' - name: Install project dependencies - run: npm ci + run: corepack pnpm install --frozen-lockfile - name: Run exercism/javascript ci precheck (stub files, config integrity) for changed exercises - run: | - PULL_REQUEST_URL=$(jq -r ".pull_request.url" "$GITHUB_EVENT_PATH") - curl --url $"${PULL_REQUEST_URL}/files?per_page=100" --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' | \ - jq -c '.[] | select(.status == "added" or .status == "modified") | select(.filename | match("\\.(js|jsx|md|json)$")) | .filename' | \ - xargs -r npx babel-node scripts/pr-check - bin/check_corejs_version.sh + run: corepack pnpm node scripts/pr.mjs ${{ steps.changed-files.outputs.changed_files }} ci: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 strategy: matrix: - node-version: ['16', '17'] + node-version: [22.x] steps: - name: Checkout PR - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 + with: + fetch-depth: ${{ github.event_name == 'pull_request' && 2 || 0 }} + + - name: Get changed files + id: changed-files + run: | + if ${{ github.event_name == 'pull_request' }}; then + echo "changed_files=$(git diff --name-only --diff-filter=ACM -r HEAD^1 HEAD | xargs)" >> $GITHUB_OUTPUT + else + echo "changed_files=$(git diff --name-only --diff-filter=ACM ${{ github.event.before }} ${{ github.event.after }} | xargs)" >> $GITHUB_OUTPUT + fi + + - name: Enable corepack to fix https://github.com/actions/setup-node/pull/901 + run: corepack enable pnpm - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@1f8c6b94b26d0feae1e387ca63ccbdc44d27b561 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 with: node-version: ${{ matrix.node-version }} + cache: 'pnpm' - name: Install project dependencies - run: npm ci + run: corepack pnpm install --frozen-lockfile - name: Run exercism/javascript ci (runs tests) for changed/added exercises - run: | - PULL_REQUEST_URL=$(jq -r ".pull_request.url" "$GITHUB_EVENT_PATH") - curl --url $"${PULL_REQUEST_URL}/files?per_page=100" --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' | \ - jq -c '.[] | select(.status == "added" or .status == "modified") | select(.filename | match("\\.(js|jsx|md|json)$")) | .filename' | \ - xargs -r npx babel-node scripts/pr + run: corepack pnpm node scripts/pr.mjs ${{ steps.changed-files.outputs.changed_files }} diff --git a/.github/workflows/run-configlet-sync.yml b/.github/workflows/run-configlet-sync.yml new file mode 100644 index 0000000000..b49cbffe81 --- /dev/null +++ b/.github/workflows/run-configlet-sync.yml @@ -0,0 +1,10 @@ +name: Run Configlet Sync + +on: + workflow_dispatch: + schedule: + - cron: '0 0 15 * *' + +jobs: + call-gha-workflow: + uses: exercism/github-actions/.github/workflows/configlet-sync.yml@main diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml index 095884ad54..e7b99e5048 100644 --- a/.github/workflows/sync-labels.yml +++ b/.github/workflows/sync-labels.yml @@ -2,20 +2,18 @@ name: Tools on: push: - branches: [main] + branches: + - main paths: - .github/labels.yml - .github/workflows/sync-labels.yml - schedule: - - cron: 0 0 1 * * workflow_dispatch: + schedule: + - cron: 0 0 1 * * # First day of each month + +permissions: + issues: write jobs: sync-labels: - name: Sync labels - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - - uses: micnncim/action-label-syncer@3abd5ab72fda571e69fffd97bd4e0033dd5f495c - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + uses: exercism/github-actions/.github/workflows/labels.yml@main diff --git a/.github/workflows/verify-code-formatting.yml b/.github/workflows/verify-code-formatting.yml index 73737392a7..705d85d23a 100644 --- a/.github/workflows/verify-code-formatting.yml +++ b/.github/workflows/verify-code-formatting.yml @@ -7,15 +7,10 @@ on: jobs: verify: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: 'Checkout code' - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - - name: Use Node.js LTS (16.x) - uses: actions/setup-node@1f8c6b94b26d0feae1e387ca63ccbdc44d27b561 - with: - node-version: '16' - - name: Install project development dependencies - run: npm i + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 + - name: 'Verify formatting of all files' run: ./bin/check-formatting.sh diff --git a/.gitignore b/.gitignore index bc18408fb7..0c88ff6ec3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /node_modules /bin/configlet -/pnpm-lock.yaml +/bin/configlet.exe +/package-lock.json /yarn.lock diff --git a/.prettierignore b/.prettierignore index 1a56ebd5b0..0187784ddc 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,4 +1,24 @@ /.github/labels.yml -/.github/workflows/sync-labels.yml + +# Generated exercises/**/README.md +pnpm-lock.yaml + !/README.md + +# Originates from https://github.com/exercism/org-wide-files +CODE_OF_CONDUCT.md +LICENSE +.github/workflows/configlet.yml +.github/workflows/sync-labels.yml +.github/workflows/no-important-files-changed.yml +.github/workflows/pause-community-contributions.yml + +# These are formatted via configlet and will not match prettier +exercises/**/.meta/config.json +exercises/**/.approaches/config.json +config.json + +# Originates from https://github.com/exercism/problem-specifications +exercises/practice/**/.docs/instructions.md +exercises/practice/**/.docs/introduction.md \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 47c6a8b260..3344c414bc 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,5 +7,6 @@ // because of how whitespace is (not) rendered. 65 ] - } + }, + "cSpell.words": ["reorderd"] } diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 368129a0bc..3f7813de10 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -2,17 +2,23 @@ ## Introduction -Exercism is a platform centered around empathetic conversation. We have a low tolerance for communication that makes anyone feel unwelcome, unsupported, insulted or discriminated against. +Exercism is a platform centered around empathetic conversation. +We have a low tolerance for communication that makes anyone feel unwelcome, unsupported, insulted or discriminated against. ## Seen or experienced something uncomfortable? -If you see or experience abuse, harassment, discrimination, or feel unsafe or upset, please email abuse@exercism.io. We will take your report seriously. +If you see or experience abuse, harassment, discrimination, or feel unsafe or upset, please email [abuse@exercism.org](mailto:abuse@exercism.org?subject=%5BCoC%5D) and include \[CoC\] in the subject line. +We will follow up with you as a priority. ## Enforcement -We actively monitor for Code of Conduct (CoC) violations and take any reports of violations extremely seriously. We have banned contributors, mentors and users due to violations. +We actively monitor for Code of Conduct (CoC) violations and take any reports of violations extremely seriously. +We have banned contributors, mentors and users due to violations. -After we receive a report of a CoC violation, we view that person's conversation history on Exercism and related communication channels and attempt to understand whether someone has deliberately broken the CoC, or accidentally crossed a line. We generally reach out to the person who has been reported to discuss any concerns we have and warn them that repeated violations will result in a ban. Sometimes we decide that no violation has occurred and that no action is required and sometimes we will also ban people on a first offense. We strive to be fair, but will err on the side of protecting the culture of our community. +After we receive a report of a CoC violation, we view that person's conversation history on Exercism and related communication channels and attempt to understand whether someone has deliberately broken the CoC, or accidentally crossed a line. +We generally reach out to the person who has been reported to discuss any concerns we have and warn them that repeated violations will result in a ban. +Sometimes we decide that no violation has occurred and that no action is required and sometimes we will also ban people on a first offense. +We strive to be fair, but will err on the side of protecting the culture of our community. Exercism's leadership reserve the right to take whatever action they feel appropriate with regards to CoC violations. @@ -36,15 +42,16 @@ Exercism should be a safe place for everybody regardless of - Race - Age - Religion -- Anything else you can think of. +- Anything else you can think of As someone who is part of this community, you agree that: -- We are collectively and individually committed to safety and inclusivity. -- We have zero tolerance for abuse, harassment, or discrimination. -- We respect people’s boundaries and identities. -- We refrain from using language that can be considered offensive or oppressive (systemically or otherwise), eg. sexist, racist, homophobic, transphobic, ableist, classist, etc. - this includes (but is not limited to) various slurs. -- We avoid using offensive topics as a form of humor. +- We are collectively and individually committed to safety and inclusivity +- We have zero tolerance for abuse, harassment, or discrimination +- We respect people’s boundaries and identities +- We refrain from using language that can be considered offensive or oppressive (systemically or otherwise), eg. sexist, racist, homophobic, transphobic, ableist, classist, etc. + - this includes (but is not limited to) various slurs. +- We avoid using offensive topics as a form of humor We actively work towards: @@ -57,26 +64,30 @@ We condemn: - Stalking, doxxing, or publishing private information - Violence, threats of violence or violent language - Anything that compromises people’s safety -- Conduct or speech which might be considered sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory or offensive in nature. -- The use of unwelcome, suggestive, derogatory or inappropriate nicknames or terms. -- Disrespect towards others (jokes, innuendo, dismissive attitudes) and towards differences of opinion. -- Intimidation or harassment (online or in-person). Please read the [Citizen Code of Conduct](https://github.com/stumpsyn/policies/blob/master/citizen_code_of_conduct.md) for how we interpret harassment. -- Inappropriate attention or contact. -- Not understanding the differences between constructive criticism and disparagement. +- Conduct or speech which might be considered sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory or offensive in nature +- The use of unwelcome, suggestive, derogatory or inappropriate nicknames or terms +- Disrespect towards others (jokes, innuendo, dismissive attitudes) and towards differences of opinion +- Intimidation or harassment (online or in-person). + Please read the [Citizen Code of Conduct](https://github.com/stumpsyn/policies/blob/master/citizen_code_of_conduct.md) for how we interpret harassment +- Inappropriate attention or contact +- Not understanding the differences between constructive criticism and disparagement These things are NOT OK. -Be aware of how your actions affect others. If it makes someone uncomfortable, stop. +Be aware of how your actions affect others. +If it makes someone uncomfortable, stop. If you say something that is found offensive, and you are called out on it, try to: -- Listen without interruption. -- Believe what the person is saying & do not attempt to disqualify what they have to say. -- Ask for tips / help with avoiding making the offense in the future. -- Apologize and ask forgiveness. +- Listen without interruption +- Believe what the person is saying & do not attempt to disqualify what they have to say +- Ask for tips / help with avoiding making the offense in the future +- Apologize and ask forgiveness ## History -This policy was initially adopted from the Front-end London Slack community and has been modified since. A version history can be seen on [GitHub](https://github.com/exercism/website-copy/edit/main/pages/code_of_conduct.md). +This policy was initially adopted from the Front-end London Slack community and has been modified since. +A version history can be seen on [GitHub](https://github.com/exercism/website-copy/edit/main/pages/code_of_conduct.md). -_This policy is a "living" document, and subject to refinement and expansion in the future. This policy applies to the Exercism website, the Exercism GitHub organization, any other Exercism-related communication channels (e.g. Slack, Twitter, email) and any other Exercism entity or event._ +_This policy is a "living" document, and subject to refinement and expansion in the future. +This policy applies to the Exercism website, the Exercism GitHub organization, any other Exercism-related communication channels (e.g. Discord, Forum, Twitter, email) and any other Exercism entity or event._ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 212cfc67c4..4aa8847d1e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,8 @@ It's not uncommon that people discover incorrect implementations of certain test We welcome contributions of all sorts and sizes, from reporting issues to submitting patches, as well as joining the current [discussions 💬][issue-discussion]. -> :warning: This guide is slightly outdated and doesn't hold the V3 changes yet. +> [!WARNING] +> This guide is slightly outdated and doesn't hold the V3 changes yet. --- @@ -76,18 +77,18 @@ The baseline of work is as follows: 1. We'll assign the issue to you, so you get to work on this exercise 1. Create a new folder in `/exercises` 1. You'll need to sync this folder with the matching config files. - You can use `scripts/sync` to do this: `ASSIGNMENT=slug npx babel-node scripts/sync`. + You can use `scripts/sync` to do this. 1. Create a `.js` stub file. 1. Create a `.spec.js` test file. Here add the tests, per canonical data if possible (more on canonical data below). 1. Create an `example.js` file. Place a working implementation, assuming it's renamed to `.js` 1. Create `.meta/tests.toml`. If the exercise that is being implemented has test data in the [problem specifications repository][problem-specifications], the contents of this file **must** be a list of UUIDs of the tests that are implemented or not implemented. Scroll down to [tools](#tools) to find configlet which aids in generating this file _interactively_. -1. Run the tests locally, using `scripts/test`: `ASSIGNMENT=slug npx babel-node scripts/test`. -1. Run the linter locally, using `scripts/lint`: `ASSIGNMENT=slug npx babel-node scripts/lint`. +1. Run the tests locally, using `scripts/test`. +1. Run the linter locally, using `scripts/lint`. 1. Create an entry in `config.json`: a unique _new_ UUID (you can use the `configlet uuid` tool to generate one, scroll down to [tools](#tools) to see how you can get it), give it a difficulty (should be similar to similar exercises), and make sure the _order_ of the file is sane. Currently, the file is ordered first on concept exercise, then on "original core", finally everything else, on difficulty low to high, and ultimately lexicographically. -1. Format the files, using `scripts/format`: `npx babel-node scripts/format`. +1. Format the files, using `scripts/format`. The final step is opening a Pull Request, with these items all checked off. Make sure the tests run and the linter is happy. It will run automatically on your PR. @@ -167,7 +168,7 @@ You may improve these files by making the required changes and opening a new Pul ## Tools You'll need LTS or higher Node.js to contribute to the _code_ in this repository. -Run `npm install` in the root to be able to run the scripts as listed below. +Run `corepack pnpm install` in the root to be able to run the scripts as listed below. We use the following dependencies: - `shelljs` to provide shell interface to scripts @@ -176,9 +177,9 @@ We use the following dependencies: - `babel` to transpile everything so it works _regardless of your version of Node.js_. We also use `prettier` to format the files. -**Prettier is installed when using `npm install`**. -You may use `npx babel-node scripts/format` to run prettier. -If you want to auto-format using your editor, install via `npm install` and it will Just Work™. +**Prettier is installed when using `corepack pnpm install`**. +You may use `corepack pnpm node scripts/format.mjs` to run prettier. +If you want to auto-format using your editor, install via `corepack pnpm install` and it will Just Work™. ### Fetch configlet @@ -209,31 +210,11 @@ It then interactively gives the maintainer the option to include or exclude test We have various `scripts` to aid with maintaining and contributing to this repository. -> ⚠ If you encounter the following error: -> -> ```text -> SyntaxError: Unexpected token 'export' -> ``` -> -> It's because your local Node.js version does **not** support es6 -> `import` and `export` statements in regular `.js` files, or -> files without extension. This is one of the reasons why these -> scripts are meant to be run through Node.js: -> -> ```shell -> npx babel-node scripts/the-script -> ``` -> -> Additionally, this ensures that the code written in the scripts -> and their dependencies can be executed by your current Node.js -> version, which may be different from the version used by the -> maintainer or contributor who contributed to the script. - #### `format` ```js /* - * Run this script (from root directory): npx babel-node scripts/format + * Run this script (from root directory): corepack pnpm node scripts/format.mjs * * This runs `prettier` on all applicable files, FORCES using the same version * as the CI uses to check if the files have been formatted. @@ -241,14 +222,14 @@ We have various `scripts` to aid with maintaining and contributing to this repos ``` Use this action to format all the files using the correct version of prettier. -If you want your editor to do this automatically, install the project development dependencies (`npm i`), which includes `prettier`. +If you want your editor to do this automatically, install the project development dependencies (`corepack pnpm install`), which includes `prettier`. The correct version will be extracted when running `.github/workflows/verify-code-formatting.yml`. #### `lint` ```js /* - * Run this script (from root directory): npx babel-node scripts/lint + * Run this script (from root directory): corepack pnpm node scripts/lint.mjs * * This runs `eslint` on all sample solutions (and test) files */ @@ -258,14 +239,24 @@ If the `ASSIGNMENT` environment variable is set, only _that_ exercise is tested. For example, if you only want to lint `two-fer`, you may, depending on your environment use: ```shell -ASSIGNMENT=two-fer npx babel-node scripts/lint +ASSIGNMENT=practice/two-fer corepack pnpm node scripts/lint.mjs +``` + +Note: on Windows, if you're not in a POSIX style command line, you can use `cross-env` to make this work: + +```shell +# if installed globally +cross-env ASSIGNMENT=practice/two-fer corepack pnpm node scripts/lint.mjs + +# otherwise +corepack pnpm dlx cross-env ASSIGNMENT=practice/two-fer node scripts/lint.mjs ``` #### `test` ```js /** - * Run this script (from root directory): npx babel-node scripts/test + * Run this script (from root directory): corepack pnpm node scripts/test.mjs * * This runs `jest` tests for all sample solutions */ @@ -275,14 +266,24 @@ If the `ASSIGNMENT` environment variable is set, only _that_ exercise is tested. For example, if you only want to test the `example.js` for `two-fer`, you may, depending on your environment, use: ```shell -ASSIGNMENT=two-fer npx babel-node scripts/test +ASSIGNMENT=practice/two-fer corepack pnpm node scripts/test.mjs +``` + +Note: on Windows, if you're not in a POSIX style command line, you can use `cross-env` to make this work: + +```shell +# if installed globally +cross-env ASSIGNMENT=practice/two-fer corepack pnpm node scripts/test.mjs + +# otherwise +corepack pnpm dlx cross-env ASSIGNMENT=practice/two-fer node scripts/test.mjs ``` #### `sync` ```js /** - * Run this script (from root directory): npx babel-node scripts/sync + * Run this script (from root directory): corepack pnpm node scripts/sync.mjs * * This script is used to propagate any change to root package.json to * all exercises and keep them in sync. @@ -295,14 +296,24 @@ If the `ASSIGNMENT` environment variable is set, only _that_ exercise is tested. For example, if you only want to sync the files for `two-fer`, you may, depending on your environment, use: ```shell -ASSIGNMENT=two-fer npx babel-node scripts/sync +ASSIGNMENT=practice/two-fer corepack pnpm node scripts/sync.mjs +``` + +Note: on Windows, if you're not in a POSIX style command line, you can use `cross-env` to make this work: + +```shell +# if installed globally +cross-env ASSIGNMENT=practice/two-fer corepack pnpm node scripts/sync.mjs + +# otherwise +corepack pnpm dlx cross-env ASSIGNMENT=practice/two-fer node scripts/sync.mjs ``` #### `checksum` ```js /* - * Run this script (from root directory): npx babel-node scripts/checksum + * Run this script (from root directory): corepack pnpm node scripts/checksum.mjs * * This will check root `package.json` matches each exercise's `package.json`. * But the catch is there are some dependencies that are only used at build-time and not served to end-users @@ -315,7 +326,7 @@ ASSIGNMENT=two-fer npx babel-node scripts/sync ```js /** - * Run this script (from root directory): npx babel-node scripts/ci-check + * Run this script (from root directory): corepack pnpm node scripts/ci-check.mjs * * This will run the following checks: * @@ -331,7 +342,7 @@ Run this script to check stubs, configuration integrity and lint the code. ```js /** - * Run this script (from root directory): npx babel-node scripts/ci + * Run this script (from root directory): corepack pnpm node scripts/ci.mjs * * This will run the following checks: * @@ -346,13 +357,13 @@ Run this script to test all exercises. ```js /** - * Run this script (from root directory): npx babel-node scripts/name-check + * Run this script (from root directory): corepack pnpm node scripts/name-check.mjs * * This will run the following checks: * * 1. Package name is of the format "@exercism/javascript-" * - * This script also allows fixing these names: npx babel-node scripts/name-check --fix + * This script also allows fixing these names: corepack pnpm node scripts/name-check.mjs --fix */ ``` @@ -362,7 +373,7 @@ Run this script to check if the package name in package.json of exercises is in ```js /** - * Run this script (from root directory): npx babel-node scripts/name-uniq + * Run this script (from root directory): corepack pnpm node scripts/name-uniq.mjs * * This will run the following checks: * @@ -376,13 +387,13 @@ Run this script to check if there is any duplicate package name. ```js /** - * Run this script (from root directory): npx babel-node scripts/directory-check + * Run this script (from root directory): corepack pnpm node scripts/directory-check.mjs * * This will run the following checks: * * 1. The package has the correct directory based on the path to the exercise. * - * This script also allows fixing these directories: npx babel-node scripts/directory-check --fix + * This script also allows fixing these directories: corepack pnpm node scripts/directory-check.mjs --fix */ ``` @@ -391,7 +402,17 @@ If the `ASSIGNMENT` environment variable is set, only _that_ exercise is tested. For example, if you only want to test the directory for `concept/closures`, you may, depending on your environment, use: ```shell -ASSIGNMENT=concept/closures npx babel-node scripts/directory-check +ASSIGNMENT=concept/closures corepack pnpm node scripts/directory-check.mjs +``` + +Note: on Windows, if you're not in a POSIX style command line, you can use `cross-env` to make this work: + +```shell +# if installed globally +cross-env ASSIGNMENT=concept/closures corepack pnpm node scripts/directory-check.mjs + +# otherwise +corepack pnpm dlx cross-env ASSIGNMENT=concept/closures node scripts/directory-check.mjs ``` [configlet]: https://exercism.org/docs/building/configlet diff --git a/README.md b/README.md index f22b37f8bd..9de18799c6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,11 @@ -# JavaScript +# Exercism JavaScript Track -[![configlet](https://github.com/exercism/javascript/workflows/configlet/badge.svg)](https://github.com/exercism/javascript/actions?query=workflow%3Aconfiglet) [![javascript / main](https://github.com/exercism/javascript/workflows/javascript%20/%20main/badge.svg)](https://github.com/exercism/javascript/actions?query=workflow%3A%22javascript+%2F+main%22) +[![Configlet](https://github.com/exercism/javascript/actions/workflows/configlet.yml/badge.svg)](https://github.com/exercism/javascript/actions/workflows/configlet.yml) [![javascript / main](https://github.com/exercism/javascript/workflows/javascript%20/%20main/badge.svg)](https://github.com/exercism/javascript/actions?query=workflow%3A%22javascript+%2F+main%22) + +> [!IMPORTANT] +> We 💙 our community but **this repository does not accept community contributions at this time**.
+> There are no active maintainers to review PRs.
+> Please read this [community blog post][freeing-maintainers] for details. **Exercism exercises in JavaScript** @@ -19,7 +24,7 @@ It also has a list of tools you can use, of which the `test` tool is one of them This run `eslint` for all files that _require_ linting. ```shell -npx eslint exercises/**/*.spec.js exercises/**/.meta/*.js --fix +corepack pnpm node scripts/lint.mjs --fix ``` These are also the files that are linted using the lint script, mentioned in [CONTRIBUTING.md][file-contributing]. @@ -43,21 +48,25 @@ This runs `jest` tests for all sample solutions. This _does not_ use the regular way to run `jest`, because the example solution files must be renamed to be imported correctly into the test files. ```shell -npx babel-node scripts/test +corepack pnpm node scripts/test.mjs ``` If the `ASSIGNMENT` environment variable is set, only _that_ exercise is tested. For example, if you only want to test the `example.js` for the practice exercise `two-fer`, you may, depending on your environment, use: ```shell -ASSIGNMENT=practice/two-fer npx babel-node scripts/test +ASSIGNMENT=practice/two-fer corepack pnpm node scripts/test.mjs ``` > Running on Windows? Depending on your shell, environment variables are set differently. > You can use `cross-env` to normalize this. The following should work across environments: > > ```bash -> npx cross-env ASSIGNMENT=practice/two-fer babel-node scripts/test +> # if installed globally +> cross-env ASSIGNMENT=practice/two-fer corepack pnpm node scripts/test.mjs +> +> # otherwise +> corepack pnpm dlx cross-env ASSIGNMENT=practice/two-fer node scripts/test.mjs > ``` ## Related repositories @@ -93,3 +102,4 @@ A lot of the improvements made to this track and tooling are also made to the Ty [git-typescript-representer]: https://github.com/exercism/typescript-representer [git-typescript-test-runner]: https://github.com/exercism/typescript-test-runner [git-website-copy]: https://github.com/exercism/website-copy +[freeing-maintainers]: https://exercism.org/blog/freeing-our-maintainers diff --git a/babel.config.js b/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/bin/check-formatting.sh b/bin/check-formatting.sh index a444a712b7..5379360c0e 100755 --- a/bin/check-formatting.sh +++ b/bin/check-formatting.sh @@ -1,27 +1,50 @@ -#!/bin/bash +#!/usr/bin/env bash -if [ -z "$EXERCISM_PRETTIER_VERSION" ]; then - echo "Pulling prettier version from package.json" - EXERCISM_PRETTIER_VERSION=$(npm list prettier | grep -Po '.*\sprettier@\K.*') +set -uo pipefail + +if [ -z "${EXERCISM_PRETTIER_VERSION:-}" ]; then + echo "[format] pulling prettier version from pnpm list using sed" + EXERCISM_PRETTIER_VERSION="$(corepack pnpm list prettier --parseable | sed -n -e '1,$s/^.*prettier@//' -e 's/\\node_modules\\prettier//p')" + echo "[format] expected version is now ${EXERCISM_PRETTIER_VERSION:-}" +fi + +if [ -z "${EXERCISM_PRETTIER_VERSION:-}" ]; then + echo "[format] pulling prettier version via pnpm-lock.yaml using grep" + EXERCISM_PRETTIER_VERSION="$(cat pnpm-lock.yaml | grep -Po ' prettier@\K[^:]+')" + echo "[format] expected version is now ${EXERCISM_PRETTIER_VERSION:-}" fi -if [ -z "$EXERCISM_PRETTIER_VERSION" ]; then +if [ -z "${EXERCISM_PRETTIER_VERSION:-}" ]; then + echo "Version could not be pulled using sed or grep" >&2 + echo "" echo "---------------------------------------------------" echo "This script requires the EXERCISM_PRETTIER_VERSION variable to work." echo "Please see https://exercism.org/docs/building/markdown/style-guide for guidance." echo "---------------------------------------------------" - echo "This is what npm list reports:" - echo "$(npm list prettier)" + echo "$(corepack pnpm -v)" + echo "" + echo "This is what corepack pnpm list reports:" + echo "$ corepack pnpm list prettier --parseable" + echo "$(corepack pnpm list prettier --parseable)" + echo "" + echo "And corepack pnpm info reports the following:" + echo "$ corepack pnpm info prettier" + echo "$(corepack pnpm info prettier)" + echo "" + echo "This is the version that can be extracted using sed:" + echo "$ corepack pnpm list prettier --parseable | sed -n -e '1,\$s/^.*prettier@//' -e 's/\\node_modules\\prettier//p'" + echo "└─ $(corepack pnpm list prettier --parseable | sed -n -e '1,$s/^.*prettier@//' -e 's/\\node_modules\\prettier//p')" echo "" - echo "This is the version that can be extracted:" - echo "$(npm list prettier | grep -Po '.*\sprettier@\K.*')" + echo "This is the version that can be extracted using grep:" + echo "$ cat pnpm-lock.yaml | grep -Po ' prettier@\K[^:]+'" + echo "└─ $(cat pnpm-lock.yaml | grep -Po ' prettier@\K[^:]+')" echo "" echo "These files are found in the repo root:" echo "$(ls -p | grep -v /)" echo "---------------------------------------------------" exit 1 else - echo "Running format with prettier@$EXERCISM_PRETTIER_VERSION" + echo "[format] running with prettier@$EXERCISM_PRETTIER_VERSION" fi -npx "prettier@$EXERCISM_PRETTIER_VERSION" --check "**/*.{js,jsx,ts,tsx,css,sass,scss,html,json,md,yml}" +corepack pnpm dlx "prettier@$EXERCISM_PRETTIER_VERSION" --check "**/*.{js,jsx,cjs,mjs,ts,tsx,css,sass,scss,html,json,md,yml}" diff --git a/bin/check_corejs_version.sh b/bin/check_corejs_version.sh deleted file mode 100755 index 8b748d76ce..0000000000 --- a/bin/check_corejs_version.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash - -babel_version="$(sed -n "s/.*corejs: '\([0-9]\+\.[0-9]\+\).*/\1/p" babel.config.js)" -package_version="$(sed -n 's/.*"core-js": "^\?\([0-9]\+\.[0-9]\+\).*/\1/p' package.json)" - -if [ ${babel_version} != ${package_version} ]; then - echo "core-js version (major.minor) in package.json does not match the version in babel.config.js!" - echo "babel.config.js version: ${babel_version}" - echo "package.json version: ${package_version}" - exit 1 -fi diff --git a/bin/fetch-configlet b/bin/fetch-configlet index 43f1c83cee..6bef43ab72 100755 --- a/bin/fetch-configlet +++ b/bin/fetch-configlet @@ -1,29 +1,10 @@ #!/usr/bin/env bash -set -eo pipefail +# This file is a copy of the +# https://github.com/exercism/configlet/blob/main/scripts/fetch-configlet file. +# Please submit bugfixes/improvements to the above file to ensure that all tracks benefit from the changes. -readonly LATEST='https://api.github.com/repos/exercism/configlet/releases/latest' - -case "$(uname)" in - Darwin*) os='mac' ;; - Linux*) os='linux' ;; - Windows*) os='windows' ;; - MINGW*) os='windows' ;; - MSYS_NT-*) os='windows' ;; - *) os='linux' ;; -esac - -case "${os}" in - windows*) ext='zip' ;; - *) ext='tgz' ;; -esac - -case "$(uname -m)" in - *64*) arch='64bit' ;; - *686*) arch='32bit' ;; - *386*) arch='32bit' ;; - *) arch='64bit' ;; -esac +set -eo pipefail curlopts=( --silent @@ -37,22 +18,74 @@ if [[ -n "${GITHUB_TOKEN}" ]]; then curlopts+=(--header "authorization: Bearer ${GITHUB_TOKEN}") fi -suffix="${os}-${arch}.${ext}" - get_download_url() { - curl "${curlopts[@]}" --header 'Accept: application/vnd.github.v3+json' "${LATEST}" | + local os="$1" + local ext="$2" + local latest='https://api.github.com/repos/exercism/configlet/releases/latest' + local arch + case "$(uname -m)" in + aarch64|arm64) arch='arm64' ;; + x86_64) arch='x86-64' ;; + *686*) arch='i386' ;; + *386*) arch='i386' ;; + *) arch='x86-64' ;; + esac + local suffix="${os}_${arch}.${ext}" + curl "${curlopts[@]}" --header 'Accept: application/vnd.github.v3+json' "${latest}" | grep "\"browser_download_url\": \".*/download/.*/configlet.*${suffix}\"$" | cut -d'"' -f4 } -download_url="$(get_download_url)" -output_dir="bin" -output_path="${output_dir}/latest-configlet.${ext}" -curl "${curlopts[@]}" --output "${output_path}" "${download_url}" +main() { + local output_dir + if [[ -d ./bin ]]; then + output_dir="./bin" + elif [[ $PWD == */bin ]]; then + output_dir="$PWD" + else + echo "Error: no ./bin directory found. This script should be ran from a repo root." >&2 + return 1 + fi + + local os + case "$(uname -s)" in + Darwin*) os='macos' ;; + Linux*) os='linux' ;; + Windows*) os='windows' ;; + MINGW*) os='windows' ;; + MSYS_NT-*) os='windows' ;; + *) os='linux' ;; + esac + + local ext + case "${os}" in + windows) ext='zip' ;; + *) ext='tar.gz' ;; + esac -case "${ext}" in - *zip) unzip "${output_path}" -d "${output_dir}" ;; - *) tar xzf "${output_path}" -C "${output_dir}" ;; -esac + echo "Fetching configlet..." >&2 + local download_url + download_url="$(get_download_url "${os}" "${ext}")" + local output_path="${output_dir}/latest-configlet.${ext}" + curl "${curlopts[@]}" --output "${output_path}" "${download_url}" + + case "${ext}" in + zip) unzip "${output_path}" -d "${output_dir}" ;; + *) tar xzf "${output_path}" -C "${output_dir}" ;; + esac + + rm -f "${output_path}" + + local executable_ext + case "${os}" in + windows) executable_ext='.exe' ;; + *) executable_ext='' ;; + esac + + local configlet_path="${output_dir}/configlet${executable_ext}" + local configlet_version + configlet_version="$(${configlet_path} --version)" + echo "Downloaded configlet ${configlet_version} to ${configlet_path}" +} -rm -f "${output_path}" +main diff --git a/bin/fetch-configlet.ps1 b/bin/fetch-configlet.ps1 index 291e57eb22..a7896b2251 100755 --- a/bin/fetch-configlet.ps1 +++ b/bin/fetch-configlet.ps1 @@ -1,26 +1,42 @@ +# This file is a copy of the +# https://github.com/exercism/configlet/blob/main/scripts/fetch-configlet.ps1 file. +# Please submit bugfixes/improvements to the above file to ensure that all tracks +# benefit from the changes. + $ErrorActionPreference = "Stop" $ProgressPreference = "SilentlyContinue" $requestOpts = @{ - Headers = If ($env:GITHUB_TOKEN) { @{ Authorization = "Bearer ${env:GITHUB_TOKEN}" } } Else { @{ } } + Headers = If ($env:GITHUB_TOKEN) { @{ Authorization = "Bearer ${env:GITHUB_TOKEN}" } } Else { @{ } } MaximumRetryCount = 3 - RetryIntervalSec = 1 + RetryIntervalSec = 1 } -$arch = If ([Environment]::Is64BitOperatingSystem) { "64bit" } Else { "32bit" } -$fileName = "configlet-windows-$arch.zip" - Function Get-DownloadUrl { + $arch = If ([Environment]::Is64BitOperatingSystem) { "x86-64" } Else { "i386" } $latestUrl = "https://api.github.com/repos/exercism/configlet/releases/latest" - Invoke-RestMethod -Uri $latestUrl -PreserveAuthorizationOnRedirect @requestOpts - | Select-Object -ExpandProperty assets - | Where-Object { $_.browser_download_url -match $FileName } - | Select-Object -ExpandProperty browser_download_url + Invoke-RestMethod -Uri $latestUrl -PreserveAuthorizationOnRedirect @requestOpts ` + | Select-Object -ExpandProperty assets ` + | Where-Object { $_.name -match "^configlet_.+_windows_${arch}.zip$" } ` + | Select-Object -ExpandProperty browser_download_url -First 1 } -$downloadUrl = Get-DownloadUrl $outputDirectory = "bin" -$outputFile = Join-Path -Path $outputDirectory -ChildPath $fileName -Invoke-WebRequest -Uri $downloadUrl -OutFile $outputFile @requestOpts -Expand-Archive $outputFile -DestinationPath $outputDirectory -Force -Remove-Item -Path $outputFile +if (!(Test-Path -Path $outputDirectory)) { + Write-Output "Error: no ./bin directory found. This script should be ran from a repo root." + exit 1 +} + +Write-Output "Fetching configlet..." +$downloadUrl = Get-DownloadUrl +$outputFileName = "configlet.zip" +$outputPath = Join-Path -Path $outputDirectory -ChildPath $outputFileName +Invoke-WebRequest -Uri $downloadUrl -OutFile $outputPath @requestOpts + +$configletPath = Join-Path -Path $outputDirectory -ChildPath "configlet.exe" +if (Test-Path -Path $configletPath) { Remove-Item -Path $configletPath } +[System.IO.Compression.ZipFile]::ExtractToDirectory($outputPath, $outputDirectory) +Remove-Item -Path $outputPath + +$configletVersion = (Select-String -Pattern "/releases/download/(.+?)/" -InputObject $downloadUrl -AllMatches).Matches.Groups[1].Value +Write-Output "Downloaded configlet ${configletVersion} to ${configletPath}" diff --git a/bin/format.sh b/bin/format.sh index 0ac8ce94de..162438126a 100755 --- a/bin/format.sh +++ b/bin/format.sh @@ -1,27 +1,50 @@ #!/usr/bin/env bash -if [ -z "$EXERCISM_PRETTIER_VERSION" ]; then - echo "Pulling prettier version from package.json" - EXERCISM_PRETTIER_VERSION=$(npm list prettier | grep -Po '.*\sprettier@\K.*') +set -uo pipefail + +if [ -z "${EXERCISM_PRETTIER_VERSION:-}" ]; then + echo "[format] pulling prettier version from pnpm list using sed" + EXERCISM_PRETTIER_VERSION="$(corepack pnpm list prettier --parseable | sed -n -e '1,$s/^.*prettier@//' -e 's/\\node_modules\\prettier//p')" + echo "[format] expected version is now ${EXERCISM_PRETTIER_VERSION:-}" +fi + +if [ -z "${EXERCISM_PRETTIER_VERSION:-}" ]; then + echo "[format] pulling prettier version via pnpm-lock.yaml using grep" + EXERCISM_PRETTIER_VERSION="$(cat pnpm-lock.yaml | grep -Po ' prettier@\K[^:]+')" + echo "[format] expected version is now ${EXERCISM_PRETTIER_VERSION:-}" fi -if [ -z "$EXERCISM_PRETTIER_VERSION" ]; then +if [ -z "${EXERCISM_PRETTIER_VERSION:-}" ]; then + echo "Version could not be pulled using sed or grep" >&2 + echo "" echo "---------------------------------------------------" echo "This script requires the EXERCISM_PRETTIER_VERSION variable to work." echo "Please see https://exercism.org/docs/building/markdown/style-guide for guidance." echo "---------------------------------------------------" - echo "This is what npm list reports:" - echo "$(npm list prettier)" + echo "$(corepack pnpm -v)" + echo "" + echo "This is what corepack pnpm list reports:" + echo "$ corepack pnpm list prettier --parseable" + echo "$(corepack pnpm list prettier --parseable)" + echo "" + echo "And corepack pnpm info reports the following:" + echo "$ corepack pnpm info prettier" + echo "$(corepack pnpm info prettier)" + echo "" + echo "This is the version that can be extracted using sed:" + echo "$ corepack pnpm list prettier --parseable | sed -n -e '1,\$s/^.*prettier@//' -e 's/\\node_modules\\prettier//p'" + echo "└─ $(corepack pnpm list prettier --parseable | sed -n -e '1,$s/^.*prettier@//' -e 's/\\node_modules\\prettier//p')" echo "" - echo "This is the version that can be extracted:" - echo "$(npm list prettier | grep -Po '.*\sprettier@\K.*')" + echo "This is the version that can be extracted using grep:" + echo "$ cat pnpm-lock.yaml | grep -Po ' prettier@\K[^:]+'" + echo "└─ $(cat pnpm-lock.yaml | grep -Po ' prettier@\K[^:]+')" echo "" echo "These files are found in the repo root:" echo "$(ls -p | grep -v /)" echo "---------------------------------------------------" exit 1 else - echo "Running format with prettier@$EXERCISM_PRETTIER_VERSION" + echo "[format] running with prettier@$EXERCISM_PRETTIER_VERSION" fi -npx "prettier@$EXERCISM_PRETTIER_VERSION" --write "**/*.{js,jsx,ts,tsx,css,sass,scss,html,json,md,yml}" +corepack pnpm dlx "prettier@$EXERCISM_PRETTIER_VERSION" --write "**/*.{js,jsx,cjs,mjs,ts,tsx,css,sass,scss,html,json,md,yml}" diff --git a/bin/generate-config-tree b/bin/generate-config-tree index 9a08db2e3a..e4c4bae844 100755 --- a/bin/generate-config-tree +++ b/bin/generate-config-tree @@ -1,35 +1,35 @@ #!/usr/bin/env node -const { exercises } = require('../config.json') -const TAG_CORE = '__core' -const TAG_BONUS = '__bonus' +const { exercises } = require('../config.json'); +const TAG_CORE = '__core'; +const TAG_BONUS = '__bonus'; // node inter-opt exports -exports.TAG_CORE = TAG_CORE -exports.TAG_BONUS = TAG_BONUS +exports.TAG_CORE = TAG_CORE; +exports.TAG_BONUS = TAG_BONUS; exports.tree = exercises.reduce((result, exercise) => { - const tag = exercise.slug + const tag = exercise.slug; const item = { slug: tag, difficulty: exercise.difficulty, - } + }; if (exercise.core) { - const current = result[TAG_CORE] || [] + const current = result[TAG_CORE] || []; if (result[tag]) { - console.warn(`${tag} is not ordered correctly in config.json`) + console.warn(`${tag} is not ordered correctly in config.json`); } return { ...result, __core: current.concat([item]), - [tag]: result[tag] || [] - } + [tag]: result[tag] || [], + }; } - const parent = exercise.unlocked_by || TAG_BONUS - const current = result[parent] || [] - return { ...result, [parent]: current.concat([item]) } -}, {}) + const parent = exercise.unlocked_by || TAG_BONUS; + const current = result[parent] || []; + return { ...result, [parent]: current.concat([item]) }; +}, {}); diff --git a/bin/lint.sh b/bin/lint.sh new file mode 100644 index 0000000000..2ddf019d16 --- /dev/null +++ b/bin/lint.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set -euo pipefail + +corepack enable pnpm +corepack pnpm lint diff --git a/bin/print-config-tree b/bin/print-config-tree index 337c2c09fd..b4061b19bc 100755 --- a/bin/print-config-tree +++ b/bin/print-config-tree @@ -1,34 +1,34 @@ #!/usr/bin/env node -const actions = require('./generate-config-tree') +const actions = require('./generate-config-tree'); -const { tree, TAG_BONUS, TAG_CORE } = actions -const { [TAG_BONUS]: __bonus, [TAG_CORE]: __core, ...track } = tree +const { tree, TAG_BONUS, TAG_CORE } = actions; +const { [TAG_BONUS]: __bonus, [TAG_CORE]: __core, ...track } = tree; function printLn(line) { - process.stdout.write(`${line}\n`) + process.stdout.write(`${line}\n`); } function printList(items) { - items.forEach(item => { - printLn(`- ${item.slug} (${item.difficulty})`) - }) + items.forEach((item) => { + printLn(`- ${item.slug} (${item.difficulty})`); + }); } -printLn('Core (matches config.json) of this track:') -printList(__core) -printLn('\n') -printLn('core') -printLn('----') -Object.keys(track).forEach(slug => { - printLn(`├─ ${slug}`) +printLn('Core (matches config.json) of this track:'); +printList(__core); +printLn('\n'); +printLn('core'); +printLn('----'); +Object.keys(track).forEach((slug) => { + printLn(`├─ ${slug}`); track[slug].forEach((side, index, self) => { - junction = index === self.length - 1 ? '└─' : '├─' - printLn(`│ ${junction} ${side.slug} (${side.difficulty})`) - }) - printLn('│') -}) + junction = index === self.length - 1 ? '└─' : '├─'; + printLn(`│ ${junction} ${side.slug} (${side.difficulty})`); + }); + printLn('│'); +}); -printLn('bonus') -printLn('----') -printList(__bonus) +printLn('bonus'); +printLn('----'); +printList(__bonus); diff --git a/concepts/arithmetic-operators/about.md b/concepts/arithmetic-operators/about.md index 93d16ca627..9bb4e8cb43 100644 --- a/concepts/arithmetic-operators/about.md +++ b/concepts/arithmetic-operators/about.md @@ -15,7 +15,7 @@ JavaScript provides 6 different operators to perform basic arithmetic operations ```javascript 19 - 2; // => 17 - 7.4 - 1.2; // => 1.5 + 7.4 - 1.2; // => 6.2 ``` - `*`: The multiplication operator is used to find the product of two numbers diff --git a/concepts/array-analysis/introduction.md b/concepts/array-analysis/introduction.md index ac05aa7a55..23eaee54be 100644 --- a/concepts/array-analysis/introduction.md +++ b/concepts/array-analysis/introduction.md @@ -83,9 +83,13 @@ numbers.findIndex((num) => num > 9); ``` [^1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes + [^2]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every + [^3]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some + [^4]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find + [^5]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex [predicate_in_programming]: https://derk-jan.com/2020/05/predicate/ diff --git a/concepts/array-destructuring/about.md b/concepts/array-destructuring/about.md index 4a550871ca..481e84157a 100644 --- a/concepts/array-destructuring/about.md +++ b/concepts/array-destructuring/about.md @@ -1,6 +1,6 @@ # About -Array [destructuring assignment][array_destructuring_docs] is a concise way of extracting values from an array. Its syntax is similar to an [array literal][array_literal_resource] expression, but on the left-hand side of the assignment instead of the right. +Array [destructuring assignment][mdn-array-destructuring] is a concise way of extracting values from an array. Its syntax is similar to an [array literal][mdn-array-literal] expression, but on the left-hand side of the assignment instead of the right. ```javascript const frenchNumbers = ['quatre-vingts', 'quatre-vingt-dix', 'cent']; @@ -14,6 +14,8 @@ french100; // => 'cent' ``` +## Re-assignment + Because variables are mapped to values in the array by position, destructuring syntax can be used to assign or re-assign multiple variables in a single expression. ```javascript @@ -40,6 +42,8 @@ c; // => 'purple' ``` +## Skipping assignment + The syntax allows skipping values when mapping, for example to ignore specific positions in the array. In the example below, imagine we have a `getUserInfo` function that returns an array containing a user's first name, last name, and street address. @@ -53,6 +57,8 @@ streetAddress; // => "Sunny Lane 523" ``` +## Dropping values + The assignment is also not required to use all the values. ```javascript @@ -65,6 +71,8 @@ lastName; // => "Noir" ``` +## Taking more values than available + It's even possible to extract _more_ values than the array contains; the leftover variables will be assigned `undefined`. This may be useful when the amount of values isn't known ahead of time. ```javascript @@ -84,6 +92,8 @@ fourth; // => undefined ``` +## Default values + The array destructuring assignment can provide _default values_ in case there is none in the source array. ```javascript @@ -96,4 +106,10 @@ fourth; // => undefined ``` +## Related concepts + +[concept:javascript/object-destructuring]() [concept:javascript/rest-and-spread]() + +[mdn-array-destructuring]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment +[mdn-array-literal]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array#Creating_an_array diff --git a/concepts/array-loops/about.md b/concepts/array-loops/about.md index 0795294d92..cc859cb0e9 100644 --- a/concepts/array-loops/about.md +++ b/concepts/array-loops/about.md @@ -23,7 +23,7 @@ for (let i = 0; i < numbers.length; i++) { ## The `for...of` Loop -When you want to work with the value directly in each iteration and do not require the index at all, you can use a `for .. of` loop. +When you want to work with the value directly in each iteration and do not require the index at all, you can use a `for...of` loop. `for...of` works like the basic `for` loop shown above, but instead of having to deal with the _index_ as a variable in the loop, you are provided with the _value_ directly. diff --git a/concepts/array-loops/introduction.md b/concepts/array-loops/introduction.md index a2094288b6..4e5ba06635 100644 --- a/concepts/array-loops/introduction.md +++ b/concepts/array-loops/introduction.md @@ -23,7 +23,7 @@ for (let i = 0; i < numbers.length; i++) { ## The `for...of` Loop -When you want to work with the value directly in each iteration and do not require the index at all, you can use a `for .. of` loop. +When you want to work with the value directly in each iteration and do not require the index at all, you can use a `for...of` loop. `for...of` works like the basic `for` loop shown above, but instead of having to deal with the _index_ as a variable in the loop, you are provided with the _value_ directly. diff --git a/concepts/array-transformations/about.md b/concepts/array-transformations/about.md index 814fb8d72b..d525810b55 100644 --- a/concepts/array-transformations/about.md +++ b/concepts/array-transformations/about.md @@ -61,7 +61,7 @@ arr.reduce( return accumulator; }, - { even: [], odd: [] } + { even: [], odd: [] }, ); // => { even: [2, 4], odd: [1, 3] } ``` @@ -81,7 +81,7 @@ This method modifies the array it is called on. ### slice (pure) -Given a start and an end index, creates a sub-array from the array passed as a parameter. +Given a start and an end index, creates a sub-array from the array it is called on. The element at the end index will not be included. Also, all parameters are optional: @@ -144,7 +144,8 @@ console.log(arr); // => ['a', 'b', 'c', 'z'] ``` -````exercism/caution + +~~~~exercism/caution This default behavior leads to wrong results when you try to sort numbers. ```javascript @@ -153,7 +154,8 @@ arr.sort(); // => [1, 10, 2, 3] // Because the string '10' comes before '2' in dictionary order. ``` -```` +~~~~ + To customize the sorting behavior, you can pass a comparison function as an argument. The comparison function itself is called with two arguments which are two elements of the array. diff --git a/concepts/array-transformations/introduction.md b/concepts/array-transformations/introduction.md index ad879d6443..ab64b7628d 100644 --- a/concepts/array-transformations/introduction.md +++ b/concepts/array-transformations/introduction.md @@ -61,7 +61,7 @@ arr.reduce( return accumulator; }, - { even: [], odd: [] } + { even: [], odd: [] }, ); // => { even: [2, 4], odd: [1, 3] } ``` @@ -81,7 +81,7 @@ This method modifies the array it is called on. ### slice (pure) -Given a start and an end index, creates a sub-array from the array passed as a parameter. +Given a start and an end index, creates a sub-array from the array it is called on. The element at the end index will not be included. Also, all parameters are optional: diff --git a/concepts/arrays/about.md b/concepts/arrays/about.md index f83bee5cce..9efe4e1839 100644 --- a/concepts/arrays/about.md +++ b/concepts/arrays/about.md @@ -31,7 +31,7 @@ The array's object properties and list of array elements are separate, and the a ```javascript const names = ['Jack', 'Laura', 'Paul', 'Megan']; names.length; -// => 3 +// => 4 // Properties can be set on arrays using bracket ['property'] or // dot .property notation, and this will affect the length, as @@ -181,7 +181,7 @@ names; const names = ['Jack', 'Laura', 'Paul', 'Megan']; names.shift(); // => 'Jack' names; -// => ['two', 3, 'four'] +// => ['Laura', 'Paul', 'Megan'] ``` ### unshift @@ -202,7 +202,7 @@ names; ```javascript const names = ['Jack', 'Laura', 'Paul', 'Megan']; -names.splice(2, 1, 'Jill'); +names.splice(2, 1, 'Jill'); // => ['Paul'] names; // => ['Jack', 'Laura', 'Jill', 'Megan'] ``` @@ -210,9 +210,13 @@ names; --- [^1]: `push`, MDN. (referenced September 29, 2021) + [^2]: `pop`, MDN. (referenced September 29, 2021) + [^3]: `shift`, MDN. (referenced September 29, 2021) + [^4]: `unshift`, MDN. (referenced September 29, 2021) + [^5]: `splice`, MDN. (referenced September 29, 2021) [array-docs]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array#Instance_methods diff --git a/concepts/arrays/introduction.md b/concepts/arrays/introduction.md index 72de86a73d..8985e53b61 100644 --- a/concepts/arrays/introduction.md +++ b/concepts/arrays/introduction.md @@ -87,7 +87,7 @@ numbers; ```javascript const numbers = [1, 'two', 3, 'four']; -numbers.splice(2, 1, 'one'); +numbers.splice(2, 1, 'one'); // => [3] numbers; // => [1, 'two', 'one', 'four'] ``` @@ -95,9 +95,13 @@ numbers; --- [^1]: `push`, MDN. (referenced September 29, 2021) + [^2]: `pop`, MDN. (referenced September 29, 2021) + [^3]: `shift`, MDN. (referenced September 29, 2021) + [^4]: `unshift`, MDN. (referenced September 29, 2021) + [^5]: `splice`, MDN. (referenced September 29, 2021) [array_methods]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array diff --git a/concepts/basics/about.md b/concepts/basics/about.md index 10c4496c76..e41c914c67 100644 --- a/concepts/basics/about.md +++ b/concepts/basics/about.md @@ -6,7 +6,7 @@ While it is most well-known as the scripting language for Web pages, many non-br ## (Re-)Assignment -There are a few primary ways to assign values to names in JavaScript - using variables or constants. On Exercism, variables are always written in [camelCase][wiki-camel-case]; constants are written in [SCREAMING_SNAKE_CASE][wiki-snake-case]. There is no official guide to follow, and various companies and organizations have various style guides. _Feel free to write variables any way you like_. The upside from writing them the way the exercises are prepared is that they'll be highlighted differently in the web interface and most IDEs. +There are a few primary ways to assign values to names in JavaScript - using variables or constants. On Exercism, variables are always written in [camelCase][wiki-camel-case]; constants are written in [SCREAMING_SNAKE_CASE][wiki-snake-case]. There is no official guide to follow, and various companies and organizations have various style guides. _Feel free to write variables any way you like_. The upside to writing them the way the exercises are prepared is that they'll be highlighted differently in the web interface and most IDEs. Variables in JavaScript can be defined using the [`const`][mdn-const], [`let`][mdn-let] or [`var`][mdn-var] keyword. diff --git a/concepts/callbacks/about.md b/concepts/callbacks/about.md index b5e0cc86c1..1899983a55 100644 --- a/concepts/callbacks/about.md +++ b/concepts/callbacks/about.md @@ -1,68 +1,54 @@ # About -[_Callbacks_ describe the pattern][wiki-callbacks] where a function receives a function as an argument to invoke when it arrives at a condition. The condition may be that its work is done, or that an event has occurred, or that some predicate passes. It can be synchronous; it can be asynchronous. +[_Callback_ functions][wiki-callbacks] are functions passed as arguments to other functions. The callback function may then be invoked to trigger a subsequent action. Often, _callbacks_ are used to handle the results of work done, or handle an action when an event occurs. _Callback_ functions can be used in synchronous and asynchronous programming. -This is a useful pattern in JavaScript because it is designed as a single-threaded runtime where only one function call can be executed at a time. During execution, the runtime cannot respond to other events or continue execution until the function has returned. You might have noticed this on websites when they seem to "freeze" or become unresponsive. - -But many API calls (often I/O functions, timers, and event listeners) use an asynchronous mechanism to place the [current function call][mdn-concurrency-stack] on the side until the work is complete. Upon completion, a callback function is placed on the [message queue][mdn-concurrency-queue] so that when the runtime is in between function calls, the messages are then processed by invoking the callback function. - -It is also useful when the `callback` (the argument passed in) may not be defined (created) at the calling site. In other words: it may have been passed down from a different place in the program. - -If the `callback` function _returns_ a boolean or boolean-like value, which is intended to be used (as opposed to a throwaway return value), it's called a predicate. - -## Synchronous Code +```javascript +const sideLength = 5; -A synchronous call is when one function is executed after the other. The order is fixed. +// Caller function takes a callback function +function applySideLength(callback) { + return callback(sideLength); +} -```javascript -function triangleArea(height, base) { - return (height * base) / 2; +// Callback must expect the possible argument from the calling function +function areaOfSquare(side) { + return side * side; } -triangleArea(2, 10); // => 10 -triangleArea(40, 3); // => 60 +applySideLength(areaOfSquare); // => 25 ``` -## Asynchronous Code - -When an asynchronous function is invoked, there is no way to know for certain when it will finish its work. This means there is no value to act on when the function returns to the caller. +You may also write callbacks as a function expression: ```javascript -// This is broken, it may or may not return your value in time to be used -let area = asynchronousTriangleArea(4, 7); -console.log(area); +applySideLength(function squarePerimeter(side) { + return side * 4; +}); ``` -So we can use callbacks to control the order of execution: +This is a useful pattern in JavaScript because JavaScript is designed as a single-threaded runtime where only one function call can be executed at a time. During execution, the runtime cannot respond to other events or continue execution until the function has returned. -```javascript -function areaCallback(area) { - console.log(area); -} +Many API calls (I/O functions, timers, and event listeners) use an asynchronous mechanism to place the [current function call][mdn-concurrency-stack] on the side until the work is complete. Upon completion, a callback function is placed on the [message queue][mdn-concurrency-queue] so that when the runtime is in between function calls, the messages are then processed by invoking the callback function. -function asynchronousTriangleArea(height, base, areaCallback) { - areaCallback((height * base) / 2); -} +It is also useful to use _callback functions_ because they may reference variables in its closure scope which are unavailable to the function where it is later invoked. -// This outputs the area of the triangle to the console as expected. -asynchronousCallback(4, 7, areaCallback); -``` - -## Specific callback forms +## Specific examples of callback functions -### Browser Events +### Event Handlers -_Event handlers_ are a common use case for callbacks in JavaScript. This often takes the form of browser events like `'onload'` or `'onclick'`, where a DOM object's `addEventListener` method then registers a callback to be invoked when a specified event occurs. +_Event handlers_ are a common use-case for callbacks in JavaScript. Browser events like `'onload'` or `'onclick'` are signals which can trigger functions to be invoked. A DOM [[Document Object Model](mdn-dom) object's `addEventListener` method registers a callback function to be invoked when it "hears" that an event has occurred. ```javascript -document.addEventListener('onload' () => alert('The webpage has now been loaded')) +document.addEventListener('onload', function () { + alert('The webpage has now been loaded'); +}); ``` -### Node.js Error Convention +### Node.js Convention -In [Node.js][nodejs], [callbacks][node-callbacks] often follow a [similar convention][node-error-convention] for their arguments: The first argument receives an `Error` object or `null` if no error occurred, and the second and subsequent receive the data that the calling function is designed to send. +In [Node.js][nodejs], [callback functions][node-callbacks] follow a [convention][node-error-convention] to control the flow of a program. They follow this pattern: the first argument of the callback function may receive an `Error` or `null`; The second and subsequent arguments receive the data that the calling function is designed to send. -If an error occurs, the second and subsequent arguments may not be present, so don't depend on them. +If an error occurs, the second and subsequent arguments may not be present, so you may not depend on them to be present. ```javascript function operation(a, b, callback) { @@ -78,7 +64,7 @@ function operation(a, b, callback) { function callback(error, returnedData) { if (error) { - // An error occured, handle it here. + // An error occurred, handle it here. return } @@ -88,11 +74,46 @@ function callback(error, returnedData) { operation(1, 2, callback) ``` -You see this pattern often when dealing with asynchronous functions. +You see this pattern often when dealing with asynchronous functions to assist with control flow. + +### Callbacks in disguise + +Common `Array` functions use callback functions to define their behaviour: + +- `Array.prototype.forEach`: + - Accepts a callback, which applies the callback to each element of an array. + + ```javascript + [1, 2, 3].forEach(function (element) { + doSomething(element); + }); + // => doSomething() is invoked 3 times, once with each element + ``` + +- `Array.prototype.map` + - Accepts a callback, which applies the callback to each element of an array using the result to create a new array. + + ```javascript + [1, 2, 3].map(function (element) { + return element + 1; + }); + // => [2, 3, 4] + ``` + +- `Array.prototype.reduce` + - Accepts a callback, which applies the callback to each element of an array, passing the result forward to the next invocation. + + ```javascript + [1, 2, 3].reduce(function (runningSum, element) { + return runningSum + element; + }, 0); + // => 6 + ``` [mdn-callbacks]: https://developer.mozilla.org/en-US/docs/Glossary/Callback_function [mdn-concurrency-stack]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#stack [mdn-concurrency-queue]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#queue +[mdn-dom]: https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model [nodejs]: https://www.nodejs.org [node-callbacks]: https://nodejs.org/en/knowledge/getting-started/control-flow/what-are-callbacks/ [node-error-convention]: https://nodejs.org/en/knowledge/errors/what-are-the-error-conventions/ diff --git a/concepts/callbacks/introduction.md b/concepts/callbacks/introduction.md index 7c42899606..93b16b69da 100644 --- a/concepts/callbacks/introduction.md +++ b/concepts/callbacks/introduction.md @@ -1,35 +1,29 @@ # Introduction -Callbacks are functions that are passed as arguments to another function. This is often done to control the order of execution in an asynchronous context. Writing a callback function is no different from writing a function, but the callback function's arguments must match the signature required by the calling function. +## Callback functions + +Callback functions are functions passed as arguments. This programming pattern creates a sequence of function calls in both synchronous and asynchronous programming. Writing a callback function is no different from writing a function; however, the callback function must match the signature defined by the calling function. ```javascript -const squareLength = 5; +const sideLength = 5; // Caller function takes a callback function -function applyToSquare(callback) { - callback(squareLength); +function applySideLength(callback) { + return callback(sideLength); } // Callback must expect the possible argument from the calling function -function areaOfSquare(number) { - return number * number; +function areaOfSquare(side) { + return side * side; } -applyToSquare(areaOfSquare); // => 25 +applySideLength(areaOfSquare); // => 25 ``` You may also write callbacks as a function expression: ```javascript -applyToSquare(function squarePerimeter(side) { +applySideLength(function squarePerimeterLength(side) { return side * 4; }); ``` - -Or an anonymous inline arrow function expression: - -```javascript -applyToSquare((side) => side * 4); -``` - -// The argument "(side) => side \* 4" is the callback diff --git a/concepts/classes/about.md b/concepts/classes/about.md index 3ecb78ef62..de48951a13 100644 --- a/concepts/classes/about.md +++ b/concepts/classes/about.md @@ -36,14 +36,16 @@ Before that, it was accessible via the key `__proto__` in many environments. Do not confuse the prototype of an object (`[[prototype]]`) with the `prototype` property of the constructor function. -```exercism/note + +~~~~exercism/note To summarize: - Constructors in JavaScript are regular functions. - Constructing a new instance creates an object with a relation to its constructor called its _prototype_. - Functions are objects (callable objects) and therefore they can have properties. - The constructor's (function) `prototype` property will become the instance's _prototype_. -``` +~~~~ + ### Instance Fields @@ -124,11 +126,13 @@ The `[[prototype]]` of `Object` is usually `null` so the prototype chain ends th In conclusion, you can call `myCar.toString()` and that method will exist because JavaScript searches for that method throughout the whole prototype chain. You can find a detailed example in the [MDN article "Inheritance and the prototype chain"][mdn-prototype-chain-example]. -```exercism/caution + +~~~~exercism/caution Note that the prototype chain is only travelled when retrieving a value. Setting a property directly or deleting a property of an instance object only targets that specific instance. This might not be what you would expect when you are used to a language with class-based inheritance. -``` +~~~~ + ### Dynamic Methods (Adding Methods to All Existing Instances) @@ -221,24 +225,24 @@ With the keywords `get` and `set` you can define functions that are executed whe ```javascript class Car { constructor() { - this._milage = 0; + this._mileage = 0; } - get milage() { - return this._milage; + get mileage() { + return this._mileage; } - set milage(value) { - throw new Error(`Milage cannot be manipulated, ${value} is ignored.`); + set mileage(value) { + throw new Error(`Mileage cannot be manipulated, ${value} is ignored.`); // Just an example, usually you would not provide a setter in this case. } } const myCar = new Car(); -myCar.milage; +myCar.mileage; // => 0 -myCar.milage = 100; -// => Error: Milage cannot be manipulated, 100 is ignored. +myCar.mileage = 100; +// => Error: Mileage cannot be manipulated, 100 is ignored. ``` ### Class Fields and Class Methods diff --git a/concepts/classes/introduction.md b/concepts/classes/introduction.md index b834d84154..1f612f79f3 100644 --- a/concepts/classes/introduction.md +++ b/concepts/classes/introduction.md @@ -31,14 +31,16 @@ Every instance object includes a hidden, internal property referred to as `[[pro It holds a reference to the value of the `prototype` key of the constructor function. Yes, you read that correctly, a JavaScript function can have key/value pairs because it is also an object behind the scenes. -```exercism/note + +~~~~exercism/note To summarize: - Constructors in JavaScript are regular functions. - Constructing a new instance creates an object with a relation to its constructor called its _prototype_. - Functions are objects (callable objects) and therefore they can have properties. - The constructor's (function) `prototype` property will become the instance's _prototype_. -``` +~~~~ + ### Instance Fields @@ -117,11 +119,13 @@ The `[[prototype]]` property of `Car.prototype` (`myCar.[[prototype]].[[prototyp It contains general methods that are available for all JavaScript objects, e.g. `toString()`. In conclusion, you can call `myCar.toString()` and that method will exist because JavaScript searches for that method throughout the whole prototype chain. -```exercism/caution + +~~~~exercism/caution Note that the prototype chain is only travelled when retrieving a value. Setting a property directly or deleting a property of an instance object only targets that specific instance. This might not be what you would expect when you are used to a language with class-based inheritance. -``` +~~~~ + ## Class Syntax @@ -180,24 +184,24 @@ With the keywords `get` and `set` you can define functions that are executed whe ```javascript class Car { constructor() { - this._milage = 0; + this._mileage = 0; } - get milage() { - return this._milage; + get mileage() { + return this._mileage; } - set milage(value) { - throw new Error(`Milage cannot be manipulated, ${value} is ignored.`); + set mileage(value) { + throw new Error(`Mileage cannot be manipulated, ${value} is ignored.`); // Just an example, usually you would not provide a setter in this case. } } const myCar = new Car(); -myCar.milage; +myCar.mileage; // => 0 -myCar.milage = 100; -// => Error: Milage cannot be manipulated, 100 is ignored. +myCar.mileage = 100; +// => Error: Mileage cannot be manipulated, 100 is ignored. ``` --- diff --git a/concepts/closures/about.md b/concepts/closures/about.md index 50d26d23a0..23cf280315 100644 --- a/concepts/closures/about.md +++ b/concepts/closures/about.md @@ -18,7 +18,6 @@ The name _closure_ is historically derived from [_λ-calculus_][wiki-lambda-calc ## Reasons to use closures in JavaScript 1. Data Privacy / Data Encapsulation - - Unlike other languages, in 2020, there was no way to specify _private_ variables. So closures can be used to effectively emulate _private_ variables (there was a proposal to introduce private variable notation, which might have become standard by the time you read this). ```javascript @@ -37,7 +36,6 @@ The name _closure_ is historically derived from [_λ-calculus_][wiki-lambda-calc ``` 2. Partial Application - - Functions may return functions, and when a returned function uses the argument of the function that created it, this is an example of using a closure to perform partial application. Sometimes this is called _currying_ a function. ```javascript @@ -73,7 +71,7 @@ In practice, a closure often occurs when a function (or block) uses a variable f ### Function-scope -The `var` keyword defines **function-scoped** variable. This means that variables defined by `var` are available anywhere in the function where they are defined and any nested lexical scope (either **function** or **block**). +The `var` keyword defines a **function-scoped** variable. This means that variables defined by `var` are available anywhere in the function where they are defined and any nested lexical scope (either **function** or **block**). ```javascript { @@ -86,7 +84,7 @@ var tenTimes = five * 10; ### Block-scope -The `let` and `const` keywords define a **block-scoped** variables. `let` defines a mutable variable, whose value maybe set multiple times and may be defined but not initialized. `const` defines a variable which must be initialized when it is defined, and its value then may not change. If the `const` variable is set to a primitive value, it may not be mutated. If the `const` variable is set to a reference value, like an _array_ or _object_, the reference may not change but the array's contents may be mutated. +The `let` and `const` keywords define **block-scoped** variables. `let` defines a mutable variable, whose value may be set multiple times and may be defined but not initialized. `const` defines a variable which must be initialized when it is defined, and its value then may not change. If the `const` variable is set to a primitive value, it may not be mutated. If the `const` variable is set to a reference value, like an _array_ or _object_, the reference may not change but the array's contents may be mutated. Consider this block: diff --git a/concepts/closures/introduction.md b/concepts/closures/introduction.md index c5104af10d..9d8409cb42 100644 --- a/concepts/closures/introduction.md +++ b/concepts/closures/introduction.md @@ -1,6 +1,7 @@ # Introduction -**Closures** are a programming pattern in JavaScript that allows variables from an outer [lexical scope][wiki-lexical-scope] to be used inside of a nested block of code. JavaScript supports closures transparently, and they are often used without knowing what they are. +**Closures** are a programming pattern in JavaScript which allows variables from an outer [lexical scope][wiki-lexical-scope] to be used inside of a nested block of code. +JavaScript supports closures transparently, and they are often used without knowing what they are. ```javascript // Top-level declarations are global-scope @@ -12,16 +13,38 @@ const dozen = 12; const twoDozen = dozen * 2; } +// Because of the block-scope declaration, twoDozen is not available here. +twoDozen; +// => Uncaught ReferenceError: twoDozen is not defined +``` + +Except for braces `{}`, functions (and classes) also create new scopes, which can _enclose_ values: + +```javascript +const dozen = 12; + // Functions create a new function-scope and block-scope. // Referencing the outer variable here is a closure. function nDozen(n) { - return dozen * n; + // This is declared inside the function scope, and uses the top-level scope. + // This works, and encloses the value 12. + const twoDozen = dozen * 2; + + // This only uses the locally declared variable and the passed argument to the parameter `n` + return (twoDozen / 2) * n; } + +// Because of the function-scope declaration, twoDozen is not available here. +twoDozen; +// => Uncaught ReferenceError: twoDozen is not defined ``` +As the `twoDozen` examples show, values can be enclosed in a _nested_ scope (function, block, etc.), but cannot be pulled out of that context. +In the majority of cases, it is intended in Modern JavaScript that a value does not _leak_ to an outside scope. + ## Closures to save state and pass along values -Using a mutable variable declaration (like `let` or `var`) allows for state to be preserved: +Using a mutable variable declaration (like `let` or `var`) allows for some state to be preserved: ```javascript let counter = 0; @@ -32,6 +55,67 @@ export function increment() { counter += 1; return counter; } + +increment(); +// => 1 + +counter; +// => 1 +``` + +## Enclosing values without leaking the state + +Combining the two ideas: enclosing a value to preserve state, and enclosed values do not leak to the outside, it's possible to create private values. + +The most common method is to make a function that returns a function which encloses some state. + +```javascript +export function makeCounter() { + let counter = 0; + + // This returns a new function that encloses the local variable counter + return function increment() { + counter += 1; + return counter; + }; +} + +// Counter did not leak +counter; +// => Uncaught ReferenceError: counter is not defined + +// This creates a new counter. +// This assigns the increment function to the variable myFirstCounter. +const myFirstCounter = makeCounter(); + +typeof myFirstCounter; +// => function + +myFirstCounter.name; +// => increment + +myFirstCounter(); +// => 1 +myFirstCounter(); +// => 2 + +// This creates new counter (with new, separate local state / enclosed counter variable) +const mySecondCounter = makeCounter(); + +mySecondCounter(); +// => 1 + +// It is not affect the first counter. + +myFirstCounter(); +// => 3 +``` + +```exercism/note +Many programmers find closures a hard concept, and returning a function from a function is not common or not even possible in all programming languages. +If you want more reading material, the [guide on MDN on Closures][mdn-closures] is quite comprehensive. + +[mdn-closures]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures ``` [wiki-lexical-scope]: https://en.wikipedia.org/wiki/Scope_(computer_science)#Lexical_scoping diff --git a/concepts/conditionals/about.md b/concepts/conditionals/about.md index a69542aa0f..8b1eea4e21 100644 --- a/concepts/conditionals/about.md +++ b/concepts/conditionals/about.md @@ -67,9 +67,13 @@ if (num >= 0 && num < 1) { // The inner brackets are obsolete because relational operators // have higher precedence than logical operators. -if (num >= 0 && num < 1) { + + +if ((num >= 0) && (num < 1)) { // ... } + + ``` Also, consider using additional variables to make the code more readable. diff --git a/concepts/dates/.meta/config.json b/concepts/dates/.meta/config.json new file mode 100644 index 0000000000..7f3da2f8e4 --- /dev/null +++ b/concepts/dates/.meta/config.json @@ -0,0 +1,5 @@ +{ + "blurb": "JavaScript has a built-in `Date` object which stores date and time and provides methods to work with them.", + "authors": ["SleeplessByte"], + "contributors": [] +} diff --git a/concepts/dates/about.md b/concepts/dates/about.md new file mode 100644 index 0000000000..454f948129 --- /dev/null +++ b/concepts/dates/about.md @@ -0,0 +1,181 @@ +# Introduction + +JavaScript has a built-in object `Date` which stores date and time, and provides methods for their management. + + +~~~exercism/caution +It was based on Java's `java.util.Date` class, which was replaced in the early 2010s, but for backwards compatibility, JavaScript's `Date` sticks around. + +Because of how hard it is to work with Dates in general and because of how bad or non-existing timezone handling is, many libraries exist such as `moment.js`, `day.js`, `date-fns` and `luxon`. +None of these are available on Exercism. + +In your own projects, do not use a deprecated / unmaintained package such as `moment.js` but rely on more modern alternatives like `luxon`, or the not yet widely available [Temporal][mdn-temporal]. +This exercise focusses on `Date`, which will remain relevant until the end of JavaScript. + +[mdn-temporal]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal +~~~ + +## Creation + +A `Date` object in an instance of the `Date` class. +It can be created without passing any arguments to the constructor function. +This results in a `Date` object that represents the current date and time: + +```javascript +const now = new Date(); +// => Thu Apr 14 2022 11:46:08 GMT+0530 (India Standard Time) +// Shows current day, date and time (in your time zone). +``` + +### Unix timestamp (number) + +If a number is passed in, this will be interpreted as a `timestamp`. +A timestamp is an integer number representing the number of **milliseconds** that has passed since **1 January 1970 [UTC][defn-utc]+0**. + +```javascript +const epoch = new Date(0); +// Thu Jan 01 1970 01:00:00 GMT+0100 (Central European Standard Time) + +const another = new Date(1749508766627); +// Tue Jun 10 2025 00:39:26 GMT+0200 (Central European Summer Time) +``` + +One may expect `new Date(0)` to generate the "earliest" date object, but JavaScript will convert the date to your local timezone, which means that only those around [GMT / with an UTC+0][defn-gmt] timezone will actually get the [Unix epoch][defn-unix-epoch] value. + +### ISO 8601 timestamp (string) + +You can pass a string value representing a date to the `Date` constructor. +The **only** format that is consistent across implementations is the [simplified version][mdn-date-string-format] of the internationally recognized and standardized so-called [ISO 8601 timestamp strings][defn-iso8601]. + +A moment in time at [UTC][defn-gmt] looks like this: + +```text +YYYY-MM-DDTHH:mm:ss.mssZ +YYYYMMDDTHHmmss.mssZ +``` + +Where the following substitutions take place: + +| Key | Description | Default | +| ---- | --------------------------------------------- | ------- | +| YYYY | The calendar year, represented in 4 digits | | +| MM | The calendar month, represented in 2 digits | 01 | +| DD | The calendar day, represented in 2 digits | 01 | +| T | A literal letter T, separating date from time | | +| HH | The hours in a 24-hour clock, 2 digits | 00 | +| mm | The minutes, 2 digits | 00 | +| ss | The seconds, 2 digits | 00 | +| mss | The milliseconds, 3 digits | 000 | +| Z | A literal letter Z, or an offset `+/-HH:mm` | | + +The literal letter `Z` indicates UTC (no timezone, no Day Light Savings). + +Because there are default values for most components, leaving parts off at the end is valid: + +```text +YYYY-MM-DD +``` + +Defaults to a time of 00:00:00.000 + +If the timestamp does not end in `Z`, and it does not end with `+HH:mm` or `-HH:mm` (indicating a timezone offset), because of historical reasons, the following applies: + +> When the time zone offset is absent, date-only forms are interpreted as a UTC time and date-time forms are interpreted as a local time. +> The interpretation as a UTC time is due to a historical spec error that was not consistent with ISO 8601 but could not be changed due to web compatibility. +> See [Broken Parser – A Web Reality Issue][ref-broken-parser]. + +### Date object + +An existing date object can also be used as a constructor argument. +This makes a copy of the existing `Date` object with the same date and time. + +```javascript +const t1 = new Date(); +const t2 = new Date(t1); +// Values of t1 and t2 will be the same. +``` + +### Supplying individual date and time component values + +A date representing a date can be created by passing three numbers. +A date representing a date and time can be created by passing in 6 numbers. + +```javascript +const date1 = new Date(95, 11, 17); +// Creates Date for Dec 17 1995 00:00 if your local timezone is equivalent to UTC. + +const date2 = new Date(2013, 12, 5, 13, 24, 0); +// Creates Date for Jan 5 2014 13:24 if your local timezone is equivalent to UTC. +``` + +The second value is the `month`, which starts at `0` for January, up to `11` for December. + +## `Date.parse()` + +You may find mentions of or references to a date parsing function `Date.parse`. +Because there are only a few invariants (truths) for this function and because the rest of the implementation is not specified (and thus not standard), one should not use it. + +## Accessing `Date` components + +There are various methods on date objects that return the components of the date: + +```javascript +getFullYear(); // Get the year (4 digits) +getMonth(); // Get the month, from 0 to 11. +getDate(); // Get the day of month, from 1 to 31. +getHours(); // Get the hour in a 24 clock, from 0 to 23 +getMinutes(); // Get the minutes, from 0 to 59 +getSeconds(); // Get the seconds, from 0 to 59 +getMilliseconds(); // Get the milliseconds, from 0 and 999 +getDay(); // Get the day of week, from 0 (Sunday) to 6 (Saturday). +``` + +Each of these has an applicable `set` variant (e.g. `setFullYear`) to update the value. +Any overflowing value rolls over to the next component: + +```javascript +const date = new Date('2025-02-28T12:42:00Z'); +// => Fri Feb 28 2025 13:42:00 GMT+0100 (Central European Standard Time) + +date.setDate(29); +// there was no February 29th in 2025. + +date.getDate(); +// => 1 + +date.toString(); +// => Sat Mar 01 2025 13:42:00 GMT+0100 (Central European Standard Time) +``` + +There are UTC variants for all the methods that disregard the local timezone. + +## Converting from date + +Date objects have a method `getTime()` that returns the UNIX timestamp in milliseconds, ie. amount of milliseconds the midnight at the beginning of January 1, 1970, UTC. +Additionally, a method `toISOString()` is available to convert from a date object to a ISO 8601 timestamp string. + +## Comparing Dates + +Greater than (`>`) and greater than or equals (`>=`) as well as less than (`<`) and less than or equals (`<=`) can be used directly between two dates or a date and a number. +This works because JavaScript will try to coerce the date to a primitive. + + +~~~exercism/advanced +When doing a comparison between two dates or date and a number, JavaScript calls [`[Symbol.toPrimitive]("number")`][mdn-to-primitive] which internally calls [`date.valueOf()`][mdn-date-value-of]. +The latter is the same as calling [`date.getTime()`][mdn-date-get-time]. + +If you do not want to rely on this behaviour, convert to a number using `getTime()` first. + +[mdn-to-primitive]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Symbol.toPrimitive +[mdn-date-value-of]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/valueOf +[mdn-date-get-time]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTime +~~~ + +Dates cannot be compared using equality (`==`, and `===`), but the result of `.getTime()` can. + +[defn-utc]: https://simple.wikipedia.org/wiki/Coordinated_Universal_Time +[defn-gmt]: https://simple.wikipedia.org/wiki/Greenwich_Mean_Time +[defn-unix-epoch]: https://en.wikipedia.org/wiki/Epoch_%28computing%29 +[defn-iso8601]: https://en.wikipedia.org/wiki/ISO_8601 +[mdn-date-string-format]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format +[ref-broken-parser]: https://maggiepint.com/2017/04/11/fixing-javascript-date-web-compatibility-and-reality/ diff --git a/concepts/dates/introduction.md b/concepts/dates/introduction.md new file mode 100644 index 0000000000..0c307fbe5b --- /dev/null +++ b/concepts/dates/introduction.md @@ -0,0 +1,181 @@ +# Introduction + +JavaScript has a built-in object `Date` which stores date and time, and provides methods for their management. + + +~~~exercism/caution +It was based on Java's `java.util.Date` class, which was replaced in the early 2010s, but for backwards compatibility, JavaScript's `Date` sticks around. + +Because of how hard it is to work with Dates in general and because of how bad or non-existing timezone handling is, many libraries exist such as `moment.js`, `day.js`, `date-fns` and `luxon`. +None of these are available on Exercism. + +In your own projects, do not use a deprecated / unmaintained package such as `moment.js` but rely on more modern alternatives like `luxon`, or the not yet widely available [Temporal][mdn-temporal]. +This exercise focusses on `Date`, which will remain relevant until the end of JavaScript. + +[mdn-temporal]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal +~~~ + +## Creation + +A `Date` object in an instance of the `Date` class. +It can be created without passing any arguments to the constructor function. +This results in a `Date` object that represents the current date and time: + +```javascript +const now = new Date(); +// => Thu Apr 14 2022 11:46:08 GMT+0530 (India Standard Time) +// Shows current day, date and time (in your time zone). +``` + +### Unix timestamp (number) + +If a number is passed in, this will be interpreted as a `timestamp`. +A timestamp is an integer number representing the number of **milliseconds** that has passed since **1 January 1970 [UTC][defn-utc]+0**. + +```javascript +const epoch = new Date(0); +// Thu Jan 01 1970 01:00:00 GMT+0100 (Central European Standard Time) + +const another = new Date(1749508766627); +// Tue Jun 10 2025 00:39:26 GMT+0200 (Central European Summer Time) +``` + +One may expect `new Date(0)` to generate the "earliest" date object, but JavaScript will convert the date to your local timezone, which means that only those around [GMT / with an UTC+0][defn-gmt] timezone will actually get the [Unix epoch][defn-unix-epoch] value. + +### ISO 8601 timestamp (string) + +You can pass a string value representing a date to the `Date` constructor. +The **only** format that is consistent across implementations is the [simplified version][mdn-date-string-format] of the internationally recognized and standardized so-called [ISO 8601 timestamp strings][defn-iso8601]. + +A moment in time at [UTC][defn-gmt] looks like this: + +```text +YYYY-MM-DDTHH:mm:ss.mssZ +YYYYMMDDTHHmmss.mssZ +``` + +Where the following substitutions take place: + +| Key | Description | Default | +| ---- | ------------------------------------------- | ------- | +| YYYY | The calendar year, represented in 4 digits | | +| MM | The calendar month, represented in 2 digits | 01 | +| DD | The calendar day, represented in 2 digits | 01 | +| T | A literal letter T, separating date & time | T | +| HH | The hours in a 24-hour clock, 2 digits | 00 | +| mm | The minutes, 2 digits | 00 | +| ss | The seconds, 2 digits | 00 | +| mss | The milliseconds, 3 digits | 000 | +| Z | A literal letter Z, or an offset `+/-HH:mm` | | + +The literal letter `Z` indicates UTC (no timezone, no Day Light Savings). + +Because there are default values for most components, leaving parts off at the end is valid: + +```text +YYYY-MM-DD +``` + +Defaults to a time of 00:00:00.000 + +If the timestamp does not end in `Z`, and it does not end with `+HH:mm` or `-HH:mm` (indicating a timezone offset), because of historical reasons, the following applies: + +> When the time zone offset is absent, date-only forms are interpreted as a UTC time and date-time forms are interpreted as a local time. +> The interpretation as a UTC time is due to a historical spec error that was not consistent with ISO 8601 but could not be changed due to web compatibility. +> See [Broken Parser – A Web Reality Issue][ref-broken-parser]. + +### Date object + +An existing date object can also be used as a constructor argument. +This makes a copy of the existing `Date` object with the same date and time. + +```javascript +const t1 = new Date(); +const t2 = new Date(t1); +// Values of t1 and t2 will be the same. +``` + +### Supplying individual date and time component values + +A date representing a date can be created by passing three numbers. +A date representing a date and time can be created by passing in 6 numbers. + +```javascript +const date1 = new Date(95, 11, 17); +// Creates Date for Dec 17 1995 00:00 if your local timezone is equivalent to UTC. + +const date2 = new Date(2013, 12, 5, 13, 24, 0); +// Creates Date for Jan 5 2014 13:24 if your local timezone is equivalent to UTC. +``` + +The second value is the `month`, which starts at `0` for January, up to `11` for December. + +## `Date.parse()` + +You may find mentions of or references to a date parsing function `Date.parse`. +Because there are only a few invariants (truths) for this function and because the rest of the implementation is not specified (and thus not standard), one should not use it. + +## Accessing `Date` components + +There are various methods on date objects that return the components of the date: + +```javascript +getFullYear(); // Get the year (4 digits) +getMonth(); // Get the month, from 0 to 11. +getDate(); // Get the day of month, from 1 to 31. +getHours(); // Get the hour in a 24 clock, from 0 to 23 +getMinutes(); // Get the minutes, from 0 to 59 +getSeconds(); // Get the seconds, from 0 to 59 +getMilliseconds(); // Get the milliseconds, from 0 and 999 +getDay(); // Get the day of week, from 0 (Sunday) to 6 (Saturday). +``` + +Each of these has an applicable `set` variant (e.g. `setFullYear`) to update the value. +Any overflowing value rolls over to the next component: + +```javascript +const date = new Date('2025-02-28T12:42:00Z'); +// => Fri Feb 28 2025 13:42:00 GMT+0100 (Central European Standard Time) + +date.setDate(29); +// there was no February 29th in 2025. + +date.getDate(); +// => 1 + +date.toString(); +// => Sat Mar 01 2025 13:42:00 GMT+0100 (Central European Standard Time) +``` + +There are UTC variants for all the methods that disregard the local timezone. + +## Converting from date + +Date objects have a method `getTime()` that returns the UNIX timestamp in milliseconds, ie. amount of milliseconds the midnight at the beginning of January 1, 1970, UTC. +Additionally, a method `toISOString()` is available to convert from a date object to a ISO 8601 timestamp string. + +## Comparing Dates + +Greater than (`>`) and greater than or equals (`>=`) as well as less than (`<`) and less than or equals (`<=`) can be used directly between two dates or a date and a number. +This works because JavaScript will try to coerce the date to a primitive. + + +~~~exercism/advanced +When doing a comparison between two dates or date and a number, JavaScript calls [`[Symbol.toPrimitive]("number")`][mdn-to-primitive] which internally calls [`date.valueOf()`][mdn-date-value-of]. +The latter is the same as calling [`date.getTime()`][mdn-date-get-time]. + +If you do not want to rely on this behaviour, convert to a number using `getTime()` first. + +[mdn-to-primitive]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Symbol.toPrimitive +[mdn-date-value-of]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/valueOf +[mdn-date-get-time]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTime +~~~ + +Dates cannot be compared using equality (`==`, and `===`), but the result of `.getTime()` can. + +[defn-utc]: https://simple.wikipedia.org/wiki/Coordinated_Universal_Time +[defn-gmt]: https://simple.wikipedia.org/wiki/Greenwich_Mean_Time +[defn-unix-epoch]: https://en.wikipedia.org/wiki/Epoch_%28computing%29 +[defn-iso8601]: https://en.wikipedia.org/wiki/ISO_8601 +[mdn-date-string-format]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format +[ref-broken-parser]: https://maggiepint.com/2017/04/11/fixing-javascript-date-web-compatibility-and-reality/ diff --git a/concepts/dates/links.json b/concepts/dates/links.json new file mode 100644 index 0000000000..115304b685 --- /dev/null +++ b/concepts/dates/links.json @@ -0,0 +1,26 @@ +[ + { + "url": "https://javascript.info/date", + "description": "javascript.info: Date" + }, + { + "url": "https://en.wikipedia.org/wiki/Epoch_%28computing%29", + "description": "Wikipedia: epoch (computing)" + }, + { + "url": "https://en.wikipedia.org/wiki/ISO_8601", + "description": "Wikipedia: ISO 8601 format" + }, + { + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date", + "description": "MDN: Date" + }, + { + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format", + "description": "MDN: Date time string format" + }, + { + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal", + "description": "MDN: Temporal" + } +] diff --git a/concepts/functions/about.md b/concepts/functions/about.md index 52db28f46a..5ede06018b 100644 --- a/concepts/functions/about.md +++ b/concepts/functions/about.md @@ -1,6 +1,6 @@ # About -A function allows to group code into a reusable unit. +A function is a block of organized, reusable code that is used to perform some action. There are multiple ways to define functions in JavaScript. Here we will look at _function declarations_ and _function expressions_. Other possibilities like [arrow functions][concept-arrow-functions] will be covered in other concepts. @@ -9,8 +9,8 @@ Other possibilities like [arrow functions][concept-arrow-functions] will be cove The standard way of defining a function in JavaScript is a _function declaration_, also called _function definition_ or _function statement_. -It consists of the `function` keyword, the name of the function, and a comma-separated list of parameters in round brackets. -This is followed by the function body (the code that should be executed) wrapped in curly brackets. +It consists of the `function` keyword, the name of the function, and a comma-separated list of parameters enclosed in round brackets. +This is followed by the function body (collection of statements that defines what a function does) wrapped in curly brackets. ```javascript function someName(param1, param2, param3) { @@ -35,6 +35,7 @@ function sayHello() { sayHello; // => [Function: sayHello] +// The text representations varies between environments. sayHello(); // => 'Hello, World!' @@ -50,13 +51,37 @@ What this means in practice depends on the data type of the argument. All "modifications" always create a new primitive value. Because of that, the original value is never affected by what happens to the argument in the function body. -- For all other values (objects, arrays, functions) it is a mixed bag. + ```javascript + const num = 0; + + function increase(number) { + number = number + 1; + } + + increase(num); + num; + // => 0 + ``` + +- It is different for values like _objects_, _arrays_, _functions_. Since the reference is copied, a reassignment will not affect the original value. However, since you are dealing with a [shallow copy][wikipedia-shalllow-copy], modifying the argument in the function body will also change the original value that was passed in. + ```javascript + const nums = [0, 1, 2, 3, 4]; + + function pushFive(arr) { + arr.push(5); + } + + pushFive(nums); + nums; + // => [0, 1, 2, 3, 4, 5] + ``` + By default, all parameters defined in the function declaration are optional in JavaScript. -If you provide fewer arguments than there are parameters, the missing arguments will be `undefined` inside the function, see [Null and Undefined][concept-null-undefined]. -In many cases, it makes sense to assign a more appropriate default value than `undefined`. +If a function is provided with fewer arguments than there are parameters, the missing arguments will be `undefined` inside the function body, see [Null and Undefined][concept-null-undefined]. +In many cases, it makes more sense to initialize a parameter with a _default_ value if no value or `undefined` property is passed. This can be done by specifying default parameters directly in the function definition. ```javascript @@ -65,16 +90,14 @@ function someName(param1 = defaultValue1, param2 = defaultValue2) { } ``` -You can even call a function with more arguments than there were parameters in the definition. +A function can be invoked (called) with more arguments than there were parameters in the function definition. All arguments, including those excess arguments, can be found in the [arguments "array"][mdn-arguments-object]. -It is also possible to define functions that accept an arbitrary number of arguments (variadic functions), see rest parameters in [Rest and Spread Operators][concept-rest-and-spread] for details about this. +It is also possible to define functions that accept an arbitrary number of arguments (variadic functions), see rest parameters in [Rest and Spread Operators][concept-rest-and-spread] for more details. ## Return Statement -Using the `return` statement, you can pass the result of a function to the code that called it. -There can be multiple `return` statements in a function. -The execution of the function ends as soon as it hits one of those `return`s. +A `return` statement ends the function execution and specifies a value to be returned to the function caller. A function can have multiple `return` statements. ```javascript function checkNumber(num) { @@ -86,8 +109,8 @@ function checkNumber(num) { } ``` -If you use a naked return or no return at all, the result of the function is `undefined`. -There are no implicit returns in JavaScript. +The result of a function that `return`s no value or does not have a `return` statement is `undefined`. +There are no implicit `return`s in JavaScript. ```javascript function nakedReturn(a) { @@ -131,7 +154,7 @@ const someFunction = function (param) { }; someOtherFunction(function (param) { - //... + // ... }); const obj = { diff --git a/concepts/functions/introduction.md b/concepts/functions/introduction.md index 5af190ad18..838502caac 100644 --- a/concepts/functions/introduction.md +++ b/concepts/functions/introduction.md @@ -1,6 +1,6 @@ # Introduction -A function allows to group code into a reusable unit. +A function is a block of organized, reusable code that is used to perform some action. There are multiple ways to define functions in JavaScript. Here we will look at _function declarations_ and _function expressions_. Other possibilities like [arrow functions][concept-arrow-functions] will be covered in other concepts. @@ -10,7 +10,7 @@ Other possibilities like [arrow functions][concept-arrow-functions] will be cove The standard way of defining a function in JavaScript is a _function declaration_, also called _function definition_ or _function statement_. It consists of the `function` keyword, the name of the function, and a comma-separated list of parameters in round brackets. -This is followed by the function body (the code that should be executed) wrapped in curly brackets. +This is followed by the function body (collection of statements that defines what a function does) wrapped in curly brackets. ```javascript function someName(param1, param2, param3) { @@ -18,7 +18,7 @@ function someName(param1, param2, param3) { } ``` -In JavaScript, a function is invoked (called) by stating the function name followed by round brackets that contain the arguments. +In JavaScript, a function is invoked (called) by stating the function name followed by parentheses that contain the arguments. ```javascript someName(arg1, arg2, arg3); @@ -46,9 +46,7 @@ function someName(param1 = defaultValue1, param2 = defaultValue2) { ## Return Statement -Using the `return` statement, you can pass the result of a function to the code that called it. -There can be multiple `return` statements in a function. -The execution of the function ends as soon as it hits one of those `return`s. +A `return` statement ends the function execution and specifies a value to be returned to the function caller. A function can have multiple `return` statements. ```javascript function checkNumber(num) { @@ -60,8 +58,8 @@ function checkNumber(num) { } ``` -If you use a naked return or no return at all, the result of the function is `undefined`. -There are no implicit returns in JavaScript. +The result of a function that `return`s no value or does not have a `return` statement is `undefined`. +There are no implicit `return`s in JavaScript. ```javascript function nakedReturn(a) { @@ -105,7 +103,7 @@ const someFunction = function (param) { }; someOtherFunction(function (param) { - //... + // ... }); const obj = { @@ -118,6 +116,6 @@ const obj = { [concept-arrow-functions]: /tracks/javascript/concepts/arrow-functions [concept-null-undefined]: /tracks/javascript/concepts/null-undefined [concept-objects]: /tracks/javascript/concepts/objects -[concept-callbacks]: /tracks/javascript/concepts/callbacks [concept-arrays]: /tracks/javascript/concepts/arrays +[concept-callbacks]: /tracks/javascript/concepts/callbacks [mdn-primitives]: https://developer.mozilla.org/en-US/docs/Glossary/Primitive diff --git a/concepts/null-undefined/introduction.md b/concepts/null-undefined/introduction.md index 838c1b3eae..f1cb19446e 100644 --- a/concepts/null-undefined/introduction.md +++ b/concepts/null-undefined/introduction.md @@ -24,22 +24,45 @@ name === null; ## Undefined -> A variable that has not been assigned a value is of type `undefined`.1 +> A variable that has not been assigned a value is of type `undefined`.[^1] That means while `null` represents an empty value (but still a value), `undefined` represents the total absence of a value. 🤯 `undefined` appears in different contexts. - If a variable is declared without a value (initialization), it is `undefined`. + + ```js + let name; + console.log(name); // => undefined + ``` + - If you try to access a value for a non-existing key in an object, you get `undefined`. + + ```js + let obj = { + name: 'John', + }; + console.log(obj.age); // => undefined + ``` + - If a function does not return a value, the result is `undefined`. + + ```js + function printName(name) { + 'My name is ' + name; + } + console.log(printName('John')); // => undefined + ``` + - If an argument is not passed to a function, it is `undefined`, unless that argument has a default value. -```javascript -let name; -console.log(name); -// => undefined -``` + ```js + function printName(name) { + return name; + } + console.log(printName()); // => undefined + ``` You can check whether a variable is undefined using the strict equality operator `===`. @@ -94,6 +117,6 @@ amount = amount ?? 1; --- -[1] Undefined, MDN. (2021). https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined (accessed June 4, 2021). +[^1]: `undefined`, MDN. (2021). https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined (accessed June 4, 2021). [mdn-strict-equality]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Strict_equality diff --git a/concepts/numbers/.meta/config.json b/concepts/numbers/.meta/config.json index 4f0d90ad13..c8977310be 100644 --- a/concepts/numbers/.meta/config.json +++ b/concepts/numbers/.meta/config.json @@ -1,5 +1,5 @@ { "blurb": "There are two different kinds of numbers in JavaScript - numbers and bigints. Numbers are for everyday use and are always floating point numbers.", "authors": ["SleeplessByte"], - "contributors": ["junedev", "JaPatGitHub"] + "contributors": ["junedev", "JaPatGitHub", "SyllightTheway"] } diff --git a/concepts/numbers/about.md b/concepts/numbers/about.md index d32f475ee2..cefa271b7b 100644 --- a/concepts/numbers/about.md +++ b/concepts/numbers/about.md @@ -21,6 +21,36 @@ The `bigint` type is not designed to replace the `number` type for everyday uses Numbers may also be expressed in literal forms like `0b101`, `0o13`, `0x0A`. Learn more on numeric lexical grammar [here][lexical-grammar]. +## Special Notations + +### Exponential Notation + +The E-notation indicates a number that should be multiplied by 10 raised to a given power. +The format of E-notation is to have a number, followed by `e` or `E`, than by the power of 10 to multiply by. + +```javascript +const num = 3.125e7; +// => 31250000 +// The notation essentially says, "Take 3.125 and multiply it by 10^7". +``` + +E-notation can also be used to represent very small numbers: + +```javascript +const num = 325987e-6; // Equals to 0. 325987 +// The notation essentially says, "Take 325987 and multiply it by 10^-6. +``` + +### Underscore Notation + +Underscores can be used to make large numbers easier to read for the user. The compiler will completely ignore the underscores. + +```javascript +const num = 1_000_000; // You can read this as 1,000,000 +console.log(num); +// => 1000000 +``` + ## Built-in Object There are two built-in objects that are useful when dealing with numbers: @@ -56,6 +86,75 @@ There are three types of maximum (and minimum / maximum negative) values for num Because of how numbers in JavaScript are implemented, **not** every number between `Number.MIN_VALUE` and `Number.MAX_VALUE` can be represented. However, _every_ number between `Number.MIN_SAFE_INTEGER - 1` and `Number.MAX_SAFE_INTEGER + 1` **can** be represented. +## Special Numbers Values + +JavaScript has several special number values: + +- Two error values, `NaN` and `Infinity`. +- Two values for zero, `+0` and `-0`. + +### NaN - Not a Number + +The error value `NaN`(aka "Not a Number") is produced in the following cases. + +- A number could not be parsed: + ```javascript + Number('123'); // => 123 + Number('Hello, World!'); // => NaN + ``` +- An operation failed: + ```javascript + Math.sqrt(-64); // => NaN + ``` +- One of the operands is NaN: + ```javascript + NaN + 69; // => NaN + ``` + +`NaN` is the only value that is not equal to itself: + +```javascript +NaN === NaN; // => false +``` + +If you want to check whether a value is `NaN`, you have to use the global function `isNaN()`: + +```javascript +isNaN(NaN); // => true +isNaN(123); // => false +``` + +### Infinity + +`Infinity` is an error value indicating one of two problems: + +- A number can't be represented because its magnitude is too large. + ```javascript + Math.pow(2, 1024); // => Infinity + ``` +- A division by zero has happened. + ```javascript + 6 / 0; // => Infinity + -6 / 0; // => -Infinity + ``` + +`Infinity` is larger than any other number (except `NaN`). +Similarly, `-Infinity` is smaller than any other number (except `NaN`) + +The global function `isFinite()` allows you to check whether a value is an actual number (neither infinite nor `NaN`): + +```javascript +isFinite(80085); // => true +isFinite(Infinity); // => false +isFinite(NaN); // => false +``` + +### The Two Zeros + +`+0` or `-0` are distinct numbers in JavaScript. They can be produced if you represented a number, that is so small that it is indistinguishable from 0. +The signed zero allows you to record “from which direction” you approached zero; that is, what sign the number had before it was considered zero. +It is best practise to pretend there's only one zero. + ## Comparison Numbers are considered equal if they have the same value. diff --git a/concepts/object-destructuring/.meta/config.json b/concepts/object-destructuring/.meta/config.json new file mode 100644 index 0000000000..3500286e76 --- /dev/null +++ b/concepts/object-destructuring/.meta/config.json @@ -0,0 +1,5 @@ +{ + "blurb": "Object destructuring is a concise way of extracting properties from an object.", + "authors": ["SleeplessByte"], + "contributors": [] +} diff --git a/concepts/object-destructuring/about.md b/concepts/object-destructuring/about.md new file mode 100644 index 0000000000..b4fd7ce627 --- /dev/null +++ b/concepts/object-destructuring/about.md @@ -0,0 +1,129 @@ +# About + +Object [destructuring][mdn-object-destructuring] is a concise way of extracting properties from an object. +Its syntax is similar to an [object literal][mdn-object-literal] expression, but on the left-hand side of the assignment instead of the right. + +```javascript +const weather = { + sun: '☀️', + sun_behind_small_cloud: '🌤️', + sun_behind_cloud: '⛅', + sun_behind_large_cloud: '🌥️', + sun_behind_rain_cloud: '🌦️', + cloud: '☁️', + cloud_with_rain: '🌧️', + cloud_with_snow: '🌨️', + cloud_with_lightning: '🌩️', + cloud_with_lightning_and_rain: '⛈️', +}; + +const { sun, cloud, cloud_with_lightning } = weather; + +sun; +// => '☀️' + +cloud; +// => '☁️' + +cloud_with_lightning; +// => '🌩️' +``` + +## Renaming in assignment + +The syntax can extract the properties by their key like `sun`, `cloud`, and `cloud_with_lightning`, but can also pick a different name for the variable: + +```javascript +const { sun: sunny, cloud: cloudy, cloud_with_rain: rainy } = weather; + +typeof cloud_with_rain; +// => 'undefined' + +typeof rainy; +// => 'string' + +rainy; +// => 🌧️ +``` + +The assignment is also not required to use all the values. + +## Default values + +The object destructuring assignment can provide _default values_ in case there is none in the source object: + +```javascript +const { sun = '🌞', tornado = '🌪️', cloud_with_snow: snowy = '❄️' } = weather; + +sun; +// => '☀️' + +tornado; +// => '🌪️' + +snowy; +// => '🌨️' +``` + +The following is observed: + +- `sun` has extracted from the object `weather` without replacing it as it is present in the object `weather`, +- `tornado` does not exist in the object `weather`, so the default value was used, +- `cloud_with_snow` was extracted as the variable `snowy`, without replacing it, as `cloud_with_snow` is present in the object `weather`. + +## Destructuring function parameters + +The `weather` object has a lot of properties. +It is possible to directly extract one or multiple properties from this object when it's passed to a function: + +```javascript +function weatherReport({ sun }) { + console.log(sun); +} + +weatherReport(sun); +// => '☀️' +``` + +## Destructuring `for of` iteration + +When iterating over an `array` (or other iterable), and the items are objects, it is possible to destructure inside the `for (...) of iterable` statement: + +```javascript +const { sun: sunny, cloud: cloudy, cloud_with_rain: rainy } = weather; + +const prediction = [ + { + chance: 0.8, + weather: sunny, + description: 'There is a 80% chance it will remain sunny.', + }, + { + chance: 0.15, + weather: cloudy, + description: + 'There is a 15% chance the clouds will keep the sun from poking through.', + }, + { + chance: 0.05, + weather: rainy, + description: 'There is a small chance of rain.', + }, +]; + +for (const { weather: symbol, description } of prediction) { + console.log(`${symbol}: ${description}`); +} + +// '☀️: There is a 80% chance it will remain sunny.' +// '☁️: There is a 15% chance the clouds will keep the sun from poking through.' +// '🌧️: There is a small chance of rain.' +``` + +## Related concepts + +[concept:javascript/array-destructuring]() +[concept:javascript/rest-and-spread]() + +[mdn-object-destructuring]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring#object_destructuring +[mdn-object-literal]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer diff --git a/concepts/object-destructuring/introduction.md b/concepts/object-destructuring/introduction.md new file mode 100644 index 0000000000..77f1b8230a --- /dev/null +++ b/concepts/object-destructuring/introduction.md @@ -0,0 +1,31 @@ +# Introduction + +JavaScript's object destructuring syntax is a concise way to extract properties from an object and assign them to distinct variables. + +In this example, weather symbols are extracted from the object `weather`: + +```javascript +const weather = { + sun: '☀️', + sun_behind_small_cloud: '🌤️', + sun_behind_cloud: '⛅', + sun_behind_large_cloud: '🌥️', + sun_behind_rain_cloud: '🌦️', + cloud: '☁️', + cloud_with_rain: '🌧️', + cloud_with_snow: '🌨️', + cloud_with_lightning: '🌩️', + cloud_with_lightning_and_rain: '⛈️', +}; + +const { sun, cloud, cloud_with_lightning } = weather; + +sun; +// => '☀️' + +cloud; +// => '☁️' + +cloud_with_lightning; +// => '🌩️' +``` diff --git a/concepts/object-destructuring/links.json b/concepts/object-destructuring/links.json new file mode 100644 index 0000000000..b4446d2b1e --- /dev/null +++ b/concepts/object-destructuring/links.json @@ -0,0 +1,10 @@ +[ + { + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring#object_destructuring", + "description": "MDN: Object destructuring" + }, + { + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer", + "description": "MDN: Object initializer (and literals)" + } +] diff --git a/concepts/objects/introduction.md b/concepts/objects/introduction.md index 824ab75a11..2efc612df4 100644 --- a/concepts/objects/introduction.md +++ b/concepts/objects/introduction.md @@ -76,6 +76,7 @@ obj[key]; Using the dot notation as a shorthand has the same restriction as omitting the quotation marks. It only works if the key follows the identifier naming rules. +In other words, if your key is not a single word using only letters, you must use square bracket notation. ## Adding or Changing a Value diff --git a/concepts/promises/about.md b/concepts/promises/about.md index f62792887e..26f6ac2f7e 100644 --- a/concepts/promises/about.md +++ b/concepts/promises/about.md @@ -1,146 +1,273 @@ # About -The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value. It allows you to associate handlers with an asynchronous action's eventual success value or failure reason. +The [`Promise`][promise-docs] object represents the eventual completion (or failure) of an asynchronous operation and its resulting value. -A Promise is in one of these states: + +~~~exercism/note +This is a hard topic for many people, specially if you know programming in a language that is completely _synchronous_. +If you feel overwhelmed, or you would like to learn more about **concurrency** and **parallelism**, [watch (via go.dev)][talk-blog] or [watch directly via vimeo][talk-video] and [read the slides][talk-slides] of the brilliant talk "Concurrency is not parallelism". -- **pending**: initial state, neither fulfilled nor rejected. -- **fulfilled**: meaning that the operation was completed successfully and the result is available. -- **rejected**: meaning that the operation failed. +[talk-slides]: https://go.dev/talks/2012/waza.slide#1 +[talk-blog]: https://go.dev/blog/waza-talk +[talk-video]: https://vimeo.com/49718712 +~~~ -When either of these options happens, the associated handlers queued up by a promise's `then` method is called. If the promise has already been fulfilled or rejected when a corresponding handler is attached, the handler will be called, so there is no race condition between an asynchronous operation completing and its handlers being attached. +## Lifecycle of a promise -Example: +A `Promise` has three states: + +1. pending +2. fulfilled +3. rejected + +When it is created, a promise is pending. +At some point in the future it may _resolve_ or _reject_. +Once a promise is resolved or rejected once, it can never be resolved or rejected again, nor can its state change. + +In other words: + +1. When pending, a promise: + - may transition to either the fulfilled or rejected state. +2. When fulfilled, a promise: + - must not transition to any other state. + - must have a value, which must not change. +3. When rejected, a promise: + - must not transition to any other state. + - must have a reason, which must not change. + +## Resolving a promise + +A promise may be resolved in various ways: ```javascript -const myPromise = new Promise(function (resolve, reject) { - setTimeout(function (resolve, reject) { - resolve('Jack'); - }, 300); +// Creates a promise that is immediately resolved +Promise.resolve(value); + +// Creates a promise that is immediately resolved +new Promise((resolve) => { + resolve(value); }); -myPromise.then(function (e) { - console.log(e); // expected output 'Jack' + +// Chaining a promise leads to a resolved promise +somePromise.then(() => { + // ... + return value; }); ``` -## Instance Methods of a Promise +In the examples above `value` can be _anything_, including an error, `undefined`, `null` or another promise. +Usually you want to resolve with a value that's not an error. -### then +## Rejecting a promise -> The `.then()` method takes up to two arguments; the first argument is a callback function for the resolved case of the promise, and the second argument is a callback function for the rejected case. Each `.then()` returns a newly generated promise object, which can optionally be used for chaining.[^1] +A promise may be rejected in various ways: + +```javascript +// Creates a promise that is immediately rejected +Promise.reject(reason) + +// Creates a promise that is immediately rejected +new Promise((_, reject) { + reject(reason) +}) + +// Chaining a promise with an error leads to a rejected promise +somePromise.then(() => { + // ... + throw reason +}) +``` + +In the examples above `reason` can be _anything_, including an error, `undefined` or `null`. +Usually you want to reject with an error. + +## Chaining a promise + +A promise may be _continued_ with a future action once it resolves or rejects. + +- [`promise.then()`][promise-then] is called once `promise` resolves +- [`promise.catch()`][promise-catch] is called once `promise` rejects +- [`promise.finally()`][promise-finally] is called once `promise` either resolves or rejects + +### **then** + +Every promise is "thenable". +That means that there is a function `then` available that will be executed once the original promise is resolves. +Given `promise.then(onResolved)`, the callback `onResolved` receives the value the original promise was resolved with. +This will always return a _new_ "chained" promise. + +Returning a `value` from `then` resolves the "chained" promise. +Throwing a `reason` in `then` rejects the "chained" promise. ```javascript const promise1 = new Promise(function (resolve, reject) { - resolve('Success!'); + setTimeout(() => { + resolve('Success!'); + }, 1000); }); -promise1.then(function (value) { + +const promise2 = promise1.then(function (value) { console.log(value); // expected output: "Success!" + + return true; }); ``` -### catch +This will log `"Success!"` after approximately 1000 ms. +The state & value of `promise1` will be `resolved` and `"Success!"`. +The state & value of `promise2` will be `resolved` and `true`. -> A `.catch()` is really just a `.then()` without a slot for a callback function for the case when the promise is resolved. It is used to handle rejected promises.[^2] +There is a second argument available that runs when the original promise rejects. +Given `promise.then(onResolved, onRejected)`, the callback `onResolved` receives the value the original promise was resolved with, or the callback `onRejected` receives the reason the promise was rejected. ```javascript -const promise1 = new Promise((resolve, reject) => { - throw 'An error occured'; -}); -promise1.catch(function (error) { - console.error(error); -}); -// expected output: An error occured -``` +const promise1 = new Promise(function (resolve, reject) { + setTimeout(() => { + resolve('Success!'); + }, 1000); -### finally + if (Math.random() < 0.5) { + reject('Nope!'); + } +}); -> When the promise is settled, i.e either fulfilled or rejected, the specified callback function is executed. This provides a way for code to be run whether the promise was fulfilled successfully or rejected once the Promise has been dealt with.[^3] +function log(value) { + console.log(value); + return true; +} -```javascript -function findDataById(id) { - return new Promise(function (resolve, reject) { - let sampleData = [1, 2, 3, 4, 5]; - if (sampleData[id]) { - resolve(sampleData[id]); - } else { - reject(new Error('Invalid id')); - } - }); +function shout(reason) { + console.error(reason.toUpperCase()); + return false; } -findDataById(4) - .then(function (response) { - console.log(response); - }) - .catch(function (err) { - console.error(err); - }) - .finally(function () { - console.log('Promise completed'); - }); + +const promise2 = promise1.then(log, shout); ``` -## Static Methods of the Promise Class +- In about 1/2 of the cases, this will log `"Success!"` after approximately 1000 ms. + - The state & value of `promise1` will be `resolved` and `"Success!"`. + - The state & value of `promise2` will be `resolved` and `true`. +- In about 1/2 of the cases, this will immediately log `"NOPE!"`. + - The state & value of `promise1` will be `rejected` and `Nope!`. + - The state & value of `promise2` will be `resolved` and `false`. + +It is important to understand that because of the rules of the lifecycle, when it `reject`s, the `resolve` that comes in ~1000ms later is silently ignored, as the internal state cannot change once it has rejected or resolved. +It is important to understand that returning a value from a promise resolves it, and throwing a value rejects it. +When `promise1` resolves and there is a chained `onResolved`: `then(onResolved)`, then that follow-up is a new promise that can resolve or reject. +When `promise1` rejects but there is a chained `onRejected`: `then(, onRejected)`, then that follow-up is a new promise that can resolve or reject. -Some of the [static methods][promise-static-methods] that are available on `Promise` can be used to resolve and reject promises. Here are a few of them: +### **catch** -### Promise.all +Sometimes you want to capture errors and only continue when the original promise `reject`s. +Given `promise.catch(onCatch)`, the callback `onCatch` receives the reason the original promise was rejected. +This will always return a _new_ "chained" promise. -> The `Promise.all()` method takes an iterable of promises as an input and returns a single Promise that resolves to an array of the results of the input promises. It rejects immediately upon any of the input promises rejecting or non-promises throwing an error and will reject with this first rejection message / error.[^4] +Returning a `value` from `catch` resolves the "chained" promise. +Throwing a `reason` in `catch` rejects the "chained" promise. ```javascript -var p1 = Promise.resolve(10); -var p2 = 45; -var p3 = new Promise(function (resolve, reject) { - setTimeout(function () { - resolve('Jill'); - }, 300); -}); -Promise.all([p1, p2, p3]).then(function (values) { - console.log(values); // => [10, 45, "Jill"] +const promise1 = new Promise(function (resolve, reject) { + setTimeout(() => { + resolve('Success!'); + }, 1000); + + if (Math.random() < 0.5) { + reject('Nope!'); + } }); -``` -### Promise.reject +function log(value) { + console.log(value); + return 'done'; +} -> The `Promise.reject()` method returns a Promise object that is rejected with a given reason.[^5] +function recover(reason) { + console.error(reason.toUpperCase()); + return 42; +} -```javascript -Promise.reject(new Error('failed')).then( - function () { - // not called - }, - function (error) { - console.error(error); // error in the console - } -); +const promise2 = promise1.catch(recover).then(log); ``` -### Promise.resolve +In about 1/2 of the cases, this will log `"Success!"` after approximately 1000 ms. +In the other 1/2 of the cases, this will immediately log `42`. -> The `Promise.resolve()` method returns a Promise object that is resolved with a given value. If the value is a promise, that promise is returned; if the value is a thenable (i.e. has a "then" method), the returned promise will "follow" that thenable, adopting its eventual state; otherwise the returned promise will be fulfilled with the value.[^6] +- If `promise1` resolves, `catch` is skipped and it reaches `then`, and logs the value. + - The state & value of `promise1` will be `resolved` and `"Success!"`. + - The state & value of `promise2` will be `resolved` and `"done"`; +- If `promise1` rejects, `catch` is executed, which _returns a value_, and thus the chain is now `resolved`, and it reaches `then`, and logs the value. + - The state & value of `promise1` will be `rejected` and `"Nope!"`. + - The state & value of `promise2` will be `resolved` and `"done"`; + +### **finally** + +Sometimes you want to execute code after a promise settles, regardless if the promise resolves or rejects. +Given `promise.finally(onSettled)`, the callback `onSettled` receives nothing. +This will always return a _new_ "chained" promise. + +Returning a `value` from `finally` copies the status & value from the original promise, ignoring the `value`. +Throwing a `reason` in `finally` rejects the "chained" promise, overwriting any status & value or reason from the original promise. + +## Example + +Various of the methods together: ```javascript -Promise.resolve('resolved!').then( - function (value) { - console.log(value); // "resolved!" - }, - function (value) { - // not called +const myPromise = new Promise(function (resolve, reject) { + const sampleData = [2, 4, 6, 8]; + const randomNumber = Math.round(Math.random() * 5); + + if (sampleData[randomNumber]) { + resolve(sampleData[randomNumber]); + } else { + reject('Sampling did not result in a sample'); } -); +}); + +const finalPromise = myPromise + .then(function (sampled) { + // If the random number was 0, 1, 2, or 3, this will be + // reached and the number 2, 4, 6, or 8 will be logged. + console.log(`Sampled data: ${sampled}`); + return 'yay'; + }) + .catch(function (reason) { + // If the random number was 4 or 5, this will be reached and + // reason will be "An error occurred". The entire chain will + // then reject with an Error with the reason as message. + throw new Error(reason); + }) + .finally(function () { + // This will always log after either the sampled data is + // logged or the error is raised. + console.log('Promise completed'); + }); ``` ---- +- In the cases `randomNumber` is `0-3`: + - `myPromise` will be resolved with the value `2, 4, 6, or 8` + - `finalPromise` will be resolved with the value `'yay'` + - There will be two logs: + - `Sampled data: ...` + - `Promise completed` +- In the cases `randomNumber` is `4-5`: + - `myPromise` will be rejected with the reason `'Sampling did not result in a sample'` + - `finalPromise` will be rejected with the reason `Error('Sampling did not result in a sample')` + - There will be one log: + - `Promise completed` + - _in some environments_ this will yield an `"uncaught rejected promise: Error('Sampling did not result in a sample')"` log + +As shown above, `reject` works with a string, and a promise can also reject with an `Error`. + + +~~~exercism/note +If chaining promises or general usage is unclear, the [tutorial on MDN][mdn-promises] is a good resource to consume. -[^4]: `all`, MDN. -[^5]: `reject`, MDN. -[^6]: `resolve`, MDN. -[^1]: `then`, MDN. -[^2]: `catch`, MDN. -[^3]: `finally`, MDN. +[mdn-promises]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises +~~~ +[promise-docs]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise [promise-catch]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch [promise-then]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then [promise-finally]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally -[promise-static-methods]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#static_methods diff --git a/concepts/promises/introduction.md b/concepts/promises/introduction.md index 14963c91c7..f077711929 100644 --- a/concepts/promises/introduction.md +++ b/concepts/promises/introduction.md @@ -1,104 +1,50 @@ # Introduction -The [`Promise`][promise-docs] object represents the eventual completion (or failure) of an -asynchronous operation, and its resulting value. +The [`Promise`][promise-docs] object represents the eventual completion (or failure) of an asynchronous operation and its resulting value. -The methods [`promise.then()`][promise-then], [`promise.catch()`][promise-catch], and [`promise.finally()`][promise-finally] are used to associate further action with a promise that becomes settled. + +~~~exercism/note +This is a hard topic for many people, specially if you know programming in a language that is completely _synchronous_. +If you feel overwhelmed, or you would like to learn more about **concurrency** and **parallelism**, [watch (via go.dev)][talk-blog] or [watch directly via vimeo][talk-video] and [read the slides][talk-slides] of the brilliant talk "Concurrency is not parallelism". -For example: +[talk-slides]: https://go.dev/talks/2012/waza.slide#1 +[talk-blog]: https://go.dev/blog/waza-talk +[talk-video]: https://vimeo.com/49718712 +~~~ -```javascript -const myPromise = new Promise(function (resolve, reject) { - let sampleData = [2, 4, 6, 8]; - let randomNumber = Math.ceil(Math.random() * 5); - if (sampleData[randomNumber]) { - resolve(sampleData[randomNumber]); - } else { - reject('An error occured!'); - } -}); +## Lifecycle of a promise -myPromise - .then(function (e) { - console.log(e); - }) - .catch(function (error) { - throw new Error(error); - }) - .finally(function () { - console.log('Promise completed'); - }); -``` +A `Promise` has three states: -## Methods +1. pending +2. fulfilled +3. rejected -These methods are available on `Promise.prototype` +When it is created, a promise is pending. +At some point in the future it may _resolve_ or _reject_. +Once a promise is resolved or rejected once, it can never be resolved or rejected again, nor can its state change. -**then** +In other words: -> The `.then()` method takes up to two arguments; the first argument is a callback function for the resolved case of the promise, and the second argument is a callback function for the rejected case. Each `.then()` returns a newly generated promise object, which can optionally be used for chaining.[^1] +1. When pending, a promise: + - may transition to either the fulfilled or rejected state. +2. When fulfilled, a promise: + - must not transition to any other state. + - must have a value, which must not change. +3. When rejected, a promise: + - must not transition to any other state. + - must have a reason, which must not change. -```javascript -const promise1 = new Promise(function (resolve, reject) { - resolve('Success!'); -}); +## Chaining promises -promise1.then(function (value) { - console.log(value); - // expected output: "Success!" -}); -``` +In JavaScript, there are various methods to chain promises. +Calling a chaining method on a promise returns another promise. -**catch** + +~~~exercism/note +The [tutorial on MDN][mdn-promises] is a good resource to consume if you want to learn more about promises before completing the concept exercise. -> A `.catch()` is really just a `.then()` without a slot for a callback function for the case when the promise is resolved. It is used to handle rejected promises.[^2] - -```javascript -const promise1 = new Promise((resolve, reject) => { - throw 'An error occured'; -}); - -promise1.catch(function (error) { - console.error(error); -}); -// expected output: An error occured -``` - -**finally** - -> When the promise is settled, i.e either fulfilled or rejected, the specified callback function is executed. This provides a way for code to be run whether the promise was fulfilled successfully or rejected once the Promise has been dealt with.[^3] - -```javascript -function findDataById(id) { - return new Promise(function (resolve, reject) { - let sampleData = [1, 2, 3, 4, 5]; - if (sampleData[id]) { - resolve(sampleData[id]); - } else { - reject(new Error('Invalid id')); - } - }); -} - -findDataById(4) - .then(function (response) { - console.log(response); - }) - .catch(function (err) { - console.error(err); - }) - .finally(function () { - console.log('Promise completed'); - }); -``` - ---- - -[^1]: `then`, MDN. -[^2]: `catch`, MDN. -[^3]: `finally`, MDN. +[mdn-promises]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises +~~~ [promise-docs]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise -[promise-catch]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch -[promise-then]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then -[promise-finally]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally diff --git a/concepts/randomness/.meta/config.json b/concepts/randomness/.meta/config.json new file mode 100644 index 0000000000..1b2ce91b6f --- /dev/null +++ b/concepts/randomness/.meta/config.json @@ -0,0 +1,5 @@ +{ + "blurb": "Random number generation using Math.random()", + "authors": ["SneakyMallard"], + "contributors": ["SleeplessByte"] +} diff --git a/concepts/randomness/about.md b/concepts/randomness/about.md new file mode 100644 index 0000000000..d09087ffd8 --- /dev/null +++ b/concepts/randomness/about.md @@ -0,0 +1,61 @@ +# Introduction + +Many programs need (pseudo-)random values to simulate real-world events. + +Common, familiar examples include: + +- A coin toss: a random value from ('H', 'T'). +- The roll of a die: a random integer from 1 to 6. +- Shuffling a deck of cards: a random ordering of a card list. +- The creation of trees and bushes in a 3-D graphics simulation. + +Generating truly random values with a computer is a [surprisingly difficult technical challenge][why-randomness-is-hard], which is why there are also "pseudorandom" generators. + + +~~~exercism/advanced +[The language specification][spec] for JavaScript doesn't force the implementation for random number generation. +All major browsers and JavaScript runtimes implement a PRNG (pseudo-random number generator). +Because the numbers are not cryptographically secure, they should never be used for anything that requires true or at least cryptographically secure random numbers, such as certificate or password generation or operations. + +There is a standard called [Web Cryptography][rfc] which standardizes an interface for doing cryptography in JavaScript. +It is implemented [by Browsers][crypto-web] as well as runtimes such as [Node.JS][crypto-node] and [Deno][crypto-deno]. + +This concept is not about Web Crypto and will restrict itself to pseudo-random number generation. + +[rfc]: https://www.w3.org/TR/webcrypto-2/ +[spec]: https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-math.random +[crypto-web]: https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues +[crypto-node]: https://nodejs.org/api/webcrypto.html#cryptogetrandomvaluestypedarray +[crypto-deno]: https://docs.deno.com/api/web/~/Crypto +~~~ + +## Generating random numbers + +In Javascript, you can generate psuedorandom numbers using the [`Math.random()`][Math.random] function. +It will return a psuedorandom floating-point number between 0 (inclusive), and 1 (exclusive). + +To get a random number between _min_ (inclusive) and _max_ (exclusive) you can use a function something like this: + +```javascript +function getRandomInRange(min, max) { + return min + Math.random() * (max - min); +} +getRandomInRange(4, 10); +// => 5.72 +``` + + +~~~exercism/advanced +Most simple techniques of returning a range of numbers based on the randomly generated number [will introduce bias][bias]. +That means that some numbers will be more likely to be rolled than others. +Using the multiplication technique spreads out the bias over the entire range, so it will be less obvious and in most cases not a big issue, but you should be aware of this. + +[bias]: https://adammil.net/blog/v134_Efficiently_generating_random_numbers_without_bias.html +~~~ + +## Generating random integers + +To generate a random integer, you can use `Math.floor()` or `Math.ceil()` to turn a randomly generated number into an integer. + +[why-randomness-is-hard]: https://www.malwarebytes.com/blog/news/2013/09/in-computers-are-random-numbers-really-random +[Math.random]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random diff --git a/concepts/randomness/introduction.md b/concepts/randomness/introduction.md new file mode 100644 index 0000000000..79b03703b6 --- /dev/null +++ b/concepts/randomness/introduction.md @@ -0,0 +1,40 @@ +# Introduction + +Many programs need (pseudo-)random values to simulate real-world events. + +Common, familiar examples include: + +- A coin toss: a random value from ('H', 'T'). +- The roll of a die: a random integer from 1 to 6. +- Shuffling a deck of cards: a random ordering of a card list. +- The creation of trees and bushes in a 3-D graphics simulation. + +Generating truly random values with a computer is a [surprisingly difficult technical challenge][why-randomness-is-hard], which is why there are also "pseudorandom" generators. + + +~~~exercism/caution +The `Math.random()` function should NOT be used for security and cryptographic applications! +Finish the learning exercise(s) about this concept to learn more +~~~ + +## Generating random numbers + +In Javascript, you can generate psuedorandom numbers using the [`Math.random()`][Math.random] function. +It will return a psuedorandom floating-point number between 0 (inclusive), and 1 (exclusive). + +To get a random number between _min_ (inclusive) and _max_ (exclusive) you can use a function something like this: + +```javascript +function getRandomInRange(min, max) { + return min + Math.random() * (max - min); +} +getRandomInRange(4, 10); +// => 5.72 +``` + +## Generating random integers + +To generate a random integer, you can use `Math.floor()` or `Math.ceil()` to turn a randomly generated number into an integer. + +[why-randomness-is-hard]: https://www.malwarebytes.com/blog/news/2013/09/in-computers-are-random-numbers-really-random +[Math.random]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random diff --git a/concepts/randomness/links.json b/concepts/randomness/links.json new file mode 100644 index 0000000000..5d949ca1a5 --- /dev/null +++ b/concepts/randomness/links.json @@ -0,0 +1,6 @@ +[ + { + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random", + "description": "MDN: The Math.random() function" + } +] diff --git a/concepts/recursion/.meta/config.json b/concepts/recursion/.meta/config.json index 0095ad370a..b2b734c22a 100644 --- a/concepts/recursion/.meta/config.json +++ b/concepts/recursion/.meta/config.json @@ -1,5 +1,5 @@ { "blurb": "Recursive functions are functions that call themselves.", - "authors": ["SleeplessByte"], + "authors": ["SleeplessByte", "zynthatrix"], "contributors": [] } diff --git a/concepts/recursion/about.md b/concepts/recursion/about.md index 4b51d644bd..051bd2d3cb 100644 --- a/concepts/recursion/about.md +++ b/concepts/recursion/about.md @@ -1 +1,93 @@ -# About +# Understanding Recursion in JavaScript + +Recursion is a powerful concept in programming that involves a function calling itself. +It can be a bit tricky to grasp at first, but once you understand the fundamentals, it becomes a valuable tool in solving complex problems. +We'll explore recursion in JavaScript with easy-to-understand examples. + +## What is Recursion? + +Recursion occurs when a function calls itself, either directly or indirectly. +It's similar to a loop, but it may involve breaking a problem down into smaller, more manageable sub-problems. + +### Example 1: Countdown + +Let's start with a simple example: a countdown function. + +```javascript +function countdown(num) { + // Base case + if (num <= 0) { + console.log('Blastoff!'); + return; + } + + // Recursive case + console.log(num); + countdown(num - 1); +} + +// Call the function +countdown(5); +``` + +In this example: + +- **Base case**: When `num` becomes less than or equal to 0, the function prints "Blastoff!" and stops calling itself. +- **Recursive case**: The function prints the current `num` and calls itself with `num - 1`. + +### Example 2: Factorial + +Now, let's look at a classic example of recursion: calculating the factorial of a number. + +```javascript +function factorial(n) { + // Base case + if (n === 0 || n === 1) { + return 1; + } + + // Recursive case + return n * factorial(n - 1); +} + +// Test the function +console.log(factorial(5)); // Output: 120 +``` + +In this example: + +- **Base case**: When `n` is 0 or 1, the function returns 1. +- **Recursive case**: The function multiplies `n` by the factorial of `n - 1`. + +## Key Concepts + +### Base Case + +Every recursive function should have at least one base case, a condition where the function stops calling itself. +Without a base case, the recursion would continue indefinitely, leading to a stack overflow. + +### Recursive Case + +The recursive case defines how the function calls itself with a smaller or simpler version of the problem. + +## Pros and Cons of Recursion + +**Pros:** + +- Elegant solution for certain problems. +- Mimics the mathematical induction concept. + +**Cons:** + +- Can be less efficient than iterative solutions. +- May lead to stack overflow for deep recursion. + +## Conclusion + +Recursion is a valuable technique that simplifies complex problems by breaking them into smaller, more manageable sub-problems. +Understanding base cases and recursive cases is crucial for implementing effective recursive solutions in JavaScript. + +**Learn More:** + +- [MDN: Recursion in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions#recursion) +- [Eloquent JavaScript: Chapter 3 - Functions](https://eloquentjavascript.net/03_functions.html) diff --git a/concepts/recursion/introduction.md b/concepts/recursion/introduction.md index 83d31dab92..74c281ea7f 100644 --- a/concepts/recursion/introduction.md +++ b/concepts/recursion/introduction.md @@ -1,3 +1,93 @@ -# Introduction +# Understanding Recursion in JavaScript -TODO: add introduction for recursion concept +Recursion is a powerful concept in programming that involves a function calling itself. +It can be a bit tricky to grasp at first, but once you understand the fundamentals, it becomes a valuable tool in solving complex problems. +We'll explore recursion in JavaScript with easy-to-understand examples. + +## What is Recursion? + +Recursion occurs when a function calls itself, either directly or indirectly. +It's similar to a loop, but it involves breaking a problem down into smaller, more manageable sub-problems. + +### Example 1: Countdown + +Let's start with a simple example: a countdown function. + +```javascript +function countdown(num) { + // Base case + if (num <= 0) { + console.log('Blastoff!'); + return; + } + + // Recursive case + console.log(num); + countdown(num - 1); +} + +// Call the function +countdown(5); +``` + +In this example: + +- **Base case**: When `num` becomes less than or equal to 0, the function prints "Blastoff!" and stops calling itself. +- **Recursive case**: The function prints the current `num` and calls itself with `num - 1`. + +### Example 2: Factorial + +Now, let's look at a classic example of recursion: calculating the factorial of a number. + +```javascript +function factorial(n) { + // Base case + if (n === 0 || n === 1) { + return 1; + } + + // Recursive case + return n * factorial(n - 1); +} + +// Test the function +console.log(factorial(5)); // Output: 120 +``` + +In this example: + +- **Base case**: When `n` is 0 or 1, the function returns 1. +- **Recursive case**: The function multiplies `n` by the factorial of `n - 1`. + +## Key Concepts + +### Base Case + +Every recursive function should have at least one base case, a condition where the function stops calling itself. +Without a base case, the recursion would continue indefinitely, leading to a stack overflow. + +### Recursive Case + +The recursive case defines how the function calls itself with a smaller or simpler version of the problem. + +## Pros and Cons of Recursion + +**Pros:** + +- Elegant solution for certain problems. +- Mimics the mathematical induction concept. + +**Cons:** + +- Can be less efficient than iterative solutions. +- May lead to stack overflow for deep recursion. + +## Conclusion + +Recursion is a valuable technique that simplifies complex problems by breaking them into smaller, more manageable sub-problems. +Understanding base cases and recursive cases is crucial for implementing effective recursive solutions in JavaScript. + +**Learn More:** + +- [MDN: Recursion in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions#recursion) +- [Eloquent JavaScript: Chapter 3 - Functions](https://eloquentjavascript.net/03_functions.html) diff --git a/concepts/regular-expressions/.meta/config.json b/concepts/regular-expressions/.meta/config.json new file mode 100644 index 0000000000..43444d6a9a --- /dev/null +++ b/concepts/regular-expressions/.meta/config.json @@ -0,0 +1,5 @@ +{ + "blurb": "JavaScript supports regular expressions as a powerful way of manipulating and searching through data.", + "authors": ["julendiaz"], + "contributors": ["junedev"] +} diff --git a/concepts/regular-expressions/about.md b/concepts/regular-expressions/about.md new file mode 100644 index 0000000000..3bf86f7aa1 --- /dev/null +++ b/concepts/regular-expressions/about.md @@ -0,0 +1,189 @@ +# About + +## Regular Expressions in JavaScript + +A [Regular Expression][mdn-regular-expressions] (or Regex) is a sequence of characters that we can use to target and manipulate certain elements in strings. Hence, we can: + +- **Search** for a text in a string +- **Replace** substrings in a string +- **Extract** information from a string + +This concept is focused on using regular expressions in JavaScript. If you want to learn about how to write regular expressions, [Eloquent JavaScript][eloquent-javascript] might be a good resource. + +> 💡 JavaScript's regex flavor is part of the ECMA-262 standard for the language. This means that you don't have to worry about browser-specific compatibility. + +## How to create Regular Expressions + +In JavaScript, regular expressions are mostly written in the format `/pattern/modifiers` where 'pattern' is the main regex, and 'modifiers' are a series of characters which we can add to indicate special options (like superpowers). + +Nevertheless, we have two ways of creating a regular expression: + +1. **Regular Expression Literal**: + +```javascript +const regex = /[a-z]/; +``` + +2. Constructor **RegExp**: + +```javascript +const regex = new RegExp('[a-z]'); +``` + +In both cases, JavaScript is creating an object out of the regex. It is recommended to use immutable patterns with the literal as default. + +The RegExp constructor can be used for cases where the regex will change or we don't know it yet (like an input). + +## 🏴‍☠️ Flags + +Regular expressions have optional superpowers called [`flags`][mdn-flags] that allow for additional features. These flags can be used separately or together in any order, and are included as part of the regular expression. + +Some of the widely used are: + +- `/g` - Global Search +- `/i` - Case Insensitive +- `/m` - Multiline Search + +Here is a simple example: + +```javascript +const re = /home/gi; +const str = 'Home, sweet home.'; + +str.match(re); + +// => ["Home", "home"] +``` + +The `g` character allows us to parse all possible matches within a string. Without this feature, JavaScript would have extracted only the first `Home` match. + +The Case Insensitive flag `/i` enables us to be flexible when it comes to finding a pattern. This way it doesn't matter if what we are looking for is in `UPPERCASE` or `lowercase`. + +When using the `RegExp` constructor, the syntax of adding flags is different. In this case, we just need to pass the flags as a second argument. + +## Commonly used functions + +When regular expressions are combined with current build-in functions in JavaScript, we have some powerful ways of manipulating and getting data from strings. + +The `test()` method is a great way of searching and knowing if a target value exists within a given string. In this way, it returns a boolean value, `true` or `false`. + +The `match()` method extracts the regular expression match from a given string. Returning an array with information and matches. + +_When to use one or the other?_ Use the `test()` when you want a fast way of checking a value within a string, use `match()` if you need to use that value or want more information about the match. + +### Test + +The [test()][mdn-regex-test] method executes a search for a match between a regular expression and a specified string. Returns true or false. + +```javascript +const str = 'It is difficult to test if you have a virus'; +const result = /virus$/.test(str); + +console.log(result); + +// => true +``` + +### Match + +Okey but, what if we want to get the actual values of the regular expression search? + +Instead of returning just a boolean, with the [match()][mdn-regex-match] method, we get a useful array whose contents depend on the presence or absence of the found matches. + +In this way, we are able both to **search** and to **extract** information from any string. For example: + +```javascript +const funnyQuote = + 'If you see someone crying, ask if it is because of their haircut.'; +const regex1 = /someone/; +const regex2 = /happy/; + +funnyQuote.match(regex1); +// => ["someone", index: 3, input: "If you see someone crying, ask if it is because of their haircut.", groups: undefined] + +funnyQuote.match(regex2); +// => null +``` + +When the Global Search flag `/g` is present, instead of getting the only match alongside useful information such as the index or input, the method returns all of the occurrences present in the array: + +```javascript +const funnyQuote = + 'If you see someone crying, ask if it is because of their haircut.'; + +const regex3 = /if/gi; + +funnyQuote.match(regex3); +// => ["If", "if"]; +``` + +### Replace + +The [replace()][mdn-regex-replace] method in JavaScript allows us to search for a value within a given string, and replace it with a new value. + +```javascript +string.replace(searchValue, newValue); +``` + +The pattern for searching the substitution can be a single string, or a regular expression. + +```javascript +let string = 'I like dogs!'; +let result = string.replace('dogs', 'cats'); + +let string = 'I would love to travel to Japan'; +let result = string.replace(/Japan/g, 'Hawaii'); +``` + +Moreover, we can apply a function on the replacement position in order to make additional changes to each value. + +```javascript +let text = 'Say hello to the chatbot.'; +let result = text.replace(/chatbot|hello/gi, function (word) { + return word.toUpperCase(); +}); +// => "Say HELLO to the CHATBOT" +``` + +> 💡 In the end, the combination of regular expressions and the `replace()` method is a more dynamic way of replacing elements. Using a single string may be limited. + +### Split + +The [split()][mdn-regex-split] method in JavaScript represents a different way of using and manipulating strings with regular expressions. + +In this way, we will be using regex in order to divide a given string by recognizing a pattern, e.g. `str.split(/[,.\s]/)`. This pattern will be used as the `separator`. + +```javascript +const str = 'hello,user.how are.you'; + +const result = str.split(/[,.\s]/); + +console.log(result); +// => ['hello', 'user', 'how', 'are', 'you'] +``` + +## Performance + +Regarding performance, both of them create a RegExp object. The main difference is how often the regex is compiled: + +- With `Regular Expression Literal`: one time during initial code parsing and compiling +- With `RegExp()` syntax: Every time new Object gets created. + +Using literal syntax may be a better option not only because of performance, but also for simplicity and readability. For more details see this [Stackoverflow discussion][stackoverflow-discussion]. + +1. It is shorter and doesn’t force you to think in terms of class-like constructors. +2. When using the `RegExp()` constructor, you also need to escape quotes and double-escape backslashes. It makes regular expressions that are hard to read and understand by their nature even more harder. + +[using-regular-expressions-in-javascript]: https://www.regular-expressions.info/javascript.html +[mdn-regex-cheatsheet]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Cheatsheet +[mdn-regular-expressions]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions +[mdn-common-functions]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#using_regular_expressions_in_javascript +[mdn-flags]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#advanced_searching_with_flags +[mdn-regex-test]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test +[mdn-regex-match]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match +[mdn-regex-replace]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace +[mdn-regex-split]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split +[demystifying-regular-expressions-with-javascript]: https://livecodestream.dev/post/demystifying-regular-expressions-with-javascript/ +[how-to-use-the-js-replace-method-on-a-string]: https://www.freecodecamp.org/news/javascript-regex-match-example-how-to-use-the-js-replace-method-on-a-string/ +[eloquent-javascript]: https://eloquentjavascript.net/09_regexp.html +[stackoverflow-discussion]: https://stackoverflow.com/a/32523333 diff --git a/concepts/regular-expressions/introduction.md b/concepts/regular-expressions/introduction.md new file mode 100644 index 0000000000..f724a70b1a --- /dev/null +++ b/concepts/regular-expressions/introduction.md @@ -0,0 +1,172 @@ +# Introduction + +## Regular Expressions in JavaScript + +A [Regular Expression][mdn-regular-expressions] (or Regex) is a sequence of characters that we can use to target and manipulate certain elements in strings. Hence, we can: + +- **Search** for a text in a string +- **Replace** substrings in a string +- **Extract** information from a string + +> 💡 JavaScript's regex flavor is part of the ECMA-262 standard for the language. This means that you don't have to worry about browser-specific compatibility. + +## How to create Regular Expressions + +In JavaScript, a regular expressions is mostly written in the format `/pattern/modifiers`. + +We have two ways of creating a regular expression: + +1. **Regular Expression Literal**: + +```javascript +const regex = /[a-z]/; +``` + +2. Constructor **RegExp**: + +```javascript +const regex = new RegExp('[a-z]'); +``` + +In both cases, JavaScript is creating an object out of the regex. It is recommended to use immutable patterns with the literal as default. + +The RegExp constructor can be used for cases where the regex will change or we don't know it yet (like an input). + +## 🏴‍☠️ Flags + +Regular expressions have optional superpowers called [`flags`][mdn-flags] that allow for additional features. + +Some of the widely used are: + +- `/g` - Global Search +- `/i` - Case Insensitive +- `/m` - Multiline Search + +Here is a simple example: + +```javascript +const re = /home/gi; +const str = 'Home, sweet home.'; +const myArray = str.match(re); +console.log(myArray); + +// => ["Home", "home"] +``` + +The `g` character allows us to parse all possible matches within a string. Without this feature, JavaScript would have extracted only the first `Home` match. + +The Case Insensitive flag `/i` enables us to be flexible when it comes to finding a pattern. This way it doesn't matter if what we are looking for is in `UPPERCASE` or `lowercase`. + +When using the `RegExp` constructor, the syntax of adding flags is different. + +```javascript +let regex = /[a-z]/gi; // literal notation +let regex = new RegExp('[a-z]', 'gi'); // constructor with string pattern as first argument +let regex = new RegExp(/[a-z]/, 'gi'); // constructor with regular expression literal as first argument (Starting with ECMAScript 6) +``` + +## Most common Functions + +When regular expressions are combined with current build-in functions in JavaScript, we have some powerful ways of manipulating and getting data from strings. + +These are some of the most common functions used alongside regex. + +### Test + +The [test()][mdn-regex-test]] method executes a search for a match between a regular expression and a specified string. Returns true or false. + +```javascript +const str = 'It is difficult to test if you have a virus'; +const result = /virus$/.test(str); + +console.log(result); + +// => true +``` + +### Match + +With the [match()][mdn-regex-match] method, we get a useful array whose contents depend on the presence or absence of the found matches. + +In this way, we are able both to **search** and to **extract** information from any string. For example: + +```javascript +const funnyQuote = + 'If you see someone crying, ask if it is because of their haircut.'; +const regex1 = /someone/; +const regex2 = /happy/; + +funnyQuote.match(regex1); +// => ["someone", index: 3, input: "If you see someone crying, ask if it is because of their haircut.", groups: undefined] + +funnyQuote.match(regex2); +// => null +``` + +When the Global Search flag `/g` is present, instead of getting the only match alongside useful information such as the index or input, the method returns all of the occurances displayed in the array: + +```javascript +const funnyQuote = + 'If you see someone crying, ask if it is because of their haircut.'; + +const regex3 = /if/gi; + +funnyQuote.match(regex3); +// => ["If", "if"]; +``` + +### Replace + +The [replace()][mdn-regex-replace] method in JavaScript allows us to search for a value within a given string, and replacing it with a desired new value. + +```javascript +string.replace(searchValue, newValue); +``` + +The pattern for searching the substitution can be a single string, or a regular expression. + +```javascript +let string = 'I like dogs!'; +let result = string.replace('dogs', 'cats'); + +let string = 'I would love to travel to Japan'; +let result = string.replace(/Japan/g, 'Hawaii'); +``` + +Moreover, we can apply a function on the replacement position in order to make additional changes to each value. + +```javascript +let text = 'Say hello to the chatbot.'; +let result = text.replace(/chatbot|hello/gi, function (word) { + return word.toUpperCase(); +}); + +// => "Say HELLO to the CHATBOT" +``` + +### Split + +The [split()][mdn-regex-split] method in JavaScript represents a different way of using and manipulating strings with regular expressions. + +In this way, we will be using regex in order to divide a given string by recognizing a pattern, e.g. `str.split(/[,.\s]/)`. This pattern will be used as the `separator`. + +```javascript +const str = 'hello,user.how are.you'; + +const result = str.split(/[,.\s]/); + +console.log(result); +// => ['hello', 'user', 'how', 'are', 'you'] +``` + +[using-regular-expressions-in-javascript]: https://www.regular-expressions.info/javascript.html +[mdn-regex-cheatsheet]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Cheatsheet +[mdn-regular-expressions]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions +[mdn-common-functions]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#using_regular_expressions_in_javascript +[mdn-flags]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#advanced_searching_with_flags +[mdn-regex-test]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test +[mdn-regex-match]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match +[mdn-regex-replace]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace +[mdn-regex-split]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split +[demystifying-regular-expressions-with-javascript]: https://livecodestream.dev/post/demystifying-regular-expressions-with-javascript/ +[how-to-use-the-js-replace-method-on-a-string]: https://www.freecodecamp.org/news/javascript-regex-match-example-how-to-use-the-js-replace-method-on-a-string/ diff --git a/concepts/regular-expressions/links.json b/concepts/regular-expressions/links.json new file mode 100644 index 0000000000..adf9c1f6bd --- /dev/null +++ b/concepts/regular-expressions/links.json @@ -0,0 +1,22 @@ +[ + { + "url": "https://www.regular-expressions.info/javascript.html", + "description": "Using regular expressions in Javascript" + }, + { + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Cheatsheet", + "description": "MDN: Regex Cheatsheet" + }, + { + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions", + "description": "MDN: Regular Expressions" + }, + { + "url": "https://livecodestream.dev/post/demystifying-regular-expressions-with-javascript/", + "description": "Demystifying regular expressions with JavaScript" + }, + { + "url": "https://www.freecodecamp.org/news/javascript-regex-match-example-how-to-use-the-js-replace-method-on-a-string/", + "description": "How to use the Replace Method" + } +] diff --git a/concepts/template-strings/about.md b/concepts/template-strings/about.md index d00054fb1e..7e54dc9cf9 100644 --- a/concepts/template-strings/about.md +++ b/concepts/template-strings/about.md @@ -22,8 +22,8 @@ All types of expressions can be used with template strings. ```javascript const track = 'JavaScript'; -`This track on exercism.io is ${track.toUpperCase()}.`; -// => This track on exercism.io is JAVASCRIPT. +`This track on exercism.org is ${track.toUpperCase()}.`; +// => This track on exercism.org is JAVASCRIPT. ``` When you are needing to have strings formatted on multiple lines: diff --git a/concepts/template-strings/introduction.md b/concepts/template-strings/introduction.md index d00054fb1e..7e54dc9cf9 100644 --- a/concepts/template-strings/introduction.md +++ b/concepts/template-strings/introduction.md @@ -22,8 +22,8 @@ All types of expressions can be used with template strings. ```javascript const track = 'JavaScript'; -`This track on exercism.io is ${track.toUpperCase()}.`; -// => This track on exercism.io is JAVASCRIPT. +`This track on exercism.org is ${track.toUpperCase()}.`; +// => This track on exercism.org is JAVASCRIPT. ``` When you are needing to have strings formatted on multiple lines: diff --git a/concepts/type-checking/.meta/config.json b/concepts/type-checking/.meta/config.json new file mode 100644 index 0000000000..72261751ff --- /dev/null +++ b/concepts/type-checking/.meta/config.json @@ -0,0 +1,8 @@ +{ + "blurb": "Learn how to check the type of a value or object in JavaScript", + "authors": [ + "quintuple-mallard", + "SleeplessByte" + ], + "contributors": [] +} diff --git a/concepts/type-checking/about.md b/concepts/type-checking/about.md new file mode 100644 index 0000000000..d855cc9dda --- /dev/null +++ b/concepts/type-checking/about.md @@ -0,0 +1,164 @@ +# About + +Knowning what the type of a piece of data is, is often very important for code to run smoothly and without errors. + +Javascript has several ways to check the type of a value or object. + +```exercism/note +Javascript's type checking mechanisms can be somewhat unreliable. + +For better type safety and stronger types, you should probably use TypeScript, a language that builds on JavaScript, but with the type syntax of a static-typed language. +``` + +## The `typeof` operator + +The `typeof` operator returns the type of its operand. +The output is a string matching the name of one of the [primitive data types][primitives], except for `"null"`. +It can also be `"function"` or `"object"`. + +```javascript +typeof undefined; +// => "undefined" + +typeof true; +// => "boolean" + +typeof 42; +// => "number" + +typeof 'Hello, World!'; +// => "string" + +typeof function () { + return 'Hello, World'; +}; +// => "function" + +typeof [1, 2, 3, 4]; +// => "object" + +typeof { city: 'Stockholm', country: 'Sweden' }; +// => "object" +``` + +For [historical reasons][`typeof null` is `"object"`]. + +## The `instanceof` operator + +For checking the type of an object, you can use the `instanceof` operator. +It evaluates into a `boolean` depending on whether the second operand is included in the first operands' [prototype chain][prototype chain]. +To clarify, `instanceof` will return whether the first operand is an instance of second operand or one of its child classes. +`instanceof` only works on objects. + +```javascript +class Beverage { + // ... +} + +// The Coffee class is a child of the Beverage class. +class Coffee extends Beverage { + // ... +} + +const java = new Coffee(); + +java instanceof Coffee; +// => true + +java instanceof Beverage; +// => true +``` + +````exercism/advanced +The `Array` class has a method called `Array.isArray()` that checks if its argument is an array. + +While `instanceof Array` will not work with an array created in a different realm such as an `iframe` in a webpage, `Array.isArray()` will. + +This is because the Array class has a different constructor in each realm, and each `iframe` has its own ream, meaning that the function in the prototype chain will be different, causing `instanceof Array` to fail. +`Array.isArray()` is capable of ignoring this, and should always be used when possible. + +It can also survive false positives where an object isn't actually an `Array`, and merely has `Array` in its prototype chain. + +```javascript +({ __proto__: Array.prototype }) instanceof Array +// => true + +Array.isArray({ __proto__: Array.prototype }) +// => false +``` + +```` + +## The `in` operator + +The `in` operator returns whether the first operand is a property of the second operand. +It does not check that the property has a defined value. +A property set to `undefined` will still be detected by `in`. + +```javascript +class Coffee { + constructor() { + this.temperature = 'hot'; + this.isDarkMatter = undefined; + } + + coolDown() { + this.temperature = 'warm'; + } +} + +const espresso = new Coffee(); + +'temperature' in espresso; +// => true + +'color' in espresso; +// => false + +'isDarkMatter' in espresso; +// => true +``` + +````exercism/note +`in` will return `true` for inherited properties and methods. + +```javascript +"coolDown" in espresso +// => true + +"constructor" in espresso +// => true +``` + +To avoid this, use `Object.hasOwn()` instead +```` + +## The `Object.hasOwn()` function + +The `Object.hasOwn()` method returns whether the specified object _owns the given property_ (it is not inherited or a method). + +```javascript +class Coffee { + constructor() { + this.temperature = 'hot'; + } + + coolDown() { + this.temperature = 'warm'; + } +} +const cappuccino = new Coffee(); + +Object.hasOwn(cappucino, 'temperature'); +// => true + +Object.hasOwn(cappucino, 'constructor'); +// => false + +Object.hasOwn(cappucino, 'coolDown'); +// => false +``` + +[primitives]: https://developer.mozilla.org/en-US/docs/Glossary/Primitive +[typeof null is object]: https://2ality.com/2013/10/typeof-null.html +[prototype chain]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Inheritance_and_the_prototype_chain diff --git a/concepts/type-checking/introduction.md b/concepts/type-checking/introduction.md new file mode 100644 index 0000000000..d855cc9dda --- /dev/null +++ b/concepts/type-checking/introduction.md @@ -0,0 +1,164 @@ +# About + +Knowning what the type of a piece of data is, is often very important for code to run smoothly and without errors. + +Javascript has several ways to check the type of a value or object. + +```exercism/note +Javascript's type checking mechanisms can be somewhat unreliable. + +For better type safety and stronger types, you should probably use TypeScript, a language that builds on JavaScript, but with the type syntax of a static-typed language. +``` + +## The `typeof` operator + +The `typeof` operator returns the type of its operand. +The output is a string matching the name of one of the [primitive data types][primitives], except for `"null"`. +It can also be `"function"` or `"object"`. + +```javascript +typeof undefined; +// => "undefined" + +typeof true; +// => "boolean" + +typeof 42; +// => "number" + +typeof 'Hello, World!'; +// => "string" + +typeof function () { + return 'Hello, World'; +}; +// => "function" + +typeof [1, 2, 3, 4]; +// => "object" + +typeof { city: 'Stockholm', country: 'Sweden' }; +// => "object" +``` + +For [historical reasons][`typeof null` is `"object"`]. + +## The `instanceof` operator + +For checking the type of an object, you can use the `instanceof` operator. +It evaluates into a `boolean` depending on whether the second operand is included in the first operands' [prototype chain][prototype chain]. +To clarify, `instanceof` will return whether the first operand is an instance of second operand or one of its child classes. +`instanceof` only works on objects. + +```javascript +class Beverage { + // ... +} + +// The Coffee class is a child of the Beverage class. +class Coffee extends Beverage { + // ... +} + +const java = new Coffee(); + +java instanceof Coffee; +// => true + +java instanceof Beverage; +// => true +``` + +````exercism/advanced +The `Array` class has a method called `Array.isArray()` that checks if its argument is an array. + +While `instanceof Array` will not work with an array created in a different realm such as an `iframe` in a webpage, `Array.isArray()` will. + +This is because the Array class has a different constructor in each realm, and each `iframe` has its own ream, meaning that the function in the prototype chain will be different, causing `instanceof Array` to fail. +`Array.isArray()` is capable of ignoring this, and should always be used when possible. + +It can also survive false positives where an object isn't actually an `Array`, and merely has `Array` in its prototype chain. + +```javascript +({ __proto__: Array.prototype }) instanceof Array +// => true + +Array.isArray({ __proto__: Array.prototype }) +// => false +``` + +```` + +## The `in` operator + +The `in` operator returns whether the first operand is a property of the second operand. +It does not check that the property has a defined value. +A property set to `undefined` will still be detected by `in`. + +```javascript +class Coffee { + constructor() { + this.temperature = 'hot'; + this.isDarkMatter = undefined; + } + + coolDown() { + this.temperature = 'warm'; + } +} + +const espresso = new Coffee(); + +'temperature' in espresso; +// => true + +'color' in espresso; +// => false + +'isDarkMatter' in espresso; +// => true +``` + +````exercism/note +`in` will return `true` for inherited properties and methods. + +```javascript +"coolDown" in espresso +// => true + +"constructor" in espresso +// => true +``` + +To avoid this, use `Object.hasOwn()` instead +```` + +## The `Object.hasOwn()` function + +The `Object.hasOwn()` method returns whether the specified object _owns the given property_ (it is not inherited or a method). + +```javascript +class Coffee { + constructor() { + this.temperature = 'hot'; + } + + coolDown() { + this.temperature = 'warm'; + } +} +const cappuccino = new Coffee(); + +Object.hasOwn(cappucino, 'temperature'); +// => true + +Object.hasOwn(cappucino, 'constructor'); +// => false + +Object.hasOwn(cappucino, 'coolDown'); +// => false +``` + +[primitives]: https://developer.mozilla.org/en-US/docs/Glossary/Primitive +[typeof null is object]: https://2ality.com/2013/10/typeof-null.html +[prototype chain]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Inheritance_and_the_prototype_chain diff --git a/concepts/type-checking/links.json b/concepts/type-checking/links.json new file mode 100644 index 0000000000..a8e4a31740 --- /dev/null +++ b/concepts/type-checking/links.json @@ -0,0 +1,14 @@ +[ + { + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof", + "description": "MDN: The typeof operator" + }, + { + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof", + "description": "MDN: The instanceof operator" + }, + { + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty", + "description": "MDN: The object.hasOwnProperty() method" + } +] diff --git a/concepts/type-conversion/about.md b/concepts/type-conversion/about.md index e1dd3ea906..ef5db85ecb 100644 --- a/concepts/type-conversion/about.md +++ b/concepts/type-conversion/about.md @@ -161,7 +161,7 @@ When a value is used in a boolean context, JavaScript will apply the same rules ```javascript const num = 0; if (num) { - // this block NOT is executed because 0 is falsy + // this block is NOT executed because 0 is falsy } const name = 'Jin'; diff --git a/concepts/type-conversion/introduction.md b/concepts/type-conversion/introduction.md index f484992f13..6e150efb72 100644 --- a/concepts/type-conversion/introduction.md +++ b/concepts/type-conversion/introduction.md @@ -102,12 +102,12 @@ Coercion to boolean commonly occurs for - the condition of an [if statement][concept-conditionals] - the first operand of the [ternary operator][mdn-ternary] `?` - the operand of the logical NOT operator `!` -- the operands of the logical AND `&&` and OR `||` operators (the result is of the expression is one of the operands, not necessarily a boolean) +- the operands of the logical AND `&&` and OR `||` operators (the result of the expression is one of the operands, not necessarily a boolean) ```javascript const num = 0; if (num) { - // this block NOT is executed because 0 is falsy + // this block is NOT executed because 0 is falsy } ``` diff --git a/config.json b/config.json index 1ea00310b0..ddc35cb96d 100644 --- a/config.json +++ b/config.json @@ -16,13 +16,21 @@ "highlightjs_language": "javascript" }, "test_runner": { - "average_run_time": 5.0 + "average_run_time": 5 }, "files": { - "solution": ["%{kebab_slug}.js"], - "test": ["%{kebab_slug}.spec.js"], - "example": [".meta/proof.ci.js"], - "exemplar": [".meta/exemplar.js"] + "solution": [ + "%{kebab_slug}.js" + ], + "test": [ + "%{kebab_slug}.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ], + "exemplar": [ + ".meta/exemplar.js" + ] }, "exercises": { "concept": [ @@ -30,7 +38,9 @@ "slug": "lasagna", "name": "Lucian's Luscious Lasagna", "uuid": "97bf898a-36fc-47fc-b870-01fc0c7fe554", - "concepts": ["basics"], + "concepts": [ + "basics" + ], "prerequisites": [], "status": "active" }, @@ -38,63 +48,103 @@ "slug": "annalyns-infiltration", "name": "Annalyn's Infiltration", "uuid": "5acafcbb-20a0-45b9-b276-3d167e0de313", - "concepts": ["booleans"], - "prerequisites": ["basics"], + "concepts": [ + "booleans" + ], + "prerequisites": [ + "basics" + ], "status": "beta" }, { "slug": "freelancer-rates", "name": "Freelancer Rates", "uuid": "0aff2fa7-55ea-47e9-af4a-78927d916baf", - "concepts": ["numbers", "arithmetic-operators"], - "prerequisites": ["basics"], + "concepts": [ + "numbers", + "arithmetic-operators" + ], + "prerequisites": [ + "basics" + ], "status": "beta" }, { "slug": "poetry-club-door-policy", "name": "Poetry Club Door Policy", "uuid": "39549583-1889-490a-bf98-ffb2e0aefe44", - "concepts": ["strings"], - "prerequisites": ["basics"], + "concepts": [ + "strings" + ], + "prerequisites": [ + "basics" + ], "status": "beta" }, { "slug": "elyses-enchantments", "name": "Elyses Enchantments", "uuid": "25cb0707-44f8-4800-b993-3fcb2b6d9f61", - "concepts": ["arrays"], - "prerequisites": ["numbers"], + "concepts": [ + "arrays" + ], + "prerequisites": [ + "numbers" + ], "status": "beta" }, { "slug": "vehicle-purchase", "name": "Vehicle Purchase", "uuid": "f75a7e2c-053a-411e-8b3d-f978a2c5100e", - "concepts": ["comparison", "conditionals"], - "prerequisites": ["booleans", "numbers", "strings"], + "concepts": [ + "comparison", + "conditionals" + ], + "prerequisites": [ + "booleans", + "numbers", + "strings" + ], "status": "beta" }, { "slug": "bird-watcher", "name": "Bird Watcher", "uuid": "3e648aa0-9ce7-4041-93d2-2b95f3832824", - "concepts": ["increment-decrement", "for-loops"], - "prerequisites": ["arrays", "comparison", "conditionals"], + "concepts": [ + "increment-decrement", + "for-loops" + ], + "prerequisites": [ + "arrays", + "comparison", + "conditionals" + ], "status": "beta" }, { "slug": "mixed-juices", "name": "Mixed Juices", "uuid": "81c3fb86-af86-4c56-a45f-e021403c4070", - "concepts": ["while-loops", "conditionals-switch"], - "prerequisites": ["comparison", "conditionals", "arrays"], + "concepts": [ + "while-loops", + "conditionals-switch" + ], + "prerequisites": [ + "comparison", + "conditionals", + "arrays" + ], "status": "beta" }, { "slug": "lucky-numbers", "name": "Lucky Numbers", "uuid": "78bbd7d2-b660-4791-a404-af5bfed31849", - "concepts": ["type-conversion"], + "concepts": [ + "type-conversion" + ], "prerequisites": [ "booleans", "strings", @@ -108,12 +158,14 @@ "slug": "elyses-analytic-enchantments", "name": "Elyses Analytic Enchantments", "uuid": "45d956db-d4ef-4468-b1d3-47021f172c15", - "concepts": ["array-analysis"], + "concepts": [ + "arrow-functions", + "array-analysis" + ], "prerequisites": [ "arrays", "booleans", "callbacks", - "arrow-functions", "numbers" ], "status": "beta" @@ -122,19 +174,41 @@ "slug": "elyses-destructured-enchantments", "name": "Elyses Destructured Enchantments", "uuid": "d9b5cd13-2f2b-4034-a571-e66c847ed6f8", - "concepts": ["array-destructuring", "rest-and-spread"], - "prerequisites": ["arrays", "functions", "objects"], + "concepts": [ + "array-destructuring", + "object-destructuring" + ], + "prerequisites": [ + "arrays", + "functions", + "objects" + ], + "status": "beta" + }, + { + "slug": "train-driver", + "name": "Train Driver", + "uuid": "6cef6712-cf1d-4b3e-9ace-1de3450b4285", + "concepts": [ + "rest-and-spread" + ], + "prerequisites": [ + "functions", + "array-destructuring", + "object-destructuring" + ], "status": "beta" }, { "slug": "elyses-looping-enchantments", "name": "Elyses Looping Enchantments", "uuid": "e06f8f70-019f-4cec-924b-3971414e15d9", - "concepts": ["array-loops"], + "concepts": [ + "array-loops" + ], "prerequisites": [ "arrays", "callbacks", - "arrow-functions", "for-loops", "conditionals" ], @@ -152,60 +226,89 @@ "slug": "amusement-park", "name": "Amusement Park", "uuid": "2d40b46b-9e49-431c-913c-81d1f42f74df", - "concepts": ["null-undefined"], - "prerequisites": ["objects"], + "concepts": [ + "null-undefined" + ], + "prerequisites": [ + "objects" + ], "status": "beta" }, { "slug": "pizza-order", "name": "Pizza Order", "uuid": "e9a9fa73-4497-43d5-a4ff-4eb319c98233", - "concepts": ["recursion"], + "concepts": [ + "recursion" + ], "prerequisites": [ "functions", "rest-and-spread", "array-destructuring", "array-transformations" ], - "status": "wip" + "status": "beta" }, { "slug": "coordinate-transformation", "name": "Coordinate Transformation", "uuid": "5aa39e89-c601-4a66-ab72-5d8512d69e02", - "concepts": ["closures"], - "prerequisites": ["arrays", "functions"], + "concepts": [ + "closures" + ], + "prerequisites": [ + "arrays", + "functions" + ], "status": "beta" }, { "slug": "fruit-picker", "name": "Fruit Picker", "uuid": "a6348db8-cc2b-4c53-9f43-3c23248d66f0", - "concepts": ["callbacks", "arrow-functions"], - "prerequisites": ["functions", "objects"], + "concepts": [ + "callbacks" + ], + "prerequisites": [ + "functions", + "objects" + ], "status": "beta" }, { "slug": "translation-service", "name": "Translation Service", "uuid": "4a967656-8615-474e-a009-5c0b09f4386f", - "concepts": ["promises"], - "prerequisites": ["callbacks", "arrow-functions", "errors"], + "concepts": [ + "promises" + ], + "prerequisites": [ + "callbacks", + "arrow-functions", + "errors" + ], "status": "beta" }, { "slug": "high-score-board", "name": "High Score Board", "uuid": "431d1f13-d280-4808-bbc6-a72e628c15c2", - "concepts": ["objects"], - "prerequisites": ["for-loops"], + "concepts": [ + "objects" + ], + "prerequisites": [ + "arithmetic-operators", + "for-loops" + ], "status": "beta" }, { "slug": "ozans-playlist", "name": "Ozan's Playlist", "uuid": "347692fb-7b0f-4ef0-9a02-2192b59bdf5d", - "concepts": ["sets"], + "concepts": [ + "sets" + ], "prerequisites": [ "array-destructuring", "array-loops", @@ -220,29 +323,43 @@ "slug": "lasagna-master", "name": "Lasagna Master", "uuid": "a7e323f1-84d8-43d5-8c26-cc119166b9fd", - "concepts": ["functions"], - "prerequisites": ["objects", "arrays", "null-undefined"], + "concepts": [ + "functions" + ], + "prerequisites": [ + "objects", + "arrays", + "null-undefined" + ], "status": "beta" }, { "slug": "factory-sensors", "name": "Factory Sensors", "uuid": "2ccafa38-2802-44c1-8758-7415edefa909", - "concepts": ["errors", "inheritance"], - "prerequisites": ["classes", "null-undefined", "conditionals"], + "concepts": [ + "errors", + "inheritance" + ], + "prerequisites": [ + "classes", + "null-undefined", + "conditionals" + ], "status": "beta" }, { "slug": "elyses-transformative-enchantments", "name": "Elyses Transformative Enchantments", "uuid": "6e156d67-2bd2-4624-956d-ddcc3795bad5", - "concepts": ["array-transformations"], + "concepts": [ + "array-transformations" + ], "prerequisites": [ "numbers", "arrays", "conditionals", - "callbacks", - "arrow-functions" + "callbacks" ], "status": "beta" }, @@ -250,17 +367,91 @@ "slug": "custom-signs", "name": "Custom Signs", "uuid": "02a9c753-614f-4814-a7a8-43c1971d2eb7", - "concepts": ["conditionals-ternary", "template-strings"], - "prerequisites": ["strings", "conditionals", "type-conversion"], + "concepts": [ + "conditionals-ternary", + "template-strings" + ], + "prerequisites": [ + "strings", + "conditionals", + "type-conversion" + ], "status": "beta" }, { "slug": "windowing-system", "name": "Windowing System", "uuid": "d697850e-fd43-408c-a958-835aa8a510f7", - "concepts": ["classes"], - "prerequisites": ["objects", "functions", "conditionals-ternary"], + "concepts": [ + "classes" + ], + "prerequisites": [ + "objects", + "functions", + "conditionals-ternary" + ], + "status": "beta" + }, + { + "slug": "regular-chatbot", + "name": "Regular Chatbot", + "uuid": "dc118a0d-be89-4ea5-a814-e471897d921a", + "concepts": [ + "regular-expressions" + ], + "prerequisites": [ + "arrays", + "classes", + "objects", + "template-strings" + ], + "status": "beta" + }, + { + "slug": "appointment-time", + "name": "Appointment Time", + "uuid": "0f694053-a388-457f-89ca-f49be4560469", + "concepts": [ + "dates" + ], + "prerequisites": [ + "classes", + "objects", + "conditionals", + "type-conversion" + ], "status": "beta" + }, + { + "slug": "captains-log", + "name": "Captain's Log", + "uuid": "65cf28ab-243c-41cb-a720-f324f2cabe28", + "concepts": [ + "randomness" + ], + "prerequisites": [ + "numbers", + "arithmetic-operators", + "strings", + "objects", + "functions" + ] + }, + { + "slug": "recycling-robot", + "name": "Recycling Robot", + "uuid": "16114449-52fe-470e-af11-cf9dc3689a93", + "concepts": [ + "type-checking" + ], + "prerequisites": [ + "basics", + "errors", + "objects", + "arrays", + "classes", + "inheritance" + ] } ], "practice": [ @@ -283,61 +474,100 @@ "name": "Two Fer", "uuid": "7f49e997-4435-4f34-a020-bddc92c838ed", "practices": [], - "prerequisites": ["strings", "functions"], + "prerequisites": [ + "strings", + "functions" + ], "difficulty": 1, - "topics": ["optional_values", "strings", "text_formatting"] + "topics": [ + "optional_values", + "strings", + "text_formatting" + ] }, { "slug": "resistor-color", "name": "Resistor Color", "uuid": "53be6837-c224-45f1-bff3-d7f74d6285ce", "practices": [], - "prerequisites": ["arrays", "array-analysis"], + "prerequisites": [ + "arrays", + "array-analysis" + ], "difficulty": 1, - "topics": ["arrays", "strings"] + "topics": [ + "arrays", + "strings" + ] }, { "slug": "resistor-color-duo", "name": "Resistor Color Duo", "uuid": "de800041-3dcc-41b9-b101-7314ff685c93", "practices": [], - "prerequisites": ["array-analysis"], + "prerequisites": [ + "array-analysis" + ], "difficulty": 2, - "topics": ["strings", "arrays"] + "topics": [ + "strings", + "arrays" + ] }, { "slug": "gigasecond", "name": "Gigasecond", "uuid": "fd7b62d4-266b-4e84-a526-bf3d47901216", "practices": [], - "prerequisites": ["dates", "numbers", "arithmetic-operators"], + "prerequisites": [ + "numbers", + "arithmetic-operators" + ], "difficulty": 1, - "topics": ["time"] + "topics": [ + "time" + ] }, { "slug": "rna-transcription", - "name": "Rna Transcription", + "name": "RNA Transcription", "uuid": "342974d6-9083-4754-a6c5-ed1e19e40ec5", "practices": [], - "prerequisites": ["strings", "array-transformations", "objects"], + "prerequisites": [ + "strings", + "array-transformations", + "objects" + ], "difficulty": 2, - "topics": ["strings", "transforming"] + "topics": [ + "strings", + "transforming" + ] }, { "slug": "space-age", "name": "Space Age", "uuid": "d9d757ed-ebe6-4d4a-aa73-f6834221cd54", "practices": [], - "prerequisites": ["objects", "numbers", "type-conversion"], + "prerequisites": [ + "objects", + "numbers", + "type-conversion" + ], "difficulty": 2, - "topics": ["floating_point_numbers"] + "topics": [ + "floating_point_numbers" + ] }, { "slug": "pangram", "name": "Pangram", "uuid": "da5b2b34-a1a7-4970-81f9-4665d875398b", "practices": [], - "prerequisites": ["strings", "array-analysis"], + "prerequisites": [ + "strings", + "array-analysis" + ], "difficulty": 2, "topics": [ "algorithms", @@ -374,7 +604,11 @@ "name": "Bob", "uuid": "a5bf36f0-5d3c-41d4-8d54-e37e484e59cd", "practices": [], - "prerequisites": ["strings", "conditionals", "regular-expressions"], + "prerequisites": [ + "strings", + "conditionals", + "regular-expressions" + ], "difficulty": 4, "topics": [ "conditionals", @@ -397,14 +631,23 @@ "functions" ], "difficulty": 5, - "topics": ["algorithms", "conditionals", "loops", "lists", "sorting"] + "topics": [ + "algorithms", + "conditionals", + "loops", + "lists", + "sorting" + ] }, { "slug": "pascals-triangle", - "name": "Pascals Triangle", + "name": "Pascal's Triangle", "uuid": "99493160-4673-402f-acda-62db5378148d", "practices": [], - "prerequisites": ["arrays", "for-loops"], + "prerequisites": [ + "arrays", + "for-loops" + ], "difficulty": 4, "topics": [ "conditionals", @@ -419,7 +662,11 @@ "name": "Linked List", "uuid": "ec60a578-8889-46a1-b7b8-306dbd8551d5", "practices": [], - "prerequisites": ["classes", "conditionals", "while-loops"], + "prerequisites": [ + "classes", + "conditionals", + "while-loops" + ], "difficulty": 5, "topics": [ "algorithms", @@ -436,18 +683,36 @@ "name": "Grade School", "uuid": "64637322-33bc-401f-8cec-1f9810a41f75", "practices": [], - "prerequisites": ["classes", "objects", "array-transformations"], + "prerequisites": [ + "classes", + "objects", + "array-transformations" + ], "difficulty": 5, - "topics": ["arrays", "maps", "sorting"] + "topics": [ + "arrays", + "maps", + "sorting" + ] }, { "slug": "list-ops", "name": "List Ops", "uuid": "7d9db056-5398-41b6-af3b-9707f5eb0dbc", "practices": [], - "prerequisites": ["classes", "arrays", "functions", "recursion"], + "prerequisites": [ + "classes", + "arrays", + "functions", + "recursion" + ], "difficulty": 6, - "topics": ["data_structures", "loops", "lists", "recursion"] + "topics": [ + "data_structures", + "loops", + "lists", + "recursion" + ] }, { "slug": "robot-name", @@ -457,9 +722,7 @@ "prerequisites": [ "classes", "arrays", - "strings", - "randomness", - "codepoints" + "strings" ], "difficulty": 6, "topics": [ @@ -479,9 +742,7 @@ "strings", "arrays", "classes", - "numbers", - "codepoints", - "randomness" + "numbers" ], "difficulty": 6, "topics": [ @@ -500,9 +761,9 @@ "uuid": "9131bdb8-2e0f-4526-b113-8a77712e7216", "practices": [], "prerequisites": [ + "arrow-functions", "strings", "objects", - "arrow-functions", "regular-expressions", "errors" ], @@ -522,7 +783,10 @@ "name": "Secret Handshake", "uuid": "74bbc9e3-edc5-41e0-84d7-5b2d98dd8370", "practices": [], - "prerequisites": ["bit-manipulation", "array-analysis", "errors"], + "prerequisites": [ + "array-analysis", + "errors" + ], "difficulty": 6, "topics": [ "algorithms", @@ -538,18 +802,31 @@ "name": "Leap", "uuid": "193a0e19-462d-4d26-a117-124f07d5a3d7", "practices": [], - "prerequisites": ["arithmetic-operators", "booleans"], - "difficulty": 1, - "topics": ["booleans", "integers", "logic"] + "prerequisites": [ + "arithmetic-operators", + "booleans" + ], + "difficulty": 1, + "topics": [ + "booleans", + "integers", + "logic" + ] }, { "slug": "reverse-string", "name": "Reverse String", "uuid": "e84c97eb-dbec-487c-b99f-ae9924e16293", "practices": [], - "prerequisites": ["strings", "array-transformations"], + "prerequisites": [ + "strings", + "array-transformations" + ], "difficulty": 2, - "topics": ["loops", "strings"] + "topics": [ + "loops", + "strings" + ] }, { "slug": "collatz-conjecture", @@ -578,9 +855,19 @@ "name": "Triangle", "uuid": "ed3ca73a-a0f0-46b8-8013-8b6d20758c8f", "practices": [], - "prerequisites": ["numbers", "booleans", "comparison", "classes"], + "prerequisites": [ + "numbers", + "booleans", + "comparison", + "classes" + ], "difficulty": 3, - "topics": ["conditionals", "loops", "exception_handling", "integers"] + "topics": [ + "conditionals", + "loops", + "exception_handling", + "integers" + ] }, { "slug": "clock", @@ -594,14 +881,21 @@ "strings" ], "difficulty": 5, - "topics": ["dates", "globalization", "time"] + "topics": [ + "dates", + "globalization", + "time" + ] }, { "slug": "meetup", "name": "Meetup", "uuid": "98617798-b49d-4d43-9f65-7131ee73d626", "practices": [], - "prerequisites": ["dates", "array-analysis", "conditionals-switch"], + "prerequisites": [ + "array-analysis", + "conditionals-switch" + ], "difficulty": 7, "topics": [ "conditionals", @@ -614,30 +908,58 @@ }, { "slug": "etl", - "name": "Etl", + "name": "ETL", "uuid": "db16804b-0f63-445d-8beb-99e0f7218d66", "practices": [], - "prerequisites": ["objects", "array-loops", "arrays", "strings"], + "prerequisites": [ + "objects", + "array-loops", + "arrays", + "strings" + ], "difficulty": 2, - "topics": ["loops", "integers", "maps", "transforming"] + "topics": [ + "loops", + "integers", + "maps", + "transforming" + ] }, { "slug": "hamming", "name": "Hamming", "uuid": "d773c4ef-c09e-40e4-a7fe-01456cb4a12a", "practices": [], - "prerequisites": ["comparison", "errors", "for-loops"], + "prerequisites": [ + "comparison", + "errors", + "for-loops" + ], "difficulty": 2, - "topics": ["conditionals", "loops", "equality", "strings"] + "topics": [ + "conditionals", + "loops", + "equality", + "strings" + ] }, { "slug": "raindrops", "name": "Raindrops", "uuid": "f77ac2d1-cf3a-497d-bf04-b484a5a9cb37", "practices": [], - "prerequisites": ["arithmetic-operators", "strings", "conditionals"], + "prerequisites": [ + "arithmetic-operators", + "strings", + "conditionals" + ], "difficulty": 2, - "topics": ["conditionals", "integers", "strings", "transforming"] + "topics": [ + "conditionals", + "integers", + "strings", + "transforming" + ] }, { "slug": "nucleotide-count", @@ -664,106 +986,213 @@ "name": "Scrabble Score", "uuid": "11771d47-1109-4579-a62b-e0b8e9583485", "practices": [], - "prerequisites": [], + "prerequisites": [ + "strings", + "array-transformations" + ], "difficulty": 5, - "topics": ["conditionals", "loops", "maps", "strings"] + "topics": [ + "conditionals", + "loops", + "maps", + "strings" + ] }, { "slug": "allergies", "name": "Allergies", "uuid": "9d33d21c-e695-427f-9f58-dd9498d61318", "practices": [], - "prerequisites": [], + "prerequisites": [ + "classes", + "numbers", + "for-loops", + "arrays" + ], "difficulty": 6, - "topics": ["arrays", "bitwise_operations", "conditionals", "loops"] + "topics": [ + "arrays", + "bitwise_operations", + "conditionals", + "loops" + ] }, { "slug": "word-count", "name": "Word Count", "uuid": "0073ff9a-cd6a-43cf-b8bf-4d5d8117b81b", "practices": [], - "prerequisites": [], - "difficulty": 1, - "topics": ["loops", "lists", "regular_expressions", "strings"] + "prerequisites": [ + "strings", + "for-loops", + "regular-expressions", + "arrays", + "objects" + ], + "difficulty": 4, + "topics": [ + "loops", + "lists", + "regular_expressions", + "strings" + ] }, { "slug": "bank-account", "name": "Bank Account", "uuid": "8beedf70-28b4-4cfb-ab53-309ee6f6aa78", "practices": [], - "prerequisites": [], + "prerequisites": [ + "classes", + "conditionals", + "numbers" + ], "difficulty": 3, - "topics": ["classes", "conditionals"] + "topics": [ + "classes", + "conditionals" + ] }, { "slug": "difference-of-squares", - "name": "Difference Of Squares", + "name": "Difference of Squares", "uuid": "7dfa878c-83a6-48ef-9170-b6633d51d601", "practices": [], - "prerequisites": ["classes", "for-loops"], + "prerequisites": [ + "classes", + "for-loops" + ], "difficulty": 3, - "topics": ["algorithms", "loops", "integers", "math"] + "topics": [ + "algorithms", + "loops", + "integers", + "math" + ] }, { "slug": "perfect-numbers", "name": "Perfect Numbers", "uuid": "c6691fd2-e10d-47df-acbf-3adeac5a2f89", "practices": [], - "prerequisites": [], + "prerequisites": [ + "numbers", + "arrays", + "conditionals", + "errors", + "for-loops" + ], "difficulty": 3, - "topics": ["arrays", "conditionals", "loops", "integers", "math"] + "topics": [ + "arrays", + "conditionals", + "loops", + "integers", + "math" + ] }, { "slug": "complex-numbers", "name": "Complex Numbers", "uuid": "ea9a9a3e-ae6a-470d-8bb4-2afead507f24", "practices": [], - "prerequisites": ["classes", "numbers", "math"], + "prerequisites": [ + "classes", + "numbers" + ], "difficulty": 4, - "topics": ["math"] + "topics": [ + "math" + ] }, { "slug": "luhn", "name": "Luhn", "uuid": "28872cc9-f1ef-487f-9a79-6bf7983148bf", "practices": [], - "prerequisites": [], + "prerequisites": [ + "numbers", + "conditionals", + "array-transformations" + ], "difficulty": 4, - "topics": ["conditionals", "loops", "integers", "strings"] + "topics": [ + "conditionals", + "loops", + "integers", + "strings" + ] }, { "slug": "prime-factors", "name": "Prime Factors", "uuid": "f43cdddf-eea8-4c4a-8359-c69e20ff9661", "practices": [], - "prerequisites": ["while-loops", "conditionals", "arrays"], + "prerequisites": [ + "while-loops", + "conditionals", + "arrays" + ], "difficulty": 4, - "topics": ["algorithms", "conditionals", "loops", "integers", "math"] + "topics": [ + "algorithms", + "conditionals", + "loops", + "integers", + "math" + ] }, { "slug": "grains", "name": "Grains", "uuid": "d003975a-9045-4f03-9ad9-c15db584dc13", "practices": [], - "prerequisites": [], + "prerequisites": [ + "conditionals", + "errors", + "numbers" + ], "difficulty": 5, - "topics": ["loops", "integers"] + "topics": [ + "loops", + "integers" + ] }, { "slug": "pythagorean-triplet", "name": "Pythagorean Triplet", "uuid": "394755a3-c743-4b85-b9b8-387907f4e32d", "practices": [], - "prerequisites": [], + "prerequisites": [ + "classes", + "arrays", + "for-loops", + "numbers", + "conditionals" + ], "difficulty": 5, - "topics": ["algorithms", "conditionals", "loops", "integers", "math"] + "topics": [ + "algorithms", + "conditionals", + "loops", + "integers", + "math" + ] }, { "slug": "palindrome-products", "name": "Palindrome Products", "uuid": "f6799d10-0210-4c73-ac08-d5cac1a00ff3", "practices": [], - "prerequisites": [], + "prerequisites": [ + "classes", + "errors", + "strings", + "objects", + "for-loops", + "arrays", + "conditionals" + ], "difficulty": 7, "topics": [ "algorithms", @@ -787,7 +1216,10 @@ "comparison" ], "difficulty": 1, - "topics": ["filtering", "strings"] + "topics": [ + "filtering", + "strings" + ] }, { "slug": "acronym", @@ -800,7 +1232,12 @@ "array-transformations" ], "difficulty": 2, - "topics": ["loops", "regular_expressions", "strings", "transforming"] + "topics": [ + "loops", + "regular_expressions", + "strings", + "transforming" + ] }, { "slug": "high-scores", @@ -814,23 +1251,37 @@ "classes" ], "difficulty": 2, - "topics": ["arrays"] + "topics": [ + "arrays" + ] }, { "slug": "isogram", "name": "Isogram", "uuid": "3df577af-2854-40ee-b211-9b608dbbad58", "practices": [], - "prerequisites": ["strings", "arrays", "regular-expressions"], + "prerequisites": [ + "strings", + "arrays", + "regular-expressions" + ], "difficulty": 2, - "topics": ["filtering", "strings"] + "topics": [ + "filtering", + "strings" + ] }, { "slug": "matching-brackets", "name": "Matching Brackets", "uuid": "4d456646-3a9b-4393-9558-6b30e5c1039c", "practices": [], - "prerequisites": [], + "prerequisites": [ + "strings", + "arrays", + "conditionals-switch", + "for-loops" + ], "difficulty": 3, "topics": [ "conditionals", @@ -852,7 +1303,10 @@ "regular-expressions" ], "difficulty": 3, - "topics": ["parsing", "transforming"] + "topics": [ + "parsing", + "transforming" + ] }, { "slug": "scale-generator", @@ -861,23 +1315,47 @@ "practices": [], "prerequisites": [], "difficulty": 3, - "topics": ["loops", "pattern_recognition", "strings", "arrays"] + "status": "deprecated", + "topics": [ + "loops", + "pattern_recognition", + "strings", + "arrays" + ] }, { "slug": "series", "name": "Series", "uuid": "5178ae53-5364-46c9-bee3-70e6e8a8c2e3", "practices": [], - "prerequisites": [], + "prerequisites": [ + "arrays", + "strings", + "errors", + "classes", + "conditionals", + "array-transformations" + ], "difficulty": 3, - "topics": ["loops", "exception_handling", "strings", "text_formatting"] + "topics": [ + "loops", + "exception_handling", + "strings", + "text_formatting" + ] }, { "slug": "largest-series-product", "name": "Largest Series Product", "uuid": "1f84305d-ea76-4fe2-9858-3b53576d683d", "practices": [], - "prerequisites": [], + "prerequisites": [ + "strings", + "numbers", + "errors", + "conditionals", + "array-transformations" + ], "difficulty": 7, "topics": [ "conditionals", @@ -894,8 +1372,13 @@ "name": "Transpose", "uuid": "9c140fb7-cc8b-411b-b613-a0e0081a9c3f", "practices": [], - "prerequisites": [], - "difficulty": 1, + "prerequisites": [ + "arrays", + "for-loops", + "strings", + "conditionals" + ], + "difficulty": 3, "topics": [ "arrays", "lists", @@ -910,9 +1393,19 @@ "name": "Grep", "uuid": "78bcbae1-a0f2-460c-ba89-e51844fe9397", "practices": [], - "prerequisites": [], + "prerequisites": [ + "arrays", + "strings", + "for-loops", + "array-analysis", + "template-strings" + ], "difficulty": 4, - "topics": ["files", "searching", "text_formatting"] + "topics": [ + "files", + "searching", + "text_formatting" + ] }, { "slug": "rectangles", @@ -921,14 +1414,24 @@ "practices": [], "prerequisites": [], "difficulty": 4, - "topics": ["arrays", "conditionals", "loops", "matrices", "strings"] + "topics": [ + "arrays", + "conditionals", + "loops", + "matrices", + "strings" + ] }, { "slug": "spiral-matrix", "name": "Spiral Matrix", "uuid": "c1abafcc-0d44-4fb5-afae-bff3ce2e1b39", "practices": [], - "prerequisites": [], + "prerequisites": [ + "arrays", + "for-loops", + "conditionals" + ], "difficulty": 4, "topics": [ "arrays", @@ -940,7 +1443,7 @@ }, { "slug": "ocr-numbers", - "name": "Ocr Numbers", + "name": "OCR Numbers", "uuid": "24c197ee-d492-4083-8615-629cb4b836b2", "practices": [], "prerequisites": [], @@ -955,6 +1458,14 @@ "text_formatting" ] }, + { + "slug": "relative-distance", + "name": "Relative Distance", + "uuid": "c72faac5-8d41-404b-8558-759b94ea22ec", + "practices": [], + "prerequisites": [], + "difficulty": 5 + }, { "slug": "saddle-points", "name": "Saddle Points", @@ -980,7 +1491,11 @@ "practices": [], "prerequisites": [], "difficulty": 8, - "topics": ["domain_specific_languages", "parsing", "stacks"] + "topics": [ + "domain_specific_languages", + "parsing", + "stacks" + ] }, { "slug": "food-chain", @@ -989,20 +1504,26 @@ "practices": [], "prerequisites": [], "difficulty": 4, - "topics": ["algorithms", "text_formatting"] + "topics": [ + "algorithms", + "text_formatting" + ] }, { "slug": "house", "name": "House", "uuid": "a8b7187d-12eb-4efc-b966-87823654ccda", "practices": [], - "prerequisites": [], - "difficulty": 4, - "topics": ["arrays", "conditionals", "loops", "recursion", "strings"] + "prerequisites": [ + "for-loops", + "arrays", + "strings" + ], + "difficulty": 4 }, { "slug": "isbn-verifier", - "name": "Isbn Verifier", + "name": "ISBN Verifier", "uuid": "b902c746-60d1-4645-a5bc-dafadec0ef32", "practices": [], "prerequisites": [], @@ -1057,7 +1578,12 @@ "name": "Twelve Days", "uuid": "64de1776-d5c6-43fe-9abf-7e3aa08ae342", "practices": [], - "prerequisites": ["arrays", "conditionals", "for-loops", "strings"], + "prerequisites": [ + "arrays", + "conditionals", + "for-loops", + "strings" + ], "difficulty": 4, "topics": [ "conditionals", @@ -1072,11 +1598,14 @@ "slug": "promises", "name": "Promises", "uuid": "ad21018c-e538-4ce5-a33a-949b4293df8c", - "core": false, "practices": [], "prerequisites": [], "difficulty": 6, - "topics": ["promises", "error_handling", "higher_order_functions"] + "topics": [ + "promises", + "error_handling", + "higher_order_functions" + ] }, { "slug": "yacht", @@ -1091,21 +1620,26 @@ "sets" ], "difficulty": 4, - "topics": ["arrays", "conditionals", "filtering", "games"] + "topics": [ + "arrays", + "conditionals", + "filtering", + "games" + ] }, { "slug": "beer-song", "name": "Beer Song", "uuid": "6573f168-d8fc-4ccf-a864-1a61f432fae1", "practices": [], - "prerequisites": [ - "arrays", - "conditionals", - "for-loops", - "template-strings" - ], + "prerequisites": [], "difficulty": 5, - "topics": ["conditionals", "loops", "strings"] + "status": "deprecated", + "topics": [ + "conditionals", + "loops", + "strings" + ] }, { "slug": "resistor-color-trio", @@ -1120,7 +1654,10 @@ "template-strings" ], "difficulty": 5, - "topics": ["conditionals", "loops"] + "topics": [ + "conditionals", + "loops" + ] }, { "slug": "dominoes", @@ -1136,7 +1673,9 @@ "while-loops" ], "difficulty": 6, - "topics": ["graph_theory"] + "topics": [ + "graph_theory" + ] }, { "slug": "say", @@ -1149,7 +1688,6 @@ "conditionals", "errors", "for-loops", - "math", "numbers", "strings" ], @@ -1168,7 +1706,11 @@ "name": "Diamond", "uuid": "6a1eee0e-f8d4-446d-9c52-f31c3700af1b", "practices": [], - "prerequisites": ["arrays", "for-loops", "strings"], + "prerequisites": [ + "arrays", + "for-loops", + "strings" + ], "difficulty": 5, "topics": [ "arrays", @@ -1189,12 +1731,15 @@ "arithmetic-operators", "classes", "errors", - "math", "numbers", "while-loops" ], "difficulty": 5, - "topics": ["algorithms", "floating_point_numbers", "math"] + "topics": [ + "algorithms", + "floating_point_numbers", + "math" + ] }, { "slug": "sublist", @@ -1208,23 +1753,41 @@ "conditionals" ], "difficulty": 4, - "topics": ["arrays", "lists"] + "topics": [ + "arrays", + "lists" + ] }, { "slug": "binary-search-tree", "name": "Binary Search Tree", "uuid": "6c4b4e25-c115-4789-9058-d28ab6ca0d26", "practices": [], - "prerequisites": [], + "prerequisites": [ + "classes", + "conditionals", + "recursion" + ], "difficulty": 6, - "topics": ["algorithms", "conditionals", "loops", "recursion"] + "topics": [ + "algorithms", + "conditionals", + "loops", + "recursion" + ] }, { "slug": "custom-set", "name": "Custom Set", "uuid": "75199d72-4cac-49ce-bffb-23fb659c57ae", "practices": [], - "prerequisites": [], + "prerequisites": [ + "arrays", + "array-analysis", + "array-transformations", + "classes", + "conditionals" + ], "difficulty": 6, "topics": [ "arrays", @@ -1242,16 +1805,34 @@ "name": "Binary Search", "uuid": "7c569e5d-bb00-44b8-8adc-34253790c19b", "practices": [], - "prerequisites": [], + "prerequisites": [ + "arrays", + "comparison", + "conditionals", + "errors", + "while-loops" + ], "difficulty": 7, - "topics": ["algorithms", "arrays", "conditionals", "loops", "recursion"] + "topics": [ + "algorithms", + "arrays", + "conditionals", + "loops", + "recursion" + ] }, { "slug": "circular-buffer", "name": "Circular Buffer", "uuid": "bf0b1f95-3425-4345-8a12-3a80d49b49c9", "practices": [], - "prerequisites": [], + "prerequisites": [ + "arrays", + "classes", + "comparison", + "conditionals", + "errors" + ], "difficulty": 8, "topics": [ "arrays", @@ -1267,16 +1848,30 @@ "name": "Simple Linked List", "uuid": "a1591026-2f02-45f9-b189-24b2359eb43f", "practices": [], - "prerequisites": [], + "prerequisites": [ + "arrays", + "classes", + "conditionals", + "for-loops" + ], "difficulty": 8, - "topics": ["arrays", "data_structures", "lists"] + "topics": [ + "arrays", + "data_structures", + "lists" + ] }, { "slug": "word-search", "name": "Word Search", "uuid": "0125f5a7-8883-4553-a6c1-45f19544af5e", "practices": [], - "prerequisites": [], + "prerequisites": [ + "arrays", + "array-loops", + "classes", + "conditionals" + ], "difficulty": 8, "topics": [ "arrays", @@ -1293,16 +1888,33 @@ "name": "Variable Length Quantity", "uuid": "6be39d1f-f68d-4a52-bd72-85a38ffd509e", "practices": [], - "prerequisites": [], + "prerequisites": [ + "arrays", + "array-transformations", + "arrow-functions", + "errors", + "for-loops", + "rest-and-spread" + ], "difficulty": 5, - "topics": ["bitwise_operations", "transforming"] + "topics": [ + "bitwise_operations", + "transforming" + ] }, { "slug": "two-bucket", "name": "Two Bucket", "uuid": "0bc6b478-40a8-47ab-889b-c403b922f7e5", "practices": [], - "prerequisites": [], + "prerequisites": [ + "arithmetic-operators", + "classes", + "conditionals", + "errors", + "objects", + "while-loops" + ], "difficulty": 6, "topics": [ "algorithms", @@ -1319,16 +1931,36 @@ "name": "Alphametics", "uuid": "a602bd33-69fc-4b67-a3d3-95198853095e", "practices": [], - "prerequisites": [], + "prerequisites": [ + "arithmetic-operators", + "array-analysis", + "array-transformations", + "conditionals", + "for-loops", + "functions", + "objects", + "rest-and-spread" + ], "difficulty": 7, - "topics": ["algorithms", "games"] + "topics": [ + "algorithms", + "games" + ] }, { "slug": "connect", "name": "Connect", "uuid": "2fa2c262-77ae-409b-bfd8-1d643faae772", "practices": [], - "prerequisites": [], + "prerequisites": [ + "arrays", + "array-analysis", + "array-loops", + "array-transformations", + "classes", + "conditionals", + "functions" + ], "difficulty": 7, "topics": [ "arrays", @@ -1380,8 +2012,13 @@ "practices": [], "prerequisites": [], "difficulty": 5, - "topics": ["algorithms", "callbacks", "loops", "lists"], - "status": "deprecated" + "status": "deprecated", + "topics": [ + "algorithms", + "callbacks", + "loops", + "lists" + ] }, { "slug": "flatten-array", @@ -1390,7 +2027,10 @@ "practices": [], "prerequisites": [], "difficulty": 5, - "topics": ["arrays", "recursion"] + "topics": [ + "arrays", + "recursion" + ] }, { "slug": "nth-prime", @@ -1415,7 +2055,13 @@ "practices": [], "prerequisites": [], "difficulty": 5, - "topics": ["conditionals", "loops", "integers", "math", "recursion"] + "topics": [ + "conditionals", + "loops", + "integers", + "math", + "recursion" + ] }, { "slug": "rotational-cipher", @@ -1424,11 +2070,16 @@ "practices": [], "prerequisites": [], "difficulty": 2, - "topics": ["conditionals", "strings", "text_formatting", "transforming"] + "topics": [ + "conditionals", + "strings", + "text_formatting", + "transforming" + ] }, { "slug": "diffie-hellman", - "name": "Diffie Hellman", + "name": "Diffie-Hellman", "uuid": "127eccbd-3009-4a8f-95c1-7d8aeb608550", "practices": [], "prerequisites": [], @@ -1449,7 +2100,12 @@ "practices": [], "prerequisites": [], "difficulty": 4, - "topics": ["algorithms", "arrays", "filtering", "math"] + "topics": [ + "algorithms", + "arrays", + "filtering", + "math" + ] }, { "slug": "atbash-cipher", @@ -1506,7 +2162,12 @@ "name": "Kindergarten Garden", "uuid": "13444eff-005a-405e-9737-7b64d99c1a61", "practices": [], - "prerequisites": ["arrays", "classes", "strings", "rest-and-spread"], + "prerequisites": [ + "arrays", + "classes", + "strings", + "rest-and-spread" + ], "difficulty": 7, "topics": [ "arrays", @@ -1518,12 +2179,18 @@ }, { "slug": "sum-of-multiples", - "name": "Sum Of Multiples", + "name": "Sum of Multiples", "uuid": "f7452f71-795b-40b6-847c-67ef4bb9db45", "practices": [], "prerequisites": [], "difficulty": 5, - "topics": ["conditionals", "loops", "integers", "lists", "math"] + "topics": [ + "conditionals", + "loops", + "integers", + "lists", + "math" + ] }, { "slug": "change", @@ -1532,16 +2199,19 @@ "practices": [], "prerequisites": [], "difficulty": 8, - "topics": ["algorithms", "performance", "searching"] - }, - { + "topics": [ + "algorithms", + "performance", + "searching" + ] + }, + { "slug": "point-mutations", "name": "Point Mutations", "uuid": "6d43709b-3809-4a6a-ab41-2a5ab841b5fb", "practices": [], "prerequisites": [], "difficulty": 1, - "topics": null, "status": "deprecated" }, { @@ -1551,7 +2221,12 @@ "practices": [], "prerequisites": [], "difficulty": 1, - "topics": ["algorithms", "conditionals", "loops", "strings"] + "topics": [ + "algorithms", + "conditionals", + "loops", + "strings" + ] }, { "slug": "armstrong-numbers", @@ -1560,20 +2235,29 @@ "practices": [], "prerequisites": [], "difficulty": 2, - "topics": ["algorithms", "math"] + "topics": [ + "algorithms", + "math" + ] }, { "slug": "dnd-character", "name": "D&D Character", "uuid": "b373e13c-f179-4b36-b6e8-2a0f41540344", "practices": [], - "prerequisites": [], + "prerequisites": [ + "randomness", + "classes" + ], "difficulty": 2, - "topics": ["classes", "randomness"] + "topics": [ + "classes", + "randomness" + ] }, { "slug": "run-length-encoding", - "name": "Run Length Encoding", + "name": "Run-Length Encoding", "uuid": "6ac4ad5f-a64a-4646-91c5-169d53f289f9", "practices": [], "prerequisites": [], @@ -1593,9 +2277,13 @@ "name": "Darts", "uuid": "6c64649b-ea81-4118-9e74-a0a55018ffbc", "practices": [], - "prerequisites": [], - "difficulty": 3, - "topics": null + "prerequisites": [ + "arithmetic-operators", + "comparison", + "conditionals", + "numbers" + ], + "difficulty": 3 }, { "slug": "roman-numerals", @@ -1618,6 +2306,7 @@ "practices": [], "prerequisites": [], "difficulty": 4, + "status": "deprecated", "topics": [ "conditionals", "loops", @@ -1626,8 +2315,7 @@ "math", "regular_expressions", "strings" - ], - "status": "deprecated" + ] }, { "slug": "hexadecimal", @@ -1636,6 +2324,7 @@ "practices": [], "prerequisites": [], "difficulty": 4, + "status": "deprecated", "topics": [ "conditionals", "loops", @@ -1643,8 +2332,7 @@ "math", "regular_expressions", "strings" - ], - "status": "deprecated" + ] }, { "slug": "octal", @@ -1653,6 +2341,7 @@ "practices": [], "prerequisites": [], "difficulty": 4, + "status": "deprecated", "topics": [ "conditionals", "loops", @@ -1660,8 +2349,7 @@ "math", "regular_expressions", "strings" - ], - "status": "deprecated" + ] }, { "slug": "square-root", @@ -1670,7 +2358,12 @@ "practices": [], "prerequisites": [], "difficulty": 4, - "topics": ["bitwise_operations", "algorithms", "loops", "math"] + "topics": [ + "bitwise_operations", + "algorithms", + "loops", + "math" + ] }, { "slug": "trinary", @@ -1679,6 +2372,7 @@ "practices": [], "prerequisites": [], "difficulty": 4, + "status": "deprecated", "topics": [ "conditionals", "loops", @@ -1686,8 +2380,7 @@ "math", "regular_expressions", "strings" - ], - "status": "deprecated" + ] }, { "slug": "all-your-base", @@ -1705,6 +2398,19 @@ "parsing" ] }, + { + "slug": "flower-field", + "name": "Flower Field", + "uuid": "cb4da136-db03-44fa-a5c8-5235f273320c", + "practices": [], + "prerequisites": [], + "difficulty": 7, + "topics": [ + "algorithms", + "arrays", + "games" + ] + }, { "slug": "minesweeper", "name": "Minesweeper", @@ -1712,7 +2418,12 @@ "practices": [], "prerequisites": [], "difficulty": 7, - "topics": ["algorithms", "arrays", "games"] + "status": "deprecated", + "topics": [ + "algorithms", + "arrays", + "games" + ] }, { "slug": "queen-attack", @@ -1720,7 +2431,7 @@ "uuid": "007a4cd4-7324-4512-8905-ead0c78146f7", "practices": [], "prerequisites": [], - "difficulty": 8, + "difficulty": 4, "topics": [ "conditionals", "loops", @@ -1738,7 +2449,11 @@ "practices": [], "prerequisites": [], "difficulty": 8, - "topics": ["algorithms", "events", "reactive_programming"] + "topics": [ + "algorithms", + "events", + "reactive_programming" + ] }, { "slug": "zipper", @@ -1747,43 +2462,79 @@ "practices": [], "prerequisites": [], "difficulty": 8, - "topics": ["recursion", "searching", "trees"] + "topics": [ + "recursion", + "searching", + "trees" + ] }, { "slug": "zebra-puzzle", "name": "Zebra Puzzle", "uuid": "3a34cb49-546f-4aae-b49e-7d5b25db7ebb", "practices": [], - "prerequisites": ["arrays", "classes", "for-loops", "maps"], + "prerequisites": [ + "arrays", + "classes", + "for-loops" + ], "difficulty": 7, - "topics": ["algorithms", "logic"] + "topics": [ + "algorithms", + "logic" + ] }, { "slug": "tournament", "name": "Tournament", "uuid": "e5e5363d-4e9a-478f-8b18-e578f3721ac5", "practices": [], - "prerequisites": ["arrays", "strings", "for-loops", "objects"], + "prerequisites": [ + "arrays", + "strings", + "for-loops", + "objects" + ], "difficulty": 3, - "topics": ["parsing", "strings"] + "topics": [ + "parsing", + "strings" + ] }, { "slug": "rail-fence-cipher", "name": "Rail Fence Cipher", "uuid": "5f0352e5-0988-434a-82c7-f6810d41d63a", "practices": [], - "prerequisites": ["arrays", "strings", "for-loops", "conditionals"], + "prerequisites": [ + "arrays", + "strings", + "for-loops", + "conditionals" + ], "difficulty": 3, - "topics": ["parsing", "strings", "iteration"] + "topics": [ + "parsing", + "strings", + "iteration" + ] }, { "slug": "rest-api", - "name": "Rest API", + "name": "REST API", "uuid": "75b4816d-a5d7-4840-92d6-7249c9e8beeb", "practices": [], - "prerequisites": ["arrays", "classes", "array-loops", "objects"], + "prerequisites": [ + "arrays", + "classes", + "array-loops", + "objects" + ], "difficulty": 6, - "topics": ["algorithms", "logic"] + "topics": [ + "algorithms", + "logic" + ] }, { "slug": "go-counting", @@ -1799,7 +2550,10 @@ "conditionals-switch" ], "difficulty": 5, - "topics": ["algorithms", "logic"] + "topics": [ + "algorithms", + "logic" + ] }, { "slug": "knapsack", @@ -1810,11 +2564,13 @@ "arrays", "for-loops", "objects", - "math", "conditionals" ], "difficulty": 5, - "topics": ["algorithms", "logic"] + "topics": [ + "algorithms", + "logic" + ] }, { "slug": "satellite", @@ -1828,8 +2584,142 @@ "conditionals", "recursion" ], - "difficulty": 6, - "topics": [] + "difficulty": 6 + }, + { + "slug": "poker", + "name": "Poker", + "uuid": "e855d32e-9d5d-4342-a3e9-e70cf2329499", + "practices": [], + "prerequisites": [ + "arrays", + "array-loops", + "array-transformations", + "objects", + "conditionals", + "strings" + ], + "difficulty": 7 + }, + { + "slug": "eliuds-eggs", + "name": "Eliud's Eggs", + "uuid": "2a3ecf62-fb5d-4dac-8369-72e7976a8f57", + "practices": [], + "prerequisites": [ + "strings", + "regular-expressions", + "rest-and-spread", + "arrow-functions", + "basics" + ], + "difficulty": 2 + }, + { + "slug": "parallel-letter-frequency", + "name": "Parallel Letter Frequency", + "uuid": "63126f78-ba0d-4271-978d-49e9312f0db2", + "practices": [], + "prerequisites": [ + "strings", + "regular-expressions", + "rest-and-spread", + "arrow-functions", + "basics" + ], + "difficulty": 7 + }, + { + "slug": "bottle-song", + "name": "Bottle Song", + "uuid": "2da8329c-2a92-4232-b104-2a08a76cab60", + "practices": [], + "prerequisites": [ + "arrays", + "conditionals", + "for-loops", + "template-strings" + ], + "difficulty": 5, + "topics": [ + "conditionals", + "loops", + "strings" + ] + }, + { + "slug": "markdown", + "name": "Markdown", + "uuid": "cd666b3a-7114-4ba9-9b2a-7622a2c8c12c", + "practices": [], + "prerequisites": [], + "difficulty": 5 + }, + { + "slug": "micro-blog", + "name": "Micro Blog", + "uuid": "ee771d09-33fb-4450-b9c3-d591a4a90a99", + "practices": [], + "prerequisites": [ + "strings" + ], + "difficulty": 2 + }, + { + "slug": "ledger", + "name": "Ledger", + "uuid": "8716b347-e18f-48a6-b373-426cc4ca98cb", + "practices": [], + "prerequisites": [], + "difficulty": 5 + }, + { + "slug": "state-of-tic-tac-toe", + "name": "State of Tic-Tac-Toe", + "uuid": "b381543d-2bcf-4999-9d44-788a2b39c1a0", + "practices": [], + "prerequisites": [ + "strings", + "array-loops", + "conditionals", + "errors" + ], + "difficulty": 5 + }, + { + "slug": "lens-person", + "name": "Lens Person", + "uuid": "a1e71425-0e7e-442a-9c8e-cc252f440760", + "practices": [], + "prerequisites": [ + "callbacks" + ], + "difficulty": 7, + "topics": [ + "lens", + "classes", + "callbacks" + ] + }, + { + "slug": "killer-sudoku-helper", + "name": "Killer Sudoku Helper", + "uuid": "4b6b00cd-62f8-4e9e-b59d-79f153f8efb5", + "practices": [], + "prerequisites": [ + "conditionals", + "recursion", + "numbers" + ], + "difficulty": 5 + }, + { + "slug": "game-of-life", + "name": "Conway's Game of Life", + "uuid": "e51c01e9-b7b1-4877-939a-6254c4efe338", + "practices": [], + "prerequisites": [], + "difficulty": 2 } ] }, @@ -1839,11 +2729,6 @@ "slug": "arithmetic-operators", "name": "Arithmetic Operators" }, - { - "uuid": "e1b15569-387c-4833-8c3b-9a94e0ee1583", - "slug": "array-analysis", - "name": "Array Analysis" - }, { "uuid": "9f8f96bb-db13-485a-bfe4-6ae3fe2fbf46", "slug": "array-destructuring", @@ -1864,11 +2749,6 @@ "slug": "arrays", "name": "Arrays" }, - { - "uuid": "e7eea65d-5a13-44ee-aae6-113cfb234457", - "slug": "arrow-functions", - "name": "Arrow Functions" - }, { "uuid": "611d6b3d-1241-4432-90f6-8fcffb36917c", "slug": "basics", @@ -1889,16 +2769,46 @@ "slug": "closures", "name": "Closures" }, + { + "uuid": "ef1e44db-5fd0-4550-9135-7522303e7e42", + "slug": "comparison", + "name": "Comparison" + }, { "uuid": "2d0b9f1f-c135-4014-b87c-25b081387002", "slug": "conditionals", "name": "Conditionals" }, + { + "uuid": "a748d628-f0e8-4625-9c90-6dd500b4fd64", + "slug": "dates", + "name": "Date and Time" + }, { "uuid": "95ba7bed-c370-4f20-9173-79cbd5c2416e", "slug": "errors", "name": "Errors" }, + { + "uuid": "be301f37-6cf4-4688-949e-75a8976a91e3", + "slug": "for-loops", + "name": "For Loops" + }, + { + "uuid": "66264917-2d3e-42e9-806c-0fa740852f0f", + "slug": "functions", + "name": "Functions" + }, + { + "uuid": "a2347a19-1e44-449b-9741-94eda00d8ba7", + "slug": "increment-decrement", + "name": "Increment/Decrement" + }, + { + "uuid": "c890e216-5acb-4fb8-8081-61eb78eabe87", + "slug": "inheritance", + "name": "Inheritance" + }, { "uuid": "008f1c88-7c14-48b2-a88d-49ecb5e3b122", "slug": "null-undefined", @@ -1909,15 +2819,25 @@ "slug": "numbers", "name": "Numbers" }, + { + "uuid": "68dd89d3-13b8-49f5-b493-ccfdc82a073c", + "slug": "objects", + "name": "Objects" + }, + { + "uuid": "38bb937b-b9d6-4550-8337-5e4f2623837a", + "slug": "object-destructuring", + "name": "Object Destructuring" + }, { "uuid": "b3aa57d9-74b2-4d04-a673-ae2402630d8b", "slug": "promises", "name": "Promises" }, { - "uuid": "cfbc96fa-717e-4f29-a91d-760ebea88822", - "slug": "recursion", - "name": "Recursion" + "uuid": "e0ff7a2b-d6f7-4513-9589-3e6854e14415", + "slug": "classes", + "name": "Prototypes & Classes" }, { "uuid": "efc895b2-8420-44f1-a385-5c637286f797", @@ -1925,34 +2845,34 @@ "name": "Rest and Spread" }, { - "uuid": "19085ad2-038a-4e08-ad34-47ff2a78fec6", - "slug": "template-strings", - "name": "Template Strings" + "uuid": "e7eea65d-5a13-44ee-aae6-113cfb234457", + "slug": "arrow-functions", + "name": "Arrow Functions" }, { - "uuid": "7d5c1533-c7cf-418e-b0f2-080da1e5bdc5", - "slug": "strings", - "name": "Strings" + "uuid": "e1b15569-387c-4833-8c3b-9a94e0ee1583", + "slug": "array-analysis", + "name": "Array Analysis" }, { - "uuid": "ef1e44db-5fd0-4550-9135-7522303e7e42", - "slug": "comparison", - "name": "Comparison" + "uuid": "cfbc96fa-717e-4f29-a91d-760ebea88822", + "slug": "recursion", + "name": "Recursion" }, { - "uuid": "a2347a19-1e44-449b-9741-94eda00d8ba7", - "slug": "increment-decrement", - "name": "Increment/Decrement" + "uuid": "e5695bba-50c1-4c55-af3e-2444883bd83b", + "slug": "regular-expressions", + "name": "Regular Expressions" }, { - "uuid": "be301f37-6cf4-4688-949e-75a8976a91e3", - "slug": "for-loops", - "name": "For Loops" + "uuid": "84e55c29-d403-4a90-8a2a-9960feae8ff3", + "slug": "sets", + "name": "Sets" }, { - "uuid": "4e68e39a-e36c-4d2d-8714-eb6482e31ff5", - "slug": "while-loops", - "name": "While Loops" + "uuid": "7d5c1533-c7cf-418e-b0f2-080da1e5bdc5", + "slug": "strings", + "name": "Strings" }, { "uuid": "d5d54931-b2a7-4b1a-a593-ad85a2810f2f", @@ -1960,19 +2880,14 @@ "name": "Switch Statement" }, { - "uuid": "68dd89d3-13b8-49f5-b493-ccfdc82a073c", - "slug": "objects", - "name": "Objects" - }, - { - "uuid": "84e55c29-d403-4a90-8a2a-9960feae8ff3", - "slug": "sets", - "name": "Sets" + "uuid": "19085ad2-038a-4e08-ad34-47ff2a78fec6", + "slug": "template-strings", + "name": "Template Strings" }, { - "uuid": "66264917-2d3e-42e9-806c-0fa740852f0f", - "slug": "functions", - "name": "Functions" + "uuid": "168cb8e8-c4f9-4e10-9d79-bffc77b86bbf", + "slug": "conditionals-ternary", + "name": "Ternary Operator" }, { "uuid": "cbad4d23-a9d8-4370-add2-f4416a4df027", @@ -1980,67 +2895,67 @@ "name": "Type Conversion" }, { - "uuid": "168cb8e8-c4f9-4e10-9d79-bffc77b86bbf", - "slug": "conditionals-ternary", - "name": "Ternary Operator" + "uuid": "4e68e39a-e36c-4d2d-8714-eb6482e31ff5", + "slug": "while-loops", + "name": "While Loops" }, { - "uuid": "c890e216-5acb-4fb8-8081-61eb78eabe87", - "slug": "inheritance", - "name": "Inheritance" + "uuid": "ca322d6f-0f7e-4a2d-a058-e98a59cdae93", + "slug": "randomness", + "name": "Randomness" }, { - "uuid": "e0ff7a2b-d6f7-4513-9589-3e6854e14415", - "slug": "classes", - "name": "Prototypes & Classes" + "uuid": "72e51fbe-db98-492e-b155-8ef21623f741", + "slug": "type-checking", + "name": "Type Checking" } ], "key_features": [ { - "icon": "cross-platform", "title": "Runs almost everywhere", - "content": "Build web-pages, write backend, create database scripts, make mobile apps, design CLI-s, and more." + "content": "Build web-pages, write backend, create database scripts, make mobile apps, design CLI-s, and more.", + "icon": "cross-platform" }, { - "icon": "multi-paradigm", "title": "Use any programming style", - "content": "Use prototype-based, object-oriented, functional, or declarative programming styles, and more." + "content": "Use prototype-based, object-oriented, functional, or declarative programming styles, and more.", + "icon": "multi-paradigm" }, { - "icon": "dynamically-typed", "title": "No types required", - "content": "Dynamically and weakly typed by default, gain typed confidence using Flow, JSDoc, or TypeScript." + "content": "Dynamically and weakly typed by default, gain typed confidence using Flow, JSDoc, or TypeScript.", + "icon": "dynamically-typed" }, { - "icon": "concurrency", "title": "Concurrency is safe", - "content": "Async/await, dedicated workers, or state sync in shared workers. No deadlocks or race conditions." + "content": "Async/await, dedicated workers, or state sync in shared workers. No deadlocks or race conditions.", + "icon": "concurrency" }, { - "icon": "tooling", "title": "Largest package registry", - "content": "No need to reinvent the wheel. Build on top of > 1.3 million packages (April, 2020)." + "content": "No need to reinvent the wheel. Build on top of > 1.3 million packages (April, 2020).", + "icon": "tooling" }, { - "icon": "community", "title": "Designed by a committee", - "content": "Frequent updates, following ECMAScript, a general purpose, cross platform, vendor-neutral standard." + "content": "Frequent updates, following ECMAScript, a general purpose, cross platform, vendor-neutral standard.", + "icon": "community" } ], "tags": [ + "execution_mode/interpreted", "paradigm/declarative", "paradigm/functional", "paradigm/imperative", "paradigm/object_oriented", - "typing/dynamic", - "typing/weak", - "execution_mode/interpreted", - "platform/windows", - "platform/mac", - "platform/linux", - "platform/ios", "platform/android", + "platform/ios", + "platform/linux", + "platform/mac", "platform/web", + "platform/windows", + "typing/dynamic", + "typing/weak", "used_for/artificial_intelligence", "used_for/backends", "used_for/cross_platform_development", diff --git a/config/exercise-readme-insert.md b/config/exercise-readme-insert.md index d2fa14273b..7927d0366a 100644 --- a/config/exercise-readme-insert.md +++ b/config/exercise-readme-insert.md @@ -14,7 +14,13 @@ Please `cd` into exercise directory before running all below commands. Install assignment dependencies: ```bash -$ npm install +$ corepack pnpm install +``` + +If `corepack` complains about not being enabled, you can do so by running: + +```bash +corepack enable pnpm ``` ## Making the test suite pass @@ -22,7 +28,7 @@ $ npm install Execute the tests with: ```bash -$ npm test +$ corepack pnpm test ``` In the test suites all tests but the first have been skipped. diff --git a/docs/FUNCTIONS.md b/docs/FUNCTIONS.md new file mode 100644 index 0000000000..a313df3c18 --- /dev/null +++ b/docs/FUNCTIONS.md @@ -0,0 +1,195 @@ +# Functions + +In JavaScript, functions are _first class objects_ which means they can be assigned to variables and properties, be passed to other functions, and returned from functions. They can have properties and methods, just like any other _object_. + +## Input: passing arguments + +JavaScript functions allow you to pass arguments when they are called. Arguments are always [passed by value](https://dev.to/xpbytes/javascript-ruby-and-c-are-not-call-by-reference-23f7). In other words, if a function reassigns a parameter inside the function, the value won't change outside the function. That doesn't mean that passed-in objects cannot be mutated. + +> [!TIP] +> When an object is passed in, the reference to that object is passed in by value. This makes it _seem_ like passing in objects are handled differently, but that's not the case. + +An example of a function that takes two arguments and multiplies them is: + +```javascript +function multiply(a, b) { + return a * b; +} + +multiply(2, 3); +// => 6 +``` + +## Output: return value + +JavaScript can return a single value from a function by using the `return` keyword, followed by an expression. When nothing is returned from a function, the returned value is `undefined`. + +An example of a function that returns a value is: + +```javascript +function multiply(a, b) { + return a * b; +} + +multiply(2, 3); +// => 6 +``` + +## Defining functions + +There are four flavours of functions in JavaScript: + +1. Regular functions that can return anything and always run to completion; +2. Async functions that always return a `Promise`, and can paused and resumed with the `await` operator; +3. Generator functions: that always return a `Generator` object, and can be paused and resumed with the `yield` operator; +4. Async generator functions; that always return an `AsyncGenerator` object, and can be paused and resumed with _both_ the `yield` and `await` operators. + +For each of these four flavours, there are three ways to define them: + +1. using a declaration +2. using an expression +3. using the constructor + +Finally there is some special syntax to define _arrow functions and methods_. + +## Supported definitions + +The tooling used by Exercism to give automated feedback recognizes the following types of declarations and expressions. Definitions using the constructors such as ` Function()` are not supported. + +```javascript +function declaration() { return 42 } +function* declaration() { yield 42 } +async function declaration() { } +async function* declaration() { } + +const named = function () { return 42 } +const named = function* () { return 42 } +const named = async function() { } +const named = async function*() { } +let named = ... +var name = ... + +const arrow = () => { return 42 } +const arrow = () => 42 +const arrow = async () => { } +const arrow = async () => 42 + +collection.assignment = () => {} +collection.assignment = async () => {} + +Object.defineProperty(collection, 'property', { value: () => { return 42 } }) + +const computed = 'name' +const collection = { + shorthand() { return 42 }, + *shorthand() { yield 42 }, + async shorthand() { }, + async *shorthand() { } + + [computed]() { return 42 } + *[computed]() { yield 42 } + async [computed]() { } + async *[computed]() { } + + property: () => { return 42 }, + property: async () => {}, + property: function () { return 42 }, + property: function* () { yield 42 }, + property: async function () {}, + property: async function* () {}, + + [computed]: () => { return 42 }, + [computed]: async () => {}, + [computed]: function () { return 42 }, + [computed]: function* () { yield 42 }, + [computed]: async function () {}, + [computed]: async function* () {} +} + +class Klazz { + get property() { return 42 } + set property(value) { } + + property = () => { return 42 } + property = async () => {} + property = function () { return 42 } + property = function* () { yield 42 } + property = async function () { } + property = async function* () { } + + [computed] = () => { return 42 }, + [computed] = async () => {}, + [computed] = function () { return 42 }, + [computed] = function* () { yield 42 }, + [computed] = async function () {}, + [computed] = async function* () {} + + shorthand() { return 42 } + *shorthand() { return 42 } + async shorthand() { } + async *shorthand() { } + + [computed]() { return 42 } + *[computed]() { yield 42 } + async [computed]() { } + async *[computed]() { } + + static property = () => { return 42 } + static property = async () => {} + static property = function () { return 42 } + static property = function* () { yield 42 } + static property = async function () { } + static property = async function* () { } + + static [computed] = () => { return 42 } + static [computed] = async () => {} + static [computed] = function () { return 42 } + static [computed] = function* () { yield 42 } + static [computed] = async function () { } + static [computed] = async function* () { } + + static shorthand() { return 42 } + static *shorthand() { yield 42 } + static async shorthand() {} + static async *shorthand() {} + + static [computed]() { return 42 } + static *[computed]() { yield 42 } + static async [computed]() { } + static async *[computed]() { } + + #property = () => { } + #shorthand() { } +} + +Klazz.prototype.fn = () => { } +Klazz.prototype.fn = async () => { } +Klazz.prototype.fn = function () { } +Klazz.prototype.fn = function* () { } +Klazz.prototype.fn = async function () { } +Klazz.prototype.fn = async function* () { } + +export default { name: () => {} } +export default { name: async () => {} } +export default { name: function () {} } +export default { name: function* () {} } +export default { name: async function () {} } +export default { name: async function* () {} } + +export default { [computed]: () => {} } +export default { [computed]: async () => {} } +export default { [computed]: function () {} } +export default { [computed]: function* () {} } +export default { [computed]: async function () {} } +export default { [computed]: async function* () {} } + +export default { name() {} } +export default { *name() {} } +export default { async name() {} } +export default { async *name() {} } + +export default { [computed]() {} } +export default { *[computed]() {} } +export default { async [computed]() {} } +export default { async *[computed]() {} } +``` diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md index abe655540d..ee9a3f4818 100644 --- a/docs/INSTALLATION.md +++ b/docs/INSTALLATION.md @@ -3,7 +3,7 @@ This track relies on [Node.js][web-nodejs] throughout to provide a runtime for JavaScript. This means that we assume all execution of JavaScript on your computer will happen using [Node.js][web-nodejs]. -# Track Requirements +## Track Requirements Many machines come pre-installed with [Node.js][web-nodejs] or might have been installed previously, or as a dependency. So before we do anything, we should check if it's already installed: @@ -39,7 +39,7 @@ Both options support Windows, macOS, and Linux. If you don't know what to do, us - We recommend using the **LTS** version. This is also indicated as _recommended_ on the [Node.js][web-nodejs] website "for most users". - Follow the instructions on the webpage and/or during the installer and install [Node.js][web-nodejs]. -### Testing the installation +## Testing the installation After the installer is done, or the package manager has completed, or the binary has been copied and the instructions have been followed, it's good to test if everything is alright. @@ -48,18 +48,19 @@ After the installer is done, or the package manager has completed, or the binary The version should match the one on the website. -**Note**: It is important to open a _new_ terminal window. -Any open terminal windows might not have been refreshed after the installation completed. -This means that the open terminals don't know that a new program was installed. +> [!NOTE] +> It is important to open a _new_ terminal window. +> Any open terminal windows might not have been refreshed after the installation completed. +> This means that the open terminals don't know that a new program was installed. -> _**Help**_: `'node' is not recognised` +> [!IMPORTANT] > _**Help**_: `'node' is not recognised` > > If you've used the official installer, your `PATH` should have been automatically configured, but if your shell has trouble locating your globally installed modules — or if you build Node.js from source — update your `PATH` to include the `npm` binaries. > > On macOS and Linux you may accomplish this by adding the following to either `~/.bash_profile` or `~/.zshrc`: > -> ```bash -> $ export PATH=/usr/local/share/npm/bin:$PATH +> ```shell +> export PATH=/usr/local/share/npm/bin:$PATH > ``` > > On Windows open the start menu and search for "environment variables". @@ -69,35 +70,45 @@ This means that the open terminals don't know that a new program was installed. > > Close any open terminals and open a new one. -### Assignment Requirements +## Enabling corepack + +In order to use a versioned package manager compatible with this track, `corepack` needs to be enabled once: + +```shell +corepack enable pnpm +``` + +## Assignment Requirements Please follow [these instructions][cli-walkthrough] to download the Exercism CLI for your OS. Once the CLI is set up and configured, download the first exercise - `hello-world`: -```bash -$ exercism download --exercise=hello-world --track=javascript +```shell +exercism download --exercise=hello-world --track=javascript ``` Each assignment then needs some tools to run the tests. They can be installed running this command within each assignment directory: -```bash -$ npm install +```shell +corepack pnpm install ``` -If you're concerned about disk space and are okay installing another tool, take a look at [pnpm](https://pnpm.io/), which ensure only one copy of each package-version is ever installed on disk. -In this case, run `pnpm install` instead of `npm install`, and everything should work as expected. +As this track has switched to pnpm, you should not be concerned about disk space. +Take a look at [pnpm](https://pnpm.io/), which ensures only one copy of each package-version is ever installed on disk. -> **But what is npm and why does this work?** +> **But what is corepack and why does this work?** > > You don't need this information to complete the JavaScript track, but if you're eager to understand what just happened, the following paragraphs are for you: > -> This works because `npm` is a package manager that comes bundled with Node.js, which has been installed per the steps above. -> The `npm` command looks for a `package.json` file, which is present in _each_ assignment folder. -> This file lists the `"dependencies"` above, which are then downloaded by `npm` and placed into the `node_modules` folder. +> This works because `corepack` is a tool that comes bundled with Node.js, which has been installed per the steps above. +> It can install the package manager `pnpm`. It configures the system +> The `corepack` command looks for a `package.json` file, which is present in _each_ assignment folder. +> It then checks `packageManager` which matches `pnpm`, so it may continue. If necessary it will upgrade `pnpm` first. +> This file also lists the `"dependencies"` above, which are then downloaded by `pnpm` and placed into a local cache. > -> The scripts in the `package.json` use the binaries from the local `node_modules` folder, and it's these scripts that are used to run the tests, as listed in the `exercise` description. +> The scripts in the `package.json` use the binaries from the local cache, and it's these scripts that are used to run the tests, as listed in the `exercise` description. [web-nodejs]: https://nodejs.org/ [web-nodejs-download]: https://nodejs.org/en/download/ diff --git a/docs/PROGRAMMING_TIPS.md b/docs/PROGRAMMING_TIPS.md new file mode 100644 index 0000000000..9fd443f3f4 --- /dev/null +++ b/docs/PROGRAMMING_TIPS.md @@ -0,0 +1,39 @@ +# Programming Tips + +## Naming A Function + +Picking good function names is an essential part of writing good code. +Consider the following recommendations when you name your function. + +### Choose meaningful function names + +When you name your function, you should be clear on what it does. +For example, the function `get(studentId, subject)` doesn't have a meaningful function name, and therefore, it is not clear what it does. +It might be getting the student's subject grade or getting their enrollment status. +A better name would be `getStudentGrade(studentId, subject)`, as it precisely communicates what the function does. + +### Don't make the function name too long + +Take care not to make a function name too long. +For example, `checkStudentFailOrPassOrMeritOrDistinction(mark)` is an unnecessarily long function name. +The longer the name is, the harder it gets to remember and the more space it takes on the computer screen. + +### Don't make the function name too short + +Conversely, making a function name too short can also confuse readers. +For example, `checkFPMD(mark)` is a bad function name as it isn't immediately obvious what 'FPMD' means. + +### Use the name that everyone understands. + +Your function name should be understandable by everyone who reads your code. +For example, an obscure function name like `DeathStar()` might be amusing to you, but the name doesn't communicate its purpose and can cause confusion for other developers who read your code. + +### Pick one convention for naming operations + +Stick to one convention for naming operations that do the same thing. +For example, if you name the function to get a student's name `getStudentName()`, you should not use a different verb in the function name to get a student's grade (e.g. `retrieveStudentGrade()`). + +### Avoid using the same word for two purposes + +For example, don't keep using `add` when you want to put a group of items into a collection if you already named it on a addition of two numbers. +Use `insert` or `append` instead. diff --git a/docs/REPRESENTER_NORMALIZATIONS.md b/docs/REPRESENTER_NORMALIZATIONS.md new file mode 100644 index 0000000000..152d6d2c81 --- /dev/null +++ b/docs/REPRESENTER_NORMALIZATIONS.md @@ -0,0 +1,85 @@ +# Representer normalizations + +The [JavaScript representer][github-javascript-representer] applies the following normalizations: + +## Remove comments + +All comments are removed. +The following examples are equivalent: + +### Example with comments + +```javascript +/** + * Returns the string literal 'hi there' just to say hello + * @return [String] + */ +function hello(/* nothing*/) { + // just return it + return 'hi there'; +} +``` + +### Example without comments + +```javascript +function hello() { + return 'hi there'; +} +``` + +## Normalize whitespace + +When the code is represented, it's _order_ is significant, but its physical location is not. +This means that whitespace is normalized. +The following examples are equivalent: + +### Example with two-space indentation + +```javascript +function hello() { + return 'hello world'; +} +``` + +### Example with four-space indentation + +```javascript +function hello() { + return 'hello world'; +} +``` + +### Example with interesting indentation + +```javascript +function hello() { + return 'hello world'; +} +``` + +## Normalize identifiers + +Identifiers are normalized to a placeholder value. + +### Before + +```javascript +const MY_CONSTANT = 42; + +function answer(multiplier, addition = 1) { + return MY_CONSTANT * multiplier + addition; +} +``` + +### After + +```javascript +const PLACEHOLDER_1 = 42; + +function PLACEHOLDER_2(PLACEHOLDER_3, PLACEHOLDER_4 = 1) { + return PLACEHOLDER_1 * PLACEHOLDER_3 + PLACEHOLDER_4; +} +``` + +[github-javascript-representer]: https://github.com/exercism/javascript-representer diff --git a/docs/TESTS.md b/docs/TESTS.md index 99b3956ad0..c45bf03a65 100644 --- a/docs/TESTS.md +++ b/docs/TESTS.md @@ -2,21 +2,21 @@ Execute the tests with: -```bash -$ npm run test +```shell +corepack pnpm test ``` Be sure your code follows best practices and coding styles, as other users do, with ESLint, a tool to perform static analysis on your code. Sometimes, tools like this save you some time detecting typos or silly mistakes in your JavaScript code: -```bash -$ npm run lint +```shell +corepack pnpm lint ``` You can also run Jest in "watch" mode, which will re-run your tests automatically when you save changes to the code or test module: -```bash -$ npm run watch +```shell +corepack pnpm watch ``` ## Understanding Skip Tests diff --git a/docs/config.json b/docs/config.json index 6e18069b6b..2439a919d6 100644 --- a/docs/config.json +++ b/docs/config.json @@ -34,6 +34,13 @@ "path": "docs/ROADMAP.md", "title": "Roadmap & Concept List", "blurb": "Priorities for future development of the JavaScript track" + }, + { + "uuid": "eef6eca6-6ee8-4ab5-a97a-3e8eb7785c7e", + "slug": "tips", + "path": "docs/PROGRAMMING_TIPS.md", + "title": "Programming Tips", + "blurb": "General tips on how to write good code" } ] } diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/concept/amusement-park/.docs/instructions.md b/exercises/concept/amusement-park/.docs/instructions.md index 07b704d247..4006090482 100644 --- a/exercises/concept/amusement-park/.docs/instructions.md +++ b/exercises/concept/amusement-park/.docs/instructions.md @@ -97,13 +97,13 @@ const tickets = { '23LA9T41': 'Verena Nardi', }; -ticketStatus(tickets, '23LA9T41'); +simpleTicketStatus(tickets, '23LA9T41'); // => 'Verena Nardi' -ticketStatus(tickets, '0H2AZ123'); +simpleTicketStatus(tickets, '0H2AZ123'); // => 'invalid ticket !!!' -ticketStatus(tickets, 'RE90VAW7'); +simpleTicketStatus(tickets, 'RE90VAW7'); // => 'invalid ticket !!!' ``` diff --git a/exercises/concept/amusement-park/.eslintrc b/exercises/concept/amusement-park/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/concept/amusement-park/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/concept/amusement-park/.gitignore b/exercises/concept/amusement-park/.gitignore index bdb912f98a..0c88ff6ec3 100644 --- a/exercises/concept/amusement-park/.gitignore +++ b/exercises/concept/amusement-park/.gitignore @@ -1,3 +1,5 @@ -node_modules -yarn-error.log - +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/concept/amusement-park/.meta/config.json b/exercises/concept/amusement-park/.meta/config.json index dfd9ffda66..a3855121db 100644 --- a/exercises/concept/amusement-park/.meta/config.json +++ b/exercises/concept/amusement-park/.meta/config.json @@ -1,11 +1,26 @@ { - "blurb": "Learn about undefined and null by managing visitors and tickets at an amusement park.", - "authors": ["junedev"], - "contributors": [], + "authors": [ + "junedev" + ], "files": { - "solution": ["amusement-park.js"], - "test": ["amusement-park.spec.js"], - "exemplar": [".meta/exemplar.js"] + "solution": [ + "amusement-park.js" + ], + "test": [ + "amusement-park.spec.js" + ], + "exemplar": [ + ".meta/exemplar.js" + ] }, - "forked_from": ["ruby/amusement-park"] + "forked_from": [ + "ruby/amusement-park" + ], + "blurb": "Learn about undefined and null by managing visitors and tickets at an amusement park.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": true, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/concept/amusement-park/.meta/design.md b/exercises/concept/amusement-park/.meta/design.md index ed3827a633..d974fff324 100644 --- a/exercises/concept/amusement-park/.meta/design.md +++ b/exercises/concept/amusement-park/.meta/design.md @@ -32,26 +32,22 @@ This exercise could benefit from the following rules in the [analyzer][analyzer] The comment types mentioned below only serve as a proposal. 1. `createVisitor` - - `actionable`: If the student used a helper variable, give feedback that the result can be returned directly. - `celebratory`: If the student used classes, celebrate but let them know it is not necessary throughout this exercise. - `informative`: If the student did not use the short-hand notation but wrote `name: name` etc instead, let them know how to shorten that. The solution should be accepted nevertheless. 2. `revokeTicket` - - `essential`: Check the ticketId field is not deleted and re-added. - `celebratory`: If they used a method on a visitor class, celebrate but let them know it is not necessary for this exercise. 3. `ticketStatus` - - `essential`: Using a type switch should be discouraged since it is confusing to read because of the `typeof null === 'object'` quirk. - `informative`: If the student did not use early returns, maybe let them know about this alternative. - `celebratory`: Congratulate if the student used a template string for the "sold" case - `celebratory`: Congratulate if the student used a `value` helper variable. 4. `simpleTicketStatus` - - `essential`: Check `??` was used and not an if-statement or something else. - `actionable`: If the student used a helper variable, give feedback that the result can be returned directly. diff --git a/exercises/concept/amusement-park/amusement-park.js b/exercises/concept/amusement-park/amusement-park.js index 93f397df61..e7d9cc19da 100644 --- a/exercises/concept/amusement-park/amusement-park.js +++ b/exercises/concept/amusement-park/amusement-park.js @@ -10,7 +10,7 @@ * @returns {Visitor} the visitor that was created */ export function createVisitor(name, age, ticketId) { - throw new Error('Please implement the createVisitor function.'); + throw new Error('Remove this line and implement the function'); } /** @@ -20,7 +20,7 @@ export function createVisitor(name, age, ticketId) { * @returns {Visitor} the visitor without a ticket */ export function revokeTicket(visitor) { - throw new Error('Please implement the revokeTicket function.'); + throw new Error('Remove this line and implement the function'); } /** @@ -31,7 +31,7 @@ export function revokeTicket(visitor) { * @returns {string} ticket status */ export function ticketStatus(tickets, ticketId) { - throw new Error('Please implement the ticketStatus function.'); + throw new Error('Remove this line and implement the function'); } /** @@ -43,7 +43,7 @@ export function ticketStatus(tickets, ticketId) { * @returns {string} ticket status */ export function simpleTicketStatus(tickets, ticketId) { - throw new Error('Please implement the simpleTicketStatus function.'); + throw new Error('Remove this line and implement the function'); } /** @@ -53,5 +53,5 @@ export function simpleTicketStatus(tickets, ticketId) { * @returns {string | undefined} version */ export function gtcVersion(visitor) { - throw new Error('Please implement the gtcVersion function.'); + throw new Error('Remove this line and implement the function'); } diff --git a/exercises/concept/amusement-park/amusement-park.spec.js b/exercises/concept/amusement-park/amusement-park.spec.js index 6b0e7aedae..c25f8d9fc1 100644 --- a/exercises/concept/amusement-park/amusement-park.spec.js +++ b/exercises/concept/amusement-park/amusement-park.spec.js @@ -1,9 +1,10 @@ +import { describe, expect, test } from '@jest/globals'; import { createVisitor, + gtcVersion, revokeTicket, - ticketStatus, simpleTicketStatus, - gtcVersion, + ticketStatus, } from './amusement-park'; describe('createVisitor', () => { diff --git a/exercises/concept/amusement-park/babel.config.js b/exercises/concept/amusement-park/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/concept/amusement-park/babel.config.js +++ b/exercises/concept/amusement-park/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/concept/amusement-park/eslint.config.mjs b/exercises/concept/amusement-park/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/concept/amusement-park/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/concept/amusement-park/jest.config.js b/exercises/concept/amusement-park/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/concept/amusement-park/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/concept/amusement-park/package.json b/exercises/concept/amusement-park/package.json index fbf1fb78dc..9119422911 100644 --- a/exercises/concept/amusement-park/package.json +++ b/exercises/concept/amusement-park/package.json @@ -10,22 +10,25 @@ "directory": "exercises/concept/amusement-park" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/concept/annalyns-infiltration/.eslintrc b/exercises/concept/annalyns-infiltration/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/concept/annalyns-infiltration/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/concept/annalyns-infiltration/.gitignore b/exercises/concept/annalyns-infiltration/.gitignore index bdb912f98a..0c88ff6ec3 100644 --- a/exercises/concept/annalyns-infiltration/.gitignore +++ b/exercises/concept/annalyns-infiltration/.gitignore @@ -1,3 +1,5 @@ -node_modules -yarn-error.log - +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/concept/annalyns-infiltration/.meta/config.json b/exercises/concept/annalyns-infiltration/.meta/config.json index 9045c57620..13b0f8c910 100644 --- a/exercises/concept/annalyns-infiltration/.meta/config.json +++ b/exercises/concept/annalyns-infiltration/.meta/config.json @@ -1,11 +1,27 @@ { - "blurb": "Help Annalyn free her best friend using boolean logic in JavaScript", - "authors": ["ovidiu141"], - "contributors": ["rishiosaur", "SleeplessByte"], + "authors": [ + "ovidiu141" + ], + "contributors": [ + "rishiosaur", + "SleeplessByte" + ], "files": { - "solution": ["annalyns-infiltration.js"], - "test": ["annalyns-infiltration.spec.js"], - "exemplar": [".meta/exemplar.js"] + "solution": [ + "annalyns-infiltration.js" + ], + "test": [ + "annalyns-infiltration.spec.js" + ], + "exemplar": [ + ".meta/exemplar.js" + ] }, - "forked_from": [] + "blurb": "Help Annalyn free her best friend using boolean logic in JavaScript", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": true, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/concept/annalyns-infiltration/.meta/exemplar.js b/exercises/concept/annalyns-infiltration/.meta/exemplar.js index a0cec712e3..1d096c536c 100644 --- a/exercises/concept/annalyns-infiltration/.meta/exemplar.js +++ b/exercises/concept/annalyns-infiltration/.meta/exemplar.js @@ -69,7 +69,7 @@ export function canFreePrisoner( knightIsAwake, archerIsAwake, prisonerIsAwake, - petDogIsPresent + petDogIsPresent, ) { return ( (!knightIsAwake && !archerIsAwake && prisonerIsAwake) || diff --git a/exercises/concept/annalyns-infiltration/annalyns-infiltration.js b/exercises/concept/annalyns-infiltration/annalyns-infiltration.js index 2d5c10213a..0c3e4adfc9 100644 --- a/exercises/concept/annalyns-infiltration/annalyns-infiltration.js +++ b/exercises/concept/annalyns-infiltration/annalyns-infiltration.js @@ -27,7 +27,7 @@ * @return {boolean} Whether or not you can execute a fast attack. */ export function canExecuteFastAttack(knightIsAwake) { - throw new Error('Implement the canExecuteFastAttack function'); + throw new Error('Remove this line and implement the function'); } /** @@ -40,7 +40,7 @@ export function canExecuteFastAttack(knightIsAwake) { * @returns {boolean} Whether or not you can spy on someone. */ export function canSpy(knightIsAwake, archerIsAwake, prisonerIsAwake) { - throw new Error('Implement the canSpy function'); + throw new Error('Remove this line and implement the function'); } /** @@ -52,7 +52,7 @@ export function canSpy(knightIsAwake, archerIsAwake, prisonerIsAwake) { * @returns {boolean} Whether or not you can send a signal to the prisoner. */ export function canSignalPrisoner(archerIsAwake, prisonerIsAwake) { - throw new Error('Implement the canSignalPrisoner function'); + throw new Error('Remove this line and implement the function'); } /** @@ -69,7 +69,7 @@ export function canFreePrisoner( knightIsAwake, archerIsAwake, prisonerIsAwake, - petDogIsPresent + petDogIsPresent, ) { - throw new Error('Implement the canFreePrisoner function'); + throw new Error('Remove this line and implement the function'); } diff --git a/exercises/concept/annalyns-infiltration/annalyns-infiltration.spec.js b/exercises/concept/annalyns-infiltration/annalyns-infiltration.spec.js index abec916d66..d373447db8 100644 --- a/exercises/concept/annalyns-infiltration/annalyns-infiltration.spec.js +++ b/exercises/concept/annalyns-infiltration/annalyns-infiltration.spec.js @@ -1,411 +1,410 @@ +import { describe, expect, test } from '@jest/globals'; import { canExecuteFastAttack, - canSpy, - canSignalPrisoner, canFreePrisoner, + canSignalPrisoner, + canSpy, } from './annalyns-infiltration'; -describe("Annalyn's infiltration", () => { - describe('can execute fast attack', () => { - test('when the knight is awake', () => { - const knightIsAwake = true; - const expected = false; +describe('can execute fast attack', () => { + test('when the knight is awake', () => { + const knightIsAwake = true; + const expected = false; + + expect(canExecuteFastAttack(knightIsAwake)).toBe(expected); + }); + + test('when the knight is asleep', () => { + const knightIsAwake = false; + const expected = true; + + expect(canExecuteFastAttack(knightIsAwake)).toBe(expected); + }); +}); + +describe('can spy', () => { + test('when everyone is asleep', () => { + const knightIsAwake = false; + const archerIsAwake = false; + const prisonerIsAwake = false; + const expected = false; + + expect(canSpy(knightIsAwake, archerIsAwake, prisonerIsAwake)).toBe( + expected, + ); + }); + + test('when only the prisoner is awake', () => { + const knightIsAwake = false; + const archerIsAwake = false; + const prisonerIsAwake = true; + const expected = true; + + expect(canSpy(knightIsAwake, archerIsAwake, prisonerIsAwake)).toBe( + expected, + ); + }); + + test('when only the archer is awake', () => { + const knightIsAwake = false; + const archerIsAwake = true; + const prisonerIsAwake = false; + const expected = true; + + expect(canSpy(knightIsAwake, archerIsAwake, prisonerIsAwake)).toBe( + expected, + ); + }); + + test('when only the knight is asleep', () => { + const knightIsAwake = false; + const archerIsAwake = true; + const prisonerIsAwake = true; + const expected = true; + + expect(canSpy(knightIsAwake, archerIsAwake, prisonerIsAwake)).toBe( + expected, + ); + }); + + test('when only the knight is awake', () => { + const knightIsAwake = true; + const archerIsAwake = false; + const prisonerIsAwake = false; + const expected = true; + + expect(canSpy(knightIsAwake, archerIsAwake, prisonerIsAwake)).toBe( + expected, + ); + }); + + test('when only the archer is asleep', () => { + const knightIsAwake = true; + const archerIsAwake = false; + const prisonerIsAwake = true; + const expected = true; + + expect(canSpy(knightIsAwake, archerIsAwake, prisonerIsAwake)).toBe( + expected, + ); + }); + + test('when everyone is awake', () => { + const knightIsAwake = true; + const archerIsAwake = true; + const prisonerIsAwake = true; + const expected = true; + + expect(canSpy(knightIsAwake, archerIsAwake, prisonerIsAwake)).toBe( + expected, + ); + }); +}); + +describe('can signal prisoner', () => { + test('when everyone is asleep', () => { + const archerIsAwake = false; + const prisonerIsAwake = false; + const expected = false; + + expect(canSignalPrisoner(archerIsAwake, prisonerIsAwake)).toBe(expected); + }); + + test('when only the prisoner is awake', () => { + const archerIsAwake = false; + const prisonerIsAwake = true; + const expected = true; + + expect(canSignalPrisoner(archerIsAwake, prisonerIsAwake)).toBe(expected); + }); + + test('when only the archer is awake', () => { + const archerIsAwake = true; + const prisonerIsAwake = false; + const expected = false; + + expect(canSignalPrisoner(archerIsAwake, prisonerIsAwake)).toBe(expected); + }); + + test('when everyone is awake', () => { + const archerIsAwake = true; + const prisonerIsAwake = true; + const expected = false; + + expect(canSignalPrisoner(archerIsAwake, prisonerIsAwake)).toBe(expected); + }); +}); - expect(canExecuteFastAttack(knightIsAwake)).toBe(expected); - }); +describe('can free prisoner', () => { + test('when everyone is asleep and pet dog is not present', () => { + const knightIsAwake = false; + const archerIsAwake = false; + const prisonerIsAwake = false; + const petDogIsPresent = false; + const expected = false; + + expect( + canFreePrisoner( + knightIsAwake, + archerIsAwake, + prisonerIsAwake, + petDogIsPresent, + ), + ).toBe(expected); + }); - test('when the knight is asleep', () => { - const knightIsAwake = false; - const expected = true; + test('when everyone is asleep and pet dog is present', () => { + const knightIsAwake = false; + const archerIsAwake = false; + const prisonerIsAwake = false; + const petDogIsPresent = true; + const expected = true; + + expect( + canFreePrisoner( + knightIsAwake, + archerIsAwake, + prisonerIsAwake, + petDogIsPresent, + ), + ).toBe(expected); + }); - expect(canExecuteFastAttack(knightIsAwake)).toBe(expected); - }); + test('when only the prisoner is awake and pet dog is not present', () => { + const knightIsAwake = false; + const archerIsAwake = false; + const prisonerIsAwake = true; + const petDogIsPresent = false; + const expected = true; + + expect( + canFreePrisoner( + knightIsAwake, + archerIsAwake, + prisonerIsAwake, + petDogIsPresent, + ), + ).toBe(expected); }); - describe('can spy', () => { - test('when everyone is asleep', () => { - const knightIsAwake = false; - const archerIsAwake = false; - const prisonerIsAwake = false; - const expected = false; - - expect(canSpy(knightIsAwake, archerIsAwake, prisonerIsAwake)).toBe( - expected - ); - }); - - test('when only the prisoner is awake', () => { - const knightIsAwake = false; - const archerIsAwake = false; - const prisonerIsAwake = true; - const expected = true; - - expect(canSpy(knightIsAwake, archerIsAwake, prisonerIsAwake)).toBe( - expected - ); - }); - - test('when only the archer is awake', () => { - const knightIsAwake = false; - const archerIsAwake = true; - const prisonerIsAwake = false; - const expected = true; - - expect(canSpy(knightIsAwake, archerIsAwake, prisonerIsAwake)).toBe( - expected - ); - }); - - test('when only the knight is asleep', () => { - const knightIsAwake = false; - const archerIsAwake = true; - const prisonerIsAwake = true; - const expected = true; - - expect(canSpy(knightIsAwake, archerIsAwake, prisonerIsAwake)).toBe( - expected - ); - }); - - test('when only the knight is awake', () => { - const knightIsAwake = true; - const archerIsAwake = false; - const prisonerIsAwake = false; - const expected = true; - - expect(canSpy(knightIsAwake, archerIsAwake, prisonerIsAwake)).toBe( - expected - ); - }); - - test('when only the archer is asleep', () => { - const knightIsAwake = true; - const archerIsAwake = false; - const prisonerIsAwake = true; - const expected = true; - - expect(canSpy(knightIsAwake, archerIsAwake, prisonerIsAwake)).toBe( - expected - ); - }); - - test('when everyone is awake', () => { - const knightIsAwake = true; - const archerIsAwake = true; - const prisonerIsAwake = true; - const expected = true; - - expect(canSpy(knightIsAwake, archerIsAwake, prisonerIsAwake)).toBe( - expected - ); - }); + test('when only the prisoner is awake and pet dog is present', () => { + const knightIsAwake = false; + const archerIsAwake = false; + const prisonerIsAwake = true; + const petDogIsPresent = true; + const expected = true; + + expect( + canFreePrisoner( + knightIsAwake, + archerIsAwake, + prisonerIsAwake, + petDogIsPresent, + ), + ).toBe(expected); }); - describe('can signal prisoner', () => { - test('when everyone is asleep', () => { - const archerIsAwake = false; - const prisonerIsAwake = false; - const expected = false; + test('when only the archer is awake and pet dog is not present', () => { + const knightIsAwake = false; + const archerIsAwake = true; + const prisonerIsAwake = false; + const petDogIsPresent = false; + const expected = false; + + expect( + canFreePrisoner( + knightIsAwake, + archerIsAwake, + prisonerIsAwake, + petDogIsPresent, + ), + ).toBe(expected); + }); - expect(canSignalPrisoner(archerIsAwake, prisonerIsAwake)).toBe(expected); - }); + test('when only the archer is awake and pet dog is present', () => { + const knightIsAwake = false; + const archerIsAwake = true; + const prisonerIsAwake = false; + const petDogIsPresent = true; + const expected = false; + + expect( + canFreePrisoner( + knightIsAwake, + archerIsAwake, + prisonerIsAwake, + petDogIsPresent, + ), + ).toBe(expected); + }); - test('when only the prisoner is awake', () => { - const archerIsAwake = false; - const prisonerIsAwake = true; - const expected = true; + test('when only the knight is asleep and pet dog is not present', () => { + const knightIsAwake = false; + const archerIsAwake = true; + const prisonerIsAwake = true; + const petDogIsPresent = false; + const expected = false; + + expect( + canFreePrisoner( + knightIsAwake, + archerIsAwake, + prisonerIsAwake, + petDogIsPresent, + ), + ).toBe(expected); + }); - expect(canSignalPrisoner(archerIsAwake, prisonerIsAwake)).toBe(expected); - }); + test('when only the knight is asleep and pet dog is present', () => { + const knightIsAwake = false; + const archerIsAwake = true; + const prisonerIsAwake = true; + const petDogIsPresent = true; + const expected = false; + + expect( + canFreePrisoner( + knightIsAwake, + archerIsAwake, + prisonerIsAwake, + petDogIsPresent, + ), + ).toBe(expected); + }); - test('when only the archer is awake', () => { - const archerIsAwake = true; - const prisonerIsAwake = false; - const expected = false; + test('when only the knight is awake and pet dog is not present', () => { + const knightIsAwake = true; + const archerIsAwake = false; + const prisonerIsAwake = false; + const petDogIsPresent = false; + const expected = false; + + expect( + canFreePrisoner( + knightIsAwake, + archerIsAwake, + prisonerIsAwake, + petDogIsPresent, + ), + ).toBe(expected); + }); - expect(canSignalPrisoner(archerIsAwake, prisonerIsAwake)).toBe(expected); - }); + test('when only the knight is awake and pet dog is present', () => { + const knightIsAwake = true; + const archerIsAwake = false; + const prisonerIsAwake = false; + const petDogIsPresent = true; + const expected = true; + + expect( + canFreePrisoner( + knightIsAwake, + archerIsAwake, + prisonerIsAwake, + petDogIsPresent, + ), + ).toBe(expected); + }); - test('when everyone is awake', () => { - const archerIsAwake = true; - const prisonerIsAwake = true; - const expected = false; + test('when only the archer is asleep and pet dog is not present', () => { + const knightIsAwake = true; + const archerIsAwake = false; + const prisonerIsAwake = true; + const petDogIsPresent = false; + const expected = false; + + expect( + canFreePrisoner( + knightIsAwake, + archerIsAwake, + prisonerIsAwake, + petDogIsPresent, + ), + ).toBe(expected); + }); + + test('when only the archer is asleep and pet dog is present', () => { + const knightIsAwake = true; + const archerIsAwake = false; + const prisonerIsAwake = true; + const petDogIsPresent = true; + const expected = true; + + expect( + canFreePrisoner( + knightIsAwake, + archerIsAwake, + prisonerIsAwake, + petDogIsPresent, + ), + ).toBe(expected); + }); + + test('when only the prisoner is asleep and pet dog is not present', () => { + const knightIsAwake = true; + const archerIsAwake = true; + const prisonerIsAwake = false; + const petDogIsPresent = false; + const expected = false; + + expect( + canFreePrisoner( + knightIsAwake, + archerIsAwake, + prisonerIsAwake, + petDogIsPresent, + ), + ).toBe(expected); + }); + + test('when only the prisoner is asleep and pet dog is present', () => { + const knightIsAwake = true; + const archerIsAwake = true; + const prisonerIsAwake = false; + const petDogIsPresent = true; + const expected = false; + + expect( + canFreePrisoner( + knightIsAwake, + archerIsAwake, + prisonerIsAwake, + petDogIsPresent, + ), + ).toBe(expected); + }); - expect(canSignalPrisoner(archerIsAwake, prisonerIsAwake)).toBe(expected); - }); + test('when everyone is awake and pet dog is not present', () => { + const knightIsAwake = true; + const archerIsAwake = true; + const prisonerIsAwake = true; + const petDogIsPresent = false; + const expected = false; + + expect( + canFreePrisoner( + knightIsAwake, + archerIsAwake, + prisonerIsAwake, + petDogIsPresent, + ), + ).toBe(expected); }); - describe('can free prisoner', () => { - test('when everyone is asleep and pet dog is not present', () => { - const knightIsAwake = false; - const archerIsAwake = false; - const prisonerIsAwake = false; - const petDogIsPresent = false; - const expected = false; - - expect( - canFreePrisoner( - knightIsAwake, - archerIsAwake, - prisonerIsAwake, - petDogIsPresent - ) - ).toBe(expected); - }); - - test('when everyone is asleep and pet dog is present', () => { - const knightIsAwake = false; - const archerIsAwake = false; - const prisonerIsAwake = false; - const petDogIsPresent = true; - const expected = true; - - expect( - canFreePrisoner( - knightIsAwake, - archerIsAwake, - prisonerIsAwake, - petDogIsPresent - ) - ).toBe(expected); - }); - - test('when only the prisoner is awake and pet dog is not present', () => { - const knightIsAwake = false; - const archerIsAwake = false; - const prisonerIsAwake = true; - const petDogIsPresent = false; - const expected = true; - - expect( - canFreePrisoner( - knightIsAwake, - archerIsAwake, - prisonerIsAwake, - petDogIsPresent - ) - ).toBe(expected); - }); - - test('when only the prisoner is awake and pet dog is present', () => { - const knightIsAwake = false; - const archerIsAwake = false; - const prisonerIsAwake = true; - const petDogIsPresent = true; - const expected = true; - - expect( - canFreePrisoner( - knightIsAwake, - archerIsAwake, - prisonerIsAwake, - petDogIsPresent - ) - ).toBe(expected); - }); - - test('when only the archer is awake and pet dog is not present', () => { - const knightIsAwake = false; - const archerIsAwake = true; - const prisonerIsAwake = false; - const petDogIsPresent = false; - const expected = false; - - expect( - canFreePrisoner( - knightIsAwake, - archerIsAwake, - prisonerIsAwake, - petDogIsPresent - ) - ).toBe(expected); - }); - - test('when only the archer is awake and pet dog is present', () => { - const knightIsAwake = false; - const archerIsAwake = true; - const prisonerIsAwake = false; - const petDogIsPresent = true; - const expected = false; - - expect( - canFreePrisoner( - knightIsAwake, - archerIsAwake, - prisonerIsAwake, - petDogIsPresent - ) - ).toBe(expected); - }); - - test('when only the knight is asleep and pet dog is not present', () => { - const knightIsAwake = false; - const archerIsAwake = true; - const prisonerIsAwake = true; - const petDogIsPresent = false; - const expected = false; - - expect( - canFreePrisoner( - knightIsAwake, - archerIsAwake, - prisonerIsAwake, - petDogIsPresent - ) - ).toBe(expected); - }); - - test('when only the knight is asleep and pet dog is present', () => { - const knightIsAwake = false; - const archerIsAwake = true; - const prisonerIsAwake = true; - const petDogIsPresent = true; - const expected = false; - - expect( - canFreePrisoner( - knightIsAwake, - archerIsAwake, - prisonerIsAwake, - petDogIsPresent - ) - ).toBe(expected); - }); - - test('when only the knight is awake and pet dog is not present', () => { - const knightIsAwake = true; - const archerIsAwake = false; - const prisonerIsAwake = false; - const petDogIsPresent = false; - const expected = false; - - expect( - canFreePrisoner( - knightIsAwake, - archerIsAwake, - prisonerIsAwake, - petDogIsPresent - ) - ).toBe(expected); - }); - - test('when only the knight is awake and pet dog is present', () => { - const knightIsAwake = true; - const archerIsAwake = false; - const prisonerIsAwake = false; - const petDogIsPresent = true; - const expected = true; - - expect( - canFreePrisoner( - knightIsAwake, - archerIsAwake, - prisonerIsAwake, - petDogIsPresent - ) - ).toBe(expected); - }); - - test('when only the archer is asleep and pet dog is not present', () => { - const knightIsAwake = true; - const archerIsAwake = false; - const prisonerIsAwake = true; - const petDogIsPresent = false; - const expected = false; - - expect( - canFreePrisoner( - knightIsAwake, - archerIsAwake, - prisonerIsAwake, - petDogIsPresent - ) - ).toBe(expected); - }); - - test('when only the archer is asleep and pet dog is present', () => { - const knightIsAwake = true; - const archerIsAwake = false; - const prisonerIsAwake = true; - const petDogIsPresent = true; - const expected = true; - - expect( - canFreePrisoner( - knightIsAwake, - archerIsAwake, - prisonerIsAwake, - petDogIsPresent - ) - ).toBe(expected); - }); - - test('when only the prisoner is asleep and pet dog is not present', () => { - const knightIsAwake = true; - const archerIsAwake = true; - const prisonerIsAwake = false; - const petDogIsPresent = false; - const expected = false; - - expect( - canFreePrisoner( - knightIsAwake, - archerIsAwake, - prisonerIsAwake, - petDogIsPresent - ) - ).toBe(expected); - }); - - test('when only the prisoner is asleep and pet dog is present', () => { - const knightIsAwake = true; - const archerIsAwake = true; - const prisonerIsAwake = false; - const petDogIsPresent = true; - const expected = false; - - expect( - canFreePrisoner( - knightIsAwake, - archerIsAwake, - prisonerIsAwake, - petDogIsPresent - ) - ).toBe(expected); - }); - - test('when everyone is awake and pet dog is not present', () => { - const knightIsAwake = true; - const archerIsAwake = true; - const prisonerIsAwake = true; - const petDogIsPresent = false; - const expected = false; - - expect( - canFreePrisoner( - knightIsAwake, - archerIsAwake, - prisonerIsAwake, - petDogIsPresent - ) - ).toBe(expected); - }); - - test('when everyone is awake and pet dog is present', () => { - const knightIsAwake = true; - const archerIsAwake = true; - const prisonerIsAwake = true; - const petDogIsPresent = true; - const expected = false; - - expect( - canFreePrisoner( - knightIsAwake, - archerIsAwake, - prisonerIsAwake, - petDogIsPresent - ) - ).toBe(expected); - }); + test('when everyone is awake and pet dog is present', () => { + const knightIsAwake = true; + const archerIsAwake = true; + const prisonerIsAwake = true; + const petDogIsPresent = true; + const expected = false; + + expect( + canFreePrisoner( + knightIsAwake, + archerIsAwake, + prisonerIsAwake, + petDogIsPresent, + ), + ).toBe(expected); }); }); diff --git a/exercises/concept/annalyns-infiltration/babel.config.js b/exercises/concept/annalyns-infiltration/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/concept/annalyns-infiltration/babel.config.js +++ b/exercises/concept/annalyns-infiltration/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/concept/annalyns-infiltration/eslint.config.mjs b/exercises/concept/annalyns-infiltration/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/concept/annalyns-infiltration/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/concept/annalyns-infiltration/jest.config.js b/exercises/concept/annalyns-infiltration/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/concept/annalyns-infiltration/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/concept/annalyns-infiltration/package.json b/exercises/concept/annalyns-infiltration/package.json index 75c223b1ce..7a54d5a37f 100644 --- a/exercises/concept/annalyns-infiltration/package.json +++ b/exercises/concept/annalyns-infiltration/package.json @@ -13,22 +13,25 @@ "directory": "exercises/concept/annalyns-infiltration" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/concept/appointment-time/.docs/hints.md b/exercises/concept/appointment-time/.docs/hints.md new file mode 100644 index 0000000000..f089f650fb --- /dev/null +++ b/exercises/concept/appointment-time/.docs/hints.md @@ -0,0 +1,30 @@ +# Hints + +## 1. Create an appointment + +- You need to create a new date. The introduction elaborates on the different ways. +- `Date.now()` gives you current time in milliseconds +- `Date` has several getter methods, listed in the introduction, to get date components. Can you use one of those methods? +- Likewise, `Date` has matching setter methods to set those components, rolling over into "higher" components if needed. + +## 2. Convert a date into a timestamp + +- The introduction lists the various ways how to convert a date to different types. Can you use one of those methods? + +## 3. Get the details of an appointment + +- The introduction has all the required information to extract the information from a date. + +## 4. Update an appointment with the given options + +- The introduction has all the required information to extract the information from a date. +- You can reuse `getAppointmentDetails` + +## 5. Get available times between two appointments + +- General subtraction between two dates will give you the timestamp between the two dates. +- You probably want to convert to a number first, and not rely on type-coercion. + +## 6. Check if an appointment is now valid or not + +- Conditional operators will help you to decide which date is bigger or smaller between two dates. diff --git a/exercises/concept/appointment-time/.docs/instructions.md b/exercises/concept/appointment-time/.docs/instructions.md new file mode 100644 index 0000000000..cc198e9c35 --- /dev/null +++ b/exercises/concept/appointment-time/.docs/instructions.md @@ -0,0 +1,75 @@ +# Instructions + +In this exercise you will work on some functions in order to manage appointments. +The system stores everything in ISO 8601 formatted strings, but that's not how people use the calendar. +Various functions are necessary to convert between the various formats. + +## 1. Create an appointment + +Create an appointment `n` days from now at current time. +The function takes `n` days and return the appointment time of `n` days from now. + +```javascript +createAppointment(4, now); +// Given now is Sun Oct 05 2022 23:28:43 GMT+0600 (Bangladesh Standard Time) +// => Sun Oct 09 2022 23:28:43 GMT+0600 (Bangladesh Standard Time) +``` + +If the second parameter `now` is unused, the current time should be used instead. + +## 2. Convert a date into a timestamp + +Various tools only work with the internationally standardized ISO 8601 format. +Write the function `getAppointmentTimestamp` to take a date and return a string in that format. + +```javascript +const appointment = new Date(Date.UTC(2010, 6, 16, 12, 42, 0, 0)); + +getAppointmentTimestamp(appointment); +// => '2010-07-16T12:42:00.000Z' +``` + +## 3. Get the details of an appointment + +Timestamps are hard to read; a function to get the appointment details should help with that. +The function `getAppointmentDetails` takes a timestamp in the ISO 8601 format, and returns the year, month, date, hour, and minute. + +```javascript +getAppointmentDetails('2022-04-24T08:15:00.000'); +// => { year: 2022, month: 3, date: 24, hour: 8, minute: 15 } +``` + +## 4. Update an appointment with the given options + +The function will receive first argument as appointment time and second argument of object of some options. +You have to update the appointment according to the options in the object and return the new appointment date. +The options object could have multiple options. + +```javascript +updateAppointment('2022-02-09T09:20:00.000', { month: 6 }); +// => { year: 2022, month: 6, date: 9, hour: 10, minute: 20 } +``` + +## 5. Get the available time between two appointments + +The function will receive two appointments (timestamps) as arguments. +You have to return the difference between those two times in seconds. + +Because half a second is almost meaningless, round the number before returning it. + +```javascript +timeBetween('2022-12-12T09:20:00.000', '2022-12-18T08:30:00.000'); +// => 515400 +``` + +## 6. Check if an appointment is now valid or not + +Finally, when the appointment is made, the system needs to check if it's valid. +In other words, the appointment must be in the future, and not the past. + +Write the function `isValid` which takes two arguments, an appointment timestamp (string), and the current time as a timestamp (string) and returns `true` if the appointment is in the future, given the current time. + +```javascript +isValid('2022-02-11T23:00:00.000', '2022-02-08T23:00:00.000'); +// => true +``` diff --git a/exercises/concept/appointment-time/.docs/introduction.md b/exercises/concept/appointment-time/.docs/introduction.md new file mode 100644 index 0000000000..d4e49404b4 --- /dev/null +++ b/exercises/concept/appointment-time/.docs/introduction.md @@ -0,0 +1,192 @@ +# Introduction + +JavaScript has a built-in object `Date` which stores date and time, and provides methods for their management. + + +~~~exercism/caution +It was based on Java's `java.util.Date` class, which was replaced in the early 2010s, but for backwards compatibility, JavaScript's `Date` sticks around. + +Because of how hard it is to work with Dates in general and because of how bad or non-existing timezone handling is, many libraries exist such as `moment.js`, `day.js`, `date-fns` and `luxon`. +None of these are available on Exercism. + +In your own projects, do not use a deprecated / unmaintained package such as `moment.js` but rely on more modern alternatives like `luxon`, or the not yet widely available [Temporal][mdn-temporal]. +This exercise focusses on `Date`, which will remain relevant until the end of JavaScript. + +[mdn-temporal]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal +~~~ + +## Creation + +A `Date` object is an instance of the `Date` class. +It can be created without passing any arguments to the constructor function. +This results in a `Date` object that represents the current date and time: + +```javascript +const now = new Date(); +// => Thu Apr 14 2022 11:46:08 GMT+0530 (India Standard Time) +// Shows current day, date and time (in your time zone). +``` + +### **Unix timestamp (number)** + +If a number is passed in, this will be interpreted as a `timestamp`. +A timestamp is an integer number representing the number of **milliseconds** that has passed since **1 January 1970 [UTC][defn-utc]+0**. + +```javascript +const epoch = new Date(0); +// Thu Jan 01 1970 01:00:00 GMT+0100 (Central European Standard Time) + +const another = new Date(1749508766627); +// Tue Jun 10 2025 00:39:26 GMT+0200 (Central European Summer Time) +``` + +One may expect `new Date(0)` to generate the "earliest" date object, but JavaScript will convert the date to your local timezone, which means that only those around [GMT / with an UTC+0][defn-gmt] timezone will actually get the [Unix epoch][defn-unix-epoch] value. + +### **ISO 8601 timestamp (string)** + +You can pass a string value representing a date to the `Date` constructor. +The **only** format that is consistent across implementations is the [simplified version][mdn-date-string-format] of the internationally recognized and standardized so-called [ISO 8601 timestamp strings][defn-iso8601]. + +A moment in time at [UTC][defn-gmt] looks like this: + +```text +YYYY-MM-DDTHH:mm:ss.mssZ +YYYYMMDDTHHmmss.mssZ +``` + +Where the following substitutions take place: + +| Key | Description | Default | +| ---- | --------------------------------------------- | ------- | +| YYYY | The calendar year, represented in 4 digits | | +| MM | The calendar month, represented in 2 digits | 01 | +| DD | The calendar day, represented in 2 digits | 01 | +| T | A literal letter T, separating date from time | | +| HH | The hours in a 24-hour clock, 2 digits | 00 | +| mm | The minutes, 2 digits | 00 | +| ss | The seconds, 2 digits | 00 | +| mss | The milliseconds, 3 digits | 000 | +| Z | A literal letter Z, or an offset `+/-HH:mm` | | + +The literal letter `Z` indicates UTC (no timezone, no Day Light Savings). + +Because there are default values for most components, leaving parts off at the end is valid: + +```text +YYYY-MM-DD +``` + +Defaults to a time of 00:00:00.000 + +If the timestamp does not end in `Z`, and it does not end with `+HH:mm` or `-HH:mm` (indicating a timezone offset), because of historical reasons, the following applies: + +> When the time zone offset is absent, date-only forms are interpreted as a UTC time and date-time forms are interpreted as a local time. +> The interpretation as a UTC time is due to a historical spec error that was not consistent with ISO 8601 but could not be changed due to web compatibility. +> See [Broken Parser – A Web Reality Issue][ref-broken-parser]. + + +~~~exercism/caution +Other formats that are accepted by `Date.parse` may or may not work. +When working with Dates in JavaScript, _always_ use an ISO 8601 timestamp when converting from a `string` to a `Date`. + +Date-only forms are allowed, but not all ISO 8601 formats are supported. +Consult the [simplified version explanation page on MDN][mdn-date-string-format]. + +[mdn-date-string-format]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format +~~~ + +### **Date object** + +An existing date object can also be used as a constructor argument. +This makes a copy of the existing `Date` object with the same date and time. + +```javascript +const t1 = new Date(); +const t2 = new Date(t1); +// Values of t1 and t2 will be the same. +``` + +### **Supplying individual date and time component values** + +A date representing a date can be created by passing three numbers. +A date representing a date and time can be created by passing in 6 numbers. + +```javascript +const date1 = new Date(95, 11, 17); +// Creates Date for Dec 17 1995 00:00 if your local timezone is equivalent to UTC. + +const date2 = new Date(2013, 12, 5, 13, 24, 0); +// Creates Date for Jan 5 2014 13:24 if your local timezone is equivalent to UTC. +``` + +The second value is the `month`, which starts at `0` for January, up to `11` for December. + +## `Date.parse()` + +You may find mentions of or references to a date parsing function `Date.parse`. +Because there are only a few invariants (truths) for this function and because the rest of the implementation is not specified (and thus not standard), one should not use it. + +## Accessing `Date` components + +There are various methods on date objects that return the components of the date: + +```javascript +getFullYear(); // Get the year (4 digits) +getMonth(); // Get the month, from 0 to 11. +getDate(); // Get the day of month, from 1 to 31. +getHours(); // Get the hour in a 24 clock, from 0 to 23 +getMinutes(); // Get the minutes, from 0 to 59 +getSeconds(); // Get the seconds, from 0 to 59 +getMilliseconds(); // Get the milliseconds, from 0 and 999 +getDay(); // Get the day of week, from 0 (Sunday) to 6 (Saturday). +``` + +Each of these has an applicable `set` variant (e.g. `setFullYear`) to update the value. +Any overflowing value rolls over to the next component: + +```javascript +const date = new Date('2025-02-28T12:42:00Z'); +// => Fri Feb 28 2025 13:42:00 GMT+0100 (Central European Standard Time) + +date.setDate(29); +// there was no February 29th in 2025. + +date.getDate(); +// => 1 + +date.toString(); +// => Sat Mar 01 2025 13:42:00 GMT+0100 (Central European Standard Time) +``` + +There are UTC variants for all the methods that disregard the local timezone. + +## Converting from date + +Date objects have a method `getTime()` that returns the UNIX timestamp in milliseconds, ie. amount of milliseconds the midnight at the beginning of January 1, 1970, UTC. +Additionally, a method `toISOString()` is available to convert from a date object to a ISO 8601 timestamp string. + +## Comparing Dates + +Greater than (`>`) and greater than or equals (`>=`) as well as less than (`<`) and less than or equals (`<=`) can be used directly between two dates or a date and a number. +This works because JavaScript will try to coerce the date to a primitive. + + +~~~exercism/advanced +When doing a comparison between two dates or date and a number, JavaScript calls [`[Symbol.toPrimitive]("number")`][mdn-to-primitive] which internally calls [`date.valueOf()`][mdn-date-value-of]. +The latter is the same as calling [`date.getTime()`][mdn-date-get-time]. + +If you do not want to rely on this behaviour, convert to a number using `getTime()` first. + +[mdn-to-primitive]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Symbol.toPrimitive +[mdn-date-value-of]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/valueOf +[mdn-date-get-time]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTime +~~~ + +Dates cannot be compared using equality (`==`, and `===`), but the result of `.getTime()` can. + +[defn-utc]: https://simple.wikipedia.org/wiki/Coordinated_Universal_Time +[defn-gmt]: https://simple.wikipedia.org/wiki/Greenwich_Mean_Time +[defn-unix-epoch]: https://en.wikipedia.org/wiki/Epoch_%28computing%29 +[defn-iso8601]: https://en.wikipedia.org/wiki/ISO_8601 +[mdn-date-string-format]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format +[ref-broken-parser]: https://maggiepint.com/2017/04/11/fixing-javascript-date-web-compatibility-and-reality/ diff --git a/exercises/concept/appointment-time/.gitignore b/exercises/concept/appointment-time/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/concept/appointment-time/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/concept/appointment-time/.meta/config.json b/exercises/concept/appointment-time/.meta/config.json new file mode 100644 index 0000000000..649f219e32 --- /dev/null +++ b/exercises/concept/appointment-time/.meta/config.json @@ -0,0 +1,27 @@ +{ + "authors": [ + "SalahuddinAhammed", + "SleeplessByte" + ], + "contributors": [ + "BadIdeaException" + ], + "files": { + "solution": [ + "appointment-time.js" + ], + "test": [ + "appointment-time.spec.js" + ], + "exemplar": [ + ".meta/exemplar.js" + ] + }, + "blurb": "Learn how to work with dates by managing appointments", + "custom": { + "version.tests.compatibility": "jest-29", + "flag.tests.task-per-describe": true, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } +} diff --git a/exercises/concept/appointment-time/.meta/exemplar.js b/exercises/concept/appointment-time/.meta/exemplar.js new file mode 100644 index 0000000000..ff47aad2fc --- /dev/null +++ b/exercises/concept/appointment-time/.meta/exemplar.js @@ -0,0 +1,90 @@ +// @ts-check + +/** + * Create an appointment + * + * @param {number} days + * @param {number} [now] (ms since the epoch, or undefined) + * + * @returns {Date} the appointment + */ +export function createAppointment(days, now = Date.now()) { + const date = new Date(now); + date.setDate(date.getDate() + days); + + return date; +} + +/** + * Generate the appointment timestamp + * + * @param {Date} appointmentDate + * + * @returns {string} timestamp + */ +export function getAppointmentTimestamp(appointmentDate) { + return appointmentDate.toISOString(); +} + +/** + * Get details of an appointment + * + * @param {string} timestamp (ISO 8601) + * + * @returns {Record<'year' | 'month' | 'date' | 'hour' | 'minute', number>} the appointment details + */ +export function getAppointmentDetails(timestamp) { + const appointmentDate = new Date(timestamp); + + return { + year: appointmentDate.getFullYear(), + month: appointmentDate.getMonth(), + date: appointmentDate.getDate(), + hour: appointmentDate.getHours(), + minute: appointmentDate.getMinutes(), + }; +} + +/** + * Update an appointment with given options + * + * @param {string} timestamp (ISO8601) + * @param {Partial>} options + * + * @returns {Record<'year' | 'month' | 'date' | 'hour' | 'minute', number>} the appointment details + */ +export function updateAppointment(timestamp, options) { + let appointmentDate = new Date(timestamp); + + if (options.year !== undefined) appointmentDate.setFullYear(options.year); + if (options.month !== undefined) appointmentDate.setMonth(options.month); + if (options.date !== undefined) appointmentDate.setDate(options.date); + if (options.hour !== undefined) appointmentDate.setHours(options.hour); + if (options.minute !== undefined) appointmentDate.setMinutes(options.minute); + + return getAppointmentDetails(appointmentDate.toISOString()); +} + +/** + * Get available time in seconds (rounded) between two appointments + * + * @param {string} timestampA (ISO 8601) + * @param {string} timestampB (ISO 8601) + * + * @returns {number} amount of seconds (rounded) + */ +export function timeBetween(timestampA, timestampB) { + return Math.round( + (new Date(timestampB).getTime() - new Date(timestampA).getTime()) / 1000, + ); +} + +/** + * Checks if the appointment is in the past + * + * @param {string} appointmentTimestamp (ISO 8601) + * @param {string} currentTimestamp (ISO 8601) + */ +export function isValid(appointmentTimestamp, currentTimestamp) { + return new Date(appointmentTimestamp) > new Date(currentTimestamp); +} diff --git a/exercises/concept/appointment-time/.npmrc b/exercises/concept/appointment-time/.npmrc new file mode 100644 index 0000000000..d26df800bb --- /dev/null +++ b/exercises/concept/appointment-time/.npmrc @@ -0,0 +1 @@ +audit=false diff --git a/exercises/concept/appointment-time/LICENSE b/exercises/concept/appointment-time/LICENSE new file mode 100644 index 0000000000..90e73be03b --- /dev/null +++ b/exercises/concept/appointment-time/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Exercism + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/exercises/concept/appointment-time/appointment-time.js b/exercises/concept/appointment-time/appointment-time.js new file mode 100644 index 0000000000..01af573b48 --- /dev/null +++ b/exercises/concept/appointment-time/appointment-time.js @@ -0,0 +1,69 @@ +// @ts-check + +/** + * Create an appointment + * + * @param {number} days + * @param {number} [now] (ms since the epoch, or undefined) + * + * @returns {Date} the appointment + */ +export function createAppointment(days, now = undefined) { + throw new Error('Remove this line and implement the function'); +} + +/** + * Generate the appointment timestamp + * + * @param {Date} appointmentDate + * + * @returns {string} timestamp + */ +export function getAppointmentTimestamp(appointmentDate) { + throw new Error('Remove this line and implement the function'); +} + +/** + * Get details of an appointment + * + * @param {string} timestamp (ISO 8601) + * + * @returns {Record<'year' | 'month' | 'date' | 'hour' | 'minute', number>} the appointment details + */ +export function getAppointmentDetails(timestamp) { + throw new Error('Remove this line and implement the function'); +} + +/** + * Update an appointment with given options + * + * @param {string} timestamp (ISO 8601) + * @param {Partial>} options + * + * @returns {Record<'year' | 'month' | 'date' | 'hour' | 'minute', number>} the appointment details + */ +export function updateAppointment(timestamp, options) { + throw new Error('Remove this line and implement the function'); +} + +/** + * Get available time in seconds (rounded) between two appointments + * + * @param {string} timestampA (ISO 8601) + * @param {string} timestampB (ISO 8601) + * + * @returns {number} amount of seconds (rounded) + */ +export function timeBetween(timestampA, timestampB) { + throw new Error('Remove this line and implement the function'); +} + +/** + * Get available times between two appointment + * + * @param {string} appointmentTimestamp (ISO 8601) + * @param {string} currentTimestamp (ISO 8601) + */ +export function isValid(appointmentTimestamp, currentTimestamp) { + throw new Error('Remove this line and implement the function'); +} diff --git a/exercises/concept/appointment-time/appointment-time.spec.js b/exercises/concept/appointment-time/appointment-time.spec.js new file mode 100644 index 0000000000..d0872cc2b3 --- /dev/null +++ b/exercises/concept/appointment-time/appointment-time.spec.js @@ -0,0 +1,151 @@ +import { describe, test, expect } from '@jest/globals'; + +import { + createAppointment, + getAppointmentTimestamp, + getAppointmentDetails, + isValid, + updateAppointment, + timeBetween, +} from './appointment-time'; + +describe('createAppointment', () => { + test('uses the passed in current time', () => { + const currentTime = Date.UTC(2000, 6, 16, 12, 0, 0, 0); + const result = createAppointment(0, currentTime); + + expect(result).toEqual(new Date(currentTime)); + }); + + test('uses the actual current time when it is not passed in', () => { + const currentTime = Date.now(); + const result = createAppointment(0); + + expect(result).toEqual(new Date(currentTime)); + }); + + test('creates appointment without DST change', () => { + const offset = 4; // days + + const currentTime = Date.UTC(2000, 6, 1, 12, 0, 0, 0); + const expectedTime = currentTime + offset * 24 * 60 * 60 * 1000; + + expect(createAppointment(offset, currentTime)).toEqual( + new Date(expectedTime), + ); + }); + + test('creates appointment with potential DST change', () => { + const offset = 180; // days + + const currentTime = Date.UTC(2000, 6, 1, 12, 0, 0, 0); + let expectedTime = currentTime + offset * 24 * 60 * 60 * 1000; + // Manually adjust for DST timezone offset: + expectedTime += + (new Date(expectedTime).getTimezoneOffset() - + new Date(currentTime).getTimezoneOffset()) * + 60 * + 1000; + + expect(createAppointment(offset, currentTime)).toEqual( + new Date(expectedTime), + ); + }); + + test('rolls over days, months, and years', () => { + const currentTime = Date.UTC(1991, 16, 6, 2, 12, 0, 0); + const result = createAppointment(720, currentTime); + + expect(result.getTime()).toStrictEqual(767326320000); + }); +}); + +describe('getAppointmentTimestamp', () => { + test('returns the correct timestamp', () => { + const currentTime = new Date(Date.UTC(2000, 6, 16, 12, 0, 0, 0)); + + expect(getAppointmentTimestamp(currentTime)).toEqual( + '2000-07-16T12:00:00.000Z', + ); + }); +}); + +describe('getAppointment', () => { + test('extracts appointment details', () => { + expect(getAppointmentDetails('2022-04-24T08:15:00.000')).toStrictEqual({ + year: 2022, + month: 3, + date: 24, + hour: 8, + minute: 15, + }); + }); +}); + +describe('updateAppointment', () => { + test('updates a field', () => { + expect( + updateAppointment('2022-02-09T09:20:00.000', { month: 6 }), + ).toStrictEqual({ year: 2022, month: 6, date: 9, hour: 9, minute: 20 }); + }); + + test('update multiple fields', () => { + expect( + updateAppointment('2022-11-21T21:20:00.000', { + year: 2023, + month: 1, + date: 12, + hour: 1, + minute: 29, + }), + ).toStrictEqual({ year: 2023, month: 1, date: 12, hour: 1, minute: 29 }); + }); + + test('updates even if option is 0', () => { + expect( + updateAppointment('2022-12-17T07:10:00.000', { minute: 0 }), + ).toStrictEqual({ year: 2022, month: 11, date: 17, hour: 7, minute: 0 }); + }); + + test('rolls over values', () => { + expect( + updateAppointment('2029-02-28T23:59:00.000', { hour: 24, minute: 60 }), + ).toStrictEqual({ year: 2029, month: 2, date: 1, hour: 1, minute: 0 }); + }); +}); + +describe('availableTimes', () => { + test('retrieves number of seconds between two appointments', () => { + expect( + timeBetween('2022-12-12T09:20:00.000', '2022-12-18T08:30:00.000'), + ).toBe(515400); + }); + + test('rounds to seconds', () => { + expect( + timeBetween('2024-03-06T09:12:15.180', '2024-03-06T18:15:12.090'), + ).toBe(32577); + }); +}); + +describe('isValid', () => { + test('is true when appointment datetime is in the future', () => { + expect(isValid('2022-02-11T23:00:00.000', '2022-02-08T23:00:00.000')).toBe( + true, + ); + }); + + test('is true when appointment date is in the future', () => { + expect(isValid('2022-02-11', '2022-02-08')).toBe(true); + }); + + test('is false when appointment datetime is in the past', () => { + expect(isValid('2022-05-20T23:00:00.000', '2023-02-08T23:00:00.000')).toBe( + false, + ); + }); + + test('is false when appointment date is in the past', () => { + expect(isValid('2022-05-21', '2022-05-22')).toBe(false); + }); +}); diff --git a/exercises/concept/appointment-time/babel.config.js b/exercises/concept/appointment-time/babel.config.js new file mode 100644 index 0000000000..a638497df1 --- /dev/null +++ b/exercises/concept/appointment-time/babel.config.js @@ -0,0 +1,4 @@ +module.exports = { + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], +}; diff --git a/exercises/concept/appointment-time/eslint.config.mjs b/exercises/concept/appointment-time/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/concept/appointment-time/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/concept/appointment-time/jest.config.js b/exercises/concept/appointment-time/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/concept/appointment-time/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/concept/appointment-time/package.json b/exercises/concept/appointment-time/package.json new file mode 100644 index 0000000000..48fdcc8354 --- /dev/null +++ b/exercises/concept/appointment-time/package.json @@ -0,0 +1,38 @@ +{ + "name": "@exercism/javascript-appointment-time", + "description": "Exercism concept exercise on appointment-time", + "author": "Katrina Owen", + "contributors": [ + "Derk-Jan Karrenbeld (https://derk-jan.com)", + "Tejas Bubane (https://tejasbubane.github.io/)" + ], + "private": true, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/exercism/javascript", + "directory": "exercises/concept/appointment-time" + }, + "devDependencies": { + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" + }, + "dependencies": {}, + "scripts": { + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" +} diff --git a/exercises/concept/bird-watcher/.docs/instructions.md b/exercises/concept/bird-watcher/.docs/instructions.md index c194278e69..a0dda80ee9 100644 --- a/exercises/concept/bird-watcher/.docs/instructions.md +++ b/exercises/concept/bird-watcher/.docs/instructions.md @@ -6,15 +6,18 @@ You already digitalized the bird counts per day for the past weeks that you kept Now you want to determine the total number of birds that you counted, calculate the bird count for a specific week and correct a counting mistake. -```exercism/note -To practice, use a for loop solve each of the tasks below. -``` + +~~~~exercism/note +To practice, use a `for` loop to solve each of the tasks below. +~~~~ ## 1. Determine the total number of birds that you counted so far -Let us start analyzing the data by getting a high-level view. Find out how many birds you counted in total since you started your logs. +Let us start analyzing the data by getting a high-level view. +Find out how many birds you counted in total since you started your logs. -Implement a function `totalBirdCount` that accepts an array that contains the bird count per day. It should return the total number of birds that you counted. +Implement a function `totalBirdCount` that accepts an array-like object that contains the bird count per day. +It should return the total number of birds that you counted. ```javascript birdsPerDay = [2, 5, 0, 7, 4, 1, 3, 0, 2, 5, 0, 1, 3, 1]; @@ -26,8 +29,9 @@ totalBirdCount(birdsPerDay); Now that you got a general feel for your bird count numbers, you want to make a more fine-grained analysis. -Implement a function `birdsInWeek` that accepts an array of bird counts per day and a week number. -It returns the total number of birds that you counted in that specific week. You can assume weeks are always tracked completely. +Implement a function `birdsInWeek` that accepts an array-like object of bird counts per day and a week number. +It returns the total number of birds that you counted in that specific week. +You can assume weeks are always tracked completely. ```javascript birdsPerDay = [2, 5, 0, 7, 4, 1, 3, 0, 2, 5, 0, 1, 3, 1]; @@ -42,7 +46,7 @@ You figured out that this bird always spent every second day in your garden. You do not know exactly where it was in between those days but definitely not in your garden. Your bird watcher intuition also tells you that the bird was in your garden on the first day that you tracked in your list. -Given this new information, write a function `fixBirdCountLog` that takes an array of birds counted per day as an argument. It should correct the counting mistake and return the modified array. +Given this new information, write a function `fixBirdCountLog` that takes an array-like object of birds counted per day as an argument. It should correct the counting mistake by modifying the given array. ```javascript birdsPerDay = [2, 5, 0, 7, 4, 1]; diff --git a/exercises/concept/bird-watcher/.eslintrc b/exercises/concept/bird-watcher/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/concept/bird-watcher/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/concept/bird-watcher/.gitignore b/exercises/concept/bird-watcher/.gitignore index bdb912f98a..0c88ff6ec3 100644 --- a/exercises/concept/bird-watcher/.gitignore +++ b/exercises/concept/bird-watcher/.gitignore @@ -1,3 +1,5 @@ -node_modules -yarn-error.log - +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/concept/bird-watcher/.meta/config.json b/exercises/concept/bird-watcher/.meta/config.json index ec8c67c2ad..c2e3f73be2 100644 --- a/exercises/concept/bird-watcher/.meta/config.json +++ b/exercises/concept/bird-watcher/.meta/config.json @@ -1,11 +1,26 @@ { - "blurb": "Professionalize counting the birds in your garden with for loops and increment/decrement operators", - "authors": ["junedev"], - "contributors": [], + "authors": [ + "junedev" + ], "files": { - "solution": ["bird-watcher.js"], - "test": ["bird-watcher.spec.js"], - "exemplar": [".meta/exemplar.js"] + "solution": [ + "bird-watcher.js" + ], + "test": [ + "bird-watcher.spec.js" + ], + "exemplar": [ + ".meta/exemplar.js" + ] }, - "forked_from": ["csharp/bird-watcher"] + "forked_from": [ + "csharp/bird-watcher" + ], + "blurb": "Professionalize counting the birds in your garden with for loops and increment/decrement operators", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": true, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/concept/bird-watcher/.meta/design.md b/exercises/concept/bird-watcher/.meta/design.md index 554e26f2c7..1b297b64d0 100644 --- a/exercises/concept/bird-watcher/.meta/design.md +++ b/exercises/concept/bird-watcher/.meta/design.md @@ -36,19 +36,16 @@ This exercise could benefit from the following rules in the [analyzer][analyzer] For all tasks check that the student actually used a for loop. 1. `totalBirdCount` - - Verify that the condition is written with `< x.length` instead of `<= y.length -1`. - Check whether a shorthand assignment `+=` was used to increase the sum (non-essential feedback). - Verify the total was properly initialized with `0` instead of e.g. `null` - Verify the increment operator was used in loop header step 2. `birdsInWeek` - - Verify a helper variable was used instead of duplicating the calculation in the initialization and condition of the loop - Other checks should be the same as for `totalBirdCount` 3. `fixBirdCountLog` - - Check whether a shorthand assignment `+=` was used to increase the loop counter (non-essential feedback) - Check whether the increment operator was used in the loop body diff --git a/exercises/concept/bird-watcher/.meta/exemplar.js b/exercises/concept/bird-watcher/.meta/exemplar.js index 616b6c8877..8eb350b240 100644 --- a/exercises/concept/bird-watcher/.meta/exemplar.js +++ b/exercises/concept/bird-watcher/.meta/exemplar.js @@ -12,9 +12,11 @@ */ export function totalBirdCount(birdsPerDay) { let total = 0; + for (let i = 0; i < birdsPerDay.length; i++) { total += birdsPerDay[i]; } + return total; } @@ -27,10 +29,12 @@ export function totalBirdCount(birdsPerDay) { */ export function birdsInWeek(birdsPerDay, week) { let total = 0; + const start = 7 * (week - 1); for (let i = start; i < start + 7; i++) { total += birdsPerDay[i]; } + return total; } @@ -39,11 +43,10 @@ export function birdsInWeek(birdsPerDay, week) { * by one for every second day. * * @param {number[]} birdsPerDay - * @returns {number[]} corrected bird count data + * @returns {void} should not return anything */ export function fixBirdCountLog(birdsPerDay) { for (let i = 0; i < birdsPerDay.length; i += 2) { birdsPerDay[i]++; } - return birdsPerDay; } diff --git a/exercises/concept/bird-watcher/babel.config.js b/exercises/concept/bird-watcher/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/concept/bird-watcher/babel.config.js +++ b/exercises/concept/bird-watcher/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/concept/bird-watcher/bird-watcher.js b/exercises/concept/bird-watcher/bird-watcher.js index c91a863c31..d94a707931 100644 --- a/exercises/concept/bird-watcher/bird-watcher.js +++ b/exercises/concept/bird-watcher/bird-watcher.js @@ -11,7 +11,7 @@ * @returns {number} total bird count */ export function totalBirdCount(birdsPerDay) { - throw new Error('Please implement the totalBirdCount function'); + throw new Error('Remove this line and implement the function'); } /** @@ -22,7 +22,7 @@ export function totalBirdCount(birdsPerDay) { * @returns {number} birds counted in the given week */ export function birdsInWeek(birdsPerDay, week) { - throw new Error('Please implement the birdsInWeek function'); + throw new Error('Remove this line and implement the function'); } /** @@ -30,8 +30,8 @@ export function birdsInWeek(birdsPerDay, week) { * by one for every second day. * * @param {number[]} birdsPerDay - * @returns {number[]} corrected bird count data + * @returns {void} should not return anything */ export function fixBirdCountLog(birdsPerDay) { - throw new Error('Please implement the fixBirdCountLog function'); + throw new Error('Remove this line and implement the function'); } diff --git a/exercises/concept/bird-watcher/bird-watcher.spec.js b/exercises/concept/bird-watcher/bird-watcher.spec.js index 67f15913e6..bbedf2210e 100644 --- a/exercises/concept/bird-watcher/bird-watcher.spec.js +++ b/exercises/concept/bird-watcher/bird-watcher.spec.js @@ -1,80 +1,130 @@ -import { totalBirdCount, birdsInWeek, fixBirdCountLog } from './bird-watcher'; +import { describe, expect, test } from '@jest/globals'; +import { birdsInWeek, fixBirdCountLog, totalBirdCount } from './bird-watcher'; -describe('bird watcher', () => { - describe('totalBirdCount', () => { - test('calculates the correct total number of birds', () => { - const birdsPerDay = [9, 0, 8, 4, 5, 1, 3]; - expect(totalBirdCount(birdsPerDay)).toBe(30); - }); +const customInspectSymbol = Symbol.for('nodejs.util.inspect.custom'); +const customLogSymbol = Symbol.for('exercism.javascript.util.log'); - test('works for a short bird count list', () => { - const birdsPerDay = [2]; - expect(totalBirdCount(birdsPerDay)).toBe(2); +// Follow the instructions in case you are stuck on "list.method is not a function" +class CountingReport { + constructor(counts) { + // Enables array[index] + counts.forEach((count, index) => { + this[index] = count; }); - test('works for a long bird count list', () => { - // prettier-ignore - const birdsPerDay = [2, 8, 4, 1, 3, 5, 0, 4, 1, 6, 0, 3, 0, 1, 5, 4, 1, 1, 2, 6]; - expect(totalBirdCount(birdsPerDay)).toBe(57); - }); - }); + // Enables .length + this.length = counts.length; + } - describe('birdsInWeek', () => { - test('calculates the number of birds in the first week', () => { - const birdsPerDay = [3, 0, 5, 1, 0, 4, 1, 0, 3, 4, 3, 0, 8, 0]; - expect(birdsInWeek(birdsPerDay, 1)).toBe(14); - }); + // Log value in non-upgraded environments + toString() { + return arrayOf(this).toString(); + } - test('calculates the number of birds for a week in the middle of the log', () => { - // prettier-ignore - const birdsPerDay = [4, 7, 3, 2, 1, 1, 2, 0, 2, 3, 2, 7, 1, 3, 0, 6, 5, 3, 7, 2, 3]; - expect(birdsInWeek(birdsPerDay, 2)).toBe(18); - }); + // Overrides logging in node (ie. students working locally) + [customInspectSymbol]() { + return `Seen birds per day: ${arrayOf(this)}`; + } - test('works when there is only one week', () => { - const birdsPerDay = [3, 0, 3, 3, 2, 1, 0]; - expect(birdsInWeek(birdsPerDay, 1)).toBe(12); - }); + // Overrides log overrides in web environment (ie. students working in editor) + [customLogSymbol]() { + return `Seen birds per day: ${arrayOf(this)}`; + } +} - test('works for a long bird count list', () => { - const week21 = [2, 0, 1, 4, 1, 3, 0]; - const birdsPerDay = randomArray(20 * 7) - .concat(week21) - .concat(randomArray(10 * 7)); +function report(...values) { + return new CountingReport(values); +} - expect(birdsInWeek(birdsPerDay, 21)).toBe(11); - }); +function arrayOf(countingReport) { + return Array.from( + { length: countingReport.length }, + (_, i) => countingReport[i], + ); +} + +function randomArray(length) { + return Array.from({ length }, () => Math.floor(Math.random() * 8)); +} + +describe('totalBirdCount', () => { + test('calculates the correct total number of birds', () => { + const birdsPerDay = report(9, 0, 8, 4, 5, 1, 3); + expect(totalBirdCount(birdsPerDay)).toBe(30); }); - describe('fixBirdCountLog', () => { - test('returns a bird count list with the corrected values', () => { - const birdsPerDay = [3, 0, 5, 1, 0, 4, 1, 0, 3, 4, 3, 0]; - const expected = [4, 0, 6, 1, 1, 4, 2, 0, 4, 4, 4, 0]; - expect(fixBirdCountLog(birdsPerDay)).toEqual(expected); - }); + test('works for a short bird count list', () => { + const birdsPerDay = report(2); + expect(totalBirdCount(birdsPerDay)).toBe(2); + }); - test('does not create a new array', () => { - const birdsPerDay = [2, 0, 1, 4, 1, 3, 0]; + test('works for a long bird count list', () => { + // prettier-ignore + const birdsPerDay = report( + 2, 8, 4, 1, 3, 5, 0, 4, 1, 6, 0, 3, 0, 1, 5, 4, 1, 1, 2, 6 + ); - // This checks that the same object that was passed in is returned. - // https://jestjs.io/docs/expect#tobevalue - expect(Object.is(fixBirdCountLog(birdsPerDay), birdsPerDay)).toBe(true); - }); + expect(totalBirdCount(birdsPerDay)).toBe(57); + }); +}); - test('works for a short bird count list', () => { - expect(fixBirdCountLog([4, 2])).toEqual([5, 2]); - }); +describe('birdsInWeek', () => { + test('calculates the number of birds in the first week', () => { + const birdsPerDay = report(3, 0, 5, 1, 0, 4, 1, 0, 3, 4, 3, 0, 8, 0); + expect(birdsInWeek(birdsPerDay, 1)).toBe(14); + }); - test('works for a long bird count list', () => { - // prettier-ignore - const birdsPerDay = [2, 8, 4, 1, 3, 5, 0, 4, 1, 6, 0, 3, 0, 1, 5, 4, 1, 1, 2, 6]; - // prettier-ignore - const expected = [3, 8, 5, 1, 4, 5, 1, 4, 2, 6, 1, 3, 1, 1, 6, 4, 2, 1, 3, 6]; - expect(fixBirdCountLog(birdsPerDay)).toEqual(expected); - }); + test('calculates the number of birds for a week in the middle of the log', () => { + // prettier-ignore + const birdsPerDay = report(4, 7, 3, 2, 1, 1, 2, 0, 2, 3, 2, 7, 1, 3, 0, 6, 5, 3, 7, 2, 3); + expect(birdsInWeek(birdsPerDay, 2)).toBe(18); + }); + + test('works when there is only one week', () => { + const birdsPerDay = report(3, 0, 3, 3, 2, 1, 0); + expect(birdsInWeek(birdsPerDay, 1)).toBe(12); + }); + + test('works for a long bird count list', () => { + const week21 = report(2, 0, 1, 4, 1, 3, 0); + const birdsPerDay = report( + ...randomArray(20 * 7) + .concat(arrayOf(week21)) + .concat(randomArray(10 * 7)), + ); + + expect(birdsInWeek(birdsPerDay, 21)).toBe(11); }); }); -function randomArray(length) { - return Array.from({ length: length }, () => Math.floor(Math.random() * 8)); -} +describe('fixBirdCountLog', () => { + test('returns a bird count list with the corrected values', () => { + const birdsPerDay = report(3, 0, 5, 1, 0, 4, 1, 0, 3, 4, 3, 0); + const expected = [4, 0, 6, 1, 1, 4, 2, 0, 4, 4, 4, 0]; + fixBirdCountLog(birdsPerDay); + + expect(arrayOf(birdsPerDay)).toEqual(expected); + }); + + test('works for a short bird count list', () => { + const birdsPerDay = report(4, 2); + fixBirdCountLog(birdsPerDay); + + expect(arrayOf(birdsPerDay)).toEqual([5, 2]); + }); + + test('works for a long bird count list', () => { + // prettier-ignore + const birdsPerDay = report( + 2, 8, 4, 1, 3, 5, 0, 4, 1, 6, 0, 3, 0, 1, 5, 4, 1, 1, 2, 6 + ); + + // prettier-ignore + const expected = [ + 3, 8, 5, 1, 4, 5, 1, 4, 2, 6, 1, 3, 1, 1, 6, 4, 2, 1, 3, 6 + ] + + fixBirdCountLog(birdsPerDay); + expect(arrayOf(birdsPerDay)).toEqual(expected); + }); +}); diff --git a/exercises/concept/bird-watcher/eslint.config.mjs b/exercises/concept/bird-watcher/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/concept/bird-watcher/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/concept/bird-watcher/jest.config.js b/exercises/concept/bird-watcher/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/concept/bird-watcher/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/concept/bird-watcher/package.json b/exercises/concept/bird-watcher/package.json index 32ff558923..148ac83395 100644 --- a/exercises/concept/bird-watcher/package.json +++ b/exercises/concept/bird-watcher/package.json @@ -10,22 +10,25 @@ "directory": "exercises/concept/bird-watcher" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/concept/captains-log/.docs/hints.md b/exercises/concept/captains-log/.docs/hints.md new file mode 100644 index 0000000000..ee4730cc40 --- /dev/null +++ b/exercises/concept/captains-log/.docs/hints.md @@ -0,0 +1,16 @@ +# Hints + +## 1. Generate a random starship registry number + +- To generate a random number in the range _min_ (inclusive) to _max_ (exclusive) you can use the snippet `min + Math.random()*(max - min)`. +- There is a [built in function][floor] for turning a floating point number into an integer. + +## 2.Generate a random stardate + +- To generate a random number in the range _min_ (inclusive) to _max_ (exclusive) you can use the snippet `min + Math.random()*(max - min)`. + +## 3. Generate a random planet + +- You can use a randomly generated integer as an array index. + +[floor]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/floor diff --git a/exercises/concept/captains-log/.docs/instructions.md b/exercises/concept/captains-log/.docs/instructions.md new file mode 100644 index 0000000000..5b8b2a8d61 --- /dev/null +++ b/exercises/concept/captains-log/.docs/instructions.md @@ -0,0 +1,52 @@ +# Instructions + +Mary is a big fan of the TV series Star Trek: The Next Generation. +She often plays pen-and-paper role playing games, where she and her friends pretend to be the crew of the Starship Enterprise. +Mary's character is Captain Picard, which means she has to keep the captain's log. +She loves the creative part of the game, but doesn't like to generate random data on the spot. + +Help Mary by creating random generators for data commonly appearing in the captain's log. + +## 1. Generate a random starship registry number + +Enterprise (registry number NCC-1701) is not the only starship flying around! +When it rendezvous with another starship, Mary needs to log the registry number of that starship. + +Registry numbers start with the prefix "NCC-" and then use a number from 1000 to 9999 (both inclusive). + +Implement the `randomShipRegistryNumber()` function that returns a random starship registry number. + +```javascript +randomShipRegistryNumber(); +// => "NCC-1947" +``` + +## 2. Generate a random stardate + +What's the use of a log if it doesn't include dates? + +A stardate is a floating point number. +The adventures of the Starship Enterprise from the first season of The Next Generation take place between the stardates 41000.0 and 42000.0. +The "4" stands for the 24th century, the "1" for the first season. + +Implement the function `randomStardate` that returns a floating point number between 41000.0 (inclusive) and 42000.0 (exclusive). + +```javascript +randomStardate(); +// => 41458.15721310934 +``` + +## 3. Generate a random planet + +The Starship Enterprise encounters many planets in its travels. +Planets in the Star Trek universe are split into categories based on their properties. +For example, Earth is a class M planet. +All possible planetary classes are: D, H, J, K, L, M, N, R, T, and Y. + +Implement the `randomPlanetClass()` function. +It should return one of the planetary classes at random. + +```javascript +randomPlanetClass(); +// => "K" +``` diff --git a/exercises/concept/captains-log/.docs/introduction.md b/exercises/concept/captains-log/.docs/introduction.md new file mode 100644 index 0000000000..12bf08e3e8 --- /dev/null +++ b/exercises/concept/captains-log/.docs/introduction.md @@ -0,0 +1,26 @@ +# Introduction + +Many programs need (pseudo-)random values to simulate real-world events. + +Common, familiar examples include: + +- A coin toss: a random value from ('H', 'T'). +- The roll of a die: a random integer from 1 to 6. +- Shuffling a deck of cards: a random ordering of a card list. +- The creation of trees and bushes in a 3-D graphics simulation. + +Generating truly random values with a computer is a [surprisingly difficult technical challenge][why-randomness-is-hard], which is why there are also "pseudorandom" generators. + + +~~~exercism/caution +The `Math.random()` function should NOT be used for security and cryptographic applications! +Finish the learning exercise(s) about this concept to learn more +~~~ + +## Generating random numbers + +In Javascript, you can generate psuedorandom numbers using the [`Math.random()`][Math.random] function. +It will return a psuedorandom floating-point number between 0 (inclusive), and 1 (exclusive). + +[why-randomness-is-hard]: https://www.malwarebytes.com/blog/news/2013/09/in-computers-are-random-numbers-really-random +[Math.random]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random diff --git a/exercises/concept/captains-log/.gitignore b/exercises/concept/captains-log/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/concept/captains-log/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/concept/captains-log/.meta/config.json b/exercises/concept/captains-log/.meta/config.json new file mode 100644 index 0000000000..9a9cff01f5 --- /dev/null +++ b/exercises/concept/captains-log/.meta/config.json @@ -0,0 +1,20 @@ +{ + "authors": [ + "SneakyMallard" + ], + "contributors": [ + "SleeplessByte" + ], + "files": { + "solution": [ + "captains-log.js" + ], + "test": [ + "captains-log.spec.js" + ], + "exemplar": [ + ".meta/exemplar.js" + ] + }, + "blurb": "Learn about randomness and the Math.random() function while helping Mary generate stardates and starship registry numbers for her Star Trek roleplay." +} diff --git a/exercises/concept/captains-log/.meta/design.md b/exercises/concept/captains-log/.meta/design.md new file mode 100644 index 0000000000..63a5f94e50 --- /dev/null +++ b/exercises/concept/captains-log/.meta/design.md @@ -0,0 +1,26 @@ +# Design + +## Goal + +The goal of this exercise is to teach the student how to generate psuedorandom numbers in JavaScript. + +## Learning objectives + +- Know how to generate a random number with `Math.random()` +- Know how to generate a random number in a range. +- Know how to generate a random integer. + +## Out of scope + +- Details of pseudorandom number generation in general. +- Different algorithms for pseudorandom number generation. + +## Concepts + +The Concepts this exercise unlocks are: + +- `randomness`: Know of the `Math.random()` function and know how to use it to generate random numbers. + +## Prerequisites + +- `numbers`: Know how numbers work in JavaScript. Know some number methods. diff --git a/exercises/concept/captains-log/.meta/exemplar.js b/exercises/concept/captains-log/.meta/exemplar.js new file mode 100644 index 0000000000..0488199d31 --- /dev/null +++ b/exercises/concept/captains-log/.meta/exemplar.js @@ -0,0 +1,30 @@ +// @ts-check + +/** + * Generates a random starship registry number. + * + * @returns {string} the generated registry number. + */ +export function randomShipRegistryNumber() { + return `NCC-${Math.floor(1000 + Math.random() * 9000)}`; +} + +/** + * Generates a random stardate. + * + * @returns {number} a stardate between 41000 (inclusive) and 42000 (exclusive). + */ +export function randomStardate() { + return 41000 + Math.random() * 1000; +} + +const PLANET_CLASSES = 'DHJKLMNRTY'; + +/** + * Generates a random planet class. + * + * @returns {string} a one-letter planet class. + */ +export function randomPlanetClass() { + return PLANET_CLASSES[Math.floor(Math.random() * PLANET_CLASSES.length)]; +} diff --git a/exercises/concept/captains-log/.npmrc b/exercises/concept/captains-log/.npmrc new file mode 100644 index 0000000000..d26df800bb --- /dev/null +++ b/exercises/concept/captains-log/.npmrc @@ -0,0 +1 @@ +audit=false diff --git a/exercises/concept/captains-log/LICENSE b/exercises/concept/captains-log/LICENSE new file mode 100644 index 0000000000..90e73be03b --- /dev/null +++ b/exercises/concept/captains-log/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Exercism + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/exercises/concept/captains-log/babel.config.js b/exercises/concept/captains-log/babel.config.js new file mode 100644 index 0000000000..a638497df1 --- /dev/null +++ b/exercises/concept/captains-log/babel.config.js @@ -0,0 +1,4 @@ +module.exports = { + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], +}; diff --git a/exercises/concept/captains-log/captains-log.js b/exercises/concept/captains-log/captains-log.js new file mode 100644 index 0000000000..978cff64fd --- /dev/null +++ b/exercises/concept/captains-log/captains-log.js @@ -0,0 +1,34 @@ +// @ts-check + +/** + * Generates a random starship registry number. + * + * @returns {string} the generated registry number. + */ +export function randomShipRegistryNumber() { + throw new Error( + 'Please remove this line and implement the randomShipRegistryNumber() function', + ); +} + +/** + * Generates a random stardate. + * + * @returns {number} a stardate between 41000 (inclusive) and 42000 (exclusive). + */ +export function randomStardate() { + throw new Error( + 'Please remove this line and implement the randomStardate() function', + ); +} + +/** + * Generates a random planet class. + * + * @returns {string} a one-letter planet class. + */ +export function randomPlanetClass() { + throw new Error( + 'Please remove this line and implement the randomStardate() function', + ); +} diff --git a/exercises/concept/captains-log/captains-log.spec.js b/exercises/concept/captains-log/captains-log.spec.js new file mode 100644 index 0000000000..5cfd79d0bb --- /dev/null +++ b/exercises/concept/captains-log/captains-log.spec.js @@ -0,0 +1,77 @@ +import { describe, expect, test } from '@jest/globals'; +import { + randomShipRegistryNumber, + randomStardate, + randomPlanetClass, +} from './captains-log'; + +describe('randomShipRegistryNumber', () => { + test('registry numbers are valid', () => { + for (let i = 0; i < 4; i++) { + expect(randomShipRegistryNumber()).toMatch(/NCC-[1-9][0-9]{3}/); + } + }); + + test('returns a random registry number', () => { + expect(randomShipRegistryNumber()).not.toEqual(randomShipRegistryNumber()); + }); +}); + +function loadDie(...values) { + const originalRandom = Math.random; + + Math.random = function loadedDie() { + if (values.length === 0) { + return originalRandom(); + } + return values.shift(); + }; + + return () => { + Math.random = originalRandom; + }; +} + +describe('randomStardate', () => { + test('stardate is between 41000 and 42000', () => { + const min = 0; + const max = 1 - Number.EPSILON * 32; + + // prettier-ignore + const restore = loadDie( + min, min, min, min, min, min, + max, max, max, max, max, max, + 0.5, 0.5, 0.5, 0.5, 0.5, 0.5 + ); + + for (let i = 0; i < 10_000; i++) { + const starDate = randomStardate(); + expect(starDate).toBeGreaterThanOrEqual(41_000); + expect(starDate).toBeLessThan(42_000); + } + + restore(); + }); +}); + +describe('randomPlanetClass', () => { + test('planet classes are valid', () => { + const expected = 'DHJKLMNRTY'; + for (let i = 0; i < 1_000; i++) { + const actual = randomPlanetClass(); + expect(expected).toContain(actual); + } + }); + + test('all planet classes can be returned', () => { + const expected = 'DHJKLMNRTY'; + const seen = {}; + + for (let i = 0; i < 1_000; i++) { + const actual = randomPlanetClass(); + seen[actual] = true; + } + + expect(Object.keys(seen).length).toBe(expected.length); + }); +}); diff --git a/exercises/concept/captains-log/eslint.config.mjs b/exercises/concept/captains-log/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/concept/captains-log/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/concept/captains-log/jest.config.js b/exercises/concept/captains-log/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/concept/captains-log/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/concept/captains-log/package.json b/exercises/concept/captains-log/package.json new file mode 100644 index 0000000000..e58159e675 --- /dev/null +++ b/exercises/concept/captains-log/package.json @@ -0,0 +1,38 @@ +{ + "name": "@exercism/javascript-captains-log", + "description": "Exercism concept exercise on randomness", + "author": "J R M (https://github.com/quintuple-mallard)", + "contributors": [ + "Derk-Jan Karrenbeld (https://derk-jan.com)", + "Cool-Katt (https://github.com/Cool-Katt)" + ], + "private": true, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/exercism/javascript", + "directory": "exercises/concept/captains-log" + }, + "devDependencies": { + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" + }, + "dependencies": {}, + "scripts": { + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" +} diff --git a/exercises/concept/coordinate-transformation/.docs/instructions.md b/exercises/concept/coordinate-transformation/.docs/instructions.md index 1463ca96a6..727589a705 100644 --- a/exercises/concept/coordinate-transformation/.docs/instructions.md +++ b/exercises/concept/coordinate-transformation/.docs/instructions.md @@ -8,6 +8,9 @@ so you decide to use a function closure to create reusable transformation for `{ Implement the `translate2d` function that returns a function making use of a closure to perform a repeatable 2d translation of a coordinate pair. +> In Geometry, [translation][wiki-translate] refers to moving points, vectors or shapes the same distance in one direction. +> It can be interpreted as addition of a constant to every point. + ```javascript const moveCoordinatesRight2Px = translate2d(2, 0); const result = moveCoordinatesRight2Px(4, 8); @@ -18,6 +21,9 @@ const result = moveCoordinatesRight2Px(4, 8); Implement the `scale2d` function that returns a function making use of a closure to perform a repeatable 2d scale of a coordinate pair. +> In geometry, uniform [scaling][wiki-scale] refers to enlarging or shrinking vectors or shapes in the same direction. +> It can be interpreted as multiplying every point by a constant (scaling factor). +> > For this exercise, assume only positive scaling values. ```javascript @@ -33,9 +39,9 @@ Combine two transformation functions to perform a repeatable transformation. Thi ```javascript const moveCoordinatesRight2Px = translate2d(2, 0); const doubleCoordinates = scale2d(2, 2); -const composedTransformations = composeTransformation( +const composedTransformations = composeTransform( moveCoordinatesRight2Px, - doubleCoordinates + doubleCoordinates, ); const result = composedTransformations(0, 1); // result => [4, 2] @@ -45,7 +51,9 @@ const result = composedTransformations(0, 1); Implement the `memoizeTransform` function. It takes a function to _memoize_, then returns a new function that remembers the inputs to the supplied function so that the last return value can be "remembered" and only calculated once if it is called again with the same arguments. -> Memoizing is sometimes called _dynamic programming_, it allows for expensive operations to be done only once since their result is remembered. +> Memoizing is sometimes used in _dynamic programming_. +> It allows for expensive operations to be done only once since their results are remembered. +> **Note** that in this exercise only the last result is remembered, unlike some solutions in dynamic programming that memoize _all_ results. ```javascript const tripleScale = scale2d(3, 3); @@ -54,3 +62,6 @@ const memoizedScale = memoizeTransform(tripleScale); memoizedScale(4, 3); // => [12, 9], this is computed since it hasn't been computed before for the arguments memoizedScale(4, 3); // => [12, 9], this is remembered, since it was computed already ``` + +[wiki-translate]: https://en.wikipedia.org/wiki/Translation_(geometry) +[wiki-scale]: https://en.wikipedia.org/wiki/Scaling_(geometry) diff --git a/exercises/concept/coordinate-transformation/.docs/introduction.md b/exercises/concept/coordinate-transformation/.docs/introduction.md index 7af38ff52e..d443933587 100644 --- a/exercises/concept/coordinate-transformation/.docs/introduction.md +++ b/exercises/concept/coordinate-transformation/.docs/introduction.md @@ -1,6 +1,7 @@ # Introduction -**Closures** are a programming pattern in JavaScript which allows variables from an outer [lexical scope][wiki-lexical-scope] to be used inside of a nested block of code. JavaScript supports closures transparently, and they are often used without knowing what they are. +**Closures** are a programming pattern in JavaScript which allows variables from an outer [lexical scope][wiki-lexical-scope] to be used inside of a nested block of code. +JavaScript supports closures transparently, and they are often used without knowing what they are. ```javascript // Top-level declarations are global-scope @@ -12,13 +13,35 @@ const dozen = 12; const twoDozen = dozen * 2; } +// Because of the block-scope declaration, twoDozen is not available here. +twoDozen; +// => Uncaught ReferenceError: twoDozen is not defined +``` + +Except for braces `{}`, functions (and classes) als create new scopes, which can _enclose_ values: + +```javascript +const dozen = 12; + // Functions create a new function-scope and block-scope. // Referencing the outer variable here is a closure. function nDozen(n) { - return dozen * n; + // This is declared inside the function scope, and uses the top-level scope. + // This works, and encloses the value 12. + const twoDozen = dozen * 2; + + // This only uses the locally declared variable and the passed argument to the parameter `n` + return (twoDozen / 2) * n; } + +// Because of the function-scope declaration, twoDozen is not available here. +twoDozen; +// => Uncaught ReferenceError: twoDozen is not defined ``` +As the `twoDozen` examples show, values can be enclosed in a _nested_ scope (function, block, etc.), but cannot be pulled out of that context. +In the majority of cases, it is intended in Modern JavaScript that a value does not _leak_ to an outside scope. + ## Closures to save state and pass along values Using a mutable variable declaration (like `let` or `var`) allows for some state to be preserved: @@ -32,6 +55,67 @@ export function increment() { counter += 1; return counter; } + +increment(); +// => 1 + +counter; +// => 1 +``` + +## Enclosing values without leaking the state + +Combining the two ideas: enclosing a value to preserve state, and enclosed values do not leak to the outside, it's possible to create private values. + +The most common method is to make a function that returns a function which encloses some state. + +```javascript +export function makeCounter() { + let counter = 0; + + // This returns a new function that encloses the local variable counter + return function increment() { + counter += 1; + return counter; + }; +} + +// Counter did not leak +counter; +// => Uncaught ReferenceError: counter is not defined + +// This creates a new counter. +// This assigns the increment function to the variable myFirstCounter. +const myFirstCounter = makeCounter(); + +typeof myFirstCounter; +// => function + +myFirstCounter.name; +// => increment + +myFirstCounter(); +// => 1 +myFirstCounter(); +// => 2 + +// This creates new counter (with new, separate local state / enclosed counter variable) +const mySecondCounter = makeCounter(); + +mySecondCounter(); +// => 1 + +// It is not affect the first counter. + +myFirstCounter(); +// => 3 +``` + +```exercism/note +Many programmers find closures a hard concept, and returning a function from a function is not common or not even possible in all programming languages. +If you want more reading material, the [guide on MDN on Closures][mdn-closures] is quite comprehensive. + +[mdn-closures]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures ``` [wiki-lexical-scope]: https://en.wikipedia.org/wiki/Scope_(computer_science)#Lexical_scoping diff --git a/exercises/concept/coordinate-transformation/.eslintrc b/exercises/concept/coordinate-transformation/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/concept/coordinate-transformation/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/concept/coordinate-transformation/.gitignore b/exercises/concept/coordinate-transformation/.gitignore index bdb912f98a..0c88ff6ec3 100644 --- a/exercises/concept/coordinate-transformation/.gitignore +++ b/exercises/concept/coordinate-transformation/.gitignore @@ -1,3 +1,5 @@ -node_modules -yarn-error.log - +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/concept/coordinate-transformation/.meta/config.json b/exercises/concept/coordinate-transformation/.meta/config.json index cfee8402da..978f9b9544 100644 --- a/exercises/concept/coordinate-transformation/.meta/config.json +++ b/exercises/concept/coordinate-transformation/.meta/config.json @@ -1,11 +1,26 @@ { - "blurb": "Practice your knowledge of closures by implementing various coordinate transformations.", - "authors": ["neenjaw"], - "contributors": ["SleeplessByte"], + "authors": [ + "neenjaw" + ], + "contributors": [ + "SleeplessByte" + ], "files": { - "solution": ["coordinate-transformation.js"], - "test": ["coordinate-transformation.spec.js"], - "exemplar": [".meta/exemplar.js"] + "solution": [ + "coordinate-transformation.js" + ], + "test": [ + "coordinate-transformation.spec.js" + ], + "exemplar": [ + ".meta/exemplar.js" + ] }, - "forked_from": [] + "blurb": "Practice your knowledge of closures by implementing various coordinate transformations.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": true, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/concept/coordinate-transformation/babel.config.js b/exercises/concept/coordinate-transformation/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/concept/coordinate-transformation/babel.config.js +++ b/exercises/concept/coordinate-transformation/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/concept/coordinate-transformation/coordinate-transformation.js b/exercises/concept/coordinate-transformation/coordinate-transformation.js index e32567ad1a..03fd4ff20b 100644 --- a/exercises/concept/coordinate-transformation/coordinate-transformation.js +++ b/exercises/concept/coordinate-transformation/coordinate-transformation.js @@ -15,7 +15,7 @@ * translated coordinate pair in the form [x, y] */ export function translate2d(dx, dy) { - throw new Error('Implement the translate2d function'); + throw new Error('Remove this line and implement the function'); } /** @@ -29,7 +29,7 @@ export function translate2d(dx, dy) { * scaled coordinate pair in the form [x, y] */ export function scale2d(sx, sy) { - throw new Error('Implement the scale2d function'); + throw new Error('Remove this line and implement the function'); } /** @@ -43,7 +43,7 @@ export function scale2d(sx, sy) { * transformed coordinate pair in the form [x, y] */ export function composeTransform(f, g) { - throw new Error('Implement the composeTransform function'); + throw new Error('Remove this line and implement the function'); } /** @@ -56,5 +56,5 @@ export function composeTransform(f, g) { * if the arguments are the same on subsequent calls, or compute a new result if they are different. */ export function memoizeTransform(f) { - throw new Error('Implement the memoizeTransform function'); + throw new Error('Remove this line and implement the function'); } diff --git a/exercises/concept/coordinate-transformation/coordinate-transformation.spec.js b/exercises/concept/coordinate-transformation/coordinate-transformation.spec.js index 9eb4b5bd1c..ab12e99d46 100644 --- a/exercises/concept/coordinate-transformation/coordinate-transformation.spec.js +++ b/exercises/concept/coordinate-transformation/coordinate-transformation.spec.js @@ -1,8 +1,9 @@ +import { describe, expect, test, jest } from '@jest/globals'; import { - translate2d, - scale2d, composeTransform, memoizeTransform, + scale2d, + translate2d, } from './coordinate-transformation'; const fakeTransform = () => { @@ -25,11 +26,11 @@ describe('translate2d', () => { const dx = 3; const dy = -5; - const translator = translate2d(dx, dy); const x1 = 0; const y1 = 0; const expected = [3, -5]; test('should be predictable', () => { + const translator = translate2d(dx, dy); expect(translator(x1, y1)).toEqual(expected); }); @@ -37,6 +38,8 @@ describe('translate2d', () => { const y2 = 5; const reusedExpected = [7, 0]; test('should be reusable', () => { + const translator = translate2d(dx, dy); + translator(x1, y1); expect(translator(x2, y2)).toEqual(reusedExpected); }); }); @@ -48,11 +51,11 @@ describe('scale2d', () => { const dx = 4; const dy = 2; - const scaler = scale2d(dx, dy); const x1 = 1; const y1 = 1; const expected = [4, 2]; test('should be predictable', () => { + const scaler = scale2d(dx, dy); expect(scaler(x1, y1)).toEqual(expected); }); @@ -60,6 +63,8 @@ describe('scale2d', () => { const y2 = 5; const reusedExpected = [-8, 10]; test('should be reusable', () => { + const scaler = scale2d(dx, dy); + scaler(x1, y1); expect(scaler(x2, y2)).toEqual(reusedExpected); }); }); @@ -67,31 +72,37 @@ describe('scale2d', () => { describe('composeTransform', () => { const dx = -6; const dy = 10; - const translator = translate2d(dx, dy); const sx = 3; const sy = 2; - const scaler = scale2d(sx, sy); test('should return a function', () => { + const translator = translate2d(dx, dy); + const scaler = scale2d(sx, sy); expect(typeof composeTransform(translator, scaler)).toBe('function'); }); test('should compose two translate functions', () => { + const translator = translate2d(dx, dy); const composeTranslate = composeTransform(translator, translator); expect(composeTranslate(0, 0)).toEqual([-12, 20]); }); test('should compose two scale functions', () => { + const scaler = scale2d(sx, sy); const composeScale = composeTransform(scaler, scaler); expect(composeScale(1, 1)).toEqual([9, 4]); }); test('should compose in the correct order: g(f(x))', () => { + const translator = translate2d(dx, dy); + const scaler = scale2d(sx, sy); const composed = composeTransform(scaler, translator); expect(composed(0, 0)).toEqual([-6, 10]); }); - test('should compose in the opposite order: g(f(x))', () => { + test('should compose in the opposite order: f(g(x))', () => { + const translator = translate2d(dx, dy); + const scaler = scale2d(sx, sy); const composed = composeTransform(translator, scaler); expect(composed(0, 0)).toEqual([-18, 20]); }); @@ -126,6 +137,16 @@ describe('memoizeTransform', () => { expect(memoizedTransform(1, 1)).toEqual([2, 2]); expect(memoizedTransform(2, 2)).toEqual([4, 4]); expect(memoizedTransform(1, 1)).toEqual([2, 2]); - expect(mockFunction).toBeCalledTimes(3); + expect(mockFunction).toHaveBeenCalledTimes(3); + }); + + test('should recalculate when a new function is passed in', () => { + const sumFunction = (x, y) => x + y; + const differenceFunction = (x, y) => x - y; + const memoizedSum = memoizeTransform(sumFunction); + const memoizedDifference = memoizeTransform(differenceFunction); + + expect(memoizedSum(1, 2)).toEqual(3); + expect(memoizedDifference(1, 2)).toEqual(-1); }); }); diff --git a/exercises/concept/coordinate-transformation/eslint.config.mjs b/exercises/concept/coordinate-transformation/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/concept/coordinate-transformation/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/concept/coordinate-transformation/jest.config.js b/exercises/concept/coordinate-transformation/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/concept/coordinate-transformation/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/concept/coordinate-transformation/package.json b/exercises/concept/coordinate-transformation/package.json index f0bef33a99..ec531a9c0a 100644 --- a/exercises/concept/coordinate-transformation/package.json +++ b/exercises/concept/coordinate-transformation/package.json @@ -10,22 +10,25 @@ "directory": "exercises/concept/coordinate-transformation" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/concept/custom-signs/.docs/instructions.md b/exercises/concept/custom-signs/.docs/instructions.md index 3635a2d8cb..3a8a659d6f 100644 --- a/exercises/concept/custom-signs/.docs/instructions.md +++ b/exercises/concept/custom-signs/.docs/instructions.md @@ -4,7 +4,8 @@ In this exercise you'll be writing code to help a sign company create custom mes ## 1. Build an occasion sign -Implement the function `buildSign(occasion, name)` that accepts a string as the `occasion` parameter and a string holding someone's name as the `name` parameter. The two parameters will be embedded into a template string to output the message on the sign. +Implement the function `buildSign(occasion, name)` that accepts a string as the `occasion` parameter and a string holding someone's name as the `name` parameter. +The two parameters will be embedded into a template string to output the message on the sign. ```javascript buildSign('Birthday', 'Rob'); @@ -13,9 +14,14 @@ buildSign('Birthday', 'Rob'); ## 2. Build a birthday sign -Implement the function `buildBirthdaySign(age)` that accepts an age and based on the age will determine part of the message on the sign. If the age is 50 or older, the sign will include the word _mature_, otherwise the sign will include the word _young_. +Implement the function `buildBirthdaySign(age)` that accepts an age and based on the age will determine part of the message on the sign. +If the age is 50 or older, the sign will refer user as _mature_, else it will refer them as _young_. +The exact expected output is shown below: ```javascript +buildBirthdaySign(50); +// => "Happy Birthday! What a mature fellow you are." + buildBirthdaySign(45); // => "Happy Birthday! What a young fellow you are." ``` diff --git a/exercises/concept/custom-signs/.docs/introduction.md b/exercises/concept/custom-signs/.docs/introduction.md index 9566924be1..c6b150c326 100644 --- a/exercises/concept/custom-signs/.docs/introduction.md +++ b/exercises/concept/custom-signs/.docs/introduction.md @@ -22,8 +22,8 @@ All types of expressions can be used with template strings. ```javascript const track = 'JavaScript'; -`This track on exercism.io is ${track.toUpperCase()}.`; -// => This track on exercism.io is JAVASCRIPT. +`This track on exercism.org is ${track.toUpperCase()}.`; +// => This track on exercism.org is JAVASCRIPT. ``` When you are needing to have strings formatted on multiple lines: diff --git a/exercises/concept/custom-signs/.eslintrc b/exercises/concept/custom-signs/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/concept/custom-signs/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/concept/custom-signs/.gitignore b/exercises/concept/custom-signs/.gitignore index bdb912f98a..0c88ff6ec3 100644 --- a/exercises/concept/custom-signs/.gitignore +++ b/exercises/concept/custom-signs/.gitignore @@ -1,3 +1,5 @@ -node_modules -yarn-error.log - +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/concept/custom-signs/.meta/config.json b/exercises/concept/custom-signs/.meta/config.json index 573b4b9664..733d59d498 100644 --- a/exercises/concept/custom-signs/.meta/config.json +++ b/exercises/concept/custom-signs/.meta/config.json @@ -1,11 +1,26 @@ { - "blurb": "Learn about template strings and the ternary operator ...", - "authors": ["pertrai1"], - "contributors": [], + "authors": [ + "pertrai1" + ], "files": { - "solution": ["custom-signs.js"], - "test": ["custom-signs.spec.js"], - "exemplar": [".meta/exemplar.js"] + "solution": [ + "custom-signs.js" + ], + "test": [ + "custom-signs.spec.js" + ], + "exemplar": [ + ".meta/exemplar.js" + ] }, - "forked_from": ["swift/custom-signs"] + "forked_from": [ + "swift/custom-signs" + ], + "blurb": "Learn about template strings and the ternary operator ...", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": true, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/concept/custom-signs/babel.config.js b/exercises/concept/custom-signs/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/concept/custom-signs/babel.config.js +++ b/exercises/concept/custom-signs/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/concept/custom-signs/custom-signs.js b/exercises/concept/custom-signs/custom-signs.js index 26d729bc9b..a05dcc934f 100644 --- a/exercises/concept/custom-signs/custom-signs.js +++ b/exercises/concept/custom-signs/custom-signs.js @@ -14,7 +14,7 @@ */ export function buildSign(occasion, name) { - throw new Error('Implement the buildSign function'); + throw new Error('Remove this line and implement the function'); } /** @@ -26,7 +26,7 @@ export function buildSign(occasion, name) { */ export function buildBirthdaySign(age) { - throw new Error('Implement the buildBirthdaySign function'); + throw new Error('Remove this line and implement the function'); } /** @@ -39,7 +39,7 @@ export function buildBirthdaySign(age) { */ export function graduationFor(name, year) { - throw new Error('Implement the graduationForm function'); + throw new Error('Remove this line and implement the function'); } /** @@ -53,5 +53,5 @@ export function graduationFor(name, year) { */ export function costOf(sign, currency) { - throw new Error('Implement the costOf function'); + throw new Error('Remove this line and implement the function'); } diff --git a/exercises/concept/custom-signs/custom-signs.spec.js b/exercises/concept/custom-signs/custom-signs.spec.js index 36c838144a..3c805c909e 100644 --- a/exercises/concept/custom-signs/custom-signs.spec.js +++ b/exercises/concept/custom-signs/custom-signs.spec.js @@ -1,10 +1,9 @@ -//@ts-check - +import { describe, expect, test } from '@jest/globals'; import { - buildSign, buildBirthdaySign, - graduationFor, + buildSign, costOf, + graduationFor, } from './custom-signs'; describe('buildSign', () => { @@ -20,19 +19,19 @@ describe('buildSign', () => { describe('buildBirthdaySign', () => { test('age is less than 50', () => { expect(buildBirthdaySign(49)).toBe( - 'Happy Birthday! What a young fellow you are.' + 'Happy Birthday! What a young fellow you are.', ); }); test('age is 50 or older', () => { expect(buildBirthdaySign(51)).toBe( - 'Happy Birthday! What a mature fellow you are.' + 'Happy Birthday! What a mature fellow you are.', ); }); test('age is 50', () => { expect(buildBirthdaySign(50)).toBe( - 'Happy Birthday! What a mature fellow you are.' + 'Happy Birthday! What a mature fellow you are.', ); }); }); diff --git a/exercises/concept/custom-signs/eslint.config.mjs b/exercises/concept/custom-signs/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/concept/custom-signs/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/concept/custom-signs/jest.config.js b/exercises/concept/custom-signs/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/concept/custom-signs/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/concept/custom-signs/package.json b/exercises/concept/custom-signs/package.json index e65364d7e6..3b0a1608e1 100644 --- a/exercises/concept/custom-signs/package.json +++ b/exercises/concept/custom-signs/package.json @@ -10,22 +10,25 @@ "directory": "exercises/concept/custom-signs" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/concept/elyses-analytic-enchantments/.docs/hints.md b/exercises/concept/elyses-analytic-enchantments/.docs/hints.md index d3388ae43c..4d370f4a0c 100644 --- a/exercises/concept/elyses-analytic-enchantments/.docs/hints.md +++ b/exercises/concept/elyses-analytic-enchantments/.docs/hints.md @@ -28,6 +28,6 @@ [indexof_method_docs]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf [includes_method_docs]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes [every_method_docs]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every -[some_method_docs]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every +[some_method_docs]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some [find_method_docs]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find [findindex_method_docs]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex diff --git a/exercises/concept/elyses-analytic-enchantments/.docs/introduction.md b/exercises/concept/elyses-analytic-enchantments/.docs/introduction.md index ac05aa7a55..489b79e710 100644 --- a/exercises/concept/elyses-analytic-enchantments/.docs/introduction.md +++ b/exercises/concept/elyses-analytic-enchantments/.docs/introduction.md @@ -1,5 +1,42 @@ # Introduction +## Arrow Functions + +Besides function declarations and function expressions, JavaScript also has another very concise syntax for defining a function. +These functions are called _arrow functions_. + +Here is a comparison between a function declaration and an arrow function. + +```javascript +function addUpTwoNumbers(num1, num2) { + return num1 + num2; +} +// function keyword removed and => added +const addUpTwoNumbers = (num1, num2) => { + return num1 + num2; +}; +``` + +If the function body contains only a return statement, like in the example above, the `{}` and the `return` keyword can be omitted. +If there is only one parameter, the parenthesis `()` can be omitted as well. + + +```javascript +const addUpTwoNumbers = (num1, num2) => num1 + num2; +const square = num => num * num; +``` + + +Arrow functions are often used to define short callback functions directly in the function call. + + +```javascript +applyToSquare(number => number * number); +``` + + +## Array Analysis + [Arrays][arrays-concept] have built-in methods to analyse the contents of the array. Most of these methods take a function that returns true or false as an argument. Such a function is called a [`predicate`][predicate_in_programming]. @@ -26,11 +63,13 @@ numbers.indexOf('two'); // => 1 ``` -Some other helpful built-in methods that are available to analyze an array are shown below. See [MDN][mdn-array-methods] for a full list of array methods. +Some other helpful built-in methods that are available to analyze an array are shown below. +See [MDN][mdn-array-methods] for a full list of array methods. -## `includes` +### `includes` -> The includes() method determines whether an array includes a certain value among its entries, returning true or false as appropriate. [^1] +The `includes(value)` method determines whether an array includes a certain value. +It returns `true` when the value is included, `false` otherwise. ```javascript const numbers = [1, 'two', 3, 'four']; @@ -40,9 +79,12 @@ numbers.includes('one'); // => false ``` -## `every` +### `every` -> The every() method tests whether all elements in the array pass the test implemented by the provided function. It returns a Boolean value. [^2] +The `every(predicate)` method take a function which is _a predicate_. +It tests whether all elements in the array return `true` when passed to the predicate. +In other words: the methods tests that all its elements pass the test passed to the function call. +It returns `true` when every element passes the predicate test, `false` otherwise. ```javascript const numbers = [1, 3, 5, 7, 9]; @@ -50,9 +92,9 @@ numbers.every((num) => num % 2 !== 0); // => true ``` -## `some` +### `some` -> The some() method tests whether at least one element in the array passes the test implemented by the provided function. [^3] +The `some(predicate)` method is the same as the `every` method, but returns `true` if at least one item in the array passes the _predicate_ test. ```javascript const numbers = [1, 3, 5, 7, 9]; @@ -60,9 +102,11 @@ numbers.some((num) => num % 2 !== 0); // => true ``` -## `find` +### `find` -> The find() method returns the value of the first element in the provided array that satisfies the provided testing function. If no values satisfy the testing function, undefined is returned. [^4] +The `find(predicate)` method returns the value of the first element in the array that passes the `predicate` test. +Where `some()` returns `true` when it passes, `find()` returns the actual value in the array. +The method returns `undefined` when none of the elements in the array pass the _predicate_ test. ```javascript const numbers = [1, 3, 5, 7, 9]; @@ -70,9 +114,10 @@ numbers.find((num) => num < 5); // => 1 ``` -## `findIndex` +### `findIndex` -> The findIndex() method returns the index of the first element in the array that satisfies the provided testing function. Otherwise, it returns -1, indicating that no element passed the test. [^5] +The `findIndex(predicate)` is the same as the `find()` method, but it returns the (first) _index_ of the element that passes the _predicate_ test instead of the `value`. +The method returns `-1` when none of the elements in the array pass the _predicate_ test. ```javascript const numbers = [1, 3, 5, 7, 9]; @@ -82,12 +127,6 @@ numbers.findIndex((num) => num > 9); // => -1 ``` -[^1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes -[^2]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every -[^3]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some -[^4]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find -[^5]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex - [predicate_in_programming]: https://derk-jan.com/2020/05/predicate/ [mdn-array-methods]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array#instance_methods [arrays-concept]: /tracks/javascript/concepts/arrays diff --git a/exercises/concept/elyses-analytic-enchantments/.eslintrc b/exercises/concept/elyses-analytic-enchantments/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/concept/elyses-analytic-enchantments/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/concept/elyses-analytic-enchantments/.gitignore b/exercises/concept/elyses-analytic-enchantments/.gitignore index bdb912f98a..0c88ff6ec3 100644 --- a/exercises/concept/elyses-analytic-enchantments/.gitignore +++ b/exercises/concept/elyses-analytic-enchantments/.gitignore @@ -1,3 +1,5 @@ -node_modules -yarn-error.log - +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/concept/elyses-analytic-enchantments/.meta/config.json b/exercises/concept/elyses-analytic-enchantments/.meta/config.json index 4a298b718a..77b249a984 100644 --- a/exercises/concept/elyses-analytic-enchantments/.meta/config.json +++ b/exercises/concept/elyses-analytic-enchantments/.meta/config.json @@ -1,11 +1,27 @@ { - "blurb": "Elyse's magic training continues, teaching you about useful built-in methods to analyse arrays.", - "authors": ["peterchu999", "SleeplessByte"], - "contributors": ["pertrai1"], + "authors": [ + "peterchu999", + "SleeplessByte" + ], + "contributors": [ + "pertrai1" + ], "files": { - "solution": ["enchantments.js"], - "test": ["enchantments.spec.js"], - "exemplar": [".meta/exemplar.js"] + "solution": [ + "enchantments.js" + ], + "test": [ + "enchantments.spec.js" + ], + "exemplar": [ + ".meta/exemplar.js" + ] }, - "forked_from": [] + "blurb": "Elyse's magic training continues, teaching you about useful built-in methods to analyse arrays.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": true, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/concept/elyses-analytic-enchantments/.meta/exemplar.js b/exercises/concept/elyses-analytic-enchantments/.meta/exemplar.js index 606292ba62..bd7f3cac8a 100644 --- a/exercises/concept/elyses-analytic-enchantments/.meta/exemplar.js +++ b/exercises/concept/elyses-analytic-enchantments/.meta/exemplar.js @@ -51,7 +51,7 @@ export function doesStackIncludeOddCard(stack) { * * @param {number[]} stack * - * @returns {number} the first odd value + * @returns {number | undefined} the first odd value */ export function getFirstOddCard(stack) { return stack.find((card) => card % 2 !== 0); diff --git a/exercises/concept/elyses-analytic-enchantments/babel.config.js b/exercises/concept/elyses-analytic-enchantments/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/concept/elyses-analytic-enchantments/babel.config.js +++ b/exercises/concept/elyses-analytic-enchantments/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/concept/elyses-analytic-enchantments/enchantments.js b/exercises/concept/elyses-analytic-enchantments/enchantments.js index 36a3c8caca..60a7c30eb6 100644 --- a/exercises/concept/elyses-analytic-enchantments/enchantments.js +++ b/exercises/concept/elyses-analytic-enchantments/enchantments.js @@ -55,7 +55,7 @@ export function doesStackIncludeOddCard(stack) { * * @param {number[]} stack * - * @returns {number} the first odd value + * @returns {number | undefined} the first odd value */ export function getFirstOddCard(stack) { throw new Error('Implement the getFirstOddCard function'); diff --git a/exercises/concept/elyses-analytic-enchantments/enchantments.spec.js b/exercises/concept/elyses-analytic-enchantments/enchantments.spec.js index 295cb21ef3..31e9ee1aae 100644 --- a/exercises/concept/elyses-analytic-enchantments/enchantments.spec.js +++ b/exercises/concept/elyses-analytic-enchantments/enchantments.spec.js @@ -1,12 +1,11 @@ -// @ts-check - +import { describe, expect, test } from '@jest/globals'; import { - getCardPosition, doesStackIncludeCard, - isEachCardEven, doesStackIncludeOddCard, - getFirstOddCard, + getCardPosition, getFirstEvenCardPosition, + getFirstOddCard, + isEachCardEven, } from './enchantments'; describe('getCardPosition', () => { diff --git a/exercises/concept/elyses-analytic-enchantments/eslint.config.mjs b/exercises/concept/elyses-analytic-enchantments/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/concept/elyses-analytic-enchantments/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/concept/elyses-analytic-enchantments/jest.config.js b/exercises/concept/elyses-analytic-enchantments/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/concept/elyses-analytic-enchantments/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/concept/elyses-analytic-enchantments/package.json b/exercises/concept/elyses-analytic-enchantments/package.json index f1173e49c4..5508918816 100644 --- a/exercises/concept/elyses-analytic-enchantments/package.json +++ b/exercises/concept/elyses-analytic-enchantments/package.json @@ -10,22 +10,25 @@ "directory": "exercises/concept/elyses-analytic-enchantments" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/concept/elyses-destructured-enchantments/.docs/hints.md b/exercises/concept/elyses-destructured-enchantments/.docs/hints.md index bdad5257c2..c6c82baeac 100644 --- a/exercises/concept/elyses-destructured-enchantments/.docs/hints.md +++ b/exercises/concept/elyses-destructured-enchantments/.docs/hints.md @@ -2,32 +2,35 @@ ## 1. Get the first card -- [This article][destructuring_overview_resource] has a good overview of array destructuring. You can find an example of basic variable assignment in the 'Basic Array Destructuring' section. +- [This article][mdn-destructuring] has a good overview of array destructuring. You can find an example of basic variable assignment in the 'Basic Array Destructuring' section. ## 2. Get the second card - You can use placeholders to ignore one or more values in the array. -- You can find an example [here][ignoring_some_values_resource]. +- You can find an example [on MDN][mdn-destructuring-ignore-value]. -## 3. Swap the first two cards +## 3. Swap the two cards - It's possible to swap two values in a single destructuring expression. -- You can find an example [here][swapping_variables_resource]. +- You can find an example [on MDN][mdn-destructuring-swapping]. -## 4. Discard the top card +## 4. Shift three cards around -- There is a [built-in][rest_operator_docs] operator that can be used to collect the remaining values in an array into a single variable. -- You can find an example [here][rest_assignment_resource]. +- It's possible to change the position of three values in a single destructuring expression. +- This is the same as swapping two values, but then with three (or more). -## 5. Insert face cards +## 5. Pick named pile -- There is a [built-in][spread_operator_docs] operator that can be used to expand an array into a list. -- You can find a more detailed overview [here][spread_operator_overview]. +- Objects can be destructured just like arrays. +- You can find an example [on MDN][mdn-object-destructuring-basic-assignment]. -[destructuring_overview_resource]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Basic_variable_assignment -[ignoring_some_values_resource]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Ignoring_some_returned_values -[swapping_variables_resource]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Swapping_variables -[rest_operator_docs]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#Rest_syntax_parameters -[rest_assignment_resource]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Assigning_the_rest_of_an_array_to_a_variable -[spread_operator_docs]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax -[spread_operator_overview]: https://blog.alexdevero.com/javascript-spread-operator +## 6. Swap named piles + +- When a property is extracted from an object, it can be renamed using specific syntax. +- You can find an example [on MDN][mdn-object-destructuring-object-rename]. + +[mdn-destructuring]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Basic_variable_assignment +[mdn-destructuring-ignore-value]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Ignoring_some_returned_values +[mdn-destructuring-swapping]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Swapping_variables +[mdn-object-destructuring-basic-assignment]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring#basic_assignment +[mdn-object-destructuring-object-rename]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring#assigning_to_new_variable_names diff --git a/exercises/concept/elyses-destructured-enchantments/.docs/instructions.md b/exercises/concept/elyses-destructured-enchantments/.docs/instructions.md index 640a6ba7a2..57b66c3cc4 100644 --- a/exercises/concept/elyses-destructured-enchantments/.docs/instructions.md +++ b/exercises/concept/elyses-destructured-enchantments/.docs/instructions.md @@ -1,12 +1,29 @@ # Instructions -Elyse, magician-to-be, continues her training. She has a deck of cards she wants to manipulate. +Elyse, magician-to-be, continues her training. +She has a deck of cards she wants to manipulate. -To make things easier, she usually only starts with cards numbered 1 to 10, although some of the tricks may involve additional cards. +To make things easier, she usually only starts with cards numbered 2 to 10, although some of the tricks may involve additional (face) cards. + + +~~~~exercism/note +There are **many** ways to shuffle the cards around, but to keep up the illusion of magic, it is vital that *no single method is used*, e.g. Elyse doesn't use `splice`, `slice`, `shift`, `unshift`, `push`, `at`. +The array accessor `array[index]` and object accessor (`object[key]` and `object.key`) are also never to be used. +~~~~ + + +Want to help Elyse level up her magic? + + +~~~~exercism/advanced +Every function can be implemented using the parameters and a function body with a single `return expression`. +~~~~ + ## 1. Get the first card -Elyse will summon the first card in the deck without using the `array[index]` or `.shift()`. It's just like magic. +Elyse will summon the first card in the deck without using the `array[index]`, `.at(index)`, or `.shift()`. +It's just like magic. ```javascript const deck = [5, 9, 7, 1, 8]; @@ -17,7 +34,7 @@ getFirstCard(deck); ## 2. Get the second card -Elyse performs sleight of hand and summons the second card in the deck without using the `array[index]`. +Elyse performs sleight of hand and summons the second card in the deck without using the `array[index]` or `.shift()`. ```javascript const deck = [3, 2, 10, 6, 7]; @@ -26,35 +43,56 @@ getSecondCard(deck); // => 2 ``` -## 3. Swap the first two cards +## 3. Swap two cards + +Elyse will make the two cards of the deck switch places. +She doesn't need to call a single function. + +```javascript +const deck = [10, 7]; + +swapTwoCards(deck); +// => [7, 10] +``` + +## 4. Shift three cards around -Elyse will make the top two cards of the deck switch places. She doesn't need to call a single function. +In order to perform some more sleight of hand, Elyse takes three cards and quickly moves the top card to the back, making the middle card the first card and the old bottom card the middle card. +She doesn't need to call a single function. ```javascript -const deck = [10, 7, 3, 8, 5]; +const deck = [2, 6, 10]; -swapTopTwoCards(deck); -// => [7, 10, 3, 8, 5] +shiftThreeCardsAround(deck); +// => [6, 10, 2] ``` -## 4. Discard the top card +## 5. Pick the named pile -Elyse will separate the deck into two piles. The first pile will contain only the top card of the original deck, while the second pile will contain all the other cards. +Elyse will separate the deck into two piles. +She then asks the observer to pick one of the two piles, which we'll name `chosen`. +The `disregarded` pile is no longer relevant, which she makes disappear. +She doesn't need to call a single function. ```javascript -const deck = [2, 5, 4, 9, 3]; +const deck = [5, 4, 7, 10]; +const chosen = [5, 4]; +const disregarded = [7, 10]; -discardTopCard(deck); -// => [2, [5, 4, 9, 3]] +pickNamedPile({ chosen, disregarded }); +// => [5, 4] ``` -## 5. Insert face cards +## 5. Swap the picked pile -Elyse will insert a set of face cards (i.e. jack, queen, and king) into her deck such that they become the second, third, and fourth cards, respectively. +Unfortunately the observer keeps picking the "wrong" pile, but with some clever fast magic, Elyse renames the `chosen` pile to be `disregarded` and the `disregarded` pile to be the `chosen` pile. +She doesn't need to call a single function. ```javascript const deck = [5, 4, 7, 10]; +const chosen = [5, 4]; +const disregarded = [7, 10]; -insertFaceCards(deck); -// => [5, 'jack', 'queen', 'king', 4, 7, 10] +swapNamedPile({ chosen, disregarded }); +// => { chosen: [7, 10], disregarded: [5, 4] } ``` diff --git a/exercises/concept/elyses-destructured-enchantments/.docs/introduction.md b/exercises/concept/elyses-destructured-enchantments/.docs/introduction.md index b8ee42f346..24117e3b0b 100644 --- a/exercises/concept/elyses-destructured-enchantments/.docs/introduction.md +++ b/exercises/concept/elyses-destructured-enchantments/.docs/introduction.md @@ -14,31 +14,45 @@ neptune; // => 14 ``` -## Rest and spread +In short: -JavaScript has a built-in `...` operator that makes it easier to work with indefinite numbers of elements. Depending on the context, it's called either a _rest operator_ or _spread operator_. +- The syntax allows for naming _positioned_ elements in an array, as well as swapping variables using re-assignment. +- Destructuring syntax is available inside function parameters, and is available on any iterable. +- Leaving a position unnamed (by not writing _any_ variable name) silently ignores that position. -### Rest elements +## Object destructuring -When `...` appears on the left-hand side of an assignment, those three dots are known as the `rest` operator. The three dots together with a variable name is called a rest element. It collects zero or more values, and stores them into a single array. +In JavaScript, there is also destructuring syntax to extract properties from an object and assign them to distinct variables. -```javascript -const [a, b, ...everythingElse] = [0, 1, 1, 2, 3, 5, 8]; +In this example, weather symbols are extracted from the object `weather`: -everythingElse; -// => [1, 2, 3, 5, 8] +```javascript +const weather = { + sun: '☀️', + sun_behind_small_cloud: '🌤️', + sun_behind_cloud: '⛅', + sun_behind_large_cloud: '🌥️', + sun_behind_rain_cloud: '🌦️', + cloud: '☁️', + cloud_with_rain: '🌧️', + cloud_with_snow: '🌨️', + cloud_with_lightning: '🌩️', + cloud_with_lightning_and_rain: '⛈️', +}; + +const { sun, cloud, cloud_with_lightning: thunder } = weather; + +sun; +// => '☀️' + +cloud; +// => '☁️' + +thunder; +// => '🌩️' ``` -Note that in JavaScript, unlike some other languages, a `rest` element cannot have a trailing comma. It _must_ be the last element in a destructuring assignment. - -### Spread elements - -When `...` appears on the right-hand side of an assignment, it's known as the `spread` operator. It expands an array into a list of elements. Unlike the rest element, it can appear anywhere in an array literal expression, and there can be more than one. +In short: -```javascript -const oneToFive = [1, 2, 3, 4, 5]; -const oneToTen = [...oneToFive, 6, 7, 8, 9, 10]; - -oneToTen; -// => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] -``` +- The syntax allows for both extracting properties as well as extracting and renaming them. +- Destructuring syntax is available inside function parameters. diff --git a/exercises/concept/elyses-destructured-enchantments/.eslintrc b/exercises/concept/elyses-destructured-enchantments/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/concept/elyses-destructured-enchantments/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/concept/elyses-destructured-enchantments/.gitignore b/exercises/concept/elyses-destructured-enchantments/.gitignore index bdb912f98a..0c88ff6ec3 100644 --- a/exercises/concept/elyses-destructured-enchantments/.gitignore +++ b/exercises/concept/elyses-destructured-enchantments/.gitignore @@ -1,3 +1,5 @@ -node_modules -yarn-error.log - +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/concept/elyses-destructured-enchantments/.meta/config.json b/exercises/concept/elyses-destructured-enchantments/.meta/config.json index 527dff2d01..022a265713 100644 --- a/exercises/concept/elyses-destructured-enchantments/.meta/config.json +++ b/exercises/concept/elyses-destructured-enchantments/.meta/config.json @@ -1,12 +1,28 @@ { - "blurb": "Elyse's magic training continues, teaching you about array destructing and the rest/spread operator.", - "icon": "elyses-enchantments", - "authors": ["kristinaborn"], - "contributors": ["SleeplessByte", "angelikatyborska"], + "authors": [ + "kristinaborn" + ], + "contributors": [ + "SleeplessByte", + "angelikatyborska" + ], "files": { - "solution": ["enchantments.js"], - "test": ["enchantments.spec.js"], - "exemplar": [".meta/exemplar.js"] + "solution": [ + "enchantments.js" + ], + "test": [ + "enchantments.spec.js" + ], + "exemplar": [ + ".meta/exemplar.js" + ] }, - "forked_from": [] + "icon": "elyses-enchantments", + "blurb": "Elyse's magic training continues, teaching you about array destructing and the rest/spread operator.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": true, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/concept/elyses-destructured-enchantments/.meta/design.md b/exercises/concept/elyses-destructured-enchantments/.meta/design.md index f2c20fea65..1db0602844 100644 --- a/exercises/concept/elyses-destructured-enchantments/.meta/design.md +++ b/exercises/concept/elyses-destructured-enchantments/.meta/design.md @@ -4,22 +4,19 @@ - Using destructuring to get the first item of an array - Using destructuring to get the second item of an array (skip hole) -- Using destructuring + rest elements to get the last item of an array - Using destructuring to get the first two items of an array -- Using destructuring + rest elements to get the head and tail of an array -- Using spread to turn an array into a list of parameters -- Using rest elements to turn a list of parameters into an array - Using destructuring to swap two values +- Using destructuring to extract properties from an object +- Using destructuring to extract properties from an object and rename them ## Out of scope -- Anything with objects - Default values ## Concepts - `array-destructuring` -- `rest-and-spread` +- `object-destructuring` ## Prerequisites diff --git a/exercises/concept/elyses-destructured-enchantments/.meta/exemplar.js b/exercises/concept/elyses-destructured-enchantments/.meta/exemplar.js index 3af3244a8d..d92e0aea61 100644 --- a/exercises/concept/elyses-destructured-enchantments/.meta/exemplar.js +++ b/exercises/concept/elyses-destructured-enchantments/.meta/exemplar.js @@ -8,9 +8,7 @@ * * @returns {Card} the first card in the deck */ -export function getFirstCard(deck) { - const [first] = deck; - +export function getFirstCard([first]) { return first; } @@ -21,48 +19,53 @@ export function getFirstCard(deck) { * * @returns {Card} the second card in the deck */ -export function getSecondCard(deck) { - const [, second] = deck; - +export function getSecondCard([, second]) { return second; } /** - * Switch the position of the first two cards in the given deck + * Switch the position of the two cards * - * @param {Card[]} deck + * @param {[Card, Card]} deck * - * @returns {Card[]} new deck with reordered cards + * @returns {[Card, Card]} new deck with the 2 cards swapped */ -export function swapTopTwoCards([a, b, ...rest]) { - return [b, a, ...rest]; +export function swapTwoCards([a, b]) { + return [b, a]; } /** - * Put the top card of the given deck into a separate discard pile + * Rotate (shift) the position of the three cards (by one place) * - * @param {Card[]} deck + * @param {[Card, Card, Card]} deck * - * @returns {[Card, Card[]]} the top card of the given - * deck and a new deck containing all the other cards + * @returns {[Card, Card, Card]} new deck with the 3 cards shifted by one position */ -export function discardTopCard(deck) { - const [first, ...rest] = deck; - - return [first, rest]; +export function shiftThreeCardsAround([a, b, c]) { + return [b, c, a]; } -/** @type Card[] **/ -const FACE_CARDS = ['jack', 'queen', 'king']; - /** - * Insert face cards into the given deck + * Grab the chosen pile from the available piles * - * @param {Card[]} deck + * @param {{ chosen: Card[], disregarded: Card[] }} piles + * + * @returns {Card[]} the pile named chosen + */ +export function pickNamedPile({ chosen }) { + // 🚨 Do NOT write piles.chosen or piles.disregarded. + return chosen; +} + +/** + * Swap the chosen pile for the disregarded pile and the disregarded pile for the chosen pile * - * @returns {Card[]} new deck where the second, - * third, and fourth cards are the face cards + * @param {{ chosen: Card[], disregarded: Card[] }} piles + * @returns {{ chosen: Card[], disregarded: Card[] }} new piles where the two piles are swapped */ -export function insertFaceCards([head, ...tail]) { - return [head, ...FACE_CARDS, ...tail]; +export function swapNamedPile({ chosen: disregarded, disregarded: chosen }) { + // 🪄 Don't break the magic. + // 🚨 Do NOT write piles.chosen or piles.disregarded. + // 🚨 Do NOT touch the next line or Elyse will accidentally reveal the trick. + return { chosen, disregarded }; } diff --git a/exercises/concept/elyses-destructured-enchantments/babel.config.js b/exercises/concept/elyses-destructured-enchantments/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/concept/elyses-destructured-enchantments/babel.config.js +++ b/exercises/concept/elyses-destructured-enchantments/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/concept/elyses-destructured-enchantments/enchantments.js b/exercises/concept/elyses-destructured-enchantments/enchantments.js index e04735feb8..f88f487877 100644 --- a/exercises/concept/elyses-destructured-enchantments/enchantments.js +++ b/exercises/concept/elyses-destructured-enchantments/enchantments.js @@ -24,39 +24,48 @@ export function getSecondCard(deck) { } /** - * Switch the position of the first two cards in the given deck + * Switch the position of the two cards * - * @param {Card[]} deck + * @param {[Card, Card]} deck * - * @returns {Card[]} new deck with reordered cards + * @returns {[Card, Card]} new deck with the 2 cards swapped */ -export function swapTopTwoCards(deck) { +export function swapTwoCards(deck) { throw new Error('Implement the swapTopTwoCards function'); } /** - * Put the top card of the given deck into a separate discard pile + * Rotate (shift) the position of the three cards (by one place) * - * @param {Card[]} deck + * @param {[Card, Card, Card]} deck * - * @returns {[Card, Card[]]} the top card of the given - * deck and a new deck containing all the other cards + * @returns {[Card, Card, Card]} new deck with the 3 cards shifted by one position */ -export function discardTopCard(deck) { +export function shiftThreeCardsAround(deck) { throw new Error('Implement the discardTopCard function'); } -/** @type Card[] **/ -const FACE_CARDS = ['jack', 'queen', 'king']; - /** - * Insert face cards into the given deck + * Grab the chosen pile from the available piles * - * @param {Card[]} deck + * @param {{ chosen: Card[], disregarded: Card[] }} piles + * + * @returns {Card[]} the pile named chosen + */ +export function pickNamedPile(piles) { + // 🚨 Do NOT use piles.chosen or piles.disregarded. + throw new Error('Implement the pickNamedPile function'); +} + +/** + * Swap the chosen pile for the disregarded pile and the disregarded pile for the chosen pile * - * @returns {Card[]} new deck where the second, - * third, and fourth cards are the face cards + * @param {{ chosen: Card[], disregarded: Card[] }} piles + * @returns {{ chosen: Card[], disregarded: Card[] }} new piles where the two piles are swapped */ -export function insertFaceCards(deck) { - throw new Error('Implement the insertFaceCards function'); +export function swapNamedPile(piles) { + // 🪄 Don't break the magic. + // 🚨 Do NOT use piles.chosen or piles.disregarded. + // 🚨 Do NOT touch the next line or Elyse will accidentally reveal the trick. + return { chosen, disregarded }; } diff --git a/exercises/concept/elyses-destructured-enchantments/enchantments.spec.js b/exercises/concept/elyses-destructured-enchantments/enchantments.spec.js index 7f61a11cb8..c4d15a9ae3 100644 --- a/exercises/concept/elyses-destructured-enchantments/enchantments.spec.js +++ b/exercises/concept/elyses-destructured-enchantments/enchantments.spec.js @@ -1,87 +1,162 @@ -// @ts-check - +import { describe, expect, test } from '@jest/globals'; import { - discardTopCard, getFirstCard, getSecondCard, - insertFaceCards, - swapTopTwoCards, + swapTwoCards, + shiftThreeCardsAround, + pickNamedPile, + swapNamedPile, } from './enchantments'; +const customInspectSymbol = Symbol.for('nodejs.util.inspect.custom'); +const customLogSymbol = Symbol.for('exercism.javascript.util.log'); + +// Follow the instructions in case you are stuck on "list.method is not a function" +class LimitedDeck { + constructor(values) { + this.values = values; + } + + // Enables rest syntax and spread operator, as wel as for of, etc. + [Symbol.iterator]() { + return this.values[Symbol.iterator](); + } + + // Log value in non-upgraded environments + toString() { + return this.values.toString(); + } + + // Overrides logging in node (ie. students working locally) + [customInspectSymbol](depth, inspectOptions, inspect) { + const inner = this.values[customInspectSymbol] + ? this.values[customInspectSymbol](depth, inspectOptions, inspect) + : this.values.toString(); + + return `List of (${inner})`; + } + + // Overrides log overrides in web environment (ie. students working in editor) + [customLogSymbol](depth, inspectOptions, inspect) { + const inner = this.values[customLogSymbol] + ? this.values[customLogSymbol](depth, inspectOptions, inspect) + : this.values.toString(); + + return `List of (${inner})`; + } +} + +function deck(...values) { + return new LimitedDeck(values); +} + describe('getFirstCard', () => { test('from a deck with a single card', () => { - expect(getFirstCard([3])).toBe(3); + expect(getFirstCard(deck(3))).toBe(3); }); test('from a deck with many cards', () => { - expect(getFirstCard([8, 3, 9, 5])).toBe(8); + expect(getFirstCard(deck(8, 3, 9, 5))).toBe(8); }); test('from an empty deck', () => { - expect(getFirstCard([])).toBe(undefined); + expect(getFirstCard(deck())).toBe(undefined); }); }); describe('getSecondCard', () => { test('from a deck with two cards', () => { - expect(getSecondCard([10, 4])).toBe(4); + expect(getSecondCard(deck(10, 4))).toBe(4); }); test('from a deck with many cards', () => { - expect(getSecondCard([2, 5, 1, 6])).toBe(5); + expect(getSecondCard(deck(2, 5, 7, 6))).toBe(5); }); test('from an empty deck', () => { - expect(getSecondCard([])).toBe(undefined); + expect(getSecondCard(deck())).toBe(undefined); }); test('from a deck with one card', () => { - expect(getSecondCard([8])).toBe(undefined); + expect(getSecondCard(deck(8))).toBe(undefined); }); }); -describe('swapTopTwoCards', () => { - test('in a deck with two cards', () => { - expect(swapTopTwoCards([3, 6])).toStrictEqual([6, 3]); +describe('swapTwoCards', () => { + test('swapping two numbered cards', () => { + expect(swapTwoCards(deck(3, 6))).toStrictEqual([6, 3]); }); - test('in a deck with many cards', () => { - expect(swapTopTwoCards([10, 4, 3, 7, 8])).toStrictEqual([4, 10, 3, 7, 8]); + test('swapping a high card with a low card', () => { + expect(swapTwoCards(deck(10, 2))).toStrictEqual([2, 10]); }); -}); -describe('discardTopCard', () => { - test('from a deck with one card', () => { - expect(discardTopCard([7])).toStrictEqual([7, []]); + test('swapping a face card with a low card', () => { + expect(swapTwoCards(deck('king', 3))).toStrictEqual([3, 'king']); }); +}); - test('from a deck with many cards', () => { - expect(discardTopCard([9, 2, 10, 4])).toStrictEqual([9, [2, 10, 4]]); +describe('shiftThreeCardsAround', () => { + test('consecutive numbers', () => { + expect(shiftThreeCardsAround(deck(6, 4, 5))).toStrictEqual([4, 5, 6]); }); -}); -describe('insertFaceCards', () => { - test('into a deck with many cards', () => { - expect(insertFaceCards([3, 10, 7])).toStrictEqual([ - 3, - 'jack', - 'queen', + test('drop the face card to the bottom', () => { + expect(shiftThreeCardsAround(deck('king', 5, 2))).toStrictEqual([ + 5, + 2, 'king', - 10, - 7, ]); }); +}); - test('into a deck with one card', () => { - expect(insertFaceCards([9])).toStrictEqual([9, 'jack', 'queen', 'king']); +describe('pickNamedPile', () => { + test('keeps the chosen pile', () => { + const chosen = deck(3, 'jack', 'queen', 'king', 10, 7); + const disregarded = deck(4, 5, 6, 8, 9); + const piles = { chosen, disregarded }; + + expect(pickNamedPile(piles)).toStrictEqual(chosen); }); - test('into a deck with no cards', () => { - expect(insertFaceCards([])).toStrictEqual([ - undefined, - 'jack', - 'queen', - 'king', - ]); + test('returns the actual pile without recreating it', () => { + const chosen = deck(3, 'jack', 'queen', 'king', 10, 7); + const disregarded = deck(4, 5, 6, 8, 9); + const piles = { chosen, disregarded }; + + const result = pickNamedPile(piles); + + chosen.values.push('joker'); + + expect(result).toStrictEqual(chosen); + }); +}); + +describe('swapNamedPile', () => { + test('renames the piles', () => { + const face_pile = deck(3, 'jack', 'queen', 'king', 10, 7); + const numbers_pile = deck(4, 5, 6, 8, 9); + const piles = { chosen: numbers_pile, disregarded: face_pile }; + + expect(swapNamedPile(piles)).toStrictEqual({ + chosen: face_pile, + disregarded: numbers_pile, + }); + }); + + test('returns the actual piles without recreating them', () => { + const face_pile = deck(3, 'jack', 'queen', 'king', 10, 7); + const numbers_pile = deck(4, 5, 6, 8, 9); + const piles = { chosen: numbers_pile, disregarded: face_pile }; + + const result = swapNamedPile(piles); + + face_pile.values.push('joker'); + numbers_pile.values.push(2); + + expect(result).toStrictEqual({ + chosen: face_pile, + disregarded: numbers_pile, + }); }); }); diff --git a/exercises/concept/elyses-destructured-enchantments/eslint.config.mjs b/exercises/concept/elyses-destructured-enchantments/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/concept/elyses-destructured-enchantments/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/concept/elyses-destructured-enchantments/jest.config.js b/exercises/concept/elyses-destructured-enchantments/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/concept/elyses-destructured-enchantments/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/concept/elyses-destructured-enchantments/package.json b/exercises/concept/elyses-destructured-enchantments/package.json index 6199790a26..a6b0e11626 100644 --- a/exercises/concept/elyses-destructured-enchantments/package.json +++ b/exercises/concept/elyses-destructured-enchantments/package.json @@ -10,22 +10,25 @@ "directory": "exercises/concept/elyses-destructured-enchantments" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/concept/elyses-enchantments/.docs/instructions.md b/exercises/concept/elyses-enchantments/.docs/instructions.md index a2af96d540..8298853c08 100644 --- a/exercises/concept/elyses-enchantments/.docs/instructions.md +++ b/exercises/concept/elyses-enchantments/.docs/instructions.md @@ -9,6 +9,14 @@ of a certain card corresponds to the index in the array. That means position 0 refers to the first card, position 1 to the second card etc. + +~~~~exercism/note +All but two functions should update the array of cards and then return the modified array - a common way of working known as the Builder pattern, which allows you to nicely daisy-chain functions together. + +The two exceptions are `getItem`, which should return the card at the given position, and `checkSizeOfStack` which should return `true` if the given size matches. +~~~~ + + ## 1. Retrieve a card from a stack To pick a card, return the card at index `position` from @@ -23,13 +31,13 @@ getItem([1, 2, 4, 1], position); ## 2. Exchange a card in the stack Perform some sleight of hand and exchange the card at index `position` -with the new card provided. +with the replacement card provided. Return the adjusted stack. ```javascript const position = 2; -const newCard = 6; -setItem([1, 2, 4, 1], position, newCard); +const replacementCard = 6; +setItem([1, 2, 4, 1], position, replacementCard); // => [1, 2, 6, 1] ``` diff --git a/exercises/concept/elyses-enchantments/.docs/introduction.md b/exercises/concept/elyses-enchantments/.docs/introduction.md index 4ef98e48bd..6f9c6a144f 100644 --- a/exercises/concept/elyses-enchantments/.docs/introduction.md +++ b/exercises/concept/elyses-enchantments/.docs/introduction.md @@ -33,12 +33,13 @@ numbers; ## Methods -Some of the [methods][array_methods] that are available on every Array object can be used to add or remove from the array. +Some of the [methods][mdn-array] that are available on every Array object can be used to add or remove from the array. Here are a few to consider when working on this exercise: ### push -> The `push()` method adds one or more elements to the end of an array and returns the new length of the array.[^1] +A `value` can be _added_ to the end of an array by using `.push(value)`. +The method returns the new length of the array. ```javascript const numbers = [1, 'two', 3, 'four']; @@ -49,8 +50,9 @@ numbers; ### pop -> The `pop()` method removes the last element from an array and returns that element. -> This method changes the length of the array.[^2] +The _last_ `value` can be _removed_ from an array by using `.pop()` +The method returns the removed value. +The length of the array will be decreased because of this change. ```javascript const numbers = [1, 'two', 3, 'four']; @@ -61,8 +63,9 @@ numbers; ### shift -> The `shift()` method removes the first element from an array and returns that removed element. -> This method changes the length of the array.[^3] +The _first_ `value` can be _removed_ from an array by using `.shift()` +The method returns the removed value. +The length of the array will be decreased because of this change. ```javascript const numbers = [1, 'two', 3, 'four']; @@ -73,7 +76,8 @@ numbers; ### unshift -> The unshift() method adds one or more elements to the beginning of an array and returns the new length of the array.[^4] +A `value` can be _added_ to the beginning of an array by using `.unshift(value)`. +The method returns the new length of the array. ```javascript const numbers = [1, 'two', 3, 'four']; @@ -84,22 +88,27 @@ numbers; ### splice -> The splice() method changes the contents of an array by removing or replacing existing elements and/or adding new elements in place. -> This method returns an array containing the deleted elements.[^5] +A `value` at a specific `index` can be _removed_ from an array by using `.splice(index, 1)`. +The method returns the removed element(s). ```javascript const numbers = [1, 'two', 3, 'four']; -numbers.splice(2, 1, 'one'); +numbers.splice(2, 1, 'one'); // => [3] numbers; // => [1, 'two', 'one', 'four'] ``` ---- + +~~~exercism/advanced +These methods are more powerful than described: -[^1]: `push`, MDN. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push (referenced September 29, 2021) -[^2]: `pop`, MDN. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/pop (referenced September 29, 2021) -[^3]: `shift`, MDN. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift (referenced September 29, 2021) -[^4]: `unshift`, MDN. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift (referenced September 29, 2021) -[^5]: `splice`, MDN. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice (referenced September 29, 2021) +- Both `push` and `unshift` allow you to push or unshift multiple values at once, by adding more arguments. + That is not necessary to complete this exercise. +- Splice can remove multiple values by increasing the second argument. + That is not necessary to complete this exercise. +- Splice can also add multiple values by adding them as arguments after the `deleteCount`. + This can be used to replace values, or insert values in the middle of an array (for example by removing 0 elements). + That is not necessary to complete this exercise. +~~~ -[array_methods]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array +[mdn-array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array diff --git a/exercises/concept/elyses-enchantments/.eslintrc b/exercises/concept/elyses-enchantments/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/concept/elyses-enchantments/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/concept/elyses-enchantments/.gitignore b/exercises/concept/elyses-enchantments/.gitignore index bdb912f98a..0c88ff6ec3 100644 --- a/exercises/concept/elyses-enchantments/.gitignore +++ b/exercises/concept/elyses-enchantments/.gitignore @@ -1,3 +1,5 @@ -node_modules -yarn-error.log - +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/concept/elyses-enchantments/.meta/config.json b/exercises/concept/elyses-enchantments/.meta/config.json index 489ac93c9c..72e2838fff 100644 --- a/exercises/concept/elyses-enchantments/.meta/config.json +++ b/exercises/concept/elyses-enchantments/.meta/config.json @@ -1,11 +1,32 @@ { - "blurb": "Help Elyse with her Enchantments and learn about arrays in the process.", - "authors": ["ovidiu141", "SleeplessByte"], - "contributors": ["peterchu999", "pertrai1", "nasch"], + "authors": [ + "ovidiu141", + "SleeplessByte" + ], + "contributors": [ + "peterchu999", + "pertrai1", + "nasch" + ], "files": { - "solution": ["enchantments.js"], - "test": ["enchantments.spec.js"], - "exemplar": [".meta/exemplar.js"] + "solution": [ + "enchantments.js" + ], + "test": [ + "enchantments.spec.js" + ], + "exemplar": [ + ".meta/exemplar.js" + ] }, - "forked_from": ["go/card-tricks"] + "forked_from": [ + "go/card-tricks" + ], + "blurb": "Help Elyse with her Enchantments and learn about arrays in the process.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": true, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/concept/elyses-enchantments/babel.config.js b/exercises/concept/elyses-enchantments/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/concept/elyses-enchantments/babel.config.js +++ b/exercises/concept/elyses-enchantments/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/concept/elyses-enchantments/enchantments.spec.js b/exercises/concept/elyses-enchantments/enchantments.spec.js index ae66970a1a..15cdf6e5b7 100644 --- a/exercises/concept/elyses-enchantments/enchantments.spec.js +++ b/exercises/concept/elyses-enchantments/enchantments.spec.js @@ -1,283 +1,275 @@ -//@ts-check - +import { describe, expect, test } from '@jest/globals'; import { + checkSizeOfStack, getItem, - setItem, - insertItemAtTop, insertItemAtBottom, + insertItemAtTop, removeItem, - removeItemFromTop, removeItemAtBottom, - checkSizeOfStack, + removeItemFromTop, + setItem, } from './enchantments'; -describe('Elyses enchantments', () => { - describe('pick a card', () => { - test('get the first card', () => { - const stack = [1, 2, 3]; - const expected = 1; +describe('pick a card', () => { + test('get the first card', () => { + const stack = [1, 2, 3]; + const expected = 1; - expect(getItem(stack, 0)).toBe(expected); - }); + expect(getItem(stack, 0)).toBe(expected); + }); - test('get the middle card', () => { - const stack = [4, 5, 6]; - const expected = 5; + test('get the middle card', () => { + const stack = [4, 5, 6]; + const expected = 5; - expect(getItem(stack, 1)).toBe(expected); - }); + expect(getItem(stack, 1)).toBe(expected); + }); - test('get the last card', () => { - const stack = [9, 8, 7]; - const expected = 7; + test('get the last card', () => { + const stack = [9, 8, 7]; + const expected = 7; - expect(getItem(stack, 2)).toBe(expected); - }); + expect(getItem(stack, 2)).toBe(expected); }); +}); + +describe('sleight of hand', () => { + test('replace the first card with a 7', () => { + const stack = [1, 2, 3]; + const position = 0; + const replacement = 7; - describe('sleight of hand', () => { - test('replace the first card with a 7', () => { - const stack = [1, 2, 3]; - const position = 0; - const replacement = 7; - - const expected = [7, 2, 3]; - expect(setItem(stack, position, replacement)).toStrictEqual(expected); - }); - - test('replace the middle card with a 5', () => { - const stack = [2, 2, 2]; - const position = 1; - const replacement = 5; - - const expected = [2, 5, 2]; - expect(setItem(stack, position, replacement)).toStrictEqual(expected); - }); - - test('replace the last card with a 7', () => { - const stack = [7, 7, 6]; - const position = 2; - const replacement = 7; - - const expected = [7, 7, 7]; - expect(setItem(stack, position, replacement)).toStrictEqual(expected); - }); + const expected = [7, 2, 3]; + expect(setItem(stack, position, replacement)).toStrictEqual(expected); }); - describe('make cards appear', () => { - test('adding a second card at the top', () => { - const stack = [1]; - const newCard = 5; + test('replace the middle card with a 5', () => { + const stack = [2, 2, 2]; + const position = 1; + const replacement = 5; - const expected = [1, 5]; - expect(insertItemAtTop(stack, newCard)).toStrictEqual(expected); - }); + const expected = [2, 5, 2]; + expect(setItem(stack, position, replacement)).toStrictEqual(expected); + }); - test('adding a third card at the top', () => { - const stack = [1, 5]; - const newCard = 9; + test('replace the last card with a 7', () => { + const stack = [7, 7, 6]; + const position = 2; + const replacement = 7; - const expected = [1, 5, 9]; - expect(insertItemAtTop(stack, newCard)).toStrictEqual(expected); - }); + const expected = [7, 7, 7]; + expect(setItem(stack, position, replacement)).toStrictEqual(expected); + }); +}); - test('adding a fourth card at the top', () => { - const stack = [1, 5, 9]; - const newCard = 2; +describe('make cards appear at the top', () => { + test('adding a second card at the top', () => { + const stack = [1]; + const newCard = 5; - const expected = [1, 5, 9, 2]; - expect(insertItemAtTop(stack, newCard)).toStrictEqual(expected); - }); + const expected = [1, 5]; + expect(insertItemAtTop(stack, newCard)).toStrictEqual(expected); + }); - test('adding a different fourth card at the top', () => { - const stack = [1, 5, 9]; - const newCard = 8; + test('adding a third card at the top', () => { + const stack = [1, 5]; + const newCard = 9; - const expected = [1, 5, 9, 8]; - expect(insertItemAtTop(stack, newCard)).toStrictEqual(expected); - }); + const expected = [1, 5, 9]; + expect(insertItemAtTop(stack, newCard)).toStrictEqual(expected); + }); - test('adding multiple cards to the stack at the top', () => { - const stack = [1]; + test('adding a fourth card at the top', () => { + const stack = [1, 5, 9]; + const newCard = 2; - insertItemAtTop(stack, 5); - insertItemAtTop(stack, 9); + const expected = [1, 5, 9, 2]; + expect(insertItemAtTop(stack, newCard)).toStrictEqual(expected); + }); - const expected = [1, 5, 9]; - expect(stack).toStrictEqual(expected); - }); + test('adding a different fourth card at the top', () => { + const stack = [1, 5, 9]; + const newCard = 8; - test('adding a second card to the bottom', () => { - const stack = [1]; - const newCard = 5; + const expected = [1, 5, 9, 8]; + expect(insertItemAtTop(stack, newCard)).toStrictEqual(expected); + }); + + test('adding multiple cards to the stack at the top', () => { + const stack = [1]; + + insertItemAtTop(stack, 5); + insertItemAtTop(stack, 9); + + const expected = [1, 5, 9]; + expect(stack).toStrictEqual(expected); + }); +}); + +describe('make cards disappear', () => { + test('remove the card at the bottom', () => { + const stack = [1, 2, 3, 4]; + const position = 0; + + const expected = [2, 3, 4]; + + if (stack[0] === undefined) { + throw new Error( + 'The card has disappeared, but the stack has not changed in size. This magic trick has turned into actual magic. Perhaps a different method of removing the card will result in a stack that Elyse can work with...', + ); + } - const expected = [5, 1]; - expect(insertItemAtBottom(stack, newCard)).toStrictEqual(expected); - }); + expect(removeItem(stack, position)).toStrictEqual(expected); + }); - test('adding a third card to the bottom', () => { - const stack = [5, 1]; - const newCard = 9; + test('remove the card at the top', () => { + const stack = [1, 2, 3, 4]; + const position = 3; - const expected = [9, 5, 1]; - expect(insertItemAtBottom(stack, newCard)).toStrictEqual(expected); - }); + const expected = [1, 2, 3]; + expect(removeItem(stack, position)).toStrictEqual(expected); + }); - test('adding a fourth card to the bottom', () => { - const stack = [9, 5, 1]; - const newCard = 2; + test('remove the second card', () => { + const stack = [1, 2, 3, 4]; + const position = 1; - const expected = [2, 9, 5, 1]; - expect(insertItemAtBottom(stack, newCard)).toStrictEqual(expected); - }); + const expected = [1, 3, 4]; + expect(removeItem(stack, position)).toStrictEqual(expected); + }); - test('adding a different fourth card to the bottom', () => { - const stack = [9, 5, 1]; - const newCard = 8; + test('remove the middle two cards', () => { + const stack = [1, 2, 3, 4]; - const expected = [8, 9, 5, 1]; - expect(insertItemAtBottom(stack, newCard)).toStrictEqual(expected); - }); + removeItem(stack, 1); + removeItem(stack, 1); - test('adding multiple cards to the stack to the bottom', () => { - const stack = [1]; + const expected = [1, 4]; + expect(stack).toStrictEqual(expected); + }); +}); - insertItemAtBottom(stack, 5); - insertItemAtBottom(stack, 9); +describe('make the top card disappear', () => { + test('remove the only card from the top', () => { + const stack = [1]; + const expected = []; + expect(removeItemFromTop(stack)).toStrictEqual(expected); + }); - const expected = [9, 5, 1]; - expect(stack).toStrictEqual(expected); - }); + test('remove the card from the top', () => { + const stack = [1, 2, 3]; + const expected = [1, 2]; + expect(removeItemFromTop(stack)).toStrictEqual(expected); }); - describe('make cards disappear', () => { - test('remove the card at the bottom', () => { - const stack = [1, 2, 3, 4]; - const position = 0; - - const expected = [2, 3, 4]; - - if (stack[0] === undefined) { - // eslint-disable-next-line no-undef - fail( - new Error( - 'The card has disappeared, but the stack has not changed in size. This magic trick has turned into actual magic. Perhaps a different method of removing the card will result in a stack that Elyse can work with...' - ) - ); - } - - expect(removeItem(stack, position)).toStrictEqual(expected); - }); - - test('remove the card at the top', () => { - const stack = [1, 2, 3, 4]; - const position = 3; - - const expected = [1, 2, 3]; - expect(removeItem(stack, position)).toStrictEqual(expected); - }); - - test('remove the second card', () => { - const stack = [1, 2, 3, 4]; - const position = 1; - - const expected = [1, 3, 4]; - expect(removeItem(stack, position)).toStrictEqual(expected); - }); - - test('remove the middle two cards', () => { - const stack = [1, 2, 3, 4]; - - removeItem(stack, 1); - removeItem(stack, 1); - - const expected = [1, 4]; - expect(expected).toStrictEqual(expected); - }); - - test('remove the only card from the top', () => { - const stack = [1]; - const expected = []; - expect(removeItemFromTop(stack)).toStrictEqual(expected); - }); - - test('remove the card from the top', () => { - const stack = [1, 2, 3]; - const expected = [1, 2]; - expect(removeItemFromTop(stack)).toStrictEqual(expected); - }); - - test('remove two cards from the top', () => { - const stack = [1, 2, 3]; - - removeItemFromTop(stack); - removeItemFromTop(stack); - - const expected = [1]; - expect(expected).toStrictEqual(expected); - }); - - test('remove the only card from the bottom', () => { - const stack = [1]; - const expected = []; - expect(removeItemAtBottom(stack)).toStrictEqual(expected); - }); - - test('remove the card from the bottom', () => { - const stack = [1, 2, 3]; - const expected = [2, 3]; - expect(removeItemAtBottom(stack)).toStrictEqual(expected); - }); - - test('remove two cards from the bottom', () => { - const stack = [1, 2, 3]; - - removeItemFromTop(stack); - removeItemFromTop(stack); - - const expected = [3]; - expect(expected).toStrictEqual(expected); - }); + test('remove two cards from the top', () => { + const stack = [1, 2, 3]; + + removeItemFromTop(stack); + removeItemFromTop(stack); + + const expected = [1]; + expect(stack).toStrictEqual(expected); }); +}); + +describe('make cards appear at the bottom', () => { + test('adding a second card to the bottom', () => { + const stack = [1]; + const newCard = 5; + + const expected = [5, 1]; + expect(insertItemAtBottom(stack, newCard)).toStrictEqual(expected); + }); + + test('adding a third card to the bottom', () => { + const stack = [5, 1]; + const newCard = 9; + + const expected = [9, 5, 1]; + expect(insertItemAtBottom(stack, newCard)).toStrictEqual(expected); + }); + + test('adding a fourth card to the bottom', () => { + const stack = [9, 5, 1]; + const newCard = 2; + + const expected = [2, 9, 5, 1]; + expect(insertItemAtBottom(stack, newCard)).toStrictEqual(expected); + }); + + test('adding a different fourth card to the bottom', () => { + const stack = [9, 5, 1]; + const newCard = 8; + + const expected = [8, 9, 5, 1]; + expect(insertItemAtBottom(stack, newCard)).toStrictEqual(expected); + }); + + test('adding multiple cards to the stack to the bottom', () => { + const stack = [1]; + + insertItemAtBottom(stack, 5); + insertItemAtBottom(stack, 9); + + const expected = [9, 5, 1]; + expect(stack).toStrictEqual(expected); + }); +}); + +describe('make the bottom card disappear', () => { + test('remove the only card from the bottom', () => { + const stack = [1]; + const expected = []; + expect(removeItemAtBottom(stack)).toStrictEqual(expected); + }); + + test('remove the card from the bottom', () => { + const stack = [1, 2, 3]; + const expected = [2, 3]; + expect(removeItemAtBottom(stack)).toStrictEqual(expected); + }); + + test('remove two cards from the bottom', () => { + const stack = [1, 2, 3]; + + removeItemAtBottom(stack); + removeItemAtBottom(stack); + + const expected = [3]; + expect(stack).toStrictEqual(expected); + }); +}); + +describe('check your work', () => { + test('an empty stack of cards has 0 cards', () => { + const stack = []; + + expect(checkSizeOfStack(stack, 0)).toBe(true); + expect(checkSizeOfStack(stack, 1)).toBe(false); + }); + + test('a stack with a single card has exactly 1 card', () => { + const stack = [7]; + + expect(checkSizeOfStack(stack, 0)).toBe(false); + expect(checkSizeOfStack(stack, 1)).toBe(true); + expect(checkSizeOfStack(stack, 2)).toBe(false); + }); + + test('a stack with the even cards has exactly 4 cards', () => { + const stack = [2, 4, 6, 8]; + + expect(checkSizeOfStack(stack, 3)).toBe(false); + expect(checkSizeOfStack(stack, 4)).toBe(true); + expect(checkSizeOfStack(stack, 5)).toBe(false); + }); + + test('a stack with the odd cards has exactly 5 cards', () => { + const stack = [1, 3, 5, 7, 9]; - describe('check your work', () => { - describe('an empty stack of cards', () => { - test('has 0 cards', () => { - const stack = []; - - expect(checkSizeOfStack(stack, 0)).toBe(true); - expect(checkSizeOfStack(stack, 1)).toBe(false); - }); - }); - - describe('a stack with a single card', () => { - test('has exactly 1 card', () => { - const stack = [7]; - - expect(checkSizeOfStack(stack, 0)).toBe(false); - expect(checkSizeOfStack(stack, 1)).toBe(true); - expect(checkSizeOfStack(stack, 2)).toBe(false); - }); - }); - - describe('a stack with the even cards', () => { - test('has exactly 4 cards', () => { - const stack = [2, 4, 6, 8]; - - expect(checkSizeOfStack(stack, 3)).toBe(false); - expect(checkSizeOfStack(stack, 4)).toBe(true); - expect(checkSizeOfStack(stack, 5)).toBe(false); - }); - }); - - describe('a stack with the odd cards', () => { - test('has exactly 5 cards', () => { - const stack = [1, 3, 5, 7, 9]; - - expect(checkSizeOfStack(stack, 3)).toBe(false); - expect(checkSizeOfStack(stack, 4)).toBe(false); - expect(checkSizeOfStack(stack, 5)).toBe(true); - }); - }); + expect(checkSizeOfStack(stack, 3)).toBe(false); + expect(checkSizeOfStack(stack, 4)).toBe(false); + expect(checkSizeOfStack(stack, 5)).toBe(true); }); }); diff --git a/exercises/concept/elyses-enchantments/eslint.config.mjs b/exercises/concept/elyses-enchantments/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/concept/elyses-enchantments/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/concept/elyses-enchantments/jest.config.js b/exercises/concept/elyses-enchantments/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/concept/elyses-enchantments/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/concept/elyses-enchantments/package.json b/exercises/concept/elyses-enchantments/package.json index 35b58b0237..1ab582c1b1 100644 --- a/exercises/concept/elyses-enchantments/package.json +++ b/exercises/concept/elyses-enchantments/package.json @@ -13,22 +13,25 @@ "directory": "exercises/concept/elyses-enchantments" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/concept/elyses-looping-enchantments/.docs/instructions.md b/exercises/concept/elyses-looping-enchantments/.docs/instructions.md index 9111eb7b59..23979e0fa3 100644 --- a/exercises/concept/elyses-looping-enchantments/.docs/instructions.md +++ b/exercises/concept/elyses-looping-enchantments/.docs/instructions.md @@ -8,7 +8,11 @@ To keep things simple, she only uses cards with values 1-10. Elyse wants to know how many cards of a particular type she has in her deck. Write a function `cardTypeCheck` that takes two parameters: an array of cards (Elyse's deck) and the type of card to count. + + +~~~exercism/note The function should use `forEach` and return the number of cards in the deck of the specified type. +~~~ ```javascript const cardType = 3; @@ -23,7 +27,11 @@ For another trick, Elyse needs to know how many odd or even cards there are in h Implement a function `determineOddEvenCards` that takes in two parameters: an array of cards (Elyse's deck), and a boolean (true is analogous to 'even', and false is analogous to 'odd'). This function should return a single number: the number of odd or even cards there are (depending on the value of the second argument) in the deck. + + +~~~exercism/note To practice, use a `for...of` loop in the function implementation this time. +~~~ ```javascript determineOddEvenCards([1, 2, 3, 1, 5, 6], true); diff --git a/exercises/concept/elyses-looping-enchantments/.docs/introduction.md b/exercises/concept/elyses-looping-enchantments/.docs/introduction.md index a2094288b6..4e5ba06635 100644 --- a/exercises/concept/elyses-looping-enchantments/.docs/introduction.md +++ b/exercises/concept/elyses-looping-enchantments/.docs/introduction.md @@ -23,7 +23,7 @@ for (let i = 0; i < numbers.length; i++) { ## The `for...of` Loop -When you want to work with the value directly in each iteration and do not require the index at all, you can use a `for .. of` loop. +When you want to work with the value directly in each iteration and do not require the index at all, you can use a `for...of` loop. `for...of` works like the basic `for` loop shown above, but instead of having to deal with the _index_ as a variable in the loop, you are provided with the _value_ directly. diff --git a/exercises/concept/elyses-looping-enchantments/.eslintrc b/exercises/concept/elyses-looping-enchantments/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/concept/elyses-looping-enchantments/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/concept/elyses-looping-enchantments/.gitignore b/exercises/concept/elyses-looping-enchantments/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/concept/elyses-looping-enchantments/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/concept/elyses-looping-enchantments/.meta/config.json b/exercises/concept/elyses-looping-enchantments/.meta/config.json index 13fe98a440..103d8e4817 100644 --- a/exercises/concept/elyses-looping-enchantments/.meta/config.json +++ b/exercises/concept/elyses-looping-enchantments/.meta/config.json @@ -1,12 +1,28 @@ { - "blurb": "Sift through Elyse's array of cards using various looping methods.", - "icon": "elyses-enchantments", - "authors": ["rishiosaur", "SleeplessByte"], - "contributors": ["junedev"], + "authors": [ + "rishiosaur", + "SleeplessByte" + ], + "contributors": [ + "junedev" + ], "files": { - "solution": ["enchantments.js"], - "test": ["enchantments.spec.js"], - "exemplar": [".meta/exemplar.js"] + "solution": [ + "enchantments.js" + ], + "test": [ + "enchantments.spec.js" + ], + "exemplar": [ + ".meta/exemplar.js" + ] }, - "forked_from": [] + "icon": "elyses-enchantments", + "blurb": "Sift through Elyse's array of cards using various looping methods.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": true, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/concept/elyses-looping-enchantments/babel.config.js b/exercises/concept/elyses-looping-enchantments/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/concept/elyses-looping-enchantments/babel.config.js +++ b/exercises/concept/elyses-looping-enchantments/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/concept/elyses-looping-enchantments/enchantments.js b/exercises/concept/elyses-looping-enchantments/enchantments.js index 6f9ae7453e..4de26a6047 100644 --- a/exercises/concept/elyses-looping-enchantments/enchantments.js +++ b/exercises/concept/elyses-looping-enchantments/enchantments.js @@ -9,6 +9,7 @@ * @returns {number} number of cards of a single type there are in the deck */ export function cardTypeCheck(stack, card) { + // 🚨 Use .forEach throw new Error('Implement the cardTypeCheck function'); } @@ -20,5 +21,6 @@ export function cardTypeCheck(stack, card) { * @returns {number} number of cards that are either odd or even (depending on `type`) */ export function determineOddEvenCards(stack, type) { + // 🚨 Use a `for...of` loop throw new Error('Implement the determineOddEvenCards function'); } diff --git a/exercises/concept/elyses-looping-enchantments/enchantments.spec.js b/exercises/concept/elyses-looping-enchantments/enchantments.spec.js index ff829883fc..3009f52f79 100644 --- a/exercises/concept/elyses-looping-enchantments/enchantments.spec.js +++ b/exercises/concept/elyses-looping-enchantments/enchantments.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test } from '@jest/globals'; import { cardTypeCheck, determineOddEvenCards } from './enchantments'; const TYPE_IS_ODD = false; diff --git a/exercises/concept/elyses-looping-enchantments/eslint.config.mjs b/exercises/concept/elyses-looping-enchantments/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/concept/elyses-looping-enchantments/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/concept/elyses-looping-enchantments/jest.config.js b/exercises/concept/elyses-looping-enchantments/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/concept/elyses-looping-enchantments/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/concept/elyses-looping-enchantments/package.json b/exercises/concept/elyses-looping-enchantments/package.json index c00aff6a10..b7e0269d6f 100644 --- a/exercises/concept/elyses-looping-enchantments/package.json +++ b/exercises/concept/elyses-looping-enchantments/package.json @@ -10,22 +10,25 @@ "directory": "exercises/concept/elyses-looping-enchantments" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/concept/elyses-transformative-enchantments/.docs/introduction.md b/exercises/concept/elyses-transformative-enchantments/.docs/introduction.md index 9744d2d5e2..92ed35f233 100644 --- a/exercises/concept/elyses-transformative-enchantments/.docs/introduction.md +++ b/exercises/concept/elyses-transformative-enchantments/.docs/introduction.md @@ -61,7 +61,7 @@ arr.reduce( return accumulator; }, - { even: [], odd: [] } + { even: [], odd: [] }, ); // => { even: [2, 4], odd: [1, 3] } ``` diff --git a/exercises/concept/elyses-transformative-enchantments/.eslintrc b/exercises/concept/elyses-transformative-enchantments/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/concept/elyses-transformative-enchantments/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/concept/elyses-transformative-enchantments/.gitignore b/exercises/concept/elyses-transformative-enchantments/.gitignore index bdb912f98a..0c88ff6ec3 100644 --- a/exercises/concept/elyses-transformative-enchantments/.gitignore +++ b/exercises/concept/elyses-transformative-enchantments/.gitignore @@ -1,3 +1,5 @@ -node_modules -yarn-error.log - +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/concept/elyses-transformative-enchantments/.meta/config.json b/exercises/concept/elyses-transformative-enchantments/.meta/config.json index 0086edb413..a87bbb0270 100644 --- a/exercises/concept/elyses-transformative-enchantments/.meta/config.json +++ b/exercises/concept/elyses-transformative-enchantments/.meta/config.json @@ -1,11 +1,27 @@ { - "blurb": "Elyse has grown her magical powers and attempts some really cool tricks now while you learn about transforming arrays.", - "authors": ["yyyc514"], - "contributors": ["SleeplessByte", "junedev"], + "authors": [ + "yyyc514" + ], + "contributors": [ + "SleeplessByte", + "junedev" + ], "files": { - "solution": ["enchantments.js"], - "test": ["enchantments.spec.js"], - "exemplar": [".meta/exemplar.js"] + "solution": [ + "enchantments.js" + ], + "test": [ + "enchantments.spec.js" + ], + "exemplar": [ + ".meta/exemplar.js" + ] }, - "forked_from": [] + "blurb": "Elyse has grown her magical powers and attempts some really cool tricks now while you learn about transforming arrays.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": true, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/concept/elyses-transformative-enchantments/babel.config.js b/exercises/concept/elyses-transformative-enchantments/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/concept/elyses-transformative-enchantments/babel.config.js +++ b/exercises/concept/elyses-transformative-enchantments/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/concept/elyses-transformative-enchantments/enchantments.spec.js b/exercises/concept/elyses-transformative-enchantments/enchantments.spec.js index a43a124045..0b10805fa7 100644 --- a/exercises/concept/elyses-transformative-enchantments/enchantments.spec.js +++ b/exercises/concept/elyses-transformative-enchantments/enchantments.spec.js @@ -1,13 +1,12 @@ -// @ts-check - +import { describe, expect, test } from '@jest/globals'; import { - seeingDouble, - threeOfEachThree, middleTwo, - sandwichTrick, - twoIsSpecial, perfectlyOrdered, reorder, + sandwichTrick, + seeingDouble, + threeOfEachThree, + twoIsSpecial, } from './enchantments'; describe('seeingDouble', () => { @@ -53,7 +52,7 @@ describe('threeOfEachThree', () => { expect(threeOfEachThree(deck)).toEqual(expected); }); - test('returns the same elements there are no 3s', () => { + test('returns the same elements if there are no 3s', () => { expect(threeOfEachThree([1, 2, 4])).toEqual([1, 2, 4]); }); diff --git a/exercises/concept/elyses-transformative-enchantments/eslint.config.mjs b/exercises/concept/elyses-transformative-enchantments/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/concept/elyses-transformative-enchantments/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/concept/elyses-transformative-enchantments/jest.config.js b/exercises/concept/elyses-transformative-enchantments/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/concept/elyses-transformative-enchantments/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/concept/elyses-transformative-enchantments/package.json b/exercises/concept/elyses-transformative-enchantments/package.json index 17a8a47edd..635ccdf82b 100644 --- a/exercises/concept/elyses-transformative-enchantments/package.json +++ b/exercises/concept/elyses-transformative-enchantments/package.json @@ -14,22 +14,25 @@ "directory": "exercises/concept/elyses-transformative-enchantments" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/concept/factory-sensors/.docs/instructions.md b/exercises/concept/factory-sensors/.docs/instructions.md index b8c47f3f9b..545c242b4e 100644 --- a/exercises/concept/factory-sensors/.docs/instructions.md +++ b/exercises/concept/factory-sensors/.docs/instructions.md @@ -1,6 +1,8 @@ # Instructions -Elena is the new quality manager of a newspaper factory. As she has just arrived in the company, she has decided to review some of the processes in the factory to see what could be improved. She found out that technicians are doing a lot of quality checks by hand. She sees there is a good opportunity for automation and asks you, a freelance developer, to develop a piece of software to monitor some of the machines. +Elena is the new quality manager of a newspaper factory. As she has just arrived in the company, she has decided to review some of the processes in the factory to see what could be improved. +She found out that technicians are doing a lot of quality checks by hand. +She sees there is a good opportunity for automation and asks you, a freelance developer, to develop a piece of software to monitor some of the machines. ## 1. Monitor the humidity level of the room @@ -63,20 +65,26 @@ Implements a function `monitorTheMachine` that takes an argument `actions`. `actions` is an object that has 4 properties : -- `check` is a _*function*_ that, when called, checks the temperature of the machine. - It may throw various errors - -- `alertDeadSensor` is a _*function*_ that, when called, alerts a technician that the temperature's sensor is dead. - -- `alertOverheating` is a _*function*_ that, when called, will turn on a warning light on the machine. - -- `shutdown` is a _*function*_ that, when called, will turn off the machine. - -The `monitorTheMachine` function should call `check()`. If it passes, the function should not return anything. However, it may `throw` an error. When this happens, you should, depending on the error: - -- `ArgumentError`: when this happens, call the `alertDeadSensor` function. -- `OverheatingError`: when this happens, if the temperature is less than 600 °C, call the `alertOverheating` function to turn on the warning light. If the temperature exceeds 600°C, the situation is critical, call the `shutdown` function. -- _anything else_: when this happens, rethrow the error +| property | description | +| ------------------ | ----------------------------------------------------------------------------------- | +| `check` | a _*function*_. Checks the temperature of the machine. It may throw various errors. | +| `alertDeadSensor` | a _*function*_. Alerts a technician that the temperature's sensor is dead. | +| `alertOverheating` | a _*function*_. Will turn on a warning light on the machine. | +| `shutdown` | a _*function*_. Will turn off the machine. | + +The `monitorTheMachine(actions)` function should internally call `check()`. +If that passes, the function should not return anything. +If that `throw`s an error, different behaviour is expected: + +| exception | expected behaviour | +| ------------------ | ------------------------------------ | +| `ArgumentError` | call the `alertDeadSensor` function. | +| `OverheatingError` | execute the overheating protocol. | +| _(anything else)_ | rethrow the error | + +**Overheating protocol** +If the temperature is less than 600 °C, turn on the warning light by calling the `alertOverheating` function. +If the temperature exceeds 600 °C, the situation is critical, so call the `shutdown` function. ```javascript monitorTheMachine({ diff --git a/exercises/concept/factory-sensors/.eslintrc b/exercises/concept/factory-sensors/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/concept/factory-sensors/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/concept/factory-sensors/.gitignore b/exercises/concept/factory-sensors/.gitignore index bdb912f98a..0c88ff6ec3 100644 --- a/exercises/concept/factory-sensors/.gitignore +++ b/exercises/concept/factory-sensors/.gitignore @@ -1,3 +1,5 @@ -node_modules -yarn-error.log - +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/concept/factory-sensors/.meta/config.json b/exercises/concept/factory-sensors/.meta/config.json index 654457afdf..50e42d1b4c 100644 --- a/exercises/concept/factory-sensors/.meta/config.json +++ b/exercises/concept/factory-sensors/.meta/config.json @@ -1,10 +1,29 @@ { - "blurb": "Learn how to handle errors by creating a piece of software for a newspaper factory.", - "authors": ["TomPradat"], - "contributors": ["SleeplessByte", "junedev"], + "authors": [ + "TomPradat" + ], + "contributors": [ + "SleeplessByte", + "junedev", + "themetar", + "orimdominic" + ], "files": { - "solution": ["factory-sensors.js"], - "test": ["factory-sensors.spec.js"], - "exemplar": [".meta/exemplar.js"] + "solution": [ + "factory-sensors.js" + ], + "test": [ + "factory-sensors.spec.js" + ], + "exemplar": [ + ".meta/exemplar.js" + ] + }, + "blurb": "Learn how to handle errors by creating a piece of software for a newspaper factory.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": true, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false } } diff --git a/exercises/concept/factory-sensors/babel.config.js b/exercises/concept/factory-sensors/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/concept/factory-sensors/babel.config.js +++ b/exercises/concept/factory-sensors/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/concept/factory-sensors/eslint.config.mjs b/exercises/concept/factory-sensors/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/concept/factory-sensors/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/concept/factory-sensors/factory-sensors.js b/exercises/concept/factory-sensors/factory-sensors.js index cfc2b02d67..be71cfd434 100644 --- a/exercises/concept/factory-sensors/factory-sensors.js +++ b/exercises/concept/factory-sensors/factory-sensors.js @@ -16,7 +16,7 @@ export class OverheatingError extends Error { * @throws {Error} */ export function checkHumidityLevel(humidityPercentage) { - throw new Error('Implement the checkHumidity function'); + throw new Error('Remove this line and implement the function'); } /** @@ -26,7 +26,7 @@ export function checkHumidityLevel(humidityPercentage) { * @throws {ArgumentError|OverheatingError} */ export function reportOverheating(temperature) { - throw new Error('Implement the reportOverheating function'); + throw new Error('Remove this line and implement the function'); } /** @@ -41,5 +41,5 @@ export function reportOverheating(temperature) { * @throws {ArgumentError|OverheatingError|Error} */ export function monitorTheMachine(actions) { - throw new Error('Implement the monitorTheMachine function'); + throw new Error('Remove this line and implement the function'); } diff --git a/exercises/concept/factory-sensors/factory-sensors.spec.js b/exercises/concept/factory-sensors/factory-sensors.spec.js index 3eda0876f5..5ecbd96202 100644 --- a/exercises/concept/factory-sensors/factory-sensors.spec.js +++ b/exercises/concept/factory-sensors/factory-sensors.spec.js @@ -1,16 +1,25 @@ +import { beforeEach, describe, expect, test, jest } from '@jest/globals'; import { + ArgumentError, checkHumidityLevel, - reportOverheating, monitorTheMachine, - ArgumentError, OverheatingError, + reportOverheating, } from './factory-sensors'; describe('checkHumidityLevel', () => { + test('should throw if the humidity percentage is 71', () => { + expect(() => checkHumidityLevel(71)).toThrow(); + }); + test('should throw if the humidity percentage is 100', () => { expect(() => checkHumidityLevel(100)).toThrow(); }); + test('should not throw if the humidity level is 70', () => { + expect(() => checkHumidityLevel(70)).not.toThrow(); + }); + test('should not throw if the humidity level is 53', () => { expect(() => checkHumidityLevel(53)).not.toThrow(); }); @@ -25,6 +34,10 @@ describe('reportOverheating', () => { expect(() => reportOverheating(null)).toThrow(ArgumentError); }); + test('should not throw if the temperature is 0°C', () => { + expect(() => reportOverheating(0)).not.toThrow(); + }); + test('should throw an OverheatingError if the temperature is 501°C', () => { expect(() => reportOverheating(501)).toThrow(OverheatingError); @@ -77,7 +90,7 @@ describe('monitorTheMachine', () => { expect(actions.shutdown).not.toHaveBeenCalled(); }); - test('should call only the shutdown action if the check throws an OverheatingError with a temperature equals to 651°C', () => { + test('should call only the shutdown action if the check throws an OverheatingError with a temperature of 651°C', () => { actions.check = jest.fn(() => { throw new OverheatingError(651); }); diff --git a/exercises/concept/factory-sensors/jest.config.js b/exercises/concept/factory-sensors/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/concept/factory-sensors/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/concept/factory-sensors/package.json b/exercises/concept/factory-sensors/package.json index 838b43f73a..ebf6a261ce 100644 --- a/exercises/concept/factory-sensors/package.json +++ b/exercises/concept/factory-sensors/package.json @@ -9,22 +9,25 @@ "directory": "exercises/concept/factory-sensors" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/concept/freelancer-rates/.docs/instructions.md b/exercises/concept/freelancer-rates/.docs/instructions.md index 790980e020..85649c1e77 100644 --- a/exercises/concept/freelancer-rates/.docs/instructions.md +++ b/exercises/concept/freelancer-rates/.docs/instructions.md @@ -1,13 +1,19 @@ # Instructions -In this exercise you will be writing code to help a freelancer communicate with his clients about the prices of certain projects. You will write a few utility functions to quickly calculate the costs for the clients. +In this exercise you will be writing code to help a freelancer communicate with their clients about the prices of certain projects. +You will write a few utility functions to quickly calculate the costs for the clients. + +```exercism/note +The `ratePerHour` variable and the `dayRate` function are related to money. +The units of measurement are money for a unit of time: hours and days respectively. +``` ## 1. Calculate the day rate given an hourly rate -A client contacts the freelancer to enquire about his rates. -The freelancer explains that he **_works 8 hours a day._** -However, the freelancer knows only his hourly rates for the project. -Help him estimate a day rate given an hourly rate. +A client contacts the freelancer to enquire about their rates. +The freelancer explains that they **_work 8 hours a day._** +However, the freelancer knows only their hourly rates for the project. +Help them estimate a day rate given an hourly rate. ```javascript dayRate(89); @@ -19,7 +25,7 @@ The day rate does not need to be rounded or changed to a "fixed" precision. ## 2. Calculate the number of workdays given a fixed budget Another day, a project manager offers the freelancer to work on a project with a fixed budget. -Given the fixed budget and the freelancer's hourly rate, help him calculate the number of days he would work until the budget is exhausted. +Given the fixed budget and the freelancer's hourly rate, help them calculate the number of days they would work until the budget is exhausted. The result _must_ be **rounded down** to the nearest whole number. ```javascript @@ -29,10 +35,10 @@ daysInBudget(20000, 89); ## 3. Calculate the discounted rate for large projects -Often, the freelancer's clients hire him for projects spanning over multiple months. +Often, the freelancer's clients hire them for projects spanning over multiple months. In these cases, the freelancer decides to offer a discount for every full month, and the remaining days are billed at day rate. -**_Every month has 22 billable days._** -Help him estimate his cost for such projects, given an hourly rate, the number of days the project spans, and a monthly discount rate. +Your excellent work-life balance means that you only work 22 days in each calendar month, so **_every month has 22 billable days._** +Help them estimate their cost for such projects, given an hourly rate, the number of billable days the project contains, and a monthly discount rate. The discount is always passed as a number, where `42%` becomes `0.42`. The result _must_ be **rounded up** to the nearest whole number. ```javascript diff --git a/exercises/concept/freelancer-rates/.docs/introduction.md b/exercises/concept/freelancer-rates/.docs/introduction.md index 3e2edcaedd..f68386e39a 100644 --- a/exercises/concept/freelancer-rates/.docs/introduction.md +++ b/exercises/concept/freelancer-rates/.docs/introduction.md @@ -14,7 +14,8 @@ Otherwise, the `number` type is likely the better option. ### Rounding -There is a built-in global object called `Math` that provides various [rounding functions][ref-math-object-rounding]. For example, you can round down (`floor`) or round up (`ceil`) decimal numbers to the nearest whole numbers. +There is a built-in global object called `Math` that provides various [rounding functions][ref-math-object-rounding]. +For example, you can round down (`floor`) or round up (`ceil`) decimal numbers to the nearest whole numbers. ```javascript Math.floor(234.34); // => 234 diff --git a/exercises/concept/freelancer-rates/.eslintrc b/exercises/concept/freelancer-rates/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/concept/freelancer-rates/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/concept/freelancer-rates/.gitignore b/exercises/concept/freelancer-rates/.gitignore index bdb912f98a..0c88ff6ec3 100644 --- a/exercises/concept/freelancer-rates/.gitignore +++ b/exercises/concept/freelancer-rates/.gitignore @@ -1,3 +1,5 @@ -node_modules -yarn-error.log - +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/concept/freelancer-rates/.meta/config.json b/exercises/concept/freelancer-rates/.meta/config.json index dfee849c7d..99c81e0df7 100644 --- a/exercises/concept/freelancer-rates/.meta/config.json +++ b/exercises/concept/freelancer-rates/.meta/config.json @@ -1,11 +1,27 @@ { - "blurb": "Learn about numbers whilst helping a freelancer communicate with a project manager about day- and month rates.", - "authors": ["SleeplessByte", "JaPatGitHub"], - "contributors": ["junedev"], + "authors": [ + "SleeplessByte", + "JaPatGitHub" + ], + "contributors": [ + "junedev" + ], "files": { - "solution": ["freelancer-rates.js"], - "test": ["freelancer-rates.spec.js"], - "exemplar": [".meta/exemplar.js"] + "solution": [ + "freelancer-rates.js" + ], + "test": [ + "freelancer-rates.spec.js" + ], + "exemplar": [ + ".meta/exemplar.js" + ] }, - "forked_from": [] + "blurb": "Learn about numbers whilst helping a freelancer communicate with a project manager about day- and month rates.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": true, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/concept/freelancer-rates/.meta/exemplar.js b/exercises/concept/freelancer-rates/.meta/exemplar.js index 2f2959787e..7b2f91fad9 100644 --- a/exercises/concept/freelancer-rates/.meta/exemplar.js +++ b/exercises/concept/freelancer-rates/.meta/exemplar.js @@ -49,11 +49,12 @@ export function daysInBudget(budget, ratePerHour) { * @returns {number} the discounted rate, rounded up */ export function priceWithMonthlyDiscount(ratePerHour, numDays, discount) { - const numMonths = Math.floor(numDays / 22); - const monthlyRate = 22 * dayRate(ratePerHour); + const billableDaysPerMonth = 22; + const numMonths = Math.floor(numDays / billableDaysPerMonth); + const monthlyRate = billableDaysPerMonth * dayRate(ratePerHour); const monthlyDiscountedRate = (1 - discount) * monthlyRate; - const numExtraDays = numDays % 22; + const numExtraDays = numDays % billableDaysPerMonth; const priceExtraDays = numExtraDays * dayRate(ratePerHour); return Math.ceil(numMonths * monthlyDiscountedRate + priceExtraDays); diff --git a/exercises/concept/freelancer-rates/babel.config.js b/exercises/concept/freelancer-rates/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/concept/freelancer-rates/babel.config.js +++ b/exercises/concept/freelancer-rates/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/concept/freelancer-rates/eslint.config.mjs b/exercises/concept/freelancer-rates/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/concept/freelancer-rates/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/concept/freelancer-rates/freelancer-rates.js b/exercises/concept/freelancer-rates/freelancer-rates.js index bc8f97f8ca..a58a1954d6 100644 --- a/exercises/concept/freelancer-rates/freelancer-rates.js +++ b/exercises/concept/freelancer-rates/freelancer-rates.js @@ -26,7 +26,7 @@ * @returns {number} the rate per day */ export function dayRate(ratePerHour) { - throw new Error('Implement the dayRate function'); + throw new Error('Remove this line and implement the function'); } /** @@ -37,7 +37,7 @@ export function dayRate(ratePerHour) { * @returns {number} the number of days */ export function daysInBudget(budget, ratePerHour) { - throw new Error('Implement the daysInBudget function'); + throw new Error('Remove this line and implement the function'); } /** @@ -49,5 +49,5 @@ export function daysInBudget(budget, ratePerHour) { * @returns {number} the rounded up discounted rate */ export function priceWithMonthlyDiscount(ratePerHour, numDays, discount) { - throw new Error('Implement the priceWithMonthlyDiscount function'); + throw new Error('Remove this line and implement the function'); } diff --git a/exercises/concept/freelancer-rates/freelancer-rates.spec.js b/exercises/concept/freelancer-rates/freelancer-rates.spec.js index 1af1893d10..9c8a3d5e60 100644 --- a/exercises/concept/freelancer-rates/freelancer-rates.spec.js +++ b/exercises/concept/freelancer-rates/freelancer-rates.spec.js @@ -1,5 +1,4 @@ -// @ts-check - +import { describe, expect, test } from '@jest/globals'; import { dayRate, daysInBudget, @@ -8,87 +7,78 @@ import { const DIFFERENCE_PRECISION_IN_DIGITS = 6; -describe('freelancer rates', () => { - describe('day rate', () => { - test('at 16/hour', () => { - const actual = dayRate(16); - expect(actual).toBe(128); - }); - - test('at 25/hour', () => { - const actual = dayRate(25); - expect(actual).toBe(200); - }); - - test('at 31.40/hour', () => { - const actual = dayRate(31.4); - expect(actual).toBeCloseTo(251.2, DIFFERENCE_PRECISION_IN_DIGITS); - }); - - test('at 89.89/hour', () => { - const actual = dayRate(89.89); - expect(actual).toBeCloseTo(719.12, DIFFERENCE_PRECISION_IN_DIGITS); - }); - - test('at 97.654321/hour', () => { - const actual = dayRate(97.654321); - expect(actual).toBeCloseTo(781.234568, DIFFERENCE_PRECISION_IN_DIGITS); - }); +describe('day rate', () => { + test('at 16/hour', () => { + const actual = dayRate(16); + expect(actual).toBe(128); + }); + + test('at 25/hour', () => { + const actual = dayRate(25); + expect(actual).toBe(200); + }); + + test('at 31.40/hour', () => { + const actual = dayRate(31.4); + expect(actual).toBeCloseTo(251.2, DIFFERENCE_PRECISION_IN_DIGITS); }); - describe('days in budget', () => { - describe('with a budget of 1280', () => { - test('at 16/hour', () => { - const actual = daysInBudget(1280, 16); - const expected = 10; + test('at 89.89/hour', () => { + const actual = dayRate(89.89); + expect(actual).toBeCloseTo(719.12, DIFFERENCE_PRECISION_IN_DIGITS); + }); - expect(actual).toBeCloseTo(expected, DIFFERENCE_PRECISION_IN_DIGITS); - }); + test('at 97.654321/hour', () => { + const actual = dayRate(97.654321); + expect(actual).toBeCloseTo(781.234568, DIFFERENCE_PRECISION_IN_DIGITS); + }); +}); - test('at 25/hour', () => { - const actual = daysInBudget(1280, 25); - const expected = 6; +describe('days in budget', () => { + test('with a budget of 1280 at 16/hour', () => { + const actual = daysInBudget(1280, 16); + const expected = 10; + + expect(actual).toBeCloseTo(expected, DIFFERENCE_PRECISION_IN_DIGITS); + }); - expect(actual).toBeCloseTo(expected, DIFFERENCE_PRECISION_IN_DIGITS); - }); + test('with a budget of 1280 at 25/hour', () => { + const actual = daysInBudget(1280, 25); + const expected = 6; - describe('with a budget of 835', () => { - test('at 12/hour', () => { - const actual = daysInBudget(835, 12); - const expected = 8; + expect(actual).toBeCloseTo(expected, DIFFERENCE_PRECISION_IN_DIGITS); + }); + + test('with a budget of 835 at 12/hour', () => { + const actual = daysInBudget(835, 12); + const expected = 8; + + expect(actual).toBeCloseTo(expected, DIFFERENCE_PRECISION_IN_DIGITS); + }); +}); + +describe('cost with monthly discount', () => { + test('at 16/hour for 70 days', () => { + const actual = priceWithMonthlyDiscount(16, 70, 0); + const expected = 8960; + expect(actual).toBeCloseTo(expected, DIFFERENCE_PRECISION_IN_DIGITS); + }); + + test('at 16/hour for 130 days with 15% discount', () => { + const actual = priceWithMonthlyDiscount(16, 130, 0.15); + const expected = 14528; + expect(actual).toBeCloseTo(expected, DIFFERENCE_PRECISION_IN_DIGITS); + }); - expect(actual).toBeCloseTo(expected, DIFFERENCE_PRECISION_IN_DIGITS); - }); - }); - }); + test('at 29.654321/hour for 220 days with 11.2%', () => { + const actual = priceWithMonthlyDiscount(29.654321, 220, 0.112); + const expected = 46347; + expect(actual).toBeCloseTo(expected, DIFFERENCE_PRECISION_IN_DIGITS); }); - describe('cost with monthly discount', () => { - describe('at 16/hour', () => { - test('for 70 days', () => { - const actual = priceWithMonthlyDiscount(16, 70, 0); - const expected = 8960; - expect(actual).toBeCloseTo(expected, DIFFERENCE_PRECISION_IN_DIGITS); - }); - - test('for 130 days with 15% discount', () => { - const actual = priceWithMonthlyDiscount(16, 130, 0.15); - const expected = 14528; - expect(actual).toBeCloseTo(expected, DIFFERENCE_PRECISION_IN_DIGITS); - }); - }); - describe('at 29.654321/hour', () => { - test('for 220 days with 11.2%', () => { - const actual = priceWithMonthlyDiscount(29.654321, 220, 0.112); - const expected = 46347; - expect(actual).toBeCloseTo(expected, DIFFERENCE_PRECISION_IN_DIGITS); - }); - - test('for 155 days with 25.47% discount', () => { - const actual = priceWithMonthlyDiscount(29.654321, 155, 0.2547); - const expected = 27467; - expect(actual).toBeCloseTo(expected, DIFFERENCE_PRECISION_IN_DIGITS); - }); - }); + test('at 29.654321/hour for 155 days with 25.47% discount', () => { + const actual = priceWithMonthlyDiscount(29.654321, 155, 0.2547); + const expected = 27467; + expect(actual).toBeCloseTo(expected, DIFFERENCE_PRECISION_IN_DIGITS); }); }); diff --git a/exercises/concept/freelancer-rates/jest.config.js b/exercises/concept/freelancer-rates/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/concept/freelancer-rates/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/concept/freelancer-rates/package.json b/exercises/concept/freelancer-rates/package.json index 3e0918379b..fc09775358 100644 --- a/exercises/concept/freelancer-rates/package.json +++ b/exercises/concept/freelancer-rates/package.json @@ -10,22 +10,25 @@ "directory": "exercises/concept/freelancer-rates" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/concept/fruit-picker/.docs/hints.md b/exercises/concept/fruit-picker/.docs/hints.md index 60894553ac..bf33da9664 100644 --- a/exercises/concept/fruit-picker/.docs/hints.md +++ b/exercises/concept/fruit-picker/.docs/hints.md @@ -4,34 +4,28 @@ - Callback functions are functions passed as an argument to a calling function. - Callback functions must meet the specification of the calling function. -- These callbacks are all synchronous, meaning they all must return a value. -## 1. Check if the grocer's service is online +## 1. Notify with a successful message -- Use the imported API function `checkStatus` in your function. -- Pass a callback function to `checkStatus`. It should expect to receive a string argument. -- Return the result from the `checkStatus` API function. +- Use the imported API function `notify` in your function. +- Pass an object to `notify`. It should have a property of `message`. Think about what the message value should be. +- Pay attention to the JSDoc type hint -- the function should not return a value. -## 2. See if the grocer has some fruit +## 2. Notify with an error message -- Use the imported API function `checkInventory` in your function. -- Create the query _object_ to pass as an argument. - - Follow the template in the instructions. -- Pass along the callback function to the `checkInventory` API function. -- Return the result from the `checkInventory` API function. +- Use the imported API function `notify` in your function. +- Pass another object to `notify`. It should have a property of `message`. The message value should differ from the last part. +- Pay attention to the comments [JSDoc] above the function definition: + - The function should not return a value. -## 3. Create a callback to buy fruit if the inventory is available +## 3. Place an order to buy fruit -- If the `err` argument is not null, throw a new error using `err`'s message. - - See [Error()][mdn-error-constructor] -- if the `err` is null, ignore it and just respond to the value of `isAvailable` - - This pattern is sometimes called a [`Node.js`-style callback][node-js-callback]. -- Return the action determined by `isAvailable` +- Use the already imported `order` function. +- Pay attention to the JSDoc type hints -- the function should receive 3 arguments and not return a value. +- Reuse the previously created callbacks as arguments to the order function. -## 4. Put it all together +## 4. Refactor your work into a more concise function -- Reuse the functions you have already written, composing them together. -- Return the value of the callback function. - -[mdn-error-constructor]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/Error -[node-js-callback]: https://nodejs.org/en/knowledge/getting-started/control-flow/what-are-callbacks/ +- Reuse the functions from the previous tasks. +- Pay attention to the JSDoc type hints -- the function should receive 2 arguments and not return a value. +- Combine the arguments into an object representing the query to the grocer to place an order. diff --git a/exercises/concept/fruit-picker/.docs/instructions.md b/exercises/concept/fruit-picker/.docs/instructions.md index 99a36741ce..278dd8c47f 100644 --- a/exercises/concept/fruit-picker/.docs/instructions.md +++ b/exercises/concept/fruit-picker/.docs/instructions.md @@ -1,22 +1,38 @@ # Instructions -You are creating a new online portal for your patrons to order their fruit fresh from the grocer. The grocer has an API that you can use to see if they have the inventory desired by your customers. You need to create a small library of functions for interacting with the grocer's API. +You are creating a new online portal for your patrons to order their fruit fresh from the grocer. +The grocer has an API that you can use to see if they have the inventory desired by your customers. +You need to create a small library of functions for interacting with the grocer's API. -## 1. Check if the grocer's service is online +## 1. Notify your customer when their order was successful -The grocer's application programming interface [API] provides a function to check if their service is online called `checkStatus`. Use the grocer's function to finish the implementation of your `isServiceOnline` function. The `checkStatus` function takes a callback function to receive the status from the API. +The portal should notify your customer if their order was successful. +Define the `onSuccess` callback function that will be called if the order was successful due to the grocer having enough fruit in stock. +This function should invoke the imported `notify` function, passing `{ message: 'SUCCESS' }` to let your customer know that their order was successful. -- if the status is `'ONLINE'`, return `true` -- if the status is `'OFFLINE'`, return `false` +```javascript +onSuccess(); +// => `notify` called with `{ message: 'SUCCESS' }` +``` + +## 2. Notify your customer when their order was unsuccessful + +The portal should notify your customer if their order was unsuccessful. +Define the `onError` callback function that will be called if the order was unsuccessful because the grocer _does not have_ the fruit in stock or there was an error. +This function should invoke the imported `notify` function, passing `{ message: 'ERROR' }` to let your customer know that their order was unsuccessful. ```javascript -isServiceOnline(); -// => true or false +onError(); +// => `notify` called with `{ message: 'ERROR' }` ``` -## 2. See if the grocer has some fruit +## 3. Create an API wrapper to wrap the grocer's API order function -The grocer's API provides a function to query their inventory called `checkInventory`. It receives two arguments: a _query_, and a _callback_ function. +Fruit orders are placed through the grocer's API via the provided `order` function. +This function receives three arguments: a _query_, containing the `variety` and `quantity` of fruit requested, a _callback_ function to be invoked when the order is successful, and a _callback_ function to be invoked when the order encounters an error. + +You want to insulate your codebase from potential external changes and decide to wrap the call to the `order` function inside a new function named `orderFromGrocer`. +Implement the `orderFromGrocer` function that attempts to place an order via a call to the grocer's API `order` function, making sure to forward the arguments passed into `orderFromGrocer` to the API call. The query takes the form of an _object_: @@ -27,39 +43,22 @@ const query = { }; ``` -For your `pickFruit` function, you have decided to generalize it and just pass along a callback. So using the arguments `variety` and `quantity` finish the function to call the `checkInventory` API. - ```javascript -function action(err, data) { - // logic -} - -pickFruit('pineapple', 20, action); -// calls the checkInventory function with the query and passes along the `action` callback function -``` - -## 3. Create a callback to buy fruit if the inventory is available - -Finish the `purchaseInventoryIfAvailable` callback function to be used with the grocer's `checkInventory` API function. The API function expects callback functions to accept two arguments, `err` and `isAvailable`. If an error occurs when checking the inventory, a string is returned to `err`. If there is no error, the value is `null`. `isAvailable` is a _boolean_ value, but if there is an error it is _undefined_. - -To finish `purchaseInventoryIfAvailable`, throw a new error if `err` is not null. Otherwise, return `'PURCHASE'` if `isAvailable` is _true_ or `'NOOP'` if _false_. - -```javascript -purchaseInventoryIfAvailable('Server Offline', undefined); -// => Throws new error "Server Offline" - -purchaseInventoryIfAvailable(null, true); -// => 'PURCHASE' - -purchaseInventoryIfAvailable(null, false); -// => 'NOOP' +orderFromGrocer( + { variety: 'pear', quantity: 12 }, + exampleSuccessCallback, + exampleErrorCallback, +); +// => `order` was called with the query and the callbacks ``` -## 4. Put it all together +## 4. Simplify handling placed orders -You notice that you're using `pickFruit` and `purchaseInventoryIfAvailable` so you decide to DRY up your code by extracting the code into a separate function called `pickAndPurchaseFruit`. Reuse `pickFruit` and `purchaseInventoryIfAvailable` to finish this function. +Your customers are now able to place fruit orders via your portal, however, you notice that you are invoking the `orderFromGrocer` function in many different places across your codebase, each time having to pass in a `query` and the two `callback` functions as arguments. +Seeing an opportunity to refactor your code, you think it would be simpler if you could place an order by just passing the `variety` and `quantity` of fruit required. +Define the `postOrder` helper function that takes `variety` and `quantity` as arguments and attempts to place an order with the grocer. ```javascript -pickAndPurchaseFruit('Red Delicious Apples', 42); -// 'PURCHASE' if available or 'NOOP' if not available +postOrder('peach', 100); +// => order placed for 100 peaches ``` diff --git a/exercises/concept/fruit-picker/.docs/introduction.md b/exercises/concept/fruit-picker/.docs/introduction.md index d86d426c34..4daee344f1 100644 --- a/exercises/concept/fruit-picker/.docs/introduction.md +++ b/exercises/concept/fruit-picker/.docs/introduction.md @@ -1,15 +1,17 @@ # Introduction -## Callbacks +## Callback functions -Callbacks are functions that are passed as arguments to another function. This is often done to control the order of execution in an asynchronous context. Writing a callback function is no different from writing a function, but the callback function's arguments must match the signature required by the calling function. +Callback functions are functions passed as arguments. +This programming pattern creates a sequence of function calls in both synchronous and asynchronous programming. +Writing a callback function is no different from writing a function; however, the callback function must match the signature defined by the calling function. ```javascript const squareLength = 5; // Caller function takes a callback function function applyToSquare(callback) { - callback(squareLength); + return callback(squareLength); } // Callback must expect the possible argument from the calling function @@ -20,46 +22,18 @@ function areaOfSquare(number) { applyToSquare(areaOfSquare); // => 25 ``` -You may also write callbacks as a function expression: +You may also write callbacks as a function expression, anonymous function expression, or arrow function expression: ```javascript applyToSquare(function squarePerimeter(side) { return side * 4; }); -``` - -## Arrow Functions - -Besides function declarations and function expressions, JavaScript also has another very concise syntax for defining a function. -These functions are called _arrow functions_. - -Here is a comparison between a function declaration and an arrow function. - -```javascript -function addUpTwoNumbers(num1, num2) { - return num1 + num2; -} - -// function keyword removed and => added -const addUpTwoNumbers = (num1, num2) => { - return num1 + num2; -}; -``` -If the function body contains only a return statement, like in the example above, the `{}` and the `return` keyword can be omitted. -If there is only one parameter, the parenthesis `()` can be omitted as well. - - -```javascript -const addUpTwoNumbers = (num1, num2) => num1 + num2; -const square = num => num * num; -``` - - -Arrow functions are often used to define short callback functions directly in the function call. +applyToSquare(function (side) { + return side * 4; +}); - -```javascript -applyToSquare(number => number * number); +applyToSquare((side) => { + return side * 4; +}); ``` - diff --git a/exercises/concept/fruit-picker/.eslintrc b/exercises/concept/fruit-picker/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/concept/fruit-picker/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/concept/fruit-picker/.gitignore b/exercises/concept/fruit-picker/.gitignore index bdb912f98a..0c88ff6ec3 100644 --- a/exercises/concept/fruit-picker/.gitignore +++ b/exercises/concept/fruit-picker/.gitignore @@ -1,3 +1,5 @@ -node_modules -yarn-error.log - +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/concept/fruit-picker/.meta/config.json b/exercises/concept/fruit-picker/.meta/config.json index a76a4aea54..8e5828e06b 100644 --- a/exercises/concept/fruit-picker/.meta/config.json +++ b/exercises/concept/fruit-picker/.meta/config.json @@ -1,11 +1,30 @@ { - "blurb": "Learn about callbacks picking fruit from the grocer", - "authors": ["neenjaw"], - "contributors": ["SleeplessByte"], + "authors": [ + "neenjaw" + ], + "contributors": [ + "SleeplessByte" + ], "files": { - "solution": ["fruit-picker.js"], - "editor": ["grocer.js"], - "test": ["fruit-picker.spec.js"], - "exemplar": [".meta/exemplar.js"] + "solution": [ + "fruit-picker.js" + ], + "test": [ + "fruit-picker.spec.js" + ], + "exemplar": [ + ".meta/exemplar.js" + ], + "editor": [ + "notifier.js", + "grocer.js" + ] + }, + "blurb": "Learn about callbacks ordering fruit from the grocer", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": true, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false } } diff --git a/exercises/concept/fruit-picker/.meta/design.md b/exercises/concept/fruit-picker/.meta/design.md index 3630cf7a31..9c16658f4f 100644 --- a/exercises/concept/fruit-picker/.meta/design.md +++ b/exercises/concept/fruit-picker/.meta/design.md @@ -11,7 +11,6 @@ In other words: how _function_ can be passed as an argument to another function, - Function that can pass along a callback function as an argument - How to write a function that can be used as a callback - How to compose functions with callbacks -- Functions using arrow functions ## Out of scope @@ -20,7 +19,6 @@ In other words: how _function_ can be passed as an argument to another function, ## Concepts -- `arrow-functions` - `callbacks` ## Prerequisites diff --git a/exercises/concept/fruit-picker/.meta/exemplar.js b/exercises/concept/fruit-picker/.meta/exemplar.js index 22ca6bf934..dba3e028eb 100644 --- a/exercises/concept/fruit-picker/.meta/exemplar.js +++ b/exercises/concept/fruit-picker/.meta/exemplar.js @@ -1,50 +1,39 @@ /// +// // @ts-check -import { checkStatus, checkInventory } from '../grocer'; +import { notify } from './notifier'; +import { order } from './grocer'; /** - * Returns the service status as a boolean value - * @return {boolean} + * @return void */ -export function isServiceOnline() { - return checkStatus((serviceStatus) => serviceStatus === 'ONLINE'); +export function onSuccess() { + notify({ message: 'SUCCESS' }); } /** - * Pick a fruit using the checkInventory API - * - * @param {string} variety - * @param {number} quantity - * @param {InventoryCallback} callback - * @return {AvailabilityAction} the result from checkInventory + * @return void */ -export function pickFruit(variety, quantity, callback) { - return checkInventory({ variety, quantity }, callback); +export function onError() { + notify({ message: 'ERROR' }); } /** - * This is a callback function to be passed to the checkInventory API - * handles the next step once the inventory is known - * @param {string | null} err - * @param {boolean} isAvailable - * @return {AvailabilityAction} whether the fruit was purchased 'PURCHASE' or 'NOOP' + * @param {GrocerQuery} query + * @param {FruitPickerSuccessCallback} onSuccessCallback + * @param {FruitPickerErrorCallback} onErrorCallback + * @return void */ -export function purchaseInventoryIfAvailable(err, isAvailable) { - if (err) { - throw new Error(err); - } - - return isAvailable ? 'PURCHASE' : 'NOOP'; +export function orderFromGrocer(query, onSuccessCallback, onErrorCallback) { + order(query, onSuccessCallback, onErrorCallback); } /** - * Pick a fruit, and if it is available, purchase it - * * @param {string} variety * @param {number} quantity - * @return {AvailabilityAction} whether the fruit was purchased 'PURCHASE' or 'NOOP' + * @return void */ -export function pickAndPurchaseFruit(variety, quantity) { - return pickFruit(variety, quantity, purchaseInventoryIfAvailable); +export function postOrder(variety, quantity) { + orderFromGrocer({ variety, quantity }, onSuccess, onError); } diff --git a/exercises/concept/fruit-picker/babel.config.js b/exercises/concept/fruit-picker/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/concept/fruit-picker/babel.config.js +++ b/exercises/concept/fruit-picker/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/concept/fruit-picker/eslint.config.mjs b/exercises/concept/fruit-picker/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/concept/fruit-picker/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/concept/fruit-picker/fruit-picker.js b/exercises/concept/fruit-picker/fruit-picker.js index 91d2147d3a..2390e6c336 100644 --- a/exercises/concept/fruit-picker/fruit-picker.js +++ b/exercises/concept/fruit-picker/fruit-picker.js @@ -9,46 +9,38 @@ // // In your own projects, files, and code, you can play with @ts-check as well. -import { checkStatus, checkInventory } from './grocer'; +import { notify } from './notifier'; +import { order } from './grocer'; /** - * Returns the service status as a boolean value - * @return {boolean} + * @return void */ -export function isServiceOnline() { - throw new Error('Implement the isServiceOnline function'); +export function onSuccess() { + // implement the onSuccess callback to call notify with a success message } /** - * Pick a fruit using the checkInventory API - * - * @param {string} variety - * @param {number} quantity - * @param {InventoryCallback} callback - * @return {AvailabilityAction} the result from checkInventory + * @return void */ -export function pickFruit(variety, quantity, callback) { - throw new Error('Implement the pickFruit function'); +export function onError() { + // implement the onError callback to call notify with an error message } /** - * This is a callback function to be passed to the checkInventory API - * handles the next step once the inventory is known - * @param {string | null} err - * @param {boolean | undefined} isAvailable - * @return {AvailabilityAction} whether the fruit was purchased 'PURCHASE' or 'NOOP' + * @param {GrocerQuery} query + * @param {FruitPickerSuccessCallback} onSuccessCallback + * @param {FruitPickerErrorCallback} onErrorCallback + * @return void */ -export function purchaseInventoryIfAvailable(err, isAvailable) { - throw new Error('Implement the purchaseInventoryIfAvailable function'); +export function orderFromGrocer(query, onSuccessCallback, onErrorCallback) { + // implement the orderFromGrocer function to order the query } /** - * Pick a fruit, and if it is available, purchase it - * * @param {string} variety * @param {number} quantity - * @return {AvailabilityAction} whether the fruit was purchased 'PURCHASE' or 'NOOP' + * @return void */ -export function pickAndPurchaseFruit(variety, quantity) { - throw new Error('Implement the pickAndPurchaseFruit function'); +export function postOrder(variety, quantity) { + //implement the postOrder function to create a query and order } diff --git a/exercises/concept/fruit-picker/fruit-picker.spec.js b/exercises/concept/fruit-picker/fruit-picker.spec.js index 21535916ff..3e1a59bdfd 100644 --- a/exercises/concept/fruit-picker/fruit-picker.spec.js +++ b/exercises/concept/fruit-picker/fruit-picker.spec.js @@ -1,116 +1,56 @@ -// @ts-check +import { afterEach, describe, expect, test, jest } from '@jest/globals'; +import { onError, onSuccess, orderFromGrocer, postOrder } from './fruit-picker'; +import { order } from './grocer'; +import { notify } from './notifier'; -import { - purchaseInventoryIfAvailable, - isServiceOnline, - pickAndPurchaseFruit, - pickFruit, -} from './fruit-picker'; -import { - setStatus, - resetStatus, - setResponse, - getLastQuery, - resetQuery, -} from './grocer'; +jest.mock('./notifier', () => ({ + notify: jest.fn(), +})); -describe('service status', () => { - beforeEach(() => { - resetStatus(); - }); - - test('returns the initial status of the service', () => { - expect(isServiceOnline()).toBe(false); - }); +jest.mock('./grocer', () => ({ + order: jest.fn(), +})); - test('returns the status when service is online', () => { - setStatus('ONLINE'); - expect(isServiceOnline()).toBe(true); - }); +afterEach(() => { + jest.resetAllMocks(); }); -describe('inventory service', () => { - beforeEach(() => { - resetQuery(); - }); - - test('uses the query format', () => { - pickFruit('strawberry', 5, () => 'PURCHASE'); - expect(getLastQuery()).toEqual({ - variety: 'strawberry', - quantity: 5, - }); - }); - - test('takes parameters for the query', () => { - pickFruit('blueberry', 20, () => 'PURCHASE'); - expect(getLastQuery()).toEqual({ - variety: 'blueberry', - quantity: 20, - }); - }); - - test('returns synchronously', () => { - expect(pickFruit('melon', 1, () => 'PURCHASE')).toBe('PURCHASE'); - }); - - test('returns the inventory status', () => { - expect(pickFruit('melon', 1, () => 'NOOP')).toBe('NOOP'); +describe('task 1', () => { + test('onSuccess should call notify with a success message', () => { + expect(onSuccess()).toEqual(undefined); + expect(notify).toHaveBeenCalledTimes(1); + expect(notify).toHaveBeenCalledWith({ message: 'SUCCESS' }); }); }); -describe('inventory result callback', () => { - test('throws error if receives inventory error', () => { - expect(() => { - purchaseInventoryIfAvailable('inventory error', undefined); - }).toThrow(); - }); - - test('returns "PURCHASE" when inventory is available', () => { - expect(purchaseInventoryIfAvailable(null, true)).toBe('PURCHASE'); - }); - - test('returns "NOOP" when inventory is unavailable', () => { - expect(purchaseInventoryIfAvailable(null, false)).toBe('NOOP'); +describe('task 2', () => { + test('onError should call notify with an error message', () => { + expect(onError()).toEqual(undefined); + expect(notify).toHaveBeenCalledTimes(1); + expect(notify).toHaveBeenCalledWith({ message: 'ERROR' }); }); }); -describe('putting it together', () => { - beforeEach(() => { - resetQuery(); - }); - - test('uses the query format', () => { - setResponse(null, true); - pickAndPurchaseFruit('jackfruit', 15); - expect(getLastQuery()).toEqual({ - variety: 'jackfruit', - quantity: 15, - }); - }); - - test('takes parameters for the query', () => { - setResponse(null, true); - pickAndPurchaseFruit('raspberry', 30); - expect(getLastQuery()).toEqual({ - variety: 'raspberry', - quantity: 30, - }); - }); - - test('throws error if receives inventory error', () => { - expect(() => { - pickAndPurchaseFruit('honeydew', 3); - }).toThrow(); - }); - - test('returns "NOOP" if quantity not available', () => { - setResponse(null, false); - expect(pickAndPurchaseFruit('apples', 12)).toBe('NOOP'); +describe('task 3', () => { + test('orderFromGrocer passes query and callback function arguments forward', () => { + const query = { variety: 'apple', quantity: 10 }; + orderFromGrocer(query, onSuccess, onError); + expect(order).toHaveBeenCalledTimes(1); + expect(order).toHaveBeenCalledWith(query, onSuccess, onError); }); +}); - test('returns "PURCHASE" if quantity available', () => { - setResponse(null, true); - expect(pickAndPurchaseFruit('oranges', 22)).toBe('PURCHASE'); +describe('task 4', () => { + test('postOrder composes a request to the grocer using the defined callbacks', () => { + const variety = 'banana'; + const quantity = 5; + postOrder(variety, quantity); + + expect(order).toHaveBeenCalledTimes(1); + expect(order).toHaveBeenCalledWith( + { variety, quantity }, + onSuccess, + onError, + ); }); }); diff --git a/exercises/concept/fruit-picker/global.d.ts b/exercises/concept/fruit-picker/global.d.ts index 26535a644e..0fbffe66e8 100644 --- a/exercises/concept/fruit-picker/global.d.ts +++ b/exercises/concept/fruit-picker/global.d.ts @@ -4,18 +4,16 @@ * type information on the fly */ -type Status = 'OFFLINE' | 'ONLINE'; -type AvailabilityAction = 'NOOP' | 'PURCHASE'; - -interface CheckStatus { - callback: StatusCallback; -} +type FruitPickerSuccess = { + message: 'SUCCESS'; +}; -type StatusCallback = (response: Status) => boolean; +type FruitPickerError = { + message: 'ERROR'; +}; -interface CheckInventory { - query: GrocerQuery; - callback: InventoryCallback; +declare module 'notifier' { + function notify(message: FruitPickerSuccess | FruitPickerError): void; } type GrocerQuery = { @@ -23,7 +21,22 @@ type GrocerQuery = { quantity: number; }; -type InventoryCallback = ( - err: string | null, - isAvailable: boolean -) => AvailabilityAction; +interface GrocerOnSuccessCallback { + (quantityOrdered: number): void; +} + +interface GrocerOnErrorCallback { + (errorMessage: string): void; +} + +declare module 'grocer' { + function order( + query: GrocerQuery, + onSuccess: GrocerOnSuccessCallback, + onError: GrocerOnErrorCallback, + ): void; +} + +type FruitPickerSuccessCallback = () => SuccessResult; + +type FruitPickerErrorCallback = () => ErrorResult; diff --git a/exercises/concept/fruit-picker/grocer.js b/exercises/concept/fruit-picker/grocer.js index 959dbeaf5f..f167889979 100644 --- a/exercises/concept/fruit-picker/grocer.js +++ b/exercises/concept/fruit-picker/grocer.js @@ -1,70 +1,12 @@ /** - * STORE STATUS API - */ - -let storeStatus = 'OFFLINE'; - -/** - * For testing purposes, set the store status - * @param {string} status - */ -export function setStatus(status) { - storeStatus = status; -} - -/** - * For testing purposes, reset the store status - */ -export function resetStatus() { - storeStatus = 'OFFLINE'; -} - -/** - * Invokes the callback with the store's status to simulate an API call - * @param {StatusCallback} callback - */ -export function checkStatus(callback) { - return callback(storeStatus); -} - -/** - * INVENTORY API - */ - -let lastInventoryQuery = undefined; -let inventoryResponse = undefined; - -/** - * For testing purposes, set the response to return when queried - * @param {any} ...nextResponse - */ -export function setResponse(...nextResponse) { - inventoryResponse = nextResponse; -} - -/** - * For testing purposes, get the last query - * @return {string} - */ -export function getLastQuery() { - return lastInventoryQuery; -} - -/** - * For testing purposes, reset the last query - */ -export function resetQuery() { - lastInventoryQuery = undefined; - inventoryResponse = ['undefined response']; -} - -/** - * Checks the inventory (inventoryResponse) then invokes the callback with the result - * @param {GrocerQuery} query - * @param {InventoryCallback} callback - * @return {AvailabilityAction} return the result of the callback - */ -export function checkInventory(query, callback) { - lastInventoryQuery = query; - return callback.apply(null, inventoryResponse); + * @param {GrocerQuery} query + * @param {GrocerOnSuccessCallback} onSuccess + * @param {GrocerOnErrorCallback} onError + * @return void + */ +export function order(query, onSuccess, onError) { + // This is a mocked function for use with the exercise. + query; + onSuccess; + onError; } diff --git a/exercises/concept/fruit-picker/jest.config.js b/exercises/concept/fruit-picker/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/concept/fruit-picker/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/concept/fruit-picker/notifier.js b/exercises/concept/fruit-picker/notifier.js new file mode 100644 index 0000000000..b30eaca2d0 --- /dev/null +++ b/exercises/concept/fruit-picker/notifier.js @@ -0,0 +1,8 @@ +/** + * @param {FruitPickerSuccess | FruitPickerError} message + * @return void + */ +export function notify(message) { + // This is a mocked function for use with the exercise. + message; +} diff --git a/exercises/concept/fruit-picker/package.json b/exercises/concept/fruit-picker/package.json index e748a100cf..f92ae9e3fa 100644 --- a/exercises/concept/fruit-picker/package.json +++ b/exercises/concept/fruit-picker/package.json @@ -10,22 +10,25 @@ "directory": "exercises/concept/fruit-picker" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/concept/high-score-board/.docs/hints.md b/exercises/concept/high-score-board/.docs/hints.md index f38a74fb6a..439fd9d8d2 100644 --- a/exercises/concept/high-score-board/.docs/hints.md +++ b/exercises/concept/high-score-board/.docs/hints.md @@ -28,11 +28,6 @@ - Use a `for...in` loop to go through all keys in the object. - For each key, set the new value as you did in task 4. -## 6. Normalize a high score - -- You can access the normalization function like you would access any other key in the object. -- Then, you can call that function using round brackets and pass in the score as an argument. - [mdn-delete]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete [mdn-shorthand-assignment]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Addition_assignment [mdn-for-in]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in diff --git a/exercises/concept/high-score-board/.docs/instructions.md b/exercises/concept/high-score-board/.docs/instructions.md index 975802c0bb..ea7118dfdb 100644 --- a/exercises/concept/high-score-board/.docs/instructions.md +++ b/exercises/concept/high-score-board/.docs/instructions.md @@ -64,7 +64,7 @@ Implement `updateScore`, which takes 3 parameters: The function should return the score board after the update was done. ```javascript -updateScore({"Freyja Ćirić", 12771008}, "Freyja Ćirić", 73) +updateScore({ 'Freyja Ćirić': 12771008 }, 'Freyja Ćirić', 73); // => {"Freyja Ćirić", 12771081} ``` @@ -87,26 +87,3 @@ const scoreBoard = { applyMondayBonus(scoreBoard); // => { 'Dave Thomas': 144, 'Freyja Ćirić': 639, 'José Valim': 365 } ``` - -## 6. Normalize a high score - -Different arcade halls award different score points. -To celebrate the best arcade player in town, a player's score needs to be normalized so scores from different arcade halls become comparable. - -Write a function `normalizeScore`. -To practice your object skills, instead of two parameters this function should accept one object as a parameter. -That object contains a key `score` with the value being a player's score (a number). -There is also a second key `normalizeFunction` that has a function as its value. -This function takes a score as an argument and returns the corrected score. - -Your function `normalizeScore` should return the normalized score that you get after applying the normalization function to the score that was passed in. - -```javascript -function normalize(score) { - return 2 * score + 10; -} - -const params = { score: 400, normalizeFunction: normalize }; -normalizeScore(params); -// => 810 -``` diff --git a/exercises/concept/high-score-board/.docs/introduction.md b/exercises/concept/high-score-board/.docs/introduction.md index a4424c2b57..299ca6a78e 100644 --- a/exercises/concept/high-score-board/.docs/introduction.md +++ b/exercises/concept/high-score-board/.docs/introduction.md @@ -4,15 +4,16 @@ Besides primitive data types like `number` and `string`, there is another important data type in JavaScript called `object`. Objects are collections of key-value pairs. -As such, they can be used as what is often referred to as maps or dictionaries in other languages. +As such, they can be used in the same way as what are often referred to as maps or dictionaries in other languages. In other languages, all values in a map often need to have the same data type. -In JavaScript, only the type of the key is restricted: it has to be a string. +In JavaScript, only the type of the key is restricted: it has to be a `string`. The values inside one object can have different types. They can be primitive types like numbers but also arrays, other objects or even functions. This makes objects very versatile so that they are also key entities for [object-oriented programming][oop] (OOP) in JavaScript. -In the following, we will focus on objects as collections or maps. Other use cases of objects will be covered in other concepts, see e.g., [Classes][concept-classes]. +In the following, we will focus on objects as collections or maps. +Other use cases of objects will be covered in other concepts, see e.g., [Classes][concept-classes]. ## Creating an Object @@ -143,7 +144,7 @@ for (let key in obj) { To avoid subtle errors, you should always assume the `for...in` loop visits the keys in an arbitrary order. Also, be aware that `for...in` includes [inherited keys][concept-inheritance] in its iteration. -[oop]: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object-oriented_JS +[oop]: https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Object-oriented_programming [concept-classes]: /tracks/javascript/concepts/classes [mdn-identifier]: https://developer.mozilla.org/en-US/docs/Glossary/Identifier [concept-inheritance]: /tracks/javascript/concepts/inheritance diff --git a/exercises/concept/high-score-board/.eslintrc b/exercises/concept/high-score-board/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/concept/high-score-board/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/concept/high-score-board/.gitignore b/exercises/concept/high-score-board/.gitignore index bdb912f98a..0c88ff6ec3 100644 --- a/exercises/concept/high-score-board/.gitignore +++ b/exercises/concept/high-score-board/.gitignore @@ -1,3 +1,5 @@ -node_modules -yarn-error.log - +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/concept/high-score-board/.meta/config.json b/exercises/concept/high-score-board/.meta/config.json index 065791fd1b..ff9b774bbe 100644 --- a/exercises/concept/high-score-board/.meta/config.json +++ b/exercises/concept/high-score-board/.meta/config.json @@ -1,11 +1,27 @@ { - "blurb": "Practice JavaScript objects by tracking high scores of an arcade game.", - "authors": ["junedev"], - "contributors": [], + "authors": [ + "junedev" + ], "files": { - "solution": ["high-score-board.js"], - "test": ["high-score-board.spec.js"], - "exemplar": [".meta/exemplar.js"] + "solution": [ + "high-score-board.js" + ], + "test": [ + "high-score-board.spec.js" + ], + "exemplar": [ + ".meta/exemplar.js" + ] }, - "forked_from": ["elixir/high-score", "swift/high-score-board"] + "forked_from": [ + "elixir/high-score", + "swift/high-score-board" + ], + "blurb": "Practice JavaScript objects by tracking high scores of an arcade game.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": true, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/concept/high-score-board/.meta/design.md b/exercises/concept/high-score-board/.meta/design.md index 45307215e9..9c425cb787 100644 --- a/exercises/concept/high-score-board/.meta/design.md +++ b/exercises/concept/high-score-board/.meta/design.md @@ -40,33 +40,27 @@ The Concepts this exercise unlocks are: This exercise could benefit from the following rules in the [analyzer][analyzer]: 1. `createScoreBoard` - - `essential`: Make sure no class, map etc. was created, there should be just an object. - `actionable`: If the student created an empty object first and then added the value, give feedback to include the entry in the object literal directly. - `actionable`: Check that the object was returned directly, no intermediate assignment to a variable necessary. 2. `addPlayer` - - `essential`: Check the assignment operator was used and no additional variables were declared. 3. `removePlayer` - - `essential`: Make sure `delete` was used and not set to undefined or null. - `actionable`: If there is additional code to check whether the key is present before deleting it, give feedback that this is not necessary. 4. `updateScore` - - `actionable`: If the student used a separate variable to calculate the new value first, tell them it is not necessary. - `actionable`: If the student did not use the shorthand assignment operator, tell them about it. If they used it already, give a `celebratory` comment. 5. `applyMondayBonus` - - `essential`: Check the student actually used `for...in`. - Same feedback as in `updateScore` applies. - Using `updateScore` in the solution should be treated as equally correct as the exemplar solution. 6. `normalizeScore` - - `actionable`: No intermediate variables necessary. ## Notes diff --git a/exercises/concept/high-score-board/.meta/exemplar.js b/exercises/concept/high-score-board/.meta/exemplar.js index c0bc95507f..5db3ee8616 100644 --- a/exercises/concept/high-score-board/.meta/exemplar.js +++ b/exercises/concept/high-score-board/.meta/exemplar.js @@ -63,13 +63,3 @@ export function applyMondayBonus(scoreBoard) { return scoreBoard; } - -/** - * Normalizes a score with the provided normalization function. - * - * @param {Params} params the parameters for performing the normalization - * @returns {number} normalized score - */ -export function normalizeScore(params) { - return params.normalizeFunction(params.score); -} diff --git a/exercises/concept/high-score-board/babel.config.js b/exercises/concept/high-score-board/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/concept/high-score-board/babel.config.js +++ b/exercises/concept/high-score-board/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/concept/high-score-board/eslint.config.mjs b/exercises/concept/high-score-board/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/concept/high-score-board/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/concept/high-score-board/high-score-board.js b/exercises/concept/high-score-board/high-score-board.js index d9ebe3b8b7..c652240b8f 100644 --- a/exercises/concept/high-score-board/high-score-board.js +++ b/exercises/concept/high-score-board/high-score-board.js @@ -7,7 +7,7 @@ * @returns {Record} new score board */ export function createScoreBoard() { - throw new Error('Please implement the createScoreBoard function'); + throw new Error('Remove this line and implement the function'); } /** @@ -19,7 +19,7 @@ export function createScoreBoard() { * @returns {Record} updated score board */ export function addPlayer(scoreBoard, player, score) { - throw new Error('Please implement the addPlayer function'); + throw new Error('Remove this line and implement the function'); } /** @@ -30,7 +30,7 @@ export function addPlayer(scoreBoard, player, score) { * @returns {Record} updated score board */ export function removePlayer(scoreBoard, player) { - throw new Error('Please implement the removePlayer function'); + throw new Error('Remove this line and implement the function'); } /** @@ -42,7 +42,7 @@ export function removePlayer(scoreBoard, player) { * @returns {Record} updated score board */ export function updateScore(scoreBoard, player, points) { - throw new Error('Please implement the updateScore function'); + throw new Error('Remove this line and implement the function'); } /** @@ -52,15 +52,5 @@ export function updateScore(scoreBoard, player, points) { * @returns {Record} updated score board */ export function applyMondayBonus(scoreBoard) { - throw new Error('Please implement the applyMondayBonus function'); -} - -/** - * Normalizes a score with the provided normalization function. - * - * @param {Params} params the parameters for performing the normalization - * @returns {number} normalized score - */ -export function normalizeScore(params) { - throw new Error('Please implement the normalizeScore function'); + throw new Error('Remove this line and implement the function'); } diff --git a/exercises/concept/high-score-board/high-score-board.spec.js b/exercises/concept/high-score-board/high-score-board.spec.js index 9a4f73260e..42f5f67823 100644 --- a/exercises/concept/high-score-board/high-score-board.spec.js +++ b/exercises/concept/high-score-board/high-score-board.spec.js @@ -1,10 +1,10 @@ +import { describe, expect, test } from '@jest/globals'; import { - createScoreBoard, addPlayer, + applyMondayBonus, + createScoreBoard, removePlayer, updateScore, - applyMondayBonus, - normalizeScore, } from './high-score-board'; describe('createScoreBoard', () => { @@ -129,27 +129,3 @@ describe('applyMondayBonus', () => { expect(Object.is(actual, scoreBoard)).toBe(true); }); }); - -describe('normalizeScore', () => { - test('applies the normalization function', () => { - const params = { - score: 45, - normalizeFunction: function (score) { - return score * 3 - 10; - }, - }; - - expect(normalizeScore(params)).toEqual(125); - }); - - test('works for different params', () => { - const params = { - score: 2100, - normalizeFunction: function (score) { - return score / 2 + 100; - }, - }; - - expect(normalizeScore(params)).toEqual(1150); - }); -}); diff --git a/exercises/concept/high-score-board/jest.config.js b/exercises/concept/high-score-board/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/concept/high-score-board/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/concept/high-score-board/package.json b/exercises/concept/high-score-board/package.json index 695ded7168..b51b226bd1 100644 --- a/exercises/concept/high-score-board/package.json +++ b/exercises/concept/high-score-board/package.json @@ -10,22 +10,25 @@ "directory": "exercises/concept/high-score-board" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/concept/lasagna-master/.docs/instructions.md b/exercises/concept/lasagna-master/.docs/instructions.md index d5068b9a74..7cd8fcec50 100644 --- a/exercises/concept/lasagna-master/.docs/instructions.md +++ b/exercises/concept/lasagna-master/.docs/instructions.md @@ -11,7 +11,7 @@ When you have lasagna in the oven, you want to know whether you can already take To make sure the lasagna does not burn in the oven, you usually set a timer. But sometimes you forget about that. -Write a function `cookingStatus` with that accepts the remaining time on the timer in minutes as a parameter. +Write a function `cookingStatus` that accepts the remaining time on the timer in minutes as a parameter. The function has three possible results. - If the timer shows `0`, it should return `'Lasagna is done.'`. @@ -31,7 +31,7 @@ cookingStatus(); ## 2. Estimate the preparation time For the next lasagna that you will prepare, you want to make sure you have enough time reserved so you can enjoy the cooking. -You already made a plan which layers your lasagna will have. +You already made a plan with all the layers your lasagna will have. Now you want to estimate how long the preparation will take based on that. Implement a function `preparationTime` that accepts an array of layers and the average preparation time per layer in minutes. @@ -70,7 +70,7 @@ The friend sent you the list of ingredients and told you the last item on the li Now you want to add that secret ingredient to your recipe as well. Write a function `addSecretIngredient` that accepts two arrays of ingredients as parameters. -The first parameter is the list your friend sent you, the second is the ingredient list for your own recipe. +The first parameter is the list your friend sent you and the second is the ingredient list for your own recipe. The function should add the last item from your friend's list to the end of your list. The array that represents your recipe should be modified directly and the function should not return anything. However, the first argument should not be modified. diff --git a/exercises/concept/lasagna-master/.docs/introduction.md b/exercises/concept/lasagna-master/.docs/introduction.md index 2ef177a752..0f9469eb68 100644 --- a/exercises/concept/lasagna-master/.docs/introduction.md +++ b/exercises/concept/lasagna-master/.docs/introduction.md @@ -1,8 +1,8 @@ # Introduction -A function allows to group code into a reusable unit. +A function is a block of organized, reusable code that is used to perform some action. There are multiple ways to define functions in JavaScript. -Here we will look at _function declarations_ and _function expressions_.. +Here we will look at _function declarations_ and _function expressions_. Other possibilities like [arrow functions][concept-arrow-functions] will be covered in other concepts. ## Function Declaration @@ -10,7 +10,7 @@ Other possibilities like [arrow functions][concept-arrow-functions] will be cove The standard way of defining a function in JavaScript is a _function declaration_, also called _function definition_ or _function statement_. It consists of the `function` keyword, the name of the function, and a comma-separated list of parameters in round brackets. -This is followed by the function body (the code that should be executed) wrapped in curly brackets. +This is followed by the function body (collection of statements that defines what a function does) wrapped in curly brackets. ```javascript function someName(param1, param2, param3) { @@ -18,7 +18,7 @@ function someName(param1, param2, param3) { } ``` -In JavaScript a function is invoked (called) by stating the function name followed by round brackets that contain the arguments. +In JavaScript, a function is invoked (called) by stating the function name followed by parentheses that contain the arguments. ```javascript someName(arg1, arg2, arg3); @@ -34,9 +34,9 @@ When working with parameters inside the function body, be aware of possible side However, if you modify such an argument (e.g. add a key to an object), that also modifies the original value that was passed in. By default, all parameters defined in the function declaration are optional in JavaScript. -If you provide less arguments than there are parameters, the missing arguments will be `undefined` inside the function, see [Null and Undefined][concept-null-undefined]. -In many cases it makes sense to assign a more appropriate default value than `undefined`. -This can by done by specifying default parameters directly in the function definition. +If you provide fewer arguments than there are parameters, the missing arguments will be `undefined` inside the function, see [Null and Undefined][concept-null-undefined]. +In many cases, it makes sense to assign a more appropriate default value than `undefined`. +This can be done by specifying default parameters directly in the function definition. ```javascript function someName(param1 = defaultValue1, param2 = defaultValue2) { @@ -46,9 +46,7 @@ function someName(param1 = defaultValue1, param2 = defaultValue2) { ## Return Statement -Using the `return` statement, you can pass the result of a function to the code that called it. -There can be multiple `return` statements in a function. -The execution of the function ends as soon as it hits one of those `return`s. +A `return` statement ends the function execution and specifies a value to be returned to the function caller. A function can have multiple `return` statements. ```javascript function checkNumber(num) { @@ -60,8 +58,19 @@ function checkNumber(num) { } ``` -If you use a naked return or no return at all, the result of the function is `undefined`. -There are no implicit returns in JavaScript. +The return value of a function can be stored in a variable. + +```javascript +function sum(x, y) { + return x + y; +} + +const total = sum(5, 10); +// => 15 +``` + +The result of a function that `return`s no value or does not have a `return` statement is `undefined`. +There are no implicit `return`s in JavaScript. ```javascript function nakedReturn(a) { @@ -105,7 +114,7 @@ const someFunction = function (param) { }; someOtherFunction(function (param) { - //... + // ... }); const obj = { diff --git a/exercises/concept/lasagna-master/.eslintrc b/exercises/concept/lasagna-master/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/concept/lasagna-master/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/concept/lasagna-master/.gitignore b/exercises/concept/lasagna-master/.gitignore index bdb912f98a..0c88ff6ec3 100644 --- a/exercises/concept/lasagna-master/.gitignore +++ b/exercises/concept/lasagna-master/.gitignore @@ -1,3 +1,5 @@ -node_modules -yarn-error.log - +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/concept/lasagna-master/.meta/config.json b/exercises/concept/lasagna-master/.meta/config.json index 6b24340dd7..6af872e73a 100644 --- a/exercises/concept/lasagna-master/.meta/config.json +++ b/exercises/concept/lasagna-master/.meta/config.json @@ -1,12 +1,25 @@ { - "blurb": "Dive deeper into JavaScript functions while preparing to cook the perfect lasagna.", - "authors": ["junedev"], - "contributors": [], + "authors": [ + "junedev" + ], "files": { - "solution": ["lasagna-master.js"], - "test": ["lasagna-master.spec.js"], - "exemplar": [".meta/exemplar.js"] + "solution": [ + "lasagna-master.js" + ], + "test": [ + "lasagna-master.spec.js" + ], + "exemplar": [ + ".meta/exemplar.js" + ] }, + "blurb": "Dive deeper into JavaScript functions while preparing to cook the perfect lasagna.", "source": "Inspired by the Lasagna Master exercise in the Swift track", - "source_url": "https://github.com/exercism/swift/blob/main/exercises/concept/lasagna-master/.docs/instructions.md" + "source_url": "https://github.com/exercism/swift/blob/main/exercises/concept/lasagna-master/.docs/instructions.md", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": true, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/concept/lasagna-master/babel.config.js b/exercises/concept/lasagna-master/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/concept/lasagna-master/babel.config.js +++ b/exercises/concept/lasagna-master/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/concept/lasagna-master/eslint.config.mjs b/exercises/concept/lasagna-master/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/concept/lasagna-master/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/concept/lasagna-master/jest.config.js b/exercises/concept/lasagna-master/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/concept/lasagna-master/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/concept/lasagna-master/lasagna-master.spec.js b/exercises/concept/lasagna-master/lasagna-master.spec.js index fb7879fef9..573007d474 100644 --- a/exercises/concept/lasagna-master/lasagna-master.spec.js +++ b/exercises/concept/lasagna-master/lasagna-master.spec.js @@ -1,8 +1,9 @@ +import { describe, expect, test } from '@jest/globals'; import { + addSecretIngredient, cookingStatus, preparationTime, quantities, - addSecretIngredient, scaleRecipe, } from './lasagna-master'; @@ -249,7 +250,7 @@ function expectObjectsToBeEqual(actualObj, expectedObj) { for (const key in expectedObj) { expect(actualObj[key]).toBeCloseTo( expectedObj[key], - DIFFERENCE_PRECISION_IN_DIGITS + DIFFERENCE_PRECISION_IN_DIGITS, ); } expect(Object.keys(actualObj).length).toBe(Object.keys(expectedObj).length); diff --git a/exercises/concept/lasagna-master/package.json b/exercises/concept/lasagna-master/package.json index 46dc9755d6..cb1448e94d 100644 --- a/exercises/concept/lasagna-master/package.json +++ b/exercises/concept/lasagna-master/package.json @@ -10,22 +10,25 @@ "directory": "exercises/concept/lasagna-master" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/concept/lasagna/.docs/introduction.md b/exercises/concept/lasagna/.docs/introduction.md index 2d9cbf3492..f852f68f5c 100644 --- a/exercises/concept/lasagna/.docs/introduction.md +++ b/exercises/concept/lasagna/.docs/introduction.md @@ -2,13 +2,18 @@ JavaScript is a dynamic language, supporting object-oriented, imperative, and declarative (e.g. functional programming) styles. -## (Re-)Assignment +## Variables -There are a few primary ways to assign values to names in JavaScript - using variables or constants. On Exercism, variables are always written in [camelCase][wiki-camel-case]; constants are written in [SCREAMING_SNAKE_CASE][wiki-snake-case]. There is no official guide to follow, and various companies and organizations have various style guides. _Feel free to write variables any way you like_. The upside from writing them the way the exercises are prepared is that they'll be highlighted differently in the web interface and most IDEs. +There are a few primary ways to assign values to names in JavaScript - using variables or constants. +On Exercism, variables are always written in [camelCase][wiki-camel-case]; constants are written in [SCREAMING_SNAKE_CASE][wiki-snake-case]. +There is no official guide to follow, and various companies and organizations have various style guides. +_Feel free to write variables any way you like_. +The upside from writing them the way the exercises are prepared is that they'll be highlighted differently in the web interface and most IDEs. Variables in JavaScript can be defined using the [`const`][mdn-const], [`let`][mdn-let] or [`var`][mdn-var] keyword. -A variable can reference different values over its lifetime when using `let` or `var`. For example, `myFirstVariable` can be defined and redefined many times using the assignment operator `=`: +A variable can reference different values over its lifetime when using `let` or `var`. +For example, `myFirstVariable` can be defined and redefined many times using the assignment operator `=`: ```javascript let myFirstVariable = 1; @@ -16,7 +21,8 @@ myFirstVariable = 'Some string'; myFirstVariable = new SomeComplexClass(); ``` -In contrast to `let` and `var`, variables that are defined with `const` can only be assigned once. This is used to define constants in JavaScript. +In contrast to `let` and `var`, variables that are defined with `const` can only be assigned once. +This is used to define constants in JavaScript. ```javascript const MY_FIRST_CONSTANT = 10; @@ -26,11 +32,16 @@ MY_FIRST_CONSTANT = 20; // => TypeError: Assignment to constant variable. ``` -> 💡 In a later Concept Exercise the difference between _constant_ assignment / binding and _constant_ value is explored and explained. + +~~~exercism/note +💡 In a later Concept Exercise the difference between _constant_ assignment, _constant_ binding, and _constant_ value is explored and explained. +~~~ ## Function Declarations -In JavaScript, units of functionality are encapsulated in _functions_, usually grouping functions together in the same file if they belong together. These functions can take parameters (arguments), and can _return_ a value using the `return` keyword. Functions are invoked using `()` syntax. +In JavaScript, units of functionality are encapsulated in _functions_, usually grouping functions together in the same file if they belong together. +These functions have parameters (so they can take arguments), and can _return_ a value using the `return` keyword. +Functions are invoked using `()` syntax. ```javascript function add(num1, num2) { @@ -41,11 +52,31 @@ add(1, 3); // => 4 ``` -> 💡 In JavaScript there are _many_ different ways to declare a function. These other ways look different than using the `function` keyword. The track tries to gradually introduce them, but if you already know about them, feel free to use any of them. In most cases, using one or the other isn't better or worse. +In this example, the function name is `add`. +It has two parameters, `num1` and `num2`. +It returns a value: the result of the expression `num1 + num2` + +Then the function `add` is _called_ using two arguments: `1` and `3`. +The result of the function, the returned value, is `4`. + +```exercism/note +💡 In JavaScript there are _many_ different ways to declare a function. +Some of these other ways look different than using the `function` keyword. +The track tries to gradually introduce them, but if you already know about them, feel free to use any of them. +In most cases, using one or the other isn't better or worse. +``` ## Exposing to Other Files -To make a `function`, a constant, or a variable available in _other files_, they need to be [exported][mdn-export] using the `export` keyword. Another file may then [import][mdn-import] these using the `import` keyword. This is also known as the module system. A great example is how all the tests work. Each exercise has at least one file, for example `lasagna.js`, which contains the _implementation_. Additionally there is at least one other file, for example `lasagna.spec.js`, that contains the _tests_. This file _imports_ the public (i.e. exported) entities in order to test the implementation: +To make a `function`, a constant, or a variable available in _other files_, they need to be [exported][mdn-export] using the `export` keyword. +Another file may then [import][mdn-import] these using the `import` keyword. +This is also known as the module system. + +A great example is how all the tests work. +Each exercise has at least one file, for example `lasagna.js`, which contains the _implementation_. +The _implementation_ is `export`ed from the file, making it available to import in another file. +Additionally there is at least one other file, for example `lasagna.spec.js`, that contains the _tests_. +This file `import`s the public (i.e. `export`ed) entities in order to test the implementation: ```javascript // file.js diff --git a/exercises/concept/lasagna/.eslintrc b/exercises/concept/lasagna/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/concept/lasagna/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/concept/lasagna/.gitignore b/exercises/concept/lasagna/.gitignore index bdb912f98a..0c88ff6ec3 100644 --- a/exercises/concept/lasagna/.gitignore +++ b/exercises/concept/lasagna/.gitignore @@ -1,3 +1,5 @@ -node_modules -yarn-error.log - +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/concept/lasagna/.meta/config.json b/exercises/concept/lasagna/.meta/config.json index 1ea5472495..4b1a67a407 100644 --- a/exercises/concept/lasagna/.meta/config.json +++ b/exercises/concept/lasagna/.meta/config.json @@ -1,11 +1,29 @@ { - "blurb": "Learn the basics of JavaScript cooking a brilliant lasagna from your favorite cooking book.", - "authors": ["SleeplessByte"], - "contributors": ["junedev"], + "authors": [ + "SleeplessByte" + ], + "contributors": [ + "junedev" + ], "files": { - "solution": ["lasagna.js"], - "test": ["lasagna.spec.js"], - "exemplar": [".meta/exemplar.js"] + "solution": [ + "lasagna.js" + ], + "test": [ + "lasagna.spec.js" + ], + "exemplar": [ + ".meta/exemplar.js" + ] }, - "forked_from": ["csharp/lasagna"] + "forked_from": [ + "csharp/lasagna" + ], + "blurb": "Learn the basics of JavaScript cooking a brilliant lasagna from your favorite cooking book.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": true, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/concept/lasagna/babel.config.js b/exercises/concept/lasagna/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/concept/lasagna/babel.config.js +++ b/exercises/concept/lasagna/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/concept/lasagna/eslint.config.mjs b/exercises/concept/lasagna/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/concept/lasagna/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/concept/lasagna/jest.config.js b/exercises/concept/lasagna/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/concept/lasagna/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/concept/lasagna/lasagna.js b/exercises/concept/lasagna/lasagna.js index 44c7226f3b..4a2badc76c 100644 --- a/exercises/concept/lasagna/lasagna.js +++ b/exercises/concept/lasagna/lasagna.js @@ -43,7 +43,7 @@ const PREPARATION_MINUTES_PER_LAYER = 2; * @returns {number} the number of minutes remaining */ export function remainingMinutesInOven(actualMinutesInOven) { - throw new Error('Please implement the remainingMinutesInOven function'); + throw new Error('Remove this line and implement the function'); } /** @@ -53,7 +53,7 @@ export function remainingMinutesInOven(actualMinutesInOven) { * @returns {number} the total preparation time */ export function preparationTimeInMinutes(numberOfLayers) { - throw new Error('Please implement the preparationTimeInMinutes function'); + throw new Error('Remove this line and implement the function'); } /** @@ -65,5 +65,5 @@ export function preparationTimeInMinutes(numberOfLayers) { * @returns {number} the total working time */ export function totalTimeInMinutes(numberOfLayers, actualMinutesInOven) { - throw new Error('Please implement the totalTimeInMinutes function'); + throw new Error('Remove this line and implement the function'); } diff --git a/exercises/concept/lasagna/lasagna.spec.js b/exercises/concept/lasagna/lasagna.spec.js index 92c02fe58a..243ecf64c0 100644 --- a/exercises/concept/lasagna/lasagna.spec.js +++ b/exercises/concept/lasagna/lasagna.spec.js @@ -1,8 +1,8 @@ +import { describe, expect, test } from '@jest/globals'; import { - // eslint-disable-next-line import/named EXPECTED_MINUTES_IN_OVEN, - remainingMinutesInOven, preparationTimeInMinutes, + remainingMinutesInOven, totalTimeInMinutes, } from './lasagna'; @@ -37,6 +37,6 @@ describe('totalTimeInMinutes', () => { test('calculates the total cooking time', () => { expect(totalTimeInMinutes(1, 5)).toBe(7); expect(totalTimeInMinutes(4, 15)).toBe(23); - expect(totalTimeInMinutes(1, 35)).toBe(37); + expect(totalTimeInMinutes(1, 30)).toBe(32); }); }); diff --git a/exercises/concept/lasagna/package.json b/exercises/concept/lasagna/package.json index e82e9e0b77..b992b56271 100644 --- a/exercises/concept/lasagna/package.json +++ b/exercises/concept/lasagna/package.json @@ -10,22 +10,25 @@ "directory": "exercises/concept/lasagna" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/concept/lucky-numbers/.docs/instructions.md b/exercises/concept/lucky-numbers/.docs/instructions.md index 4d54b563dc..d71bbd99be 100644 --- a/exercises/concept/lucky-numbers/.docs/instructions.md +++ b/exercises/concept/lucky-numbers/.docs/instructions.md @@ -1,7 +1,7 @@ # Instructions Your friend Kojo is a big fan of numbers. -He has a small website for playing around with numbers: www.fun-with-numbers.com. +He has a small website called 'fun-with-numbers'. Kojo is not that good at programming so he asked you for help. You will build two helper functions for new number games on Kojos' website and a third one to validate some input the user can enter. diff --git a/exercises/concept/lucky-numbers/.docs/introduction.md b/exercises/concept/lucky-numbers/.docs/introduction.md index f484992f13..6e150efb72 100644 --- a/exercises/concept/lucky-numbers/.docs/introduction.md +++ b/exercises/concept/lucky-numbers/.docs/introduction.md @@ -102,12 +102,12 @@ Coercion to boolean commonly occurs for - the condition of an [if statement][concept-conditionals] - the first operand of the [ternary operator][mdn-ternary] `?` - the operand of the logical NOT operator `!` -- the operands of the logical AND `&&` and OR `||` operators (the result is of the expression is one of the operands, not necessarily a boolean) +- the operands of the logical AND `&&` and OR `||` operators (the result of the expression is one of the operands, not necessarily a boolean) ```javascript const num = 0; if (num) { - // this block NOT is executed because 0 is falsy + // this block is NOT executed because 0 is falsy } ``` diff --git a/exercises/concept/lucky-numbers/.eslintrc b/exercises/concept/lucky-numbers/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/concept/lucky-numbers/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/concept/lucky-numbers/.gitignore b/exercises/concept/lucky-numbers/.gitignore index bdb912f98a..0c88ff6ec3 100644 --- a/exercises/concept/lucky-numbers/.gitignore +++ b/exercises/concept/lucky-numbers/.gitignore @@ -1,3 +1,5 @@ -node_modules -yarn-error.log - +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/concept/lucky-numbers/.meta/config.json b/exercises/concept/lucky-numbers/.meta/config.json index 37542eef2c..90f3ff37cb 100644 --- a/exercises/concept/lucky-numbers/.meta/config.json +++ b/exercises/concept/lucky-numbers/.meta/config.json @@ -1,11 +1,28 @@ { - "blurb": "Practice type conversion while helping your friend Kojo with his website www.fun-with-numbers.com.", - "authors": ["shubhsk88", "junedev"], - "contributors": ["neenjaw", "SleeplessByte"], + "authors": [ + "shubhsk88", + "junedev" + ], + "contributors": [ + "neenjaw", + "SleeplessByte" + ], "files": { - "solution": ["lucky-numbers.js"], - "test": ["lucky-numbers.spec.js"], - "exemplar": [".meta/exemplar.js"] + "solution": [ + "lucky-numbers.js" + ], + "test": [ + "lucky-numbers.spec.js" + ], + "exemplar": [ + ".meta/exemplar.js" + ] }, - "forked_from": [] + "blurb": "Practice type conversion while helping your friend Kojo with his website www.fun-with-numbers.com.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": true, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/concept/lucky-numbers/.meta/exemplar.js b/exercises/concept/lucky-numbers/.meta/exemplar.js index eddd45dcaa..c4b488f852 100644 --- a/exercises/concept/lucky-numbers/.meta/exemplar.js +++ b/exercises/concept/lucky-numbers/.meta/exemplar.js @@ -18,7 +18,7 @@ export function twoSum(array1, array2) { * Checks whether a number is a palindrome. * * @param {number} value - * @returns {boolean} whether the number is a palindrome or not + * @returns {boolean} whether the number is a palindrome or not */ export function luckyNumber(value) { const strValue = String(value); diff --git a/exercises/concept/lucky-numbers/babel.config.js b/exercises/concept/lucky-numbers/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/concept/lucky-numbers/babel.config.js +++ b/exercises/concept/lucky-numbers/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/concept/lucky-numbers/eslint.config.mjs b/exercises/concept/lucky-numbers/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/concept/lucky-numbers/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/concept/lucky-numbers/jest.config.js b/exercises/concept/lucky-numbers/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/concept/lucky-numbers/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/concept/lucky-numbers/lucky-numbers.js b/exercises/concept/lucky-numbers/lucky-numbers.js index 2f5d931f1b..3146f9154b 100644 --- a/exercises/concept/lucky-numbers/lucky-numbers.js +++ b/exercises/concept/lucky-numbers/lucky-numbers.js @@ -8,17 +8,17 @@ * @returns {number} sum of the two arrays */ export function twoSum(array1, array2) { - throw new Error('Implement the twoSum function'); + throw new Error('Remove this line and implement the function'); } /** * Checks whether a number is a palindrome. * * @param {number} value - * @returns {boolean} whether the number is a palindrome or not + * @returns {boolean} whether the number is a palindrome or not */ export function luckyNumber(value) { - throw new Error('Implement the luckyNumber function'); + throw new Error('Remove this line and implement the function'); } /** @@ -29,5 +29,5 @@ export function luckyNumber(value) { * @returns {string} error message */ export function errorMessage(input) { - throw new Error('Implement the errorMessage function'); + throw new Error('Remove this line and implement the function'); } diff --git a/exercises/concept/lucky-numbers/lucky-numbers.spec.js b/exercises/concept/lucky-numbers/lucky-numbers.spec.js index 801bc6d54b..098c881f06 100644 --- a/exercises/concept/lucky-numbers/lucky-numbers.spec.js +++ b/exercises/concept/lucky-numbers/lucky-numbers.spec.js @@ -1,4 +1,5 @@ -import { twoSum, luckyNumber, errorMessage } from './lucky-numbers'; +import { describe, expect, test } from '@jest/globals'; +import { errorMessage, luckyNumber, twoSum } from './lucky-numbers'; describe('twoSum', () => { test('sums the numbers correctly for short arrays', () => { diff --git a/exercises/concept/lucky-numbers/package.json b/exercises/concept/lucky-numbers/package.json index 4b55c4aae6..aad33e6ab4 100644 --- a/exercises/concept/lucky-numbers/package.json +++ b/exercises/concept/lucky-numbers/package.json @@ -10,22 +10,25 @@ "directory": "exercises/concept/lucky-numbers" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/concept/mixed-juices/.docs/hints.md b/exercises/concept/mixed-juices/.docs/hints.md index b44bd7498b..218134f715 100644 --- a/exercises/concept/mixed-juices/.docs/hints.md +++ b/exercises/concept/mixed-juices/.docs/hints.md @@ -23,7 +23,7 @@ - You can combine two conditions for the loop using [logical operators][concept-booleans]. [mdn-switch]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch# -[mdn-fallthrough]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch#what_happens_if_i_forgot_a_break +[mdn-fallthrough]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch#breaking_and_fall-through [mdn-while]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/while [mdn-do-while]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/do...while [concept-booleans]: /tracks/javascript/concepts/booleans diff --git a/exercises/concept/mixed-juices/.docs/instructions.md b/exercises/concept/mixed-juices/.docs/instructions.md index b0e4efbf80..56ea47e264 100644 --- a/exercises/concept/mixed-juices/.docs/instructions.md +++ b/exercises/concept/mixed-juices/.docs/instructions.md @@ -50,6 +50,7 @@ To make the hand-over easier, implement a function `remainingOrders` which takes The function should return the orders that Li Mei cannot start preparing before the end of her workday. The time left in the shift will always be greater than 0. +The array of juices to prepare will never be empty. Furthermore, the orders are prepared in the order in which they appear in the array. If Li Mei starts to mix a certain juice, she will always finish it even if she has to work a bit longer. If there are no remaining orders left that Dmitry needs to take care of, an empty array should be returned. diff --git a/exercises/concept/mixed-juices/.eslintrc b/exercises/concept/mixed-juices/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/concept/mixed-juices/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/concept/mixed-juices/.gitignore b/exercises/concept/mixed-juices/.gitignore index bdb912f98a..0c88ff6ec3 100644 --- a/exercises/concept/mixed-juices/.gitignore +++ b/exercises/concept/mixed-juices/.gitignore @@ -1,3 +1,5 @@ -node_modules -yarn-error.log - +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/concept/mixed-juices/.meta/config.json b/exercises/concept/mixed-juices/.meta/config.json index 5581bc9024..0110a59329 100644 --- a/exercises/concept/mixed-juices/.meta/config.json +++ b/exercises/concept/mixed-juices/.meta/config.json @@ -1,11 +1,26 @@ { - "blurb": "Help your friend Li Mei run her juice bar with your knowledge of while loops and switch statements.", - "authors": ["junedev"], - "contributors": [], + "authors": [ + "junedev" + ], "files": { - "solution": ["mixed-juices.js"], - "test": ["mixed-juices.spec.js"], - "exemplar": [".meta/exemplar.js"] + "solution": [ + "mixed-juices.js" + ], + "test": [ + "mixed-juices.spec.js" + ], + "exemplar": [ + ".meta/exemplar.js" + ] }, - "forked_from": ["swift/master-mixologist"] + "forked_from": [ + "swift/master-mixologist" + ], + "blurb": "Help your friend Li Mei run her juice bar with your knowledge of while loops and switch statements.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": true, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/concept/mixed-juices/.meta/design.md b/exercises/concept/mixed-juices/.meta/design.md index 5eb2a9b48c..de569e6e9e 100644 --- a/exercises/concept/mixed-juices/.meta/design.md +++ b/exercises/concept/mixed-juices/.meta/design.md @@ -38,7 +38,6 @@ This exercise could benefit from the following rules in the [analyzer][analyzer] The comment types mentioned below only serve as a proposal. 1. `timeToMixJuice` - - `essential`: Verify the student used a switch statement. Would be nice if we could give different feedback depending on what the student used instead. If it was if-else, comment that switch is better suited for so many different variants. @@ -53,7 +52,6 @@ The comment types mentioned below only serve as a proposal. ``` 2. `limesToCut` - - A solution that uses `if (limes.length < 0) break;` instead of combining the conditions should be considered equally correct to the exemplar solution. The version in the exemplar file is shorter but the break version emphasizes that there is a special edge case. - `essential`: Verify that `while` was used. @@ -68,7 +66,6 @@ The comment types mentioned below only serve as a proposal. - `celebratory`: Celebrate if the student used `++` and `+=`. 3. `remainingOrders` - - `essential`: Verify that do-while was used. If while was used instead, say that do-while is a better fit because there is always at least one iteration (because `timeLeft` is always > 0) and the condition can best be checked after running the code. - `essential`: Verify `timeToMixJuice` was reused instead of duplicating the code. diff --git a/exercises/concept/mixed-juices/babel.config.js b/exercises/concept/mixed-juices/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/concept/mixed-juices/babel.config.js +++ b/exercises/concept/mixed-juices/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/concept/mixed-juices/eslint.config.mjs b/exercises/concept/mixed-juices/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/concept/mixed-juices/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/concept/mixed-juices/jest.config.js b/exercises/concept/mixed-juices/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/concept/mixed-juices/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/concept/mixed-juices/mixed-juices.js b/exercises/concept/mixed-juices/mixed-juices.js index dde696fac8..d5f175fbe6 100644 --- a/exercises/concept/mixed-juices/mixed-juices.js +++ b/exercises/concept/mixed-juices/mixed-juices.js @@ -11,7 +11,7 @@ * @returns {number} time in minutes */ export function timeToMixJuice(name) { - throw new Error('Please implement the timeToMixJuice function'); + throw new Error('Remove this line and implement the function'); } /** @@ -23,7 +23,7 @@ export function timeToMixJuice(name) { * @returns {number} number of limes cut */ export function limesToCut(wedgesNeeded, limes) { - throw new Error('Please implement the limesToCut function'); + throw new Error('Remove this line and implement the function'); } /** @@ -34,5 +34,5 @@ export function limesToCut(wedgesNeeded, limes) { * @returns {string[]} remaining orders after the time is up */ export function remainingOrders(timeLeft, orders) { - throw new Error('Please implement the remainingOrders function'); + throw new Error('Remove this line and implement the function'); } diff --git a/exercises/concept/mixed-juices/mixed-juices.spec.js b/exercises/concept/mixed-juices/mixed-juices.spec.js index 3dbb303149..64eeea52c1 100644 --- a/exercises/concept/mixed-juices/mixed-juices.spec.js +++ b/exercises/concept/mixed-juices/mixed-juices.spec.js @@ -1,4 +1,5 @@ -import { timeToMixJuice, limesToCut, remainingOrders } from './mixed-juices'; +import { describe, expect, test } from '@jest/globals'; +import { limesToCut, remainingOrders, timeToMixJuice } from './mixed-juices'; describe('timeToMixJuice', () => { test("returns the correct time for 'Pure Strawberry Joy'", () => { diff --git a/exercises/concept/mixed-juices/package.json b/exercises/concept/mixed-juices/package.json index e5ecb11013..0b65061749 100644 --- a/exercises/concept/mixed-juices/package.json +++ b/exercises/concept/mixed-juices/package.json @@ -10,22 +10,25 @@ "directory": "exercises/concept/mixed-juices" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/concept/nullability/.docs/instructions.md b/exercises/concept/nullability/.docs/instructions.md index 0c70d2f3eb..b21d31650d 100644 --- a/exercises/concept/nullability/.docs/instructions.md +++ b/exercises/concept/nullability/.docs/instructions.md @@ -4,7 +4,7 @@ While working for a factory, a need arises to create the printed message on empl A badge requires the `id` of the employee, the `name` of the employee, as well as the department in which they are working. -## 1. Create the badge text. +## 1. Create the badge text Implement a function named `printBadge` that returns the text to print on the badge. diff --git a/exercises/concept/nullability/.eslintrc b/exercises/concept/nullability/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/concept/nullability/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/concept/nullability/.gitignore b/exercises/concept/nullability/.gitignore index bdb912f98a..0c88ff6ec3 100644 --- a/exercises/concept/nullability/.gitignore +++ b/exercises/concept/nullability/.gitignore @@ -1,3 +1,5 @@ -node_modules -yarn-error.log - +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/concept/nullability/.meta/config.json b/exercises/concept/nullability/.meta/config.json index 27c8da083e..efd1fc4888 100644 --- a/exercises/concept/nullability/.meta/config.json +++ b/exercises/concept/nullability/.meta/config.json @@ -1,11 +1,27 @@ { - "blurb": "TODO: add blurb for nullability exercise", - "authors": ["SleeplessByte", "Jlamon"], - "contributors": [], + "authors": [ + "SleeplessByte", + "Jlamon" + ], "files": { - "solution": ["nullability.js"], - "test": ["nullability.spec.js"], - "exemplar": [".meta/exemplar.js"] + "solution": [ + "nullability.js" + ], + "test": [ + "nullability.spec.js" + ], + "exemplar": [ + ".meta/exemplar.js" + ] }, - "forked_from": ["csharp/tim-from-marketing"] + "forked_from": [ + "csharp/tim-from-marketing" + ], + "blurb": "TODO: add blurb for nullability exercise", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": true, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/concept/nullability/babel.config.js b/exercises/concept/nullability/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/concept/nullability/babel.config.js +++ b/exercises/concept/nullability/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/concept/nullability/eslint.config.mjs b/exercises/concept/nullability/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/concept/nullability/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/concept/nullability/jest.config.js b/exercises/concept/nullability/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/concept/nullability/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/concept/nullability/nullability.js b/exercises/concept/nullability/nullability.js index b8ead1ec6f..7638e2a5de 100644 --- a/exercises/concept/nullability/nullability.js +++ b/exercises/concept/nullability/nullability.js @@ -14,5 +14,5 @@ * @returns {string} the text to print on the badge */ export function printBadge(id, name, department) { - throw new Error('Please implement the printBadge function'); + throw new Error('Remove this line and implement the function'); } diff --git a/exercises/concept/nullability/nullability.spec.js b/exercises/concept/nullability/nullability.spec.js index a6cf6233e1..95944bb5e3 100644 --- a/exercises/concept/nullability/nullability.spec.js +++ b/exercises/concept/nullability/nullability.spec.js @@ -1,29 +1,30 @@ +import { describe, expect, test } from '@jest/globals'; import { printBadge } from './nullability'; describe('nullability', () => { describe('printBadge', () => { - it("printBadge(17, 'Ryder Herbert', 'Marketing')", () => { + test("printBadge(17, 'Ryder Herbert', 'Marketing')", () => { const actual = printBadge(17, 'Ryder Herbert', 'Marketing'); expect(actual).toBe('[17] Ryder Herbert - MARKETING'); }); }); describe('printBadge without an employee ID', () => { - it("printBadge(null, 'Bogdan Rosario', 'Marketing')", () => { + test("printBadge(null, 'Bogdan Rosario', 'Marketing')", () => { const actual = printBadge(null, 'Bogdan Rosario', 'Marketing'); expect(actual).toBe('Bogdan Rosario - MARKETING'); }); }); describe('printBadge without a department', () => { - it("printBadge(59, 'Julie Sokato', null)", () => { + test("printBadge(59, 'Julie Sokato', null)", () => { const actual = printBadge(59, 'Julie Sokato', null); expect(actual).toBe('[59] Julie Sokato - OWNER'); }); }); describe('printBadge for a new owner', () => { - it("printBadge(null, 'Amare Osei', null)", () => { + test("printBadge(null, 'Amare Osei', null)", () => { const actual = printBadge(null, 'Amare Osei', null); expect(actual).toBe('Amare Osei - OWNER'); }); diff --git a/exercises/concept/nullability/package.json b/exercises/concept/nullability/package.json index 35888179d6..a165fe0a50 100644 --- a/exercises/concept/nullability/package.json +++ b/exercises/concept/nullability/package.json @@ -10,22 +10,25 @@ "directory": "exercises/concept/nullability" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/concept/ozans-playlist/.docs/introduction.md b/exercises/concept/ozans-playlist/.docs/introduction.md index e7f8a3c2d8..2a5c914459 100644 --- a/exercises/concept/ozans-playlist/.docs/introduction.md +++ b/exercises/concept/ozans-playlist/.docs/introduction.md @@ -25,5 +25,31 @@ console.log(set.size); //=> 4 ``` +You can provide an array as an argument when creating a set, and the array's values will become the values of the set, also removing the duplicate values. + +```javascript +const array = [1, 5, 4, 1]; +const set = new Set(array); // the set's values become [1, 5, 4] + +console.log(set.size); +//=> 3 +``` + +To convert a set to an array, you can use [Array.from()][mdn-array-from], which converts an iterable such as a set or a map to an array. + +```javascript +const set = new Set(); + +set.add(1); +set.add(2); +set.add(3); +set.add(4); + +const array = Array.from(set); +console.log(array); +//=> [1, 2, 3, 4] +``` + [mdn-sets]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set [mdn-strict-equality]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness#strict_equality_using +[mdn-array-from]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from diff --git a/exercises/concept/ozans-playlist/.eslintrc b/exercises/concept/ozans-playlist/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/concept/ozans-playlist/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/concept/ozans-playlist/.gitignore b/exercises/concept/ozans-playlist/.gitignore index bdb912f98a..0c88ff6ec3 100644 --- a/exercises/concept/ozans-playlist/.gitignore +++ b/exercises/concept/ozans-playlist/.gitignore @@ -1,3 +1,5 @@ -node_modules -yarn-error.log - +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/concept/ozans-playlist/.meta/config.json b/exercises/concept/ozans-playlist/.meta/config.json index e2d42f6233..79d0910202 100644 --- a/exercises/concept/ozans-playlist/.meta/config.json +++ b/exercises/concept/ozans-playlist/.meta/config.json @@ -1,10 +1,26 @@ { - "blurb": "Use sets to avoid repeating tracks in a playlist", - "authors": ["kristinaborn"], - "contributors": [], + "authors": [ + "kristinaborn" + ], + "contributors": [ + "SleeplessByte" + ], "files": { - "solution": ["ozans-playlist.js"], - "test": ["ozans-playlist.spec.js"], - "exemplar": [".meta/exemplar.js"] + "solution": [ + "ozans-playlist.js" + ], + "test": [ + "ozans-playlist.spec.js" + ], + "exemplar": [ + ".meta/exemplar.js" + ] + }, + "blurb": "Use sets to avoid repeating tracks in a playlist", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": true, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false } } diff --git a/exercises/concept/ozans-playlist/.meta/design.md b/exercises/concept/ozans-playlist/.meta/design.md index 79f9bc0f1e..0ae1f98a0f 100644 --- a/exercises/concept/ozans-playlist/.meta/design.md +++ b/exercises/concept/ozans-playlist/.meta/design.md @@ -35,11 +35,9 @@ This exercise could benefit from the following rules in the [analyzer][analyzer] For all tasks, verify that the student actually used a `Set`. 1. `addTrack` - - Verify that there was no redundant `Set.has()` call 2. `deleteTrack` - - Verify that there was no redundant `Set.has()` call [analyzer]: https://github.com/exercism/javascript-analyzer diff --git a/exercises/concept/ozans-playlist/babel.config.js b/exercises/concept/ozans-playlist/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/concept/ozans-playlist/babel.config.js +++ b/exercises/concept/ozans-playlist/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/concept/ozans-playlist/eslint.config.mjs b/exercises/concept/ozans-playlist/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/concept/ozans-playlist/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/concept/ozans-playlist/jest.config.js b/exercises/concept/ozans-playlist/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/concept/ozans-playlist/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/concept/ozans-playlist/ozans-playlist.js b/exercises/concept/ozans-playlist/ozans-playlist.js index 349cdd5f04..1b02443a3a 100644 --- a/exercises/concept/ozans-playlist/ozans-playlist.js +++ b/exercises/concept/ozans-playlist/ozans-playlist.js @@ -11,7 +11,7 @@ * @returns {string[]} new playlist with unique entries */ export function removeDuplicates(playlist) { - throw new Error('Please implement the removeDuplicates function'); + throw new Error('Remove this line and implement the function'); } /** @@ -22,7 +22,7 @@ export function removeDuplicates(playlist) { * @returns {boolean} whether the track is in the playlist */ export function hasTrack(playlist, track) { - throw new Error('Please implement the hasTrack function'); + throw new Error('Remove this line and implement the function'); } /** @@ -33,7 +33,7 @@ export function hasTrack(playlist, track) { * @returns {string[]} new playlist */ export function addTrack(playlist, track) { - throw new Error('Please implement the addTrack function'); + throw new Error('Remove this line and implement the function'); } /** @@ -44,7 +44,7 @@ export function addTrack(playlist, track) { * @returns {string[]} new playlist */ export function deleteTrack(playlist, track) { - throw new Error('Please implement the deleteTrack function'); + throw new Error('Remove this line and implement the function'); } /** @@ -54,5 +54,5 @@ export function deleteTrack(playlist, track) { * @returns {string[]} list of artists */ export function listArtists(playlist) { - throw new Error('Please implement the listArtists function'); + throw new Error('Remove this line and implement the function'); } diff --git a/exercises/concept/ozans-playlist/ozans-playlist.spec.js b/exercises/concept/ozans-playlist/ozans-playlist.spec.js index af8e09aefc..5047bd1618 100644 --- a/exercises/concept/ozans-playlist/ozans-playlist.spec.js +++ b/exercises/concept/ozans-playlist/ozans-playlist.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test } from '@jest/globals'; import { addTrack, deleteTrack, @@ -6,95 +7,93 @@ import { removeDuplicates, } from './ozans-playlist'; -describe("Ozan's playlist", () => { - describe('removeDuplicates', () => { - test('works for an empty playlist', () => { - const playlist = []; +describe('removeDuplicates', () => { + test('works for an empty playlist', () => { + const playlist = []; - expect(removeDuplicates(playlist)).toEqual([]); - }); + expect(removeDuplicates(playlist)).toEqual([]); + }); - test('works for a non-empty playlist', () => { - const TRACK_1 = 'Two Paintings and a Drum - Carl Cox'; - const TRACK_2 = 'Leash Called Love - The Sugarcubes'; - const playlist = [TRACK_1, TRACK_2, TRACK_1]; - const expected = [TRACK_1, TRACK_2]; + test('works for a non-empty playlist', () => { + const TRACK_1 = 'Two Paintings and a Drum - Carl Cox'; + const TRACK_2 = 'Leash Called Love - The Sugarcubes'; + const playlist = [TRACK_1, TRACK_2, TRACK_1]; + const expected = [TRACK_1, TRACK_2]; - expect(removeDuplicates(playlist)).toEqual(expected); - }); + expect(removeDuplicates(playlist)).toEqual(expected); }); +}); - describe('hasTrack', () => { - const TRACK_1 = 'Big Science - Laurie Anderson'; - const TRACK_2 = 'Tightrope - Laurie Anderson'; +describe('hasTrack', () => { + const TRACK_1 = 'Big Science - Laurie Anderson'; + const TRACK_2 = 'Tightrope - Laurie Anderson'; - test('returns true when the track is in the playlist', () => { - const playlist = [TRACK_1, TRACK_2]; + test('returns true when the track is in the playlist', () => { + const playlist = [TRACK_1, TRACK_2]; - expect(hasTrack(playlist, TRACK_1)).toBe(true); - }); + expect(hasTrack(playlist, TRACK_1)).toBe(true); + }); - test('returns false when the track is not in the playlist', () => { - const playlist = [TRACK_2]; + test('returns false when the track is not in the playlist', () => { + const playlist = [TRACK_2]; - expect(hasTrack(playlist, TRACK_1)).toBe(false); - }); + expect(hasTrack(playlist, TRACK_1)).toBe(false); }); +}); - describe('addTrack', () => { - const TRACK_1 = 'Jigsaw Feeling - Siouxsie and the Banshees'; - const TRACK_2 = 'Feeling Good - Nina Simone'; +describe('addTrack', () => { + const TRACK_1 = 'Jigsaw Feeling - Siouxsie and the Banshees'; + const TRACK_2 = 'Feeling Good - Nina Simone'; - test('adds a track that is not already in the playlist', () => { - const playlist = []; - const expected = [TRACK_1]; + test('adds a track that is not already in the playlist', () => { + const playlist = []; + const expected = [TRACK_1]; - expect(addTrack(playlist, TRACK_1)).toEqual(expected); - }); + expect(addTrack(playlist, TRACK_1)).toEqual(expected); + }); - test('does not add a track that is already in the playlist', () => { - const playlist = [TRACK_1, TRACK_2]; - const expected = [TRACK_1, TRACK_2]; + test('does not add a track that is already in the playlist', () => { + const playlist = [TRACK_1, TRACK_2]; + const expected = [TRACK_1, TRACK_2]; - expect(addTrack(playlist, TRACK_1)).toEqual(expected); - }); + expect(addTrack(playlist, TRACK_1)).toEqual(expected); }); +}); - describe('deleteTrack', () => { - const TRACK_1 = 'Ancestors - Tanya Tagaq'; - const TRACK_2 = 'Take This Hammer - Lead Belly'; +describe('deleteTrack', () => { + const TRACK_1 = 'Ancestors - Tanya Tagaq'; + const TRACK_2 = 'Take This Hammer - Lead Belly'; - test('works if the track is present in the playlist', () => { - const playlist = [TRACK_1, TRACK_2]; - const expected = [TRACK_2]; + test('works if the track is present in the playlist', () => { + const playlist = [TRACK_1, TRACK_2]; + const expected = [TRACK_2]; - expect(deleteTrack(playlist, TRACK_1)).toEqual(expected); - }); + expect(deleteTrack(playlist, TRACK_1)).toEqual(expected); + }); - test('works if the track is not present in the playlist', () => { - const playlist = [TRACK_2]; - const expected = [TRACK_2]; + test('works if the track is not present in the playlist', () => { + const playlist = [TRACK_2]; + const expected = [TRACK_2]; - expect(deleteTrack(playlist, TRACK_1)).toEqual(expected); - }); + expect(deleteTrack(playlist, TRACK_1)).toEqual(expected); }); +}); - describe('listArtists', () => { - test('works for an empty playlist', () => { - const playlist = []; +describe('listArtists', () => { + test('works for an empty playlist', () => { + const playlist = []; - expect(listArtists(playlist)).toEqual([]); - }); + expect(listArtists(playlist)).toEqual([]); + }); - test('works for a non-empty playlist', () => { - const playlist = [ - 'Onu Alma Beni Al - Sezen Aksu', - 'Famous Blue Raincoat - Leonard Cohen', - 'Rakkas - Sezen Aksu', - ]; - const expected = ['Sezen Aksu', 'Leonard Cohen']; + test('works for a non-empty playlist', () => { + const playlist = [ + 'Onu Alma Beni Al - Sezen Aksu', + 'Famous Blue Raincoat - Leonard Cohen', + 'Rakkas - Sezen Aksu', + ]; + const expected = ['Sezen Aksu', 'Leonard Cohen']; - expect(listArtists(playlist)).toEqual(expected); - }); + expect(listArtists(playlist)).toEqual(expected); }); }); diff --git a/exercises/concept/ozans-playlist/package.json b/exercises/concept/ozans-playlist/package.json index 6de1127608..d1c19267a0 100644 --- a/exercises/concept/ozans-playlist/package.json +++ b/exercises/concept/ozans-playlist/package.json @@ -10,22 +10,25 @@ "directory": "exercises/concept/ozans-playlist" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/concept/pizza-order/.docs/hints.md b/exercises/concept/pizza-order/.docs/hints.md index b5296c3664..f938b628ed 100644 --- a/exercises/concept/pizza-order/.docs/hints.md +++ b/exercises/concept/pizza-order/.docs/hints.md @@ -1 +1,13 @@ # Hints + +## 1. Calculate the price of a pizza + +- Set up a recursive function that returns the price of one extra option added to the price of the pizza without that extra topping. +- If `Maximum call stack size exceeded` error is raised, check if you've missed out on the base case. +- If you're getting a `Memory Allocation Error`, check if you're duplicating the input array of toppings instead of mutating it. + +## 2. Calculate the total price of an order + +- Using `pizzaPrice`, calculate the price of each pizza in the array, and add it to a sum. +- If `Maximum call stack size exceeded` error is raised, and more than one test is failing, check if you've missed out on the base case. If only one test is failing, try implementing this with a loop! +- If you're getting a `Memory Allocation Error`, check if you're duplicating the input array instead of mutating it. diff --git a/exercises/concept/pizza-order/.docs/instructions.md b/exercises/concept/pizza-order/.docs/instructions.md index 63fd98ab00..0ae66b16ca 100644 --- a/exercises/concept/pizza-order/.docs/instructions.md +++ b/exercises/concept/pizza-order/.docs/instructions.md @@ -1 +1,89 @@ # Instructions + +You run a pizza shop, and offer three types of pizzas: + +- Margherita: $7 +- Caprese: $9 +- Formaggio: $10 + +If customers want, they can add an unlimited number of extra options: either "ExtraSauce" for $1 or "ExtraToppings" for $2. + +Your task is to write code that assists the customer in figuring out the cost to them. + +## Calculate the price of a pizza + +Provided the pizza's name as the first argument, and an unlimited number of added options, calculate the price of the pizza in dollars. + +```js +pizzaPrice('Margherita'); +// => 7 + +pizzaPrice('Caprese', 'ExtraSauce', 'ExtraToppings'); +// => 12 + +pizzaPrice( + 'Caprese', + 'ExtraToppings', + 'ExtraToppings', + 'ExtraToppings', + 'ExtraToppings', +); +// => 17 +``` + +## Calculate the total price of an order + +Your function is called with a list of `PizzaOrder`s and should return the total price of the order in dollars. +Each `PizzaOrder` has a `pizza` property - the pizza's name, and an `extras` property - the list of extra options. + +```js +const margherita = new PizzaOrder('Margherita'); +const caprese = new PizzaOrder('Caprese', 'ExtraToppings'); +orderPrice([margherita, caprese]); +// => 18 +``` + +You'll realize that you can't write this using recursion, as one test with a tremendous amount of orders will raise a `Maximum call stack size exceeded`. +No worries, this is intentional - try implementing this function using an imperative loop! +You have many options, such as, but not limited to using `reduce` or a `for` loop. + + +~~~~exercism/advanced +When the JavaScript interpreter is running the JavaScript code, it will keep track of which functions it has entered (started to call) on a data structure called "a stack". +When the function returns (ends), it is removed from the stack. + +However, this stack has a limited size. +The most common mistake made is a recursive function that never ends. +Each call is placed on the stack, but before it returns, another call is placed on the stack. + +```javascript +function kaboom() { + kaboom() +} + +kaboom() +// => RangeError: Maximum call stack size exceeded +``` + +The stacktrace of this error shows the same line over and over, which makes sense, because the function calls itself. +Whilst it has no real practical application in most cases, you can find out how tall that stack can get. + +```javascript +let calls = 0; +function kaboom() { + calls +=1 ; + kaboom() +} + +kaboom() +// => RangeError: Maximum call stack size exceeded + +console.log(calls) +// => a number, generally higher than 10.000 +``` + +There are only two viable solutions to a call stack error caused by a synchronous recursive function: +- ensure the functions return before the stack limit is reached, usually by adding or fixing a base case. +- rewrite the recursive function to an imperative loop, which will execute the body of the loop, without having to enter a function, thus without increasing the stack. +~~~~ + diff --git a/exercises/concept/pizza-order/.docs/introduction.md b/exercises/concept/pizza-order/.docs/introduction.md index e10b99d013..8a4d726b7d 100644 --- a/exercises/concept/pizza-order/.docs/introduction.md +++ b/exercises/concept/pizza-order/.docs/introduction.md @@ -1 +1,93 @@ -# Introduction +# Understanding Recursion in JavaScript + +Recursion is a powerful concept in programming that involves a function calling itself. +It can be a bit tricky to grasp at first, but once you understand the fundamentals, it becomes a valuable tool in solving complex problems. +In this tutorial, we'll explore recursion in JavaScript with easy-to-understand examples. + +## What is Recursion? + +Recursion occurs when a function calls itself, either directly or indirectly. +It's similar to a loop, but it involves breaking a problem down into smaller, more manageable sub-problems. + +### Example 1: Countdown + +Let's start with a simple example: a countdown function. + +```javascript +function countdown(num) { + // Base case + if (num <= 0) { + console.log('Blastoff!'); + return; + } + + // Recursive case + console.log(num); + countdown(num - 1); +} + +// Call the function +countdown(5); +``` + +In this example: + +- **Base case**: When `num` becomes less than or equal to 0, the function prints "Blastoff!" and stops calling itself. +- **Recursive case**: The function prints the current `num` and calls itself with `num - 1`. + +### Example 2: Factorial + +Now, let's look at a classic example of recursion: calculating the factorial of a number. + +```javascript +function factorial(n) { + // Base case + if (n === 0 || n === 1) { + return 1; + } + + // Recursive case + return n * factorial(n - 1); +} + +// Test the function +console.log(factorial(5)); // Output: 120 +``` + +In this example: + +- **Base case**: When `n` is 0 or 1, the function returns 1. +- **Recursive case**: The function multiplies `n` by the factorial of `n - 1`. + +## Key Concepts + +### Base Case + +Every recursive function must have at least one base case, a condition where the function stops calling itself. +Without a base case, the recursion would continue indefinitely, leading to a stack overflow. + +### Recursive Case + +The recursive case defines how the function calls itself with a smaller or simpler version of the problem. + +## Pros and Cons of Recursion + +**Pros:** + +- Elegant solution for certain problems. +- Mimics the mathematical induction concept. + +**Cons:** + +- Can be less efficient than iterative solutions. +- May lead to stack overflow for deep recursion. + +## Conclusion + +Recursion is a valuable technique that can simplify complex problems by breaking them into smaller, more manageable sub-problems. +Understanding base cases and recursive cases is crucial for implementing effective recursive solutions in JavaScript. + +**Learn More:** + +- [MDN: Recursion in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions#recursion) +- [Eloquent JavaScript: Chapter 3 - Functions](https://eloquentjavascript.net/03_functions.html) diff --git a/exercises/concept/pizza-order/.eslintrc b/exercises/concept/pizza-order/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/concept/pizza-order/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/concept/pizza-order/.gitignore b/exercises/concept/pizza-order/.gitignore index bdb912f98a..0c88ff6ec3 100644 --- a/exercises/concept/pizza-order/.gitignore +++ b/exercises/concept/pizza-order/.gitignore @@ -1,3 +1,5 @@ -node_modules -yarn-error.log - +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/concept/pizza-order/.meta/config.json b/exercises/concept/pizza-order/.meta/config.json index 51a8889de8..ecacc4de6e 100644 --- a/exercises/concept/pizza-order/.meta/config.json +++ b/exercises/concept/pizza-order/.meta/config.json @@ -1,11 +1,33 @@ { - "blurb": "TODO: add blurb for recursion exercise", - "authors": ["SleeplessByte"], - "contributors": [], + "authors": [ + "SleeplessByte", + "safwansamsudeen" + ], + "contributors": [ + "Cool-Katt" + ], "files": { - "solution": ["pizza-order.js"], - "test": ["pizza-order.spec.js"], - "exemplar": [".meta/exemplar.js"] + "solution": [ + "pizza-order.js" + ], + "test": [ + "pizza-order.spec.js" + ], + "exemplar": [ + ".meta/exemplar.js" + ], + "editor": [ + "global.d.ts" + ] }, - "forked_from": ["fsharp/pizza-pricing"] + "forked_from": [ + "fsharp/pizza-pricing" + ], + "blurb": "Allow customers to calculate the cost of their order", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": true, + "flag.tests.includes-optional": false + } } diff --git a/exercises/concept/pizza-order/.meta/exemplar.js b/exercises/concept/pizza-order/.meta/exemplar.js index 2eba3fed43..31ac0d6ba7 100644 --- a/exercises/concept/pizza-order/.meta/exemplar.js +++ b/exercises/concept/pizza-order/.meta/exemplar.js @@ -41,6 +41,6 @@ export function pizzaPrice(pizza, ...[extra, ...otherExtras]) { export function orderPrice(pizzaOrders) { return pizzaOrders.reduce( (result, order) => result + pizzaPrice(order.pizza, ...order.extras), - 0 + 0, ); } diff --git a/exercises/concept/pizza-order/babel.config.js b/exercises/concept/pizza-order/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/concept/pizza-order/babel.config.js +++ b/exercises/concept/pizza-order/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/concept/pizza-order/eslint.config.mjs b/exercises/concept/pizza-order/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/concept/pizza-order/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/concept/pizza-order/jest.config.js b/exercises/concept/pizza-order/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/concept/pizza-order/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/concept/pizza-order/package.json b/exercises/concept/pizza-order/package.json index 9e10a61454..2d56d6d732 100644 --- a/exercises/concept/pizza-order/package.json +++ b/exercises/concept/pizza-order/package.json @@ -10,22 +10,25 @@ "directory": "exercises/concept/pizza-order" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/concept/pizza-order/pizza-order.js b/exercises/concept/pizza-order/pizza-order.js index c36d646138..2bbb25f2f3 100644 --- a/exercises/concept/pizza-order/pizza-order.js +++ b/exercises/concept/pizza-order/pizza-order.js @@ -3,7 +3,7 @@ // @ts-check /** - * Determine the prize of the pizza given the pizza and optional extras + * Determine the price of the pizza given the pizza and optional extras * * @param {Pizza} pizza name of the pizza to be made * @param {Extra[]} extras list of extras @@ -11,15 +11,18 @@ * @returns {number} the price of the pizza */ export function pizzaPrice(pizza, ...extras) { - throw new Error('Please implement the pizzaPrice function'); + throw new Error('Remove this line and implement the function'); } /** - * Calculate the prize of the total order, given individual orders + * Calculate the price of the total order, given individual orders + * + * (HINT: For this exercise, you can take a look at the supplied "global.d.ts" file + * for a more info about the type definitions used) * * @param {PizzaOrder[]} pizzaOrders a list of pizza orders * @returns {number} the price of the total order */ export function orderPrice(pizzaOrders) { - throw new Error('Please implement the orderPrice function'); + throw new Error('Remove this line and implement the function'); } diff --git a/exercises/concept/pizza-order/pizza-order.spec.js b/exercises/concept/pizza-order/pizza-order.spec.js index 98c8adf86f..58c7677028 100644 --- a/exercises/concept/pizza-order/pizza-order.spec.js +++ b/exercises/concept/pizza-order/pizza-order.spec.js @@ -1,4 +1,5 @@ -import { pizzaPrice, orderPrice } from './pizza-order'; +import { describe, expect, test } from '@jest/globals'; +import { orderPrice, pizzaPrice } from './pizza-order'; class PizzaOrder { /** @@ -15,83 +16,83 @@ class PizzaOrder { } describe('Price for pizza margherita', () => { - it("pizzaPrice('Margherita')", () => { + test("pizzaPrice('Margherita')", () => { expect(pizzaPrice('Margherita')).toBe(7); }); }); describe('Price for pizza formaggio', () => { - it("pizzaPrice('Formaggio')", () => { + test("pizzaPrice('Formaggio')", () => { expect(pizzaPrice('Formaggio')).toBe(10); }); }); describe('Price for pizza caprese', () => { - it("pizzaPrice('Caprese')", () => { + test("pizzaPrice('Caprese')", () => { expect(pizzaPrice('Caprese')).toBe(9); }); }); describe('Price for pizza margherita with extra sauce', () => { - it("pizzaPrice('Margherita', 'ExtraSauce')", () => { + test("pizzaPrice('Margherita', 'ExtraSauce')", () => { expect(pizzaPrice('Margherita', 'ExtraSauce')).toBe(8); }); }); describe('Price for pizza caprese with extra toppings', () => { - it("pizzaPrice('Caprese', 'ExtraToppings')", () => { + test("pizzaPrice('Caprese', 'ExtraToppings')", () => { expect(pizzaPrice('Caprese', 'ExtraToppings')).toBe(11); }); }); describe('Price for pizza formaggio with extra sauce and toppings', () => { - it("pizzaPrice('Formaggio', 'ExtraSauce', 'ExtraToppings')", () => { + test("pizzaPrice('Formaggio', 'ExtraSauce', 'ExtraToppings')", () => { expect(pizzaPrice('Formaggio', 'ExtraSauce', 'ExtraToppings')).toBe(13); }); }); describe('Price for pizza caprese with extra sauce and toppings', () => { - it("pizzaPrice('Caprese', 'ExtraSauce', 'ExtraToppings')", () => { + test("pizzaPrice('Caprese', 'ExtraSauce', 'ExtraToppings')", () => { expect(pizzaPrice('Caprese', 'ExtraSauce', 'ExtraToppings')).toBe(12); }); }); describe('Price for pizza caprese with a lot of extra toppings', () => { - it("pizzaPrice('Caprese', 'ExtraToppings', 'ExtraToppings', 'ExtraToppings', 'ExtraToppings')", () => { + test("pizzaPrice('Caprese', 'ExtraToppings', 'ExtraToppings', 'ExtraToppings', 'ExtraToppings')", () => { expect( pizzaPrice( 'Caprese', 'ExtraToppings', 'ExtraToppings', 'ExtraToppings', - 'ExtraToppings' - ) + 'ExtraToppings', + ), ).toBe(17); }); }); describe('Order price for no pizzas', () => { - it('orderPrice([])', () => { + test('orderPrice([])', () => { expect(orderPrice([])).toBe(0); }); }); describe('Order price for a single pizza caprese', () => { - it("orderPrice([PizzaOrder('Caprese')])", () => { + test("orderPrice([PizzaOrder('Caprese')])", () => { const order = new PizzaOrder('Caprese'); expect(orderPrice([order])).toBe(9); }); }); describe('Order price for a single pizza formaggio with extra sauce', () => { - it("orderPrice([PizzaOrder('Formaggio', 'ExtraSauce')])", () => { + test("orderPrice([PizzaOrder('Formaggio', 'ExtraSauce')])", () => { const order = new PizzaOrder('Formaggio', 'ExtraSauce'); expect(orderPrice([order])).toBe(11); }); }); describe('Order price for one pizza margherita and one pizza caprese with extra toppings', () => { - it("orderPrice([PizzaOrder('Margherita'), PizzaOrder('Caprese', 'ExtraToppings')])", () => { + test("orderPrice([PizzaOrder('Margherita'), PizzaOrder('Caprese', 'ExtraToppings')])", () => { const margherita = new PizzaOrder('Margherita'); const caprese = new PizzaOrder('Caprese', 'ExtraToppings'); @@ -103,19 +104,19 @@ describe('Order price for one pizza margherita and one pizza caprese with extra }); describe('Order price for one pizza margherita with a LOT of sauce and one pizza caprese with a LOT of toppings', () => { - it("orderPrice([PizzaOrder('Margherita', 'ExtraSauce', 'ExtraSauce', 'ExtraSauce'), PizzaOrder('Caprese', 'ExtraToppings', 'ExtraToppings', 'ExtraToppings', 'ExtraToppings')])", () => { + test("orderPrice([PizzaOrder('Margherita', 'ExtraSauce', 'ExtraSauce', 'ExtraSauce'), PizzaOrder('Caprese', 'ExtraToppings', 'ExtraToppings', 'ExtraToppings', 'ExtraToppings')])", () => { const saucyMargherita = new PizzaOrder( 'Margherita', 'ExtraSauce', 'ExtraSauce', - 'ExtraSauce' + 'ExtraSauce', ); const toppedCaprese = new PizzaOrder( 'Caprese', 'ExtraToppings', 'ExtraToppings', 'ExtraToppings', - 'ExtraToppings' + 'ExtraToppings', ); expect(orderPrice([saucyMargherita, toppedCaprese])).toBe(27); @@ -126,7 +127,7 @@ describe('Order price for one pizza margherita with a LOT of sauce and one pizza }); describe('Order price for very large order', () => { - it('orderPrice([/* lots of */])', () => { + test('orderPrice([/* lots of */])', () => { const margherita = new PizzaOrder('Margherita'); const margherita2 = new PizzaOrder('Margherita', 'ExtraSauce'); const caprese = new PizzaOrder('Caprese'); @@ -136,14 +137,14 @@ describe('Order price for very large order', () => { const formaggio3 = new PizzaOrder( 'Formaggio', 'ExtraSauce', - 'ExtraToppings' + 'ExtraToppings', ); const formaggio4 = new PizzaOrder( 'Formaggio', 'ExtraToppings', 'ExtraSauce', 'ExtraToppings', - 'ExtraSauce' + 'ExtraSauce', ); const actual = orderPrice([ @@ -161,9 +162,9 @@ describe('Order price for very large order', () => { }); describe('Order price for a gigantic order', () => { - it('orderPrice([/* lots of */])', () => { + test('orderPrice([/* lots of */])', () => { const allTheMargheritas = Array(100 * 1000).fill( - new PizzaOrder('Margherita') + new PizzaOrder('Margherita'), ); const actual = orderPrice(allTheMargheritas); expect(actual).toBe(700 * 1000); diff --git a/exercises/concept/poetry-club-door-policy/.approaches/config.json b/exercises/concept/poetry-club-door-policy/.approaches/config.json new file mode 100644 index 0000000000..0b90d0d0dc --- /dev/null +++ b/exercises/concept/poetry-club-door-policy/.approaches/config.json @@ -0,0 +1,7 @@ +{ + "introduction": { + "authors": [ + "Yrahcaz7" + ] + } +} diff --git a/exercises/concept/poetry-club-door-policy/.approaches/introduction.md b/exercises/concept/poetry-club-door-policy/.approaches/introduction.md new file mode 100644 index 0000000000..520930c6d1 --- /dev/null +++ b/exercises/concept/poetry-club-door-policy/.approaches/introduction.md @@ -0,0 +1,166 @@ +# Introduction + +There are various ways to solve each part of Poetry Club Door Policy. +A commonality between most of the parts is needing to get a character from the provided string. + +There are multiple ways to do this, one of which is the standard way of using `[index]` access. + +One other way is to use [`charAt`][mdn-char-at], which is the same as `[index]` access for most purposes. + +Another method is [`at`][mdn-at], which is the same as `[index]` access, except it accepts negative numbers. +A negative number will count backwards from the end of the string, unlike positive numbers, which count forwards from the start. + +In addition, [`substring`][mdn-substring] and [`slice`][mdn-slice] can be used. +These string methods are normally used to get portions of strings, rather than a single character. + +An important distiction is that `slice` accepts negative numbers like `at` does, but `substring` does not. + +## Different ways to implement `frontDoorPassword` + +For `frontDoorPassword`, there are a variety of ways to make strings uppercase and lowercase. + +### Approach: `toUpperCase` and `toLowerCase` + +```js +export function frontDoorPassword(word) { + return word[0].toUpperCase() + word.slice(1).toLowerCase(); +} +``` + +This approach is a standard method that uses [`toUpperCase`][mdn-to-upper-case] and [`toLowerCase`][mdn-to-lower-case]. + +### Approach: `toLocaleUpperCase` and `toLocaleLowerCase` + +```js +export function frontDoorPassword(word) { + return word[0].toLocaleUpperCase() + word.substring(1).toLocaleLowerCase(); +} +``` + +This approach uses [`toLocaleUpperCase`][mdn-to-locale-upper-case] and [`toLocaleLowerCase`][mdn-to-locale-lower-case], which are very similar to `toUpperCase` and `toLowerCase`, but work with either the current locale or a given locale, which can be specified as an argument. +This approach is necessary when the language locale has a non-standard mapping between lower and uppercase. + +```javascript +const str = 'istanbul'; + +str.toUpperCase(); +// => 'ISTANBUL' +str.toLocaleUpperCase('en-US'); +// => 'ISTANBUL' + +str.toLocaleUpperCase('tr'); +// => 'İSTANBUL' +``` + +### Approach: `String.fromCharCode` and `charCodeAt` + +```js +export function frontDoorPassword(word) { + let charCode = word.charCodeAt(0); + if (charCode >= 97) { + charCode -= 32; + } + + let password = String.fromCharCode(charCode); + + for (let index = 1; index < word.length; index++) { + charCode = word.charCodeAt(index); + if (charCode <= 90) { + charCode += 32; + } + + password += String.fromCharCode(charCode); + } + return password; +} +``` + +This approach uses [`String.fromCharCode`][mdn-from-char-code] along with [`charCodeAt`][mdn-char-code-at]. + +This method is much longer than the others and it only works with english letters, so it is less than ideal. + +## Different ways to implement `backDoorResponse` + +There are many ways to go about trimming whitespace for `backDoorResponse`. + +### Approach: `trim` and `[index]` access + +```js +export function backDoorResponse(line) { + const trimmed = line.trim(); + return trimmed[trimmed.length - 1]; +} +``` + +This standard approach uses `[index]` access and the built-in string method [`trim`][mdn-trim], which trims any leading and trailing whitespace from a string. + +### Approach: `trimEnd` and `at` + +```js +export function backDoorResponse(line) { + return line.trimEnd().at(-1); +} +``` + +This approach uses the string method [`trimEnd`][mdn-trim-end], which only trims trailing whitespace, unlike `trim`. + +It also uses `at` instead of `[index]` access make the solution shorter. + +### Approach: `replaceAll` and `charAt` + +```js +export function backDoorResponse(line) { + const trimmed = line.replaceAll(' ', ''); + return trimmed.charAt(trimmed.length - 1); +} +``` + +This approach uses [`replaceAll`][mdn-replace-all] to remove all of the spaces in the string. + +This merges all the words in the string together, but that doesn't matter here as we only care about the last character and not the rest of the string. + +### Approach: `replace` and literal `RegExp` + +```js +export function backDoorResponse(line) { + const trimmed = line.replace(/\s/g, ''); + return trimmed[trimmed.length - 1]; +} +``` + +This approach uses [`replace`][mdn-replace] with a [regular expression literal][mdn-regular-expressions], achieving a similar result to the previous approach. + +The main difference is that the previous approach only removes spaces, while this approach can remove any type of whitespace. + +### Approach: `for` loop + +```js +export function backDoorResponse(line) { + for (let index = line.length - 1; index >= 0; index--) { + if (line[index] != ' ') { + return line[index]; + } + } + return ''; +} +``` + +This approach does not trim whitespace. +Instead, it uses a [for loop][mdn-for] to return the first character that is not a space from the end of the string. + +[mdn-char-at]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charAt +[mdn-at]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/at +[mdn-substring]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substring +[mdn-slice]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice +[mdn-to-upper-case]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase +[mdn-to-lower-case]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toLowerCase +[mdn-to-locale-upper-case]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toLocaleUpperCase +[mdn-to-locale-lower-case]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toLocaleLowerCase +[mdn-from-char-code]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCharCode +[mdn-char-code-at]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charCodeAt +[mdn-trim]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim +[mdn-trim-end]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimEnd +[mdn-replace-all]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replaceAll +[mdn-replace]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace +[mdn-regular-expressions]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions +[mdn-for]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for diff --git a/exercises/concept/poetry-club-door-policy/.docs/hints.md b/exercises/concept/poetry-club-door-policy/.docs/hints.md index 74820a760e..89f52c0eeb 100644 --- a/exercises/concept/poetry-club-door-policy/.docs/hints.md +++ b/exercises/concept/poetry-club-door-policy/.docs/hints.md @@ -10,7 +10,7 @@ - Capitalization means having a single _uppercase_ character, followed by _lowercase_ characters. - So first, you need to extract the first letter and convert it [to upper case][mdn-to-upper-case]. - Then you need to determine the rest of the string (everything besides the first character). - The [slice method][mdn.slice] can help you with that. + The [slice method][mdn-slice] can help you with that. - After you applied the conversion [to lower case][mdn-to-lower-case], you can combine the first letter with the rest using the addition operator `+`. ## 3. Get the last letter of a sentence diff --git a/exercises/concept/poetry-club-door-policy/.docs/instructions.md b/exercises/concept/poetry-club-door-policy/.docs/instructions.md index a21b5ea5bf..b61a1f3079 100644 --- a/exercises/concept/poetry-club-door-policy/.docs/instructions.md +++ b/exercises/concept/poetry-club-door-policy/.docs/instructions.md @@ -69,7 +69,7 @@ Eager to leave When the guard recites **Stands so high**, you'll respond **h**, when the guard recites **Huge hooves too**, you'll respond **o**. -Note that sometimes the guard does stylist pauses (in the form of whitespace) at the end of a line. +Note that sometimes the guard does stylistic pauses (in the form of whitespace) at the beginning or at the end of a line. You will need to ignore those pauses to derive the correct letter. Implement the function `backDoorResponse` that takes a line of the poem as an argument and returns the last letter of that line that is not a whitespace character. diff --git a/exercises/concept/poetry-club-door-policy/.eslintrc b/exercises/concept/poetry-club-door-policy/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/concept/poetry-club-door-policy/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/concept/poetry-club-door-policy/.gitignore b/exercises/concept/poetry-club-door-policy/.gitignore index bdb912f98a..0c88ff6ec3 100644 --- a/exercises/concept/poetry-club-door-policy/.gitignore +++ b/exercises/concept/poetry-club-door-policy/.gitignore @@ -1,3 +1,5 @@ -node_modules -yarn-error.log - +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/concept/poetry-club-door-policy/.meta/config.json b/exercises/concept/poetry-club-door-policy/.meta/config.json index 1e852f4dac..0939287c4b 100644 --- a/exercises/concept/poetry-club-door-policy/.meta/config.json +++ b/exercises/concept/poetry-club-door-policy/.meta/config.json @@ -1,11 +1,28 @@ { - "blurb": "Learn about strings using poems to get into the poetry club.", - "authors": ["SleeplessByte"], - "contributors": ["hayashi-ay", "junedev", "mmmmmrob"], + "authors": [ + "SleeplessByte" + ], + "contributors": [ + "hayashi-ay", + "junedev", + "mmmmmrob" + ], "files": { - "solution": ["door-policy.js"], - "test": ["door-policy.spec.js"], - "exemplar": [".meta/exemplar.js"] + "solution": [ + "door-policy.js" + ], + "test": [ + "door-policy.spec.js" + ], + "exemplar": [ + ".meta/exemplar.js" + ] }, - "forked_from": [] + "blurb": "Learn about strings using poems to get into the poetry club.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": true, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/concept/poetry-club-door-policy/.meta/exemplar.js b/exercises/concept/poetry-club-door-policy/.meta/exemplar.js index 0dc048defb..7aaca7cf1c 100644 --- a/exercises/concept/poetry-club-door-policy/.meta/exemplar.js +++ b/exercises/concept/poetry-club-door-policy/.meta/exemplar.js @@ -30,8 +30,7 @@ export function frontDoorPassword(word) { * @returns {string} */ export function backDoorResponse(line) { - const trimmed = line.trim(); - return trimmed[trimmed.length - 1]; + return line.trimEnd().slice(-1); } /** @@ -42,5 +41,5 @@ export function backDoorResponse(line) { * @returns {string} the back door password */ export function backDoorPassword(word) { - return frontDoorPassword(word) + `, please`; + return frontDoorPassword(word) + ', please'; } diff --git a/exercises/concept/poetry-club-door-policy/babel.config.js b/exercises/concept/poetry-club-door-policy/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/concept/poetry-club-door-policy/babel.config.js +++ b/exercises/concept/poetry-club-door-policy/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/concept/poetry-club-door-policy/door-policy.js b/exercises/concept/poetry-club-door-policy/door-policy.js index 41a3e4390d..2469cc1a86 100644 --- a/exercises/concept/poetry-club-door-policy/door-policy.js +++ b/exercises/concept/poetry-club-door-policy/door-policy.js @@ -27,7 +27,7 @@ * @returns {string} */ export function frontDoorResponse(line) { - throw new Error('Implement the frontDoorResponse function'); + throw new Error('Remove this line and implement the function'); } /** @@ -38,7 +38,7 @@ export function frontDoorResponse(line) { * @returns {string} the front door password */ export function frontDoorPassword(word) { - throw new Error('Implement the frontDoorPassword function'); + throw new Error('Remove this line and implement the function'); } /** @@ -49,7 +49,7 @@ export function frontDoorPassword(word) { * @returns {string} */ export function backDoorResponse(line) { - throw new Error('Implement the backDoorResponse function'); + throw new Error('Remove this line and implement the function'); } /** @@ -60,5 +60,5 @@ export function backDoorResponse(line) { * @returns {string} the back door password */ export function backDoorPassword(word) { - throw new Error('Implement the backDoorPassword function'); + throw new Error('Remove this line and implement the function'); } diff --git a/exercises/concept/poetry-club-door-policy/door-policy.spec.js b/exercises/concept/poetry-club-door-policy/door-policy.spec.js index 79c3fc1f75..3847542712 100644 --- a/exercises/concept/poetry-club-door-policy/door-policy.spec.js +++ b/exercises/concept/poetry-club-door-policy/door-policy.spec.js @@ -1,10 +1,9 @@ -// @ts-check - +import { describe, expect, test } from '@jest/globals'; import { - frontDoorResponse, + backDoorPassword, backDoorResponse, frontDoorPassword, - backDoorPassword, + frontDoorResponse, } from './door-policy'; const recite = (poem, responseFn) => { @@ -61,12 +60,12 @@ describe('front door password', () => { expect(frontDoorPassword('SUMMER')).toBe('Summer'); }); - test('should capitalize SOPHIA', () => { - expect(frontDoorPassword('SOPHIA')).toBe('Sophia'); + test('should capitalize sophia', () => { + expect(frontDoorPassword('sophia')).toBe('Sophia'); }); - test('should capitalize CODE', () => { - expect(frontDoorPassword('CODE')).toBe('Code'); + test('should capitalize Code', () => { + expect(frontDoorPassword('Code')).toBe('Code'); }); }); @@ -90,9 +89,9 @@ describe('back door response', () => { // with trailing whitespace const SHIRE_HORSE_WITH_SPACES = [ 'Stands so high ', - 'Huge hooves too\t', + '\tHuge hooves too\t', 'Impatiently waits for \t \t', - 'Reins and harness ', + ' Reins and harness ', 'Eager to leave\n\n', ]; diff --git a/exercises/concept/poetry-club-door-policy/eslint.config.mjs b/exercises/concept/poetry-club-door-policy/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/concept/poetry-club-door-policy/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/concept/poetry-club-door-policy/jest.config.js b/exercises/concept/poetry-club-door-policy/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/concept/poetry-club-door-policy/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/concept/poetry-club-door-policy/package.json b/exercises/concept/poetry-club-door-policy/package.json index 77d673acda..3391e577e6 100644 --- a/exercises/concept/poetry-club-door-policy/package.json +++ b/exercises/concept/poetry-club-door-policy/package.json @@ -10,22 +10,25 @@ "directory": "exercises/concept/poetry-club-door-policy" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/concept/recycling-robot/.docs/hints.md b/exercises/concept/recycling-robot/.docs/hints.md new file mode 100644 index 0000000000..58e1f325dc --- /dev/null +++ b/exercises/concept/recycling-robot/.docs/hints.md @@ -0,0 +1,61 @@ +# Hints + +## 1. Check if a value is a boolean + +- You can use `typeof` to find the type of a value. +- `typeof` returns a string. + +## 2. Check if a value is a number. + +- You can use `typeof` to find the type of a value. +- `typeof` returns a string. +- You need to check for `Infinity` and `NaN`. +- `NaN` is never equal to itself, but there is a [built in function][isNaN] to check if a value is NaN. + +## 3. Check if a value is an object + +- You can use `typeof` to find the type of a value. +- `typeof` returns a string. +- You will need to check for `null`. + +## 4. Check if a string is numeric + +- You can use `typeof` to find the type of a value. +- `typeof` returns a string. +- You can iterate over a string to check if all characters are digits. + +## 5. Check if an object is electronic + +- You can use `instanceof` to check if an object is an instance of a class or one of its children. + +## 6. Check if a value is a non empty array + +- You can use `typeof` to find the type of a value. +- `typeof` returns a string. +- You can check the length of an array to find out how many elements it contains. + +## 7. Check if a value is an empty array + +- You can use `typeof` to find the type of a value. +- `typeof` returns a string. +- You can check the length of an array to find out how many elements it contains. + +## 8. Check if an object has a `type` property or method + +- You can use the `in` operator to check if an object has a property or method. + +## 9. Throw an error if an object does not have the `id` property or method + +- You can use the `in` operator to check if an object has a property or method. +- If the `id` property or method is missing, your function should throw an `Error`. + +## 10. Check if an object has an `id` property + +- To check if an object has a property (not a method), you can use the `Object.hasOwn()` function. + +## 11. Check if an object has a defined `type` property + +- To check if an object has a property (not a method), you can use the `Object.hasOwn()` function. +- You will have to access the `type` property and check if it is defined. + +[isNaN]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isNaN diff --git a/exercises/concept/recycling-robot/.docs/instructions.md b/exercises/concept/recycling-robot/.docs/instructions.md new file mode 100644 index 0000000000..117a24171e --- /dev/null +++ b/exercises/concept/recycling-robot/.docs/instructions.md @@ -0,0 +1,192 @@ +## Instructions + +You have been hired by a recycling center. +Due to lack of space, all the products are put on the same conveyor belt, but this has lead to different materials mixing together, making them unusable. +To fix this, you have been tasked with making functions to identify the type of a product. + +### 1. Check if a value is a boolean + +Implement the `isBoolean` function, that checks if a value is a boolean. + +```javascript +isBoolean(true); +// => true + +isBoolean(null); +// => false +``` + +### 2. Check if a value is a number. + +Implement the `isNumber` function, that checks if a value is a _finite_ `number` or `bigint`, ie. not `NaN` or `Infinity`. + +Sometimes, the device for reading IDs fails and reads a non-numeric value as `NaN` (Not a Number) or `Infinity`. +Your function should be able to correctly handle this as well. + +```javascript +isNumber(42); +// => true + +isNumber('Hello, World!'); +// => false + +isNumber(42n); +// => true + +isNumber(NaN); +// => false +``` + +### 3. Check if a value is an object + +Implement the `isObject` function, that should check if the value is an object. +On the conveyor, `null` is nothing and not considered an object. + +```javascript +isObject({ greeting: 'Hello' }); +// => true + +isObject(25n); +// => false +``` + +### 4. Check if a string is numeric + +Implement the `isNumericString` function, that should check if the value is a string that only consists of digits or a minus followed by digits indicating a negative number. +Only integers should be considered, decimals are not considered numeric for this check of the recycling robot. + +```javascript +isNumericString(42); +// => false + +isNumericString('42'); +// => true + +isNumericString('Hi!'); +// => false +``` + +### 5. Check if an object is electronic + +Implement the `isElectronic` function, that checks if an object is an instance of the provided `ElectronicDevice` class or one of its child classes. + +```javascript +class Duck { + //... +} + +class WashingMachine extends ElectronicDevice { + //... +} + +isElectronic(new Duck()); +// => false + +isElectronic(new WashingMachine()); +// => true +``` + +### 6. Check if a value is a non empty array + +Implement the `isNonEmptyArray` function, that checks if a value is a non-empty array. + +```javascript +isNonEmptyArray([1, 2, 3]); +// => true + +isNonEmptyArray([]); +// => false +``` + +### 7. Check if a value is an empty array + +Implement the `isEmptyArray` function, that checks if a value is an empty array. + +```javascript +isEmptyArray([1, 2, 3]); +// => false + +isEmptyArray([]); +// => true +``` + +### 8. Check if an object has a `type` property or method + +Implement the `hasType` function, that checks whether an object has a `type` property or method. + +```javascript +class Keyboard(){ + type(){ + // ... + } +} +hasType({ type:"car", color:"red" }) +// => true + +hasType({ color:"green" }) +// => false + +hasType(new Keyboard()) +// => true +``` + +### 9. Throw an error if an object does not have an `id` property or method + +Implement the `assertHasId` function, that will throw an `Error` if an object is missing the `id` property. + +If an object does have the `id` property, it should not return anything. + +```javascript +assertHasId({ id: 42, color: 'red' }); +// => undefined + +assertHasId({ color: 'green' }); +// Error: "Object is missing the 'id' property" +``` + +### 10. Check if an object has an `id` property + +Implement the `hasIdProperty` function, that checks whether an object has an `id` property. + +```javascript +class SimpleData { + constructor() { + this.number = '42'; + this.id = 'BC269327FE1D9B95'; + } +} + +class StealingData extends SimpleData {} + +class MethodData { + constructor() { + this.number = '42'; + this._id = 'BC269327FE1D9B95'; + } + + get id() { + return this._id; + } +} + +hasIdProperty(new SimpleData()); +// => true + +hasIdProperty(new MethodData()); +// => false + +hasIdProperty(new StealingData()); +// => false +``` + +### 11. Check if an object has a defined `type` property + +Implement the `hasDefinedType` function, that checks if an object has a `type` property that is not `undefined`. + +```javascript +hasDefinedType({ type: undefined, color: 'red' }); +// => false + +hasDefinedType({ type: 'car', color: 'green' }); +// => true +``` diff --git a/exercises/concept/recycling-robot/.docs/introduction.md b/exercises/concept/recycling-robot/.docs/introduction.md new file mode 100644 index 0000000000..21f826324b --- /dev/null +++ b/exercises/concept/recycling-robot/.docs/introduction.md @@ -0,0 +1,164 @@ +# Introduction + +Knowning what the type of a piece of data is, is often very important for code to run smoothly and without errors. + +Javascript has several ways to check the type of a value or object. + +```exercism/note +Javascript's type checking mechanisms can be somewhat unreliable. + +For better type safety and stronger types, you should probably use TypeScript, a language that builds on JavaScript, but with the type syntax of a static-typed language. +``` + +## The `typeof` operator + +The `typeof` operator returns the type of its operand. +The output is a string matching the name of one of the [primitive data types][primitives], except for `"null"`. +It can also be `"function"` or `"object"`. + +```javascript +typeof undefined; +// => "undefined" + +typeof true; +// => "boolean" + +typeof 42; +// => "number" + +typeof 'Hello, World!'; +// => "string" + +typeof function () { + return 'Hello, World'; +}; +// => "function" + +typeof [1, 2, 3, 4]; +// => "object" + +typeof { city: 'Stockholm', country: 'Sweden' }; +// => "object" +``` + +For [historical reasons][`typeof null` is `"object"`]. + +## The `instanceof` operator + +For checking the type of an object, you can use the `instanceof` operator. +It evaluates into a `boolean` depending on whether the second operand is included in the first operands' [prototype chain][prototype chain]. +To clarify, `instanceof` will return whether the first operand is an instance of second operand or one of its child classes. +`instanceof` only works on objects. + +```javascript +class Beverage { + // ... +} + +// The Coffee class is a child of the Beverage class. +class Coffee extends Beverage { + // ... +} + +const java = new Coffee(); + +java instanceof Coffee; +// => true + +java instanceof Beverage; +// => true +``` + +````exercism/advanced +The `Array` class has a method called `Array.isArray()` that checks if its argument is an array. + +While `instanceof Array` will not work with an array created in a different realm such as an `iframe` in a webpage, `Array.isArray()` will. + +This is because the Array class has a different constructor in each realm, and each `iframe` has its own ream, meaning that the function in the prototype chain will be different, causing `instanceof Array` to fail. +`Array.isArray()` is capable of ignoring this, and should always be used when possible. + +It can also survive false positives where an object isn't actually an `Array`, and merely has `Array` in its prototype chain. + +```javascript +({ __proto__: Array.prototype }) instanceof Array +// => true + +Array.isArray({ __proto__: Array.prototype }) +// => false +``` + +```` + +## The `in` operator + +The `in` operator returns whether the first operand is a property of the second operand. +It does not check that the property has a defined value. +A property set to `undefined` will still be detected by `in`. + +```javascript +class Coffee { + constructor() { + this.temperature = 'hot'; + this.isDarkMatter = undefined; + } + + coolDown() { + this.temperature = 'warm'; + } +} + +const espresso = new Coffee(); + +'temperature' in espresso; +// => true + +'color' in espresso; +// => false + +'isDarkMatter' in espresso; +// => true +``` + +````exercism/note +`in` will return `true` for inherited properties and methods. + +```javascript +"coolDown" in espresso +// => true + +"constructor" in espresso +// => true +``` + +To avoid this, use `Object.hasOwn()` instead +```` + +## The `Object.hasOwn()` function + +The `Object.hasOwn()` method returns whether the specified object _owns the given property_ (it is not inherited or a method). + +```javascript +class Coffee { + constructor() { + this.temperature = 'hot'; + } + + coolDown() { + this.temperature = 'warm'; + } +} +const cappuccino = new Coffee(); + +Object.hasOwn(cappucino, 'temperature'); +// => true + +Object.hasOwn(cappucino, 'constructor'); +// => false + +Object.hasOwn(cappucino, 'coolDown'); +// => false +``` + +[primitives]: https://developer.mozilla.org/en-US/docs/Glossary/Primitive +[typeof null is object]: https://2ality.com/2013/10/typeof-null.html +[prototype chain]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Inheritance_and_the_prototype_chain diff --git a/exercises/concept/recycling-robot/.gitignore b/exercises/concept/recycling-robot/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/concept/recycling-robot/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/concept/recycling-robot/.meta/config.json b/exercises/concept/recycling-robot/.meta/config.json new file mode 100644 index 0000000000..aec5af47a5 --- /dev/null +++ b/exercises/concept/recycling-robot/.meta/config.json @@ -0,0 +1,21 @@ +{ + "authors": [ + "quintuple-mallard", + "SleeplessByte" + ], + "files": { + "solution": [ + "assembly-line.js" + ], + "test": [ + "assembly-line.spec.js" + ], + "exemplar": [ + ".meta/exemplar.js" + ], + "editor": [ + "lib.js" + ] + }, + "blurb": "Learn about type checking while helping manage an assembly line" +} diff --git a/exercises/concept/recycling-robot/.meta/exemplar.js b/exercises/concept/recycling-robot/.meta/exemplar.js new file mode 100644 index 0000000000..e2c4bf84f1 --- /dev/null +++ b/exercises/concept/recycling-robot/.meta/exemplar.js @@ -0,0 +1,123 @@ +// @ts-check +// +// The line above enables type checking for this file. Various IDEs interpret +// the @ts-check directive. It will give you helpful autocompletion when +// implementing this exercise. + +import { ElectronicDevice } from './lib.js'; + +/** + * Checks if input is a boolean. + * + * @param {unknown} value + * @returns {value is boolean} whether the input is a boolean + */ +export function isBoolean(value) { + return typeof value === 'boolean'; +} + +/** + * Checks if input is a finite number or bigint. + * + * @param {unknown} value + * @returns {value is number | bigint} whether the input is a finite number or bigint + */ +export function isNumber(value) { + return ( + (typeof value === 'number' && isFinite(value)) || typeof value === 'bigint' + ); +} + +/** + * Checks if a value is an object. + * + * @param {unknown} value + * @returns {value is object} whether the input is an object. + */ +export function isObject(value) { + return value !== null && typeof value === 'object'; +} + +/** + * Checks if a value is a numeric string. + * + * @param {unknown} value + * @returns {boolean} whether the input is a numeric string. + */ +export function isNumericString(value) { + return typeof value === 'string' && /^-?\d+$/.test(value); +} + +/** + * Checks if an object is an instance of the "ElectronicDevice" class or one of its children. + * + * @param {object} object + * @returns {boolean} whether the object is an instance of the "ElectronicDevice" class or one of its children. + */ +export function isElectronic(object) { + return object instanceof ElectronicDevice; +} + +/** + * Checks if a value is a non empty array. + * + * @param {unknown} value + * @returns {boolean} whether the input is a non empty array. + */ +export function isNonEmptyArray(value) { + return Array.isArray(value) && value.length > 0; +} + +/** + * Checks if a value is an empty array. + * + * @param {unknown} value + * @returns {boolean} whether the input is an empty array. + */ +export function isEmptyArray(value) { + return Array.isArray(value) && value.length === 0; +} + +/** + * Checks if a value has a "type" property or method. + * + * @param {object} object + * @returns {boolean} whether the input has a "type" property. + */ +export function hasType(object) { + return 'type' in object; +} + +/** + * Throws an error if an object is missing an "id" property or method. + * + * @param {object} object + * @returns {never|void} undefined if the input has an "id" property, otherwise throws an error. + */ +export function assertHasId(object) { + if ('id' in object) { + return; + } + + throw new Error('The "id" property is missing.'); +} + +/** + * Checks if a value has a "id" property. + * + * @param {object} object + * @returns {boolean} whether the input has a "id" property. + */ +export function hasIdProperty(object) { + return Object.hasOwn(object, 'id'); +} + +/** + * Checks if a value has a defined "type" property. + * + * @param {object} object + * @returns {boolean} whether the input has a defined "type" property. + */ +export function hasDefinedType(object) { + return Object.hasOwn(object, 'type') && object.type !== undefined; +} diff --git a/exercises/concept/recycling-robot/.npmrc b/exercises/concept/recycling-robot/.npmrc new file mode 100644 index 0000000000..d26df800bb --- /dev/null +++ b/exercises/concept/recycling-robot/.npmrc @@ -0,0 +1 @@ +audit=false diff --git a/exercises/concept/recycling-robot/LICENSE b/exercises/concept/recycling-robot/LICENSE new file mode 100644 index 0000000000..90e73be03b --- /dev/null +++ b/exercises/concept/recycling-robot/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Exercism + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/exercises/concept/recycling-robot/assembly-line.js b/exercises/concept/recycling-robot/assembly-line.js new file mode 100644 index 0000000000..bdd32f2d47 --- /dev/null +++ b/exercises/concept/recycling-robot/assembly-line.js @@ -0,0 +1,121 @@ +// @ts-check +// +// The line above enables type checking for this file. Various IDEs interpret +// the @ts-check directive. It will give you helpful autocompletion when +// implementing this exercise. + +import { ElectronicDevice } from './lib.js'; + +/** + * Checks if input is a boolean. + * + * @param {unknown} value + * @returns {boolean} whether the input is a boolean + */ +export function isBoolean(value) { + throw new Error('Remove this line and implement the isBoolean function'); +} + +/** + * Checks if input is a finite number or bigint. + * + * @param {unknown} value + * @returns {boolean} whether the input is a finite number or bigint + */ +export function isNumber(value) { + throw new Error('Remove this line and implement the isNumber function'); +} + +/** + * Checks if a value is an object. + * + * @param {unknown} value + * @returns {boolean} whether the input is an object. + */ +export function isObject(value) { + throw new Error('Remove this line and implement the isObject function'); +} + +/** + * Checks if a value is a numeric string. + * + * @param {unknown} value + * @returns {boolean} whether the input is a numeric string. + */ +export function isNumericString(value) { + throw new Error( + 'Remove this line and implement the isNumericString function', + ); +} + +/** + * Checks if an object is an instance of the `ElectronicDevice` class or one of its children. + * + * @param {object} object + * @returns {boolean} whether the object is an instance of the `ElectronicDevice` class or one of its children. + */ +export function isElectronic(object) { + throw new Error('Remove this line and implement the isElectronic function'); +} + +/** + * Checks if a value is a non empty array. + * + * @param {unknown} value + * @returns {boolean} whether the input is a non empty array. + */ +export function isNonEmptyArray(value) { + throw new Error( + 'Remove this line and implement the isNonEmptyArray function', + ); +} + +/** + * Checks if a value is an empty array. + * + * @param {unknown} value + * @returns {boolean} whether the input is an empty array. + */ +export function isEmptyArray(value) { + throw new Error('Remove this line and implement the isEmptyArray function'); +} + +/** + * Checks if a value has a "type" property or method. + * + * @param {object} object + * @returns {boolean} whether the input has a "type" property or method. + */ +export function hasType(object) { + throw new Error('Remove this line and implement the hasType function'); +} + +/** + * Throws an error if an object is missing an "id" property or method. + * + * @param {object} object + * @returns {never|void} undefined if the input has an "id" property or method, otherwise throws an error. + */ +export function assertHasId(object) { + throw new Error('Remove this line and implement the assertHasId function'); +} + +/** + * Checks if a value has an "id" property. + * + * @param {object} object + * @returns {boolean} whether the input has an "id" property. + */ +export function hasIdProperty(object) { + throw new Error('Remove this line and implement the hasIdProperty function'); +} + +/** + * Checks if a value has a defined "type" property. + * + * @param {object} object + * @returns {boolean} whether the input has a defined "type" property. + */ +export function hasDefinedType(object) { + throw new Error('Remove this line and implement the hasDefinedType function'); +} diff --git a/exercises/concept/recycling-robot/assembly-line.spec.js b/exercises/concept/recycling-robot/assembly-line.spec.js new file mode 100644 index 0000000000..d338348dff --- /dev/null +++ b/exercises/concept/recycling-robot/assembly-line.spec.js @@ -0,0 +1,520 @@ +import { describe, expect, test } from '@jest/globals'; +import { + isBoolean, + isNumber, + isObject, + isNumericString, + isElectronic, + isNonEmptyArray, + isEmptyArray, + assertHasId, + hasType, + hasIdProperty, + hasDefinedType, +} from './assembly-line'; +import { ElectronicDevice } from './lib.js'; + +describe('isBoolean', () => { + test('returns true for true', () => { + expect(isBoolean(true)).toBe(true); + }); + + test('returns true for false', () => { + expect(isBoolean(false)).toBe(true); + }); + + test('returns false for numbers', () => { + expect(isBoolean(42)).toBe(false); + expect(isBoolean(42n)).toBe(false); + expect(isBoolean(0)).toBe(false); + }); + + test('returns false for strings', () => { + expect(isBoolean('Hello, World!')).toBe(false); + expect(isBoolean('42')).toBe(false); + expect(isBoolean('true')).toBe(false); + expect(isBoolean('')).toBe(false); + }); + + test('returns false for null', () => { + expect(isBoolean(null)).toBe(false); + }); + + test('returns false for undefined', () => { + expect(isBoolean(undefined)).toBe(false); + }); + + test('returns false for symbols', () => { + expect(isBoolean(Symbol('1'))).toBe(false); + expect(isBoolean(Symbol('true'))).toBe(false); + }); + + test('returns false for objects', () => { + expect(isBoolean({})).toBe(false); + expect(isBoolean({ true: false })).toBe(false); + }); + + test('returns false for arrays', () => { + expect(isBoolean([])).toBe(false); + expect(isBoolean([true, false])).toBe(false); + }); +}); + +describe('isNumber', () => { + test('returns true for numbers', () => { + expect(isNumber(42)).toBe(true); + expect(isNumber(0)).toBe(true); + expect(isNumber(43_859_435.12)).toBe(true); + expect(isNumber(Number.MAX_SAFE_INTEGER)).toBe(true); + expect(isNumber(Number.MAX_VALUE)).toBe(true); + expect(isNumber(Number.MIN_SAFE_INTEGER)).toBe(true); + expect(isNumber(Number.MIN_VALUE)).toBe(true); + }); + + test('returns true for bigints', () => { + expect(isNumber(42n)).toBe(true); + expect(isNumber(0n)).toBe(true); + expect(isNumber(92n)).toBe(true); + expect(isNumber(1_848_958_451n)).toBe(true); + expect(isNumber(9_007_199_254_740_991n)).toBe(true); + expect(isNumber(9_999_999_999_999_999n)).toBe(true); + }); + + test('returns false for non-finite numbers such as NaN', () => { + expect(isNumber(NaN)).toBe(false); + expect(isNumber(Infinity)).toBe(false); + }); + + test('returns false for strings', () => { + expect(isNumber('Hello, World!')).toBe(false); + expect(isNumber('42')).toBe(false); + expect(isNumber('true')).toBe(false); + expect(isNumber('')).toBe(false); + }); + + test('returns false for null', () => { + expect(isNumber(null)).toBe(false); + }); + + test('returns false for undefined', () => { + expect(isNumber(undefined)).toBe(false); + }); + + test('returns false for symbols', () => { + expect(isNumber(Symbol('1'))).toBe(false); + expect(isNumber(Symbol('true'))).toBe(false); + }); + + test('returns false for objects', () => { + expect(isNumber({})).toBe(false); + expect(isNumber({ true: false })).toBe(false); + }); + + test('returns false for arrays', () => { + expect(isNumber([])).toBe(false); + expect(isNumber([1])).toBe(false); + }); + + test('returns false for booleans', () => { + expect(isNumber(true)).toBe(false); + expect(isNumber(false)).toBe(false); + }); +}); + +class ClassForTesting { + constructor(number, word) { + this.number = number; + this.word = word; + } + + id() {} +} + +describe('isObject', () => { + test('returns true on object literals', () => { + expect(isObject({})).toBe(true); + expect(isObject({ greeting: 'hello' })).toBe(true); + }); + + test('returns true on class instances', () => { + expect(isObject(new ClassForTesting(5, 'Hello'))).toBe(true); + expect(isObject(new ClassForTesting(58, 'null'))).toBe(true); + expect(isObject(new ClassForTesting(1488, 'World!'))).toBe(true); + }); + + test('returns true for arrays which are objects', () => { + expect(isObject([])).toBe(true); + expect(isObject([{}])).toBe(true); + }); + + test('returns false on functions', () => { + expect(isObject(isObject)).toBe(false); + expect(isObject(() => {})).toBe(false); + expect(isObject(() => ({}))).toBe(false); + }); + + test('returns false for strings', () => { + expect(isObject('Hello, World!')).toBe(false); + expect(isObject('{}')).toBe(false); + expect(isObject('42')).toBe(false); + expect(isObject('true')).toBe(false); + expect(isObject('')).toBe(false); + }); + + test('returns false for null', () => { + expect(isObject(null)).toBe(false); + }); + + test('returns false for undefined', () => { + expect(isObject(undefined)).toBe(false); + }); + + test('returns false for symbols', () => { + expect(isObject(Symbol('1'))).toBe(false); + expect(isObject(Symbol('true'))).toBe(false); + }); + test('returns false for booleans', () => { + expect(isObject(true)).toBe(false); + expect(isObject(false)).toBe(false); + }); +}); + +describe('isNumericString', () => { + test('returns true on single-digit strings', () => { + expect(isNumericString('1')).toBe(true); + expect(isNumericString('0')).toBe(true); + expect(isNumericString('9')).toBe(true); + }); + + test('returns true on negative single-digit strings', () => { + expect(isNumericString('-1')).toBe(true); + expect(isNumericString('-0')).toBe(true); + expect(isNumericString('-9')).toBe(true); + }); + + test('returns true on multi-digit strings', () => { + expect(isNumericString('12')).toBe(true); + expect(isNumericString('00')).toBe(true); + expect(isNumericString('42')).toBe(true); + expect(isNumericString('-582')).toBe(true); + }); + + test('returns false on non-numeric strings', () => { + expect(isNumericString('')).toBe(false); + expect(isNumericString('-')).toBe(false); + expect(isNumericString('--')).toBe(false); + expect(isNumericString('--32')).toBe(false); + expect(isNumericString('Hello, World!')).toBe(false); + expect(isNumericString('')).toBe(false); + expect(isNumericString('NaN')).toBe(false); + }); + + test('returns false for bigint strings', () => { + expect(isNumericString('12n')).toBe(false); + expect(isNumericString('-582n')).toBe(false); + }); + + test('returns false for null', () => { + expect(isNumericString(null)).toBe(false); + }); + + test('returns false for undefined', () => { + expect(isNumericString(undefined)).toBe(false); + }); + + test('returns false for symbols', () => { + expect(isNumericString(Symbol('1'))).toBe(false); + expect(isNumericString(Symbol('true'))).toBe(false); + }); + + test('returns false for arrays', () => { + expect(isNumericString([])).toBe(false); + expect(isNumericString(['42'])).toBe(false); + }); + + test('returns false for booleans', () => { + expect(isNumericString(true)).toBe(false); + expect(isNumericString(false)).toBe(false); + }); +}); + +class Oven extends ElectronicDevice {} +class Computer extends ElectronicDevice {} +class PersonalComputer extends Computer {} +class HomeMadePersonalComputer extends PersonalComputer {} + +describe('isElectronic', () => { + test('returns true on ElectronicDevices', () => { + expect(isElectronic(new ElectronicDevice())).toBe(true); + }); + + test('returns true on sub-classes of ElectronicDevice', () => { + expect(isElectronic(new Oven())).toBe(true); + expect(isElectronic(new PersonalComputer())).toBe(true); + expect(isElectronic(new HomeMadePersonalComputer())).toBe(true); + }); + + test('returns false on electronic devices not created using the constructor', () => { + expect(isElectronic(Object.create(ElectronicDevice.prototype))).toBe(false); + expect(isElectronic({ __proto__: ElectronicDevice.prototype })).toBe(false); + + const fakeDevice = {}; + Object.setPrototypeOf(fakeDevice, ElectronicDevice.prototype); + expect(isElectronic(fakeDevice)).toBe(false); + }); + + test('returns false on non-electronic device objects', () => { + expect(isElectronic({ language: 'javascript', typing: 'dynamic' })).toBe( + false, + ); + expect(isElectronic(new ClassForTesting(42, 'ElectronicDevice'))).toBe( + false, + ); + expect(isElectronic([1, 2, 3, 4])).toBe(false); + }); + + test('returns false for strings', () => { + expect(isElectronic('12n')).toBe(false); + expect(isElectronic('ElectronicDevice')).toBe(false); + }); + + test('returns false for null', () => { + expect(isElectronic(null)).toBe(false); + }); + + test('returns false for undefined', () => { + expect(isElectronic(undefined)).toBe(false); + }); + + test('returns false for symbols', () => { + expect(isElectronic(Symbol('1'))).toBe(false); + expect(isElectronic(Symbol('true'))).toBe(false); + }); + + test('returns false for arrays', () => { + expect(isElectronic([])).toBe(false); + expect(isElectronic(['42'])).toBe(false); + }); + + test('returns false for booleans', () => { + expect(isElectronic(true)).toBe(false); + expect(isElectronic(false)).toBe(false); + }); +}); + +describe('isNonEmptyArray', () => { + test('returns true for non-empty arrays', () => { + expect(isNonEmptyArray([1, 2, 3])).toBe(true); + expect(isNonEmptyArray(['a', 'b'])).toBe(true); + + // The prototype of Array is also an array, but in Node it's considered empty + // expect(isNonEmptyArray(Array.prototype)).toBe(true); + }); + + test('returns false for empty arrays', () => { + expect(isNonEmptyArray([])).toBe(false); + }); + + test('returns false for fake non-empty arrays', () => { + expect(isNonEmptyArray({ __proto__: Array.prototype, length: 1 })).toBe( + false, + ); + + const fakeArray = { length: 1 }; + Object.setPrototypeOf(fakeArray, Array.prototype); + expect(isNonEmptyArray(fakeArray)).toBe(false); + }); + + test('returns false for strings', () => { + expect(isNonEmptyArray('12n')).toBe(false); + expect(isNonEmptyArray('[1]')).toBe(false); + }); + + test('returns false for null', () => { + expect(isNonEmptyArray(null)).toBe(false); + }); + + test('returns false for undefined', () => { + expect(isNonEmptyArray(undefined)).toBe(false); + }); + + test('returns false for symbols', () => { + expect(isNonEmptyArray(Symbol('1'))).toBe(false); + expect(isNonEmptyArray(Symbol('[1]'))).toBe(false); + }); + + test('returns false for booleans', () => { + expect(isNonEmptyArray(true)).toBe(false); + expect(isNonEmptyArray(false)).toBe(false); + }); +}); + +describe('isEmptyArray', () => { + test('returns true for empty arrays', () => { + expect(isEmptyArray([])).toBe(true); + }); + + test('returns false for non-empty arrays', () => { + expect(isEmptyArray([1, 2, 3])).toBe(false); + expect(isEmptyArray(['a', 'b'])).toBe(false); + + // The prototype of Array is also an array, but in Node it's considered empty + // expect(isEmptyArray(Array.prototype)).toBe(false); + }); + + test('returns false on fake empty arrays', () => { + expect(isEmptyArray({ __proto__: Array.prototype, length: 0 })).toBe(false); + expect(isEmptyArray(Object.create(Array.prototype))).toBe(false); + + const fakeArray = {}; + Object.setPrototypeOf(fakeArray, Array.prototype); + expect(isNonEmptyArray(fakeArray)).toBe(false); + }); + + test('returns false for strings', () => { + expect(isEmptyArray('12n')).toBe(false); + expect(isEmptyArray('[]')).toBe(false); + }); + + test('returns false for null', () => { + expect(isEmptyArray(null)).toBe(false); + }); + + test('returns false for undefined', () => { + expect(isEmptyArray(undefined)).toBe(false); + }); + + test('returns false for symbols', () => { + expect(isEmptyArray(Symbol('1'))).toBe(false); + expect(isEmptyArray(Symbol('[]'))).toBe(false); + }); + + test('returns false for booleans', () => { + expect(isEmptyArray(true)).toBe(false); + expect(isEmptyArray(false)).toBe(false); + }); +}); + +class MagicInspector { + type() { + return 'sleight of hand'; + } +} + +class MagicRevealer extends MagicInspector { + spill() { + throw new Error('A true magician never reveals their secrets'); + } +} + +describe('hasType', () => { + test('returns true if the type property exists', () => { + expect(hasType({ type: 'car', color: 'red' })).toBe(true); + }); + + test('returns true if the type method exists', () => { + expect(hasType(new MagicInspector())).toBe(true); + }); + + test('returns true if the type method is inherited', () => { + expect(hasType(new MagicRevealer())).toBe(true); + }); + + test('returns false if neither the type property, nor the method exists', () => { + expect(hasType({ color: 'green' })).toBe(false); + }); +}); + +class IdGenerator { + id() { + return Math.random() * 42; + } +} + +class MagicIdGenerator extends IdGenerator { + magic() { + return '🔮'; + } +} + +describe('assertHasId', () => { + test('returns nothing if the id property is present', () => { + expect(() => assertHasId({ id: 1 })).not.toThrow(); + expect(() => assertHasId({ id: 42, color: 'red' })).not.toThrow(); + + const oven = new Oven(); + oven.id = 42; + expect(() => assertHasId(oven)).not.toThrow(); + + // Even when there is no ID set + expect(() => assertHasId({ id: null })).not.toThrow(); + }); + + test('returns nothing if the id method is present', () => { + expect(() => assertHasId(new IdGenerator())).not.toThrow(); + }); + + test('returns nothing if the id method is inherited', () => { + expect(() => assertHasId(new MagicIdGenerator())).not.toThrow(); + }); + + test("throws error if object has no 'id' property or method", () => { + expect(() => assertHasId({})).toThrow(Error); + expect(() => assertHasId({ color: 'green' })).toThrow(Error); + }); +}); + +class SimpleData { + constructor() { + this.number = '42'; + this.id = 'BC269327FE1D9B95'; + } +} + +class StealingData extends SimpleData {} + +class MethodData { + constructor() { + this.number = '42'; + this._id = 'BC269327FE1D9B95'; + } + + get id() { + return this._id; + } +} + +describe('hasIdProperty', () => { + test('returns true if it has the id property', () => { + expect(hasIdProperty({ id: 'test' })).toBe(true); + expect(hasIdProperty(new SimpleData())).toBe(true); + }); + + test('returns false if it does not have the id property', () => { + expect(hasIdProperty(new MethodData())).toBe(false); + expect(hasIdProperty({ color: 'green' })).toBe(false); + }); + + test('returns true if the id property was set in the constructor in the prototype chain', () => { + expect(hasIdProperty(new StealingData())).toBe(true); + }); +}); + +describe('hasDefinedType', () => { + test('returns true if the type property is defined and set', () => { + expect(hasDefinedType({ type: 'car', color: 'green' })).toBe(true); + }); + + test('returns true if the type property is defined and set to an empty value', () => { + expect(hasDefinedType({ type: null, color: 'blue' })).toBe(true); + }); + + test('returns false if the type property is defined but not set', () => { + expect(hasDefinedType({ type: undefined, color: 'red' })).toBe(false); + }); + + test('returns false if the type property is missing', () => { + expect(hasDefinedType({ color: 'white' })).toBe(false); + expect(hasDefinedType(new MagicInspector())).toBe(false); + }); +}); diff --git a/exercises/concept/recycling-robot/babel.config.js b/exercises/concept/recycling-robot/babel.config.js new file mode 100644 index 0000000000..a638497df1 --- /dev/null +++ b/exercises/concept/recycling-robot/babel.config.js @@ -0,0 +1,4 @@ +module.exports = { + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], +}; diff --git a/exercises/concept/recycling-robot/eslint.config.mjs b/exercises/concept/recycling-robot/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/concept/recycling-robot/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/concept/recycling-robot/jest.config.js b/exercises/concept/recycling-robot/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/concept/recycling-robot/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/concept/recycling-robot/lib.js b/exercises/concept/recycling-robot/lib.js new file mode 100644 index 0000000000..55ce7cadea --- /dev/null +++ b/exercises/concept/recycling-robot/lib.js @@ -0,0 +1,18 @@ +const certification = Symbol('Certification'); + +export class ElectronicDevice { + // This class will be used in the exercise. + + static [Symbol.hasInstance](instance) { + return instance && instance.__certification === certification; + } + + constructor() { + Object.defineProperty(this, '__certification', { + enumerable: false, + writable: false, + configurable: false, + value: certification, + }); + } +} diff --git a/exercises/concept/recycling-robot/package.json b/exercises/concept/recycling-robot/package.json new file mode 100644 index 0000000000..930bad9e0c --- /dev/null +++ b/exercises/concept/recycling-robot/package.json @@ -0,0 +1,38 @@ +{ + "name": "@exercism/javascript-recycling-robot", + "description": "Exercism concept exercise on type checking", + "author": "Katrina Owen", + "contributors": [ + "Derk-Jan Karrenbeld (https://derk-jan.com)", + "Tejas Bubane (https://tejasbubane.github.io/)" + ], + "private": true, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/exercism/javascript", + "directory": "exercises/concept/recycling-robot" + }, + "devDependencies": { + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" + }, + "dependencies": {}, + "scripts": { + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" +} diff --git a/exercises/concept/regular-chatbot/.docs/hints.md b/exercises/concept/regular-chatbot/.docs/hints.md new file mode 100644 index 0000000000..0baf7dcba7 --- /dev/null +++ b/exercises/concept/regular-chatbot/.docs/hints.md @@ -0,0 +1,49 @@ +# Hints + +## 1. Check Valid Command + +- Use the [test()][mdn-regex-test] method for returning a boolean. +- Remember that we can use [flags][mdn-flags] at the end of the regular expression for additional features such as case insensitive. + +## 2. Remove Encrypted Emojis + +- In this case we must use the [RexExp][mdn-regular-expressions] constructor to build our regular expression. +- Thanks to the common encryption of each emoji, we can use `[0-9]` to search for any digit after the `emoji`word. +- The character `+` matches one or more consecutive items. +- Use the [split()][mdn-regex-split] method alongside the regex for each emoji to get rid of all encryption. +- Finally, return this splitted array as a string using the [join()][mdn-join] method. + +## 3. Check Valid Phone Number + +- In this exercise we are playing with `groups and ranges`. Read this [documentation][mdn-groups] for learning more about it. +- This [article][phone-validation] is really good at explaining different ways of number validation. +- You may also want to use this [cheatsheet][mdn-regex-cheatsheet] for a broad view of available features in regular expressions. +- Use the `test()` method to check whether the phone number is valid or not. +- Return the final answer with a simple `if statement`. + +## 4. Get Website Link + +- We are targeting words that are joined by one or more `.(dots)`. +- Remember to put the flag `g` at the end so we can get all the domains in any sentence. +- Use the [match()][mdn-regex-match] method to return an array with all the previous matches. + +## 5. Greet the user + +- First, use the [replace()][mdn-regex-replace] method directly to the given string. +- Use [Named Capture Groups][mdn-groups] to store the `name` and `surname` into different values. +- Pass a `(...match)` destructuring to the replacer function so you can get the name and surname into the object within the matched groups. +- Use the `pop()` method to store the matched object with `name` and `surname`. +- Finally, implement a `template String` so you can get the values from the regular expression object. See [this][mdn-capturing-groups] example for knowing about how match() stores objects. + +[mdn-regex-cheatsheet]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Cheatsheet +[mdn-regular-expressions]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions +[mdn-common-functions]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#using_regular_expressions_in_javascript +[mdn-flags]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#advanced_searching_with_flags +[mdn-regex-test]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test +[mdn-regex-match]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match +[mdn-regex-replace]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace +[mdn-regex-split]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split +[mdn-join]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join +[mdn-groups]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Groups_and_Ranges +[phone-validation]: https://www.w3resource.com/javascript/form/phone-no-validation.php +[mdn-capturing-groups]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match#using_named_capturing_groups diff --git a/exercises/concept/regular-chatbot/.docs/instructions.md b/exercises/concept/regular-chatbot/.docs/instructions.md new file mode 100644 index 0000000000..779fa40688 --- /dev/null +++ b/exercises/concept/regular-chatbot/.docs/instructions.md @@ -0,0 +1,93 @@ +# Instructions + +You have been hired as a Regular Expression Specialist in a company that is developing a Chatbot. + +It is in a very basic phase of development, hence your mission is to use Regular Expressions to improve the chatbot's ability to understand and generate natural language. + +## Check Valid Command + +Apart from being smart, the Chatbot is also a loyal assistant. + +To ask something to the chatbot, the user must say the word “**Chatbot**” in the first position of the command. + +It doesn't matter if the keyword is in UPPERCASE or lowercase. The important aspect here is the position of the word. + +Implement the function `isValidCommand()` that helps the Chatbot recognize when the user is giving a command. + +```javascript +isValidCommand("Chatbot, play a song from the 80's."; +// => True +isValidCommand("Hey Chatbot, where is the closest pharmacy?"; +// => False +isValidCommand("CHATBOT, do you have a solution for this challenge?"; +// => True +``` + +## Remove encrypted emojis + +The Chatbot has a difficult time understanding how humans use emojis to express their emotions. + +When the chatbot receives user messages, each emoji is represented as “_emoji_” followed by its _id_ number. + +Implement the `removeEmoji()` method to take a string and remove all the emoji’s encryption throughout the message. + +Lines not containing emoji’s text should be returned unmodified. + +Just remove the emoji string. Do not attempt to adjust the whitespace. + +For this particular challenge, use constructor syntax for creating the regular expression. + +```javascript +removeEmoji("I love playing videogames emoji3465 it's one of my hobbies"); +// => "I love playing videogames it's one of my hobbies" +``` + +## Check Valid Phone Number + +Considering the download of chatbot features on a mobile app, the user is expected to write a phone number during the conversation. + +The problem is that the chatbot can only read and validate a number with a specific format. + +If the number is valid (matches the character sequence specified by the **regular expression**), the chatbot answers with a message thanking the user and confirming the number. If the number is invalid, the function informs the user that the phone number is not valid. + +The expected format is: (+##) ###-###-### + +```javascript +checkPhoneNumber('(+34) 659-771-594'); +// => "Thanks! You can now download me to your phone." +checkPhoneNumber('659-771-594'); +// => "Oops, it seems like I can't reach out to 659-771-594" +``` + +## Get website link + +The Chatbot is a really curious software. Even though he can search on the internet about a particular topic, he likes to ask users about cool websites or URL’s to go find relevant information. + +Example of Conversation: + +- **Chatbot**: Hey username, I would like to learn how to code in JavaScript, do you know any cool website where I could learn? +- **User**: I learned a lot from [exercism.org](http://exercism.org) + +Implement the function `getURL()` which is able to return an array with just the link of each website. + +```javascript +getURL('I learned a lot from exercism.org'); +// => ["exercism.org"]; +``` + +## Greet the user + +For storing data from all the persons who have had a conversation with, the chatbot is able to get the Full Name from the user’s profile in this style: **“smith, john”**. + +In this way, we want our chatbot to be really polite and make a good impression. + +Write the function `niceToMeetYou()` that takes a string with the full name of the user, and returns the string **“Nice to meet you, John Smith”** + +For learning purposes, implement the function using a **replacement method** from Regular Expressions. + +```javascript +let str = 'Smith, John'; + +niceToMeetYou(str); +// => "Nice to meet you, John Smith" +``` diff --git a/exercises/concept/regular-chatbot/.docs/introduction.md b/exercises/concept/regular-chatbot/.docs/introduction.md new file mode 100644 index 0000000000..9d4b09ac22 --- /dev/null +++ b/exercises/concept/regular-chatbot/.docs/introduction.md @@ -0,0 +1,171 @@ +# Introduction + +## Regular Expressions in JavaScript + +A [Regular Expression][mdn-regular-expressions] (or Regex) is a sequence of characters that we can use to target and manipulate certain elements in strings. Hence, we can: + +- **Search** for a text in a string +- **Replace** substrings in a string +- **Extract** information from a string + +> 💡 JavaScript's regex flavor is part of the ECMA-262 standard for the language. This means that you don't have to worry about browser-specific compatibility. + +## How to create Regular Expressions + +In JavaScript, a regular expressions is mostly written in the format `/pattern/modifiers`. + +We have two ways of creating a regular expression: + +1. **Regular Expression Literal**: + +```javascript +const regex = /[a-z]/; +``` + +2. Constructor **RegExp**: + +```javascript +const regex = new RegExp('[a-z]'); +``` + +In both cases, JavaScript is creating an object out of the regex. It is recommended to use immutable patterns with the literal as default. + +The RegExp constructor can be used for cases where the regex will change or we don't know it yet (like an input). + +## 🏴‍☠️ Flags + +Regular expressions have optional superpowers called [`flags`][mdn-flags] that allow for additional features. + +Some of the widely used are: + +- `/g` - Global Search +- `/i` - Case Insensitive +- `/m` - Multiline Search + +Here is a simple example: + +```javascript +const re = /home/gi; +const str = 'Home, sweet home.'; +const myArray = str.match(re); +console.log(myArray); + +=> // ["Home", "home"] +``` + +The `g` character allows us to parse all possible matches within a string. Without this feature, JavaScript would have extracted only the first `Home` match. + +The Case Insensitive flag `/i` enables us to be flexible when it comes to finding a pattern. This way it doesn't matter if what we are looking for is in `UPPERCASE` or `lowercase`. + +When using the `RegExp` constructor, the syntax of adding flags is different. + +```javascript +let regex = /[a-z]/gi; // literal notation +let regex = new RegExp('[a-z]', 'gi'); // constructor with string pattern as first argument +let regex = new RegExp(/[a-z]/, 'gi'); // constructor with regular expression literal as first argument (Starting with ECMAScript 6) +``` + +## Most common Functions + +When regular expressions are combined with current build-in functions in JavaScript, we have some powerful ways of manipulating and getting data from strings. + +These are some of the most common functions used alongside regex. + +### Test + +The [test()][mdn-regex-test] method executes a search for a match between a regular expression and a specified string. Returns true or false. + +```javascript +const str = 'It is difficult to test if you have a virus'; +const result = /virus$/.test(str); + +console.log(result); + +// => true +``` + +### Match + +With the [match()][mdn-regex-match] method, we get a useful array whose contents depend on the presence or absence of the found matches. + +In this way, we are able both to **search** and to **extract** information from any string. For example: + +```javascript +const funnyQuote = + 'If you see someone crying, ask if it is because of their haircut.'; +const regex1 = /someone/; +const regex2 = /happy/; + +funnyQuote.match(regex1); +// => ["someone", index: 11, input: "If you see someone crying, ask if it is because of their haircut.", groups: undefined] + +funnyQuote.match(regex2); +// => null +``` + +When the Global Search flag `/g` is present, instead of getting the only match alongside useful information such as the index or input, the method returns all of the occurances displayed in the array: + +```javascript +const funnyQuote = + 'If you see someone crying, ask if it is because of their haircut.'; + +const regex3 = /if/gi; + +funnyQuote.match(regex3); +// => ["If", "if"]; +``` + +### Replace + +The [replace()][mdn-regex-replace] method in JavaScript allows us to search for a value within a given string, and replacing it with a desired new value. + +```javascript +string.replace(searchValue, newValue); +``` + +The pattern for searching the substitution can be a single string, or a regular expression. + +```javascript +let string = 'I like dogs!'; +let result = string.replace('dogs', 'cats'); + +let string = 'I would love to travel to Japan'; +let result = string.replace(/Japan/g, 'Hawaii'); +``` + +Moreover, we can apply a function on the replacement position in order to make additional changes to each value. + +```javascript +let text = 'Say hello to the chatbot.'; +let result = text.replace(/chatbot|hello/gi, function (word) { + return word.toUpperCase(); +}); +// => "Say HELLO to the CHATBOT" +``` + +### Split + +The [split()][mdn-regex-split] method in JavaScript represents a different way of using and manipulating strings with regular expressions. + +In this way, we will be using regex in order to divide a given string by recognizing a pattern, e.g. `str.split(/[,.\s]/)`. This pattern will be used as the `separator`. + +```javascript +const str = 'hello,user.how are.you'; + +const result = str.split(/[,.\s]/); + +console.log(result); +// => ['hello', 'user', 'how', 'are', 'you'] +``` + +[using-regular-expressions-in-javascript]: https://www.regular-expressions.info/javascript.html +[mdn-regex-cheatsheet]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Cheatsheet +[mdn-regular-expressions]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions +[mdn-common-functions]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#using_regular_expressions_in_javascript +[mdn-flags]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#advanced_searching_with_flags +[mdn-regex-test]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test +[mdn-regex-match]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match +[mdn-regex-replace]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace +[mdn-regex-split]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split +[demystifying-regular-expressions-with-javascript]: https://livecodestream.dev/post/demystifying-regular-expressions-with-javascript/ +[how-to-use-the-js-replace-method-on-a-string]: https://www.freecodecamp.org/news/javascript-regex-match-example-how-to-use-the-js-replace-method-on-a-string/ diff --git a/exercises/concept/regular-chatbot/.gitignore b/exercises/concept/regular-chatbot/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/concept/regular-chatbot/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/concept/regular-chatbot/.meta/config.json b/exercises/concept/regular-chatbot/.meta/config.json new file mode 100644 index 0000000000..01dda4e544 --- /dev/null +++ b/exercises/concept/regular-chatbot/.meta/config.json @@ -0,0 +1,20 @@ +{ + "authors": [ + "julendiaz" + ], + "contributors": [ + "junedev" + ], + "files": { + "solution": [ + "regular-chatbot.js" + ], + "test": [ + "regular-chatbot.spec.js" + ], + "exemplar": [ + ".meta/exemplar.js" + ] + }, + "blurb": "Learn the basics of regular expressions with JavaScript by helping a chatbot understand humans." +} diff --git a/exercises/concept/regular-chatbot/.meta/design.md b/exercises/concept/regular-chatbot/.meta/design.md new file mode 100644 index 0000000000..c7dd1d24a6 --- /dev/null +++ b/exercises/concept/regular-chatbot/.meta/design.md @@ -0,0 +1,27 @@ +# Design + +## Learning objectives + +- Learn about the flavour of JavaScript regarding Regular Expressions. +- How to create a regular expression, with regular expression literal and RexExp +- How to pass flags in both versions +- How to use the most common regex related functions, with special focus on test and match. +- Understanding the different parts of the match result, incl. capture results +- Using the replace method alongside regular expressions +- Being aware of performance implications + +## Out of scope + +This exercise is intended for an Introduction to the flavour of Regular Expressions in JavaScript. The main focus resides in understanding the syntax behind the creation and manipulation of regex. + +Explaining how to write regular expressions themselves is out of scope for the concept here but we should link to some good resource a student could read to learn about them from scratch. We don't do this as part of the concept because Exercism assumes the student is already fluent in another language and most languages include some form of regular expressions. + +## Concepts + +- `regular-expressions` + +## Prerequisites + +- `arrays` as this is the result of match and other common functions. +- `classes` to understand the new `Regex` syntax. +- `objects` to know that every regular expression is creating an object out of the search. diff --git a/exercises/concept/regular-chatbot/.meta/exemplar.js b/exercises/concept/regular-chatbot/.meta/exemplar.js new file mode 100644 index 0000000000..f16b665266 --- /dev/null +++ b/exercises/concept/regular-chatbot/.meta/exemplar.js @@ -0,0 +1,65 @@ +// @ts-check + +/** + * Given a certain command, help the chatbot recognize whether the command is valid or not. + * + * @param {string} command + * @returns {boolean} whether or not is the command valid + */ + +export function isValidCommand(command) { + const regex = /^chatbot/i; + return regex.test(command); +} + +/** + * Given a certain message, help the chatbot get rid of all the emoji's encryption throught the message. + * + * @param {string} message + * @returns {string} The message without the emojis encryption + */ +export function removeEmoji(message) { + const regex = new RegExp('emoji[0-9]+'); + return message.split(regex).join(''); +} + +/** + * Given a certain phone number, help the chatbot recognize whether it is in the correct format. + * + * @param {string} number + * @returns {string} the Chatbot response to the phone Validation + */ +export function checkPhoneNumber(number) { + const regex = + /^\(\+?([0-9]{2})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{3})[-. ]?([0-9]{3})$/; + if (regex.test(number)) { + return 'Thanks! You can now download me to your phone.'; + } else { + return `Oops, it seems like I can't reach out to ${number}`; + } +} + +/** + * Given a certain response from the user, help the chatbot get only the URL + * + * @param {string} userInput + * @returns {string[] | null} all the possible URL's that the user may have answered + */ +export function getURL(userInput) { + const regex = /(\w+\.)+\w+/g; + return userInput.match(regex); +} + +/** + * Greet the user using its full name data from the profile + * + * @param {string} fullName + * @returns {string} Greeting from the chatbot + */ +export function niceToMeetYou(fullName) { + return fullName.replace(/(?\w+), (?\w+)/, (...match) => { + let groups = match.pop(); + + return `Nice to meet you, ${groups.name} ${groups.surname}`; + }); +} diff --git a/exercises/concept/regular-chatbot/.npmrc b/exercises/concept/regular-chatbot/.npmrc new file mode 100644 index 0000000000..d26df800bb --- /dev/null +++ b/exercises/concept/regular-chatbot/.npmrc @@ -0,0 +1 @@ +audit=false diff --git a/exercises/concept/regular-chatbot/LICENSE b/exercises/concept/regular-chatbot/LICENSE new file mode 100644 index 0000000000..90e73be03b --- /dev/null +++ b/exercises/concept/regular-chatbot/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Exercism + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/exercises/concept/regular-chatbot/babel.config.js b/exercises/concept/regular-chatbot/babel.config.js new file mode 100644 index 0000000000..a638497df1 --- /dev/null +++ b/exercises/concept/regular-chatbot/babel.config.js @@ -0,0 +1,4 @@ +module.exports = { + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], +}; diff --git a/exercises/concept/regular-chatbot/eslint.config.mjs b/exercises/concept/regular-chatbot/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/concept/regular-chatbot/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/concept/regular-chatbot/jest.config.js b/exercises/concept/regular-chatbot/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/concept/regular-chatbot/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/concept/regular-chatbot/package.json b/exercises/concept/regular-chatbot/package.json new file mode 100644 index 0000000000..21b9374160 --- /dev/null +++ b/exercises/concept/regular-chatbot/package.json @@ -0,0 +1,34 @@ +{ + "name": "@exercism/javascript-regular-chatbot", + "description": "Exercism concept exercise on basics", + "author": "Julen Diaz ", + "private": true, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/exercism/javascript", + "directory": "exercises/concept/regular-chatbot" + }, + "devDependencies": { + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" + }, + "dependencies": {}, + "scripts": { + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" +} diff --git a/exercises/concept/regular-chatbot/regular-chatbot.js b/exercises/concept/regular-chatbot/regular-chatbot.js new file mode 100644 index 0000000000..9bfe2d554e --- /dev/null +++ b/exercises/concept/regular-chatbot/regular-chatbot.js @@ -0,0 +1,52 @@ +// @ts-check + +/** + * Given a certain command, help the chatbot recognize whether the command is valid or not. + * + * @param {string} command + * @returns {boolean} whether or not is the command valid + */ + +export function isValidCommand(command) { + throw new Error('Remove this line and implement the function'); +} + +/** + * Given a certain message, help the chatbot get rid of all the emoji's encryption through the message. + * + * @param {string} message + * @returns {string} The message without the emojis encryption + */ +export function removeEmoji(message) { + throw new Error('Remove this line and implement the function'); +} + +/** + * Given a certain phone number, help the chatbot recognize whether it is in the correct format. + * + * @param {string} number + * @returns {string} the Chatbot response to the phone Validation + */ +export function checkPhoneNumber(number) { + throw new Error('Remove this line and implement the function'); +} + +/** + * Given a certain response from the user, help the chatbot get only the URL. + * + * @param {string} userInput + * @returns {string[] | null} all the possible URL's that the user may have answered + */ +export function getURL(userInput) { + throw new Error('Remove this line and implement the function'); +} + +/** + * Greet the user using the full name data from the profile. + * + * @param {string} fullName + * @returns {string} Greeting from the chatbot + */ +export function niceToMeetYou(fullName) { + throw new Error('Remove this line and implement the function'); +} diff --git a/exercises/concept/regular-chatbot/regular-chatbot.spec.js b/exercises/concept/regular-chatbot/regular-chatbot.spec.js new file mode 100644 index 0000000000..4d409d72aa --- /dev/null +++ b/exercises/concept/regular-chatbot/regular-chatbot.spec.js @@ -0,0 +1,88 @@ +import { describe, expect, test } from '@jest/globals'; +import { + checkPhoneNumber, + getURL, + isValidCommand, + niceToMeetYou, + removeEmoji, +} from './regular-chatbot'; + +describe('isValidCommand', () => { + test('recognizes wheter the command is at the first position', () => { + expect(isValidCommand('Chatbot, Do you understand this command?')).toBe( + true, + ); + expect( + isValidCommand( + 'Hey Chatbot, please tell me what is the weather for tomorrow.', + ), + ).toBe(false); + }); + + test('does not care about UPPERCASE or lowercase', () => { + expect(isValidCommand('CHATBOT, Is it okey if I shout at you?')).toBe(true); + expect( + isValidCommand('chatbot, please tell me what is happening here.'), + ).toBe(true); + }); +}); + +describe('removeEmoji', () => { + test('removes properly one single emoji encryption', () => { + const expected = 'What was your name? Sorry I forgot about it.'; + expect( + removeEmoji('What was your name? emoji2134 Sorry I forgot about it.'), + ).toBe(expected); + }); + + test('removes all the emoji encryption', () => { + const expected = ' How about ordering ?'; + expect(removeEmoji('emoji5321 How about ordering emoji8921 ?')).toBe( + expected, + ); + }); +}); + +describe('checkPhoneNumber', () => { + test('recognizes a phone number with the correct format', () => { + const expected = 'Thanks! You can now download me to your phone.'; + expect(checkPhoneNumber('(+34) 643-876-459')).toBe(expected); + expect(checkPhoneNumber('(+49) 543-928-190')).toBe(expected); + }); + + test('informs the user that it is a wrong phone number format', () => { + expect(checkPhoneNumber('322-787-654')).toBe( + "Oops, it seems like I can't reach out to 322-787-654", + ); + expect(checkPhoneNumber('4355-67-274')).toBe( + "Oops, it seems like I can't reach out to 4355-67-274", + ); + }); +}); + +describe('getURL', () => { + test('returns only the link of the website', () => { + expect(getURL('You can check more info on youtube.com')).toStrictEqual([ + 'youtube.com', + ]); + expect( + getURL('There is a cool website called theodinproject.com to learn from'), + ).toStrictEqual(['theodinproject.com']); + }); + + test('returns an array with the multiple websites links', () => { + expect(getURL('That was from reddit.com and notion.so')).toStrictEqual([ + 'reddit.com', + 'notion.so', + ]); + }); +}); + +describe('niceToMeetYou', () => { + test('greets the user by its proper name', () => { + expect(niceToMeetYou('Sanz, Pablo')).toBe('Nice to meet you, Pablo Sanz'); + expect(niceToMeetYou('Stephan, Sandro')).toBe( + 'Nice to meet you, Sandro Stephan', + ); + }); +}); diff --git a/exercises/concept/train-driver/.docs/hints.md b/exercises/concept/train-driver/.docs/hints.md new file mode 100644 index 0000000000..a80b5c2af0 --- /dev/null +++ b/exercises/concept/train-driver/.docs/hints.md @@ -0,0 +1,32 @@ +# Hints + +## General + +- To extract multiple arguments in the function parameters so can you pack them with the `...`. +- To use rest and spread use the `...` operator. + +## 1. Create a list of all wagons + +- Multiple arguments in the function parameters can be packed with the [`...` (spread) syntax][spread-syntax]. operator. + +## 2. Move the first two elements to the end of the array + +- You can unpack a series of parameters using [a destructuring assignment (`...`)][destructuring-assignment]. + This lets you extract the first two elements of a `array` while keeping the rest intact. +- To add another `array` into an existing `array`, you can use the `...` operator to "spread" the `array`. + +## 3. Add missing values + +- Using unpacking with the rest operator(`...`), lets you extract the first two elements of a `array` while keeping the rest intact. +- To add another `array` into an existing `array`, you can use the `...` operator to "spread" the `array`. + +## 4. Extend routing information + +- To add another `object` into an existing `object`, you can use the `...` operator to "spread" the `object`. + +## 5. Separate arrival time from routing information + +- To extract a value from an `object` while keeping the rest intact, you can use the rest operator(`...`). + +[spread-syntax]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax +[destructuring-assignment]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment diff --git a/exercises/concept/train-driver/.docs/instructions.md b/exercises/concept/train-driver/.docs/instructions.md new file mode 100644 index 0000000000..d9d89dea21 --- /dev/null +++ b/exercises/concept/train-driver/.docs/instructions.md @@ -0,0 +1,101 @@ +# Instructions + +Your friend Linus is a train driver who drives cargo trains between cities. +Although they are amazing at handling trains, they are not amazing at handling logistics or computers. +They would like to enlist your programming help organizing train details and correcting mistakes in route data. + +```exercism/note +To practice, use the rest or spread operator to solve each of the tasks below. +``` + +## 1. Create a list of all wagons + +Your friend has been keeping track of each wagon identifier (ID), but they are never sure how many wagons the system is going to have to process at any given time. +It would be much easier for the rest of the logistics program to have this data packaged into a unified `array`. + +Implement a function `getListOfWagons` that accepts an arbitrary number of wagon IDs which are the IDs of each wagon. +Each ID will be a positive integer. +The function should then return the given IDs as a single `array`. + +```javascript +getListOfWagons(1, 7, 12, 3, 14, 8, 5); +// => [1, 7, 12, 3, 14, 8, 5] +``` + +## 2. Move the first two elements to the end of the array + +At this point, you are starting to get a feel for the data and how it's used in the logistics program. +The ID system always assigns the locomotive an ID of **1**, with the remainder of the wagons in the train assigned a randomly chosen ID greater than **1**. + +Your friend had to connect two new wagons to the train and forgot to update the system! +Now, the first two wagons in the train `array` have to be moved to the end, or everything will be out of order. + +Linus would be really grateful to you for fixing their mistakes. + +Implement a function `fixListOfWagons` that accepts an array of the id of each wagon. +It `return`s an `array` where the 2 first elements repositioned to the end of the `array` so that the locomotive can be in the front. + +```javascript +eachWagonsID = [2, 5, 1, 7, 4, 12, 6, 3, 13]; +fixListOfWagons(eachWagonsID); +// => [1, 7, 4, 12, 6, 3, 13, 2, 5] +``` + +## 3. Add missing values + +Uh-oh. some wagons seem to have gone missing. + +Fortunately, your friend just found another `array` which appears to contain the missing wagon IDs, and would like you to add them into the main wagon ID `array`. +All they can remember is that the missing values should be placed directly after the designated locomotive. + +Given this new information, write a function called `correctListOfWagons` that takes two arrays which have the IDs of each wagon as the arguments. +The wagon IDs of the second `array` should be added into the first `array` directly after the locomotive (ID 1). + +```javascript +eachWagonsID = [1, 5, 20, 7, 4, 8]; +missingWagons = [3, 17, 6, 15]; +correctListOfWagons(eachWagonsID, missingWagons); +// => [1, 3, 17, 6, 15, 5, 20, 7, 4, 8] +``` + +## 4. Extend routing information + +Now that all the wagon data is correct, your friend would like you to update the systems routing information. +Initial routing information has been constructed as an `object`, and you friend would like you to update it with the additions provided. +Every route requires slightly different information, so your friend would really prefer a generic solution. + +Implement a function `extendRouteInformation` that accepts two `objects`. +The first `object` contains which cities the train route moves between. + +The second `object` contains other routing details such as train speed or length. +The function should return a consolidated `object` with all routing information. + +```exercism/note +The variable `moreRouteInformation` can contain different properties. +``` + +```javascript +route = { from: 'Berlin', to: 'Hamburg' }; +moreRouteInformation = { length: '100', speed: '50' }; +extendRouteInformation(route, moreRouteInformation); +// => {from: "Berlin", to: "Hamburg", length: "100", speed: "50"} +``` + +## 5. Separate arrival time from routing information + +Your friend has noticed that they don't need the arrival time in the routing information. +Therefore your friend would like you to separate the arrival time from the routing information. + +Implement a function `separateTimeOfArrival` that accepts an object with the routing information. +The function should return an array where the first element of the array is the arrival time and the second element is an object with the routing information without arrival time. + +```javascript +routeInformation = { + from: 'Berlin', + to: 'Hamburg', + length: '100', + timeOfArrival: '10:10', +}; +separateTimeOfArrival(routeInformation); +// => ["10:10", {from: "Berlin", to: "Hamburg", length: "100"}] +``` diff --git a/exercises/concept/train-driver/.docs/introduction.md b/exercises/concept/train-driver/.docs/introduction.md new file mode 100644 index 0000000000..53dbcfa7be --- /dev/null +++ b/exercises/concept/train-driver/.docs/introduction.md @@ -0,0 +1,88 @@ +# Introduction + +JavaScript has a built-in `...` operator that makes it easier to work with indefinite numbers of elements. Depending on the context, it's called either a _rest operator_ or _spread operator_. + +## Rest operator + +### Rest elements + +When `...` appears on the left-hand side of an assignment, those three dots are known as the `rest` operator. The three dots together with a variable name is called a rest element. It collects zero or more values, and stores them into a single array. + +```javascript +const [a, b, ...everythingElse] = [0, 1, 1, 2, 3, 5, 8]; +a; +// => 0 +b; +// => 1 +everythingElse; +// => [1, 2, 3, 5, 8] +``` + +Note that in JavaScript, unlike some other languages, a `rest` element cannot have a trailing comma. It _must_ be the last element in a destructuring assignment. The example below throws a `SyntaxError`: + +```javascript +const [...items, last] = [2, 4, 8, 16] +``` + +### Rest properties + +Similarly to arrays, the rest operator can also be used to collect one or more object properties and store them in a single object. + +```javascript +const { street, ...address } = { + street: 'Platz der Republik 1', + postalCode: '11011', + city: 'Berlin', +}; +street; +// => 'Platz der Republik 1' +address; +// => {postalCode: '11011', city: 'Berlin'} +``` + +## Rest parameters + +When `...` appears in a function definition next to its last argument, that parameter is called a _rest parameter_. It allows the function to accept an indefinite number of arguments as an array. + +```javascript +function concat(...strings) { + return strings.join(' '); +} +concat('one'); +// => 'one' +concat('one', 'two', 'three'); +// => 'one two three' +``` + +## Spread + +### Spread elements + +When `...` appears on the right-hand side of an assignment, it's known as the `spread` operator. It expands an array into a list of elements. Unlike the rest element, it can appear anywhere in an array literal expression, and there can be more than one. + +```javascript +const oneToFive = [1, 2, 3, 4, 5]; +const oneToTen = [...oneToFive, 6, 7, 8, 9, 10]; +oneToTen; +// => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +const woow = ['A', ...oneToFive, 'B', 'C', 'D', 'E', ...oneToFive, 42]; +woow; +// => ["A", 1, 2, 3, 4, 5, "B", "C", "D", "E", 1, 2, 3, 4, 5, 42] +``` + +### Spread properties + +Similarly to arrays, the spread operator can also be used to copy properties from one object to another. + +```javascript +let address = { + postalCode: '11011', + city: 'Berlin', +}; +address = { ...address, country: 'Germany' }; +// => { +// postalCode: '11011', +// city: 'Berlin', +// country: 'Germany', +// } +``` diff --git a/exercises/concept/train-driver/.gitignore b/exercises/concept/train-driver/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/concept/train-driver/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/concept/train-driver/.meta/config.json b/exercises/concept/train-driver/.meta/config.json new file mode 100644 index 0000000000..b771267146 --- /dev/null +++ b/exercises/concept/train-driver/.meta/config.json @@ -0,0 +1,27 @@ +{ + "authors": [ + "meatball133" + ], + "contributors": [ + "bethanyg", + "junedev" + ], + "files": { + "solution": [ + "train-driver.js" + ], + "test": [ + "train-driver.spec.js" + ], + "exemplar": [ + ".meta/exemplar.js" + ] + }, + "blurb": "Professionalize using rest and spread operators.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": true, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } +} diff --git a/exercises/concept/train-driver/.meta/design.md b/exercises/concept/train-driver/.meta/design.md new file mode 100644 index 0000000000..3d9bd201fd --- /dev/null +++ b/exercises/concept/train-driver/.meta/design.md @@ -0,0 +1,24 @@ +# Design + +## Learning objectives + +- Using spread to turn an array into a list of parameters +- Using rest elements to turn a list of parameters into an array +- Using spread to turn an extract value out of an object +- Using spread to combine objects +- Using rest to collect multiple parameters into an array + +## Out of scope + +- Default values + +## Concepts + +- `rest-and-spread` + +## Prerequisites + +- `arrays` are needed to understand array restructuring +- `functions` are needed as basis for rest parameters +- `objects` are needed for object spread etc. +- `array-destructuring` are needed to understand rest elements diff --git a/exercises/concept/train-driver/.meta/exemplar.js b/exercises/concept/train-driver/.meta/exemplar.js new file mode 100644 index 0000000000..716e020729 --- /dev/null +++ b/exercises/concept/train-driver/.meta/exemplar.js @@ -0,0 +1,59 @@ +// @ts-check +// +// The line above enables type checking for this file. Various IDEs interpret +// the @ts-check directive. It will give you helpful autocompletion when +// implementing this exercise. + +/** + * Return each wagon's id in form of an array. + * + * @param {number[]} ids + * @returns {number[]} wagon ids + */ +export function getListOfWagons(...ids) { + return ids; +} + +/** + * Reorder the array of wagons by moving the first 2 wagons to the end of the array. + * + * @param {Iterable} ids + * @returns {number[]} reordered list of wagons + */ +export function fixListOfWagons([first, second, ...rest]) { + return [...rest, first, second]; +} + +/** + * Fixes the array of wagons by inserting an array of wagons after the first element in eachWagonsID. + * + * @param {Iterable} ids + * @param {Iterable} missingWagons + * @returns {number[]} corrected list of wagons + */ +export function correctListOfWagons([first, ...rest], missingWagons) { + return [first, ...missingWagons, ...rest]; +} + +/** + * Extend route information by adding another object + * + * @param {Record} information + * @param {Record} additional + * @returns {Record} extended route information + */ +export function extendRouteInformation(information, additional) { + return { ...information, ...additional }; +} + +/** + * Separate arrival time from the route information object + * + * @param {Record} information + * @returns {[string, Record]} array with arrival time and object without arrival time + */ +export function separateTimeOfArrival(information) { + const { timeOfArrival, ...rest } = information; + + return [timeOfArrival, rest]; +} diff --git a/exercises/concept/train-driver/.npmrc b/exercises/concept/train-driver/.npmrc new file mode 100644 index 0000000000..d26df800bb --- /dev/null +++ b/exercises/concept/train-driver/.npmrc @@ -0,0 +1 @@ +audit=false diff --git a/exercises/concept/train-driver/LICENSE b/exercises/concept/train-driver/LICENSE new file mode 100644 index 0000000000..90e73be03b --- /dev/null +++ b/exercises/concept/train-driver/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Exercism + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/exercises/concept/train-driver/babel.config.js b/exercises/concept/train-driver/babel.config.js new file mode 100644 index 0000000000..a638497df1 --- /dev/null +++ b/exercises/concept/train-driver/babel.config.js @@ -0,0 +1,4 @@ +module.exports = { + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], +}; diff --git a/exercises/concept/train-driver/eslint.config.mjs b/exercises/concept/train-driver/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/concept/train-driver/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/concept/train-driver/jest.config.js b/exercises/concept/train-driver/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/concept/train-driver/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/concept/train-driver/package.json b/exercises/concept/train-driver/package.json new file mode 100644 index 0000000000..db170f0a11 --- /dev/null +++ b/exercises/concept/train-driver/package.json @@ -0,0 +1,34 @@ +{ + "name": "@exercism/javascript-train-driver", + "description": "Exercism concept exercise on rest and spread operators", + "author": "Meatball133", + "private": true, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/exercism/javascript", + "directory": "exercises/concept/train-driver" + }, + "devDependencies": { + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" + }, + "dependencies": {}, + "scripts": { + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" +} diff --git a/exercises/concept/train-driver/train-driver.js b/exercises/concept/train-driver/train-driver.js new file mode 100644 index 0000000000..b33bc4309f --- /dev/null +++ b/exercises/concept/train-driver/train-driver.js @@ -0,0 +1,57 @@ +// @ts-check +// +// The line above enables type checking for this file. Various IDEs interpret +// the @ts-check directive. It will give you helpful autocompletion when +// implementing this exercise. + +/** + * Return each wagon's id in form of an array. + * + * @param {...numbers} ids + * @returns {number[]} wagon ids + */ +export function getListOfWagons(a, b, c, d, e, f, g, h, i, j, k, l, m, n) { + throw new Error('Remove this line and implement the function'); +} + +/** + * Reorder the array of wagons by moving the first 2 wagons to the end of the array. + * + * @param {Iterable} ids + * @returns {number[]} reordered list of wagons + */ +export function fixListOfWagons(ids) { + throw new Error('Remove this line and implement the function'); +} + +/** + * Fixes the array of wagons by inserting an array of wagons after the first element in eachWagonsID. + * + * @param {Iterable} ids + * @param {Iterable} missingWagons + * @returns {number[]} corrected list of wagons + */ +export function correctListOfWagons(ids, missingWagons) { + throw new Error('Remove this line and implement the function'); +} + +/** + * Extend route information by adding another object + * + * @param {Record} information + * @param {Record} additional + * @returns {Record} extended route information + */ +export function extendRouteInformation(information, additional) { + throw new Error('Remove this line and implement the function'); +} + +/** + * Separate arrival time from the route information object + * + * @param {Record} information + * @returns {[string, Record]} array with arrival time and object without arrival time + */ +export function separateTimeOfArrival(information) { + throw new Error('Remove this line and implement the function'); +} diff --git a/exercises/concept/train-driver/train-driver.spec.js b/exercises/concept/train-driver/train-driver.spec.js new file mode 100644 index 0000000000..87f7a321fb --- /dev/null +++ b/exercises/concept/train-driver/train-driver.spec.js @@ -0,0 +1,251 @@ +import { describe, test, expect } from '@jest/globals'; + +import { + getListOfWagons, + fixListOfWagons, + correctListOfWagons, + extendRouteInformation, + separateTimeOfArrival, +} from './train-driver'; + +const customInspectSymbol = Symbol.for('nodejs.util.inspect.custom'); +const customLogSymbol = Symbol.for('exercism.javascript.util.log'); + +// Follow the instructions in case you are stuck on "list.method is not a function" +class LimitedArray { + constructor(values) { + this.values = values; + } + + // Enables rest syntax and spread operator, as wel as for of, etc. + [Symbol.iterator]() { + return this.values[Symbol.iterator](); + } + + // Log value in non-upgraded environments + toString() { + return this.values.toString(); + } + + // Overrides logging in node (ie. students working locally) + [customInspectSymbol](depth, inspectOptions, inspect) { + const inner = this.values[customInspectSymbol] + ? this.values[customInspectSymbol](depth, inspectOptions, inspect) + : this.values.toString(); + + return `List of (${inner})`; + } + + // Overrides log overrides in web environment (ie. students working in editor) + [customLogSymbol](depth, inspectOptions, inspect) { + const inner = this.values[customLogSymbol] + ? this.values[customLogSymbol](depth, inspectOptions, inspect) + : this.values.toString(); + + return `List of (${inner})`; + } +} + +function list(...values) { + return new LimitedArray(values); +} + +function time(timeOfArrival, route) { + Object.defineProperty(route, 'timeOfArrival', { + configurable: false, + writable: false, + enumerable: true, + value: timeOfArrival, + }); + + return route; +} + +describe('getListOfWagons', () => { + test('returns the correct array', () => { + expect(getListOfWagons(1, 5, 2, 7, 4)).toStrictEqual([1, 5, 2, 7, 4]); + }); + + test('works for a few arguments', () => { + expect(getListOfWagons(1, 5)).toStrictEqual([1, 5]); + }); + + test('works for a one argument', () => { + expect(getListOfWagons(1)).toStrictEqual([1]); + }); + + test('works for many arguments', () => { + expect(getListOfWagons(1, 5, 6, 3, 9, 8, 4, 14, 24, 7)).toStrictEqual([ + 1, 5, 6, 3, 9, 8, 4, 14, 24, 7, + ]); + }); +}); + +describe('fixListOfWagons', () => { + test('reorders the first 2 wagons to the end of the array', () => { + const eachWagonsID = list(3, 7, 1, 14, 10, 4, 12, 6, 23, 17, 13, 20, 8, 19); + const expected = [1, 14, 10, 4, 12, 6, 23, 17, 13, 20, 8, 19, 3, 7]; + + expect(fixListOfWagons(eachWagonsID)).toStrictEqual(expected); + }); + + test('works when only 3 wagons given', () => { + const eachWagonsID = list(4, 2, 1); + + expect(fixListOfWagons(eachWagonsID)).toStrictEqual([1, 4, 2]); + }); + + test('works for a few wagons', () => { + const eachWagonsID = list(3, 4, 1, 5, 7, 9, 10); + + expect(fixListOfWagons(eachWagonsID)).toStrictEqual([1, 5, 7, 9, 10, 3, 4]); + }); +}); + +describe('correctListOfWagons', () => { + test('returns a wagon weight list with the inserted array of values', () => { + const eachWagonsID = list(1, 6, 11, 15, 13, 14, 17, 22, 2, 16, 19, 21); + const missingWagons = list(8, 10, 5, 9, 3, 7, 20); + const expected = [ + 1, 8, 10, 5, 9, 3, 7, 20, 6, 11, 15, 13, 14, 17, 22, 2, 16, 19, 21, + ]; + + expect(correctListOfWagons(eachWagonsID, missingWagons)).toStrictEqual( + expected, + ); + }); + + test('works for short arrays', () => { + const eachWagonsID = list(1, 7, 15, 24); + const missingWagons = list(8, 6, 4); + const expected = [1, 8, 6, 4, 7, 15, 24]; + + expect(correctListOfWagons(eachWagonsID, missingWagons)).toStrictEqual( + expected, + ); + }); + + test('works when missingWagons is longer', () => { + const eachWagonsID = list(1, 7, 15, 24); + const missingWagons = list(8, 6, 4, 5, 9, 21, 2, 13); + const expected = [1, 8, 6, 4, 5, 9, 21, 2, 13, 7, 15, 24]; + + expect(correctListOfWagons(eachWagonsID, missingWagons)).toStrictEqual( + expected, + ); + }); +}); + +describe('extendRouteInformation', () => { + test('correctly extends route information', () => { + const route = { from: 'Berlin', to: 'Hamburg' }; + const moreRouteInformation = { + timeOfArrival: '12:00', + precipitation: '10', + temperature: '5', + }; + + const expected = { + from: 'Berlin', + to: 'Hamburg', + timeOfArrival: '12:00', + precipitation: '10', + temperature: '5', + }; + + expect(extendRouteInformation(route, moreRouteInformation)).toStrictEqual( + expected, + ); + }); + + test('works when not adding precipitation', () => { + const route = { from: 'Paris', to: 'London' }; + const moreRouteInformation = { timeOfArrival: '10:30', temperature: '20' }; + + const expected = { + from: 'Paris', + to: 'London', + timeOfArrival: '10:30', + temperature: '20', + }; + + expect(extendRouteInformation(route, moreRouteInformation)).toStrictEqual( + expected, + ); + }); + + test('works when written in different order', () => { + const route = { from: 'Gothenburg', to: 'Copenhagen' }; + const moreRouteInformation = { + precipitation: '1', + timeOfArrival: '21:20', + temperature: '-6', + }; + + const expected = { + from: 'Gothenburg', + to: 'Copenhagen', + precipitation: '1', + timeOfArrival: '21:20', + temperature: '-6', + }; + + expect(extendRouteInformation(route, moreRouteInformation)).toStrictEqual( + expected, + ); + }); +}); + +describe('separateTimeOfArrival', () => { + test('separates timeOfArrival from complete object', () => { + const route = time('12:00', { + from: 'Berlin', + to: 'Hamburg', + precipitation: '10', + temperature: '5', + }); + + const expected = [ + '12:00', + { from: 'Berlin', to: 'Hamburg', precipitation: '10', temperature: '5' }, + ]; + + expect(separateTimeOfArrival(route)).toStrictEqual(expected); + }); + + test('separates timeOfArrival with smaller object', () => { + const route = time('10:30', { + from: 'Paris', + to: 'London', + temperature: '20', + }); + + const expected = [ + '10:30', + { from: 'Paris', to: 'London', temperature: '20' }, + ]; + + expect(separateTimeOfArrival(route)).toStrictEqual(expected); + }); + + test('separates timeOfArrival from differently ordered object', () => { + const route = time('21:20', { + from: 'Gothenburg', + to: 'Copenhagen', + precipitation: '1', + temperature: '-6', + }); + + const expected = [ + '21:20', + { + from: 'Gothenburg', + to: 'Copenhagen', + precipitation: '1', + temperature: '-6', + }, + ]; + + expect(separateTimeOfArrival(route)).toStrictEqual(expected); + }); +}); diff --git a/exercises/concept/translation-service/.docs/instructions.md b/exercises/concept/translation-service/.docs/instructions.md index 755867a275..b1ed9b4dd1 100644 --- a/exercises/concept/translation-service/.docs/instructions.md +++ b/exercises/concept/translation-service/.docs/instructions.md @@ -1,61 +1,72 @@ # Instructions -In this exercise, you'll be providing a `TranslationService` where paid members have some quality assurance. +In this exercise, you'll be providing a `TranslationService` that provides basic translation services to free members, and advanced translation to premium members with quality assurances. -You have found an out-of-space translation API that can fulfill any translation _request_ in a reasonable amount of time, and you want to capitalize on this. +**The API** -**The API interface** +You have found an outer space translation API that fulfills any translation `request` in a reasonable amount of time. +You want to capitalize on this. +The space translators are extremely fickle and hate redundancy, so they also provide _API storage_ satellites where you can `fetch` past translations without bothering them. -The API has a very minimal interface: +**_Fetching a translation_** -**Fetching a translation** - -`api.fetch(text)` fetches the translation of `text`, returning two values: +`api.fetch(text)` fetches a translation of `text` from the _API storage_ and returns a `promise` that provides two values: - `translation`: the actual translation - `quality`: the quality expressed as a number -If there is no translation available (because it has not been requested yet, see below), the API throws a `NotAvailable` error. -An `Untranslatable` error is thrown if a piece of text is untranslatable. +If a translation is not found in the _API storage_, the API throws a `NotAvailable` error. +Translations can be added using the `api.request` method. +If 'text' is not translatable, the API throws an `Untranslatable` error. ```javascript api.fetch('jIyaj'); // => Promise({ resolved: 'I understand' }) ``` -**Requesting a translation** +**_Requesting a translation_** -Some translations are known in the future. -The API knows about these. -That's the difference between `NotAvailable` (will be available, but must be requested) and `Untranslatable` (will never be available). +Some translations are sure to exist, but haven't been added to the _API storage_ yet. That's the difference between `NotAvailable` ( not in storage, but can be requested ) and `Untranslatable` ( cannot be translated ). -`api.request(text, callback)` requests the translation of `text`, calling the `callback` once it's ready, without a value, only indicating that it is now available. +`api.request(text, callback)` requests that a translation of `text` be performed and added into the _API storage_. +On completion the `callback` function is called. -> This API is _unstable_, which means that sometimes the API will fail and call the `callback` with an error. -> If that happens, it is okay to re-request. +- On success `callback` is passed `undefined`: this indicates the translation was successful and is accessible using the `api.fetch` method. +- On failure `callback` is passed an `error`: this indicates something went wrong. + The outspace API is _unstable_, which means that the API fails often. + If that happens, it is okay to `api.request` again. ```javascript -api.request('majQa’'); -// => Promise({ resolved: undefined }) +api.request('majQa’', callback); +// => undefined +// +// later: the passed callback is called with undefined +// because it was successful. ``` **⚠ Warning! ⚠** -```exercism/caution + +~~~~exercism/caution The API works its magic by teleporting in the various translators when a `request` comes in. This is a very costly action, so it shouldn't be called when a translation *is* available. Unfortunately, not everyone reads the manual, so there is a system in place to kick-out bad actors. If an `api.request` is called for `text` is available, the API throws an `AbusiveClientError` for this call, **and every call after that**. Ensure that you *never* request a translation if something has already been translated. -``` +~~~~ + ## 1. Fetch a translation, ignoring the quality -Implement a function `free(text)` to fetch a translation, ignoring the quality, and forwarding any errors thrown by the API: +The free service only provides translations that are currently in the _API storage_. + +Implement a method `free(text)` that provides free members with translation that already exist in the _API storage_. +Ignore the quality and forward any errors thrown by the API. - Returns the translation if it can be retrieved, regardless of its quality - Forwards any error from the translation API +- Uses the `api.fetch` method (`api.fetch` returns a `promise`) ```javascript service.free('jIyaj'); @@ -67,7 +78,7 @@ service.free("jIyajbe'"); ## 2. Fetch a batch of translations, all-or-nothing -Implement a function `batch([text, text, ...])` that translates the given texts using the free service, returning all the translations, or a single error. +Implement a method `batch([text, text, ...])` for free members that translates an array of text using the free service, returning all the translations, or a single error. - Resolves with all the translations (in the same order), if they are all available - Rejects with the first error that is encountered @@ -86,11 +97,13 @@ service.batch([]); ## 3. Request a translation, retrying at most 2 times -Implement a function `request(text)` that _requests_ a translation, with automatic retries, up to a total of **3 calls** for the same request. +Implement a premium user method `request(text)`, that _requests_ a translation be added to the _API storage_. +The request should automatically retry if a failure occurs. +It should perform no more than **3 calls** for the same request (_don't upset the space translators!!!_). - If `api.request` does not return an error, resolve with `undefined` - If `api.request` returns an error, retry at most two times -- If you're out of retires, reject with the last error received +- If you run out of retries, reject with the last error received ```javascript service.request("jIyajbe'"); @@ -99,12 +112,13 @@ service.request("jIyajbe'"); ## 4. Fetch a translation, inspect the quality, or request it -Implement the function `premium(text, quality)` for premium users, which fetches a translation, request it if it's not available, and only returns it if it meets a certain threshold. +Implement a premium user method `premium(text, quality)` to fetch a translation. +If a translation is `NotAvailable`, request the translation and fetch it after its been added to the _API storage_. +The method should only return the translation if it meets a certain `quality` threshold. - If `api.fetch` resolves, check the quality before resolving -- If `api.fetch` rejects with `NotAvailable`, _request_ the translation instead -- If `api.fetch` rejects with `Untranslatable`, forward the error -- If _requesting_ rejects, forward the error +- If `api.fetch` rejects, _request_ the translation instead +- If `api.request` rejects, forward the error ```javascript service.premium("jIyajbe'", 100); @@ -119,6 +133,8 @@ service.premium("'arlogh Qoylu'pu'?", 40); **N.B.** -```exercism/note + +~~~~exercism/note The correct translation of `'arlogh Qoylu'pu'?` is **How many times has it been heard?**. -``` +~~~~ + diff --git a/exercises/concept/translation-service/.docs/introduction.md b/exercises/concept/translation-service/.docs/introduction.md index b3ea8220ba..427a71173c 100644 --- a/exercises/concept/translation-service/.docs/introduction.md +++ b/exercises/concept/translation-service/.docs/introduction.md @@ -1,102 +1,271 @@ # Introduction -The [`Promise`][promise-docs] object represents the eventual completion (or failure) of an -asynchronous operation and its resulting value. +The [`Promise`][promise-docs] object represents the eventual completion (or failure) of an asynchronous operation and its resulting value. -The methods [`promise.then()`][promise-then], [`promise.catch()`][promise-catch], and [`promise.finally()`][promise-finally] are used to associate further action with a promise that becomes settled. + +~~~exercism/note +This is a hard topic for many people, specially if you know programming in a language that is completely _synchronous_. +If you feel overwhelmed, or you would like to learn more about **concurrency** and **parallelism**, [watch (via go.dev)][talk-blog] or [watch directly via vimeo][talk-video] and [read the slides][talk-slides] of the brilliant talk "Concurrency is not parallelism". -For example: +[talk-slides]: https://go.dev/talks/2012/waza.slide#1 +[talk-blog]: https://go.dev/blog/waza-talk +[talk-video]: https://vimeo.com/49718712 +~~~ + +## Lifecycle of a promise + +A `Promise` has three states: + +1. pending +2. fulfilled +3. rejected + +When it is created, a promise is pending. +At some point in the future it may _resolve_ or _reject_. +Once a promise is resolved or rejected once, it can never be resolved or rejected again, nor can its state change. + +In other words: + +1. When pending, a promise: + - may transition to either the fulfilled or rejected state. +2. When fulfilled, a promise: + - must not transition to any other state. + - must have a value, which must not change. +3. When rejected, a promise: + - must not transition to any other state. + - must have a reason, which must not change. + +## Resolving a promise + +A promise may be resolved in various ways: ```javascript -const myPromise = new Promise(function (resolve, reject) { - let sampleData = [2, 4, 6, 8]; - let randomNumber = Math.ceil(Math.random() * 5); - if (sampleData[randomNumber]) { - resolve(sampleData[randomNumber]); - } else { - reject('An error occured!'); - } +// Creates a promise that is immediately resolved +Promise.resolve(value); + +// Creates a promise that is immediately resolved +new Promise((resolve) => { + resolve(value); }); -myPromise - .then(function (e) { - console.log(e); - }) - .catch(function (error) { - throw new Error(error); - }) - .finally(function () { - console.log('Promise completed'); - }); +// Chaining a promise leads to a resolved promise +somePromise.then(() => { + // ... + return value; +}); +``` + +In the examples above `value` can be _anything_, including an error, `undefined`, `null` or another promise. +Usually you want to resolve with a value that's not an error. + +## Rejecting a promise + +A promise may be rejected in various ways: + +```javascript +// Creates a promise that is immediately rejected +Promise.reject(reason) + +// Creates a promise that is immediately rejected +new Promise((_, reject) { + reject(reason) +}) + +// Chaining a promise with an error leads to a rejected promise +somePromise.then(() => { + // ... + throw reason +}) ``` -## Methods +In the examples above `reason` can be _anything_, including an error, `undefined` or `null`. +Usually you want to reject with an error. -These methods are available on `Promise.prototype` +## Chaining a promise -**then** +A promise may be _continued_ with a future action once it resolves or rejects. -> The `.then()` method takes up to two arguments; the first argument is a callback function for the resolved case of the promise, and the second argument is a callback function for the rejected case. Each `.then()` returns a newly generated promise object, which can optionally be used for chaining.[^1] +- [`promise.then()`][promise-then] is called once `promise` resolves +- [`promise.catch()`][promise-catch] is called once `promise` rejects +- [`promise.finally()`][promise-finally] is called once `promise` either resolves or rejects + +### **then** + +Every promise is "thenable". +That means that there is a function `then` available that will be executed once the original promise is resolves. +Given `promise.then(onResolved)`, the callback `onResolved` receives the value the original promise was resolved with. +This will always return a _new_ "chained" promise. + +Returning a `value` from `then` resolves the "chained" promise. +Throwing a `reason` in `then` rejects the "chained" promise. ```javascript const promise1 = new Promise(function (resolve, reject) { - resolve('Success!'); + setTimeout(() => { + resolve('Success!'); + }, 1000); }); -promise1.then(function (value) { +const promise2 = promise1.then(function (value) { console.log(value); // expected output: "Success!" + + return true; }); ``` -**catch** +This will log `"Success!"` after approximately 1000 ms. +The state & value of `promise1` will be `resolved` and `"Success!"`. +The state & value of `promise2` will be `resolved` and `true`. -> A `.catch()` is just a `.then()` without a slot for a callback function for the case when the promise is resolved. It is used to handle rejected promises.[^2] +There is a second argument available that runs when the original promise rejects. +Given `promise.then(onResolved, onRejected)`, the callback `onResolved` receives the value the original promise was resolved with, or the callback `onRejected` receives the reason the promise was rejected. ```javascript -const promise1 = new Promise((resolve, reject) => { - throw 'An error occured'; -}); +const promise1 = new Promise(function (resolve, reject) { + setTimeout(() => { + resolve('Success!'); + }, 1000); -promise1.catch(function (error) { - console.error(error); + if (Math.random() < 0.5) { + reject('Nope!'); + } }); -// expected output: An error occured + +function log(value) { + console.log(value); + return true; +} + +function shout(reason) { + console.error(reason.toUpperCase()); + return false; +} + +const promise2 = promise1.then(log, shout); ``` -**finally** +- In about 1/2 of the cases, this will log `"Success!"` after approximately 1000 ms. + - The state & value of `promise1` will be `resolved` and `"Success!"`. + - The state & value of `promise2` will be `resolved` and `true`. +- In about 1/2 of the cases, this will immediately log `"NOPE!"`. + - The state & value of `promise1` will be `rejected` and `Nope!`. + - The state & value of `promise2` will be `resolved` and `false`. + +It is important to understand that because of the rules of the lifecycle, when it `reject`s, the `resolve` that comes in ~1000ms later is silently ignored, as the internal state cannot change once it has rejected or resolved. +It is important to understand that returning a value from a promise resolves it, and throwing a value rejects it. +When `promise1` resolves and there is a chained `onResolved`: `then(onResolved)`, then that follow-up is a new promise that can resolve or reject. +When `promise1` rejects but there is a chained `onRejected`: `then(, onRejected)`, then that follow-up is a new promise that can resolve or reject. -> When the promise is settled, i.e either fulfilled or rejected, the specified callback function is executed. This provides a way for code to be run whether the promise was fulfilled successfully or rejected once the Promise has been dealt with.[^3] +### **catch** + +Sometimes you want to capture errors and only continue when the original promise `reject`s. +Given `promise.catch(onCatch)`, the callback `onCatch` receives the reason the original promise was rejected. +This will always return a _new_ "chained" promise. + +Returning a `value` from `catch` resolves the "chained" promise. +Throwing a `reason` in `catch` rejects the "chained" promise. ```javascript -function findDataById(id) { - return new Promise(function (resolve, reject) { - let sampleData = [1, 2, 3, 4, 5]; - if (sampleData[id]) { - resolve(sampleData[id]); - } else { - reject(new Error('Invalid id')); - } - }); +const promise1 = new Promise(function (resolve, reject) { + setTimeout(() => { + resolve('Success!'); + }, 1000); + + if (Math.random() < 0.5) { + reject('Nope!'); + } +}); + +function log(value) { + console.log(value); + return 'done'; } -findDataById(4) - .then(function (response) { - console.log(response); +function recover(reason) { + console.error(reason.toUpperCase()); + return 42; +} + +const promise2 = promise1.catch(recover).then(log); +``` + +In about 1/2 of the cases, this will log `"Success!"` after approximately 1000 ms. +In the other 1/2 of the cases, this will immediately log `42`. + +- If `promise1` resolves, `catch` is skipped and it reaches `then`, and logs the value. + - The state & value of `promise1` will be `resolved` and `"Success!"`. + - The state & value of `promise2` will be `resolved` and `"done"`; +- If `promise1` rejects, `catch` is executed, which _returns a value_, and thus the chain is now `resolved`, and it reaches `then`, and logs the value. + - The state & value of `promise1` will be `rejected` and `"Nope!"`. + - The state & value of `promise2` will be `resolved` and `"done"`; + +### **finally** + +Sometimes you want to execute code after a promise settles, regardless if the promise resolves or rejects. +Given `promise.finally(onSettled)`, the callback `onSettled` receives nothing. +This will always return a _new_ "chained" promise. + +Returning a `value` from `finally` copies the status & value from the original promise, ignoring the `value`. +Throwing a `reason` in `finally` rejects the "chained" promise, overwriting any status & value or reason from the original promise. + +## Example + +Various of the methods together: + +```javascript +const myPromise = new Promise(function (resolve, reject) { + const sampleData = [2, 4, 6, 8]; + const randomNumber = Math.round(Math.random() * 5); + + if (sampleData[randomNumber]) { + resolve(sampleData[randomNumber]); + } else { + reject('Sampling did not result in a sample'); + } +}); + +const finalPromise = myPromise + .then(function (sampled) { + // If the random number was 0, 1, 2, or 3, this will be + // reached and the number 2, 4, 6, or 8 will be logged. + console.log(`Sampled data: ${sampled}`); + return 'yay'; }) - .catch(function (err) { - console.error(err); + .catch(function (reason) { + // If the random number was 4 or 5, this will be reached and + // reason will be "An error occurred". The entire chain will + // then reject with an Error with the reason as message. + throw new Error(reason); }) .finally(function () { + // This will always log after either the sampled data is + // logged or the error is raised. console.log('Promise completed'); }); ``` ---- +- In the cases `randomNumber` is `0-3`: + - `myPromise` will be resolved with the value `2, 4, 6, or 8` + - `finalPromise` will be resolved with the value `'yay'` + - There will be two logs: + - `Sampled data: ...` + - `Promise completed` +- In the cases `randomNumber` is `4-5`: + - `myPromise` will be rejected with the reason `'Sampling did not result in a sample'` + - `finalPromise` will be rejected with the reason `Error('Sampling did not result in a sample')` + - There will be one log: + - `Promise completed` + - _in some environments_ this will yield an `"uncaught rejected promise: Error('Sampling did not result in a sample')"` log + +As shown above, `reject` works with a string, and a promise can also reject with an `Error`. + + +~~~exercism/note +If chaining promises or general usage is unclear, the [tutorial on MDN][mdn-promises] is a good resource to consume. -[^1]: `then`, MDN. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then -[^2]: `catch`, MDN. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch -[^3]: `finally`, MDN. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally +[mdn-promises]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises +~~~ [promise-docs]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise [promise-catch]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch diff --git a/exercises/concept/translation-service/.eslintignore b/exercises/concept/translation-service/.eslintignore deleted file mode 100644 index 925cb98e75..0000000000 --- a/exercises/concept/translation-service/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -/global.d.ts -/env.d.ts diff --git a/exercises/concept/translation-service/.eslintrc b/exercises/concept/translation-service/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/concept/translation-service/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/concept/translation-service/.gitignore b/exercises/concept/translation-service/.gitignore index bdb912f98a..0c88ff6ec3 100644 --- a/exercises/concept/translation-service/.gitignore +++ b/exercises/concept/translation-service/.gitignore @@ -1,3 +1,5 @@ -node_modules -yarn-error.log - +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/concept/translation-service/.meta/config.json b/exercises/concept/translation-service/.meta/config.json index 45fe9a3230..694c26f511 100644 --- a/exercises/concept/translation-service/.meta/config.json +++ b/exercises/concept/translation-service/.meta/config.json @@ -1,12 +1,32 @@ { - "blurb": "Connect to the Klingon Translation Service and learn about promises.", - "authors": ["SleeplessByte"], - "contributors": ["AndrewLawendy"], + "authors": [ + "SleeplessByte" + ], + "contributors": [ + "AndrewLawendy", + "themetar" + ], "files": { - "solution": ["service.js"], - "test": ["service.spec.js"], - "exemplar": [".meta/exemplar.js"], - "editor": ["api.js", "errors.js", "global.d.ts"] + "solution": [ + "service.js" + ], + "test": [ + "service.spec.js" + ], + "exemplar": [ + ".meta/exemplar.js" + ], + "editor": [ + "api.js", + "errors.js", + "global.d.ts" + ] }, - "forked_from": [] + "blurb": "Connect to the Klingon Translation Service and learn about promises.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": true, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/concept/translation-service/.meta/exemplar.alternative.js b/exercises/concept/translation-service/.meta/exemplar.alternative.js index 37816be861..1a184def5e 100644 --- a/exercises/concept/translation-service/.meta/exemplar.alternative.js +++ b/exercises/concept/translation-service/.meta/exemplar.alternative.js @@ -80,11 +80,13 @@ export class TranslationService { */ async request(text, attempt = 1) { try { - await new Promise((resolve, reject) => { - this.api.request(text, (err) => { - err ? reject(err) : resolve(); - }); - }); + await /** @type {Promise} */ ( + new Promise((resolve, reject) => { + this.api.request(text, (err) => { + err ? reject(err) : resolve(); + }); + }) + ); } catch (err) { if (attempt === 3) { throw err; @@ -100,7 +102,7 @@ export class QualityThresholdNotMet extends Error { super( ` The translation of ${text} does not meet the requested quality threshold. - `.trim() + `.trim(), ); } } @@ -110,7 +112,7 @@ export class BatchIsEmpty extends Error { super( ` Requested a batch translation, but there are no texts in the batch. - `.trim() + `.trim(), ); } } diff --git a/exercises/concept/translation-service/.meta/exemplar.js b/exercises/concept/translation-service/.meta/exemplar.js index b8a372b28a..6219de5e24 100644 --- a/exercises/concept/translation-service/.meta/exemplar.js +++ b/exercises/concept/translation-service/.meta/exemplar.js @@ -96,7 +96,7 @@ export class QualityThresholdNotMet extends Error { */ constructor(text) { super( - `The translation of ${text} does not meet the requested quality threshold.` + `The translation of ${text} does not meet the requested quality threshold.`, ); this.text = text; @@ -110,7 +110,7 @@ export class QualityThresholdNotMet extends Error { export class BatchIsEmpty extends Error { constructor() { super( - `Requested a batch translation, but there are no texts in the batch.` + `Requested a batch translation, but there are no texts in the batch.`, ); } } diff --git a/exercises/concept/translation-service/api.js b/exercises/concept/translation-service/api.js index de99e74704..a4023a23bc 100644 --- a/exercises/concept/translation-service/api.js +++ b/exercises/concept/translation-service/api.js @@ -1,4 +1,9 @@ -import { AbusiveClientError, NotAvailable, Untranslatable } from './errors'; +import { + AbusiveClientError, + NotAvailable, + Untranslatable, + ConnectionError, +} from './errors'; const mutex = { current: false }; @@ -39,6 +44,12 @@ export class ExternalApi { * @returns {Promise} */ fetch(text) { + if (typeof text !== 'string') { + throw new BadRequest( + `Expected text when calling fetch(text), actual ${typeof text}.`, + ); + } + // Check if client is banned if (mutex.current) { return rejectWithRandomDelay(new AbusiveClientError()); @@ -60,6 +71,18 @@ export class ExternalApi { * @param {(err?: Error) => void} callback */ request(text, callback) { + if (typeof text !== 'string') { + throw new BadRequest( + `Expected string text when calling request(text, callback), actual ${typeof text}.`, + ); + } + + if (typeof callback !== 'function') { + throw new BadRequest( + `Expected callback function when calling request(text, callback), actual ${typeof callback}.`, + ); + } + if (this.values[text] && this.values[text][0]) { mutex.current = true; callback(new AbusiveClientError()); @@ -67,13 +90,12 @@ export class ExternalApi { } if (this.values[text]) { - this.values[text].shift(); + setTimeout(() => { + this.values[text].shift(); - // If it's now available, yay, otherwise, nay - setTimeout( - () => callback(this.values[text][0] ? undefined : makeRandomError()), - 1 - ); + // If it's now available, yay, otherwise, nay + callback(this.values[text][0] ? undefined : makeRandomError()); + }, 1); return; } @@ -96,5 +118,11 @@ function rejectWithRandomDelay(value) { } function makeRandomError() { - return new Error(`Error code ${Math.ceil(Math.random() * 10000)}`); + return new ConnectionError(`Error code ${Math.ceil(Math.random() * 10000)}`); +} + +class BadRequest extends Error { + constructor(message) { + super(message); + } } diff --git a/exercises/concept/translation-service/babel.config.js b/exercises/concept/translation-service/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/concept/translation-service/babel.config.js +++ b/exercises/concept/translation-service/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/concept/translation-service/errors.js b/exercises/concept/translation-service/errors.js index 7c98b921a4..a3ed001521 100644 --- a/exercises/concept/translation-service/errors.js +++ b/exercises/concept/translation-service/errors.js @@ -3,7 +3,7 @@ export class NotAvailable extends Error { super( ` The requested text "${text}" has not been translated yet. - `.trim() + `.trim(), ); } } @@ -15,7 +15,7 @@ export class AbusiveClientError extends Error { Your client has been rejected because of abusive behaviour. naDevvo’ yIghoS! - `.trim() + `.trim(), ); } } @@ -25,3 +25,9 @@ export class Untranslatable extends Error { super('jIyajbe’'); } } + +export class ConnectionError extends Error { + constructor(message) { + super(message); + } +} diff --git a/exercises/concept/translation-service/eslint.config.mjs b/exercises/concept/translation-service/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/concept/translation-service/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/concept/translation-service/global.d.ts b/exercises/concept/translation-service/global.d.ts index 5276467907..a3dfdeef46 100644 --- a/exercises/concept/translation-service/global.d.ts +++ b/exercises/concept/translation-service/global.d.ts @@ -17,5 +17,5 @@ interface Translation { type fetchTranslation = (text: string) => Promise; type requestTranslation = ( text: string, - callback: (err?: Error) => void + callback: (err?: Error) => void, ) => void; diff --git a/exercises/concept/translation-service/jest.config.js b/exercises/concept/translation-service/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/concept/translation-service/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/concept/translation-service/package.json b/exercises/concept/translation-service/package.json index f69135ee66..7ae392644d 100644 --- a/exercises/concept/translation-service/package.json +++ b/exercises/concept/translation-service/package.json @@ -10,22 +10,25 @@ "directory": "exercises/concept/translation-service" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/concept/translation-service/service.js b/exercises/concept/translation-service/service.js index d1ee3b1c79..3320abef61 100644 --- a/exercises/concept/translation-service/service.js +++ b/exercises/concept/translation-service/service.js @@ -84,7 +84,7 @@ export class QualityThresholdNotMet extends Error { super( ` The translation of ${text} does not meet the requested quality threshold. - `.trim() + `.trim(), ); this.text = text; @@ -100,7 +100,7 @@ export class BatchIsEmpty extends Error { super( ` Requested a batch translation, but there are no texts in the batch. - `.trim() + `.trim(), ); } } diff --git a/exercises/concept/translation-service/service.spec.js b/exercises/concept/translation-service/service.spec.js index 02ffdf8699..0514cb2b75 100644 --- a/exercises/concept/translation-service/service.spec.js +++ b/exercises/concept/translation-service/service.spec.js @@ -1,13 +1,12 @@ -// @ts-check - +import { beforeEach, describe, expect, test } from '@jest/globals'; import { - TranslationService, - QualityThresholdNotMet, BatchIsEmpty, + QualityThresholdNotMet, + TranslationService, } from './service'; -import { NotAvailable, Untranslatable } from './errors'; import { ExternalApi } from './api'; +import { ConnectionError, NotAvailable, Untranslatable } from './errors'; describe('Free service', () => { /** @type {TranslationService} */ @@ -134,7 +133,7 @@ describe('Request service', () => { test('it requests at most three times (does not retry thrice or more)', async () => { const actual = service.request('ghobe’'); - await expect(actual).rejects.toThrow(Error); + await expect(actual).rejects.toThrow(ConnectionError); }); }); @@ -182,10 +181,17 @@ describe('Premium service', () => { test('it requests at most three times (does not retry thrice or more)', async () => { const actual = service.premium('ghobe’', 0); - await expect(actual).rejects.toThrow(Error); + await expect(actual).rejects.toThrow(ConnectionError); + }); + + test('it recognizes sufficient quality', async () => { + const actual = service.premium('‘arlogh Qoylu’pu’?', 40); + const expected = 'What time is it?'; + + await expect(actual).resolves.toBe(expected); }); - test('it ensures the quality of the translation', async () => { + test('it recognizes insufficient quality', async () => { const actual = service.premium('majQa’', 100); const expected = QualityThresholdNotMet; @@ -193,11 +199,6 @@ describe('Premium service', () => { }); test('it ensures the quality even after a request', async () => { - const actual = service.premium('‘arlogh Qoylu’pu’?', 40); - const expected = 'What time is it?'; - - await expect(actual).resolves.toBe(expected); - const actualQuality = service.premium('‘arlogh Qoylu’pu’?', 100); const expectedQuality = QualityThresholdNotMet; await expect(actualQuality).rejects.toThrow(expectedQuality); diff --git a/exercises/concept/vehicle-purchase/.docs/hints.md b/exercises/concept/vehicle-purchase/.docs/hints.md index 7aad9c2126..a57dd002bd 100644 --- a/exercises/concept/vehicle-purchase/.docs/hints.md +++ b/exercises/concept/vehicle-purchase/.docs/hints.md @@ -1,6 +1,6 @@ # Hints -## 1. Determine if you will need a drivers licence +## 1. Determine if you will need a drivers license - Use the [strict equals operator][mdn-equality-operators] to check whether your input equals a certain string. - Use one of the two [logical operators][mdn-logical-operators] you learned about in the boolean concept to combine the two requirements. diff --git a/exercises/concept/vehicle-purchase/.docs/instructions.md b/exercises/concept/vehicle-purchase/.docs/instructions.md index 769dd2d957..b0dd514a3e 100644 --- a/exercises/concept/vehicle-purchase/.docs/instructions.md +++ b/exercises/concept/vehicle-purchase/.docs/instructions.md @@ -2,9 +2,9 @@ In this exercise, you will write some code to help you prepare to buy a vehicle. -You have three tasks, one to determine if you will need to get a licence, one to help you choose between two vehicles and one to estimate the acceptable price for a used vehicle. +You have three tasks, one to determine if you will need to get a license, one to help you choose between two vehicles and one to estimate the acceptable price for a used vehicle. -## 1. Determine if you will need a drivers licence +## 1. Determine if you will need a drivers license Some kinds of vehicles require a drivers license to operate them. Assume only the kinds `'car'` and `'truck'` require a license, everything else can be operated without a license. @@ -41,7 +41,7 @@ For a rough estimate, assume if the vehicle is less than 3 years old, it costs 8 If it is more than 10 years old, it costs 50%. If the vehicle is at least 3 years old but not older than 10 years, it costs 70% of the original price. -Implement the `calculateResellPrice(originalPrice, age)` function that applies this logic using `if`, `else if` and `else` (there are other ways but you want to practice). +Implement the `calculateResellPrice(originalPrice, age)` function that applies this logic using `if`, `else if` and `else` (there are other ways if you want to practice). It takes the original price and the age of the vehicle as arguments and returns the estimated price in the dealership. ```javascript diff --git a/exercises/concept/vehicle-purchase/.eslintrc b/exercises/concept/vehicle-purchase/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/concept/vehicle-purchase/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/concept/vehicle-purchase/.gitignore b/exercises/concept/vehicle-purchase/.gitignore index bdb912f98a..0c88ff6ec3 100644 --- a/exercises/concept/vehicle-purchase/.gitignore +++ b/exercises/concept/vehicle-purchase/.gitignore @@ -1,3 +1,5 @@ -node_modules -yarn-error.log - +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/concept/vehicle-purchase/.meta/config.json b/exercises/concept/vehicle-purchase/.meta/config.json index 05135d81e7..f4753be0f7 100644 --- a/exercises/concept/vehicle-purchase/.meta/config.json +++ b/exercises/concept/vehicle-purchase/.meta/config.json @@ -1,11 +1,29 @@ { - "blurb": "Learn about comparison and conditionals while preparing for your next vehicle purchase", - "authors": ["junedev"], - "contributors": [], + "authors": [ + "junedev" + ], + "contributors": [ + "SleeplessByte" + ], "files": { - "solution": ["vehicle-purchase.js"], - "test": ["vehicle-purchase.spec.js"], - "exemplar": [".meta/exemplar.js"] + "solution": [ + "vehicle-purchase.js" + ], + "test": [ + "vehicle-purchase.spec.js" + ], + "exemplar": [ + ".meta/exemplar.js" + ] }, - "forked_from": ["julia/vehicle-purchase"] + "forked_from": [ + "julia/vehicle-purchase" + ], + "blurb": "Learn about comparison and conditionals while preparing for your next vehicle purchase", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": true, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/concept/vehicle-purchase/.meta/exemplar.js b/exercises/concept/vehicle-purchase/.meta/exemplar.js index 00aaf1dbae..69b3abf8c2 100644 --- a/exercises/concept/vehicle-purchase/.meta/exemplar.js +++ b/exercises/concept/vehicle-purchase/.meta/exemplar.js @@ -5,7 +5,7 @@ // implementing this exercise. /** - * Determines whether or not you need a licence to operate a certain kind of vehicle. + * Determines whether or not you need a license to operate a certain kind of vehicle. * * @param {string} kind * @returns {boolean} whether a license is required @@ -39,7 +39,7 @@ export function chooseVehicle(option1, option2) { * * @param {number} originalPrice * @param {number} age - * @returns expected resell price in the dealership + * @returns {number} expected resell price in the dealership */ export function calculateResellPrice(originalPrice, age) { let percentage; diff --git a/exercises/concept/vehicle-purchase/babel.config.js b/exercises/concept/vehicle-purchase/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/concept/vehicle-purchase/babel.config.js +++ b/exercises/concept/vehicle-purchase/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/concept/vehicle-purchase/eslint.config.mjs b/exercises/concept/vehicle-purchase/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/concept/vehicle-purchase/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/concept/vehicle-purchase/jest.config.js b/exercises/concept/vehicle-purchase/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/concept/vehicle-purchase/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/concept/vehicle-purchase/package.json b/exercises/concept/vehicle-purchase/package.json index 9175a198fe..d5601f238f 100644 --- a/exercises/concept/vehicle-purchase/package.json +++ b/exercises/concept/vehicle-purchase/package.json @@ -10,22 +10,25 @@ "directory": "exercises/concept/vehicle-purchase" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/concept/vehicle-purchase/vehicle-purchase.js b/exercises/concept/vehicle-purchase/vehicle-purchase.js index 8f80a66efb..aa414d4d38 100644 --- a/exercises/concept/vehicle-purchase/vehicle-purchase.js +++ b/exercises/concept/vehicle-purchase/vehicle-purchase.js @@ -5,13 +5,13 @@ // implementing this exercise. /** - * Determines whether or not you need a licence to operate a certain kind of vehicle. + * Determines whether or not you need a license to operate a certain kind of vehicle. * * @param {string} kind * @returns {boolean} whether a license is required */ export function needsLicense(kind) { - throw new Error('Please implement the needsLicense function'); + throw new Error('Remove this line and implement the function'); } /** @@ -23,7 +23,7 @@ export function needsLicense(kind) { * @returns {string} a sentence of advice which option to choose */ export function chooseVehicle(option1, option2) { - throw new Error('Please implement the chooseVehicle function'); + throw new Error('Remove this line and implement the function'); } /** @@ -32,8 +32,8 @@ export function chooseVehicle(option1, option2) { * * @param {number} originalPrice * @param {number} age - * @returns expected resell price in the dealership + * @returns {number} expected resell price in the dealership */ export function calculateResellPrice(originalPrice, age) { - throw new Error('Please implement the calculateResellPrice function'); + throw new Error('Remove this line and implement the function'); } diff --git a/exercises/concept/vehicle-purchase/vehicle-purchase.spec.js b/exercises/concept/vehicle-purchase/vehicle-purchase.spec.js index d14ca5e960..e38b916e6c 100644 --- a/exercises/concept/vehicle-purchase/vehicle-purchase.spec.js +++ b/exercises/concept/vehicle-purchase/vehicle-purchase.spec.js @@ -1,74 +1,73 @@ +import { describe, expect, test } from '@jest/globals'; import { - needsLicense, - chooseVehicle, calculateResellPrice, + chooseVehicle, + needsLicense, } from './vehicle-purchase'; -describe('vehicle purchase', () => { - describe('needsLicense', () => { - test('requires a license for a car', () => { - expect(needsLicense('car')).toBe(true); - }); +describe('needsLicense', () => { + test('requires a license for a car', () => { + expect(needsLicense('car')).toBe(true); + }); - test('requires a license for a truck', () => { - expect(needsLicense('truck')).toBe(true); - }); + test('requires a license for a truck', () => { + expect(needsLicense('truck')).toBe(true); + }); - test('does not require a license for a bike', () => { - expect(needsLicense('bike')).toBe(false); - }); + test('does not require a license for a bike', () => { + expect(needsLicense('bike')).toBe(false); + }); - test('does not require a license for a stroller', () => { - expect(needsLicense('stroller')).toBe(false); - }); + test('does not require a license for a stroller', () => { + expect(needsLicense('stroller')).toBe(false); + }); - test('does not require a license for an e-scooter', () => { - expect(needsLicense('e-scooter')).toBe(false); - }); + test('does not require a license for an e-scooter', () => { + expect(needsLicense('e-scooter')).toBe(false); }); +}); - describe('chooseVehicle', () => { - const rest = ' is clearly the better choice.'; +describe('chooseVehicle', () => { + const rest = ' is clearly the better choice.'; - test('correctly recommends the first option', () => { - expect(chooseVehicle('Bugatti Veyron', 'Ford Pinto')).toBe( - 'Bugatti Veyron' + rest - ); - expect(chooseVehicle('Chery EQ', 'Kia Niro Elektro')).toBe( - 'Chery EQ' + rest - ); - }); + test('correctly recommends the first option', () => { + expect(chooseVehicle('Bugatti Veyron', 'Ford Pinto')).toBe( + 'Bugatti Veyron' + rest, + ); + expect(chooseVehicle('Chery EQ', 'Kia Niro Elektro')).toBe( + 'Chery EQ' + rest, + ); + }); - test('correctly recommends the second option', () => { - expect(chooseVehicle('Ford Pinto', 'Bugatti Veyron')).toBe( - 'Bugatti Veyron' + rest - ); - expect(chooseVehicle('2020 Gazelle Medeo', '2018 Bergamont City')).toBe( - '2018 Bergamont City' + rest - ); - }); + test('correctly recommends the second option', () => { + expect(chooseVehicle('Ford Pinto', 'Bugatti Veyron')).toBe( + 'Bugatti Veyron' + rest, + ); + expect(chooseVehicle('2020 Gazelle Medeo', '2018 Bergamont City')).toBe( + '2018 Bergamont City' + rest, + ); }); +}); - describe('calculateResellPrice', () => { - test('price is reduced to 80% for age below 3', () => { - expect(calculateResellPrice(40000, 2)).toBe(32000); - expect(calculateResellPrice(40000, 2.5)).toBe(32000); - }); +describe('calculateResellPrice', () => { + test('price is reduced to 80% for age below 3', () => { + expect(calculateResellPrice(40000, 2)).toBe(32000); + expect(calculateResellPrice(40000, 2.5)).toBe(32000); + }); - test('price is reduced to 50% for age above 10', () => { - expect(calculateResellPrice(40000, 12)).toBe(20000); - }); + test('price is reduced to 50% for age above 10', () => { + expect(calculateResellPrice(40000, 12)).toBe(20000); + }); - test('price is reduced to 70% for between 3 and 10', () => { - expect(calculateResellPrice(25000, 7)).toBe(17500); - }); + test('price is reduced to 70% for between 3 and 10', () => { + expect(calculateResellPrice(25000, 7)).toBe(17500); + }); - test('works correctly for threshold age 3', () => { - expect(calculateResellPrice(40000, 3)).toBe(28000); - }); + test('works correctly for threshold age 3', () => { + expect(calculateResellPrice(40000, 3)).toBe(28000); + }); - test('works correctly for threshold age 10', () => { - expect(calculateResellPrice(25000, 10)).toBe(17500); - }); + test('works correctly for threshold age 10', () => { + expect(calculateResellPrice(25000, 10)).toBe(17500); }); }); diff --git a/exercises/concept/windowing-system/.docs/instructions.md b/exercises/concept/windowing-system/.docs/instructions.md index 94c314d4a8..b0980785a9 100644 --- a/exercises/concept/windowing-system/.docs/instructions.md +++ b/exercises/concept/windowing-system/.docs/instructions.md @@ -4,26 +4,26 @@ In this exercise, you will be simulating a windowing based computer system. You will create some windows that can be moved and resized. The following image is representative of the values you will be working with below. -``` +```text <--------------------- screenSize.width ---------------------> - ^ ╔════════════════════════════════════════════════════════════╗ - | ║ ║ - | ║ position.x,_ ║ - | ║ position.y \ ║ - | ║ \<----- size.width -----> ║ - | ║ ^ *──────────────────────┐ ║ - | ║ | │ title │ ║ - | ║ | ├──────────────────────┤ ║ -screenSize.height ║ | │ │ ║ - | ║ size.height │ │ ║ - | ║ | │ contents │ ║ - | ║ | │ │ ║ - | ║ | │ │ ║ - | ║ v └──────────────────────┘ ║ - | ║ ║ - | ║ ║ - v ╚════════════════════════════════════════════════════════════╝ + ^ ┌────────────────────────────────────────────────────────────┐ + | │ │ + | │ position.x, _ │ + | │ position.y \ │ + | │ \<----- size.width -----> │ + | │ ^ *──────────────────────┐ │ + | │ | │ title │ │ + | │ | ├──────────────────────┤ │ +screenSize.height │ | │ │ │ + | │ size.height │ │ │ + | │ | │ contents │ │ + | │ | │ │ │ + | │ | │ │ │ + | │ v └──────────────────────┘ │ + | │ │ + | │ │ + v └────────────────────────────────────────────────────────────┘ ``` 📣 To practice your wide range of JavaScript skills, **try to solve tasks 1 and 2 with prototype syntax and the remaining tasks with class syntax**. @@ -148,7 +148,7 @@ programWindow.position.y; ## 6. Change a program window -Implement a `changeWindow` method that accepts a `ProgramWindow` instance as input and changes the window to the specified size and position. +Implement a `changeWindow` function that accepts a `ProgramWindow` instance as input and changes the window to the specified size and position. The function should return the `ProgramWindow` instance that was passed in after the changes were applied. The window should get a width of 400, a height of 300 and be positioned at x = 100, y = 150. diff --git a/exercises/concept/windowing-system/.docs/introduction.md b/exercises/concept/windowing-system/.docs/introduction.md index 1f312ef87e..1f612f79f3 100644 --- a/exercises/concept/windowing-system/.docs/introduction.md +++ b/exercises/concept/windowing-system/.docs/introduction.md @@ -2,12 +2,12 @@ JavaScript includes the capabilities for object-oriented programming ([OOP][wiki-oop]). In OOP, you want to create objects (_instances_) from "templates" (_classes_) so that they include certain data and functionality. -The data properties are called _fields_ in the OOP context, function properties are called _methods_. +The data properties are called _fields_ in the OOP context, the function properties are called _methods_. JavaScript did not have classes at all before they were added to the language specification in 2015 but allowed for object-oriented programming using prototype-based inheritance. And even though a `class` keyword is available nowadays, JavaScript is still a _prototype-based_ language. -To understand what it means to be a prototype-based language and how JavaScript works, we will go back to the time when there were no classes. +To understand what it means to be a prototype-based language and how JavaScript actually works, we will go back to the time when there were no classes. ## Prototype Syntax @@ -31,14 +31,16 @@ Every instance object includes a hidden, internal property referred to as `[[pro It holds a reference to the value of the `prototype` key of the constructor function. Yes, you read that correctly, a JavaScript function can have key/value pairs because it is also an object behind the scenes. -```exercism/note + +~~~~exercism/note To summarize: - Constructors in JavaScript are regular functions. - Constructing a new instance creates an object with a relation to its constructor called its _prototype_. - Functions are objects (callable objects) and therefore they can have properties. - The constructor's (function) `prototype` property will become the instance's _prototype_. -``` +~~~~ + ### Instance Fields @@ -117,11 +119,13 @@ The `[[prototype]]` property of `Car.prototype` (`myCar.[[prototype]].[[prototyp It contains general methods that are available for all JavaScript objects, e.g. `toString()`. In conclusion, you can call `myCar.toString()` and that method will exist because JavaScript searches for that method throughout the whole prototype chain. -```exercism/caution + +~~~~exercism/caution Note that the prototype chain is only travelled when retrieving a value. Setting a property directly or deleting a property of an instance object only targets that specific instance. This might not be what you would expect when you are used to a language with class-based inheritance. -``` +~~~~ + ## Class Syntax @@ -180,24 +184,24 @@ With the keywords `get` and `set` you can define functions that are executed whe ```javascript class Car { constructor() { - this._milage = 0; + this._mileage = 0; } - get milage() { - return this._milage; + get mileage() { + return this._mileage; } - set milage(value) { - throw new Error(`Milage cannot be manipulated, ${value} is ignored.`); + set mileage(value) { + throw new Error(`Mileage cannot be manipulated, ${value} is ignored.`); // Just an example, usually you would not provide a setter in this case. } } const myCar = new Car(); -myCar.milage; +myCar.mileage; // => 0 -myCar.milage = 100; -// => Error: Milage cannot be manipulated, 100 is ignored. +myCar.mileage = 100; +// => Error: Mileage cannot be manipulated, 100 is ignored. ``` --- diff --git a/exercises/concept/windowing-system/.eslintrc b/exercises/concept/windowing-system/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/concept/windowing-system/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/concept/windowing-system/.gitignore b/exercises/concept/windowing-system/.gitignore index bdb912f98a..0c88ff6ec3 100644 --- a/exercises/concept/windowing-system/.gitignore +++ b/exercises/concept/windowing-system/.gitignore @@ -1,3 +1,5 @@ -node_modules -yarn-error.log - +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/concept/windowing-system/.meta/config.json b/exercises/concept/windowing-system/.meta/config.json index d33b7167ee..bf6cc7a8ec 100644 --- a/exercises/concept/windowing-system/.meta/config.json +++ b/exercises/concept/windowing-system/.meta/config.json @@ -1,11 +1,29 @@ { - "blurb": "Learn about prototypes and classes by developing a windowing system.", - "authors": ["junedev"], - "contributors": [], + "authors": [ + "junedev" + ], + "contributors": [ + "SleeplessByte" + ], "files": { - "solution": ["windowing-system.js"], - "test": ["windowing-system.spec.js"], - "exemplar": [".meta/exemplar.js"] + "solution": [ + "windowing-system.js" + ], + "test": [ + "windowing-system.spec.js" + ], + "exemplar": [ + ".meta/exemplar.js" + ] }, - "forked_from": ["swift/windowing-system"] + "forked_from": [ + "swift/windowing-system" + ], + "blurb": "Learn about prototypes and classes by developing a windowing system.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": true, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/concept/windowing-system/babel.config.js b/exercises/concept/windowing-system/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/concept/windowing-system/babel.config.js +++ b/exercises/concept/windowing-system/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/concept/windowing-system/eslint.config.mjs b/exercises/concept/windowing-system/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/concept/windowing-system/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/concept/windowing-system/jest.config.js b/exercises/concept/windowing-system/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/concept/windowing-system/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/concept/windowing-system/package.json b/exercises/concept/windowing-system/package.json index f4c6e26680..f9625d873a 100644 --- a/exercises/concept/windowing-system/package.json +++ b/exercises/concept/windowing-system/package.json @@ -10,22 +10,25 @@ "directory": "exercises/concept/windowing-system" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/concept/windowing-system/windowing-system.spec.js b/exercises/concept/windowing-system/windowing-system.spec.js index c671797f96..1798a924b3 100644 --- a/exercises/concept/windowing-system/windowing-system.spec.js +++ b/exercises/concept/windowing-system/windowing-system.spec.js @@ -1,11 +1,12 @@ +import { describe, expect, test } from '@jest/globals'; import { - Size, Position, ProgramWindow, + Size, changeWindow, } from './windowing-system'; -describe('Size', () => { +describe('Size class', () => { test('allows to create a new instance', () => { const size = new Size(110, 220); expect(size.width).toBe(110); @@ -26,7 +27,7 @@ describe('Size', () => { }); }); -describe('Position', () => { +describe('Position class', () => { test('allows to create a new instance', () => { const position = new Position(10, 20); expect(position.x).toBe(10); @@ -47,7 +48,7 @@ describe('Position', () => { }); }); -describe('ProgramWindow', () => { +describe('ProgramWindow class', () => { test('allows to create a new instance', () => { const window = new ProgramWindow(); @@ -72,7 +73,9 @@ describe('ProgramWindow', () => { expect(programWindow.position.x).toBe(0); expect(programWindow.position.y).toBe(0); }); +}); +describe('resize', () => { test('provides a resize method', () => { const programWindow = new ProgramWindow(); const newSize = new Size(300, 200); @@ -90,18 +93,9 @@ describe('ProgramWindow', () => { expect(programWindow.size.width).toBe(1); expect(programWindow.size.height).toBe(1); }); +}); - test('resize respects limits due to position and screen size', () => { - const programWindow = new ProgramWindow(); - const newPosition = new Position(710, 525); - programWindow.move(newPosition); - const newSize = new Size(1000, 1000); - programWindow.resize(newSize); - - expect(programWindow.size.width).toBe(90); - expect(programWindow.size.height).toBe(75); - }); - +describe('move', () => { test('provides a move method', () => { const programWindow = new ProgramWindow(); const newPosition = new Position(525, 450); @@ -130,6 +124,17 @@ describe('ProgramWindow', () => { expect(programWindow.position.x).toBe(700); expect(programWindow.position.y).toBe(500); }); + + test('resize respects limits due to position and screen size', () => { + const programWindow = new ProgramWindow(); + const newPosition = new Position(710, 525); + programWindow.move(newPosition); + const newSize = new Size(1000, 1000); + programWindow.resize(newSize); + + expect(programWindow.size.width).toBe(90); + expect(programWindow.size.height).toBe(75); + }); }); describe('changeWindow', () => { diff --git a/exercises/practice/accumulate/.eslintrc b/exercises/practice/accumulate/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/accumulate/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/accumulate/.gitignore b/exercises/practice/accumulate/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/accumulate/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/accumulate/.meta/config.json b/exercises/practice/accumulate/.meta/config.json index 8f841c94ef..ab353ac856 100644 --- a/exercises/practice/accumulate/.meta/config.json +++ b/exercises/practice/accumulate/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Implement the `accumulate` operation, which, given a collection and an operation to perform on each element of the collection, returns a new collection containing the result of applying that operation to each element of the input collection.", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "ankorGH", "gargrave", @@ -10,10 +11,23 @@ "xarxziux" ], "files": { - "solution": ["accumulate.js"], - "test": ["accumulate.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "accumulate.js" + ], + "test": [ + "accumulate.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Implement the `accumulate` operation, which, given a collection and an operation to perform on each element of the collection, returns a new collection containing the result of applying that operation to each element of the input collection.", "source": "Conversation with James Edward Gray II", - "source_url": "https://twitter.com/jeg2" + "source_url": "https://twitter.com/jeg2", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/accumulate/accumulate.js b/exercises/practice/accumulate/accumulate.js index 55d1745771..a15eb3fa37 100644 --- a/exercises/practice/accumulate/accumulate.js +++ b/exercises/practice/accumulate/accumulate.js @@ -4,5 +4,5 @@ // export const accumulate = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/accumulate/accumulate.spec.js b/exercises/practice/accumulate/accumulate.spec.js index dc5e9f0da8..8db9a8fc3f 100644 --- a/exercises/practice/accumulate/accumulate.spec.js +++ b/exercises/practice/accumulate/accumulate.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { accumulate } from './accumulate'; describe('accumulate()', () => { @@ -22,14 +23,14 @@ describe('accumulate()', () => { const accumulator = (word) => word.split('').reverse().join(''); const result = accumulate( 'the quick brown fox etc'.split(/\s/), - accumulator + accumulator, ); expect(result).toEqual(['eht', 'kciuq', 'nworb', 'xof', 'cte']); }); xtest('accumulate recursively', () => { const result = accumulate('a b c'.split(/\s/), (char) => - accumulate('1 2 3'.split(/\s/), (digit) => char + digit) + accumulate('1 2 3'.split(/\s/), (digit) => char + digit), ); expect(result).toEqual([ diff --git a/exercises/practice/accumulate/babel.config.js b/exercises/practice/accumulate/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/accumulate/babel.config.js +++ b/exercises/practice/accumulate/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/accumulate/eslint.config.mjs b/exercises/practice/accumulate/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/accumulate/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/accumulate/jest.config.js b/exercises/practice/accumulate/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/accumulate/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/accumulate/package.json b/exercises/practice/accumulate/package.json index b6228031ec..20a733dcc5 100644 --- a/exercises/practice/accumulate/package.json +++ b/exercises/practice/accumulate/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/accumulate" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/acronym/.docs/instructions.md b/exercises/practice/acronym/.docs/instructions.md index e0515b4d1d..133bd2cbb7 100644 --- a/exercises/practice/acronym/.docs/instructions.md +++ b/exercises/practice/acronym/.docs/instructions.md @@ -4,5 +4,14 @@ Convert a phrase to its acronym. Techies love their TLA (Three Letter Acronyms)! -Help generate some jargon by writing a program that converts a long name -like Portable Network Graphics to its acronym (PNG). +Help generate some jargon by writing a program that converts a long name like Portable Network Graphics to its acronym (PNG). + +Punctuation is handled as follows: hyphens are word separators (like whitespace); all other punctuation can be removed from the input. + +For example: + +| Input | Output | +| ------------------------- | ------ | +| As Soon As Possible | ASAP | +| Liquid-crystal display | LCD | +| Thank George It's Friday! | TGIF | diff --git a/exercises/practice/acronym/.eslintrc b/exercises/practice/acronym/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/acronym/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/acronym/.gitignore b/exercises/practice/acronym/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/acronym/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/acronym/.meta/config.json b/exercises/practice/acronym/.meta/config.json index d4e0dd828f..2e3b101d78 100644 --- a/exercises/practice/acronym/.meta/config.json +++ b/exercises/practice/acronym/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Convert a long phrase to its acronym", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "ankorGH", "cmccandless", @@ -11,10 +12,23 @@ "SleeplessByte" ], "files": { - "solution": ["acronym.js"], - "test": ["acronym.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "acronym.js" + ], + "test": [ + "acronym.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Convert a long phrase to its acronym.", "source": "Julien Vanier", - "source_url": "https://github.com/monkbroc" + "source_url": "https://github.com/monkbroc", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/acronym/acronym.js b/exercises/practice/acronym/acronym.js index 45e9a0e57d..e42d87a751 100644 --- a/exercises/practice/acronym/acronym.js +++ b/exercises/practice/acronym/acronym.js @@ -4,5 +4,5 @@ // export const parse = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/acronym/acronym.spec.js b/exercises/practice/acronym/acronym.spec.js index a8cc6f9f45..59a0eb7fe6 100644 --- a/exercises/practice/acronym/acronym.spec.js +++ b/exercises/practice/acronym/acronym.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { parse } from './acronym'; describe('Acronyms are produced from', () => { @@ -30,8 +31,8 @@ describe('Acronyms are produced from', () => { xtest('long phrases', () => { expect( parse( - 'Rolling On The Floor Laughing So Hard That My Dogs Came Over And Licked Me' - ) + 'Rolling On The Floor Laughing So Hard That My Dogs Came Over And Licked Me', + ), ).toEqual('ROTFLSHTMDCOALM'); }); diff --git a/exercises/practice/acronym/babel.config.js b/exercises/practice/acronym/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/acronym/babel.config.js +++ b/exercises/practice/acronym/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/acronym/eslint.config.mjs b/exercises/practice/acronym/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/acronym/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/acronym/jest.config.js b/exercises/practice/acronym/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/acronym/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/acronym/package.json b/exercises/practice/acronym/package.json index 1c9313a9d4..7f77cbebfb 100644 --- a/exercises/practice/acronym/package.json +++ b/exercises/practice/acronym/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/acronym" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/affine-cipher/.docs/instructions.md b/exercises/practice/affine-cipher/.docs/instructions.md index 408cf591bd..1603dbbce9 100644 --- a/exercises/practice/affine-cipher/.docs/instructions.md +++ b/exercises/practice/affine-cipher/.docs/instructions.md @@ -1,73 +1,74 @@ # Instructions -Create an implementation of the affine cipher, -an ancient encryption system created in the Middle East. +Create an implementation of the affine cipher, an ancient encryption system created in the Middle East. The affine cipher is a type of monoalphabetic substitution cipher. -Each character is mapped to its numeric equivalent, encrypted with -a mathematical function and then converted to the letter relating to -its new numeric value. Although all monoalphabetic ciphers are weak, -the affine cypher is much stronger than the atbash cipher, -because it has many more keys. +Each character is mapped to its numeric equivalent, encrypted with a mathematical function and then converted to the letter relating to its new numeric value. +Although all monoalphabetic ciphers are weak, the affine cipher is much stronger than the Atbash cipher, because it has many more keys. + +[//]: # " monoalphabetic as spelled by Merriam-Webster, compare to polyalphabetic " + +## Encryption The encryption function is: -`E(x) = (ax + b) mod m` +```text +E(x) = (ai + b) mod m +``` -- where `x` is the letter's index from 0 - length of alphabet - 1 -- `m` is the length of the alphabet. For the roman alphabet `m == 26`. -- and `a` and `b` make the key +Where: -The decryption function is: +- `i` is the letter's index from `0` to the length of the alphabet - 1. +- `m` is the length of the alphabet. + For the Latin alphabet `m` is `26`. +- `a` and `b` are integers which make up the encryption key. -`D(y) = a^-1(y - b) mod m` +Values `a` and `m` must be _coprime_ (or, _relatively prime_) for automatic decryption to succeed, i.e., they have number `1` as their only common factor (more information can be found in the [Wikipedia article about coprime integers][coprime-integers]). +In case `a` is not coprime to `m`, your program should indicate that this is an error. +Otherwise it should encrypt or decrypt with the provided key. -- where `y` is the numeric value of an encrypted letter, ie. `y = E(x)` -- it is important to note that `a^-1` is the modular multiplicative inverse - of `a mod m` -- the modular multiplicative inverse of `a` only exists if `a` and `m` are - coprime. +For the purpose of this exercise, digits are valid input but they are not encrypted. +Spaces and punctuation characters are excluded. +Ciphertext is written out in groups of fixed length separated by space, the traditional group size being `5` letters. +This is to make it harder to guess encrypted text based on word boundaries. -To find the MMI of `a`: +## Decryption + +The decryption function is: -`an mod m = 1` +```text +D(y) = (a^-1)(y - b) mod m +``` -- where `n` is the modular multiplicative inverse of `a mod m` +Where: -More information regarding how to find a Modular Multiplicative Inverse -and what it means can be found [here.](https://en.wikipedia.org/wiki/Modular_multiplicative_inverse) +- `y` is the numeric value of an encrypted letter, i.e., `y = E(x)` +- it is important to note that `a^-1` is the modular multiplicative inverse (MMI) of `a mod m` +- the modular multiplicative inverse only exists if `a` and `m` are coprime. -Because automatic decryption fails if `a` is not coprime to `m` your -program should return status 1 and `"Error: a and m must be coprime."` -if they are not. Otherwise it should encode or decode with the -provided key. +The MMI of `a` is `x` such that the remainder after dividing `ax` by `m` is `1`: -The Caesar (shift) cipher is a simple affine cipher where `a` is 1 and -`b` as the magnitude results in a static displacement of the letters. -This is much less secure than a full implementation of the affine cipher. +```text +ax mod m = 1 +``` -Ciphertext is written out in groups of fixed length, the traditional group -size being 5 letters, and punctuation is excluded. This is to make it -harder to guess things based on word boundaries. +More information regarding how to find a Modular Multiplicative Inverse and what it means can be found in the [related Wikipedia article][mmi]. ## General Examples -- Encoding `test` gives `ybty` with the key a=5 b=7 -- Decoding `ybty` gives `test` with the key a=5 b=7 -- Decoding `ybty` gives `lqul` with the wrong key a=11 b=7 -- Decoding `kqlfd jzvgy tpaet icdhm rtwly kqlon ubstx` - - gives `thequickbrownfoxjumpsoverthelazydog` with the key a=19 b=13 -- Encoding `test` with the key a=18 b=13 - - gives `Error: a and m must be coprime.` - - because a and m are not relatively prime - -## Examples of finding a Modular Multiplicative Inverse (MMI) - -- simple example: - - `9 mod 26 = 9` - - `9 * 3 mod 26 = 27 mod 26 = 1` - - `3` is the MMI of `9 mod 26` -- a more complicated example: - - `15 mod 26 = 15` - - `15 * 7 mod 26 = 105 mod 26 = 1` - - `7` is the MMI of `15 mod 26` +- Encrypting `"test"` gives `"ybty"` with the key `a = 5`, `b = 7` +- Decrypting `"ybty"` gives `"test"` with the key `a = 5`, `b = 7` +- Decrypting `"ybty"` gives `"lqul"` with the wrong key `a = 11`, `b = 7` +- Decrypting `"kqlfd jzvgy tpaet icdhm rtwly kqlon ubstx"` gives `"thequickbrownfoxjumpsoverthelazydog"` with the key `a = 19`, `b = 13` +- Encrypting `"test"` with the key `a = 18`, `b = 13` is an error because `18` and `26` are not coprime + +## Example of finding a Modular Multiplicative Inverse (MMI) + +Finding MMI for `a = 15`: + +- `(15 * x) mod 26 = 1` +- `(15 * 7) mod 26 = 1`, ie. `105 mod 26 = 1` +- `7` is the MMI of `15 mod 26` + +[mmi]: https://en.wikipedia.org/wiki/Modular_multiplicative_inverse +[coprime-integers]: https://en.wikipedia.org/wiki/Coprime_integers diff --git a/exercises/practice/affine-cipher/.eslintrc b/exercises/practice/affine-cipher/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/affine-cipher/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/affine-cipher/.gitignore b/exercises/practice/affine-cipher/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/affine-cipher/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/affine-cipher/.meta/config.json b/exercises/practice/affine-cipher/.meta/config.json index c56dab7947..2e56724755 100644 --- a/exercises/practice/affine-cipher/.meta/config.json +++ b/exercises/practice/affine-cipher/.meta/config.json @@ -1,12 +1,28 @@ { - "blurb": "Create an implementation of the Affine cipher, an ancient encryption algorithm from the Middle East.", - "authors": ["TomPradat"], - "contributors": ["SleeplessByte"], + "authors": [ + "TomPradat" + ], + "contributors": [ + "SleeplessByte" + ], "files": { - "solution": ["affine-cipher.js"], - "test": ["affine-cipher.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "affine-cipher.js" + ], + "test": [ + "affine-cipher.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Create an implementation of the Affine cipher, an ancient encryption algorithm from the Middle East.", "source": "Wikipedia", - "source_url": "http://en.wikipedia.org/wiki/Affine_cipher" + "source_url": "https://en.wikipedia.org/wiki/Affine_cipher", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/affine-cipher/.meta/proof.ci.js b/exercises/practice/affine-cipher/.meta/proof.ci.js index ee1665b0ed..6b129951d2 100644 --- a/exercises/practice/affine-cipher/.meta/proof.ci.js +++ b/exercises/practice/affine-cipher/.meta/proof.ci.js @@ -24,7 +24,6 @@ const isNumber = (candidate) => { const findMMI = (a) => { let i = 1; - // eslint-disable-next-line no-constant-condition while (true) { i++; diff --git a/exercises/practice/affine-cipher/affine-cipher.js b/exercises/practice/affine-cipher/affine-cipher.js index 545736f6ab..e2b1330623 100644 --- a/exercises/practice/affine-cipher/affine-cipher.js +++ b/exercises/practice/affine-cipher/affine-cipher.js @@ -1,7 +1,7 @@ export const encode = (phrase, key) => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; export const decode = (phrase, key) => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/affine-cipher/affine-cipher.spec.js b/exercises/practice/affine-cipher/affine-cipher.spec.js index 9156312c5d..507aad8de3 100644 --- a/exercises/practice/affine-cipher/affine-cipher.spec.js +++ b/exercises/practice/affine-cipher/affine-cipher.spec.js @@ -1,4 +1,5 @@ -import { encode, decode } from './affine-cipher'; +import { describe, expect, test, xtest } from '@jest/globals'; +import { decode, encode } from './affine-cipher'; describe('Affine cipher', () => { describe('encode', () => { @@ -24,26 +25,29 @@ describe('Affine cipher', () => { xtest('encode numbers', () => { expect(encode('Testing,1 2 3, testing.', { a: 3, b: 4 })).toBe( - 'jqgjc rw123 jqgjc rw' + 'jqgjc rw123 jqgjc rw', ); }); xtest('encode deep thought', () => { expect(encode('Truth is fiction.', { a: 5, b: 17 })).toBe( - 'iynia fdqfb ifje' + 'iynia fdqfb ifje', ); }); xtest('encode all the letters', () => { expect( - encode('The quick brown fox jumps over the lazy dog.', { a: 17, b: 33 }) + encode('The quick brown fox jumps over the lazy dog.', { + a: 17, + b: 33, + }), ).toBe('swxtj npvyk lruol iejdc blaxk swxmh qzglf'); }); xtest('encode with a not coprime to m', () => { expect(() => { encode('This is a test.', { a: 6, b: 17 }); - }).toThrowError('a and m must be coprime.'); + }).toThrow('a and m must be coprime.'); }); }); describe('decode', () => { @@ -53,38 +57,38 @@ describe('Affine cipher', () => { xtest('decode a sentence', () => { expect( - decode('qdwju nqcro muwhn odqun oppmd aunwd o', { a: 19, b: 16 }) + decode('qdwju nqcro muwhn odqun oppmd aunwd o', { a: 19, b: 16 }), ).toBe('anobstacleisoftenasteppingstone'); }); xtest('decode numbers', () => { expect(decode('odpoz ub123 odpoz ub', { a: 25, b: 7 })).toBe( - 'testing123testing' + 'testing123testing', ); }); xtest('decode all the letters', () => { expect( - decode('swxtj npvyk lruol iejdc blaxk swxmh qzglf', { a: 17, b: 33 }) + decode('swxtj npvyk lruol iejdc blaxk swxmh qzglf', { a: 17, b: 33 }), ).toBe('thequickbrownfoxjumpsoverthelazydog'); }); xtest('decode with no spaces in input', () => { expect( - decode('swxtjnpvyklruoliejdcblaxkswxmhqzglf', { a: 17, b: 33 }) + decode('swxtjnpvyklruoliejdcblaxkswxmhqzglf', { a: 17, b: 33 }), ).toBe('thequickbrownfoxjumpsoverthelazydog'); }); xtest('decode with too many spaces', () => { expect(decode('vszzm cly yd cg qdp', { a: 15, b: 16 })).toBe( - 'jollygreengiant' + 'jollygreengiant', ); }); xtest('decode with a not coprime to m', () => { expect(() => { decode('Test', { a: 13, b: 5 }); - }).toThrowError('a and m must be coprime.'); + }).toThrow('a and m must be coprime.'); }); }); }); diff --git a/exercises/practice/affine-cipher/babel.config.js b/exercises/practice/affine-cipher/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/affine-cipher/babel.config.js +++ b/exercises/practice/affine-cipher/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/affine-cipher/eslint.config.mjs b/exercises/practice/affine-cipher/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/affine-cipher/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/affine-cipher/jest.config.js b/exercises/practice/affine-cipher/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/affine-cipher/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/affine-cipher/package.json b/exercises/practice/affine-cipher/package.json index 64837e748c..b9fcfe36b4 100644 --- a/exercises/practice/affine-cipher/package.json +++ b/exercises/practice/affine-cipher/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/affine-cipher" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/all-your-base/.docs/instructions.md b/exercises/practice/all-your-base/.docs/instructions.md index 858f0815e4..1b688b6915 100644 --- a/exercises/practice/all-your-base/.docs/instructions.md +++ b/exercises/practice/all-your-base/.docs/instructions.md @@ -1,32 +1,28 @@ # Instructions -Convert a number, represented as a sequence of digits in one base, to any other base. +Convert a sequence of digits in one base, representing a number, into a sequence of digits in another base, representing the same number. -Implement general base conversion. Given a number in base **a**, -represented as a sequence of digits, convert it to base **b**. +~~~~exercism/note +Try to implement the conversion yourself. +Do not use something else to perform the conversion for you. +~~~~ -## Note +## About [Positional Notation][positional-notation] -- Try to implement the conversion yourself. - Do not use something else to perform the conversion for you. - -## About [Positional Notation](https://en.wikipedia.org/wiki/Positional_notation) - -In positional notation, a number in base **b** can be understood as a linear -combination of powers of **b**. +In positional notation, a number in base **b** can be understood as a linear combination of powers of **b**. The number 42, _in base 10_, means: -(4 _ 10^1) + (2 _ 10^0) +`(4 × 10¹) + (2 × 10⁰)` The number 101010, _in base 2_, means: -(1 _ 2^5) + (0 _ 2^4) + (1 _ 2^3) + (0 _ 2^2) + (1 _ 2^1) + (0 _ 2^0) +`(1 × 2⁵) + (0 × 2⁴) + (1 × 2³) + (0 × 2²) + (1 × 2¹) + (0 × 2⁰)` The number 1120, _in base 3_, means: -(1 _ 3^3) + (1 _ 3^2) + (2 _ 3^1) + (0 _ 3^0) - -I think you got the idea! +`(1 × 3³) + (1 × 3²) + (2 × 3¹) + (0 × 3⁰)` _Yes. Those three numbers above are exactly the same. Congratulations!_ + +[positional-notation]: https://en.wikipedia.org/wiki/Positional_notation diff --git a/exercises/practice/all-your-base/.docs/introduction.md b/exercises/practice/all-your-base/.docs/introduction.md new file mode 100644 index 0000000000..68aaffbed9 --- /dev/null +++ b/exercises/practice/all-your-base/.docs/introduction.md @@ -0,0 +1,8 @@ +# Introduction + +You've just been hired as professor of mathematics. +Your first week went well, but something is off in your second week. +The problem is that every answer given by your students is wrong! +Luckily, your math skills have allowed you to identify the problem: the student answers _are_ correct, but they're all in base 2 (binary)! +Amazingly, it turns out that each week, the students use a different base. +To help you quickly verify the student answers, you'll be building a tool to translate between bases. diff --git a/exercises/practice/all-your-base/.eslintrc b/exercises/practice/all-your-base/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/all-your-base/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/all-your-base/.gitignore b/exercises/practice/all-your-base/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/all-your-base/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/all-your-base/.meta/config.json b/exercises/practice/all-your-base/.meta/config.json index cfcbb7e94a..21a172b7fc 100644 --- a/exercises/practice/all-your-base/.meta/config.json +++ b/exercises/practice/all-your-base/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Convert a number, represented as a sequence of digits in one base, to any other base.", - "authors": ["javaeeeee"], + "authors": [ + "javaeeeee" + ], "contributors": [ "ankorGH", "matthewmorgan", @@ -10,8 +11,21 @@ "SleeplessByte" ], "files": { - "solution": ["all-your-base.js"], - "test": ["all-your-base.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "all-your-base.js" + ], + "test": [ + "all-your-base.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] + }, + "blurb": "Convert a number, represented as a sequence of digits in one base, to any other base.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false } } diff --git a/exercises/practice/all-your-base/.meta/proof.ci.js b/exercises/practice/all-your-base/.meta/proof.ci.js index 57eb0a2873..f7bd2176e2 100644 --- a/exercises/practice/all-your-base/.meta/proof.ci.js +++ b/exercises/practice/all-your-base/.meta/proof.ci.js @@ -44,7 +44,7 @@ export const convert = (array, inputBase, outputBase) => { } const decimalValue = array.reduce( (accumulator, value) => accumulator * inputBase + value, - 0 + 0, ); return convertFromDecimalToBase(decimalValue, outputBase); }; diff --git a/exercises/practice/all-your-base/all-your-base.js b/exercises/practice/all-your-base/all-your-base.js index ce43914067..644bfb7142 100644 --- a/exercises/practice/all-your-base/all-your-base.js +++ b/exercises/practice/all-your-base/all-your-base.js @@ -4,5 +4,5 @@ // export const convert = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/all-your-base/all-your-base.spec.js b/exercises/practice/all-your-base/all-your-base.spec.js index 8af67824b6..5c84bc8114 100644 --- a/exercises/practice/all-your-base/all-your-base.spec.js +++ b/exercises/practice/all-your-base/all-your-base.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { convert } from './all-your-base'; describe('Converter', () => { diff --git a/exercises/practice/all-your-base/babel.config.js b/exercises/practice/all-your-base/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/all-your-base/babel.config.js +++ b/exercises/practice/all-your-base/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/all-your-base/eslint.config.mjs b/exercises/practice/all-your-base/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/all-your-base/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/all-your-base/jest.config.js b/exercises/practice/all-your-base/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/all-your-base/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/all-your-base/package.json b/exercises/practice/all-your-base/package.json index 2e7e362c72..cac41d5b73 100644 --- a/exercises/practice/all-your-base/package.json +++ b/exercises/practice/all-your-base/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/all-your-base" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/allergies/.docs/instructions.md b/exercises/practice/allergies/.docs/instructions.md index 4ae3818fa8..daf8cfde21 100644 --- a/exercises/practice/allergies/.docs/instructions.md +++ b/exercises/practice/allergies/.docs/instructions.md @@ -2,9 +2,7 @@ Given a person's allergy score, determine whether or not they're allergic to a given item, and their full list of allergies. -An allergy test produces a single numeric score which contains the -information about all the allergies the person has (that they were -tested for). +An allergy test produces a single numeric score which contains the information about all the allergies the person has (that they were tested for). The list of items (and their value) that were tested are: @@ -24,7 +22,6 @@ Now, given just that score of 34, your program should be able to say: - Whether Tom is allergic to any one of those allergens listed above. - All the allergens Tom is allergic to. -Note: a given score may include allergens **not** listed above (i.e. -allergens that score 256, 512, 1024, etc.). Your program should -ignore those components of the score. For example, if the allergy -score is 257, your program should only report the eggs (1) allergy. +Note: a given score may include allergens **not** listed above (i.e. allergens that score 256, 512, 1024, etc.). +Your program should ignore those components of the score. +For example, if the allergy score is 257, your program should only report the eggs (1) allergy. diff --git a/exercises/practice/allergies/.eslintrc b/exercises/practice/allergies/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/allergies/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/allergies/.gitignore b/exercises/practice/allergies/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/allergies/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/allergies/.meta/config.json b/exercises/practice/allergies/.meta/config.json index 46a78dab1c..7b04fe6c24 100644 --- a/exercises/practice/allergies/.meta/config.json +++ b/exercises/practice/allergies/.meta/config.json @@ -1,8 +1,10 @@ { - "blurb": "Given a person's allergy score, determine whether or not they're allergic to a given item, and their full list of allergies.", - "authors": ["rchavarria"], + "authors": [ + "rchavarria" + ], "contributors": [ "ankorGH", + "jagdish-15", "matthewmorgan", "ovidiu141", "ryanplusplus", @@ -12,10 +14,23 @@ "xarxziux" ], "files": { - "solution": ["allergies.js"], - "test": ["allergies.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "allergies.js" + ], + "test": [ + "allergies.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, - "source": "Jumpstart Lab Warm-up", - "source_url": "http://jumpstartlab.com" + "blurb": "Given a person's allergy score, determine whether or not they're allergic to a given item, and their full list of allergies.", + "source": "Exercise by the JumpstartLab team for students at The Turing School of Software and Design.", + "source_url": "https://turing.edu", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/allergies/.meta/proof.ci.js b/exercises/practice/allergies/.meta/proof.ci.js index 9b997105a8..c3f3e1b94d 100644 --- a/exercises/practice/allergies/.meta/proof.ci.js +++ b/exercises/practice/allergies/.meta/proof.ci.js @@ -15,9 +15,8 @@ export class Allergies { } list() { - // eslint-disable-next-line no-bitwise, no-restricted-properties return possibleAllergies.filter( - (allergy, i) => this.allergenIndex & Math.pow(2, i) + (_allergy, i) => this.allergenIndex & Math.pow(2, i), ); } diff --git a/exercises/practice/allergies/.meta/tests.toml b/exercises/practice/allergies/.meta/tests.toml index 8a754c20d9..799ab8563e 100644 --- a/exercises/practice/allergies/.meta/tests.toml +++ b/exercises/practice/allergies/.meta/tests.toml @@ -1,150 +1,160 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [17fc7296-2440-4ac4-ad7b-d07c321bc5a0] -description = "not allergic to anything" +description = "testing for eggs allergy -> not allergic to anything" [07ced27b-1da5-4c2e-8ae2-cb2791437546] -description = "allergic only to eggs" +description = "testing for eggs allergy -> allergic only to eggs" [5035b954-b6fa-4b9b-a487-dae69d8c5f96] -description = "allergic to eggs and something else" +description = "testing for eggs allergy -> allergic to eggs and something else" [64a6a83a-5723-4b5b-a896-663307403310] -description = "allergic to something, but not eggs" +description = "testing for eggs allergy -> allergic to something, but not eggs" [90c8f484-456b-41c4-82ba-2d08d93231c6] -description = "allergic to everything" +description = "testing for eggs allergy -> allergic to everything" [d266a59a-fccc-413b-ac53-d57cb1f0db9d] -description = "not allergic to anything" +description = "testing for peanuts allergy -> not allergic to anything" [ea210a98-860d-46b2-a5bf-50d8995b3f2a] -description = "allergic only to peanuts" +description = "testing for peanuts allergy -> allergic only to peanuts" [eac69ae9-8d14-4291-ac4b-7fd2c73d3a5b] -description = "allergic to peanuts and something else" +description = "testing for peanuts allergy -> allergic to peanuts and something else" [9152058c-ce39-4b16-9b1d-283ec6d25085] -description = "allergic to something, but not peanuts" +description = "testing for peanuts allergy -> allergic to something, but not peanuts" [d2d71fd8-63d5-40f9-a627-fbdaf88caeab] -description = "allergic to everything" +description = "testing for peanuts allergy -> allergic to everything" [b948b0a1-cbf7-4b28-a244-73ff56687c80] -description = "not allergic to anything" +description = "testing for shellfish allergy -> not allergic to anything" [9ce9a6f3-53e9-4923-85e0-73019047c567] -description = "allergic only to shellfish" +description = "testing for shellfish allergy -> allergic only to shellfish" [b272fca5-57ba-4b00-bd0c-43a737ab2131] -description = "allergic to shellfish and something else" +description = "testing for shellfish allergy -> allergic to shellfish and something else" [21ef8e17-c227-494e-8e78-470a1c59c3d8] -description = "allergic to something, but not shellfish" +description = "testing for shellfish allergy -> allergic to something, but not shellfish" [cc789c19-2b5e-4c67-b146-625dc8cfa34e] -description = "allergic to everything" +description = "testing for shellfish allergy -> allergic to everything" [651bde0a-2a74-46c4-ab55-02a0906ca2f5] -description = "not allergic to anything" +description = "testing for strawberries allergy -> not allergic to anything" [b649a750-9703-4f5f-b7f7-91da2c160ece] -description = "allergic only to strawberries" +description = "testing for strawberries allergy -> allergic only to strawberries" [50f5f8f3-3bac-47e6-8dba-2d94470a4bc6] -description = "allergic to strawberries and something else" +description = "testing for strawberries allergy -> allergic to strawberries and something else" [23dd6952-88c9-48d7-a7d5-5d0343deb18d] -description = "allergic to something, but not strawberries" +description = "testing for strawberries allergy -> allergic to something, but not strawberries" [74afaae2-13b6-43a2-837a-286cd42e7d7e] -description = "allergic to everything" +description = "testing for strawberries allergy -> allergic to everything" [c49a91ef-6252-415e-907e-a9d26ef61723] -description = "not allergic to anything" +description = "testing for tomatoes allergy -> not allergic to anything" [b69c5131-b7d0-41ad-a32c-e1b2cc632df8] -description = "allergic only to tomatoes" +description = "testing for tomatoes allergy -> allergic only to tomatoes" [1ca50eb1-f042-4ccf-9050-341521b929ec] -description = "allergic to tomatoes and something else" +description = "testing for tomatoes allergy -> allergic to tomatoes and something else" [e9846baa-456b-4eff-8025-034b9f77bd8e] -description = "allergic to something, but not tomatoes" +description = "testing for tomatoes allergy -> allergic to something, but not tomatoes" [b2414f01-f3ad-4965-8391-e65f54dad35f] -description = "allergic to everything" +description = "testing for tomatoes allergy -> allergic to everything" [978467ab-bda4-49f7-b004-1d011ead947c] -description = "not allergic to anything" +description = "testing for chocolate allergy -> not allergic to anything" [59cf4e49-06ea-4139-a2c1-d7aad28f8cbc] -description = "allergic only to chocolate" +description = "testing for chocolate allergy -> allergic only to chocolate" [b0a7c07b-2db7-4f73-a180-565e07040ef1] -description = "allergic to chocolate and something else" +description = "testing for chocolate allergy -> allergic to chocolate and something else" [f5506893-f1ae-482a-b516-7532ba5ca9d2] -description = "allergic to something, but not chocolate" +description = "testing for chocolate allergy -> allergic to something, but not chocolate" [02debb3d-d7e2-4376-a26b-3c974b6595c6] -description = "allergic to everything" +description = "testing for chocolate allergy -> allergic to everything" [17f4a42b-c91e-41b8-8a76-4797886c2d96] -description = "not allergic to anything" +description = "testing for pollen allergy -> not allergic to anything" [7696eba7-1837-4488-882a-14b7b4e3e399] -description = "allergic only to pollen" +description = "testing for pollen allergy -> allergic only to pollen" [9a49aec5-fa1f-405d-889e-4dfc420db2b6] -description = "allergic to pollen and something else" +description = "testing for pollen allergy -> allergic to pollen and something else" [3cb8e79f-d108-4712-b620-aa146b1954a9] -description = "allergic to something, but not pollen" +description = "testing for pollen allergy -> allergic to something, but not pollen" [1dc3fe57-7c68-4043-9d51-5457128744b2] -description = "allergic to everything" +description = "testing for pollen allergy -> allergic to everything" [d3f523d6-3d50-419b-a222-d4dfd62ce314] -description = "not allergic to anything" +description = "testing for cats allergy -> not allergic to anything" [eba541c3-c886-42d3-baef-c048cb7fcd8f] -description = "allergic only to cats" +description = "testing for cats allergy -> allergic only to cats" [ba718376-26e0-40b7-bbbe-060287637ea5] -description = "allergic to cats and something else" +description = "testing for cats allergy -> allergic to cats and something else" [3c6dbf4a-5277-436f-8b88-15a206f2d6c4] -description = "allergic to something, but not cats" +description = "testing for cats allergy -> allergic to something, but not cats" [1faabb05-2b98-4995-9046-d83e4a48a7c1] -description = "allergic to everything" +description = "testing for cats allergy -> allergic to everything" [f9c1b8e7-7dc5-4887-aa93-cebdcc29dd8f] -description = "no allergies" +description = "list when: -> no allergies" [9e1a4364-09a6-4d94-990f-541a94a4c1e8] -description = "just eggs" +description = "list when: -> just eggs" [8851c973-805e-4283-9e01-d0c0da0e4695] -description = "just peanuts" +description = "list when: -> just peanuts" [2c8943cb-005e-435f-ae11-3e8fb558ea98] -description = "just strawberries" +description = "list when: -> just strawberries" [6fa95d26-044c-48a9-8a7b-9ee46ec32c5c] -description = "eggs and peanuts" +description = "list when: -> eggs and peanuts" [19890e22-f63f-4c5c-a9fb-fb6eacddfe8e] -description = "more than eggs but not peanuts" +description = "list when: -> more than eggs but not peanuts" [4b68f470-067c-44e4-889f-c9fe28917d2f] -description = "lots of stuff" +description = "list when: -> lots of stuff" [0881b7c5-9efa-4530-91bd-68370d054bc7] -description = "everything" +description = "list when: -> everything" [12ce86de-b347-42a0-ab7c-2e0570f0c65b] -description = "no allergen score parts" +description = "list when: -> no allergen score parts" + +[93c2df3e-4f55-4fed-8116-7513092819cd] +description = "list when: -> no allergen score parts without highest valid score" diff --git a/exercises/practice/allergies/allergies.js b/exercises/practice/allergies/allergies.js index acf1d5376f..f0b7ef3207 100644 --- a/exercises/practice/allergies/allergies.js +++ b/exercises/practice/allergies/allergies.js @@ -5,14 +5,14 @@ export class Allergies { constructor() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } list() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } allergicTo() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/allergies/allergies.spec.js b/exercises/practice/allergies/allergies.spec.js index 18f9f9c51c..bc11b58911 100644 --- a/exercises/practice/allergies/allergies.spec.js +++ b/exercises/practice/allergies/allergies.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { Allergies } from './allergies'; describe('Allergies', () => { @@ -285,5 +286,10 @@ describe('Allergies', () => { 'cats', ]); }); + + xtest('no allergen score parts without highest valid score', () => { + const allergies = new Allergies(257); + expect(allergies.list()).toEqual(['eggs']); + }); }); }); diff --git a/exercises/practice/allergies/babel.config.js b/exercises/practice/allergies/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/allergies/babel.config.js +++ b/exercises/practice/allergies/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/allergies/eslint.config.mjs b/exercises/practice/allergies/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/allergies/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/allergies/jest.config.js b/exercises/practice/allergies/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/allergies/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/allergies/package.json b/exercises/practice/allergies/package.json index 1478224793..a5545720ef 100644 --- a/exercises/practice/allergies/package.json +++ b/exercises/practice/allergies/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/allergies" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/alphametics/.docs/instructions.md b/exercises/practice/alphametics/.docs/instructions.md index 6936c192d5..ef2cbb4a71 100644 --- a/exercises/practice/alphametics/.docs/instructions.md +++ b/exercises/practice/alphametics/.docs/instructions.md @@ -1,9 +1,8 @@ # Instructions -Write a function to solve alphametics puzzles. +Given an alphametics puzzle, find the correct solution. -[Alphametics](https://en.wikipedia.org/wiki/Alphametics) is a puzzle where -letters in words are replaced with numbers. +[Alphametics][alphametics] is a puzzle where letters in words are replaced with numbers. For example `SEND + MORE = MONEY`: @@ -23,10 +22,8 @@ Replacing these with valid numbers gives: 1 0 6 5 2 ``` -This is correct because every letter is replaced by a different number and the -words, translated into numbers, then make a valid sum. +This is correct because every letter is replaced by a different number and the words, translated into numbers, then make a valid sum. -Each letter must represent a different digit, and the leading digit of -a multi-digit number must not be zero. +Each letter must represent a different digit, and the leading digit of a multi-digit number must not be zero. -Write a function to solve alphametics puzzles. +[alphametics]: https://en.wikipedia.org/wiki/Alphametics diff --git a/exercises/practice/alphametics/.eslintrc b/exercises/practice/alphametics/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/alphametics/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/alphametics/.gitignore b/exercises/practice/alphametics/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/alphametics/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/alphametics/.meta/config.json b/exercises/practice/alphametics/.meta/config.json index 2053c782ab..25236efe0a 100644 --- a/exercises/practice/alphametics/.meta/config.json +++ b/exercises/practice/alphametics/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Write a function to solve alphametics puzzles.", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "rchavarria", "slaymance", @@ -9,8 +10,21 @@ "xarxziux" ], "files": { - "solution": ["alphametics.js"], - "test": ["alphametics.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "alphametics.js" + ], + "test": [ + "alphametics.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] + }, + "blurb": "Given an alphametics puzzle, find the correct solution.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false } } diff --git a/exercises/practice/alphametics/.meta/proof.ci.js b/exercises/practice/alphametics/.meta/proof.ci.js index f982eb4543..fe9b11f9d9 100644 --- a/exercises/practice/alphametics/.meta/proof.ci.js +++ b/exercises/practice/alphametics/.meta/proof.ci.js @@ -38,7 +38,7 @@ function testPermutation(letterCounts, numbers) { return counts.reduce((sum, count, i) => sum + count * numbers[i], 0) === 0 ? letters.reduce( (solution, letter, i) => ({ ...solution, [letter]: numbers[i] }), - {} + {}, ) : null; } diff --git a/exercises/practice/alphametics/alphametics.js b/exercises/practice/alphametics/alphametics.js index fd5babdcdc..a53b20e597 100644 --- a/exercises/practice/alphametics/alphametics.js +++ b/exercises/practice/alphametics/alphametics.js @@ -4,5 +4,5 @@ // export const solve = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/alphametics/alphametics.spec.js b/exercises/practice/alphametics/alphametics.spec.js index 7f7c3439de..07f990e446 100644 --- a/exercises/practice/alphametics/alphametics.spec.js +++ b/exercises/practice/alphametics/alphametics.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { solve } from './alphametics'; describe('Solve the alphametics puzzle', () => { diff --git a/exercises/practice/alphametics/babel.config.js b/exercises/practice/alphametics/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/alphametics/babel.config.js +++ b/exercises/practice/alphametics/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/alphametics/eslint.config.mjs b/exercises/practice/alphametics/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/alphametics/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/alphametics/jest.config.js b/exercises/practice/alphametics/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/alphametics/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/alphametics/package.json b/exercises/practice/alphametics/package.json index d7444eb951..f5b67f1402 100644 --- a/exercises/practice/alphametics/package.json +++ b/exercises/practice/alphametics/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/alphametics" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/anagram/.docs/instructions.append.md b/exercises/practice/anagram/.docs/instructions.append.md new file mode 100644 index 0000000000..8d71a920bd --- /dev/null +++ b/exercises/practice/anagram/.docs/instructions.append.md @@ -0,0 +1,3 @@ +# Instructions Append + +The anagrams can be returned in any order. diff --git a/exercises/practice/anagram/.docs/instructions.md b/exercises/practice/anagram/.docs/instructions.md index aa52548c4d..dca24f5262 100644 --- a/exercises/practice/anagram/.docs/instructions.md +++ b/exercises/practice/anagram/.docs/instructions.md @@ -1,7 +1,12 @@ # Instructions -An anagram is a rearrangement of letters to form a new word. -Given a word and a list of candidates, select the sublist of anagrams of the given word. +Given a target word and one or more candidate words, your task is to find the candidates that are anagrams of the target. -Given `"listen"` and a list of candidates like `"enlists" "google" "inlets" "banana"` the program should return a list containing -`"inlets"`. +An anagram is a rearrangement of letters to form a new word: for example `"owns"` is an anagram of `"snow"`. +A word is _not_ its own anagram: for example, `"stop"` is not an anagram of `"stop"`. + +The target word and candidate words are made up of one or more ASCII alphabetic characters (`A`-`Z` and `a`-`z`). +Lowercase and uppercase characters are equivalent: for example, `"PoTS"` is an anagram of `"sTOp"`, but `"StoP"` is not an anagram of `"sTOp"`. +The words you need to find should be taken from the candidate words, using the same letter case. + +Given the target `"stone"` and the candidate words `"stone"`, `"tones"`, `"banana"`, `"tons"`, `"notes"`, and `"Seton"`, the anagram words you need to find are `"tones"`, `"notes"`, and `"Seton"`. diff --git a/exercises/practice/anagram/.docs/introduction.md b/exercises/practice/anagram/.docs/introduction.md new file mode 100644 index 0000000000..1acbdf00b0 --- /dev/null +++ b/exercises/practice/anagram/.docs/introduction.md @@ -0,0 +1,12 @@ +# Introduction + +At a garage sale, you find a lovely vintage typewriter at a bargain price! +Excitedly, you rush home, insert a sheet of paper, and start typing away. +However, your excitement wanes when you examine the output: all words are garbled! +For example, it prints "stop" instead of "post" and "least" instead of "stale." +Carefully, you try again, but now it prints "spot" and "slate." +After some experimentation, you find there is a random delay before each letter is printed, which messes up the order. +You now understand why they sold it for so little money! + +You realize this quirk allows you to generate anagrams, which are words formed by rearranging the letters of another word. +Pleased with your finding, you spend the rest of the day generating hundreds of anagrams. diff --git a/exercises/practice/anagram/.eslintrc b/exercises/practice/anagram/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/anagram/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/anagram/.gitignore b/exercises/practice/anagram/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/anagram/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/anagram/.meta/config.json b/exercises/practice/anagram/.meta/config.json index ffc69e358b..42cb3ee2a3 100644 --- a/exercises/practice/anagram/.meta/config.json +++ b/exercises/practice/anagram/.meta/config.json @@ -1,12 +1,14 @@ { - "blurb": "Given a word and a list of possible anagrams, select the correct sublist.", - "authors": ["rchavarria"], + "authors": [ + "rchavarria" + ], "contributors": [ "amscotti", "ankorGH", "draalger", "gabriel376", "gargrave", + "jagdish-15", "kytrinyx", "matthewmorgan", "ovidiu141", @@ -15,10 +17,23 @@ "tejasbubane" ], "files": { - "solution": ["anagram.js"], - "test": ["anagram.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "anagram.js" + ], + "test": [ + "anagram.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Given a word and a list of possible anagrams, select the correct sublist.", "source": "Inspired by the Extreme Startup game", - "source_url": "https://github.com/rchatley/extreme_startup" + "source_url": "https://github.com/rchatley/extreme_startup", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/anagram/.meta/proof.ci.js b/exercises/practice/anagram/.meta/proof.ci.js index 0147356fdc..ac53a2c23c 100644 --- a/exercises/practice/anagram/.meta/proof.ci.js +++ b/exercises/practice/anagram/.meta/proof.ci.js @@ -7,6 +7,6 @@ export const findAnagrams = (subject, candidates) => { const wordsCopy = Array.isArray(candidates) ? candidates : [...candidates]; return wordsCopy.filter( (candidate) => - !sameWord(subject, candidate) && isAnagram(subject, candidate) + !sameWord(subject, candidate) && isAnagram(subject, candidate), ); }; diff --git a/exercises/practice/anagram/.meta/tests.toml b/exercises/practice/anagram/.meta/tests.toml index 7fb93f71b5..4d90562705 100644 --- a/exercises/practice/anagram/.meta/tests.toml +++ b/exercises/practice/anagram/.meta/tests.toml @@ -1,12 +1,24 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [dd40c4d2-3c8b-44e5-992a-f42b393ec373] description = "no matches" [b3cca662-f50a-489e-ae10-ab8290a09bdc] description = "detects two anagrams" +include = false + +[03eb9bbe-8906-4ea0-84fa-ffe711b52c8b] +description = "detects two anagrams" +reimplements = "b3cca662-f50a-489e-ae10-ab8290a09bdc" [a27558ee-9ba0-4552-96b1-ecf665b06556] description = "does not detect anagram subsets" @@ -34,12 +46,41 @@ description = "detects anagrams using case-insensitive possible matches" [7cc195ad-e3c7-44ee-9fd2-d3c344806a2c] description = "does not detect an anagram if the original word is repeated" +include = false + +[630abb71-a94e-4715-8395-179ec1df9f91] +description = "does not detect an anagram if the original word is repeated" +reimplements = "7cc195ad-e3c7-44ee-9fd2-d3c344806a2c" [9878a1c9-d6ea-4235-ae51-3ea2befd6842] description = "anagrams must use all letters exactly once" [85757361-4535-45fd-ac0e-3810d40debc1] description = "words are not anagrams of themselves (case-insensitive)" +include = false + +[68934ed0-010b-4ef9-857a-20c9012d1ebf] +description = "words are not anagrams of themselves" +reimplements = "85757361-4535-45fd-ac0e-3810d40debc1" + +[589384f3-4c8a-4e7d-9edc-51c3e5f0c90e] +description = "words are not anagrams of themselves even if letter case is partially different" +reimplements = "85757361-4535-45fd-ac0e-3810d40debc1" + +[ba53e423-7e02-41ee-9ae2-71f91e6d18e6] +description = "words are not anagrams of themselves even if letter case is completely different" +reimplements = "85757361-4535-45fd-ac0e-3810d40debc1" [a0705568-628c-4b55-9798-82e4acde51ca] description = "words other than themselves can be anagrams" +include = false + +[33d3f67e-fbb9-49d3-a90e-0beb00861da7] +description = "words other than themselves can be anagrams" +reimplements = "a0705568-628c-4b55-9798-82e4acde51ca" + +[a6854f66-eec1-4afd-a137-62ef2870c051] +description = "handles case of greek letters" + +[fd3509e5-e3ba-409d-ac3d-a9ac84d13296] +description = "different characters may have the same bytes" diff --git a/exercises/practice/anagram/anagram.js b/exercises/practice/anagram/anagram.js index 0733d01302..2b63d207d5 100644 --- a/exercises/practice/anagram/anagram.js +++ b/exercises/practice/anagram/anagram.js @@ -4,5 +4,5 @@ // export const findAnagrams = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/anagram/anagram.spec.js b/exercises/practice/anagram/anagram.spec.js index 59e73d546b..dc011aea9e 100644 --- a/exercises/practice/anagram/anagram.spec.js +++ b/exercises/practice/anagram/anagram.spec.js @@ -1,83 +1,144 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { findAnagrams } from './anagram'; +const areSetsEqual = (setA, setB) => + setA.size === setB.size && [...setA].every((val) => setB.has(val)); + describe('Anagram', () => { test('no matches', () => { - expect( - findAnagrams('diaper', ['hello', 'world', 'zombies', 'pants']) - ).toEqual([]); + const expected = []; + const actual = findAnagrams('diaper', [ + 'hello', + 'world', + 'zombies', + 'pants', + ]); + expect(areSetsEqual(new Set(expected), new Set(actual))).toEqual(true); }); xtest('detects two anagrams', () => { - expect(findAnagrams('master', ['stream', 'pigeon', 'maters'])).toEqual([ - 'stream', - 'maters', - ]); + const expected = ['lemons', 'melons']; + const actual = findAnagrams('solemn', ['lemons', 'cherry', 'melons']); + expect(areSetsEqual(new Set(expected), new Set(actual))).toEqual(true); }); xtest('does not detect anagram subsets', () => { - expect(findAnagrams('good', ['dog', 'goody'])).toEqual([]); + const expected = []; + const actual = findAnagrams('good', ['dog', 'goody']); + expect(areSetsEqual(new Set(expected), new Set(actual))).toEqual(true); }); xtest('detects anagram', () => { - expect( - findAnagrams('listen', ['enlists', 'google', 'inlets', 'banana']) - ).toEqual(['inlets']); + const expected = ['inlets']; + const actual = findAnagrams('listen', [ + 'enlists', + 'google', + 'inlets', + 'banana', + ]); + expect(areSetsEqual(new Set(expected), new Set(actual))).toEqual(true); }); xtest('detects three anagrams', () => { - expect( - findAnagrams('allergy', [ - 'gallery', - 'ballerina', - 'regally', - 'clergy', - 'largely', - 'leading', - ]) - ).toEqual(['gallery', 'regally', 'largely']); + const expected = ['gallery', 'regally', 'largely']; + const actual = findAnagrams('allergy', [ + 'gallery', + 'ballerina', + 'regally', + 'clergy', + 'largely', + 'leading', + ]); + expect(areSetsEqual(new Set(expected), new Set(actual))).toEqual(true); }); xtest('detects multiple anagrams with different case', () => { - expect(findAnagrams('nose', ['Eons', 'ONES'])).toEqual(['Eons', 'ONES']); + const expected = ['Eons', 'ONES']; + const actual = findAnagrams('nose', ['Eons', 'ONES']); + expect(areSetsEqual(new Set(expected), new Set(actual))).toEqual(true); }); xtest('does not detect non-anagrams with identical checksum', () => { - expect(findAnagrams('mass', ['last'])).toEqual([]); + const expected = []; + const actual = findAnagrams('mass', ['last']); + expect(areSetsEqual(new Set(expected), new Set(actual))).toEqual(true); }); xtest('detects anagrams case-insensitively', () => { - expect( - findAnagrams('Orchestra', ['cashregister', 'Carthorse', 'radishes']) - ).toEqual(['Carthorse']); + const expected = ['Carthorse']; + const actual = findAnagrams('Orchestra', [ + 'cashregister', + 'Carthorse', + 'radishes', + ]); + expect(areSetsEqual(new Set(expected), new Set(actual))).toEqual(true); }); xtest('detects anagrams using case-insensitive subject', () => { - expect( - findAnagrams('Orchestra', ['cashregister', 'carthorse', 'radishes']) - ).toEqual(['carthorse']); + const expected = ['carthorse']; + const actual = findAnagrams('Orchestra', [ + 'cashregister', + 'carthorse', + 'radishes', + ]); + expect(areSetsEqual(new Set(expected), new Set(actual))).toEqual(true); }); xtest('detects anagrams using case-insensitive possible matches', () => { - expect( - findAnagrams('orchestra', ['cashregister', 'Carthorse', 'radishes']) - ).toEqual(['Carthorse']); + const expected = ['Carthorse']; + const actual = findAnagrams('orchestra', [ + 'cashregister', + 'Carthorse', + 'radishes', + ]); + expect(areSetsEqual(new Set(expected), new Set(actual))).toEqual(true); }); xtest('does not detect an anagram if the original word is repeated', () => { - expect(findAnagrams('go', ['go Go GO'])).toEqual([]); + const expected = []; + const actual = findAnagrams('go', ['goGoGO']); + expect(areSetsEqual(new Set(expected), new Set(actual))).toEqual(true); }); xtest('anagrams must use all letters exactly once', () => { - expect(findAnagrams('tapper', ['patter'])).toEqual([]); + const expected = []; + const actual = findAnagrams('tapper', ['patter']); + expect(areSetsEqual(new Set(expected), new Set(actual))).toEqual(true); }); - xtest('words are not anagrams of themselves (case-insensitive)', () => { - expect(findAnagrams('BANANA', ['BANANA', 'Banana', 'banana'])).toEqual([]); + xtest('words are not anagrams of themselves', () => { + const expected = []; + const actual = findAnagrams('BANANA', ['BANANA']); + expect(areSetsEqual(new Set(expected), new Set(actual))).toEqual(true); + }); + + xtest('words are not anagrams of themselves even if letter case is partially different', () => { + const expected = []; + const actual = findAnagrams('BANANA', ['Banana']); + expect(areSetsEqual(new Set(expected), new Set(actual))).toEqual(true); + }); + + xtest('words are not anagrams of themselves even if letter case is completely different', () => { + const expected = []; + const actual = findAnagrams('BANANA', ['banana']); + expect(areSetsEqual(new Set(expected), new Set(actual))).toEqual(true); }); xtest('words other than themselves can be anagrams', () => { - expect(findAnagrams('LISTEN', ['Listen', 'Silent', 'LISTEN'])).toEqual([ - 'Silent', - ]); + const expected = ['Silent']; + const actual = findAnagrams('LISTEN', ['LISTEN', 'Silent']); + expect(areSetsEqual(new Set(expected), new Set(actual))).toEqual(true); + }); + + xtest('handles case of greek letters', () => { + const expected = ['ΒΓΑ', 'γβα']; + const actual = findAnagrams('ΑΒΓ', ['ΒΓΑ', 'ΒΓΔ', 'γβα', 'αβγ']); + expect(areSetsEqual(new Set(expected), new Set(actual))).toEqual(true); + }); + + xtest('different characters may have the same bytes', () => { + const expected = []; + const actual = findAnagrams('a⬂', ['€a']); + expect(areSetsEqual(new Set(expected), new Set(actual))).toEqual(true); }); }); diff --git a/exercises/practice/anagram/babel.config.js b/exercises/practice/anagram/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/anagram/babel.config.js +++ b/exercises/practice/anagram/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/anagram/eslint.config.mjs b/exercises/practice/anagram/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/anagram/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/anagram/jest.config.js b/exercises/practice/anagram/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/anagram/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/anagram/package.json b/exercises/practice/anagram/package.json index de66a890c6..6c36cc946c 100644 --- a/exercises/practice/anagram/package.json +++ b/exercises/practice/anagram/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/anagram" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/armstrong-numbers/.docs/instructions.append.md b/exercises/practice/armstrong-numbers/.docs/instructions.append.md new file mode 100644 index 0000000000..2d121967ea --- /dev/null +++ b/exercises/practice/armstrong-numbers/.docs/instructions.append.md @@ -0,0 +1,6 @@ + +~~~~exercism/note +Some of the tests might pass a `BigInt` as input. +Ensure that your implementation can handle such cases. +~~~~ + diff --git a/exercises/practice/armstrong-numbers/.docs/instructions.md b/exercises/practice/armstrong-numbers/.docs/instructions.md index b5c46e6fe2..5e56bbe465 100644 --- a/exercises/practice/armstrong-numbers/.docs/instructions.md +++ b/exercises/practice/armstrong-numbers/.docs/instructions.md @@ -1,6 +1,6 @@ # Instructions -An [Armstrong number](https://en.wikipedia.org/wiki/Narcissistic_number) is a number that is the sum of its own digits each raised to the power of the number of digits. +An [Armstrong number][armstrong-number] is a number that is the sum of its own digits each raised to the power of the number of digits. For example: @@ -10,3 +10,5 @@ For example: - 154 is _not_ an Armstrong number, because: `154 != 1^3 + 5^3 + 4^3 = 1 + 125 + 64 = 190` Write some code to determine whether a number is an Armstrong number. + +[armstrong-number]: https://en.wikipedia.org/wiki/Narcissistic_number diff --git a/exercises/practice/armstrong-numbers/.eslintrc b/exercises/practice/armstrong-numbers/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/armstrong-numbers/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/armstrong-numbers/.gitignore b/exercises/practice/armstrong-numbers/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/armstrong-numbers/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/armstrong-numbers/.meta/config.json b/exercises/practice/armstrong-numbers/.meta/config.json index c6d603656b..2b559e8013 100644 --- a/exercises/practice/armstrong-numbers/.meta/config.json +++ b/exercises/practice/armstrong-numbers/.meta/config.json @@ -1,19 +1,34 @@ { - "blurb": "Determine if a number is an Armstrong number", - "authors": ["PakkuDon"], + "authors": [ + "PakkuDon" + ], "contributors": [ "ankorGH", "gargrave", "hayashi-ay", + "jagdish-15", "ovidiu141", "SleeplessByte", "xarxziux" ], "files": { - "solution": ["armstrong-numbers.js"], - "test": ["armstrong-numbers.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "armstrong-numbers.js" + ], + "test": [ + "armstrong-numbers.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Determine if a number is an Armstrong number.", "source": "Wikipedia", - "source_url": "https://en.wikipedia.org/wiki/Narcissistic_number" + "source_url": "https://en.wikipedia.org/wiki/Narcissistic_number", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/armstrong-numbers/.meta/proof.ci.js b/exercises/practice/armstrong-numbers/.meta/proof.ci.js index 0fe35c82e6..720db16d0d 100644 --- a/exercises/practice/armstrong-numbers/.meta/proof.ci.js +++ b/exercises/practice/armstrong-numbers/.meta/proof.ci.js @@ -1,8 +1,12 @@ export const isArmstrongNumber = (input) => { - const digits = [...String(input)]; + const bigInput = BigInt(input); + + const digits = [...String(bigInput)]; + const sum = digits.reduce( - (total, current) => total + current ** digits.length, - 0 + (total, current) => total + BigInt(current) ** BigInt(digits.length), + BigInt(0), ); - return sum === input; + + return sum === bigInput; }; diff --git a/exercises/practice/armstrong-numbers/.meta/tests.toml b/exercises/practice/armstrong-numbers/.meta/tests.toml index fdada6d1ef..b3f09e4cc9 100644 --- a/exercises/practice/armstrong-numbers/.meta/tests.toml +++ b/exercises/practice/armstrong-numbers/.meta/tests.toml @@ -1,30 +1,43 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [c1ed103c-258d-45b2-be73-d8c6d9580c7b] description = "Zero is an Armstrong number" [579e8f03-9659-4b85-a1a2-d64350f6b17a] -description = "Single digit numbers are Armstrong numbers" +description = "Single-digit numbers are Armstrong numbers" [2d6db9dc-5bf8-4976-a90b-b2c2b9feba60] -description = "There are no 2 digit Armstrong numbers" +description = "There are no two-digit Armstrong numbers" [509c087f-e327-4113-a7d2-26a4e9d18283] -description = "Three digit number that is an Armstrong number" +description = "Three-digit number that is an Armstrong number" [7154547d-c2ce-468d-b214-4cb953b870cf] -description = "Three digit number that is not an Armstrong number" +description = "Three-digit number that is not an Armstrong number" [6bac5b7b-42e9-4ecb-a8b0-4832229aa103] -description = "Four digit number that is an Armstrong number" +description = "Four-digit number that is an Armstrong number" [eed4b331-af80-45b5-a80b-19c9ea444b2e] -description = "Four digit number that is not an Armstrong number" +description = "Four-digit number that is not an Armstrong number" [f971ced7-8d68-4758-aea1-d4194900b864] -description = "Seven digit number that is an Armstrong number" +description = "Seven-digit number that is an Armstrong number" [7ee45d52-5d35-4fbd-b6f1-5c8cd8a67f18] -description = "Seven digit number that is not an Armstrong number" +description = "Seven-digit number that is not an Armstrong number" + +[5ee2fdf8-334e-4a46-bb8d-e5c19c02c148] +description = "Armstrong number containing seven zeroes" + +[12ffbf10-307a-434e-b4ad-c925680e1dd4] +description = "The largest and last Armstrong number" diff --git a/exercises/practice/armstrong-numbers/armstrong-numbers.js b/exercises/practice/armstrong-numbers/armstrong-numbers.js index d3ff1cdc3b..31eba9f6f4 100644 --- a/exercises/practice/armstrong-numbers/armstrong-numbers.js +++ b/exercises/practice/armstrong-numbers/armstrong-numbers.js @@ -4,5 +4,5 @@ // export const isArmstrongNumber = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/armstrong-numbers/armstrong-numbers.spec.js b/exercises/practice/armstrong-numbers/armstrong-numbers.spec.js index 192664789e..4d380e8b7d 100644 --- a/exercises/practice/armstrong-numbers/armstrong-numbers.spec.js +++ b/exercises/practice/armstrong-numbers/armstrong-numbers.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { isArmstrongNumber } from './armstrong-numbers'; describe('Armstrong Numbers', () => { @@ -5,35 +6,45 @@ describe('Armstrong Numbers', () => { expect(isArmstrongNumber(0)).toEqual(true); }); - xtest('Single digit numbers are Armstrong numbers', () => { + xtest('Single-digit numbers are Armstrong numbers', () => { expect(isArmstrongNumber(5)).toEqual(true); }); - xtest('There are no 2 digit Armstrong numbers', () => { + xtest('There are no two-digit Armstrong numbers', () => { expect(isArmstrongNumber(10)).toEqual(false); }); - xtest('Three digit number that is an Armstrong number', () => { + xtest('Three-digit number that is an Armstrong number', () => { expect(isArmstrongNumber(153)).toEqual(true); }); - xtest('Three digit number that is not an Armstrong number', () => { + xtest('Three-digit number that is not an Armstrong number', () => { expect(isArmstrongNumber(100)).toEqual(false); }); - xtest('Four digit number that is an Armstrong number', () => { + xtest('Four-digit number that is an Armstrong number', () => { expect(isArmstrongNumber(9474)).toEqual(true); }); - xtest('Four digit number that is not an Armstrong number', () => { + xtest('Four-digit number that is not an Armstrong number', () => { expect(isArmstrongNumber(9475)).toEqual(false); }); - xtest('Seven digit number that is an Armstrong number', () => { + xtest('Seven-digit number that is an Armstrong number', () => { expect(isArmstrongNumber(9926315)).toEqual(true); }); - xtest('Seven digit number that is not an Armstrong number', () => { + xtest('Seven-digit number that is not an Armstrong number', () => { expect(isArmstrongNumber(9926314)).toEqual(false); }); + + xtest('Armstrong number containing seven zeroes', () => { + const bigInput = 186709961001538790100634132976990n; + expect(isArmstrongNumber(bigInput)).toEqual(true); + }); + + xtest('The largest and last Armstrong number', () => { + const bigInput = 115132219018763992565095597973971522401n; + expect(isArmstrongNumber(bigInput)).toEqual(true); + }); }); diff --git a/exercises/practice/armstrong-numbers/babel.config.js b/exercises/practice/armstrong-numbers/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/armstrong-numbers/babel.config.js +++ b/exercises/practice/armstrong-numbers/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/armstrong-numbers/eslint.config.mjs b/exercises/practice/armstrong-numbers/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/armstrong-numbers/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/armstrong-numbers/jest.config.js b/exercises/practice/armstrong-numbers/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/armstrong-numbers/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/armstrong-numbers/package.json b/exercises/practice/armstrong-numbers/package.json index 378926f583..9fc3297273 100644 --- a/exercises/practice/armstrong-numbers/package.json +++ b/exercises/practice/armstrong-numbers/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/armstrong-numbers" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/atbash-cipher/.docs/instructions.md b/exercises/practice/atbash-cipher/.docs/instructions.md index 2f712b1592..1e7627b1e5 100644 --- a/exercises/practice/atbash-cipher/.docs/instructions.md +++ b/exercises/practice/atbash-cipher/.docs/instructions.md @@ -1,11 +1,9 @@ # Instructions -Create an implementation of the atbash cipher, an ancient encryption system created in the Middle East. +Create an implementation of the Atbash cipher, an ancient encryption system created in the Middle East. -The Atbash cipher is a simple substitution cipher that relies on -transposing all the letters in the alphabet such that the resulting -alphabet is backwards. The first letter is replaced with the last -letter, the second with the second-last, and so on. +The Atbash cipher is a simple substitution cipher that relies on transposing all the letters in the alphabet such that the resulting alphabet is backwards. +The first letter is replaced with the last letter, the second with the second-last, and so on. An Atbash cipher for the Latin alphabet would be as follows: @@ -14,16 +12,16 @@ Plain: abcdefghijklmnopqrstuvwxyz Cipher: zyxwvutsrqponmlkjihgfedcba ``` -It is a very weak cipher because it only has one possible key, and it is -a simple monoalphabetic substitution cipher. However, this may not have -been an issue in the cipher's time. +It is a very weak cipher because it only has one possible key, and it is a simple mono-alphabetic substitution cipher. +However, this may not have been an issue in the cipher's time. -Ciphertext is written out in groups of fixed length, the traditional group size -being 5 letters, and punctuation is excluded. This is to make it harder to guess -things based on word boundaries. +Ciphertext is written out in groups of fixed length, the traditional group size being 5 letters, leaving numbers unchanged, and punctuation is excluded. +This is to make it harder to guess things based on word boundaries. +All text will be encoded as lowercase letters. ## Examples - Encoding `test` gives `gvhg` +- Encoding `x123 yes` gives `c123b vh` - Decoding `gvhg` gives `test` - Decoding `gsvjf rxpyi ldmul cqfnk hlevi gsvoz abwlt` gives `thequickbrownfoxjumpsoverthelazydog` diff --git a/exercises/practice/atbash-cipher/.eslintrc b/exercises/practice/atbash-cipher/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/atbash-cipher/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/atbash-cipher/.gitignore b/exercises/practice/atbash-cipher/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/atbash-cipher/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/atbash-cipher/.meta/config.json b/exercises/practice/atbash-cipher/.meta/config.json index 4a63ba66ec..13d409345e 100644 --- a/exercises/practice/atbash-cipher/.meta/config.json +++ b/exercises/practice/atbash-cipher/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Create an implementation of the atbash cipher, an ancient encryption system created in the Middle East.", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "Futuro212", "ovidiu141", @@ -11,10 +12,23 @@ "xarxziux" ], "files": { - "solution": ["atbash-cipher.js"], - "test": ["atbash-cipher.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "atbash-cipher.js" + ], + "test": [ + "atbash-cipher.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Create an implementation of the Atbash cipher, an ancient encryption system created in the Middle East.", "source": "Wikipedia", - "source_url": "http://en.wikipedia.org/wiki/Atbash" + "source_url": "https://en.wikipedia.org/wiki/Atbash", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/atbash-cipher/atbash-cipher.js b/exercises/practice/atbash-cipher/atbash-cipher.js index 18545fb900..ef98d1956a 100644 --- a/exercises/practice/atbash-cipher/atbash-cipher.js +++ b/exercises/practice/atbash-cipher/atbash-cipher.js @@ -4,9 +4,9 @@ // export const encode = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; export const decode = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/atbash-cipher/atbash-cipher.spec.js b/exercises/practice/atbash-cipher/atbash-cipher.spec.js index 250fa84a1f..fd68ae2ad2 100644 --- a/exercises/practice/atbash-cipher/atbash-cipher.spec.js +++ b/exercises/practice/atbash-cipher/atbash-cipher.spec.js @@ -1,4 +1,5 @@ -import { encode, decode } from './atbash-cipher'; +import { describe, expect, test, xtest } from '@jest/globals'; +import { decode, encode } from './atbash-cipher'; describe('Atbash Cipher', () => { describe('encode', () => { diff --git a/exercises/practice/atbash-cipher/babel.config.js b/exercises/practice/atbash-cipher/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/atbash-cipher/babel.config.js +++ b/exercises/practice/atbash-cipher/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/atbash-cipher/eslint.config.mjs b/exercises/practice/atbash-cipher/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/atbash-cipher/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/atbash-cipher/jest.config.js b/exercises/practice/atbash-cipher/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/atbash-cipher/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/atbash-cipher/package.json b/exercises/practice/atbash-cipher/package.json index 527d7c6b38..bb20418054 100644 --- a/exercises/practice/atbash-cipher/package.json +++ b/exercises/practice/atbash-cipher/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/atbash-cipher" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/bank-account/.docs/instructions.md b/exercises/practice/bank-account/.docs/instructions.md index 1265ac8b34..7398fbea18 100644 --- a/exercises/practice/bank-account/.docs/instructions.md +++ b/exercises/practice/bank-account/.docs/instructions.md @@ -1,27 +1,10 @@ # Instructions -Simulate a bank account supporting opening/closing, withdrawals, and deposits -of money. Watch out for concurrent transactions! +Your task is to implement bank accounts supporting opening/closing, withdrawals, and deposits of money. -A bank account can be accessed in multiple ways. Clients can make -deposits and withdrawals using the internet, mobile phones, etc. Shops -can charge against the account. +As bank accounts can be accessed in many different ways (internet, mobile phones, automatic charges), your bank software must allow accounts to be safely accessed from multiple threads/processes (terminology depends on your programming language) in parallel. +For example, there may be many deposits and withdrawals occurring in parallel; you need to ensure there are no [race conditions][wikipedia] between when you read the account balance and set the new balance. -Create an account that can be accessed from multiple threads/processes -(terminology depends on your programming language). +It should be possible to close an account; operations against a closed account must fail. -It should be possible to close an account; operations against a closed -account must fail. - -## Instructions - -Run the test file, and fix each of the errors in turn. When you get the -first test to pass, go to the first pending or skipped test, and make -that pass as well. When all of the tests are passing, feel free to -submit. - -Remember that passing code is just the first step. The goal is to work -towards a solution that is as readable and expressive as you can make -it. - -Have fun! +[wikipedia]: https://en.wikipedia.org/wiki/Race_condition#In_software diff --git a/exercises/practice/bank-account/.docs/introduction.md b/exercises/practice/bank-account/.docs/introduction.md new file mode 100644 index 0000000000..650b5d9c46 --- /dev/null +++ b/exercises/practice/bank-account/.docs/introduction.md @@ -0,0 +1,20 @@ +# Introduction + +After years of filling out forms and waiting, you've finally acquired your banking license. +This means you are now officially eligible to open your own bank, hurray! + +Your first priority is to get the IT systems up and running. +After a day of hard work, you can already open and close accounts, as well as handle withdrawals and deposits. + +Since you couldn't be bothered writing tests, you invite some friends to help test the system. +However, after just five minutes, one of your friends claims they've lost money! +While you're confident your code is bug-free, you start looking through the logs to investigate. + +Ah yes, just as you suspected, your friend is at fault! +They shared their test credentials with another friend, and together they conspired to make deposits and withdrawals from the same account _in parallel_. +Who would do such a thing? + +While you argue that it's physically _impossible_ for someone to access their account in parallel, your friend smugly notifies you that the banking rules _require_ you to support this. +Thus, no parallel banking support, no go-live signal. +Sighing, you create a mental note to work on this tomorrow. +This will set your launch date back at _least_ one more day, but well... diff --git a/exercises/practice/bank-account/.eslintrc b/exercises/practice/bank-account/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/bank-account/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/bank-account/.gitignore b/exercises/practice/bank-account/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/bank-account/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/bank-account/.meta/config.json b/exercises/practice/bank-account/.meta/config.json index 57bba6b3b6..330828347b 100644 --- a/exercises/practice/bank-account/.meta/config.json +++ b/exercises/practice/bank-account/.meta/config.json @@ -1,10 +1,27 @@ { - "blurb": "Simulate a bank account supporting opening/closing, withdraws, and deposits of money. Watch out for concurrent transactions!", - "authors": ["TomPradat"], - "contributors": ["SleeplessByte"], + "authors": [ + "TomPradat" + ], + "contributors": [ + "jagdish-15", + "SleeplessByte" + ], "files": { - "solution": ["bank-account.js"], - "test": ["bank-account.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "bank-account.js" + ], + "test": [ + "bank-account.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] + }, + "blurb": "Simulate a bank account supporting opening/closing, withdraws, and deposits of money. Watch out for concurrent transactions!", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false } } diff --git a/exercises/practice/bank-account/.meta/tests.toml b/exercises/practice/bank-account/.meta/tests.toml new file mode 100644 index 0000000000..4e42d4dcb5 --- /dev/null +++ b/exercises/practice/bank-account/.meta/tests.toml @@ -0,0 +1,61 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[983a1528-4ceb-45e5-8257-8ce01aceb5ed] +description = "Newly opened account has zero balance" + +[e88d4ec3-c6bf-4752-8e59-5046c44e3ba7] +description = "Single deposit" + +[3d9147d4-63f4-4844-8d2b-1fee2e9a2a0d] +description = "Multiple deposits" + +[08f1af07-27ae-4b38-aa19-770bde558064] +description = "Withdraw once" + +[6f6d242f-8c31-4ac6-8995-a90d42cad59f] +description = "Withdraw twice" + +[45161c94-a094-4c77-9cec-998b70429bda] +description = "Can do multiple operations sequentially" + +[f9facfaa-d824-486e-8381-48832c4bbffd] +description = "Cannot check balance of closed account" + +[7a65ba52-e35c-4fd2-8159-bda2bde6e59c] +description = "Cannot deposit into closed account" + +[a0a1835d-faae-4ad4-a6f3-1fcc2121380b] +description = "Cannot deposit into unopened account" + +[570dfaa5-0532-4c1f-a7d3-0f65c3265608] +description = "Cannot withdraw from closed account" + +[c396d233-1c49-4272-98dc-7f502dbb9470] +description = "Cannot close an account that was not opened" + +[c06f534f-bdc2-4a02-a388-1063400684de] +description = "Cannot open an already opened account" + +[0722d404-6116-4f92-ba3b-da7f88f1669c] +description = "Reopened account does not retain balance" + +[ec42245f-9361-4341-8231-a22e8d19c52f] +description = "Cannot withdraw more than deposited" + +[4f381ef8-10ef-4507-8e1d-0631ecc8ee72] +description = "Cannot withdraw negative" + +[d45df9ea-1db0-47f3-b18c-d365db49d938] +description = "Cannot deposit negative" + +[ba0c1e0b-0f00-416f-8097-a7dfc97871ff] +description = "Can handle concurrent transactions" diff --git a/exercises/practice/bank-account/babel.config.js b/exercises/practice/bank-account/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/bank-account/babel.config.js +++ b/exercises/practice/bank-account/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/bank-account/bank-account.js b/exercises/practice/bank-account/bank-account.js index 8c2448180a..7ce11bf802 100644 --- a/exercises/practice/bank-account/bank-account.js +++ b/exercises/practice/bank-account/bank-account.js @@ -5,27 +5,27 @@ export class BankAccount { constructor() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } open() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } close() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } deposit() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } withdraw() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get balance() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/bank-account/bank-account.spec.js b/exercises/practice/bank-account/bank-account.spec.js index d7ff125ae5..7b2664b6f3 100644 --- a/exercises/practice/bank-account/bank-account.spec.js +++ b/exercises/practice/bank-account/bank-account.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { BankAccount, ValueError } from './bank-account'; describe('Bank Account', () => { @@ -7,14 +8,14 @@ describe('Bank Account', () => { expect(account.balance).toEqual(0); }); - test('can deposit money', () => { + xtest('Single deposit', () => { const account = new BankAccount(); account.open(); account.deposit(100); expect(account.balance).toEqual(100); }); - test('can deposit money sequentially', () => { + xtest('Multiple deposits"', () => { const account = new BankAccount(); account.open(); account.deposit(100); @@ -22,7 +23,7 @@ describe('Bank Account', () => { expect(account.balance).toEqual(150); }); - test('can withdraw money', () => { + xtest('Withdraw once', () => { const account = new BankAccount(); account.open(); account.deposit(100); @@ -30,7 +31,7 @@ describe('Bank Account', () => { expect(account.balance).toEqual(50); }); - test('can withdraw money sequentially', () => { + xtest('Withdraw twice', () => { const account = new BankAccount(); account.open(); account.deposit(100); @@ -39,14 +40,25 @@ describe('Bank Account', () => { expect(account.balance).toEqual(0); }); - test('checking balance of closed account throws error', () => { + xtest('Can do multiple operations sequentially', () => { + const account = new BankAccount(); + account.open(); + account.deposit(100); + account.deposit(110); + account.withdraw(200); + account.deposit(60); + account.withdraw(50); + expect(account.balance).toEqual(20); + }); + + xtest('Cannot check balance of closed account', () => { const account = new BankAccount(); account.open(); account.close(); expect(() => account.balance).toThrow(ValueError); }); - test('deposit into closed account throws error', () => { + xtest('Cannot deposit into closed account', () => { const account = new BankAccount(); account.open(); account.close(); @@ -55,7 +67,14 @@ describe('Bank Account', () => { }).toThrow(ValueError); }); - test('withdraw from closed account throws error', () => { + xtest('Cannot deposit into closed account', () => { + const account = new BankAccount(); + expect(() => { + account.deposit(50); + }).toThrow(ValueError); + }); + + xtest('Cannot withdraw from closed account', () => { const account = new BankAccount(); account.open(); account.close(); @@ -64,14 +83,14 @@ describe('Bank Account', () => { }).toThrow(ValueError); }); - test('close already closed account throws error', () => { + xtest('Cannot close an account that was not opened', () => { const account = new BankAccount(); expect(() => { account.close(); }).toThrow(ValueError); }); - test('open already opened account throws error', () => { + xtest('Cannot open an already opened account', () => { const account = new BankAccount(); account.open(); expect(() => { @@ -79,7 +98,7 @@ describe('Bank Account', () => { }).toThrow(ValueError); }); - test('reopened account does not retain balance', () => { + xtest('Reopened account does not retain balance', () => { const account = new BankAccount(); account.open(); account.deposit(50); @@ -88,7 +107,7 @@ describe('Bank Account', () => { expect(account.balance).toEqual(0); }); - test('cannot withdraw more than deposited', () => { + xtest('Cannot withdraw more than deposited', () => { const account = new BankAccount(); account.open(); account.deposit(25); @@ -97,7 +116,7 @@ describe('Bank Account', () => { }).toThrow(ValueError); }); - test('cannot withdraw negative amount', () => { + xtest('Cannot withdraw negative', () => { const account = new BankAccount(); account.open(); account.deposit(100); @@ -106,7 +125,7 @@ describe('Bank Account', () => { }).toThrow(ValueError); }); - test('cannot deposit negative amount', () => { + xtest('Cannot deposit negative', () => { const account = new BankAccount(); account.open(); expect(() => { @@ -114,7 +133,40 @@ describe('Bank Account', () => { }).toThrow(ValueError); }); - test('changing balance directly throws error', () => { + xtest('Can handle concurrent transactions', async () => { + const account = new BankAccount(); + account.open(); + account.deposit(1000); + + for (let i = 0; i < 10; i++) { + await adjustBalanceConcurrently(account); + expect(account.balance).toEqual(1000); + } + }); + + function adjustBalanceConcurrently(account) { + const random = () => Math.floor(Math.random() * 10); + + const tasks = Array.from( + { length: 1000 }, + () => + new Promise((resolve) => { + try { + account.deposit(5); + setTimeout(() => { + account.withdraw(5); + resolve(); + }, random()); + } catch (e) { + throw new Error(`Exception should not be thrown: ${e.message}`); + } + }), + ); + + return Promise.all(tasks); + } + + xtest('Changing balance directly throws error', () => { const account = new BankAccount(); account.open(); expect(() => { diff --git a/exercises/practice/bank-account/eslint.config.mjs b/exercises/practice/bank-account/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/bank-account/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/bank-account/jest.config.js b/exercises/practice/bank-account/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/bank-account/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/bank-account/package.json b/exercises/practice/bank-account/package.json index 6765acde99..8ceca36715 100644 --- a/exercises/practice/bank-account/package.json +++ b/exercises/practice/bank-account/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/bank-account" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/beer-song/.docs/instructions.md b/exercises/practice/beer-song/.docs/instructions.md index 5680128293..e909cfe317 100644 --- a/exercises/practice/beer-song/.docs/instructions.md +++ b/exercises/practice/beer-song/.docs/instructions.md @@ -305,17 +305,3 @@ Take it down and pass it around, no more bottles of beer on the wall. No more bottles of beer on the wall, no more bottles of beer. Go to the store and buy some more, 99 bottles of beer on the wall. ``` - -## For bonus points - -Did you get the tests passing and the code clean? If you want to, these -are some additional things you could try: - -- Remove as much duplication as you possibly can. -- Optimize for readability, even if it means introducing duplication. -- If you've removed all the duplication, do you have a lot of - conditionals? Try replacing the conditionals with polymorphism, if it - applies in this language. How readable is it? - -Then please share your thoughts in a comment on the submission. Did this -experiment make the code better? Worse? Did you learn anything from it? diff --git a/exercises/practice/beer-song/.eslintrc b/exercises/practice/beer-song/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/beer-song/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/beer-song/.gitignore b/exercises/practice/beer-song/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/beer-song/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/beer-song/.meta/config.json b/exercises/practice/beer-song/.meta/config.json index fe4ca5ce5c..dd543caec9 100644 --- a/exercises/practice/beer-song/.meta/config.json +++ b/exercises/practice/beer-song/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Produce the lyrics to that beloved classic, that field-trip favorite: 99 Bottles of Beer on the Wall.", - "authors": ["rchavarria"], + "authors": [ + "rchavarria" + ], "contributors": [ "ankorGH", "draalger", @@ -14,10 +15,23 @@ "tejasbubane" ], "files": { - "solution": ["beer-song.js"], - "test": ["beer-song.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "beer-song.js" + ], + "test": [ + "beer-song.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Produce the lyrics to that beloved classic, that field-trip favorite: 99 Bottles of Beer on the Wall.", "source": "Learn to Program by Chris Pine", - "source_url": "http://pine.fm/LearnToProgram/?Chapter=06" + "source_url": "https://pine.fm/LearnToProgram/?Chapter=06", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/beer-song/.meta/proof.ci.js b/exercises/practice/beer-song/.meta/proof.ci.js index 1f38385c64..92db51db27 100644 --- a/exercises/practice/beer-song/.meta/proof.ci.js +++ b/exercises/practice/beer-song/.meta/proof.ci.js @@ -25,13 +25,13 @@ function nextVerse(currentVerse) { function nextBottle(currentVerse) { return `${bottles( - nextVerse(currentVerse) + nextVerse(currentVerse), ).toLowerCase()} of beer on the wall.`; } function verse(number) { const line1 = `${bottles(number)} of beer on the wall, ${bottles( - number + number, ).toLowerCase()} of beer.`; const line2 = action(number) + nextBottle(number); diff --git a/exercises/practice/beer-song/babel.config.js b/exercises/practice/beer-song/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/beer-song/babel.config.js +++ b/exercises/practice/beer-song/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/beer-song/beer-song.js b/exercises/practice/beer-song/beer-song.js index 8340bc23bd..0c93f00a4e 100644 --- a/exercises/practice/beer-song/beer-song.js +++ b/exercises/practice/beer-song/beer-song.js @@ -4,5 +4,5 @@ // export const recite = (initialBottlesCount, takeDownCount) => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/beer-song/beer-song.spec.js b/exercises/practice/beer-song/beer-song.spec.js index c02ee0d55d..9738c5fe25 100644 --- a/exercises/practice/beer-song/beer-song.spec.js +++ b/exercises/practice/beer-song/beer-song.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { recite } from './beer-song'; describe('Beer Song', () => { diff --git a/exercises/practice/beer-song/eslint.config.mjs b/exercises/practice/beer-song/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/beer-song/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/beer-song/jest.config.js b/exercises/practice/beer-song/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/beer-song/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/beer-song/package.json b/exercises/practice/beer-song/package.json index 063b59886e..3b3c916bc2 100644 --- a/exercises/practice/beer-song/package.json +++ b/exercises/practice/beer-song/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/beer-song" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/binary-search-tree/.docs/instructions.md b/exercises/practice/binary-search-tree/.docs/instructions.md index ba3c42eb67..7625220e9a 100644 --- a/exercises/practice/binary-search-tree/.docs/instructions.md +++ b/exercises/practice/binary-search-tree/.docs/instructions.md @@ -2,53 +2,69 @@ Insert and search for numbers in a binary tree. -When we need to represent sorted data, an array does not make a good -data structure. - -Say we have the array `[1, 3, 4, 5]`, and we add 2 to it so it becomes -`[1, 3, 4, 5, 2]` now we must sort the entire array again! We can -improve on this by realizing that we only need to make space for the new -item `[1, nil, 3, 4, 5]`, and then adding the item in the space we -added. But this still requires us to shift many elements down by one. - -Binary Search Trees, however, can operate on sorted data much more -efficiently. - -A binary search tree consists of a series of connected nodes. Each node -contains a piece of data (e.g. the number 3), a variable named `left`, -and a variable named `right`. The `left` and `right` variables point at -`nil`, or other nodes. Since these other nodes in turn have other nodes -beneath them, we say that the left and right variables are pointing at -subtrees. All data in the left subtree is less than or equal to the -current node's data, and all data in the right subtree is greater than -the current node's data. - -For example, if we had a node containing the data 4, and we added the -data 2, our tree would look like this: +When we need to represent sorted data, an array does not make a good data structure. +Say we have the array `[1, 3, 4, 5]`, and we add 2 to it so it becomes `[1, 3, 4, 5, 2]`. +Now we must sort the entire array again! +We can improve on this by realizing that we only need to make space for the new item `[1, nil, 3, 4, 5]`, and then adding the item in the space we added. +But this still requires us to shift many elements down by one. + +Binary Search Trees, however, can operate on sorted data much more efficiently. + +A binary search tree consists of a series of connected nodes. +Each node contains a piece of data (e.g. the number 3), a variable named `left`, and a variable named `right`. +The `left` and `right` variables point at `nil`, or other nodes. +Since these other nodes in turn have other nodes beneath them, we say that the left and right variables are pointing at subtrees. +All data in the left subtree is less than or equal to the current node's data, and all data in the right subtree is greater than the current node's data. + +For example, if we had a node containing the data 4, and we added the data 2, our tree would look like this: + +![A graph with root node 4 and a single child node 2.](https://assets.exercism.org/images/exercises/binary-search-tree/tree-4-2.svg) + +```text 4 / 2 +``` If we then added 6, it would look like this: +![A graph with root node 4 and two child nodes 2 and 6.](https://assets.exercism.org/images/exercises/binary-search-tree/tree-4-2-6.svg) + +```text 4 / \ 2 6 +``` If we then added 3, it would look like this +![A graph with root node 4, two child nodes 2 and 6, and a grandchild node 3.](https://assets.exercism.org/images/exercises/binary-search-tree/tree-4-2-6-3.svg) + +```text 4 / \ 2 6 \ 3 +``` And if we then added 1, 5, and 7, it would look like this +![A graph with root node 4, two child nodes 2 and 6, and four grandchild nodes 1, 3, 5 and 7.](https://assets.exercism.org/images/exercises/binary-search-tree/tree-4-2-6-1-3-5-7.svg) + +```text 4 / \ / \ 2 6 / \ / \ 1 3 5 7 +``` + +## Credit + +The images were created by [habere-et-dispertire][habere-et-dispertire] using [PGF/TikZ][pgf-tikz] by Till Tantau. + +[habere-et-dispertire]: https://exercism.org/profiles/habere-et-dispertire +[pgf-tikz]: https://en.wikipedia.org/wiki/PGF/TikZ diff --git a/exercises/practice/binary-search-tree/.eslintrc b/exercises/practice/binary-search-tree/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/binary-search-tree/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/binary-search-tree/.gitignore b/exercises/practice/binary-search-tree/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/binary-search-tree/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/binary-search-tree/.meta/config.json b/exercises/practice/binary-search-tree/.meta/config.json index f3f331ec60..21afb3ab79 100644 --- a/exercises/practice/binary-search-tree/.meta/config.json +++ b/exercises/practice/binary-search-tree/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Insert and search for numbers in a binary tree.", - "authors": ["rchavarria"], + "authors": [ + "rchavarria" + ], "contributors": [ "ankorGH", "Futuro212", @@ -10,10 +11,22 @@ "tejasbubane" ], "files": { - "solution": ["binary-search-tree.js"], - "test": ["binary-search-tree.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "binary-search-tree.js" + ], + "test": [ + "binary-search-tree.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Insert and search for numbers in a binary tree.", "source": "Josh Cheek", - "source_url": "https://twitter.com/josh_cheek" + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/binary-search-tree/babel.config.js b/exercises/practice/binary-search-tree/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/binary-search-tree/babel.config.js +++ b/exercises/practice/binary-search-tree/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/binary-search-tree/binary-search-tree.js b/exercises/practice/binary-search-tree/binary-search-tree.js index eeb74c3a3e..bcfa41475c 100644 --- a/exercises/practice/binary-search-tree/binary-search-tree.js +++ b/exercises/practice/binary-search-tree/binary-search-tree.js @@ -5,25 +5,25 @@ export class BinarySearchTree { constructor() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get data() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get right() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get left() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } insert() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } each() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/binary-search-tree/binary-search-tree.spec.js b/exercises/practice/binary-search-tree/binary-search-tree.spec.js index 658451d1a0..1f207a0a1b 100644 --- a/exercises/practice/binary-search-tree/binary-search-tree.spec.js +++ b/exercises/practice/binary-search-tree/binary-search-tree.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { BinarySearchTree } from './binary-search-tree'; function recordAllData(bst) { diff --git a/exercises/practice/binary-search-tree/eslint.config.mjs b/exercises/practice/binary-search-tree/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/binary-search-tree/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/binary-search-tree/jest.config.js b/exercises/practice/binary-search-tree/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/binary-search-tree/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/binary-search-tree/package.json b/exercises/practice/binary-search-tree/package.json index 47aa541ad2..84e3dd0864 100644 --- a/exercises/practice/binary-search-tree/package.json +++ b/exercises/practice/binary-search-tree/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/binary-search-tree" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/binary-search/.docs/instructions.md b/exercises/practice/binary-search/.docs/instructions.md index 4dcaba726a..12f4358ebc 100644 --- a/exercises/practice/binary-search/.docs/instructions.md +++ b/exercises/practice/binary-search/.docs/instructions.md @@ -1,35 +1,29 @@ # Instructions -Implement a binary search algorithm. +Your task is to implement a binary search algorithm. -Searching a sorted collection is a common task. A dictionary is a sorted -list of word definitions. Given a word, one can find its definition. A -telephone book is a sorted list of people's names, addresses, and -telephone numbers. Knowing someone's name allows one to quickly find -their telephone number and address. +A binary search algorithm finds an item in a list by repeatedly splitting it in half, only keeping the half which contains the item we're looking for. +It allows us to quickly narrow down the possible locations of our item until we find it, or until we've eliminated all possible locations. -If the list to be searched contains more than a few items (a dozen, say) -a binary search will require far fewer comparisons than a linear search, -but it imposes the requirement that the list be sorted. +~~~~exercism/caution +Binary search only works when a list has been sorted. +~~~~ -In computer science, a binary search or half-interval search algorithm -finds the position of a specified input value (the search "key") within -an array sorted by key value. +The algorithm looks like this: -In each step, the algorithm compares the search key value with the key -value of the middle element of the array. +- Find the middle element of a _sorted_ list and compare it with the item we're looking for. +- If the middle element is our item, then we're done! +- If the middle element is greater than our item, we can eliminate that element and all the elements **after** it. +- If the middle element is less than our item, we can eliminate that element and all the elements **before** it. +- If every element of the list has been eliminated then the item is not in the list. +- Otherwise, repeat the process on the part of the list that has not been eliminated. -If the keys match, then a matching element has been found and its index, -or position, is returned. +Here's an example: -Otherwise, if the search key is less than the middle element's key, then -the algorithm repeats its action on the sub-array to the left of the -middle element or, if the search key is greater, on the sub-array to the -right. +Let's say we're looking for the number 23 in the following sorted list: `[4, 8, 12, 16, 23, 28, 32]`. -If the remaining array to be searched is empty, then the key cannot be -found in the array and a special "not found" indication is returned. - -A binary search halves the number of items to check with each iteration, -so locating an item (or determining its absence) takes logarithmic time. -A binary search is a dichotomic divide and conquer search algorithm. +- We start by comparing 23 with the middle element, 16. +- Since 23 is greater than 16, we can eliminate the left half of the list, leaving us with `[23, 28, 32]`. +- We then compare 23 with the new middle element, 28. +- Since 23 is less than 28, we can eliminate the right half of the list: `[23]`. +- We've found our item. diff --git a/exercises/practice/binary-search/.docs/introduction.md b/exercises/practice/binary-search/.docs/introduction.md new file mode 100644 index 0000000000..03496599e7 --- /dev/null +++ b/exercises/practice/binary-search/.docs/introduction.md @@ -0,0 +1,13 @@ +# Introduction + +You have stumbled upon a group of mathematicians who are also singer-songwriters. +They have written a song for each of their favorite numbers, and, as you can imagine, they have a lot of favorite numbers (like [0][zero] or [73][seventy-three] or [6174][kaprekars-constant]). + +You are curious to hear the song for your favorite number, but with so many songs to wade through, finding the right song could take a while. +Fortunately, they have organized their songs in a playlist sorted by the title — which is simply the number that the song is about. + +You realize that you can use a binary search algorithm to quickly find a song given the title. + +[zero]: https://en.wikipedia.org/wiki/0 +[seventy-three]: https://en.wikipedia.org/wiki/73_(number) +[kaprekars-constant]: https://en.wikipedia.org/wiki/6174_(number) diff --git a/exercises/practice/binary-search/.eslintrc b/exercises/practice/binary-search/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/binary-search/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/binary-search/.gitignore b/exercises/practice/binary-search/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/binary-search/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/binary-search/.meta/config.json b/exercises/practice/binary-search/.meta/config.json index 9b2a456afb..fcd3cd81e4 100644 --- a/exercises/practice/binary-search/.meta/config.json +++ b/exercises/practice/binary-search/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Implement a binary search algorithm.", - "authors": ["rchavarria"], + "authors": [ + "rchavarria" + ], "contributors": [ "Futuro212", "matthewmorgan", @@ -10,10 +11,23 @@ "tejasbubane" ], "files": { - "solution": ["binary-search.js"], - "test": ["binary-search.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "binary-search.js" + ], + "test": [ + "binary-search.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Implement a binary search algorithm.", "source": "Wikipedia", - "source_url": "http://en.wikipedia.org/wiki/Binary_search_algorithm" + "source_url": "https://en.wikipedia.org/wiki/Binary_search_algorithm", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/binary-search/babel.config.js b/exercises/practice/binary-search/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/binary-search/babel.config.js +++ b/exercises/practice/binary-search/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/binary-search/binary-search.js b/exercises/practice/binary-search/binary-search.js index 88e2fa3063..4589a19e66 100644 --- a/exercises/practice/binary-search/binary-search.js +++ b/exercises/practice/binary-search/binary-search.js @@ -4,5 +4,5 @@ // export const find = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/binary-search/binary-search.spec.js b/exercises/practice/binary-search/binary-search.spec.js index 0444899681..a216ba93a2 100644 --- a/exercises/practice/binary-search/binary-search.spec.js +++ b/exercises/practice/binary-search/binary-search.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { find } from './binary-search'; describe('Binary Search', () => { diff --git a/exercises/practice/binary-search/eslint.config.mjs b/exercises/practice/binary-search/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/binary-search/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/binary-search/jest.config.js b/exercises/practice/binary-search/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/binary-search/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/binary-search/package.json b/exercises/practice/binary-search/package.json index 2c3d66f48b..9a466afa0c 100644 --- a/exercises/practice/binary-search/package.json +++ b/exercises/practice/binary-search/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/binary-search" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/binary/.eslintrc b/exercises/practice/binary/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/binary/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/binary/.gitignore b/exercises/practice/binary/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/binary/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/binary/.meta/config.json b/exercises/practice/binary/.meta/config.json index 31688be834..6f34e8da2f 100644 --- a/exercises/practice/binary/.meta/config.json +++ b/exercises/practice/binary/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Convert a binary number, represented as a string (e.g. '101010'), to its decimal equivalent using first principles", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "ankorGH", "Futuro212", @@ -10,10 +11,23 @@ "tejasbubane" ], "files": { - "solution": ["binary.js"], - "test": ["binary.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "binary.js" + ], + "test": [ + "binary.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Convert a binary number, represented as a string (e.g. '101010'), to its decimal equivalent using first principles", "source": "All of Computer Science", - "source_url": "http://www.wolframalpha.com/input/?i=binary&a=*C.binary-_*MathWorld-" + "source_url": "http://www.wolframalpha.com/input/?i=binary&a=*C.binary-_*MathWorld-", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/binary/babel.config.js b/exercises/practice/binary/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/binary/babel.config.js +++ b/exercises/practice/binary/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/binary/binary.js b/exercises/practice/binary/binary.js index dfbca26ec8..d5fc4e68e5 100644 --- a/exercises/practice/binary/binary.js +++ b/exercises/practice/binary/binary.js @@ -5,10 +5,10 @@ export class Binary { constructor() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } toDecimal() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/binary/binary.spec.js b/exercises/practice/binary/binary.spec.js index 4cc07f4fc1..415daed688 100644 --- a/exercises/practice/binary/binary.spec.js +++ b/exercises/practice/binary/binary.spec.js @@ -1,3 +1,4 @@ +import { describe, test, expect, xtest } from '@jest/globals'; import { Binary } from './binary'; describe('binary', () => { diff --git a/exercises/practice/binary/eslint.config.mjs b/exercises/practice/binary/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/binary/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/binary/jest.config.js b/exercises/practice/binary/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/binary/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/binary/package.json b/exercises/practice/binary/package.json index bf2dd45810..53268bf5fd 100644 --- a/exercises/practice/binary/package.json +++ b/exercises/practice/binary/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/binary" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/bob/.approaches/answer-array/content.md b/exercises/practice/bob/.approaches/answer-array/content.md new file mode 100644 index 0000000000..8063c663d9 --- /dev/null +++ b/exercises/practice/bob/.approaches/answer-array/content.md @@ -0,0 +1,73 @@ +# Answer array + +```javascript +const answers = [ + 'Whatever.', + 'Sure.', + 'Whoa, chill out!', + "Calm down, I know what I'm doing!", +]; + +export function hey(message) { + const speech = message.trimEnd(); + if (speech == '') { + return 'Fine. Be that way!'; + } + + const isQuestion = speech.endsWith('?') ? 1 : 0; + const isShout = + /[A-Z]{1}/.test(speech) && speech == speech.toUpperCase() ? 2 : 0; + return answers[isQuestion + isShout]; +} +``` + +In this approach you define an array that contains Bob’s answers, and each condition is given a score. +The correct answer is selected from the array by using the score as the array index. + +The [`String`][string] [trimEnd][trimend] method is applied to the input to eliminate any whitespace at the end of the input. +If the string has no characters left, it returns the response for saying nothing. + + +~~~~exercism/caution +Note that a `null` or `undefined` `String` would be different from a `String` of all whitespace. +A `null` or `undefined` `String` would raise a `TypeError` if `trimEnd` were applied to it. +~~~~ + + +The first half of the shout condition + +``` +/[A-Z]{1}/.test(speech) +``` + +is constructed from a [regular expression pattern][regex] to ensure there is at least one uppercase letter character in the `String`. +This is because the second half of the condition tests that the uppercased input is the same as the input. +If the input were only `"123"` it would equal itself uppercased, but without letters it would not be a shout. + +The uppercasing is done by using the `String` method [toUpperCase][touppercase]. + +The conditions of being a question and being a shout are assigned scores through the use of the [ternary operator][ternary]. +For example, giving a question a score of `1` would use an index of `1` to get the element from the answers array, which is `"Sure."`. + +| isShout | isQuestion | Index | Answer | +| ------- | ---------- | --------- | ------------------------------------- | +| `false` | `false` | 0 + 0 = 0 | `"Whatever."` | +| `false` | `true` | 0 + 1 = 1 | `"Sure."` | +| `true` | `false` | 2 + 0 = 2 | `"Whoa, chill out!"` | +| `true` | `true` | 2 + 1 = 3 | `"Calm down, I know what I'm doing!"` | + +## Shortening + +Note that when the body of an `if` statement is a short single line, both the test expression and the body could be put on the same line, like so + +```javascript +if (speech == '') return 'Fine. Be that way!'; +``` + +It may not comply with some coding styles, but your team preferences may allow it. + +[string]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String +[trimend]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimEnd +[regex]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions +[touppercase]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase +[ternary]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator diff --git a/exercises/practice/bob/.approaches/answer-array/snippet.txt b/exercises/practice/bob/.approaches/answer-array/snippet.txt new file mode 100644 index 0000000000..757cacabef --- /dev/null +++ b/exercises/practice/bob/.approaches/answer-array/snippet.txt @@ -0,0 +1,8 @@ +const answers = ["Whatever.", "Sure.", "Whoa, chill out!", "Calm down, I know what I'm doing!"]; + +const speech = message.trim() +if (speech == "") return "Fine. Be that way!" + +const isQuestion = speech.endsWith("?") ? 1: 0 +const isShout = /[A-Z]{1}/.test(speech) && speech == speech.toUpperCase() ? 2 : 0 +return answers[isQuestion + isShout] diff --git a/exercises/practice/bob/.approaches/config.json b/exercises/practice/bob/.approaches/config.json new file mode 100644 index 0000000000..6bb7d60e90 --- /dev/null +++ b/exercises/practice/bob/.approaches/config.json @@ -0,0 +1,36 @@ +{ + "introduction": { + "authors": [ + "bobahop" + ] + }, + "approaches": [ + { + "uuid": "a4775bb1-cad6-4523-b9ec-d0dfda2e98ad", + "slug": "if-statements", + "title": "If statements", + "blurb": "Use if statements to return the answer.", + "authors": [ + "bobahop" + ] + }, + { + "uuid": "a439f836-57c3-4b99-995f-d76a0321e4b8", + "slug": "switch-statement", + "title": "Switch statement", + "blurb": "Use a switch to return the answer.", + "authors": [ + "bobahop" + ] + }, + { + "uuid": "ad78cacc-d3e1-4b3d-a2e1-5488495f590c", + "slug": "answer-array", + "title": "Answer array", + "blurb": "Index into an array to return the answer.", + "authors": [ + "bobahop" + ] + } + ] +} diff --git a/exercises/practice/bob/.approaches/if-statements/content.md b/exercises/practice/bob/.approaches/if-statements/content.md new file mode 100644 index 0000000000..1f73996e4e --- /dev/null +++ b/exercises/practice/bob/.approaches/if-statements/content.md @@ -0,0 +1,77 @@ +# `if` statements + +```javascript +export function hey(message) { + const speech = message.trimEnd(); + if (speech == "") { + return "Fine. Be that way!"; + } + + const isQuestion = speech.endsWith("?"); + const isShout = /[A-Z]{1}/.test(speech) && speech == speech.toUpperCase(); + + if (isShout) { + return isQuestion + ? "Calm down, I know what I'm doing!" + : "Whoa, chill out!"; + } + if (isQuestion) { + return "Sure."; + } + return "Whatever."; +``` + +In this approach you have a series of `if` statements using the private methods to evaluate the conditions. +As soon as the right condition is found, the correct response is returned. + + +~~~~exercism/note +Note that there are no `else if` or `else` statements. +If an `if` statement can return, then an `else if` or `else` is not needed. +Execution will either return or will continue to the next statement anyway. +~~~~ + + +The [`String`][string] [trimEnd][trimend] method is applied to the input to eliminate any whitespace at the end of the input. +If the string has no characters left, it returns the response for saying nothing. + +The `String` method [endsWith][endswith] is used to determine if the input ends with a question mark. + + +~~~~exercism/caution +Note that a `null` or `undefined` `String` would be different from a `String` of all whitespace. +A `null` or `undefined` `String` would raise a `TypeError` if `trimEnd` were applied to it. +~~~~ + + +The first half of the shout condition + +```javascript +/[A-Z]{1}/.test(speech); +``` + +is constructed from a [regular expression pattern][regex] to ensure there is at least one uppercase letter character in the `String`. +This is because the second half of the condition tests that the uppercased input is the same as the input. +If the input were only `"123"` it would equal itself uppercased, but without letters it would not be a shout. + +The uppercasing is done by using the `String` method [toUpperCase][touppercase]. + +If `isShout ` is `true`, a [ternary operator][ternary] is used to return the response for whether `isQuestion` is also `true`, +or only `isShout` is true. + +## Shortening + +Note that when the body of an `if` statement is a short single line, both the test expression and the body could be put on the same line, like so + +```javascript +if (speech == '') return 'Fine. Be that way!'; +``` + +It may not comply with some coding styles, but your team preferences may allow it. + +[string]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String +[trimend]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimEnd +[endswith]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith +[regex]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions +[touppercase]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase +[ternary]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator diff --git a/exercises/practice/bob/.approaches/if-statements/snippet.txt b/exercises/practice/bob/.approaches/if-statements/snippet.txt new file mode 100644 index 0000000000..aee2a14553 --- /dev/null +++ b/exercises/practice/bob/.approaches/if-statements/snippet.txt @@ -0,0 +1,7 @@ +if (speech == "") + return "Fine. Be that way!" +if (isShout) + return isQuestion? "Calm down, I know what I'm doing!" : "Whoa, chill out!" +if (isQuestion) + return "Sure." +return "Whatever." diff --git a/exercises/practice/bob/.approaches/introduction.md b/exercises/practice/bob/.approaches/introduction.md new file mode 100644 index 0000000000..c566425f6b --- /dev/null +++ b/exercises/practice/bob/.approaches/introduction.md @@ -0,0 +1,101 @@ +# Introduction + +There are various idiomatic approaches to solve Bob. +A basic approach can use a series of `if` statements to test the conditions. +Or a `switch` statement can be used. +An array can contain answers from which the right response is selected by an index calculated from scores given to the conditions. + +## General guidance + +Regardless of the approach used, some things you could look out for include + +- If the input is trimmed, [trim][trim] only once. + +- Use the [endsWith][endswith] `String` method instead of checking the last character by index for `?`. + +- Don't copy/paste the logic for determining a shout and for determing a question into determing a shouted question. + Combine the two determinations instead of copying them. + Not duplicating the code will keep the code [DRY][dry]. + +- Perhaps consider making `IsQuestion` and `IsShout` values set once instead of functions that are possibly called twice. + +- If an `if` statement can return, then an `else if` or `else` is not needed. + Execution will either return or will continue to the next statement anyway. + +- If the body of an `if` statement is only one line, curly braces aren't needed. + Some teams may still require them in their style guidelines, though. + +## Approach: `if` statements + +```javascript +export function hey(message) { + const speech = message.trimEnd(); + if (speech == "") { + return "Fine. Be that way!"; + } + + const isQuestion = speech.endsWith("?"); + const isShout = /[A-Z]{1}/.test(speech) && speech == speech.toUpperCase(); + + if (isShout) { + return isQuestion + ? "Calm down, I know what I'm doing!" + : "Whoa, chill out!"; + } + if (isQuestion) { + return "Sure."; + } + return "Whatever."; +``` + +For more information, check the [`if` statements approach][approach-if]. + +## Approach: `switch` statement + +```javascript +export function hey(message) { + const speech = message.trim(); + if (speech == '') { + return 'Fine. Be that way!'; + } + + const isQuestion = speech.endsWith('?'); + const isShout = /[A-Z]{1}/.test(speech) && speech == speech.toUpperCase(); + + switch (true) { + case isQuestion && isShout: + return "Calm down, I know what I'm doing!"; + case isShout: + return 'Whoa, chill out!'; + case isQuestion: + return 'Sure.'; + default: + return 'Whatever.'; + } +} +``` + +For more information, check the [`switch` statement approach][approach-switch]. + +## Other approaches + +Besides the aforementioned, idiomatic approaches, you could also approach the exercise as follows: + +### Other approach: answer array + +An array can be defined that contains Bob’s answers, and each condition is given a score. +The correct answer is selected from the array by using the score as the array index. +For more information, check the [Answer array approach][approach-answer-array]. + +## Which approach to use? + +Which to use is pretty much a matter of personal preference. +Some may prefer the answer array approach to avoid the series of `if` statements or `switch` cases, +but some may find the answer array approach to be less readable. + +[trim]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trim +[endswith]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith +[dry]: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself +[approach-if]: https://exercism.org/tracks/javascript/exercises/bob/approaches/if-statements +[approach-switch]: https://exercism.org/tracks/javascript/exercises/bob/approaches/switch-statement +[approach-answer-array]: https://exercism.org/tracks/javascript/exercises/bob/approaches/answer-array diff --git a/exercises/practice/bob/.approaches/switch-statement/content.md b/exercises/practice/bob/.approaches/switch-statement/content.md new file mode 100644 index 0000000000..c683b96123 --- /dev/null +++ b/exercises/practice/bob/.approaches/switch-statement/content.md @@ -0,0 +1,69 @@ +# `switch` statement + +```javascript +export function hey(message) { + const speech = message.trim(); + if (speech == '') { + return 'Fine. Be that way!'; + } + + const isQuestion = speech.endsWith('?'); + const isShout = /[A-Z]{1}/.test(speech) && speech == speech.toUpperCase(); + + switch (true) { + case isQuestion && isShout: + return "Calm down, I know what I'm doing!"; + case isShout: + return 'Whoa, chill out!'; + case isQuestion: + return 'Sure.'; + default: + return 'Whatever.'; + } +} +``` + +In this approach you use a [`switch`][switch] statement to test if there is a question or a shout. +The `switch` returns the right response for a question, shout, shouted question, or for not being a shout or question. + +The [`String`][string] [trimEnd][trimend] method is applied to the input to eliminate any whitespace at the end of the input. +If the string has no characters left, it returns the response for saying nothing. + + +~~~~exercism/caution +Note that a `null` or `undefined` `String` would be different from a `String` of all whitespace. +A `null` or `undefined` `String` would raise a `TypeError` if `trimEnd` were applied to it. +~~~~ + + +The first half of the shout condition + +```javascript +/[A-Z]{1}/.test(speech); +``` + +is constructed from a [regular expression pattern][regex] to ensure there is at least one uppercase letter character in the `String`. +This is because the second half of the condition tests that the uppercased input is the same as the input. +If the input were only `"123"` it would equal itself uppercased, but without letters it would not be a shout. + +The uppercasing is done by using the `String` method [toUpperCase][touppercase]. + +The `isQuestion` and `isShout` values are tested in a `switch`. +If neither `isQuestion` and `isShout` are `true`, the `default` arm of the `switch` returns the response when the input is neither a question nor a shout. + +## Shortening + +Note that when the body of an `if` statement is a short single line, both the test expression and the body could be put on the same line, like so + +```javascript +if (input == '') return 'Fine. Be that way!'; +``` + +It may not comply with some coding styles, but your team preferences may allow it. + +[switch]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch +[string]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String +[trimend]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimEnd +[regex]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions +[touppercase]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase +[ternary]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator diff --git a/exercises/practice/bob/.approaches/switch-statement/snippet.txt b/exercises/practice/bob/.approaches/switch-statement/snippet.txt new file mode 100644 index 0000000000..737789bc09 --- /dev/null +++ b/exercises/practice/bob/.approaches/switch-statement/snippet.txt @@ -0,0 +1,7 @@ +switch (true) { + case isQuestion && isShout: + return "Calm down, I know what I'm doing!" + case isShout: + return "Whoa, chill out!" + case isQuestion: + return "Sure." diff --git a/exercises/practice/bob/.docs/instructions.md b/exercises/practice/bob/.docs/instructions.md index edddb1413d..bb702f7bbe 100644 --- a/exercises/practice/bob/.docs/instructions.md +++ b/exercises/practice/bob/.docs/instructions.md @@ -1,16 +1,19 @@ # Instructions -Bob is a lackadaisical teenager. In conversation, his responses are very limited. +Your task is to determine what Bob will reply to someone when they say something to him or ask him a question. -Bob answers 'Sure.' if you ask him a question, such as "How are you?". +Bob only ever answers one of five things: -He answers 'Whoa, chill out!' if you YELL AT HIM (in all capitals). - -He answers 'Calm down, I know what I'm doing!' if you yell a question at him. - -He says 'Fine. Be that way!' if you address him without actually saying -anything. - -He answers 'Whatever.' to anything else. - -Bob's conversational partner is a purist when it comes to written communication and always follows normal rules regarding sentence punctuation in English. +- **"Sure."** + This is his response if you ask him a question, such as "How are you?" + The convention used for questions is that it ends with a question mark. +- **"Whoa, chill out!"** + This is his answer if you YELL AT HIM. + The convention used for yelling is ALL CAPITAL LETTERS. +- **"Calm down, I know what I'm doing!"** + This is what he says if you yell a question at him. +- **"Fine. Be that way!"** + This is how he responds to silence. + The convention used for silence is nothing, or various combinations of whitespace characters. +- **"Whatever."** + This is what he answers to anything else. diff --git a/exercises/practice/bob/.docs/introduction.md b/exercises/practice/bob/.docs/introduction.md new file mode 100644 index 0000000000..ea4a80776b --- /dev/null +++ b/exercises/practice/bob/.docs/introduction.md @@ -0,0 +1,10 @@ +# Introduction + +Bob is a [lackadaisical][] teenager. +He likes to think that he's very cool. +And he definitely doesn't get excited about things. +That wouldn't be cool. + +When people talk to him, his responses are pretty limited. + +[lackadaisical]: https://www.collinsdictionary.com/dictionary/english/lackadaisical diff --git a/exercises/practice/bob/.eslintrc b/exercises/practice/bob/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/bob/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/bob/.gitignore b/exercises/practice/bob/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/bob/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/bob/.meta/config.json b/exercises/practice/bob/.meta/config.json index bda68b7f3e..153830ffee 100644 --- a/exercises/practice/bob/.meta/config.json +++ b/exercises/practice/bob/.meta/config.json @@ -1,12 +1,14 @@ { - "blurb": "Bob is a lackadaisical teenager. In conversation, his responses are very limited.", - "authors": ["rchavarria"], + "authors": [ + "rchavarria" + ], "contributors": [ "amscotti", "austinratcliff", "brendan-c", "draalger", "hayashi-ay", + "jagdish-15", "kytrinyx", "matthewmorgan", "ryanplusplus", @@ -15,10 +17,23 @@ "xarxziux" ], "files": { - "solution": ["bob.js"], - "test": ["bob.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "bob.js" + ], + "test": [ + "bob.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Bob is a lackadaisical teenager. In conversation, his responses are very limited.", "source": "Inspired by the 'Deaf Grandma' exercise in Chris Pine's Learn to Program tutorial.", - "source_url": "http://pine.fm/LearnToProgram/?Chapter=06" + "source_url": "https://pine.fm/LearnToProgram/?Chapter=06", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/bob/.meta/tests.toml b/exercises/practice/bob/.meta/tests.toml index 6304855792..5299e2895f 100644 --- a/exercises/practice/bob/.meta/tests.toml +++ b/exercises/practice/bob/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [e162fead-606f-437a-a166-d051915cea8e] description = "stating something" @@ -64,6 +71,7 @@ description = "alternate silence" [66953780-165b-4e7e-8ce3-4bcb80b6385a] description = "multiple line question" +include = false [5371ef75-d9ea-4103-bcfa-2da973ddec1b] description = "starting with whitespace" @@ -76,3 +84,7 @@ description = "other whitespace" [12983553-8601-46a8-92fa-fcaa3bc4a2a0] description = "non-question ending with whitespace" + +[2c7278ac-f955-4eb4-bf8f-e33eb4116a15] +description = "multiple line question" +reimplements = "66953780-165b-4e7e-8ce3-4bcb80b6385a" diff --git a/exercises/practice/bob/babel.config.js b/exercises/practice/bob/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/bob/babel.config.js +++ b/exercises/practice/bob/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/bob/bob.js b/exercises/practice/bob/bob.js index cc8a6fe97a..fde9cb9c80 100644 --- a/exercises/practice/bob/bob.js +++ b/exercises/practice/bob/bob.js @@ -4,5 +4,5 @@ // export const hey = (message) => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/bob/bob.spec.js b/exercises/practice/bob/bob.spec.js index eeba00d1a1..f423c26044 100644 --- a/exercises/practice/bob/bob.spec.js +++ b/exercises/practice/bob/bob.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { hey } from './bob'; describe('Bob', () => { @@ -102,8 +103,8 @@ describe('Bob', () => { }); xtest('multiple line question', () => { - const result = hey('\nDoes this cryogenic chamber make me look fat?\nno'); - expect(result).toEqual('Whatever.'); + const result = hey('\nDoes this cryogenic chamber make\n me look fat?'); + expect(result).toEqual('Sure.'); }); xtest('starting with whitespace', () => { diff --git a/exercises/practice/bob/eslint.config.mjs b/exercises/practice/bob/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/bob/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/bob/jest.config.js b/exercises/practice/bob/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/bob/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/bob/package.json b/exercises/practice/bob/package.json index accd28d85e..ce32b5b00f 100644 --- a/exercises/practice/bob/package.json +++ b/exercises/practice/bob/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/bob" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/book-store/.docs/instructions.append.md b/exercises/practice/book-store/.docs/instructions.append.md index 0f51b54f7e..251c2ed8ca 100644 --- a/exercises/practice/book-store/.docs/instructions.append.md +++ b/exercises/practice/book-store/.docs/instructions.append.md @@ -1,7 +1,7 @@ # Implementation -Define a function - `Cost` - that calculates the cost for a given list of books based on defined discounts. +Define a function - `cost` - that calculates the cost for a given list of books based on defined discounts. -Cost will return the total cost (after discounts) in cents. +`cost` will return the total cost (after discounts) in cents. For example, for a single book, the cost is 800 cents, which equals $8.00. Only integer calculations are necessary for this exercise. diff --git a/exercises/practice/book-store/.docs/instructions.md b/exercises/practice/book-store/.docs/instructions.md index 5d767b0360..54403f17bf 100644 --- a/exercises/practice/book-store/.docs/instructions.md +++ b/exercises/practice/book-store/.docs/instructions.md @@ -1,12 +1,10 @@ -# Description +# Instructions -To try and encourage more sales of different books from a popular 5 book -series, a bookshop has decided to offer discounts on multiple book purchases. +To try and encourage more sales of different books from a popular 5 book series, a bookshop has decided to offer discounts on multiple book purchases. One copy of any of the five books costs $8. -If, however, you buy two different books, you get a 5% -discount on those two books. +If, however, you buy two different books, you get a 5% discount on those two books. If you buy 3 different books, you get a 10% discount. @@ -14,14 +12,9 @@ If you buy 4 different books, you get a 20% discount. If you buy all 5, you get a 25% discount. -Note: that if you buy four books, of which 3 are -different titles, you get a 10% discount on the 3 that -form part of a set, but the fourth book still costs $8. +Note that if you buy four books, of which 3 are different titles, you get a 10% discount on the 3 that form part of a set, but the fourth book still costs $8. -Your mission is to write a piece of code to calculate the -price of any conceivable shopping basket (containing only -books of the same series), giving as big a discount as -possible. +Your mission is to write code to calculate the price of any conceivable shopping basket (containing only books of the same series), giving as big a discount as possible. For example, how much does this basket of books cost? @@ -33,36 +26,36 @@ For example, how much does this basket of books cost? One way of grouping these 8 books is: -- 1 group of 5 --> 25% discount (1st,2nd,3rd,4th,5th) -- +1 group of 3 --> 10% discount (1st,2nd,3rd) +- 1 group of 5 (1st, 2nd,3rd, 4th, 5th) +- 1 group of 3 (1st, 2nd, 3rd) This would give a total of: - 5 books at a 25% discount -- +3 books at a 10% discount +- 3 books at a 10% discount Resulting in: -- 5 _ (8 - 2.00) == 5 _ 6.00 == $30.00 -- +3 _ (8 - 0.80) == 3 _ 7.20 == $21.60 +- 5 × (100% - 25%) × $8 = 5 × $6.00 = $30.00, plus +- 3 × (100% - 10%) × $8 = 3 × $7.20 = $21.60 -For a total of $51.60 +Which equals $51.60. However, a different way to group these 8 books is: -- 1 group of 4 books --> 20% discount (1st,2nd,3rd,4th) -- +1 group of 4 books --> 20% discount (1st,2nd,3rd,5th) +- 1 group of 4 books (1st, 2nd, 3rd, 4th) +- 1 group of 4 books (1st, 2nd, 3rd, 5th) This would give a total of: - 4 books at a 20% discount -- +4 books at a 20% discount +- 4 books at a 20% discount Resulting in: -- 4 _ (8 - 1.60) == 4 _ 6.40 == $25.60 -- +4 _ (8 - 1.60) == 4 _ 6.40 == $25.60 +- 4 × (100% - 20%) × $8 = 4 × $6.40 = $25.60, plus +- 4 × (100% - 20%) × $8 = 4 × $6.40 = $25.60 -For a total of $51.20 +Which equals $51.20. And $51.20 is the price with the biggest discount. diff --git a/exercises/practice/book-store/.eslintrc b/exercises/practice/book-store/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/book-store/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/book-store/.gitignore b/exercises/practice/book-store/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/book-store/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/book-store/.meta/config.json b/exercises/practice/book-store/.meta/config.json index 6e6712003d..a27d472a8e 100644 --- a/exercises/practice/book-store/.meta/config.json +++ b/exercises/practice/book-store/.meta/config.json @@ -1,12 +1,25 @@ { - "blurb": "To try and encourage more sales of different books from a popular 5 book series, a bookshop has decided to offer discounts of multiple-book purchases.", - "authors": ["kmelow"], - "contributors": [], + "authors": [ + "kmelow" + ], "files": { - "solution": ["book-store.js"], - "test": ["book-store.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "book-store.js" + ], + "test": [ + "book-store.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "To try and encourage more sales of different books from a popular 5 book series, a bookshop has decided to offer discounts of multiple-book purchases.", "source": "Inspired by the harry potter kata from Cyber-Dojo.", - "source_url": "http://cyber-dojo.org" + "source_url": "https://cyber-dojo.org", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/book-store/.meta/tests.toml b/exercises/practice/book-store/.meta/tests.toml index 70d3f906a5..8241ee55f2 100644 --- a/exercises/practice/book-store/.meta/tests.toml +++ b/exercises/practice/book-store/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [17146bd5-2e80-4557-ab4c-05632b6b0d01] description = "Only a single book" @@ -33,19 +40,27 @@ description = "Two groups of four is cheaper than groups of five and three" description = "Group of four plus group of two is cheaper than two groups of three" [68ea9b78-10ad-420e-a766-836a501d3633] -description = "Two each of first 4 books and 1 copy each of rest" +description = "Two each of first four books and one copy each of rest" [c0a779d5-a40c-47ae-9828-a340e936b866] description = "Two copies of each book" [18fd86fe-08f1-4b68-969b-392b8af20513] -description = "Three copies of first book and 2 each of remaining" +description = "Three copies of first book and two each of remaining" [0b19a24d-e4cf-4ec8-9db2-8899a41af0da] -description = "Three each of first 2 books and 2 each of remaining books" +description = "Three each of first two books and two each of remaining books" [bb376344-4fb2-49ab-ab85-e38d8354a58d] description = "Four groups of four are cheaper than two groups each of five and three" [5260ddde-2703-4915-b45a-e54dbbac4303] description = "Check that groups of four are created properly even when there are more groups of three than groups of five" + +[b0478278-c551-4747-b0fc-7e0be3158b1f] +description = "One group of one and four is cheaper than one group of two and three" +include = false + +[cf868453-6484-4ae1-9dfc-f8ee85bbde01] +description = "One group of one and two plus three groups of four is cheaper than one group of each size" +include = false diff --git a/exercises/practice/book-store/babel.config.js b/exercises/practice/book-store/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/book-store/babel.config.js +++ b/exercises/practice/book-store/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/book-store/book-store.js b/exercises/practice/book-store/book-store.js index b8cc26b5af..11a6fb7c30 100644 --- a/exercises/practice/book-store/book-store.js +++ b/exercises/practice/book-store/book-store.js @@ -4,5 +4,5 @@ // export const cost = (books) => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/book-store/book-store.spec.js b/exercises/practice/book-store/book-store.spec.js index 107a6c594e..c0ae7bd61f 100644 --- a/exercises/practice/book-store/book-store.spec.js +++ b/exercises/practice/book-store/book-store.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { cost } from './book-store'; describe('Book Store', () => { diff --git a/exercises/practice/book-store/eslint.config.mjs b/exercises/practice/book-store/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/book-store/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/book-store/jest.config.js b/exercises/practice/book-store/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/book-store/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/book-store/package.json b/exercises/practice/book-store/package.json index 2b11d17d8b..6833611a17 100644 --- a/exercises/practice/book-store/package.json +++ b/exercises/practice/book-store/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/book-store" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/bottle-song/.docs/instructions.md b/exercises/practice/bottle-song/.docs/instructions.md new file mode 100644 index 0000000000..febdfc8639 --- /dev/null +++ b/exercises/practice/bottle-song/.docs/instructions.md @@ -0,0 +1,57 @@ +# Instructions + +Recite the lyrics to that popular children's repetitive song: Ten Green Bottles. + +Note that not all verses are identical. + +```text +Ten green bottles hanging on the wall, +Ten green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be nine green bottles hanging on the wall. + +Nine green bottles hanging on the wall, +Nine green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be eight green bottles hanging on the wall. + +Eight green bottles hanging on the wall, +Eight green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be seven green bottles hanging on the wall. + +Seven green bottles hanging on the wall, +Seven green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be six green bottles hanging on the wall. + +Six green bottles hanging on the wall, +Six green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be five green bottles hanging on the wall. + +Five green bottles hanging on the wall, +Five green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be four green bottles hanging on the wall. + +Four green bottles hanging on the wall, +Four green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be three green bottles hanging on the wall. + +Three green bottles hanging on the wall, +Three green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be two green bottles hanging on the wall. + +Two green bottles hanging on the wall, +Two green bottles hanging on the wall, +And if one green bottle should accidentally fall, +There'll be one green bottle hanging on the wall. + +One green bottle hanging on the wall, +One green bottle hanging on the wall, +And if one green bottle should accidentally fall, +There'll be no green bottles hanging on the wall. +``` diff --git a/exercises/practice/bottle-song/.gitignore b/exercises/practice/bottle-song/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/bottle-song/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/bottle-song/.meta/config.json b/exercises/practice/bottle-song/.meta/config.json new file mode 100644 index 0000000000..db4f3be94c --- /dev/null +++ b/exercises/practice/bottle-song/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "Cool-Katt" + ], + "files": { + "solution": [ + "bottle-song.js" + ], + "test": [ + "bottle-song.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] + }, + "blurb": "Produce the lyrics to the popular children's repetitive song: Ten Green Bottles.", + "source": "Wikipedia", + "source_url": "https://en.wikipedia.org/wiki/Ten_Green_Bottles" +} diff --git a/exercises/practice/bottle-song/.meta/proof.ci.js b/exercises/practice/bottle-song/.meta/proof.ci.js new file mode 100644 index 0000000000..81f8a4df42 --- /dev/null +++ b/exercises/practice/bottle-song/.meta/proof.ci.js @@ -0,0 +1,38 @@ +const NUMBERS = { + 10: 'ten', + 9: 'nine', + 8: 'eight', + 7: 'seven', + 6: 'six', + 5: 'five', + 4: 'four', + 3: 'three', + 2: 'two', + 1: 'one', + 0: 'no', +}; + +export const recite = (initialBottlesCount, takeDownCount = 1) => { + let out = []; + for ( + let i = initialBottlesCount; + i > initialBottlesCount - takeDownCount; + i-- + ) { + let plural = i === 1 ? '' : 's'; + let secondPlural = i - 1 === 1 ? '' : 's'; + out.push( + textsTemplate(cap(NUMBERS[i]), NUMBERS[i - 1], plural, secondPlural), + ); + if (initialBottlesCount - takeDownCount !== i - 1) out.push(''); + } + return out.flat(); +}; + +const textsTemplate = (curCount, prevCount, pluralOne, pluralTwo) => [ + ...Array(2).fill(`${curCount} green bottle${pluralOne} hanging on the wall,`), + `And if one green bottle should accidentally fall,`, + `There'll be ${prevCount} green bottle${pluralTwo} hanging on the wall.`, +]; + +const cap = (str) => str.replace(/./i, (char) => char.toUpperCase()); diff --git a/exercises/practice/bottle-song/.meta/tests.toml b/exercises/practice/bottle-song/.meta/tests.toml new file mode 100644 index 0000000000..1f6e40a37c --- /dev/null +++ b/exercises/practice/bottle-song/.meta/tests.toml @@ -0,0 +1,31 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[d4ccf8fc-01dc-48c0-a201-4fbeb30f2d03] +description = "verse -> single verse -> first generic verse" + +[0f0aded3-472a-4c64-b842-18d4f1f5f030] +description = "verse -> single verse -> last generic verse" + +[f61f3c97-131f-459e-b40a-7428f3ed99d9] +description = "verse -> single verse -> verse with 2 bottles" + +[05eadba9-5dbd-401e-a7e8-d17cc9baa8e0] +description = "verse -> single verse -> verse with 1 bottle" + +[a4a28170-83d6-4dc1-bd8b-319b6abb6a80] +description = "lyrics -> multiple verses -> first two verses" + +[3185d438-c5ac-4ce6-bcd3-02c9ff1ed8db] +description = "lyrics -> multiple verses -> last three verses" + +[28c1584a-0e51-4b65-9ae2-fbc0bf4bbb28] +description = "lyrics -> multiple verses -> all verses" diff --git a/exercises/practice/bottle-song/.npmrc b/exercises/practice/bottle-song/.npmrc new file mode 100644 index 0000000000..d26df800bb --- /dev/null +++ b/exercises/practice/bottle-song/.npmrc @@ -0,0 +1 @@ +audit=false diff --git a/exercises/practice/bottle-song/LICENSE b/exercises/practice/bottle-song/LICENSE new file mode 100644 index 0000000000..90e73be03b --- /dev/null +++ b/exercises/practice/bottle-song/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Exercism + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/exercises/practice/bottle-song/babel.config.js b/exercises/practice/bottle-song/babel.config.js new file mode 100644 index 0000000000..a638497df1 --- /dev/null +++ b/exercises/practice/bottle-song/babel.config.js @@ -0,0 +1,4 @@ +module.exports = { + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], +}; diff --git a/exercises/practice/bottle-song/bottle-song.js b/exercises/practice/bottle-song/bottle-song.js new file mode 100644 index 0000000000..4d05f10d57 --- /dev/null +++ b/exercises/practice/bottle-song/bottle-song.js @@ -0,0 +1,8 @@ +// +// This is only a SKELETON file for the 'Bottle Song' exercise. It's been provided as a +// convenience to get you started writing code faster. +// + +export const recite = (initialBottlesCount, takeDownCount) => { + throw new Error('Remove this line and implement the function'); +}; diff --git a/exercises/practice/bottle-song/bottle-song.spec.js b/exercises/practice/bottle-song/bottle-song.spec.js new file mode 100644 index 0000000000..75cde11eec --- /dev/null +++ b/exercises/practice/bottle-song/bottle-song.spec.js @@ -0,0 +1,142 @@ +import { describe, expect, test, xtest } from '@jest/globals'; +import { recite } from './bottle-song'; + +describe('Bottle Song', () => { + describe('verse', () => { + describe('single verse', () => { + test('first generic verse', () => { + let expected = [ + `Ten green bottles hanging on the wall,`, + `Ten green bottles hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be nine green bottles hanging on the wall.`, + ]; + expect(recite(10, 1)).toEqual(expected); + }); + + xtest('last generic verse', () => { + let expected = [ + `Three green bottles hanging on the wall,`, + `Three green bottles hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be two green bottles hanging on the wall.`, + ]; + expect(recite(3, 1)).toEqual(expected); + }); + + xtest('verse with 2 bottles', () => { + let expected = [ + `Two green bottles hanging on the wall,`, + `Two green bottles hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be one green bottle hanging on the wall.`, + ]; + expect(recite(2, 1)).toEqual(expected); + }); + + xtest('verse with 1 bottle', () => { + let expected = [ + `One green bottle hanging on the wall,`, + `One green bottle hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be no green bottles hanging on the wall.`, + ]; + expect(recite(1, 1)).toEqual(expected); + }); + }); + }); + + describe('lyrics', () => { + describe('multiple verses', () => { + xtest('first two verses', () => { + let expected = [ + `Ten green bottles hanging on the wall,`, + `Ten green bottles hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be nine green bottles hanging on the wall.`, + ``, + `Nine green bottles hanging on the wall,`, + `Nine green bottles hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be eight green bottles hanging on the wall.`, + ]; + expect(recite(10, 2)).toEqual(expected); + }); + + xtest('last three verses', () => { + let expected = [ + `Three green bottles hanging on the wall,`, + `Three green bottles hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be two green bottles hanging on the wall.`, + ``, + `Two green bottles hanging on the wall,`, + `Two green bottles hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be one green bottle hanging on the wall.`, + ``, + `One green bottle hanging on the wall,`, + `One green bottle hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be no green bottles hanging on the wall.`, + ]; + expect(recite(3, 3)).toEqual(expected); + }); + + xtest('all verses', () => { + let expected = [ + `Ten green bottles hanging on the wall,`, + `Ten green bottles hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be nine green bottles hanging on the wall.`, + ``, + `Nine green bottles hanging on the wall,`, + `Nine green bottles hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be eight green bottles hanging on the wall.`, + ``, + `Eight green bottles hanging on the wall,`, + `Eight green bottles hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be seven green bottles hanging on the wall.`, + ``, + `Seven green bottles hanging on the wall,`, + `Seven green bottles hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be six green bottles hanging on the wall.`, + ``, + `Six green bottles hanging on the wall,`, + `Six green bottles hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be five green bottles hanging on the wall.`, + ``, + `Five green bottles hanging on the wall,`, + `Five green bottles hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be four green bottles hanging on the wall.`, + ``, + `Four green bottles hanging on the wall,`, + `Four green bottles hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be three green bottles hanging on the wall.`, + ``, + `Three green bottles hanging on the wall,`, + `Three green bottles hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be two green bottles hanging on the wall.`, + ``, + `Two green bottles hanging on the wall,`, + `Two green bottles hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be one green bottle hanging on the wall.`, + ``, + `One green bottle hanging on the wall,`, + `One green bottle hanging on the wall,`, + `And if one green bottle should accidentally fall,`, + `There'll be no green bottles hanging on the wall.`, + ]; + expect(recite(10, 10)).toEqual(expected); + }); + }); + }); +}); diff --git a/exercises/practice/bottle-song/eslint.config.mjs b/exercises/practice/bottle-song/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/bottle-song/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/bottle-song/jest.config.js b/exercises/practice/bottle-song/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/bottle-song/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/bottle-song/package.json b/exercises/practice/bottle-song/package.json new file mode 100644 index 0000000000..af7073d600 --- /dev/null +++ b/exercises/practice/bottle-song/package.json @@ -0,0 +1,39 @@ +{ + "name": "@exercism/javascript-bottle-song", + "description": "Exercism practice exercise on bottle-song", + "author": "Katrina Owen", + "contributors": [ + "Cool-Katt (https://github.com/Cool-Katt)", + "Derk-Jan Karrenbeld (https://derk-jan.com)", + "Tejas Bubane (https://tejasbubane.github.io/)" + ], + "private": true, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/exercism/javascript", + "directory": "exercises/practice/bottle-song" + }, + "devDependencies": { + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" + }, + "dependencies": {}, + "scripts": { + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" +} diff --git a/exercises/practice/bowling/.docs/instructions.md b/exercises/practice/bowling/.docs/instructions.md index 6c6575a1b2..60ccad1b61 100644 --- a/exercises/practice/bowling/.docs/instructions.md +++ b/exercises/practice/bowling/.docs/instructions.md @@ -2,29 +2,24 @@ Score a bowling game. -Bowling is a game where players roll a heavy ball to knock down pins -arranged in a triangle. Write code to keep track of the score -of a game of bowling. +Bowling is a game where players roll a heavy ball to knock down pins arranged in a triangle. +Write code to keep track of the score of a game of bowling. ## Scoring Bowling -The game consists of 10 frames. A frame is composed of one or two ball -throws with 10 pins standing at frame initialization. There are three -cases for the tabulation of a frame. +The game consists of 10 frames. +A frame is composed of one or two ball throws with 10 pins standing at frame initialization. +There are three cases for the tabulation of a frame. -- An open frame is where a score of less than 10 is recorded for the - frame. In this case the score for the frame is the number of pins - knocked down. +- An open frame is where a score of less than 10 is recorded for the frame. + In this case the score for the frame is the number of pins knocked down. -- A spare is where all ten pins are knocked down by the second - throw. The total value of a spare is 10 plus the number of pins - knocked down in their next throw. +- A spare is where all ten pins are knocked down by the second throw. + The total value of a spare is 10 plus the number of pins knocked down in their next throw. -- A strike is where all ten pins are knocked down by the first - throw. The total value of a strike is 10 plus the number of pins - knocked down in the next two throws. If a strike is immediately - followed by a second strike, then the value of the first strike - cannot be determined until the ball is thrown one more time. +- A strike is where all ten pins are knocked down by the first throw. + The total value of a strike is 10 plus the number of pins knocked down in the next two throws. + If a strike is immediately followed by a second strike, then the value of the first strike cannot be determined until the ball is thrown one more time. Here is a three frame example: @@ -40,11 +35,11 @@ Frame 3 is (9 + 0) = 9 This means the current running total is 48. -The tenth frame in the game is a special case. If someone throws a -strike or a spare then they get a fill ball. Fill balls exist to -calculate the total of the 10th frame. Scoring a strike or spare on -the fill ball does not give the player more fill balls. The total -value of the 10th frame is the total number of pins knocked down. +The tenth frame in the game is a special case. +If someone throws a spare or a strike then they get one or two fill balls respectively. +Fill balls exist to calculate the total of the 10th frame. +Scoring a strike or spare on the fill ball does not give the player more fill balls. +The total value of the 10th frame is the total number of pins knocked down. For a tenth frame of X1/ (strike and a spare), the total value is 20. @@ -52,10 +47,10 @@ For a tenth frame of XXX (three strikes), the total value is 30. ## Requirements -Write code to keep track of the score of a game of bowling. It should -support two operations: +Write code to keep track of the score of a game of bowling. +It should support two operations: -- `roll(pins : int)` is called each time the player rolls a ball. The - argument is the number of pins knocked down. -- `score() : int` is called only at the very end of the game. It - returns the total score for that game. +- `roll(pins : int)` is called each time the player rolls a ball. + The argument is the number of pins knocked down. +- `score() : int` is called only at the very end of the game. + It returns the total score for that game. diff --git a/exercises/practice/bowling/.eslintrc b/exercises/practice/bowling/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/bowling/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/bowling/.gitignore b/exercises/practice/bowling/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/bowling/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/bowling/.meta/config.json b/exercises/practice/bowling/.meta/config.json index bd82ee82b3..4d1535b428 100644 --- a/exercises/practice/bowling/.meta/config.json +++ b/exercises/practice/bowling/.meta/config.json @@ -1,12 +1,31 @@ { - "blurb": "Score a bowling game", - "authors": ["trvrfrd"], - "contributors": ["danielj-jordan", "SleeplessByte", "tejasbubane"], + "authors": [ + "trvrfrd" + ], + "contributors": [ + "danielj-jordan", + "jagdish-15", + "SleeplessByte", + "tejasbubane" + ], "files": { - "solution": ["bowling.js"], - "test": ["bowling.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "bowling.js" + ], + "test": [ + "bowling.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, - "source": "The Bowling Game Kata at but UncleBob", - "source_url": "http://butunclebob.com/ArticleS.UncleBob.TheBowlingGameKata" + "blurb": "Score a bowling game.", + "source": "The Bowling Game Kata from UncleBob", + "source_url": "https://web.archive.org/web/20221001111000/http://butunclebob.com/ArticleS.UncleBob.TheBowlingGameKata", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/bowling/.meta/tests.toml b/exercises/practice/bowling/.meta/tests.toml index 963df175a7..19042607d7 100644 --- a/exercises/practice/bowling/.meta/tests.toml +++ b/exercises/practice/bowling/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [656ae006-25c2-438c-a549-f338e7ec7441] description = "should be able to score a game with all zeros" @@ -38,6 +45,9 @@ description = "rolling a spare with the two roll bonus does not get a bonus roll [576faac1-7cff-4029-ad72-c16bcada79b5] description = "strikes with the two roll bonus do not get bonus rolls" +[efb426ec-7e15-42e6-9b96-b4fca3ec2359] +description = "last two strikes followed by only last bonus with non strike points" + [72e24404-b6c6-46af-b188-875514c0377b] description = "a strike with the one roll bonus after a spare in the last frame does not get a bonus" diff --git a/exercises/practice/bowling/babel.config.js b/exercises/practice/bowling/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/bowling/babel.config.js +++ b/exercises/practice/bowling/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/bowling/bowling.js b/exercises/practice/bowling/bowling.js index 9d83338b7c..d3b490db36 100644 --- a/exercises/practice/bowling/bowling.js +++ b/exercises/practice/bowling/bowling.js @@ -5,10 +5,10 @@ export class Bowling { roll() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } score() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/bowling/bowling.spec.js b/exercises/practice/bowling/bowling.spec.js index 981471cbf6..6e95f7c5f6 100644 --- a/exercises/practice/bowling/bowling.spec.js +++ b/exercises/practice/bowling/bowling.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { Bowling } from './bowling'; describe('Bowling', () => { @@ -128,6 +129,17 @@ describe('Bowling', () => { expect(bowling.score()).toEqual(30); }); + xtest('last two strikes followed by only last bonus with non strike points', () => { + const rolls = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 10, 0, 1, + ]; + const bowling = new Bowling(); + rolls.forEach((roll) => { + bowling.roll(roll); + }); + expect(bowling.score()).toEqual(31); + }); + xtest('a strike with the one roll bonus after a spare in the last frame does not get a bonus', () => { const rolls = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 3, 10, diff --git a/exercises/practice/bowling/eslint.config.mjs b/exercises/practice/bowling/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/bowling/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/bowling/jest.config.js b/exercises/practice/bowling/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/bowling/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/bowling/package.json b/exercises/practice/bowling/package.json index dc78550d4e..e63266ac62 100644 --- a/exercises/practice/bowling/package.json +++ b/exercises/practice/bowling/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/bowling" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/change/.docs/instructions.md b/exercises/practice/change/.docs/instructions.md index 59f4f4f90d..5887f4cb69 100644 --- a/exercises/practice/change/.docs/instructions.md +++ b/exercises/practice/change/.docs/instructions.md @@ -1,17 +1,8 @@ # Instructions -Correctly determine the fewest number of coins to be given to a customer such -that the sum of the coins' value would equal the correct amount of change. +Determine the fewest number of coins to give a customer so that the sum of their values equals the correct amount of change. -## For example +## Examples -- An input of 15 with [1, 5, 10, 25, 100] should return one nickel (5) - and one dime (10) or [5, 10] -- An input of 40 with [1, 5, 10, 25, 100] should return one nickel (5) - and one dime (10) and one quarter (25) or [5, 10, 25] - -## Edge cases - -- Does your algorithm work for any given set of coins? -- Can you ask for negative change? -- Can you ask for a change value smaller than the smallest coin value? +- An amount of 15 with available coin values [1, 5, 10, 25, 100] should return one coin of value 5 and one coin of value 10, or [5, 10]. +- An amount of 40 with available coin values [1, 5, 10, 25, 100] should return one coin of value 5, one coin of value 10, and one coin of value 25, or [5, 10, 25]. diff --git a/exercises/practice/change/.docs/introduction.md b/exercises/practice/change/.docs/introduction.md new file mode 100644 index 0000000000..b4f8308a1b --- /dev/null +++ b/exercises/practice/change/.docs/introduction.md @@ -0,0 +1,26 @@ +# Introduction + +In the mystical village of Coinholt, you stand behind the counter of your bakery, arranging a fresh batch of pastries. +The door creaks open, and in walks Denara, a skilled merchant with a keen eye for quality goods. +After a quick meal, she slides a shimmering coin across the counter, representing a value of 100 units. + +You smile, taking the coin, and glance at the total cost of the meal: 88 units. +That means you need to return 12 units in change. + +Denara holds out her hand expectantly. +"Just give me the fewest coins," she says with a smile. +"My pouch is already full, and I don't want to risk losing them on the road." + +You know you have a few options. +"We have Lumis (worth 10 units), Viras (worth 5 units), and Zenth (worth 2 units) available for change." + +You quickly calculate the possibilities in your head: + +- one Lumis (1 × 10 units) + one Zenth (1 × 2 units) = 2 coins total +- two Viras (2 × 5 units) + one Zenth (1 × 2 units) = 3 coins total +- six Zenth (6 × 2 units) = 6 coins total + +"The best choice is two coins: one Lumis and one Zenth," you say, handing her the change. + +Denara smiles, clearly impressed. +"As always, you've got it right." diff --git a/exercises/practice/change/.eslintrc b/exercises/practice/change/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/change/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/change/.gitignore b/exercises/practice/change/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/change/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/change/.meta/config.json b/exercises/practice/change/.meta/config.json index f182075815..1b2d266e3f 100644 --- a/exercises/practice/change/.meta/config.json +++ b/exercises/practice/change/.meta/config.json @@ -1,8 +1,10 @@ { - "blurb": "Correctly determine change to be given using the least number of coins", - "authors": ["RobinCsl"], + "authors": [ + "RobinCsl" + ], "contributors": [ "adamxtokyo", + "jagdish-15", "rchavarria", "SleeplessByte", "tejasbubane", @@ -10,10 +12,23 @@ "whatcoda" ], "files": { - "solution": ["change.js"], - "test": ["change.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "change.js" + ], + "test": [ + "change.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Correctly determine change to be given using the least number of coins.", "source": "Software Craftsmanship - Coin Change Kata", - "source_url": "https://web.archive.org/web/20130115115225/http://craftsmanship.sv.cmu.edu:80/exercises/coin-change-kata" + "source_url": "https://web.archive.org/web/20130115115225/http://craftsmanship.sv.cmu.edu:80/exercises/coin-change-kata", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/change/.meta/proof.ci.js b/exercises/practice/change/.meta/proof.ci.js index b0cc9bf9e8..a3a0c93efd 100644 --- a/exercises/practice/change/.meta/proof.ci.js +++ b/exercises/practice/change/.meta/proof.ci.js @@ -74,13 +74,13 @@ export class Change { // is everything searched? const isDone = () => candidates.every( - (candidate) => isNumber(candidate) || candidate.isSearched() + (candidate) => isNumber(candidate) || candidate.isSearched(), ); // get the next unsearched member of the candidate array const getNext = () => candidates.find( - (candidate) => !isNumber(candidate) && !candidate.isSearched() + (candidate) => !isNumber(candidate) && !candidate.isSearched(), ); // for the candidate, generate another candidate for each of the possible coins @@ -106,7 +106,7 @@ export class Change { if (target < Math.min.apply(null, coinArray)) { throw new Error( - `The total ${target} cannot be represented in the given currency.` + `The total ${target} cannot be represented in the given currency.`, ); } @@ -122,7 +122,7 @@ export class Change { // print the result if (!isNumber(candidates[target])) return candidates[target].getCoins(); throw new Error( - `The total ${target} cannot be represented in the given currency.` + `The total ${target} cannot be represented in the given currency.`, ); } } diff --git a/exercises/practice/change/.meta/tests.toml b/exercises/practice/change/.meta/tests.toml index 6d36d3c760..2d2f44bc21 100644 --- a/exercises/practice/change/.meta/tests.toml +++ b/exercises/practice/change/.meta/tests.toml @@ -1,6 +1,16 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[d0ebd0e1-9d27-4609-a654-df5c0ba1d83a] +description = "change for 1 cent" [36887bea-7f92-4a9c-b0cc-c0e886b3ecc8] description = "single coin change" @@ -23,6 +33,9 @@ description = "possible change without unit coins available" [9a166411-d35d-4f7f-a007-6724ac266178] description = "another possible change without unit coins available" +[ce0f80d5-51c3-469d-818c-3e69dbd25f75] +description = "a greedy approach is not optimal" + [bbbcc154-e9e9-4209-a4db-dd6d81ec26bb] description = "no coins make 0 change" diff --git a/exercises/practice/change/babel.config.js b/exercises/practice/change/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/change/babel.config.js +++ b/exercises/practice/change/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/change/change.js b/exercises/practice/change/change.js index a1e61fa949..0288c23657 100644 --- a/exercises/practice/change/change.js +++ b/exercises/practice/change/change.js @@ -5,6 +5,6 @@ export class Change { calculate(coinArray, target) { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/change/change.spec.js b/exercises/practice/change/change.spec.js index c57ca6cc77..8dbc425d12 100644 --- a/exercises/practice/change/change.spec.js +++ b/exercises/practice/change/change.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { Change } from './change'; describe('Change', () => { @@ -53,6 +54,12 @@ describe('Change', () => { expect(result).toEqual([4, 4, 4, 5, 5, 5]); }); + xtest('a greedy approach is not optimal', () => { + const change = new Change(); + const result = change.calculate([1, 10, 11], 20); + expect(result).toEqual([10, 10]); + }); + xtest('no coins make 0 change', () => { const change = new Change(); const result = change.calculate([1, 5, 10, 21, 25], 0); @@ -62,27 +69,27 @@ describe('Change', () => { xtest('error testing for change smaller than the smallest of coins', () => { const change = new Change(); const message = 'The total 3 cannot be represented in the given currency.'; - const test = () => { + const expectation = () => { change.calculate([5, 10], 3); }; - expect(test).toThrowError(message); + expect(expectation).toThrow(message); }); xtest('error testing if no combination can add up to target', () => { const change = new Change(); const message = 'The total 94 cannot be represented in the given currency.'; - const test = () => { + const expectation = () => { change.calculate([5, 10], 94); }; - expect(test).toThrowError(message); + expect(expectation).toThrow(message); }); xtest('cannot find negative change values', () => { const change = new Change(); const message = 'Negative totals are not allowed.'; - const test = () => { + const expectation = () => { change.calculate([1, 2, 5], -5); }; - expect(test).toThrowError(message); + expect(expectation).toThrow(message); }); }); diff --git a/exercises/practice/change/eslint.config.mjs b/exercises/practice/change/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/change/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/change/jest.config.js b/exercises/practice/change/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/change/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/change/package.json b/exercises/practice/change/package.json index 70c3bec83d..0348f07a0b 100644 --- a/exercises/practice/change/package.json +++ b/exercises/practice/change/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/change" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/circular-buffer/.docs/instructions.md b/exercises/practice/circular-buffer/.docs/instructions.md index 0dec68d574..2ba1fda2aa 100644 --- a/exercises/practice/circular-buffer/.docs/instructions.md +++ b/exercises/practice/circular-buffer/.docs/instructions.md @@ -1,59 +1,58 @@ # Instructions -A circular buffer, cyclic buffer or ring buffer is a data structure that -uses a single, fixed-size buffer as if it were connected end-to-end. +A circular buffer, cyclic buffer or ring buffer is a data structure that uses a single, fixed-size buffer as if it were connected end-to-end. -A circular buffer first starts empty and of some predefined length. For -example, this is a 7-element buffer: +A circular buffer first starts empty and of some predefined length. +For example, this is a 7-element buffer: - - [ ][ ][ ][ ][ ][ ][ ] +```text +[ ][ ][ ][ ][ ][ ][ ] +``` -Assume that a 1 is written into the middle of the buffer (exact starting -location does not matter in a circular buffer): +Assume that a 1 is written into the middle of the buffer (exact starting location does not matter in a circular buffer): - - [ ][ ][ ][1][ ][ ][ ] +```text +[ ][ ][ ][1][ ][ ][ ] +``` -Then assume that two more elements are added — 2 & 3 — which get -appended after the 1: +Then assume that two more elements are added — 2 & 3 — which get appended after the 1: - - [ ][ ][ ][1][2][3][ ] +```text +[ ][ ][ ][1][2][3][ ] +``` -If two elements are then removed from the buffer, the oldest values -inside the buffer are removed. The two elements removed, in this case, -are 1 & 2, leaving the buffer with just a 3: +If two elements are then removed from the buffer, the oldest values inside the buffer are removed. +The two elements removed, in this case, are 1 & 2, leaving the buffer with just a 3: - - [ ][ ][ ][ ][ ][3][ ] +```text +[ ][ ][ ][ ][ ][3][ ] +``` If the buffer has 7 elements then it is completely full: - - [5][6][7][8][9][3][4] +```text +[5][6][7][8][9][3][4] +``` -When the buffer is full an error will be raised, alerting the client -that further writes are blocked until a slot becomes free. +When the buffer is full an error will be raised, alerting the client that further writes are blocked until a slot becomes free. -When the buffer is full, the client can opt to overwrite the oldest -data with a forced write. In this case, two more elements — A & B — -are added and they overwrite the 3 & 4: +When the buffer is full, the client can opt to overwrite the oldest data with a forced write. +In this case, two more elements — A & B — are added and they overwrite the 3 & 4: - - [5][6][7][8][9][A][B] +```text +[5][6][7][8][9][A][B] +``` -3 & 4 have been replaced by A & B making 5 now the oldest data in the -buffer. Finally, if two elements are removed then what would be -returned is 5 & 6 yielding the buffer: +3 & 4 have been replaced by A & B making 5 now the oldest data in the buffer. +Finally, if two elements are removed then what would be returned is 5 & 6 yielding the buffer: - - [ ][ ][7][8][9][A][B] +```text +[ ][ ][7][8][9][A][B] +``` -Because there is space available, if the client again uses overwrite -to store C & D then the space where 5 & 6 were stored previously will -be used not the location of 7 & 8. 7 is still the oldest element and -the buffer is once again full. +Because there is space available, if the client again uses overwrite to store C & D then the space where 5 & 6 were stored previously will be used not the location of 7 & 8. +7 is still the oldest element and the buffer is once again full. - - [C][D][7][8][9][A][B] +```text +[C][D][7][8][9][A][B] +``` diff --git a/exercises/practice/circular-buffer/.eslintrc b/exercises/practice/circular-buffer/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/circular-buffer/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/circular-buffer/.gitignore b/exercises/practice/circular-buffer/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/circular-buffer/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/circular-buffer/.meta/config.json b/exercises/practice/circular-buffer/.meta/config.json index 29b62b7f39..bf38693069 100644 --- a/exercises/practice/circular-buffer/.meta/config.json +++ b/exercises/practice/circular-buffer/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "A data structure that uses a single, fixed-size buffer as if it were connected end-to-end.", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "diego-caceres", "ramitmittal", @@ -13,10 +14,23 @@ "yanick" ], "files": { - "solution": ["circular-buffer.js"], - "test": ["circular-buffer.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "circular-buffer.js" + ], + "test": [ + "circular-buffer.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "A data structure that uses a single, fixed-size buffer as if it were connected end-to-end.", "source": "Wikipedia", - "source_url": "http://en.wikipedia.org/wiki/Circular_buffer" + "source_url": "https://en.wikipedia.org/wiki/Circular_buffer", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/circular-buffer/.meta/proof.ci.js b/exercises/practice/circular-buffer/.meta/proof.ci.js index c31f24e834..8ba5832015 100644 --- a/exercises/practice/circular-buffer/.meta/proof.ci.js +++ b/exercises/practice/circular-buffer/.meta/proof.ci.js @@ -11,7 +11,6 @@ export class BufferFullError extends Error { } } -// eslint-disable-next-line import/no-default-export export default class CircularBuffer { constructor(capacity) { this.buffer = []; diff --git a/exercises/practice/circular-buffer/babel.config.js b/exercises/practice/circular-buffer/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/circular-buffer/babel.config.js +++ b/exercises/practice/circular-buffer/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/circular-buffer/circular-buffer.js b/exercises/practice/circular-buffer/circular-buffer.js index ce5136f121..b35fb133a3 100644 --- a/exercises/practice/circular-buffer/circular-buffer.js +++ b/exercises/practice/circular-buffer/circular-buffer.js @@ -5,23 +5,23 @@ class CircularBuffer { constructor() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } write() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } read() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } forceWrite() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } clear() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } @@ -29,12 +29,12 @@ export default CircularBuffer; export class BufferFullError extends Error { constructor() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } export class BufferEmptyError extends Error { constructor() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/circular-buffer/circular-buffer.spec.js b/exercises/practice/circular-buffer/circular-buffer.spec.js index 52b85af305..8d3d6d33c4 100644 --- a/exercises/practice/circular-buffer/circular-buffer.spec.js +++ b/exercises/practice/circular-buffer/circular-buffer.spec.js @@ -1,6 +1,7 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import CircularBuffer, { - BufferFullError, BufferEmptyError, + BufferFullError, } from './circular-buffer'; describe('CircularBuffer', () => { diff --git a/exercises/practice/circular-buffer/eslint.config.mjs b/exercises/practice/circular-buffer/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/circular-buffer/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/circular-buffer/jest.config.js b/exercises/practice/circular-buffer/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/circular-buffer/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/circular-buffer/package.json b/exercises/practice/circular-buffer/package.json index 452ed3a5a2..4f332b428f 100644 --- a/exercises/practice/circular-buffer/package.json +++ b/exercises/practice/circular-buffer/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/circular-buffer" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/clock/.eslintrc b/exercises/practice/clock/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/clock/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/clock/.gitignore b/exercises/practice/clock/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/clock/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/clock/.meta/config.json b/exercises/practice/clock/.meta/config.json index a03335771e..38666d650b 100644 --- a/exercises/practice/clock/.meta/config.json +++ b/exercises/practice/clock/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Implement a clock that handles times without dates.", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "ankorGH", "ovidiu141", @@ -12,10 +13,22 @@ "xarxziux" ], "files": { - "solution": ["clock.js"], - "test": ["clock.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "clock.js" + ], + "test": [ + "clock.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Implement a clock that handles times without dates.", "source": "Pairing session with Erin Drummond", - "source_url": "https://twitter.com/ebdrummond" + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/clock/babel.config.js b/exercises/practice/clock/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/clock/babel.config.js +++ b/exercises/practice/clock/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/clock/clock.js b/exercises/practice/clock/clock.js index 7d77d7a4b1..bba0b5e594 100644 --- a/exercises/practice/clock/clock.js +++ b/exercises/practice/clock/clock.js @@ -5,22 +5,22 @@ export class Clock { constructor() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } toString() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } plus() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } minus() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } equals() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/clock/clock.spec.js b/exercises/practice/clock/clock.spec.js index d4046b9976..c480984839 100644 --- a/exercises/practice/clock/clock.spec.js +++ b/exercises/practice/clock/clock.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { Clock } from './clock'; describe('Clock', () => { diff --git a/exercises/practice/clock/eslint.config.mjs b/exercises/practice/clock/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/clock/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/clock/jest.config.js b/exercises/practice/clock/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/clock/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/clock/package.json b/exercises/practice/clock/package.json index dd69c6025e..18146eb9a6 100644 --- a/exercises/practice/clock/package.json +++ b/exercises/practice/clock/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/clock" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/collatz-conjecture/.docs/instructions.md b/exercises/practice/collatz-conjecture/.docs/instructions.md index f8c76e7f11..af332a810f 100644 --- a/exercises/practice/collatz-conjecture/.docs/instructions.md +++ b/exercises/practice/collatz-conjecture/.docs/instructions.md @@ -1,27 +1,3 @@ # Instructions -The Collatz Conjecture or 3x+1 problem can be summarized as follows: - -Take any positive integer n. If n is even, divide n by 2 to get n / 2. If n is -odd, multiply n by 3 and add 1 to get 3n + 1. Repeat the process indefinitely. -The conjecture states that no matter which number you start with, you will -always reach 1 eventually. - -Given a number n, return the number of steps required to reach 1. - -## Examples - -Starting with n = 12, the steps would be as follows: - -0. 12 -1. 6 -2. 3 -3. 10 -4. 5 -5. 16 -6. 8 -7. 4 -8. 2 -9. 1 - -Resulting in 9 steps. So for input n = 12, the return value would be 9. +Given a positive integer, return the number of steps it takes to reach 1 according to the rules of the Collatz Conjecture. diff --git a/exercises/practice/collatz-conjecture/.docs/introduction.md b/exercises/practice/collatz-conjecture/.docs/introduction.md new file mode 100644 index 0000000000..c35bdeb67d --- /dev/null +++ b/exercises/practice/collatz-conjecture/.docs/introduction.md @@ -0,0 +1,28 @@ +# Introduction + +One evening, you stumbled upon an old notebook filled with cryptic scribbles, as though someone had been obsessively chasing an idea. +On one page, a single question stood out: **Can every number find its way to 1?** +It was tied to something called the **Collatz Conjecture**, a puzzle that has baffled thinkers for decades. + +The rules were deceptively simple. +Pick any positive integer. + +- If it's even, divide it by 2. +- If it's odd, multiply it by 3 and add 1. + +Then, repeat these steps with the result, continuing indefinitely. + +Curious, you picked number 12 to test and began the journey: + +12 ➜ 6 ➜ 3 ➜ 10 ➜ 5 ➜ 16 ➜ 8 ➜ 4 ➜ 2 ➜ 1 + +Counting from the second number (6), it took 9 steps to reach 1, and each time the rules repeated, the number kept changing. +At first, the sequence seemed unpredictable — jumping up, down, and all over. +Yet, the conjecture claims that no matter the starting number, we'll always end at 1. + +It was fascinating, but also puzzling. +Why does this always seem to work? +Could there be a number where the process breaks down, looping forever or escaping into infinity? +The notebook suggested solving this could reveal something profound — and with it, fame, [fortune][collatz-prize], and a place in history awaits whoever could unlock its secrets. + +[collatz-prize]: https://mathprize.net/posts/collatz-conjecture/ diff --git a/exercises/practice/collatz-conjecture/.eslintrc b/exercises/practice/collatz-conjecture/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/collatz-conjecture/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/collatz-conjecture/.gitignore b/exercises/practice/collatz-conjecture/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/collatz-conjecture/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/collatz-conjecture/.meta/config.json b/exercises/practice/collatz-conjecture/.meta/config.json index a7a54fbfe5..bcde7be429 100644 --- a/exercises/practice/collatz-conjecture/.meta/config.json +++ b/exercises/practice/collatz-conjecture/.meta/config.json @@ -1,12 +1,30 @@ { - "blurb": "Calculate the number of steps to reach 1 using the Collatz conjecture", "authors": [], - "contributors": ["ankorGH", "rchavarria", "SleeplessByte", "xarxziux"], + "contributors": [ + "ankorGH", + "jagdish-15", + "rchavarria", + "SleeplessByte", + "xarxziux" + ], "files": { - "solution": ["collatz-conjecture.js"], - "test": ["collatz-conjecture.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "collatz-conjecture.js" + ], + "test": [ + "collatz-conjecture.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, - "source": "An unsolved problem in mathematics named after mathematician Lothar Collatz", - "source_url": "https://en.wikipedia.org/wiki/3x_%2B_1_problem" + "blurb": "Calculate the number of steps to reach 1 using the Collatz conjecture.", + "source": "Wikipedia", + "source_url": "https://en.wikipedia.org/wiki/Collatz_conjecture", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/collatz-conjecture/.meta/proof.ci.js b/exercises/practice/collatz-conjecture/.meta/proof.ci.js index ed0f95f494..6ebeeff93a 100644 --- a/exercises/practice/collatz-conjecture/.meta/proof.ci.js +++ b/exercises/practice/collatz-conjecture/.meta/proof.ci.js @@ -1,6 +1,6 @@ export const steps = (n) => { if (n <= 0) { - throw new Error('Only positive numbers are allowed'); + throw new Error('Only positive integers are allowed'); } const iterate = (number, step) => { diff --git a/exercises/practice/collatz-conjecture/.meta/tests.toml b/exercises/practice/collatz-conjecture/.meta/tests.toml index 04187f605c..cc34e16847 100644 --- a/exercises/practice/collatz-conjecture/.meta/tests.toml +++ b/exercises/practice/collatz-conjecture/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [540a3d51-e7a6-47a5-92a3-4ad1838f0bfd] description = "zero steps for one" @@ -16,6 +23,16 @@ description = "large number of even and odd steps" [7d4750e6-def9-4b86-aec7-9f7eb44f95a3] description = "zero is an error" +include = false + +[2187673d-77d6-4543-975e-66df6c50e2da] +description = "zero is an error" +reimplements = "7d4750e6-def9-4b86-aec7-9f7eb44f95a3" [c6c795bf-a288-45e9-86a1-841359ad426d] description = "negative value is an error" +include = false + +[ec11f479-56bc-47fd-a434-bcd7a31a7a2e] +description = "negative value is an error" +reimplements = "c6c795bf-a288-45e9-86a1-841359ad426d" diff --git a/exercises/practice/collatz-conjecture/babel.config.js b/exercises/practice/collatz-conjecture/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/collatz-conjecture/babel.config.js +++ b/exercises/practice/collatz-conjecture/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/collatz-conjecture/collatz-conjecture.js b/exercises/practice/collatz-conjecture/collatz-conjecture.js index 28767bc3c2..cbc9af54fd 100644 --- a/exercises/practice/collatz-conjecture/collatz-conjecture.js +++ b/exercises/practice/collatz-conjecture/collatz-conjecture.js @@ -4,5 +4,5 @@ // export const steps = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/collatz-conjecture/collatz-conjecture.spec.js b/exercises/practice/collatz-conjecture/collatz-conjecture.spec.js index 91a30a07ea..87714b9b4e 100644 --- a/exercises/practice/collatz-conjecture/collatz-conjecture.spec.js +++ b/exercises/practice/collatz-conjecture/collatz-conjecture.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { steps } from './collatz-conjecture'; describe('steps()', () => { @@ -20,12 +21,12 @@ describe('steps()', () => { xtest('zero is an error', () => { expect(() => { steps(0); - }).toThrow(new Error('Only positive numbers are allowed')); + }).toThrow(new Error('Only positive integers are allowed')); }); xtest('negative value is an error', () => { expect(() => { steps(-15); - }).toThrow(new Error('Only positive numbers are allowed')); + }).toThrow(new Error('Only positive integers are allowed')); }); }); diff --git a/exercises/practice/collatz-conjecture/eslint.config.mjs b/exercises/practice/collatz-conjecture/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/collatz-conjecture/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/collatz-conjecture/jest.config.js b/exercises/practice/collatz-conjecture/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/collatz-conjecture/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/collatz-conjecture/package.json b/exercises/practice/collatz-conjecture/package.json index db76f35536..ceccb02279 100644 --- a/exercises/practice/collatz-conjecture/package.json +++ b/exercises/practice/collatz-conjecture/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/collatz-conjecture" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/complex-numbers/.docs/instructions.md b/exercises/practice/complex-numbers/.docs/instructions.md index 50b19aedff..2b8a7a49d8 100644 --- a/exercises/practice/complex-numbers/.docs/instructions.md +++ b/exercises/practice/complex-numbers/.docs/instructions.md @@ -1,29 +1,100 @@ # Instructions -A complex number is a number in the form `a + b * i` where `a` and `b` are real and `i` satisfies `i^2 = -1`. +A **complex number** is expressed in the form `z = a + b * i`, where: -`a` is called the real part and `b` is called the imaginary part of `z`. -The conjugate of the number `a + b * i` is the number `a - b * i`. -The absolute value of a complex number `z = a + b * i` is a real number `|z| = sqrt(a^2 + b^2)`. The square of the absolute value `|z|^2` is the result of multiplication of `z` by its complex conjugate. +- `a` is the **real part** (a real number), -The sum/difference of two complex numbers involves adding/subtracting their real and imaginary parts separately: -`(a + i * b) + (c + i * d) = (a + c) + (b + d) * i`, -`(a + i * b) - (c + i * d) = (a - c) + (b - d) * i`. +- `b` is the **imaginary part** (also a real number), and -Multiplication result is by definition -`(a + i * b) * (c + i * d) = (a * c - b * d) + (b * c + a * d) * i`. +- `i` is the **imaginary unit** satisfying `i^2 = -1`. -The reciprocal of a non-zero complex number is -`1 / (a + i * b) = a/(a^2 + b^2) - b/(a^2 + b^2) * i`. +## Operations on Complex Numbers -Dividing a complex number `a + i * b` by another `c + i * d` gives: -`(a + i * b) / (c + i * d) = (a * c + b * d)/(c^2 + d^2) + (b * c - a * d)/(c^2 + d^2) * i`. +### Conjugate -Raising e to a complex exponent can be expressed as `e^(a + i * b) = e^a * e^(i * b)`, the last term of which is given by Euler's formula `e^(i * b) = cos(b) + i * sin(b)`. +The conjugate of the complex number `z = a + b * i` is given by: -Implement the following operations: +```text +zc = a - b * i +``` -- addition, subtraction, multiplication and division of two complex numbers, -- conjugate, absolute value, exponent of a given complex number. +### Absolute Value -Assume the programming language you are using does not have an implementation of complex numbers. +The absolute value (or modulus) of `z` is defined as: + +```text +|z| = sqrt(a^2 + b^2) +``` + +The square of the absolute value is computed as the product of `z` and its conjugate `zc`: + +```text +|z|^2 = z * zc = a^2 + b^2 +``` + +### Addition + +The sum of two complex numbers `z1 = a + b * i` and `z2 = c + d * i` is computed by adding their real and imaginary parts separately: + +```text +z1 + z2 = (a + b * i) + (c + d * i) + = (a + c) + (b + d) * i +``` + +### Subtraction + +The difference of two complex numbers is obtained by subtracting their respective parts: + +```text +z1 - z2 = (a + b * i) - (c + d * i) + = (a - c) + (b - d) * i +``` + +### Multiplication + +The product of two complex numbers is defined as: + +```text +z1 * z2 = (a + b * i) * (c + d * i) + = (a * c - b * d) + (b * c + a * d) * i +``` + +### Reciprocal + +The reciprocal of a non-zero complex number is given by: + +```text +1 / z = 1 / (a + b * i) + = a / (a^2 + b^2) - b / (a^2 + b^2) * i +``` + +### Division + +The division of one complex number by another is given by: + +```text +z1 / z2 = z1 * (1 / z2) + = (a + b * i) / (c + d * i) + = (a * c + b * d) / (c^2 + d^2) + (b * c - a * d) / (c^2 + d^2) * i +``` + +### Exponentiation + +Raising _e_ (the base of the natural logarithm) to a complex exponent can be expressed using Euler's formula: + +```text +e^(a + b * i) = e^a * e^(b * i) + = e^a * (cos(b) + i * sin(b)) +``` + +## Implementation Requirements + +Given that you should not use built-in support for complex numbers, implement the following operations: + +- **addition** of two complex numbers +- **subtraction** of two complex numbers +- **multiplication** of two complex numbers +- **division** of two complex numbers +- **conjugate** of a complex number +- **absolute value** of a complex number +- **exponentiation** of _e_ (the base of the natural logarithm) to a complex number diff --git a/exercises/practice/complex-numbers/.eslintrc b/exercises/practice/complex-numbers/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/complex-numbers/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/complex-numbers/.gitignore b/exercises/practice/complex-numbers/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/complex-numbers/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/complex-numbers/.meta/config.json b/exercises/practice/complex-numbers/.meta/config.json index e5f4053926..0663ab094c 100644 --- a/exercises/practice/complex-numbers/.meta/config.json +++ b/exercises/practice/complex-numbers/.meta/config.json @@ -1,19 +1,34 @@ { - "blurb": "Implement complex numbers.", - "authors": ["MattH-be"], + "authors": [ + "MattH-be" + ], "contributors": [ "ankorGH", "burennto", "cmccandless", + "jagdish-15", "SleeplessByte", "tejasbubane", "trvrfrd" ], "files": { - "solution": ["complex-numbers.js"], - "test": ["complex-numbers.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "complex-numbers.js" + ], + "test": [ + "complex-numbers.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Implement complex numbers.", "source": "Wikipedia", - "source_url": "https://en.wikipedia.org/wiki/Complex_number" + "source_url": "https://en.wikipedia.org/wiki/Complex_number", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/complex-numbers/.meta/proof.ci.js b/exercises/practice/complex-numbers/.meta/proof.ci.js index ff3bba9ce7..ee89763e19 100644 --- a/exercises/practice/complex-numbers/.meta/proof.ci.js +++ b/exercises/practice/complex-numbers/.meta/proof.ci.js @@ -15,7 +15,7 @@ export class ComplexNumber { mul(other) { return new ComplexNumber( this.real * other.real - this.imag * other.imag, - this.imag * other.real + this.real * other.imag + this.imag * other.real + this.real * other.imag, ); } @@ -24,7 +24,7 @@ export class ComplexNumber { (this.real * other.real + this.imag * other.imag) / (other.real * other.real + other.imag * other.imag), (this.imag * other.real - this.real * other.imag) / - (other.real * other.real + other.imag * other.imag) + (other.real * other.real + other.imag * other.imag), ); } @@ -39,7 +39,7 @@ export class ComplexNumber { get exp() { return new ComplexNumber( Math.exp(this.real) * Math.cos(this.imag), - Math.exp(this.real) * Math.sin(this.imag) + Math.exp(this.real) * Math.sin(this.imag), ); } } diff --git a/exercises/practice/complex-numbers/.meta/tests.toml b/exercises/practice/complex-numbers/.meta/tests.toml index d67ac05f59..dffb1f2a31 100644 --- a/exercises/practice/complex-numbers/.meta/tests.toml +++ b/exercises/practice/complex-numbers/.meta/tests.toml @@ -1,96 +1,130 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [9f98e133-eb7f-45b0-9676-cce001cd6f7a] -description = "Real part of a purely real number" +description = "Real part -> Real part of a purely real number" [07988e20-f287-4bb7-90cf-b32c4bffe0f3] -description = "Real part of a purely imaginary number" +description = "Real part -> Real part of a purely imaginary number" [4a370e86-939e-43de-a895-a00ca32da60a] -description = "Real part of a number with real and imaginary part" +description = "Real part -> Real part of a number with real and imaginary part" [9b3fddef-4c12-4a99-b8f8-e3a42c7ccef6] -description = "Imaginary part of a purely real number" +description = "Imaginary part -> Imaginary part of a purely real number" [a8dafedd-535a-4ed3-8a39-fda103a2b01e] -description = "Imaginary part of a purely imaginary number" +description = "Imaginary part -> Imaginary part of a purely imaginary number" [0f998f19-69ee-4c64-80ef-01b086feab80] -description = "Imaginary part of a number with real and imaginary part" +description = "Imaginary part -> Imaginary part of a number with real and imaginary part" [a39b7fd6-6527-492f-8c34-609d2c913879] description = "Imaginary unit" [9a2c8de9-f068-4f6f-b41c-82232cc6c33e] -description = "Add purely real numbers" +description = "Arithmetic -> Addition -> Add purely real numbers" [657c55e1-b14b-4ba7-bd5c-19db22b7d659] -description = "Add purely imaginary numbers" +description = "Arithmetic -> Addition -> Add purely imaginary numbers" [4e1395f5-572b-4ce8-bfa9-9a63056888da] -description = "Add numbers with real and imaginary part" +description = "Arithmetic -> Addition -> Add numbers with real and imaginary part" [1155dc45-e4f7-44b8-af34-a91aa431475d] -description = "Subtract purely real numbers" +description = "Arithmetic -> Subtraction -> Subtract purely real numbers" [f95e9da8-acd5-4da4-ac7c-c861b02f774b] -description = "Subtract purely imaginary numbers" +description = "Arithmetic -> Subtraction -> Subtract purely imaginary numbers" [f876feb1-f9d1-4d34-b067-b599a8746400] -description = "Subtract numbers with real and imaginary part" +description = "Arithmetic -> Subtraction -> Subtract numbers with real and imaginary part" [8a0366c0-9e16-431f-9fd7-40ac46ff4ec4] -description = "Multiply purely real numbers" +description = "Arithmetic -> Multiplication -> Multiply purely real numbers" [e560ed2b-0b80-4b4f-90f2-63cefc911aaf] -description = "Multiply purely imaginary numbers" +description = "Arithmetic -> Multiplication -> Multiply purely imaginary numbers" [4d1d10f0-f8d4-48a0-b1d0-f284ada567e6] -description = "Multiply numbers with real and imaginary part" +description = "Arithmetic -> Multiplication -> Multiply numbers with real and imaginary part" [b0571ddb-9045-412b-9c15-cd1d816d36c1] -description = "Divide purely real numbers" +description = "Arithmetic -> Division -> Divide purely real numbers" [5bb4c7e4-9934-4237-93cc-5780764fdbdd] -description = "Divide purely imaginary numbers" +description = "Arithmetic -> Division -> Divide purely imaginary numbers" [c4e7fef5-64ac-4537-91c2-c6529707701f] -description = "Divide numbers with real and imaginary part" +description = "Arithmetic -> Division -> Divide numbers with real and imaginary part" [c56a7332-aad2-4437-83a0-b3580ecee843] -description = "Absolute value of a positive purely real number" +description = "Absolute value -> Absolute value of a positive purely real number" [cf88d7d3-ee74-4f4e-8a88-a1b0090ecb0c] -description = "Absolute value of a negative purely real number" +description = "Absolute value -> Absolute value of a negative purely real number" [bbe26568-86c1-4bb4-ba7a-da5697e2b994] -description = "Absolute value of a purely imaginary number with positive imaginary part" +description = "Absolute value -> Absolute value of a purely imaginary number with positive imaginary part" [3b48233d-468e-4276-9f59-70f4ca1f26f3] -description = "Absolute value of a purely imaginary number with negative imaginary part" +description = "Absolute value -> Absolute value of a purely imaginary number with negative imaginary part" [fe400a9f-aa22-4b49-af92-51e0f5a2a6d3] -description = "Absolute value of a number with real and imaginary part" +description = "Absolute value -> Absolute value of a number with real and imaginary part" [fb2d0792-e55a-4484-9443-df1eddfc84a2] -description = "Conjugate a purely real number" +description = "Complex conjugate -> Conjugate a purely real number" [e37fe7ac-a968-4694-a460-66cb605f8691] -description = "Conjugate a purely imaginary number" +description = "Complex conjugate -> Conjugate a purely imaginary number" [f7704498-d0be-4192-aaf5-a1f3a7f43e68] -description = "Conjugate a number with real and imaginary part" +description = "Complex conjugate -> Conjugate a number with real and imaginary part" [6d96d4c6-2edb-445b-94a2-7de6d4caaf60] -description = "Euler's identity/formula" +description = "Complex exponential function -> Euler's identity/formula" [2d2c05a0-4038-4427-a24d-72f6624aa45f] -description = "Exponential of 0" +description = "Complex exponential function -> Exponential of 0" [ed87f1bd-b187-45d6-8ece-7e331232c809] -description = "Exponential of a purely real number" +description = "Complex exponential function -> Exponential of a purely real number" [08eedacc-5a95-44fc-8789-1547b27a8702] -description = "Exponential of a number with real and imaginary part" +description = "Complex exponential function -> Exponential of a number with real and imaginary part" + +[d2de4375-7537-479a-aa0e-d474f4f09859] +description = "Complex exponential function -> Exponential resulting in a number with real and imaginary part" + +[06d793bf-73bd-4b02-b015-3030b2c952ec] +description = "Operations between real numbers and complex numbers -> Add real number to complex number" + +[d77dbbdf-b8df-43f6-a58d-3acb96765328] +description = "Operations between real numbers and complex numbers -> Add complex number to real number" + +[20432c8e-8960-4c40-ba83-c9d910ff0a0f] +description = "Operations between real numbers and complex numbers -> Subtract real number from complex number" + +[b4b38c85-e1bf-437d-b04d-49bba6e55000] +description = "Operations between real numbers and complex numbers -> Subtract complex number from real number" + +[dabe1c8c-b8f4-44dd-879d-37d77c4d06bd] +description = "Operations between real numbers and complex numbers -> Multiply complex number by real number" + +[6c81b8c8-9851-46f0-9de5-d96d314c3a28] +description = "Operations between real numbers and complex numbers -> Multiply real number by complex number" + +[8a400f75-710e-4d0c-bcb4-5e5a00c78aa0] +description = "Operations between real numbers and complex numbers -> Divide complex number by real number" + +[9a867d1b-d736-4c41-a41e-90bd148e9d5e] +description = "Operations between real numbers and complex numbers -> Divide real number by complex number" diff --git a/exercises/practice/complex-numbers/babel.config.js b/exercises/practice/complex-numbers/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/complex-numbers/babel.config.js +++ b/exercises/practice/complex-numbers/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/complex-numbers/complex-numbers.js b/exercises/practice/complex-numbers/complex-numbers.js index 9cf27f5b01..4fcd0799e8 100644 --- a/exercises/practice/complex-numbers/complex-numbers.js +++ b/exercises/practice/complex-numbers/complex-numbers.js @@ -5,42 +5,42 @@ export class ComplexNumber { constructor() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get real() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get imag() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } add() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } sub() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } div() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } mul() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get abs() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get conj() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get exp() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/complex-numbers/complex-numbers.spec.js b/exercises/practice/complex-numbers/complex-numbers.spec.js index bdb1e221a1..0275b31924 100644 --- a/exercises/practice/complex-numbers/complex-numbers.spec.js +++ b/exercises/practice/complex-numbers/complex-numbers.spec.js @@ -1,224 +1,311 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { ComplexNumber } from './complex-numbers'; describe('Complex numbers', () => { - test('Real part of a purely real number', () => { - const expected = 1; - const actual = new ComplexNumber(1, 0).real; + describe('Real part', () => { + test('Real part of a purely real number', () => { + const expected = 1; + const actual = new ComplexNumber(1, 0).real; - expect(actual).toEqual(expected); - }); + expect(actual).toEqual(expected); + }); - xtest('Real part of a purely imaginary number', () => { - const expected = 0; - const actual = new ComplexNumber(0, 1).real; + xtest('Real part of a purely imaginary number', () => { + const expected = 0; + const actual = new ComplexNumber(0, 1).real; - expect(actual).toEqual(expected); - }); + expect(actual).toEqual(expected); + }); - xtest('Real part of a number with real and imaginary part', () => { - const expected = 1; - const actual = new ComplexNumber(1, 2).real; + xtest('Real part of a number with real and imaginary part', () => { + const expected = 1; + const actual = new ComplexNumber(1, 2).real; - expect(actual).toEqual(expected); + expect(actual).toEqual(expected); + }); }); - xtest('Imaginary part of a purely real number', () => { - const expected = 0; - const actual = new ComplexNumber(1, 0).imag; + describe('Imaginary part', () => { + xtest('Imaginary part of a purely real number', () => { + const expected = 0; + const actual = new ComplexNumber(1, 0).imag; - expect(actual).toEqual(expected); - }); + expect(actual).toEqual(expected); + }); - xtest('Imaginary part of a purely imaginary number', () => { - const expected = 1; - const actual = new ComplexNumber(0, 1).imag; + xtest('Imaginary part of a purely imaginary number', () => { + const expected = 1; + const actual = new ComplexNumber(0, 1).imag; - expect(actual).toEqual(expected); - }); + expect(actual).toEqual(expected); + }); - xtest('Imaginary part of a number with real and imaginary part', () => { - const expected = 2; - const actual = new ComplexNumber(1, 2).imag; + xtest('Imaginary part of a number with real and imaginary part', () => { + const expected = 2; + const actual = new ComplexNumber(1, 2).imag; - expect(actual).toEqual(expected); + expect(actual).toEqual(expected); + }); }); - xtest('Add purely real numbers', () => { - const expected = new ComplexNumber(3, 0); - const actual = new ComplexNumber(1, 0).add(new ComplexNumber(2, 0)); + xtest('Imaginary unit', () => { + const expected = new ComplexNumber(-1, 0); + const actual = new ComplexNumber(0, 1).mul(new ComplexNumber(0, 1)); expect(actual).toEqual(expected); }); - xtest('Add purely imaginary numbers', () => { - const expected = new ComplexNumber(0, 3); - const actual = new ComplexNumber(0, 1).add(new ComplexNumber(0, 2)); + describe('Arithmetic', () => { + xtest('Add purely real numbers', () => { + const expected = new ComplexNumber(3, 0); + const actual = new ComplexNumber(1, 0).add(new ComplexNumber(2, 0)); - expect(actual).toEqual(expected); - }); + expect(actual).toEqual(expected); + }); - xtest('Add numbers with real and imaginary part', () => { - const expected = new ComplexNumber(4, 6); - const actual = new ComplexNumber(1, 2).add(new ComplexNumber(3, 4)); + xtest('Add purely imaginary numbers', () => { + const expected = new ComplexNumber(0, 3); + const actual = new ComplexNumber(0, 1).add(new ComplexNumber(0, 2)); - expect(actual).toEqual(expected); - }); + expect(actual).toEqual(expected); + }); - xtest('Subtract purely real numbers', () => { - const expected = new ComplexNumber(-1, 0); - const actual = new ComplexNumber(1, 0).sub(new ComplexNumber(2, 0)); + xtest('Add numbers with real and imaginary part', () => { + const expected = new ComplexNumber(4, 6); + const actual = new ComplexNumber(1, 2).add(new ComplexNumber(3, 4)); - expect(actual).toEqual(expected); - }); + expect(actual).toEqual(expected); + }); - xtest('Subtract purely imaginary numbers', () => { - const expected = new ComplexNumber(0, -1); - const actual = new ComplexNumber(0, 1).sub(new ComplexNumber(0, 2)); + xtest('Subtract purely real numbers', () => { + const expected = new ComplexNumber(-1, 0); + const actual = new ComplexNumber(1, 0).sub(new ComplexNumber(2, 0)); - expect(actual).toEqual(expected); - }); + expect(actual).toEqual(expected); + }); - xtest('Subtract numbers with real and imaginary part', () => { - const expected = new ComplexNumber(-2, -2); - const actual = new ComplexNumber(1, 2).sub(new ComplexNumber(3, 4)); + xtest('Subtract purely imaginary numbers', () => { + const expected = new ComplexNumber(0, -1); + const actual = new ComplexNumber(0, 1).sub(new ComplexNumber(0, 2)); - expect(actual).toEqual(expected); - }); + expect(actual).toEqual(expected); + }); - xtest('Multiply purely real numbers', () => { - const expected = new ComplexNumber(2, 0); - const actual = new ComplexNumber(1, 0).mul(new ComplexNumber(2, 0)); + xtest('Subtract numbers with real and imaginary part', () => { + const expected = new ComplexNumber(-2, -2); + const actual = new ComplexNumber(1, 2).sub(new ComplexNumber(3, 4)); - expect(actual).toEqual(expected); - }); + expect(actual).toEqual(expected); + }); - xtest('Multiply imaginary unit', () => { - const expected = new ComplexNumber(-1, 0); - const actual = new ComplexNumber(0, 1).mul(new ComplexNumber(0, 1)); + xtest('Multiply purely real numbers', () => { + const expected = new ComplexNumber(2, 0); + const actual = new ComplexNumber(1, 0).mul(new ComplexNumber(2, 0)); - expect(actual).toEqual(expected); - }); + expect(actual).toEqual(expected); + }); - xtest('Multiply purely imaginary numbers', () => { - const expected = new ComplexNumber(-2, 0); - const actual = new ComplexNumber(0, 1).mul(new ComplexNumber(0, 2)); + xtest('Multiply purely imaginary numbers', () => { + const expected = new ComplexNumber(-2, 0); + const actual = new ComplexNumber(0, 1).mul(new ComplexNumber(0, 2)); - expect(actual).toEqual(expected); - }); + expect(actual).toEqual(expected); + }); - xtest('Multiply numbers with real and imaginary part', () => { - const expected = new ComplexNumber(-5, 10); - const actual = new ComplexNumber(1, 2).mul(new ComplexNumber(3, 4)); + xtest('Multiply numbers with real and imaginary part', () => { + const expected = new ComplexNumber(-5, 10); + const actual = new ComplexNumber(1, 2).mul(new ComplexNumber(3, 4)); - expect(actual).toEqual(expected); - }); + expect(actual).toEqual(expected); + }); - xtest('Divide purely real numbers', () => { - const expected = new ComplexNumber(0.5, 0); - const actual = new ComplexNumber(1, 0).div(new ComplexNumber(2, 0)); + xtest('Divide purely real numbers', () => { + const expected = new ComplexNumber(0.5, 0); + const actual = new ComplexNumber(1, 0).div(new ComplexNumber(2, 0)); - expect(actual).toEqual(expected); - }); + expect(actual).toEqual(expected); + }); - xtest('Divide purely imaginary numbers', () => { - const expected = new ComplexNumber(0.5, 0); - const actual = new ComplexNumber(0, 1).div(new ComplexNumber(0, 2)); + xtest('Divide purely imaginary numbers', () => { + const expected = new ComplexNumber(0.5, 0); + const actual = new ComplexNumber(0, 1).div(new ComplexNumber(0, 2)); - expect(actual).toEqual(expected); - }); + expect(actual).toEqual(expected); + }); - xtest('Divide numbers with real and imaginary part', () => { - const expected = new ComplexNumber(0.44, 0.08); - const actual = new ComplexNumber(1, 2).div(new ComplexNumber(3, 4)); + xtest('Divide numbers with real and imaginary part', () => { + const expected = new ComplexNumber(0.44, 0.08); + const actual = new ComplexNumber(1, 2).div(new ComplexNumber(3, 4)); - expect(actual).toEqual(expected); + expect(actual).toEqual(expected); + }); }); - xtest('Absolute value of a positive purely real number', () => { - const expected = 5; - const actual = new ComplexNumber(5, 0).abs; + describe('Absolute value', () => { + xtest('Absolute value of a positive purely real number', () => { + const expected = 5; + const actual = new ComplexNumber(5, 0).abs; - expect(actual).toEqual(expected); - }); + expect(actual).toEqual(expected); + }); - xtest('Absolute value of a negative purely real number', () => { - const expected = 5; - const actual = new ComplexNumber(-5, 0).abs; + xtest('Absolute value of a negative purely real number', () => { + const expected = 5; + const actual = new ComplexNumber(-5, 0).abs; - expect(actual).toEqual(expected); - }); + expect(actual).toEqual(expected); + }); - xtest('Absolute value of a purely imaginary number with positive imaginary part', () => { - const expected = 5; - const actual = new ComplexNumber(0, 5).abs; + xtest('Absolute value of a purely imaginary number with positive imaginary part', () => { + const expected = 5; + const actual = new ComplexNumber(0, 5).abs; - expect(actual).toEqual(expected); - }); + expect(actual).toEqual(expected); + }); - xtest('Absolute value of a purely imaginary number with negative imaginary part', () => { - const expected = 5; - const actual = new ComplexNumber(0, -5).abs; + xtest('Absolute value of a purely imaginary number with negative imaginary part', () => { + const expected = 5; + const actual = new ComplexNumber(0, -5).abs; - expect(actual).toEqual(expected); - }); + expect(actual).toEqual(expected); + }); - xtest('Absolute value of a number with real and imaginary part', () => { - const expected = 5; - const actual = new ComplexNumber(3, 4).abs; + xtest('Absolute value of a number with real and imaginary part', () => { + const expected = 5; + const actual = new ComplexNumber(3, 4).abs; - expect(actual).toEqual(expected); + expect(actual).toEqual(expected); + }); }); - xtest('Conjugate a purely real number', () => { - const expected = new ComplexNumber(5, 0); - const actual = new ComplexNumber(5, 0).conj; + describe('Complex conjugate', () => { + xtest('Conjugate a purely real number', () => { + const expected = new ComplexNumber(5, 0); + const actual = new ComplexNumber(5, 0).conj; - expect(actual).toEqual(expected); - }); + expect(actual).toEqual(expected); + }); - xtest('Conjugate a purely imaginary number', () => { - const expected = new ComplexNumber(0, -5); - const actual = new ComplexNumber(0, 5).conj; + xtest('Conjugate a purely imaginary number', () => { + const expected = new ComplexNumber(0, -5); + const actual = new ComplexNumber(0, 5).conj; - expect(actual).toEqual(expected); - }); + expect(actual).toEqual(expected); + }); - xtest('Conjugate a number with real and imaginary part', () => { - const expected = new ComplexNumber(1, -1); - const actual = new ComplexNumber(1, 1).conj; + xtest('Conjugate a number with real and imaginary part', () => { + const expected = new ComplexNumber(1, -1); + const actual = new ComplexNumber(1, 1).conj; - expect(actual).toEqual(expected); + expect(actual).toEqual(expected); + }); }); - xtest("Euler's identity/formula", () => { - const expected = new ComplexNumber(-1, 0); - const actual = new ComplexNumber(0, Math.PI).exp; + describe('Complex exponential function', () => { + xtest("Euler's identity/formula", () => { + const expected = new ComplexNumber(-1, 0); + const actual = new ComplexNumber(0, Math.PI).exp; - expect(actual.real).toBeCloseTo(expected.real); - expect(actual.imag).toBeCloseTo(expected.imag); - }); + expect(actual.real).toBeCloseTo(expected.real); + expect(actual.imag).toBeCloseTo(expected.imag); + }); - xtest('Exponential of 0', () => { - const expected = new ComplexNumber(1, 0); - const actual = new ComplexNumber(0, 0).exp; + xtest('Exponential of 0', () => { + const expected = new ComplexNumber(1, 0); + const actual = new ComplexNumber(0, 0).exp; - expect(actual.real).toBeCloseTo(expected.real); - expect(actual.imag).toBeCloseTo(expected.imag); - }); + expect(actual.real).toBeCloseTo(expected.real); + expect(actual.imag).toBeCloseTo(expected.imag); + }); - xtest('Exponential of a purely real number', () => { - const expected = new ComplexNumber(Math.E, 0); - const actual = new ComplexNumber(1, 0).exp; + xtest('Exponential of a purely real number', () => { + const expected = new ComplexNumber(Math.E, 0); + const actual = new ComplexNumber(1, 0).exp; - expect(actual.real).toBeCloseTo(expected.real); - expect(actual.imag).toBeCloseTo(expected.imag); + expect(actual.real).toBeCloseTo(expected.real); + expect(actual.imag).toBeCloseTo(expected.imag); + }); + + xtest('Exponential of a number with real and imaginary part', () => { + const expected = new ComplexNumber(-2, 0); + const actual = new ComplexNumber(Math.LN2, Math.PI).exp; + + expect(actual.real).toBeCloseTo(expected.real); + expect(actual.imag).toBeCloseTo(expected.imag); + }); + + xtest('Exponential resulting in a number with real and imaginary part', () => { + const expected = new ComplexNumber(1, 1); + const actual = new ComplexNumber(Math.LN2 / 2, Math.PI / 4).exp; + + expect(actual.real).toBeCloseTo(expected.real); + expect(actual.imag).toBeCloseTo(expected.imag); + }); }); - xtest('Exponential of a number with real and imaginary part', () => { - const expected = new ComplexNumber(-2, 0); - const actual = new ComplexNumber(Math.LN2, Math.PI).exp; + describe('Operations between real numbers and complex numbers', () => { + xtest('Add real number to complex number', () => { + const expected = new ComplexNumber(6, 2); + const actual = new ComplexNumber(1, 2).add(new ComplexNumber(5, 0)); + + expect(actual.real).toBeCloseTo(expected.real); + expect(actual.imag).toBeCloseTo(expected.imag); + }); + + xtest('Add complex number to real number', () => { + const expected = new ComplexNumber(6, 2); + const actual = new ComplexNumber(5, 0).add(new ComplexNumber(1, 2)); + + expect(actual.real).toBeCloseTo(expected.real); + expect(actual.imag).toBeCloseTo(expected.imag); + }); + + xtest('Subtract real number from complex number', () => { + const expected = new ComplexNumber(1, 7); + const actual = new ComplexNumber(5, 7).sub(new ComplexNumber(4, 0)); + + expect(actual.real).toBeCloseTo(expected.real); + expect(actual.imag).toBeCloseTo(expected.imag); + }); + + xtest('Subtract complex number from real number', () => { + const expected = new ComplexNumber(-1, -7); + const actual = new ComplexNumber(4, 0).sub(new ComplexNumber(5, 7)); + + expect(actual.real).toBeCloseTo(expected.real); + expect(actual.imag).toBeCloseTo(expected.imag); + }); + + xtest('Multiply complex number by real number', () => { + const expected = new ComplexNumber(10, 25); + const actual = new ComplexNumber(2, 5).mul(new ComplexNumber(5, 0)); + + expect(actual.real).toBeCloseTo(expected.real); + expect(actual.imag).toBeCloseTo(expected.imag); + }); + + xtest('Multiply real number by complex number', () => { + const expected = new ComplexNumber(10, 25); + const actual = new ComplexNumber(5, 0).mul(new ComplexNumber(2, 5)); + + expect(actual.real).toBeCloseTo(expected.real); + expect(actual.imag).toBeCloseTo(expected.imag); + }); + + xtest('Divide complex number by real number', () => { + const expected = new ComplexNumber(1, 10); + const actual = new ComplexNumber(10, 100).div(new ComplexNumber(10, 0)); + + expect(actual.real).toBeCloseTo(expected.real); + expect(actual.imag).toBeCloseTo(expected.imag); + }); + + xtest('Divide real number by complex number', () => { + const expected = new ComplexNumber(2.5, -2.5); + const actual = new ComplexNumber(5, 0).div(new ComplexNumber(1, 1)); - expect(actual.real).toBeCloseTo(expected.real); - expect(actual.imag).toBeCloseTo(expected.imag); + expect(actual.real).toBeCloseTo(expected.real); + expect(actual.imag).toBeCloseTo(expected.imag); + }); }); }); diff --git a/exercises/practice/complex-numbers/eslint.config.mjs b/exercises/practice/complex-numbers/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/complex-numbers/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/complex-numbers/jest.config.js b/exercises/practice/complex-numbers/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/complex-numbers/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/complex-numbers/package.json b/exercises/practice/complex-numbers/package.json index 79ef0fc4ff..fed44d94f6 100644 --- a/exercises/practice/complex-numbers/package.json +++ b/exercises/practice/complex-numbers/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/complex-numbers" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/connect/.docs/instructions.md b/exercises/practice/connect/.docs/instructions.md index 2fa003a835..7f34bfa817 100644 --- a/exercises/practice/connect/.docs/instructions.md +++ b/exercises/practice/connect/.docs/instructions.md @@ -2,19 +2,14 @@ Compute the result for a game of Hex / Polygon. -The abstract boardgame known as -[Hex](https://en.wikipedia.org/wiki/Hex_%28board_game%29) / Polygon / -CON-TAC-TIX is quite simple in rules, though complex in practice. Two players -place stones on a parallelogram with hexagonal fields. The player to connect his/her -stones to the opposite side first wins. The four sides of the parallelogram are -divided between the two players (i.e. one player gets assigned a side and the -side directly opposite it and the other player gets assigned the two other -sides). +The abstract boardgame known as [Hex][hex] / Polygon / CON-TAC-TIX is quite simple in rules, though complex in practice. +Two players place stones on a parallelogram with hexagonal fields. +The player to connect his/her stones to the opposite side first wins. +The four sides of the parallelogram are divided between the two players (i.e. one player gets assigned a side and the side directly opposite it and the other player gets assigned the two other sides). -Your goal is to build a program that given a simple representation of a board -computes the winner (or lack thereof). Note that all games need not be "fair". -(For example, players may have mismatched piece counts or the game's board might -have a different width and height.) +Your goal is to build a program that given a simple representation of a board computes the winner (or lack thereof). +Note that all games need not be "fair". +(For example, players may have mismatched piece counts or the game's board might have a different width and height.) The boards look like this: @@ -26,6 +21,7 @@ The boards look like this: X O O O X ``` -"Player `O`" plays from top to bottom, "Player `X`" plays from left to right. In -the above example `O` has made a connection from left to right but nobody has -won since `O` didn't connect top and bottom. +"Player `O`" plays from top to bottom, "Player `X`" plays from left to right. +In the above example `O` has made a connection from left to right but nobody has won since `O` didn't connect top and bottom. + +[hex]: https://en.wikipedia.org/wiki/Hex_%28board_game%29 diff --git a/exercises/practice/connect/.eslintrc b/exercises/practice/connect/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/connect/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/connect/.gitignore b/exercises/practice/connect/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/connect/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/connect/.meta/config.json b/exercises/practice/connect/.meta/config.json index acf9fc2737..e10265cab4 100644 --- a/exercises/practice/connect/.meta/config.json +++ b/exercises/practice/connect/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Compute the result for a game of Hex / Polygon", - "authors": ["arthurchipdean"], + "authors": [ + "arthurchipdean" + ], "contributors": [ "diego-caceres", "matthewmorgan", @@ -10,8 +11,21 @@ "xarxziux" ], "files": { - "solution": ["connect.js"], - "test": ["connect.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "connect.js" + ], + "test": [ + "connect.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] + }, + "blurb": "Compute the result for a game of Hex / Polygon.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false } } diff --git a/exercises/practice/connect/.meta/proof.ci.js b/exercises/practice/connect/.meta/proof.ci.js index 9159886d03..b40653e079 100644 --- a/exercises/practice/connect/.meta/proof.ci.js +++ b/exercises/practice/connect/.meta/proof.ci.js @@ -36,7 +36,7 @@ export class Board { const matches = this.neighbors(pos).filter( ({ x, y }) => this.matches({ x, y }, XorO) && - checked.filter((spot) => spot.x === x && spot.y === y).length === 0 + checked.filter((spot) => spot.x === x && spot.y === y).length === 0, ); if (matches.length === 0) { return false; diff --git a/exercises/practice/connect/babel.config.js b/exercises/practice/connect/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/connect/babel.config.js +++ b/exercises/practice/connect/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/connect/connect.js b/exercises/practice/connect/connect.js index c27a6b8c81..79aa964fa5 100644 --- a/exercises/practice/connect/connect.js +++ b/exercises/practice/connect/connect.js @@ -5,10 +5,10 @@ export class Board { constructor() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } winner() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/connect/connect.spec.js b/exercises/practice/connect/connect.spec.js index c8bd390341..a5fcca9ac7 100644 --- a/exercises/practice/connect/connect.spec.js +++ b/exercises/practice/connect/connect.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { Board } from './connect'; describe('Judging a game of connect', () => { diff --git a/exercises/practice/connect/eslint.config.mjs b/exercises/practice/connect/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/connect/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/connect/jest.config.js b/exercises/practice/connect/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/connect/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/connect/package.json b/exercises/practice/connect/package.json index cb04db803e..498e1ec33c 100644 --- a/exercises/practice/connect/package.json +++ b/exercises/practice/connect/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/connect" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/crypto-square/.docs/instructions.md b/exercises/practice/crypto-square/.docs/instructions.md index f919ff5b93..6c3826ee55 100644 --- a/exercises/practice/crypto-square/.docs/instructions.md +++ b/exercises/practice/crypto-square/.docs/instructions.md @@ -4,11 +4,10 @@ Implement the classic method for composing secret messages called a square code. Given an English text, output the encoded version of that text. -First, the input is normalized: the spaces and punctuation are removed -from the English text and the message is downcased. +First, the input is normalized: the spaces and punctuation are removed from the English text and the message is down-cased. -Then, the normalized characters are broken into rows. These rows can be -regarded as forming a rectangle when printed with intervening newlines. +Then, the normalized characters are broken into rows. +These rows can be regarded as forming a rectangle when printed with intervening newlines. For example, the sentence @@ -22,13 +21,16 @@ is normalized to: "ifmanwasmeanttostayonthegroundgodwouldhavegivenusroots" ``` -The plaintext should be organized in to a rectangle. The size of the -rectangle (`r x c`) should be decided by the length of the message, -such that `c >= r` and `c - r <= 1`, where `c` is the number of columns -and `r` is the number of rows. +The plaintext should be organized into a rectangle as square as possible. +The size of the rectangle should be decided by the length of the message. -Our normalized text is 54 characters long, dictating a rectangle with -`c = 8` and `r = 7`: +If `c` is the number of columns and `r` is the number of rows, then for the rectangle `r` x `c` find the smallest possible integer `c` such that: + +- `r * c >= length of message`, +- and `c >= r`, +- and `c - r <= 1`. + +Our normalized text is 54 characters long, dictating a rectangle with `c = 8` and `r = 7`: ```text "ifmanwas" @@ -40,8 +42,7 @@ Our normalized text is 54 characters long, dictating a rectangle with "sroots " ``` -The coded message is obtained by reading down the columns going left to -right. +The coded message is obtained by reading down the columns going left to right. The message above is coded as: @@ -49,17 +50,14 @@ The message above is coded as: "imtgdvsfearwermayoogoanouuiontnnlvtwttddesaohghnsseoau" ``` -Output the encoded text in chunks that fill perfect rectangles `(r X c)`, -with `c` chunks of `r` length, separated by spaces. For phrases that are -`n` characters short of the perfect rectangle, pad each of the last `n` -chunks with a single trailing space. +Output the encoded text in chunks that fill perfect rectangles `(r X c)`, with `c` chunks of `r` length, separated by spaces. +For phrases that are `n` characters short of the perfect rectangle, pad each of the last `n` chunks with a single trailing space. ```text "imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn sseoau " ``` -Notice that were we to stack these, we could visually decode the -ciphertext back in to the original message: +Notice that were we to stack these, we could visually decode the ciphertext back in to the original message: ```text "imtgdvs" diff --git a/exercises/practice/crypto-square/.eslintrc b/exercises/practice/crypto-square/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/crypto-square/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/crypto-square/.gitignore b/exercises/practice/crypto-square/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/crypto-square/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/crypto-square/.meta/config.json b/exercises/practice/crypto-square/.meta/config.json index e01e7b8f08..a8087b25bd 100644 --- a/exercises/practice/crypto-square/.meta/config.json +++ b/exercises/practice/crypto-square/.meta/config.json @@ -1,8 +1,10 @@ { - "blurb": "Implement the classic method for composing secret messages called a square code.", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "diego-caceres", + "jagdish-15", "ntshcalleia", "rchavarria", "ryanplusplus", @@ -10,10 +12,23 @@ "tejasbubane" ], "files": { - "solution": ["crypto-square.js"], - "test": ["crypto-square.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "crypto-square.js" + ], + "test": [ + "crypto-square.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Implement the classic method for composing secret messages called a square code.", "source": "J Dalbey's Programming Practice problems", - "source_url": "http://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html" + "source_url": "https://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": true + } } diff --git a/exercises/practice/crypto-square/.meta/tests.toml b/exercises/practice/crypto-square/.meta/tests.toml index 054544573b..94ef0819fe 100644 --- a/exercises/practice/crypto-square/.meta/tests.toml +++ b/exercises/practice/crypto-square/.meta/tests.toml @@ -1,10 +1,20 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [407c3837-9aa7-4111-ab63-ec54b58e8e9f] description = "empty plaintext results in an empty ciphertext" +[aad04a25-b8bb-4304-888b-581bea8e0040] +description = "normalization results in empty plaintext" + [64131d65-6fd9-4f58-bdd8-4a2370fb481d] description = "Lowercase" @@ -22,3 +32,8 @@ description = "8 character plaintext results in 3 chunks, the last one with a tr [fbcb0c6d-4c39-4a31-83f6-c473baa6af80] description = "54 character plaintext results in 7 chunks, the last two with trailing spaces" +include = false + +[33fd914e-fa44-445b-8f38-ff8fbc9fe6e6] +description = "54 character plaintext results in 8 chunks, the last two with trailing spaces" +reimplements = "fbcb0c6d-4c39-4a31-83f6-c473baa6af80" diff --git a/exercises/practice/crypto-square/babel.config.js b/exercises/practice/crypto-square/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/crypto-square/babel.config.js +++ b/exercises/practice/crypto-square/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/crypto-square/crypto-square.js b/exercises/practice/crypto-square/crypto-square.js index 82ab1001dc..d6d8927d61 100644 --- a/exercises/practice/crypto-square/crypto-square.js +++ b/exercises/practice/crypto-square/crypto-square.js @@ -5,10 +5,10 @@ export class Crypto { constructor() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get ciphertext() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/crypto-square/crypto-square.spec.js b/exercises/practice/crypto-square/crypto-square.spec.js index 7197c2ee08..2b45f1d56f 100644 --- a/exercises/practice/crypto-square/crypto-square.spec.js +++ b/exercises/practice/crypto-square/crypto-square.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { Crypto } from './crypto-square'; describe('Crypto', () => { @@ -6,17 +7,22 @@ describe('Crypto', () => { expect(crypto.ciphertext).toEqual(''); }); - test('Lowercase', () => { + test('normalization results in empty plaintext', () => { + const crypto = new Crypto('... --- ...'); + expect(crypto.ciphertext).toEqual(''); + }); + + xtest('Lowercase', () => { const crypto = new Crypto('A'); expect(crypto.ciphertext).toEqual('a'); }); - test('Remove spaces', () => { + xtest('Remove spaces', () => { const crypto = new Crypto(' b '); expect(crypto.ciphertext).toEqual('b'); }); - test('Remove punctuation', () => { + xtest('Remove punctuation', () => { const crypto = new Crypto('@1,%!'); expect(crypto.ciphertext).toEqual('1'); }); @@ -31,12 +37,18 @@ describe('Crypto', () => { expect(crypto.ciphertext).toEqual('clu hlt io '); }); - test.skip('54 character plaintext results in 7 chunks, the last two with trailing spaces', () => { - const crypto = new Crypto( - 'If man was meant to stay on the ground, god would have given us roots.' - ); - expect(crypto.ciphertext).toEqual( - 'imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn sseoau ' - ); - }); + // This test doesn't run on our online test runner because it will time-out + // with most implementations. It's up to you to test your solution locally. + test.skip( + '54 character plaintext results in 8 chunks, the last two with trailing spaces', + () => { + const crypto = new Crypto( + 'If man was meant to stay on the ground, god would have given us roots.', + ); + expect(crypto.ciphertext).toEqual( + 'imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn sseoau ', + ); + }, + 20 * 1000, + ); }); diff --git a/exercises/practice/crypto-square/eslint.config.mjs b/exercises/practice/crypto-square/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/crypto-square/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/crypto-square/jest.config.js b/exercises/practice/crypto-square/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/crypto-square/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/crypto-square/package.json b/exercises/practice/crypto-square/package.json index edae366663..c9e6db08f8 100644 --- a/exercises/practice/crypto-square/package.json +++ b/exercises/practice/crypto-square/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/crypto-square" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/custom-set/.docs/instructions.md b/exercises/practice/custom-set/.docs/instructions.md index e4931b058b..33b90e28d7 100644 --- a/exercises/practice/custom-set/.docs/instructions.md +++ b/exercises/practice/custom-set/.docs/instructions.md @@ -2,7 +2,6 @@ Create a custom set type. -Sometimes it is necessary to define a custom data structure of some -type, like a set. In this exercise you will define your own set. How it -works internally doesn't matter, as long as it behaves like a set of -unique elements. +Sometimes it is necessary to define a custom data structure of some type, like a set. +In this exercise you will define your own set. +How it works internally doesn't matter, as long as it behaves like a set of unique elements. diff --git a/exercises/practice/custom-set/.eslintrc b/exercises/practice/custom-set/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/custom-set/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/custom-set/.gitignore b/exercises/practice/custom-set/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/custom-set/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/custom-set/.meta/config.json b/exercises/practice/custom-set/.meta/config.json index 8922cd0c5f..130dbc3fef 100644 --- a/exercises/practice/custom-set/.meta/config.json +++ b/exercises/practice/custom-set/.meta/config.json @@ -1,9 +1,11 @@ { - "blurb": "Create a custom set type.", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "ankorGH", "apapirovski", + "jagdish-15", "ovidiu141", "rchavarria", "ryanplusplus", @@ -11,8 +13,21 @@ "tejasbubane" ], "files": { - "solution": ["custom-set.js"], - "test": ["custom-set.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "custom-set.js" + ], + "test": [ + "custom-set.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] + }, + "blurb": "Create a custom set type.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false } } diff --git a/exercises/practice/custom-set/.meta/proof.ci.js b/exercises/practice/custom-set/.meta/proof.ci.js index b56c67315f..aeabdbce32 100644 --- a/exercises/practice/custom-set/.meta/proof.ci.js +++ b/exercises/practice/custom-set/.meta/proof.ci.js @@ -34,7 +34,7 @@ export class CustomSet { difference(other) { return new CustomSet( - Object.keys(this.data).filter((el) => other.data[el] === undefined) + Object.keys(this.data).filter((el) => other.data[el] === undefined), ); } diff --git a/exercises/practice/custom-set/.meta/tests.toml b/exercises/practice/custom-set/.meta/tests.toml index 6ba6231595..430c139e68 100644 --- a/exercises/practice/custom-set/.meta/tests.toml +++ b/exercises/practice/custom-set/.meta/tests.toml @@ -1,117 +1,130 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [20c5f855-f83a-44a7-abdd-fe75c6cf022b] -description = "sets with no elements are empty" +description = "Returns true if the set contains no elements -> sets with no elements are empty" [d506485d-5706-40db-b7d8-5ceb5acf88d2] -description = "sets with elements are not empty" +description = "Returns true if the set contains no elements -> sets with elements are not empty" [759b9740-3417-44c3-8ca3-262b3c281043] -description = "nothing is contained in an empty set" +description = "Sets can report if they contain an element -> nothing is contained in an empty set" [f83cd2d1-2a85-41bc-b6be-80adbff4be49] -description = "when the element is in the set" +description = "Sets can report if they contain an element -> when the element is in the set" [93423fc0-44d0-4bc0-a2ac-376de8d7af34] -description = "when the element is not in the set" +description = "Sets can report if they contain an element -> when the element is not in the set" [c392923a-637b-4495-b28e-34742cd6157a] -description = "empty set is a subset of another empty set" +description = "A set is a subset if all of its elements are contained in the other set -> empty set is a subset of another empty set" [5635b113-be8c-4c6f-b9a9-23c485193917] -description = "empty set is a subset of non-empty set" +description = "A set is a subset if all of its elements are contained in the other set -> empty set is a subset of non-empty set" [832eda58-6d6e-44e2-92c2-be8cf0173cee] -description = "non-empty set is not a subset of empty set" +description = "A set is a subset if all of its elements are contained in the other set -> non-empty set is not a subset of empty set" [c830c578-8f97-4036-b082-89feda876131] -description = "set is a subset of set with exact same elements" +description = "A set is a subset if all of its elements are contained in the other set -> set is a subset of set with exact same elements" [476a4a1c-0fd1-430f-aa65-5b70cbc810c5] -description = "set is a subset of larger set with same elements" +description = "A set is a subset if all of its elements are contained in the other set -> set is a subset of larger set with same elements" [d2498999-3e46-48e4-9660-1e20c3329d3d] -description = "set is not a subset of set that does not contain its elements" +description = "A set is a subset if all of its elements are contained in the other set -> set is not a subset of set that does not contain its elements" [7d38155e-f472-4a7e-9ad8-5c1f8f95e4cc] -description = "the empty set is disjoint with itself" +description = "Sets are disjoint if they share no elements -> the empty set is disjoint with itself" [7a2b3938-64b6-4b32-901a-fe16891998a6] -description = "empty set is disjoint with non-empty set" +description = "Sets are disjoint if they share no elements -> empty set is disjoint with non-empty set" [589574a0-8b48-48ea-88b0-b652c5fe476f] -description = "non-empty set is disjoint with empty set" +description = "Sets are disjoint if they share no elements -> non-empty set is disjoint with empty set" [febeaf4f-f180-4499-91fa-59165955a523] -description = "sets are not disjoint if they share an element" +description = "Sets are disjoint if they share no elements -> sets are not disjoint if they share an element" [0de20d2f-c952-468a-88c8-5e056740f020] -description = "sets are disjoint if they share no elements" +description = "Sets are disjoint if they share no elements -> sets are disjoint if they share no elements" [4bd24adb-45da-4320-9ff6-38c044e9dff8] -description = "empty sets are equal" +description = "Sets with the same elements are equal -> empty sets are equal" [f65c0a0e-6632-4b2d-b82c-b7c6da2ec224] -description = "empty set is not equal to non-empty set" +description = "Sets with the same elements are equal -> empty set is not equal to non-empty set" [81e53307-7683-4b1e-a30c-7e49155fe3ca] -description = "non-empty set is not equal to empty set" +description = "Sets with the same elements are equal -> non-empty set is not equal to empty set" [d57c5d7c-a7f3-48cc-a162-6b488c0fbbd0] -description = "sets with the same elements are equal" +description = "Sets with the same elements are equal -> sets with the same elements are equal" [dd61bafc-6653-42cc-961a-ab071ee0ee85] -description = "sets with different elements are not equal" +description = "Sets with the same elements are equal -> sets with different elements are not equal" [06059caf-9bf4-425e-aaff-88966cb3ea14] -description = "set is not equal to larger set with same elements" +description = "Sets with the same elements are equal -> set is not equal to larger set with same elements" + +[d4a1142f-09aa-4df9-8b83-4437dcf7ec24] +description = "Sets with the same elements are equal -> set is equal to a set constructed from an array with duplicates" [8a677c3c-a658-4d39-bb88-5b5b1a9659f4] -description = "add to empty set" +description = "Unique elements can be added to a set -> add to empty set" [0903dd45-904d-4cf2-bddd-0905e1a8d125] -description = "add to non-empty set" +description = "Unique elements can be added to a set -> add to non-empty set" [b0eb7bb7-5e5d-4733-b582-af771476cb99] -description = "adding an existing element does not change the set" +description = "Unique elements can be added to a set -> adding an existing element does not change the set" [893d5333-33b8-4151-a3d4-8f273358208a] -description = "intersection of two empty sets is an empty set" +description = "Intersection returns a set of all shared elements -> intersection of two empty sets is an empty set" [d739940e-def2-41ab-a7bb-aaf60f7d782c] -description = "intersection of an empty set and non-empty set is an empty set" +description = "Intersection returns a set of all shared elements -> intersection of an empty set and non-empty set is an empty set" [3607d9d8-c895-4d6f-ac16-a14956e0a4b7] -description = "intersection of a non-empty set and an empty set is an empty set" +description = "Intersection returns a set of all shared elements -> intersection of a non-empty set and an empty set is an empty set" [b5120abf-5b5e-41ab-aede-4de2ad85c34e] -description = "intersection of two sets with no shared elements is an empty set" +description = "Intersection returns a set of all shared elements -> intersection of two sets with no shared elements is an empty set" [af21ca1b-fac9-499c-81c0-92a591653d49] -description = "intersection of two sets with shared elements is a set of the shared elements" +description = "Intersection returns a set of all shared elements -> intersection of two sets with shared elements is a set of the shared elements" [c5e6e2e4-50e9-4bc2-b89f-c518f015b57e] -description = "difference of two empty sets is an empty set" +description = "Difference (or Complement) of a set is a set of all elements that are only in the first set -> difference of two empty sets is an empty set" [2024cc92-5c26-44ed-aafd-e6ca27d6fcd2] -description = "difference of empty set and non-empty set is an empty set" +description = "Difference (or Complement) of a set is a set of all elements that are only in the first set -> difference of empty set and non-empty set is an empty set" [e79edee7-08aa-4c19-9382-f6820974b43e] -description = "difference of a non-empty set and an empty set is the non-empty set" +description = "Difference (or Complement) of a set is a set of all elements that are only in the first set -> difference of a non-empty set and an empty set is the non-empty set" [c5ac673e-d707-4db5-8d69-7082c3a5437e] -description = "difference of two non-empty sets is a set of elements that are only in the first set" +description = "Difference (or Complement) of a set is a set of all elements that are only in the first set -> difference of two non-empty sets is a set of elements that are only in the first set" + +[20d0a38f-7bb7-4c4a-ac15-90c7392ecf2b] +description = "Difference (or Complement) of a set is a set of all elements that are only in the first set -> difference removes all duplicates in the first set" [c45aed16-5494-455a-9033-5d4c93589dc6] -description = "union of empty sets is an empty set" +description = "Union returns a set of all elements in either set -> union of empty sets is an empty set" [9d258545-33c2-4fcb-a340-9f8aa69e7a41] -description = "union of an empty set and non-empty set is the non-empty set" +description = "Union returns a set of all elements in either set -> union of an empty set and non-empty set is the non-empty set" [3aade50c-80c7-4db8-853d-75bac5818b83] -description = "union of a non-empty set and empty set is the non-empty set" +description = "Union returns a set of all elements in either set -> union of a non-empty set and empty set is the non-empty set" [a00bb91f-c4b4-4844-8f77-c73e2e9df77c] -description = "union of non-empty sets contains all unique elements" +description = "Union returns a set of all elements in either set -> union of non-empty sets contains all unique elements" diff --git a/exercises/practice/custom-set/babel.config.js b/exercises/practice/custom-set/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/custom-set/babel.config.js +++ b/exercises/practice/custom-set/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/custom-set/custom-set.js b/exercises/practice/custom-set/custom-set.js index a203975de1..ca0d8f55fe 100644 --- a/exercises/practice/custom-set/custom-set.js +++ b/exercises/practice/custom-set/custom-set.js @@ -5,42 +5,42 @@ export class CustomSet { constructor() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } empty() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } contains() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } add() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } subset() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } disjoint() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } eql() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } union() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } intersection() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } difference() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/custom-set/custom-set.spec.js b/exercises/practice/custom-set/custom-set.spec.js index aec7416b08..08daf6367e 100644 --- a/exercises/practice/custom-set/custom-set.spec.js +++ b/exercises/practice/custom-set/custom-set.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { CustomSet } from './custom-set'; describe('CustomSet', () => { @@ -53,7 +54,7 @@ describe('CustomSet', () => { xtest('set is a subset of larger set with same elements', () => { const actual = new CustomSet([1, 2, 3]).subset( - new CustomSet([4, 1, 2, 3]) + new CustomSet([4, 1, 2, 3]), ); expect(actual).toBe(true); }); @@ -116,10 +117,16 @@ describe('CustomSet', () => { const actual = new CustomSet([1, 2, 3]).eql(new CustomSet([1, 2, 4])); expect(actual).toBe(false); }); + xtest('set is not equal to larger set with same elements', () => { const actual = new CustomSet([1, 2, 3]).eql(new CustomSet([1, 2, 3, 4])); expect(actual).toBe(false); }); + + xtest('set is equal to a set constructed from an array with duplicates', () => { + const actual = new CustomSet([1]).eql(new CustomSet([1, 1])); + expect(actual).toBe(true); + }); }); describe('add: unique elements can be added to a set', () => { @@ -157,7 +164,7 @@ describe('CustomSet', () => { xtest('intersection of a non-empty set and an empty set is an empty set', () => { const actual = new CustomSet([1, 2, 3, 4]).intersection( - new CustomSet([]) + new CustomSet([]), ); const expected = new CustomSet([]); expect(actual.eql(expected)).toBe(true); @@ -165,7 +172,7 @@ describe('CustomSet', () => { xtest('intersection of two sets with no shared elements is an empty set', () => { const actual = new CustomSet([1, 2, 3]).intersection( - new CustomSet([4, 5, 6]) + new CustomSet([4, 5, 6]), ); const expected = new CustomSet([]); expect(actual.eql(expected)).toBe(true); @@ -173,7 +180,7 @@ describe('CustomSet', () => { xtest('intersection of two sets with shared elements is a set of the shared elements', () => { const actual = new CustomSet([1, 2, 3, 4]).intersection( - new CustomSet([3, 2, 5]) + new CustomSet([3, 2, 5]), ); const expected = new CustomSet([2, 3]); expect(actual.eql(expected)).toBe(true); @@ -204,6 +211,12 @@ describe('CustomSet', () => { const expected = new CustomSet([1, 3]); expect(actual.eql(expected)).toBe(true); }); + + xtest('difference removes all duplicates in the first set', () => { + const actual = new CustomSet([1, 1]).difference(new CustomSet([1])); + const expected = new CustomSet([]); + expect(actual.eql(expected)).toBe(true); + }); }); describe('union: returns a set of all elements in either set', () => { diff --git a/exercises/practice/custom-set/eslint.config.mjs b/exercises/practice/custom-set/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/custom-set/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/custom-set/jest.config.js b/exercises/practice/custom-set/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/custom-set/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/custom-set/package.json b/exercises/practice/custom-set/package.json index 7a8da68ae8..a9b3f1c73d 100644 --- a/exercises/practice/custom-set/package.json +++ b/exercises/practice/custom-set/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/custom-set" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/darts/.docs/instructions.md b/exercises/practice/darts/.docs/instructions.md index ba48371eff..6518201c77 100644 --- a/exercises/practice/darts/.docs/instructions.md +++ b/exercises/practice/darts/.docs/instructions.md @@ -1,17 +1,31 @@ # Instructions -Write a function that returns the earned points in a single toss of a Darts game. +Calculate the points scored in a single toss of a Darts game. -[Darts](https://en.wikipedia.org/wiki/Darts) is a game where players -throw darts to a [target](https://en.wikipedia.org/wiki/Darts#/media/File:Darts_in_a_dartboard.jpg). +[Darts][darts] is a game where players throw darts at a [target][darts-target]. -In our particular instance of the game, the target rewards with 4 different amounts of points, depending on where the dart lands: +In our particular instance of the game, the target rewards 4 different amounts of points, depending on where the dart lands: + +![Our dart scoreboard with values from a complete miss to a bullseye](https://assets.exercism.org/images/exercises/darts/darts-scoreboard.svg) - If the dart lands outside the target, player earns no points (0 points). - If the dart lands in the outer circle of the target, player earns 1 point. - If the dart lands in the middle circle of the target, player earns 5 points. - If the dart lands in the inner circle of the target, player earns 10 points. -The outer circle has a radius of 10 units (This is equivalent to the total radius for the entire target), the middle circle a radius of 5 units, and the inner circle a radius of 1. Of course, they are all centered to the same point (That is, the circles are [concentric](http://mathworld.wolfram.com/ConcentricCircles.html)) defined by the coordinates (0, 0). +The outer circle has a radius of 10 units (this is equivalent to the total radius for the entire target), the middle circle a radius of 5 units, and the inner circle a radius of 1. +Of course, they are all centered at the same point — that is, the circles are [concentric][] defined by the coordinates (0, 0). + +Given a point in the target (defined by its [Cartesian coordinates][cartesian-coordinates] `x` and `y`, where `x` and `y` are [real][real-numbers]), calculate the correct score earned by a dart landing at that point. + +## Credit + +The scoreboard image was created by [habere-et-dispertire][habere-et-dispertire] using [Inkscape][inkscape]. -Write a function that given a point in the target (defined by its `real` cartesian coordinates `x` and `y`), returns the correct amount earned by a dart landing in that point. +[darts]: https://en.wikipedia.org/wiki/Darts +[darts-target]: https://en.wikipedia.org/wiki/Darts#/media/File:Darts_in_a_dartboard.jpg +[concentric]: https://mathworld.wolfram.com/ConcentricCircles.html +[cartesian-coordinates]: https://www.mathsisfun.com/data/cartesian-coordinates.html +[real-numbers]: https://www.mathsisfun.com/numbers/real-numbers.html +[habere-et-dispertire]: https://exercism.org/profiles/habere-et-dispertire +[inkscape]: https://en.wikipedia.org/wiki/Inkscape diff --git a/exercises/practice/darts/.eslintrc b/exercises/practice/darts/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/darts/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/darts/.gitignore b/exercises/practice/darts/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/darts/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/darts/.meta/config.json b/exercises/practice/darts/.meta/config.json index c430b44b23..f62649ddf0 100644 --- a/exercises/practice/darts/.meta/config.json +++ b/exercises/practice/darts/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Write a function that returns the earned points in a single toss of a Darts game", - "authors": ["ramadis"], + "authors": [ + "ramadis" + ], "contributors": [ "ankorGH", "d-vail", @@ -9,9 +10,22 @@ "Tyresius92" ], "files": { - "solution": ["darts.js"], - "test": ["darts.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "darts.js" + ], + "test": [ + "darts.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, - "source": "Inspired by an exercise created by a professor Della Paolera in Argentina" + "blurb": "Calculate the points scored in a single toss of a Darts game.", + "source": "Inspired by an exercise created by a professor Della Paolera in Argentina", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/darts/babel.config.js b/exercises/practice/darts/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/darts/babel.config.js +++ b/exercises/practice/darts/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/darts/darts.js b/exercises/practice/darts/darts.js index 7b6d4dd3de..8d7f78edb6 100644 --- a/exercises/practice/darts/darts.js +++ b/exercises/practice/darts/darts.js @@ -4,5 +4,5 @@ // export const score = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/darts/darts.spec.js b/exercises/practice/darts/darts.spec.js index 163cabbfcc..cbf63b5c71 100644 --- a/exercises/practice/darts/darts.spec.js +++ b/exercises/practice/darts/darts.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { score } from './darts'; describe('Darts', () => { diff --git a/exercises/practice/darts/eslint.config.mjs b/exercises/practice/darts/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/darts/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/darts/jest.config.js b/exercises/practice/darts/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/darts/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/darts/package.json b/exercises/practice/darts/package.json index 214da8e7b2..d29d03e089 100644 --- a/exercises/practice/darts/package.json +++ b/exercises/practice/darts/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/darts" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/diamond/.docs/instructions.md b/exercises/practice/diamond/.docs/instructions.md index 2195a4c90c..3034802feb 100644 --- a/exercises/practice/diamond/.docs/instructions.md +++ b/exercises/practice/diamond/.docs/instructions.md @@ -1,8 +1,7 @@ # Instructions -The diamond kata takes as its input a letter, and outputs it in a diamond -shape. Given a letter, it prints a diamond starting with 'A', with the -supplied letter at the widest point. +The diamond kata takes as its input a letter, and outputs it in a diamond shape. +Given a letter, it prints a diamond starting with 'A', with the supplied letter at the widest point. ## Requirements diff --git a/exercises/practice/diamond/.eslintrc b/exercises/practice/diamond/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/diamond/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/diamond/.gitignore b/exercises/practice/diamond/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/diamond/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/diamond/.meta/config.json b/exercises/practice/diamond/.meta/config.json index 9477c8a331..f6da8ca248 100644 --- a/exercises/practice/diamond/.meta/config.json +++ b/exercises/practice/diamond/.meta/config.json @@ -1,5 +1,4 @@ { - "blurb": "Given a letter, print a diamond starting with 'A' with the supplied letter at the widest point.", "authors": [], "contributors": [ "ankorGH", @@ -11,10 +10,23 @@ "tejasbubane" ], "files": { - "solution": ["diamond.js"], - "test": ["diamond.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "diamond.js" + ], + "test": [ + "diamond.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Given a letter, print a diamond starting with 'A' with the supplied letter at the widest point.", "source": "Seb Rose", - "source_url": "http://claysnow.co.uk/recycling-tests-in-tdd/" + "source_url": "https://web.archive.org/web/20220807163751/http://claysnow.co.uk/recycling-tests-in-tdd/", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/diamond/babel.config.js b/exercises/practice/diamond/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/diamond/babel.config.js +++ b/exercises/practice/diamond/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/diamond/diamond.js b/exercises/practice/diamond/diamond.js index 03b7ad0ce8..ffb3cab42d 100644 --- a/exercises/practice/diamond/diamond.js +++ b/exercises/practice/diamond/diamond.js @@ -4,5 +4,5 @@ // export const rows = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/diamond/diamond.spec.js b/exercises/practice/diamond/diamond.spec.js index 0563893923..d92b9c9d4c 100644 --- a/exercises/practice/diamond/diamond.spec.js +++ b/exercises/practice/diamond/diamond.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { rows } from './diamond'; describe('Diamond', () => { diff --git a/exercises/practice/diamond/eslint.config.mjs b/exercises/practice/diamond/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/diamond/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/diamond/jest.config.js b/exercises/practice/diamond/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/diamond/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/diamond/package.json b/exercises/practice/diamond/package.json index 98362d9315..3a11f2610d 100644 --- a/exercises/practice/diamond/package.json +++ b/exercises/practice/diamond/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/diamond" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/difference-of-squares/.docs/instructions.md b/exercises/practice/difference-of-squares/.docs/instructions.md index c3999e86ab..39c38b5094 100644 --- a/exercises/practice/difference-of-squares/.docs/instructions.md +++ b/exercises/practice/difference-of-squares/.docs/instructions.md @@ -8,10 +8,7 @@ The square of the sum of the first ten natural numbers is The sum of the squares of the first ten natural numbers is 1² + 2² + ... + 10² = 385. -Hence the difference between the square of the sum of the first -ten natural numbers and the sum of the squares of the first ten -natural numbers is 3025 - 385 = 2640. +Hence the difference between the square of the sum of the first ten natural numbers and the sum of the squares of the first ten natural numbers is 3025 - 385 = 2640. -You are not expected to discover an efficient solution to this yourself from -first principles; research is allowed, indeed, encouraged. Finding the best -algorithm for the problem is a key skill in software engineering. +You are not expected to discover an efficient solution to this yourself from first principles; research is allowed, indeed, encouraged. +Finding the best algorithm for the problem is a key skill in software engineering. diff --git a/exercises/practice/difference-of-squares/.eslintrc b/exercises/practice/difference-of-squares/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/difference-of-squares/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/difference-of-squares/.gitignore b/exercises/practice/difference-of-squares/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/difference-of-squares/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/difference-of-squares/.meta/config.json b/exercises/practice/difference-of-squares/.meta/config.json index e8a960b810..862b92d58a 100644 --- a/exercises/practice/difference-of-squares/.meta/config.json +++ b/exercises/practice/difference-of-squares/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Find the difference between the square of the sum and the sum of the squares of the first N natural numbers.", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "ankorGH", "hayashi-ay", @@ -12,10 +13,23 @@ "xarxziux" ], "files": { - "solution": ["difference-of-squares.js"], - "test": ["difference-of-squares.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "difference-of-squares.js" + ], + "test": [ + "difference-of-squares.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Find the difference between the square of the sum and the sum of the squares of the first N natural numbers.", "source": "Problem 6 at Project Euler", - "source_url": "http://projecteuler.net/problem=6" + "source_url": "https://projecteuler.net/problem=6", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/difference-of-squares/babel.config.js b/exercises/practice/difference-of-squares/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/difference-of-squares/babel.config.js +++ b/exercises/practice/difference-of-squares/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/difference-of-squares/difference-of-squares.js b/exercises/practice/difference-of-squares/difference-of-squares.js index 19aad4d0ba..b24e7b0150 100644 --- a/exercises/practice/difference-of-squares/difference-of-squares.js +++ b/exercises/practice/difference-of-squares/difference-of-squares.js @@ -5,18 +5,18 @@ export class Squares { constructor() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get sumOfSquares() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get squareOfSum() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get difference() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/difference-of-squares/difference-of-squares.spec.js b/exercises/practice/difference-of-squares/difference-of-squares.spec.js index 59b040c165..300c9e382c 100644 --- a/exercises/practice/difference-of-squares/difference-of-squares.spec.js +++ b/exercises/practice/difference-of-squares/difference-of-squares.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { Squares } from './difference-of-squares'; describe('difference-of-squares', () => { diff --git a/exercises/practice/difference-of-squares/eslint.config.mjs b/exercises/practice/difference-of-squares/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/difference-of-squares/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/difference-of-squares/jest.config.js b/exercises/practice/difference-of-squares/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/difference-of-squares/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/difference-of-squares/package.json b/exercises/practice/difference-of-squares/package.json index 3ca2e57a82..4ae916896d 100644 --- a/exercises/practice/difference-of-squares/package.json +++ b/exercises/practice/difference-of-squares/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/difference-of-squares" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/diffie-hellman/.docs/instructions.md b/exercises/practice/diffie-hellman/.docs/instructions.md index 6b3a641c51..9f1c85e312 100644 --- a/exercises/practice/diffie-hellman/.docs/instructions.md +++ b/exercises/practice/diffie-hellman/.docs/instructions.md @@ -2,9 +2,8 @@ Diffie-Hellman key exchange. -Alice and Bob use Diffie-Hellman key exchange to share secrets. They -start with prime numbers, pick private keys, generate and share public -keys, and then generate a shared secret key. +Alice and Bob use Diffie-Hellman key exchange to share secrets. +They start with prime numbers, pick private keys, generate and share public keys, and then generate a shared secret key. ## Step 0 @@ -12,27 +11,27 @@ The test program supplies prime numbers p and g. ## Step 1 -Alice picks a private key, a, greater than 1 and less than p. Bob does -the same to pick a private key b. +Alice picks a private key, a, greater than 1 and less than p. +Bob does the same to pick a private key b. ## Step 2 Alice calculates a public key A. - A = g**a mod p + A = gᵃ mod p -Using the same p and g, Bob similarly calculates a public key B from his -private key b. +Using the same p and g, Bob similarly calculates a public key B from his private key b. ## Step 3 -Alice and Bob exchange public keys. Alice calculates secret key s. +Alice and Bob exchange public keys. +Alice calculates secret key s. - s = B**a mod p + s = Bᵃ mod p Bob calculates - s = A**b mod p + s = Aᵇ mod p -The calculations produce the same result! Alice and Bob now share -secret s. +The calculations produce the same result! +Alice and Bob now share secret s. diff --git a/exercises/practice/diffie-hellman/.eslintrc b/exercises/practice/diffie-hellman/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/diffie-hellman/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/diffie-hellman/.gitignore b/exercises/practice/diffie-hellman/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/diffie-hellman/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/diffie-hellman/.meta/config.json b/exercises/practice/diffie-hellman/.meta/config.json index 5febce0d2a..cac727712a 100644 --- a/exercises/practice/diffie-hellman/.meta/config.json +++ b/exercises/practice/diffie-hellman/.meta/config.json @@ -1,18 +1,33 @@ { - "blurb": "Diffie-Hellman key exchange.", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "ankorGH", + "jagdish-15", "rchavarria", "serixscorpio", "SleeplessByte", "tejasbubane" ], "files": { - "solution": ["diffie-hellman.js"], - "test": ["diffie-hellman.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "diffie-hellman.js" + ], + "test": [ + "diffie-hellman.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Diffie-Hellman key exchange.", "source": "Wikipedia, 1024 bit key from www.cryptopp.com/wiki.", - "source_url": "http://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange" + "source_url": "https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/diffie-hellman/.meta/proof.ci.js b/exercises/practice/diffie-hellman/.meta/proof.ci.js index c10c46a2e9..4c7f8a93d0 100644 --- a/exercises/practice/diffie-hellman/.meta/proof.ci.js +++ b/exercises/practice/diffie-hellman/.meta/proof.ci.js @@ -91,7 +91,7 @@ export class DiffieHellman { getPublicKey(privateKey) { if (privateKey <= 1 || privateKey > this.p - 1) { throw Error( - 'Private key a must be greater than one but less than modulus parameter p!' + 'Private key a must be greater than one but less than modulus parameter p!', ); } return this.g ** privateKey % this.p; @@ -113,4 +113,8 @@ export class DiffieHellman { PRIMES.includes(g) ); } + + static getPrivateKey(p) { + return Math.floor(Math.random() * (p - 2) + 2); + } } diff --git a/exercises/practice/diffie-hellman/.meta/tests.toml b/exercises/practice/diffie-hellman/.meta/tests.toml index e17d006ea7..a56c97fae2 100644 --- a/exercises/practice/diffie-hellman/.meta/tests.toml +++ b/exercises/practice/diffie-hellman/.meta/tests.toml @@ -1,9 +1,16 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [1b97bf38-4307-418e-bfd2-446ffc77588d] -description = "private key is in range 1 .. p" +description = "private key is greater than 1 and less than p" [68b2a5f7-7755-44c3-97b2-d28d21f014a9] description = "private key is random" @@ -11,6 +18,9 @@ description = "private key is random" [b4161d8e-53a1-4241-ae8f-48cc86527f22] description = "can calculate public key using private key" +[0d25f8d7-4897-4338-a033-2d3d7a9af688] +description = "can calculate public key when given a different private key" + [cd02ad45-3f52-4510-99cc-5161dad948a8] description = "can calculate secret using other party's public key" diff --git a/exercises/practice/diffie-hellman/babel.config.js b/exercises/practice/diffie-hellman/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/diffie-hellman/babel.config.js +++ b/exercises/practice/diffie-hellman/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/diffie-hellman/diffie-hellman.js b/exercises/practice/diffie-hellman/diffie-hellman.js index d825928b50..8286f120e8 100644 --- a/exercises/practice/diffie-hellman/diffie-hellman.js +++ b/exercises/practice/diffie-hellman/diffie-hellman.js @@ -5,14 +5,18 @@ export class DiffieHellman { constructor(p, g) { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } getPublicKey(privateKey) { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } getSecret(theirPublicKey, myPrivateKey) { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); + } + + getPrivateKey() { + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/diffie-hellman/diffie-hellman.spec.js b/exercises/practice/diffie-hellman/diffie-hellman.spec.js index 14129c23a7..213a5df723 100644 --- a/exercises/practice/diffie-hellman/diffie-hellman.spec.js +++ b/exercises/practice/diffie-hellman/diffie-hellman.spec.js @@ -1,4 +1,4 @@ -/* eslint-disable no-new */ +import { describe, expect, test, xtest } from '@jest/globals'; import { DiffieHellman } from './diffie-hellman'; describe('diffie-hellman', () => { @@ -14,7 +14,7 @@ describe('diffie-hellman', () => { }).toThrow(); }); - describe('input validation', () => { + describe('private key is greater than 1 and less than p', () => { const p = 23; const g = 5; const diffieHellman = new DiffieHellman(p, g); @@ -61,7 +61,7 @@ describe('diffie-hellman', () => { xtest('can calculate public key using private key', () => { expect(diffieHellman.getPublicKey(alicePrivateKey)).toEqual( - alicePublicKey + alicePublicKey, ); }); @@ -87,4 +87,25 @@ describe('diffie-hellman', () => { expect(secretA).toEqual(secretB); }); + + xtest('private key is greater than 1 and less than p', () => { + let p = 23; + for (let i = 0; i < 10; i++) { + let privateKey = DiffieHellman.getPrivateKey(p); + expect(privateKey).toBeGreaterThan(1); + expect(privateKey).toBeLessThan(p); + } + }); + + xtest('private key is random', () => { + let p = 7919; + let uniqueKeys = new Set(); + let testIterations = 1000; + + for (let i = 0; i < testIterations; i++) { + uniqueKeys.add(DiffieHellman.getPrivateKey(p)); + } + + expect(uniqueKeys.size).toBeGreaterThan(testIterations - 100); + }); }); diff --git a/exercises/practice/diffie-hellman/eslint.config.mjs b/exercises/practice/diffie-hellman/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/diffie-hellman/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/diffie-hellman/jest.config.js b/exercises/practice/diffie-hellman/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/diffie-hellman/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/diffie-hellman/package.json b/exercises/practice/diffie-hellman/package.json index 05403e3e6c..a0fd4f98f8 100644 --- a/exercises/practice/diffie-hellman/package.json +++ b/exercises/practice/diffie-hellman/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/diffie-hellman" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/dnd-character/.docs/instructions.md b/exercises/practice/dnd-character/.docs/instructions.md index 6f4ad2aedf..e14e7949d6 100644 --- a/exercises/practice/dnd-character/.docs/instructions.md +++ b/exercises/practice/dnd-character/.docs/instructions.md @@ -1,17 +1,15 @@ # Instructions -For a game of [Dungeons & Dragons][dnd], each player starts by generating a -character they can play with. This character has, among other things, six -abilities; strength, dexterity, constitution, intelligence, wisdom and -charisma. These six abilities have scores that are determined randomly. You -do this by rolling four 6-sided dice and record the sum of the largest three -dice. You do this six times, once for each ability. +For a game of [Dungeons & Dragons][dnd], each player starts by generating a character they can play with. +This character has, among other things, six abilities; strength, dexterity, constitution, intelligence, wisdom and charisma. +These six abilities have scores that are determined randomly. +You do this by rolling four 6-sided dice and recording the sum of the largest three dice. +You do this six times, once for each ability. -Your character's initial hitpoints are 10 + your character's constitution -modifier. You find your character's constitution modifier by subtracting 10 -from your character's constitution, divide by 2 and round down. +Your character's initial hitpoints are 10 + your character's constitution modifier. +You find your character's constitution modifier by subtracting 10 from your character's constitution, divide by 2 and round down. -Write a random character generator that follows the rules above. +Write a random character generator that follows the above rules. For example, the six throws of four dice may look like: @@ -24,10 +22,11 @@ For example, the six throws of four dice may look like: Because constitution is 3, the constitution modifier is -4 and the hitpoints are 6. -## Notes +~~~~exercism/note +Most programming languages feature (pseudo-)random generators, but few programming languages are designed to roll dice. +One such language is [Troll][troll]. -Most programming languages feature (pseudo-)random generators, but few -programming languages are designed to roll dice. One such language is [Troll]. +[troll]: https://di.ku.dk/Ansatte/?pure=da%2Fpublications%2Ftroll-a-language-for-specifying-dicerolls(84a45ff0-068b-11df-825d-000ea68e967b)%2Fexport.html +~~~~ [dnd]: https://en.wikipedia.org/wiki/Dungeons_%26_Dragons -[troll]: http://hjemmesider.diku.dk/~torbenm/Troll/ diff --git a/exercises/practice/dnd-character/.docs/introduction.md b/exercises/practice/dnd-character/.docs/introduction.md new file mode 100644 index 0000000000..5301f61829 --- /dev/null +++ b/exercises/practice/dnd-character/.docs/introduction.md @@ -0,0 +1,10 @@ +# Introduction + +After weeks of anticipation, you and your friends get together for your very first game of [Dungeons & Dragons][dnd] (D&D). +Since this is the first session of the game, each player has to generate a character to play with. +The character's abilities are determined by rolling 6-sided dice, but where _are_ the dice? +With a shock, you realize that your friends are waiting for _you_ to produce the dice; after all it was your idea to play D&D! +Panicking, you realize you forgot to bring the dice, which would mean no D&D game. +As you have some basic coding skills, you quickly come up with a solution: you'll write a program to simulate dice rolls. + +[dnd]: https://en.wikipedia.org/wiki/Dungeons_%26_Dragons diff --git a/exercises/practice/dnd-character/.eslintrc b/exercises/practice/dnd-character/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/dnd-character/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/dnd-character/.gitignore b/exercises/practice/dnd-character/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/dnd-character/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/dnd-character/.meta/config.json b/exercises/practice/dnd-character/.meta/config.json index dc5c25fd89..029c75b146 100644 --- a/exercises/practice/dnd-character/.meta/config.json +++ b/exercises/practice/dnd-character/.meta/config.json @@ -1,12 +1,31 @@ { - "blurb": "Randomly generate Dungeons & Dragons characters", - "authors": ["Tyresius92"], - "contributors": ["ankorGH", "hayashi-ay", "SleeplessByte"], + "authors": [ + "Tyresius92" + ], + "contributors": [ + "ankorGH", + "hayashi-ay", + "jagdish-15", + "SleeplessByte" + ], "files": { - "solution": ["dnd-character.js"], - "test": ["dnd-character.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "dnd-character.js" + ], + "test": [ + "dnd-character.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Randomly generate Dungeons & Dragons characters.", "source": "Simon Shine, Erik Schierboom", - "source_url": "https://github.com/exercism/problem-specifications/issues/616#issuecomment-437358945" + "source_url": "https://github.com/exercism/problem-specifications/issues/616#issuecomment-437358945", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/dnd-character/.meta/tests.toml b/exercises/practice/dnd-character/.meta/tests.toml index 5d9d1aa34a..719043b253 100644 --- a/exercises/practice/dnd-character/.meta/tests.toml +++ b/exercises/practice/dnd-character/.meta/tests.toml @@ -1,54 +1,61 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [1e9ae1dc-35bd-43ba-aa08-e4b94c20fa37] -description = "ability modifier for score 3 is -4" +description = "ability modifier -> ability modifier for score 3 is -4" [cc9bb24e-56b8-4e9e-989d-a0d1a29ebb9c] -description = "ability modifier for score 4 is -3" +description = "ability modifier -> ability modifier for score 4 is -3" [5b519fcd-6946-41ee-91fe-34b4f9808326] -description = "ability modifier for score 5 is -3" +description = "ability modifier -> ability modifier for score 5 is -3" [dc2913bd-6d7a-402e-b1e2-6d568b1cbe21] -description = "ability modifier for score 6 is -2" +description = "ability modifier -> ability modifier for score 6 is -2" [099440f5-0d66-4b1a-8a10-8f3a03cc499f] -description = "ability modifier for score 7 is -2" +description = "ability modifier -> ability modifier for score 7 is -2" [cfda6e5c-3489-42f0-b22b-4acb47084df0] -description = "ability modifier for score 8 is -1" +description = "ability modifier -> ability modifier for score 8 is -1" [c70f0507-fa7e-4228-8463-858bfbba1754] -description = "ability modifier for score 9 is -1" +description = "ability modifier -> ability modifier for score 9 is -1" [6f4e6c88-1cd9-46a0-92b8-db4a99b372f7] -description = "ability modifier for score 10 is 0" +description = "ability modifier -> ability modifier for score 10 is 0" [e00d9e5c-63c8-413f-879d-cd9be9697097] -description = "ability modifier for score 11 is 0" +description = "ability modifier -> ability modifier for score 11 is 0" [eea06f3c-8de0-45e7-9d9d-b8cab4179715] -description = "ability modifier for score 12 is +1" +description = "ability modifier -> ability modifier for score 12 is +1" [9c51f6be-db72-4af7-92ac-b293a02c0dcd] -description = "ability modifier for score 13 is +1" +description = "ability modifier -> ability modifier for score 13 is +1" [94053a5d-53b6-4efc-b669-a8b5098f7762] -description = "ability modifier for score 14 is +2" +description = "ability modifier -> ability modifier for score 14 is +2" [8c33e7ca-3f9f-4820-8ab3-65f2c9e2f0e2] -description = "ability modifier for score 15 is +2" +description = "ability modifier -> ability modifier for score 15 is +2" [c3ec871e-1791-44d0-b3cc-77e5fb4cd33d] -description = "ability modifier for score 16 is +3" +description = "ability modifier -> ability modifier for score 16 is +3" [3d053cee-2888-4616-b9fd-602a3b1efff4] -description = "ability modifier for score 17 is +3" +description = "ability modifier -> ability modifier for score 17 is +3" [bafd997a-e852-4e56-9f65-14b60261faee] -description = "ability modifier for score 18 is +4" +description = "ability modifier -> ability modifier for score 18 is +4" [4f28f19c-2e47-4453-a46a-c0d365259c14] description = "random ability is within range" @@ -58,3 +65,8 @@ description = "random character is valid" [2ca77b9b-c099-46c3-a02c-0d0f68ffa0fe] description = "each ability is only calculated once" +include = false + +[dca2b2ec-f729-4551-84b9-078876bb4808] +description = "each ability is only calculated once" +reimplements = "2ca77b9b-c099-46c3-a02c-0d0f68ffa0fe" diff --git a/exercises/practice/dnd-character/babel.config.js b/exercises/practice/dnd-character/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/dnd-character/babel.config.js +++ b/exercises/practice/dnd-character/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/dnd-character/dnd-character.js b/exercises/practice/dnd-character/dnd-character.js index dac0b6190d..e71613e7dd 100644 --- a/exercises/practice/dnd-character/dnd-character.js +++ b/exercises/practice/dnd-character/dnd-character.js @@ -4,39 +4,39 @@ // export const abilityModifier = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; export class Character { static rollAbility() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get strength() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get dexterity() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get constitution() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get intelligence() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get wisdom() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get charisma() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get hitpoints() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/dnd-character/dnd-character.spec.js b/exercises/practice/dnd-character/dnd-character.spec.js index 210a7b09f3..d930c140a1 100644 --- a/exercises/practice/dnd-character/dnd-character.spec.js +++ b/exercises/practice/dnd-character/dnd-character.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { Character, abilityModifier } from './dnd-character'; describe('D&D Character', () => { @@ -68,13 +69,13 @@ describe('D&D Character', () => { xtest('ability score less than 3 throws error', () => { expect(() => abilityModifier(2)).toThrow( - new Error('Ability scores must be at least 3') + new Error('Ability scores must be at least 3'), ); }); xtest('ability score greater than 18 throws error', () => { expect(() => abilityModifier(19)).toThrow( - new Error('Ability scores can be at most 18') + new Error('Ability scores can be at most 18'), ); }); }); @@ -84,27 +85,54 @@ describe('D&D Character', () => { expect(Character.rollAbility()).toBeGreaterThanOrEqual(3); }); - xtest('random character is valid', () => { + xtest('random character is valid - strength', () => { const Drizzt = new Character(); - expect(Drizzt.strength).toBeLessThanOrEqual(18); expect(Drizzt.strength).toBeGreaterThanOrEqual(3); + }); + + xtest('random character is valid - dexterity', () => { + const Drizzt = new Character(); expect(Drizzt.dexterity).toBeLessThanOrEqual(18); expect(Drizzt.dexterity).toBeGreaterThanOrEqual(3); + }); + + xtest('random character is valid - constitution', () => { + const Drizzt = new Character(); expect(Drizzt.constitution).toBeLessThanOrEqual(18); expect(Drizzt.constitution).toBeGreaterThanOrEqual(3); + }); + + xtest('random character is valid - intelligence', () => { + const Drizzt = new Character(); expect(Drizzt.intelligence).toBeLessThanOrEqual(18); expect(Drizzt.intelligence).toBeGreaterThanOrEqual(3); + }); + + xtest('random character is valid - wisdom', () => { + const Drizzt = new Character(); expect(Drizzt.wisdom).toBeLessThanOrEqual(18); expect(Drizzt.wisdom).toBeGreaterThanOrEqual(3); + }); + + xtest('random character is valid - charisma', () => { + const Drizzt = new Character(); expect(Drizzt.charisma).toBeLessThanOrEqual(18); expect(Drizzt.charisma).toBeGreaterThanOrEqual(3); + }); + + xtest('random character is valid - hitpoints', () => { + const Drizzt = new Character(); expect(Drizzt.hitpoints).toEqual(10 + abilityModifier(Drizzt.constitution)); }); xtest('each ability is only calculated once', () => { const Drizzt = new Character(); - expect(Drizzt.strength).toEqual(Drizzt.strength); + expect(Drizzt.dexterity).toEqual(Drizzt.dexterity); + expect(Drizzt.constitution).toEqual(Drizzt.constitution); + expect(Drizzt.intelligence).toEqual(Drizzt.intelligence); + expect(Drizzt.wisdom).toEqual(Drizzt.wisdom); + expect(Drizzt.charisma).toEqual(Drizzt.charisma); }); }); diff --git a/exercises/practice/dnd-character/eslint.config.mjs b/exercises/practice/dnd-character/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/dnd-character/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/dnd-character/jest.config.js b/exercises/practice/dnd-character/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/dnd-character/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/dnd-character/package.json b/exercises/practice/dnd-character/package.json index e5f882bf3e..6b476bcd61 100644 --- a/exercises/practice/dnd-character/package.json +++ b/exercises/practice/dnd-character/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/dnd-character" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/dominoes/.docs/instructions.md b/exercises/practice/dominoes/.docs/instructions.md index 47f05a60df..75055b9e89 100644 --- a/exercises/practice/dominoes/.docs/instructions.md +++ b/exercises/practice/dominoes/.docs/instructions.md @@ -2,14 +2,14 @@ Make a chain of dominoes. -Compute a way to order a given set of dominoes in such a way that they form a -correct domino chain (the dots on one half of a stone match the dots on the -neighbouring half of an adjacent stone) and that dots on the halves of the -stones which don't have a neighbour (the first and last stone) match each other. +Compute a way to order a given set of domino stones so that they form a correct domino chain. +In the chain, the dots on one half of a stone must match the dots on the neighboring half of an adjacent stone. +Additionally, the dots on the halves of the stones without neighbors (the first and last stone) must match each other. For example given the stones `[2|1]`, `[2|3]` and `[1|3]` you should compute something like `[1|2] [2|3] [3|1]` or `[3|2] [2|1] [1|3]` or `[1|3] [3|2] [2|1]` etc, where the first and last numbers are the same. -For stones `[1|2]`, `[4|1]` and `[2|3]` the resulting chain is not valid: `[4|1] [1|2] [2|3]`'s first and last numbers are not the same. 4 != 3 +For stones `[1|2]`, `[4|1]` and `[2|3]` the resulting chain is not valid: `[4|1] [1|2] [2|3]`'s first and last numbers are not the same. +4 != 3 Some test cases may use duplicate stones in a chain solution, assume that multiple Domino sets are being used. diff --git a/exercises/practice/dominoes/.docs/introduction.md b/exercises/practice/dominoes/.docs/introduction.md new file mode 100644 index 0000000000..df248c2116 --- /dev/null +++ b/exercises/practice/dominoes/.docs/introduction.md @@ -0,0 +1,13 @@ +# Introduction + +In Toyland, the trains are always busy delivering treasures across the city, from shiny marbles to rare building blocks. +The tracks they run on are made of colorful domino-shaped pieces, each marked with two numbers. +For the trains to move, the dominoes must form a perfect chain where the numbers match. + +Today, an urgent delivery of rare toys is on hold. +You've been handed a set of track pieces to inspect. +If they can form a continuous chain, the train will be on its way, bringing smiles across Toyland. +If not, the set will be discarded, and another will be tried. + +The toys are counting on you to solve this puzzle. +Will the dominoes connect the tracks and send the train rolling, or will the set be left behind? diff --git a/exercises/practice/dominoes/.eslintrc b/exercises/practice/dominoes/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/dominoes/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/dominoes/.gitignore b/exercises/practice/dominoes/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/dominoes/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/dominoes/.meta/config.json b/exercises/practice/dominoes/.meta/config.json index 1ab0f2fc95..e09fdcb27b 100644 --- a/exercises/practice/dominoes/.meta/config.json +++ b/exercises/practice/dominoes/.meta/config.json @@ -1,10 +1,27 @@ { - "blurb": "Make a chain of dominoes.", - "authors": ["chauchakching"], - "contributors": ["SleeplessByte"], + "authors": [ + "chauchakching" + ], + "contributors": [ + "jagdish-15", + "SleeplessByte" + ], "files": { - "solution": ["dominoes.js"], - "test": ["dominoes.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "dominoes.js" + ], + "test": [ + "dominoes.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] + }, + "blurb": "Make a chain of dominoes.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false } } diff --git a/exercises/practice/dominoes/.meta/tests.toml b/exercises/practice/dominoes/.meta/tests.toml index 23ff84f906..08c8e08d02 100644 --- a/exercises/practice/dominoes/.meta/tests.toml +++ b/exercises/practice/dominoes/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [31a673f2-5e54-49fe-bd79-1c1dae476c9c] description = "empty input = empty output" @@ -37,3 +44,6 @@ description = "separate loops" [cd061538-6046-45a7-ace9-6708fe8f6504] description = "nine elements" + +[44704c7c-3adb-4d98-bd30-f45527cf8b49] +description = "separate three-domino loops" diff --git a/exercises/practice/dominoes/babel.config.js b/exercises/practice/dominoes/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/dominoes/babel.config.js +++ b/exercises/practice/dominoes/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/dominoes/dominoes.js b/exercises/practice/dominoes/dominoes.js index 2081ec1c5f..87292b287c 100644 --- a/exercises/practice/dominoes/dominoes.js +++ b/exercises/practice/dominoes/dominoes.js @@ -4,5 +4,5 @@ // export const chain = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/dominoes/dominoes.spec.js b/exercises/practice/dominoes/dominoes.spec.js index bc924694d8..a99eb0663b 100644 --- a/exercises/practice/dominoes/dominoes.spec.js +++ b/exercises/practice/dominoes/dominoes.spec.js @@ -1,3 +1,4 @@ +import { describe, xdescribe, expect, test, xtest } from '@jest/globals'; import { chain } from './dominoes'; function runTest(dominoes, expected) { @@ -11,7 +12,7 @@ function runTest(dominoes, expected) { function runTestsExpectingNull(dominoes) { const result = chain(dominoes); - it('Should not have a chain', () => { + test('Should not have a chain', () => { expect(result).toBe(null); }); } @@ -19,37 +20,37 @@ function runTestsExpectingNull(dominoes) { function runTestsExpectingChain(dominoes) { const result = chain(dominoes); - it('Should have a chain', () => { + test('Should have a chain', () => { expect(result).not.toBe(null); }); - it('The number of dominoes in the output equals the number of dominoes in the input.', () => { + xtest('The number of dominoes in the output equals the number of dominoes in the input.', () => { expect(result).toHaveLength(dominoes.length); }); - it('For each adjacent pair of dominoes ... (a, b), (c, d) ...: b is equal to c.', () => { + xtest('For each adjacent pair of dominoes ... (a, b), (c, d) ...: b is equal to c.', () => { expect( result .map((v, i) => { if (i === result.length - 1) return true; return v[1] === result[i + 1][0]; }) - .every(Boolean) + .every(Boolean), ).toBe(true); }); if (dominoes.length > 0) { - it('For the dominoes on the ends (a, b) ... (c, d): a is equal to d.', () => { + xtest('For the dominoes on the ends (a, b) ... (c, d): a is equal to d.', () => { expect(result[0][0] === result[result.length - 1][1]).toBe(true); }); } // 4. Every domino appears in the output an equal number of times as the number of times it appears in the input. // (in other words, the dominoes in the output are the same dominoes as the ones in the input) - it('Should have the same dominoes', () => { + xtest('Should have the same dominoes', () => { const sortDomino = (domino) => [...domino].sort(); expect([...dominoes].map(sortDomino).sort()).toEqual( - [...result].map(sortDomino).sort() + [...result].map(sortDomino).sort(), ); }); } @@ -74,7 +75,7 @@ describe('Dominoes', () => { [3, 1], [2, 3], ], - true + true, ); }); @@ -85,7 +86,7 @@ describe('Dominoes', () => { [1, 3], [2, 3], ], - true + true, ); }); @@ -96,7 +97,7 @@ describe('Dominoes', () => { [4, 1], [2, 3], ], - false + false, ); }); @@ -106,7 +107,7 @@ describe('Dominoes', () => { [1, 1], [2, 2], ], - false + false, ); }); @@ -118,7 +119,7 @@ describe('Dominoes', () => { [3, 4], [4, 3], ], - false + false, ); }); @@ -130,7 +131,7 @@ describe('Dominoes', () => { [3, 1], [4, 4], ], - false + false, ); }); @@ -143,7 +144,7 @@ describe('Dominoes', () => { [2, 4], [2, 4], ], - true + true, ); }); @@ -157,7 +158,7 @@ describe('Dominoes', () => { [2, 2], [3, 3], ], - true + true, ); }); @@ -174,7 +175,21 @@ describe('Dominoes', () => { [3, 4], [5, 6], ], - true + true, + ); + }); + + xdescribe('separate three-domino loops', () => { + runTest( + [ + [1, 2], + [2, 3], + [3, 1], + [4, 5], + [5, 6], + [6, 4], + ], + false, ); }); }); diff --git a/exercises/practice/dominoes/eslint.config.mjs b/exercises/practice/dominoes/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/dominoes/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/dominoes/jest.config.js b/exercises/practice/dominoes/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/dominoes/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/dominoes/package.json b/exercises/practice/dominoes/package.json index 5a09a3c505..d22c576522 100644 --- a/exercises/practice/dominoes/package.json +++ b/exercises/practice/dominoes/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/dominoes" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/eliuds-eggs/.docs/instructions.md b/exercises/practice/eliuds-eggs/.docs/instructions.md new file mode 100644 index 0000000000..b0c2df593c --- /dev/null +++ b/exercises/practice/eliuds-eggs/.docs/instructions.md @@ -0,0 +1,8 @@ +# Instructions + +Your task is to count the number of 1 bits in the binary representation of a number. + +## Restrictions + +Keep your hands off that bit-count functionality provided by your standard library! +Solve this one yourself using other basic tools instead. diff --git a/exercises/practice/eliuds-eggs/.docs/introduction.md b/exercises/practice/eliuds-eggs/.docs/introduction.md new file mode 100644 index 0000000000..2b2e5c43d8 --- /dev/null +++ b/exercises/practice/eliuds-eggs/.docs/introduction.md @@ -0,0 +1,65 @@ +# Introduction + +Your friend Eliud inherited a farm from her grandma Tigist. +Her granny was an inventor and had a tendency to build things in an overly complicated manner. +The chicken coop has a digital display showing an encoded number representing the positions of all eggs that could be picked up. + +Eliud is asking you to write a program that shows the actual number of eggs in the coop. + +The position information encoding is calculated as follows: + +1. Scan the potential egg-laying spots and mark down a `1` for an existing egg or a `0` for an empty spot. +2. Convert the number from binary to decimal. +3. Show the result on the display. + +## Example 1 + +![Seven individual nest boxes arranged in a row whose first, third, fourth and seventh nests each have a single egg.](https://assets.exercism.org/images/exercises/eliuds-eggs/example-1-coop.svg) + +```text + _ _ _ _ _ _ _ +|E| |E|E| | |E| +``` + +### Resulting Binary + +![1011001](https://assets.exercism.org/images/exercises/eliuds-eggs/example-1-binary.svg) + +```text + _ _ _ _ _ _ _ +|1|0|1|1|0|0|1| +``` + +### Decimal number on the display + +89 + +### Actual eggs in the coop + +4 + +## Example 2 + +![Seven individual nest boxes arranged in a row where only the fourth nest has an egg.](https://assets.exercism.org/images/exercises/eliuds-eggs/example-2-coop.svg) + +```text + _ _ _ _ _ _ _ +| | | |E| | | | +``` + +### Resulting Binary + +![0001000](https://assets.exercism.org/images/exercises/eliuds-eggs/example-2-binary.svg) + +```text + _ _ _ _ _ _ _ +|0|0|0|1|0|0|0| +``` + +### Decimal number on the display + +8 + +### Actual eggs in the coop + +1 diff --git a/exercises/practice/eliuds-eggs/.gitignore b/exercises/practice/eliuds-eggs/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/eliuds-eggs/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/eliuds-eggs/.meta/config.json b/exercises/practice/eliuds-eggs/.meta/config.json new file mode 100644 index 0000000000..9b5f350dc8 --- /dev/null +++ b/exercises/practice/eliuds-eggs/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "Cool-Katt" + ], + "files": { + "solution": [ + "eliuds-eggs.js" + ], + "test": [ + "eliuds-eggs.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] + }, + "blurb": "Help Eliud count the number of eggs in her chicken coop by counting the number of 1 bits in a binary representation.", + "source": "Christian Willner, Eric Willigers", + "source_url": "https://forum.exercism.org/t/new-exercise-suggestion-pop-count/7632/5" +} diff --git a/exercises/practice/eliuds-eggs/.meta/proof.ci.js b/exercises/practice/eliuds-eggs/.meta/proof.ci.js new file mode 100644 index 0000000000..ad767f867f --- /dev/null +++ b/exercises/practice/eliuds-eggs/.meta/proof.ci.js @@ -0,0 +1,5 @@ +export const eggCount = (displayValue) => + [...displayValue.toString(2)].reduce( + (acc, curr) => (curr === '1' ? (acc += 1) : acc), + 0, + ); diff --git a/exercises/practice/eliuds-eggs/.meta/tests.toml b/exercises/practice/eliuds-eggs/.meta/tests.toml new file mode 100644 index 0000000000..e11683c2ef --- /dev/null +++ b/exercises/practice/eliuds-eggs/.meta/tests.toml @@ -0,0 +1,22 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[559e789d-07d1-4422-9004-3b699f83bca3] +description = "0 eggs" + +[97223282-f71e-490c-92f0-b3ec9e275aba] +description = "1 egg" + +[1f8fd18f-26e9-4144-9a0e-57cdfc4f4ff5] +description = "4 eggs" + +[0c18be92-a498-4ef2-bcbb-28ac4b06cb81] +description = "13 eggs" diff --git a/exercises/practice/eliuds-eggs/.npmrc b/exercises/practice/eliuds-eggs/.npmrc new file mode 100644 index 0000000000..d26df800bb --- /dev/null +++ b/exercises/practice/eliuds-eggs/.npmrc @@ -0,0 +1 @@ +audit=false diff --git a/exercises/practice/eliuds-eggs/LICENSE b/exercises/practice/eliuds-eggs/LICENSE new file mode 100644 index 0000000000..90e73be03b --- /dev/null +++ b/exercises/practice/eliuds-eggs/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Exercism + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/exercises/practice/eliuds-eggs/babel.config.js b/exercises/practice/eliuds-eggs/babel.config.js new file mode 100644 index 0000000000..a638497df1 --- /dev/null +++ b/exercises/practice/eliuds-eggs/babel.config.js @@ -0,0 +1,4 @@ +module.exports = { + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], +}; diff --git a/exercises/practice/eliuds-eggs/eliuds-eggs.js b/exercises/practice/eliuds-eggs/eliuds-eggs.js new file mode 100644 index 0000000000..9f552ac1fa --- /dev/null +++ b/exercises/practice/eliuds-eggs/eliuds-eggs.js @@ -0,0 +1,8 @@ +// +// This is only a SKELETON file for the 'Eliud's Eggs' exercise. It's been provided as a +// convenience to get you started writing code faster. +// + +export const eggCount = (displayValue) => { + throw new Error('Remove this line and implement the function'); +}; diff --git a/exercises/practice/eliuds-eggs/eliuds-eggs.spec.js b/exercises/practice/eliuds-eggs/eliuds-eggs.spec.js new file mode 100644 index 0000000000..f27a0b29ee --- /dev/null +++ b/exercises/practice/eliuds-eggs/eliuds-eggs.spec.js @@ -0,0 +1,28 @@ +import { describe, expect, test, xtest } from '@jest/globals'; +import { eggCount } from './eliuds-eggs'; + +describe('EliudsEggs', () => { + test('0 eggs', () => { + const expected = 0; + const actual = eggCount(0); + expect(actual).toEqual(expected); + }); + + xtest('1 egg', () => { + const expected = 1; + const actual = eggCount(16); + expect(actual).toEqual(expected); + }); + + xtest('4 eggs', () => { + const expected = 4; + const actual = eggCount(89); + expect(actual).toEqual(expected); + }); + + xtest('13 eggs', () => { + const expected = 13; + const actual = eggCount(2000000000); + expect(actual).toEqual(expected); + }); +}); diff --git a/exercises/practice/eliuds-eggs/eslint.config.mjs b/exercises/practice/eliuds-eggs/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/eliuds-eggs/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/eliuds-eggs/jest.config.js b/exercises/practice/eliuds-eggs/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/eliuds-eggs/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/eliuds-eggs/package.json b/exercises/practice/eliuds-eggs/package.json new file mode 100644 index 0000000000..b811c4487a --- /dev/null +++ b/exercises/practice/eliuds-eggs/package.json @@ -0,0 +1,39 @@ +{ + "name": "@exercism/javascript-eliuds-eggs", + "description": "Exercism practice exercise on eliuds-eggs", + "author": "Katrina Owen", + "contributors": [ + "Cool-Katt (https://github.com/Cool-Katt)", + "Derk-Jan Karrenbeld (https://derk-jan.com)", + "Tejas Bubane (https://tejasbubane.github.io/)" + ], + "private": true, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/exercism/javascript", + "directory": "exercises/practice/eliuds-eggs" + }, + "devDependencies": { + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" + }, + "dependencies": {}, + "scripts": { + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" +} diff --git a/exercises/practice/etl/.docs/instructions.md b/exercises/practice/etl/.docs/instructions.md index ff96906c6b..802863b540 100644 --- a/exercises/practice/etl/.docs/instructions.md +++ b/exercises/practice/etl/.docs/instructions.md @@ -1,21 +1,8 @@ # Instructions -We are going to do the `Transform` step of an Extract-Transform-Load. +Your task is to change the data format of letters and their point values in the game. -## ETL - -Extract-Transform-Load (ETL) is a fancy way of saying, "We have some crufty, legacy data over in this system, and now we need it in this shiny new system over here, so -we're going to migrate this." - -(Typically, this is followed by, "We're only going to need to run this -once." That's then typically followed by much forehead slapping and -moaning about how stupid we could possibly be.) - -## The goal - -We're going to extract some Scrabble scores from a legacy system. - -The old system stored a list of letters per score: +Currently, letters are stored in groups based on their score, in a one-to-many mapping. - 1 point: "A", "E", "I", "O", "U", "L", "N", "R", "S", "T", - 2 points: "D", "G", @@ -25,23 +12,16 @@ The old system stored a list of letters per score: - 8 points: "J", "X", - 10 points: "Q", "Z", -The shiny new Scrabble system instead stores the score per letter, which -makes it much faster and easier to calculate the score for a word. It -also stores the letters in lower-case regardless of the case of the -input letters: +This needs to be changed to store each individual letter with its score in a one-to-one mapping. - "a" is worth 1 point. - "b" is worth 3 points. - "c" is worth 3 points. - "d" is worth 2 points. -- Etc. - -Your mission, should you choose to accept it, is to transform the legacy data -format to the shiny new format. +- etc. -## Notes +As part of this change, the team has also decided to change the letters to be lower-case rather than upper-case. -A final note about scoring, Scrabble is played around the world in a -variety of languages, each with its own unique scoring table. For -example, an "E" is scored at 2 in the Māori-language version of the -game while being scored at 4 in the Hawaiian-language version. +~~~~exercism/note +If you want to look at how the data was previously structured and how it needs to change, take a look at the examples in the test suite. +~~~~ diff --git a/exercises/practice/etl/.docs/introduction.md b/exercises/practice/etl/.docs/introduction.md new file mode 100644 index 0000000000..5be65147d7 --- /dev/null +++ b/exercises/practice/etl/.docs/introduction.md @@ -0,0 +1,16 @@ +# Introduction + +You work for a company that makes an online multiplayer game called Lexiconia. + +To play the game, each player is given 13 letters, which they must rearrange to create words. +Different letters have different point values, since it's easier to create words with some letters than others. + +The game was originally launched in English, but it is very popular, and now the company wants to expand to other languages as well. + +Different languages need to support different point values for letters. +The point values are determined by how often letters are used, compared to other letters in that language. + +For example, the letter 'C' is quite common in English, and is only worth 3 points. +But in Norwegian it's a very rare letter, and is worth 10 points. + +To make it easier to add new languages, your team needs to change the way letters and their point values are stored in the game. diff --git a/exercises/practice/etl/.eslintrc b/exercises/practice/etl/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/etl/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/etl/.gitignore b/exercises/practice/etl/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/etl/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/etl/.meta/config.json b/exercises/practice/etl/.meta/config.json index cb04ec7e5e..592c6932ea 100644 --- a/exercises/practice/etl/.meta/config.json +++ b/exercises/practice/etl/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "We are going to do the `Transform` step of an Extract-Transform-Load.", - "authors": ["rchavarria"], + "authors": [ + "rchavarria" + ], "contributors": [ "ankorGH", "draalger", @@ -13,10 +14,23 @@ "trvrfrd" ], "files": { - "solution": ["etl.js"], - "test": ["etl.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "etl.js" + ], + "test": [ + "etl.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, - "source": "The Jumpstart Lab team", - "source_url": "http://jumpstartlab.com" + "blurb": "Change the data format for scoring a game to more easily add other languages.", + "source": "Based on an exercise by the JumpstartLab team for students at The Turing School of Software and Design.", + "source_url": "https://turing.edu", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/etl/babel.config.js b/exercises/practice/etl/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/etl/babel.config.js +++ b/exercises/practice/etl/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/etl/eslint.config.mjs b/exercises/practice/etl/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/etl/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/etl/etl.js b/exercises/practice/etl/etl.js index 9890b2afda..37958eeec1 100644 --- a/exercises/practice/etl/etl.js +++ b/exercises/practice/etl/etl.js @@ -4,5 +4,5 @@ // export const transform = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/etl/etl.spec.js b/exercises/practice/etl/etl.spec.js index 93688c85de..82a61e8eb4 100644 --- a/exercises/practice/etl/etl.spec.js +++ b/exercises/practice/etl/etl.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { transform } from './etl'; describe('Transform legacy to new', () => { diff --git a/exercises/practice/etl/jest.config.js b/exercises/practice/etl/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/etl/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/etl/package.json b/exercises/practice/etl/package.json index 6ad820d7a0..224fcfe4df 100644 --- a/exercises/practice/etl/package.json +++ b/exercises/practice/etl/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/etl" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/flatten-array/.docs/instructions.md b/exercises/practice/flatten-array/.docs/instructions.md index 02b68cdfeb..b5b82713d9 100644 --- a/exercises/practice/flatten-array/.docs/instructions.md +++ b/exercises/practice/flatten-array/.docs/instructions.md @@ -1,11 +1,16 @@ # Instructions -Take a nested list and return a single flattened list with all values except nil/null. +Take a nested array of any depth and return a fully flattened array. -The challenge is to write a function that accepts an arbitrarily-deep nested list-like structure and returns a flattened structure without any nil/null values. +Note that some language tracks may include null-like values in the input array, and the way these values are represented varies by track. +Such values should be excluded from the flattened array. -For Example +Additionally, the input may be of a different data type and contain different types, depending on the track. -input: [1,[2,3,null,4],[null],5] +Check the test suite for details. -output: [1,2,3,4,5] +## Example + +input: `[1, [2, 6, null], [[null, [4]], 5]]` + +output: `[1, 2, 6, 4, 5]` diff --git a/exercises/practice/flatten-array/.docs/introduction.md b/exercises/practice/flatten-array/.docs/introduction.md new file mode 100644 index 0000000000..a314857465 --- /dev/null +++ b/exercises/practice/flatten-array/.docs/introduction.md @@ -0,0 +1,7 @@ +# Introduction + +A shipment of emergency supplies has arrived, but there's a problem. +To protect from damage, the items — flashlights, first-aid kits, blankets — are packed inside boxes, and some of those boxes are nested several layers deep inside other boxes! + +To be prepared for an emergency, everything must be easily accessible in one box. +Can you unpack all the supplies and place them into a single box, so they're ready when needed most? diff --git a/exercises/practice/flatten-array/.eslintrc b/exercises/practice/flatten-array/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/flatten-array/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/flatten-array/.gitignore b/exercises/practice/flatten-array/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/flatten-array/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/flatten-array/.meta/config.json b/exercises/practice/flatten-array/.meta/config.json index 030f7cdbb3..3f5fbf678f 100644 --- a/exercises/practice/flatten-array/.meta/config.json +++ b/exercises/practice/flatten-array/.meta/config.json @@ -1,19 +1,34 @@ { - "blurb": "Take a nested list and return a single list with all values except nil/null", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "ankorGH", "gabriel376", + "jagdish-15", "rchavarria", "SleeplessByte", "tejasbubane", "xarxziux" ], "files": { - "solution": ["flatten-array.js"], - "test": ["flatten-array.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "flatten-array.js" + ], + "test": [ + "flatten-array.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Take a nested list and return a single list with all values except nil/null.", "source": "Interview Question", - "source_url": "https://reference.wolfram.com/language/ref/Flatten.html" + "source_url": "https://reference.wolfram.com/language/ref/Flatten.html", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/flatten-array/.meta/proof.ci.js b/exercises/practice/flatten-array/.meta/proof.ci.js index d0412cd8ff..6b14e5b7ad 100644 --- a/exercises/practice/flatten-array/.meta/proof.ci.js +++ b/exercises/practice/flatten-array/.meta/proof.ci.js @@ -3,7 +3,7 @@ export const flatten = (arr) => { .reduce( (acc, el) => Array.isArray(el) ? acc.concat(flatten(el)) : acc.concat(el), - [] + [], ) .filter((el) => el !== null && el !== undefined); }; diff --git a/exercises/practice/flatten-array/.meta/tests.toml b/exercises/practice/flatten-array/.meta/tests.toml index 99eea4950e..44acf175d2 100644 --- a/exercises/practice/flatten-array/.meta/tests.toml +++ b/exercises/practice/flatten-array/.meta/tests.toml @@ -1,10 +1,23 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[8c71dabd-da60-422d-a290-4a571471fb14] +description = "empty" [d268b919-963c-442d-9f07-82b93f1b518c] description = "no nesting" +[3f15bede-c856-479e-bb71-1684b20c6a30] +description = "flattens a nested array" + [c84440cc-bb3a-48a6-862c-94cf23f2815d] description = "flattens array with just integers present" @@ -14,8 +27,37 @@ description = "5 level nesting" [d572bdba-c127-43ed-bdcd-6222ac83d9f7] description = "6 level nesting" +[0705a8e5-dc86-4cec-8909-150c5e54fa9c] +description = "null values are omitted from the final result" + +[c6cf26de-8ccd-4410-84bd-b9efd88fd2bc] +description = "consecutive null values at the front of the list are omitted from the final result" +include = false + +[bc72da10-5f55-4ada-baf3-50e4da02ec8e] +description = "consecutive null values at the front of the array are omitted from the final result" +reimplements = "c6cf26de-8ccd-4410-84bd-b9efd88fd2bc" + +[382c5242-587e-4577-b8ce-a5fb51e385a1] +description = "consecutive null values in the middle of the list are omitted from the final result" +include = false + +[6991836d-0d9b-4703-80a0-3f1f23eb5981] +description = "consecutive null values in the middle of the array are omitted from the final result" +reimplements = "382c5242-587e-4577-b8ce-a5fb51e385a1" + [ef1d4790-1b1e-4939-a179-51ace0829dbd] description = "6 level nest list with null values" +include = false + +[dc90a09c-5376-449c-a7b3-c2d20d540069] +description = "6 level nested array with null values" +reimplements = "ef1d4790-1b1e-4939-a179-51ace0829dbd" [85721643-705a-4150-93ab-7ae398e2942d] description = "all values in nested list are null" +include = false + +[51f5d9af-8f7f-4fb5-a156-69e8282cb275] +description = "all values in nested array are null" +reimplements = "85721643-705a-4150-93ab-7ae398e2942d" diff --git a/exercises/practice/flatten-array/babel.config.js b/exercises/practice/flatten-array/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/flatten-array/babel.config.js +++ b/exercises/practice/flatten-array/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/flatten-array/eslint.config.mjs b/exercises/practice/flatten-array/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/flatten-array/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/flatten-array/flatten-array.js b/exercises/practice/flatten-array/flatten-array.js index 06de551447..56172f28aa 100644 --- a/exercises/practice/flatten-array/flatten-array.js +++ b/exercises/practice/flatten-array/flatten-array.js @@ -4,5 +4,5 @@ // export const flatten = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/flatten-array/flatten-array.spec.js b/exercises/practice/flatten-array/flatten-array.spec.js index 648a313410..c9a3c222cc 100644 --- a/exercises/practice/flatten-array/flatten-array.spec.js +++ b/exercises/practice/flatten-array/flatten-array.spec.js @@ -1,7 +1,8 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { flatten } from './flatten-array'; describe('FlattenArray', () => { - xtest('empty', () => { + test('empty', () => { expect(flatten([])).toEqual([]); }); @@ -39,15 +40,23 @@ describe('FlattenArray', () => { expect(flatten([1, 2, null])).toEqual([1, 2]); }); - xtest('6 level nest list with null values', () => { + xtest('consecutive null values at the front of the array are omitted from the final result', () => { + expect(flatten([null, null, 3])).toEqual([3]); + }); + + xtest('consecutive null values in the middle of the array are omitted from the final result', () => { + expect(flatten([1, null, null, 4])).toEqual([1, 4]); + }); + + xtest('6 level nested array with null values', () => { expect(flatten([0, 2, [[2, 3], 8, [[100]], null, [[null]]], -2])).toEqual([ 0, 2, 2, 3, 8, 100, -2, ]); }); - xtest('all values in nested list are null', () => { + xtest('all values in nested array are null', () => { expect( - flatten([null, [[[null]]], null, null, [[null, null], null], null]) + flatten([null, [[[null]]], null, null, [[null, null], null], null]), ).toEqual([]); }); }); diff --git a/exercises/practice/flatten-array/jest.config.js b/exercises/practice/flatten-array/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/flatten-array/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/flatten-array/package.json b/exercises/practice/flatten-array/package.json index cfd068757a..fe36f372db 100644 --- a/exercises/practice/flatten-array/package.json +++ b/exercises/practice/flatten-array/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/flatten-array" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/flower-field/.docs/instructions.md b/exercises/practice/flower-field/.docs/instructions.md new file mode 100644 index 0000000000..bbdae0c2cb --- /dev/null +++ b/exercises/practice/flower-field/.docs/instructions.md @@ -0,0 +1,26 @@ +# Instructions + +Your task is to add flower counts to empty squares in a completed Flower Field garden. +The garden itself is a rectangle board composed of squares that are either empty (`' '`) or a flower (`'*'`). + +For each empty square, count the number of flowers adjacent to it (horizontally, vertically, diagonally). +If the empty square has no adjacent flowers, leave it empty. +Otherwise replace it with the count of adjacent flowers. + +For example, you may receive a 5 x 4 board like this (empty spaces are represented here with the '·' character for display on screen): + +```text +·*·*· +··*·· +··*·· +····· +``` + +Which your code should transform into this: + +```text +1*3*1 +13*31 +·2*2· +·111· +``` diff --git a/exercises/practice/flower-field/.docs/introduction.md b/exercises/practice/flower-field/.docs/introduction.md new file mode 100644 index 0000000000..af9b615361 --- /dev/null +++ b/exercises/practice/flower-field/.docs/introduction.md @@ -0,0 +1,7 @@ +# Introduction + +[Flower Field][history] is a compassionate reimagining of the popular game Minesweeper. +The object of the game is to find all the flowers in the garden using numeric hints that indicate how many flowers are directly adjacent (horizontally, vertically, diagonally) to a square. +"Flower Field" shipped in regional versions of Microsoft Windows in Italy, Germany, South Korea, Japan and Taiwan. + +[history]: https://web.archive.org/web/20020409051321fw_/http://rcm.usr.dsi.unimi.it/rcmweb/fnm/ diff --git a/exercises/practice/flower-field/.gitignore b/exercises/practice/flower-field/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/flower-field/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/flower-field/.meta/config.json b/exercises/practice/flower-field/.meta/config.json new file mode 100644 index 0000000000..0deab7edd9 --- /dev/null +++ b/exercises/practice/flower-field/.meta/config.json @@ -0,0 +1,32 @@ +{ + "authors": [ + "matthewmorgan" + ], + "contributors": [ + "BNAndras", + "brendanmckeown", + "cr0t", + "rchavarria", + "serixscorpio", + "SleeplessByte", + "xarxziux" + ], + "files": { + "solution": [ + "flower-field.js" + ], + "test": [ + "flower-field.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] + }, + "blurb": "Mark all the flowers in a garden.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } +} diff --git a/exercises/practice/flower-field/.meta/proof.ci.js b/exercises/practice/flower-field/.meta/proof.ci.js new file mode 100644 index 0000000000..ca8756b1fc --- /dev/null +++ b/exercises/practice/flower-field/.meta/proof.ci.js @@ -0,0 +1,56 @@ +const FLOWER = '*'; + +const DELTAS = [ + [-1, -1], + [-1, 0], + [-1, 1], + [1, 1], + [1, 0], + [1, -1], + [0, 1], + [0, -1], +]; + +function adjacentSquareIsOnBoard(board, x, d) { + return board[x + d[0]]; +} + +function adjacentSquareHasFlower(board, x, y, d) { + return board[x + d[0]][y + d[1]] === FLOWER; +} + +function countAdjacentFlowers(board, x, y) { + return DELTAS.filter((d) => adjacentSquareIsOnBoard(board, x, d)).filter( + (d) => adjacentSquareHasFlower(board, x, y, d), + ).length; +} + +function cellToFlowerOrCount(cell, inputBoard, x, y) { + if (cell === FLOWER) { + return FLOWER; + } + + return countAdjacentFlowers(inputBoard, x, y) || ' '; +} + +function stringify(board) { + return board.map((row) => row.join('')); +} + +function noDataPresent(rows) { + return rows.length === 0 || rows[0].length === 0; +} + +export function annotate(rows) { + if (noDataPresent(rows)) { + return rows; + } + + const inputBoard = rows.map((row) => [...row]); + + return stringify( + inputBoard.map((row, x) => + [...row].map((cell, y) => cellToFlowerOrCount(cell, inputBoard, x, y)), + ), + ); +} diff --git a/exercises/practice/flower-field/.meta/tests.toml b/exercises/practice/flower-field/.meta/tests.toml new file mode 100644 index 0000000000..c2b24fdaf5 --- /dev/null +++ b/exercises/practice/flower-field/.meta/tests.toml @@ -0,0 +1,46 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[237ff487-467a-47e1-9b01-8a891844f86c] +description = "no rows" + +[4b4134ec-e20f-439c-a295-664c38950ba1] +description = "no columns" + +[d774d054-bbad-4867-88ae-069cbd1c4f92] +description = "no flowers" + +[225176a0-725e-43cd-aa13-9dced501f16e] +description = "garden full of flowers" + +[3f345495-f1a5-4132-8411-74bd7ca08c49] +description = "flower surrounded by spaces" + +[6cb04070-4199-4ef7-a6fa-92f68c660fca] +description = "space surrounded by flowers" + +[272d2306-9f62-44fe-8ab5-6b0f43a26338] +description = "horizontal line" + +[c6f0a4b2-58d0-4bf6-ad8d-ccf4144f1f8e] +description = "horizontal line, flowers at edges" + +[a54e84b7-3b25-44a8-b8cf-1753c8bb4cf5] +description = "vertical line" + +[b40f42f5-dec5-4abc-b167-3f08195189c1] +description = "vertical line, flowers at edges" + +[58674965-7b42-4818-b930-0215062d543c] +description = "cross" + +[dd9d4ca8-9e68-4f78-a677-a2a70fd7a7b8] +description = "large garden" diff --git a/exercises/practice/flower-field/.npmrc b/exercises/practice/flower-field/.npmrc new file mode 100644 index 0000000000..d26df800bb --- /dev/null +++ b/exercises/practice/flower-field/.npmrc @@ -0,0 +1 @@ +audit=false diff --git a/exercises/practice/flower-field/LICENSE b/exercises/practice/flower-field/LICENSE new file mode 100644 index 0000000000..90e73be03b --- /dev/null +++ b/exercises/practice/flower-field/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Exercism + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/exercises/practice/flower-field/babel.config.js b/exercises/practice/flower-field/babel.config.js new file mode 100644 index 0000000000..a638497df1 --- /dev/null +++ b/exercises/practice/flower-field/babel.config.js @@ -0,0 +1,4 @@ +module.exports = { + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], +}; diff --git a/exercises/practice/flower-field/eslint.config.mjs b/exercises/practice/flower-field/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/flower-field/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/flower-field/flower-field.js b/exercises/practice/flower-field/flower-field.js new file mode 100644 index 0000000000..b133690abd --- /dev/null +++ b/exercises/practice/flower-field/flower-field.js @@ -0,0 +1,8 @@ +// +// This is only a SKELETON file for the 'Flower Field' exercise. It's been provided as a +// convenience to get you started writing code faster. +// + +export const annotate = (input) => { + throw new Error('Remove this statement and implement this function'); +}; diff --git a/exercises/practice/flower-field/flower-field.spec.js b/exercises/practice/flower-field/flower-field.spec.js new file mode 100644 index 0000000000..115693b513 --- /dev/null +++ b/exercises/practice/flower-field/flower-field.spec.js @@ -0,0 +1,79 @@ +import { describe, expect, test, xtest } from '@jest/globals'; +import { annotate } from './flower-field'; + +describe('Flower Field', () => { + test('handles no rows', () => { + expect(annotate([])).toEqual([]); + }); + + xtest('handles no columns', () => { + expect(annotate([''])).toEqual(['']); + }); + + xtest('handles no flowers', () => { + const input = [' ', ' ', ' ']; + const expected = [' ', ' ', ' ']; + expect(annotate(input)).toEqual(expected); + }); + + xtest('handles garden full of flowers', () => { + const input = ['***', '***', '***']; + const expected = ['***', '***', '***']; + expect(annotate(input)).toEqual(expected); + }); + + xtest('handles flower surrounded by spaces', () => { + const input = [' ', ' * ', ' ']; + const expected = ['111', '1*1', '111']; + expect(annotate(input)).toEqual(expected); + }); + + xtest('handles space surrounded by flowers', () => { + const input = ['***', '* *', '***']; + const expected = ['***', '*8*', '***']; + expect(annotate(input)).toEqual(expected); + }); + + xtest('handles horizontal line', () => { + const input = [' * * ']; + const expected = ['1*2*1']; + expect(annotate(input)).toEqual(expected); + }); + + xtest('handles horizontal line, flowers at edges', () => { + const input = ['* *']; + const expected = ['*1 1*']; + expect(annotate(input)).toEqual(expected); + }); + + xtest('handles vertical line', () => { + const input = [' ', '*', ' ', '*', ' ']; + const expected = ['1', '*', '2', '*', '1']; + expect(annotate(input)).toEqual(expected); + }); + + xtest('handles vertical line, flowers at edges', () => { + const input = ['*', ' ', ' ', ' ', '*']; + const expected = ['*', '1', ' ', '1', '*']; + expect(annotate(input)).toEqual(expected); + }); + + xtest('handles cross', () => { + const input = [' * ', ' * ', '*****', ' * ', ' * ']; + const expected = [' 2*2 ', '25*52', '*****', '25*52', ' 2*2 ']; + expect(annotate(input)).toEqual(expected); + }); + + xtest('handles large garden', () => { + const input = [' * * ', ' * ', ' * ', ' * *', ' * * ', ' ']; + const expected = [ + '1*22*1', + '12*322', + ' 123*2', + '112*4*', + '1*22*2', + '111111', + ]; + expect(annotate(input)).toEqual(expected); + }); +}); diff --git a/exercises/practice/flower-field/jest.config.js b/exercises/practice/flower-field/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/flower-field/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/flower-field/package.json b/exercises/practice/flower-field/package.json new file mode 100644 index 0000000000..70fa0fe9ba --- /dev/null +++ b/exercises/practice/flower-field/package.json @@ -0,0 +1,34 @@ +{ + "name": "@exercism/javascript-flower-field", + "description": "Exercism exercises in Javascript.", + "author": "Katrina Owen", + "private": true, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/exercism/javascript", + "directory": "exercises/practice/flower-field" + }, + "devDependencies": { + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" + }, + "dependencies": {}, + "scripts": { + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" +} diff --git a/exercises/practice/food-chain/.docs/instructions.md b/exercises/practice/food-chain/.docs/instructions.md index 4d9c10b599..125820e321 100644 --- a/exercises/practice/food-chain/.docs/instructions.md +++ b/exercises/practice/food-chain/.docs/instructions.md @@ -2,11 +2,9 @@ Generate the lyrics of the song 'I Know an Old Lady Who Swallowed a Fly'. -While you could copy/paste the lyrics, -or read them from a file, this problem is much more -interesting if you approach it algorithmically. +While you could copy/paste the lyrics, or read them from a file, this problem is much more interesting if you approach it algorithmically. -This is a [cumulative song](http://en.wikipedia.org/wiki/Cumulative_song) of unknown origin. +This is a [cumulative song][cumulative-song] of unknown origin. This is one of many common variants. @@ -62,3 +60,5 @@ I don't know why she swallowed the fly. Perhaps she'll die. I know an old lady who swallowed a horse. She's dead, of course! ``` + +[cumulative-song]: https://en.wikipedia.org/wiki/Cumulative_song diff --git a/exercises/practice/food-chain/.eslintrc b/exercises/practice/food-chain/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/food-chain/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/food-chain/.gitignore b/exercises/practice/food-chain/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/food-chain/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/food-chain/.meta/config.json b/exercises/practice/food-chain/.meta/config.json index 1a182fcebc..26f0fc63bf 100644 --- a/exercises/practice/food-chain/.meta/config.json +++ b/exercises/practice/food-chain/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Generate the lyrics of the song 'I Know an Old Lady Who Swallowed a Fly'", - "authors": ["rchavarria"], + "authors": [ + "rchavarria" + ], "contributors": [ "ankorGH", "draalger", @@ -12,10 +13,23 @@ "xarxziux" ], "files": { - "solution": ["food-chain.js"], - "test": ["food-chain.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "food-chain.js" + ], + "test": [ + "food-chain.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Generate the lyrics of the song 'I Know an Old Lady Who Swallowed a Fly'.", "source": "Wikipedia", - "source_url": "http://en.wikipedia.org/wiki/There_Was_an_Old_Lady_Who_Swallowed_a_Fly" + "source_url": "https://en.wikipedia.org/wiki/There_Was_an_Old_Lady_Who_Swallowed_a_Fly", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/food-chain/babel.config.js b/exercises/practice/food-chain/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/food-chain/babel.config.js +++ b/exercises/practice/food-chain/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/food-chain/eslint.config.mjs b/exercises/practice/food-chain/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/food-chain/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/food-chain/food-chain.js b/exercises/practice/food-chain/food-chain.js index 3704489537..ab2d6c4a48 100644 --- a/exercises/practice/food-chain/food-chain.js +++ b/exercises/practice/food-chain/food-chain.js @@ -5,10 +5,10 @@ export class Song { verse() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } verses() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/food-chain/food-chain.spec.js b/exercises/practice/food-chain/food-chain.spec.js index 348b494415..b7338e7651 100644 --- a/exercises/practice/food-chain/food-chain.spec.js +++ b/exercises/practice/food-chain/food-chain.spec.js @@ -1,3 +1,4 @@ +import { beforeEach, describe, expect, test, xtest } from '@jest/globals'; import { Song } from './food-chain'; describe('Food Chain', () => { diff --git a/exercises/practice/food-chain/jest.config.js b/exercises/practice/food-chain/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/food-chain/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/food-chain/package.json b/exercises/practice/food-chain/package.json index 647b39f4f4..5025679676 100644 --- a/exercises/practice/food-chain/package.json +++ b/exercises/practice/food-chain/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/food-chain" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/forth/.docs/instructions.md b/exercises/practice/forth/.docs/instructions.md index f481b725a6..91ad26e6e9 100644 --- a/exercises/practice/forth/.docs/instructions.md +++ b/exercises/practice/forth/.docs/instructions.md @@ -2,25 +2,22 @@ Implement an evaluator for a very simple subset of Forth. -[Forth](https://en.wikipedia.org/wiki/Forth_%28programming_language%29) -is a stack-based programming language. Implement a very basic evaluator -for a small subset of Forth. +[Forth][forth] +is a stack-based programming language. +Implement a very basic evaluator for a small subset of Forth. Your evaluator has to support the following words: - `+`, `-`, `*`, `/` (integer arithmetic) - `DUP`, `DROP`, `SWAP`, `OVER` (stack manipulation) -Your evaluator also has to support defining new words using the -customary syntax: `: word-name definition ;`. +Your evaluator also has to support defining new words using the customary syntax: `: word-name definition ;`. -To keep things simple the only data type you need to support is signed -integers of at least 16 bits size. +To keep things simple the only data type you need to support is signed integers of at least 16 bits size. -You should use the following rules for the syntax: a number is a -sequence of one or more (ASCII) digits, a word is a sequence of one or -more letters, digits, symbols or punctuation that is not a number. -(Forth probably uses slightly different rules, but this is close -enough.) +You should use the following rules for the syntax: a number is a sequence of one or more (ASCII) digits, a word is a sequence of one or more letters, digits, symbols or punctuation that is not a number. +(Forth probably uses slightly different rules, but this is close enough.) Words are case-insensitive. + +[forth]: https://en.wikipedia.org/wiki/Forth_%28programming_language%29 diff --git a/exercises/practice/forth/.eslintrc b/exercises/practice/forth/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/forth/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/forth/.gitignore b/exercises/practice/forth/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/forth/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/forth/.meta/config.json b/exercises/practice/forth/.meta/config.json index 74fa8ec8bd..42d8162d0a 100644 --- a/exercises/practice/forth/.meta/config.json +++ b/exercises/practice/forth/.meta/config.json @@ -1,8 +1,10 @@ { - "blurb": "Implement an evaluator for a very simple subset of Forth", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "brendanmckeown", + "jagdish-15", "slaymance", "SleeplessByte", "tejasbubane", @@ -10,8 +12,21 @@ "xarxziux" ], "files": { - "solution": ["forth.js"], - "test": ["forth.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "forth.js" + ], + "test": [ + "forth.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] + }, + "blurb": "Implement an evaluator for a very simple subset of Forth.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false } } diff --git a/exercises/practice/forth/.meta/proof.ci.js b/exercises/practice/forth/.meta/proof.ci.js index 7557f2acbb..b26d7d111f 100644 --- a/exercises/practice/forth/.meta/proof.ci.js +++ b/exercises/practice/forth/.meta/proof.ci.js @@ -24,7 +24,7 @@ export class Forth { this.defineCommand( words[t + 1], - words.slice(t + 2, semicolon).join(' ') + words.slice(t + 2, semicolon).join(' '), ); t = semicolon; @@ -67,6 +67,9 @@ export class Forth { performCommand(command) { if (command.arity > this.stack.length) { + if (this.stack.length === 1) { + throw new Error('Only one value on the stack'); + } throw new Error('Stack empty'); } diff --git a/exercises/practice/forth/.meta/tests.toml b/exercises/practice/forth/.meta/tests.toml index fbcc407b22..d1e146a1eb 100644 --- a/exercises/practice/forth/.meta/tests.toml +++ b/exercises/practice/forth/.meta/tests.toml @@ -1,144 +1,175 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [9962203f-f00a-4a85-b404-8a8ecbcec09d] -description = "numbers just get pushed onto the stack" +description = "parsing and numbers -> numbers just get pushed onto the stack" + +[fd7a8da2-6818-4203-a866-fed0714e7aa0] +description = "parsing and numbers -> pushes negative numbers onto the stack" [9e69588e-a3d8-41a3-a371-ea02206c1e6e] -description = "can add two numbers" +description = "addition -> can add two numbers" [52336dd3-30da-4e5c-8523-bdf9a3427657] -description = "errors if there is nothing on the stack" +description = "addition -> errors if there is nothing on the stack" [06efb9a4-817a-435e-b509-06166993c1b8] -description = "errors if there is only one value on the stack" +description = "addition -> errors if there is only one value on the stack" + +[1e07a098-c5fa-4c66-97b2-3c81205dbc2f] +description = "addition -> more than two values on the stack" [09687c99-7bbc-44af-8526-e402f997ccbf] -description = "can subtract two numbers" +description = "subtraction -> can subtract two numbers" [5d63eee2-1f7d-4538-b475-e27682ab8032] -description = "errors if there is nothing on the stack" +description = "subtraction -> errors if there is nothing on the stack" [b3cee1b2-9159-418a-b00d-a1bb3765c23b] -description = "errors if there is only one value on the stack" +description = "subtraction -> errors if there is only one value on the stack" + +[2c8cc5ed-da97-4cb1-8b98-fa7b526644f4] +description = "subtraction -> more than two values on the stack" [5df0ceb5-922e-401f-974d-8287427dbf21] -description = "can multiply two numbers" +description = "multiplication -> can multiply two numbers" [9e004339-15ac-4063-8ec1-5720f4e75046] -description = "errors if there is nothing on the stack" +description = "multiplication -> errors if there is nothing on the stack" [8ba4b432-9f94-41e0-8fae-3b3712bd51b3] -description = "errors if there is only one value on the stack" +description = "multiplication -> errors if there is only one value on the stack" + +[5cd085b5-deb1-43cc-9c17-6b1c38bc9970] +description = "multiplication -> more than two values on the stack" [e74c2204-b057-4cff-9aa9-31c7c97a93f5] -description = "can divide two numbers" +description = "division -> can divide two numbers" [54f6711c-4b14-4bb0-98ad-d974a22c4620] -description = "performs integer division" +description = "division -> performs integer division" [a5df3219-29b4-4d2f-b427-81f82f42a3f1] -description = "errors if dividing by zero" +description = "division -> errors if dividing by zero" [1d5bb6b3-6749-4e02-8a79-b5d4d334cb8a] -description = "errors if there is nothing on the stack" +description = "division -> errors if there is nothing on the stack" [d5547f43-c2ff-4d5c-9cb0-2a4f6684c20d] -description = "errors if there is only one value on the stack" +description = "division -> errors if there is only one value on the stack" + +[f224f3e0-b6b6-4864-81de-9769ecefa03f] +description = "division -> more than two values on the stack" [ee28d729-6692-4a30-b9be-0d830c52a68c] -description = "addition and subtraction" +description = "combined arithmetic -> addition and subtraction" [40b197da-fa4b-4aca-a50b-f000d19422c1] -description = "multiplication and division" +description = "combined arithmetic -> multiplication and division" + +[f749b540-53aa-458e-87ec-a70797eddbcb] +description = "combined arithmetic -> multiplication and addition" + +[c8e5a4c2-f9bf-4805-9a35-3c3314e4989a] +description = "combined arithmetic -> addition and multiplication" [c5758235-6eef-4bf6-ab62-c878e50b9957] -description = "copies a value on the stack" +description = "dup -> copies a value on the stack" [f6889006-5a40-41e7-beb3-43b09e5a22f4] -description = "copies the top value on the stack" +description = "dup -> copies the top value on the stack" [40b7569c-8401-4bd4-a30d-9adf70d11bc4] -description = "errors if there is nothing on the stack" +description = "dup -> errors if there is nothing on the stack" [1971da68-1df2-4569-927a-72bf5bb7263c] -description = "removes the top value on the stack if it is the only one" +description = "drop -> removes the top value on the stack if it is the only one" [8929d9f2-4a78-4e0f-90ad-be1a0f313fd9] -description = "removes the top value on the stack if it is not the only one" +description = "drop -> removes the top value on the stack if it is not the only one" [6dd31873-6dd7-4cb8-9e90-7daa33ba045c] -description = "errors if there is nothing on the stack" +description = "drop -> errors if there is nothing on the stack" [3ee68e62-f98a-4cce-9e6c-8aae6c65a4e3] -description = "swaps the top two values on the stack if they are the only ones" +description = "swap -> swaps the top two values on the stack if they are the only ones" [8ce869d5-a503-44e4-ab55-1da36816ff1c] -description = "swaps the top two values on the stack if they are not the only ones" +description = "swap -> swaps the top two values on the stack if they are not the only ones" [74ba5b2a-b028-4759-9176-c5c0e7b2b154] -description = "errors if there is nothing on the stack" +description = "swap -> errors if there is nothing on the stack" [dd52e154-5d0d-4a5c-9e5d-73eb36052bc8] -description = "errors if there is only one value on the stack" +description = "swap -> errors if there is only one value on the stack" [a2654074-ba68-4f93-b014-6b12693a8b50] -description = "copies the second element if there are only two" +description = "over -> copies the second element if there are only two" [c5b51097-741a-4da7-8736-5c93fa856339] -description = "copies the second element if there are more than two" +description = "over -> copies the second element if there are more than two" [6e1703a6-5963-4a03-abba-02e77e3181fd] -description = "errors if there is nothing on the stack" +description = "over -> errors if there is nothing on the stack" [ee574dc4-ef71-46f6-8c6a-b4af3a10c45f] -description = "errors if there is only one value on the stack" +description = "over -> errors if there is only one value on the stack" [ed45cbbf-4dbf-4901-825b-54b20dbee53b] -description = "can consist of built-in words" +description = "user-defined words -> can consist of built-in words" [2726ea44-73e4-436b-bc2b-5ff0c6aa014b] -description = "execute in the right order" +description = "user-defined words -> execute in the right order" [9e53c2d0-b8ef-4ad8-b2c9-a559b421eb33] -description = "can override other user-defined words" +description = "user-defined words -> can override other user-defined words" [669db3f3-5bd6-4be0-83d1-618cd6e4984b] -description = "can override built-in words" +description = "user-defined words -> can override built-in words" [588de2f0-c56e-4c68-be0b-0bb1e603c500] -description = "can override built-in operators" +description = "user-defined words -> can override built-in operators" [ac12aaaf-26c6-4a10-8b3c-1c958fa2914c] -description = "can use different words with the same name" +description = "user-defined words -> can use different words with the same name" [53f82ef0-2750-4ccb-ac04-5d8c1aefabb1] -description = "can define word that uses word with the same name" +description = "user-defined words -> can define word that uses word with the same name" [35958cee-a976-4a0f-9378-f678518fa322] -description = "cannot redefine numbers" +description = "user-defined words -> cannot redefine non-negative numbers" [df5b2815-3843-4f55-b16c-c3ed507292a7] -description = "cannot redefine negative numbers" +description = "user-defined words -> cannot redefine negative numbers" [5180f261-89dd-491e-b230-62737e09806f] -description = "errors if executing a non-existent word" +description = "user-defined words -> errors if executing a non-existent word" + +[3c8bfef3-edbb-49c1-9993-21d4030043cb] +description = "user-defined words -> only defines locally" [7b83bb2e-b0e8-461f-ad3b-96ee2e111ed6] -description = "DUP is case-insensitive" +description = "case-insensitivity -> DUP is case-insensitive" [339ed30b-f5b4-47ff-ab1c-67591a9cd336] -description = "DROP is case-insensitive" +description = "case-insensitivity -> DROP is case-insensitive" [ee1af31e-1355-4b1b-bb95-f9d0b2961b87] -description = "SWAP is case-insensitive" +description = "case-insensitivity -> SWAP is case-insensitive" [acdc3a49-14c8-4cc2-945d-11edee6408fa] -description = "OVER is case-insensitive" +description = "case-insensitivity -> OVER is case-insensitive" [5934454f-a24f-4efc-9fdd-5794e5f0c23c] -description = "user-defined words are case-insensitive" +description = "case-insensitivity -> user-defined words are case-insensitive" [037d4299-195f-4be7-a46d-f07ca6280a06] -description = "definitions are case-insensitive" +description = "case-insensitivity -> definitions are case-insensitive" diff --git a/exercises/practice/forth/babel.config.js b/exercises/practice/forth/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/forth/babel.config.js +++ b/exercises/practice/forth/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/forth/eslint.config.mjs b/exercises/practice/forth/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/forth/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/forth/forth.spec.js b/exercises/practice/forth/forth.spec.js index 64c6df913d..df1e8765d7 100644 --- a/exercises/practice/forth/forth.spec.js +++ b/exercises/practice/forth/forth.spec.js @@ -1,3 +1,4 @@ +import { beforeEach, describe, expect, test, xtest } from '@jest/globals'; import { Forth } from './forth'; describe('Forth', () => { @@ -34,7 +35,12 @@ describe('Forth', () => { xtest('errors if there is only one value on the stack', () => { expect(() => { forth.evaluate('1 +'); - }).toThrow(new Error('Stack empty')); + }).toThrow(new Error('Only one value on the stack')); + }); + + xtest('more than two values on the stack', () => { + forth.evaluate('1 2 3 +'); + expect(forth.stack).toEqual([1, 5]); }); }); @@ -53,7 +59,12 @@ describe('Forth', () => { xtest('errors if there is only one value on the stack', () => { expect(() => { forth.evaluate('1 -'); - }).toThrow(new Error('Stack empty')); + }).toThrow(new Error('Only one value on the stack')); + }); + + xtest('more than two values on the stack', () => { + forth.evaluate('1 12 3 -'); + expect(forth.stack).toEqual([1, 9]); }); }); @@ -72,7 +83,12 @@ describe('Forth', () => { xtest('errors if there is only one value on the stack', () => { expect(() => { forth.evaluate('1 *'); - }).toThrow(new Error('Stack empty')); + }).toThrow(new Error('Only one value on the stack')); + }); + + xtest('more than two values on the stack', () => { + forth.evaluate('1 2 3 *'); + expect(forth.stack).toEqual([1, 6]); }); }); @@ -102,7 +118,12 @@ describe('Forth', () => { xtest('errors if there is only one value on the stack', () => { expect(() => { forth.evaluate('1 /'); - }).toThrow(new Error('Stack empty')); + }).toThrow(new Error('Only one value on the stack')); + }); + + xtest('more than two values on the stack', () => { + forth.evaluate('1 12 3 /'); + expect(forth.stack).toEqual([1, 4]); }); }); @@ -116,6 +137,16 @@ describe('Forth', () => { forth.evaluate('2 4 * 3 /'); expect(forth.stack).toEqual([2]); }); + + xtest('multiplication and addition', () => { + forth.evaluate('1 3 4 * +'); + expect(forth.stack).toEqual([13]); + }); + + xtest('addition and multiplication', () => { + forth.evaluate('1 3 4 + *'); + expect(forth.stack).toEqual([7]); + }); }); describe('dup', () => { @@ -174,7 +205,7 @@ describe('Forth', () => { xtest('errors if there is only one value on the stack', () => { expect(() => { forth.evaluate('1 swap'); - }).toThrow(new Error('Stack empty')); + }).toThrow(new Error('Only one value on the stack')); }); }); @@ -198,7 +229,7 @@ describe('Forth', () => { xtest('errors if there is only one value on the stack', () => { expect(() => { forth.evaluate('1 over'); - }).toThrow(new Error('Stack empty')); + }).toThrow(new Error('Only one value on the stack')); }); }); @@ -249,11 +280,12 @@ describe('Forth', () => { expect(forth.stack).toEqual([11]); }); - xtest('cannot redefine numbers', () => { + xtest('cannot redefine non-negative numbers', () => { expect(() => { forth.evaluate(': 1 2 ;'); }).toThrow(new Error('Invalid definition')); }); + xtest('cannot redefine negative numbers', () => { expect(() => { forth.evaluate(': -1 2 ;'); diff --git a/exercises/practice/forth/jest.config.js b/exercises/practice/forth/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/forth/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/forth/package.json b/exercises/practice/forth/package.json index 62ab350f52..e1881cd5b2 100644 --- a/exercises/practice/forth/package.json +++ b/exercises/practice/forth/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/forth" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/game-of-life/.docs/instructions.md b/exercises/practice/game-of-life/.docs/instructions.md new file mode 100644 index 0000000000..4953140648 --- /dev/null +++ b/exercises/practice/game-of-life/.docs/instructions.md @@ -0,0 +1,11 @@ +# Instructions + +After each generation, the cells interact with their eight neighbors, which are cells adjacent horizontally, vertically, or diagonally. + +The following rules are applied to each cell: + +- Any live cell with two or three live neighbors lives on. +- Any dead cell with exactly three live neighbors becomes a live cell. +- All other cells die or stay dead. + +Given a matrix of 1s and 0s (corresponding to live and dead cells), apply the rules to each cell, and return the next generation. diff --git a/exercises/practice/game-of-life/.docs/introduction.md b/exercises/practice/game-of-life/.docs/introduction.md new file mode 100644 index 0000000000..2347b936e4 --- /dev/null +++ b/exercises/practice/game-of-life/.docs/introduction.md @@ -0,0 +1,9 @@ +# Introduction + +[Conway's Game of Life][game-of-life] is a fascinating cellular automaton created by the British mathematician John Horton Conway in 1970. + +The game consists of a two-dimensional grid of cells that can either be "alive" or "dead." + +After each generation, the cells interact with their eight neighbors via a set of rules, which define the new generation. + +[game-of-life]: https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life diff --git a/exercises/practice/game-of-life/.gitignore b/exercises/practice/game-of-life/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/game-of-life/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/game-of-life/.meta/config.json b/exercises/practice/game-of-life/.meta/config.json new file mode 100644 index 0000000000..4387c8dded --- /dev/null +++ b/exercises/practice/game-of-life/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "BNAndras" + ], + "files": { + "solution": [ + "game-of-life.js" + ], + "test": [ + "game-of-life.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] + }, + "blurb": "Implement Conway's Game of Life.", + "source": "Wikipedia", + "source_url": "https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life" +} diff --git a/exercises/practice/game-of-life/.meta/proof.ci.js b/exercises/practice/game-of-life/.meta/proof.ci.js new file mode 100644 index 0000000000..f92312a126 --- /dev/null +++ b/exercises/practice/game-of-life/.meta/proof.ci.js @@ -0,0 +1,52 @@ +export class GameOfLife { + #matrix; + + constructor(matrix) { + this.#matrix = matrix; + } + + tick() { + if (this.#matrix.length === 0) { + return; + } + const rows = this.#matrix.length; + const cols = this.#matrix[0].length; + + const newMatrix = JSON.parse(JSON.stringify(this.#matrix)); + + for (let row = 0; row < rows; row++) { + for (let col = 0; col < cols; col++) { + let liveNeighbors = 0; + for (let newRow = row - 1; newRow <= row + 1; newRow++) { + for (let newCol = col - 1; newCol <= col + 1; newCol++) { + if (newRow === row && newCol === col) { + continue; + } + if (newRow >= 0 && newRow < rows && newCol >= 0 && newCol < cols) { + liveNeighbors += this.#matrix[newRow][newCol]; + } + } + } + + var cell = this.#matrix[row][col]; + if (cell === 1) { + if (liveNeighbors < 2 || liveNeighbors > 3) { + cell = 0; + } + } else { + if (liveNeighbors === 3) { + cell = 1; + } + } + + newMatrix[row][col] = cell; + } + } + + this.#matrix = newMatrix; + } + + state() { + return this.#matrix; + } +} diff --git a/exercises/practice/game-of-life/.meta/tests.toml b/exercises/practice/game-of-life/.meta/tests.toml new file mode 100644 index 0000000000..398cd4546e --- /dev/null +++ b/exercises/practice/game-of-life/.meta/tests.toml @@ -0,0 +1,34 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[ae86ea7d-bd07-4357-90b3-ac7d256bd5c5] +description = "empty matrix" + +[4ea5ccb7-7b73-4281-954a-bed1b0f139a5] +description = "live cells with zero live neighbors die" + +[df245adc-14ff-4f9c-b2ae-f465ef5321b2] +description = "live cells with only one live neighbor die" + +[2a713b56-283c-48c8-adae-1d21306c80ae] +description = "live cells with two live neighbors stay alive" + +[86d5c5a5-ab7b-41a1-8907-c9b3fc5e9dae] +description = "live cells with three live neighbors stay alive" + +[015f60ac-39d8-4c6c-8328-57f334fc9f89] +description = "dead cells with three live neighbors become alive" + +[2ee69c00-9d41-4b8b-89da-5832e735ccf1] +description = "live cells with four or more neighbors die" + +[a79b42be-ed6c-4e27-9206-43da08697ef6] +description = "bigger matrix" diff --git a/exercises/practice/game-of-life/.npmrc b/exercises/practice/game-of-life/.npmrc new file mode 100644 index 0000000000..d26df800bb --- /dev/null +++ b/exercises/practice/game-of-life/.npmrc @@ -0,0 +1 @@ +audit=false diff --git a/exercises/practice/game-of-life/LICENSE b/exercises/practice/game-of-life/LICENSE new file mode 100644 index 0000000000..90e73be03b --- /dev/null +++ b/exercises/practice/game-of-life/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Exercism + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/exercises/practice/game-of-life/babel.config.js b/exercises/practice/game-of-life/babel.config.js new file mode 100644 index 0000000000..a638497df1 --- /dev/null +++ b/exercises/practice/game-of-life/babel.config.js @@ -0,0 +1,4 @@ +module.exports = { + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], +}; diff --git a/exercises/practice/game-of-life/eslint.config.mjs b/exercises/practice/game-of-life/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/game-of-life/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/game-of-life/game-of-life.js b/exercises/practice/game-of-life/game-of-life.js new file mode 100644 index 0000000000..62a98ee0ec --- /dev/null +++ b/exercises/practice/game-of-life/game-of-life.js @@ -0,0 +1,18 @@ +// +// This is only a SKELETON file for the 'Conway's Game of Life' exercise. It's been provided +// as a convenience to get you started writing code faster. +// + +export class GameOfLife { + constructor() { + throw new Error('Remove this line and implement the function'); + } + + tick() { + throw new Error('Remove this line and implement the function'); + } + + state() { + throw new Error('Remove this line and implement the function'); + } +} diff --git a/exercises/practice/game-of-life/game-of-life.spec.js b/exercises/practice/game-of-life/game-of-life.spec.js new file mode 100644 index 0000000000..89d0ed1039 --- /dev/null +++ b/exercises/practice/game-of-life/game-of-life.spec.js @@ -0,0 +1,142 @@ +import { describe, expect, test, xtest } from '@jest/globals'; +import { GameOfLife } from './game-of-life'; + +describe('Game of Life', () => { + // Empty matrix + test('empty matrix', () => { + const matrix = []; + const game = new GameOfLife(matrix); + game.tick(); + const expected = []; + expect(game.state()).toEqual(expected); + }); + + // Live cells with zero live neighbors die + xtest('live cells with zero live neighbors die', () => { + const matrix = [ + [0, 0, 0], + [0, 1, 0], + [0, 0, 0], + ]; + const game = new GameOfLife(matrix); + game.tick(); + const expected = [ + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + ]; + expect(game.state()).toEqual(expected); + }); + + // Live cells with only one live neighbor die + xtest('live cells with only one live neighbor die', () => { + const matrix = [ + [0, 0, 0], + [0, 1, 0], + [0, 1, 0], + ]; + const game = new GameOfLife(matrix); + game.tick(); + const expected = [ + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + ]; + expect(game.state()).toEqual(expected); + }); + + // Live cells with two live neighbors stay alive + xtest('live cells with two live neighbors stay alive', () => { + const matrix = [ + [1, 0, 1], + [1, 0, 1], + [1, 0, 1], + ]; + const game = new GameOfLife(matrix); + game.tick(); + const expected = [ + [0, 0, 0], + [1, 0, 1], + [0, 0, 0], + ]; + expect(game.state()).toEqual(expected); + }); + + // Live cells with three live neighbors stay alive + xtest('live cells with three live neighbors stay alive', () => { + const matrix = [ + [0, 1, 0], + [1, 0, 0], + [1, 1, 0], + ]; + const game = new GameOfLife(matrix); + game.tick(); + const expected = [ + [0, 0, 0], + [1, 0, 0], + [1, 1, 0], + ]; + expect(game.state()).toEqual(expected); + }); + + // Dead cells with three live neighbors become alive + xtest('dead cells with three live neighbors become alive', () => { + const matrix = [ + [1, 1, 0], + [0, 0, 0], + [1, 0, 0], + ]; + const game = new GameOfLife(matrix); + game.tick(); + const expected = [ + [0, 0, 0], + [1, 1, 0], + [0, 0, 0], + ]; + expect(game.state()).toEqual(expected); + }); + + // Live cells with four or more neighbors die + xtest('live cells with four or more neighbors die', () => { + const matrix = [ + [1, 1, 1], + [1, 1, 1], + [1, 1, 1], + ]; + const game = new GameOfLife(matrix); + game.tick(); + const expected = [ + [1, 0, 1], + [0, 0, 0], + [1, 0, 1], + ]; + expect(game.state()).toEqual(expected); + }); + + // Bigger matrix + xtest('bigger matrix', () => { + const matrix = [ + [1, 1, 0, 1, 1, 0, 0, 0], + [1, 0, 1, 1, 0, 0, 0, 0], + [1, 1, 1, 0, 0, 1, 1, 1], + [0, 0, 0, 0, 0, 1, 1, 0], + [1, 0, 0, 0, 1, 1, 0, 0], + [1, 1, 0, 0, 0, 1, 1, 1], + [0, 0, 1, 0, 1, 0, 0, 1], + [1, 0, 0, 0, 0, 0, 1, 1], + ]; + const game = new GameOfLife(matrix); + game.tick(); + const expected = [ + [1, 1, 0, 1, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 1, 0], + [1, 0, 1, 1, 1, 1, 0, 1], + [1, 0, 0, 0, 0, 0, 0, 1], + [1, 1, 0, 0, 1, 0, 0, 1], + [1, 1, 0, 1, 0, 0, 0, 1], + [1, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 1, 1], + ]; + expect(game.state()).toEqual(expected); + }); +}); diff --git a/exercises/practice/game-of-life/jest.config.js b/exercises/practice/game-of-life/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/game-of-life/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/game-of-life/package.json b/exercises/practice/game-of-life/package.json new file mode 100644 index 0000000000..154591f036 --- /dev/null +++ b/exercises/practice/game-of-life/package.json @@ -0,0 +1,34 @@ +{ + "name": "@exercism/javascript-game-of-life", + "description": "Exercism exercises in Javascript.", + "author": "Katrina Owen", + "private": true, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/exercism/javascript", + "directory": "exercises/practice/game-of-life" + }, + "devDependencies": { + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" + }, + "dependencies": {}, + "scripts": { + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" +} diff --git a/exercises/practice/game-of-life/pnpm-lock.yaml b/exercises/practice/game-of-life/pnpm-lock.yaml new file mode 100644 index 0000000000..0cc9b43a05 --- /dev/null +++ b/exercises/practice/game-of-life/pnpm-lock.yaml @@ -0,0 +1,5621 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@exercism/babel-preset-javascript': + specifier: ^0.5.1 + version: 0.5.1 + '@exercism/eslint-config-javascript': + specifier: ^0.8.1 + version: 0.8.1(@babel/core@7.26.10)(@exercism/babel-preset-javascript@0.5.1)(eslint@9.33.0)(jest@29.7.0(@types/node@22.17.2))(typescript@5.8.3) + '@jest/globals': + specifier: ^29.7.0 + version: 29.7.0 + '@types/node': + specifier: ^22.15.29 + version: 22.17.2 + '@types/shelljs': + specifier: ^0.8.17 + version: 0.8.17 + babel-jest: + specifier: ^29.7.0 + version: 29.7.0(@babel/core@7.26.10) + core-js: + specifier: ~3.42.0 + version: 3.42.0 + diff: + specifier: ^8.0.2 + version: 8.0.2 + eslint: + specifier: ^9.28.0 + version: 9.33.0 + expect: + specifier: ^29.7.0 + version: 29.7.0 + globals: + specifier: ^16.2.0 + version: 16.3.0 + jest: + specifier: ^29.7.0 + version: 29.7.0(@types/node@22.17.2) + +packages: + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.26.8': + resolution: {integrity: sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.26.10': + resolution: {integrity: sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==} + engines: {node: '>=6.9.0'} + + '@babel/eslint-parser@7.27.0': + resolution: {integrity: sha512-dtnzmSjXfgL/HDgMcmsLSzyGbEosi4DrGWoCNfuI+W4IkVJw6izpTe7LtOdwAXnkDqw5yweboYCTkM2rQizCng==} + engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0} + peerDependencies: + '@babel/core': ^7.11.0 + eslint: ^7.5.0 || ^8.0.0 || ^9.0.0 + + '@babel/eslint-plugin@7.27.0': + resolution: {integrity: sha512-b8YXz2RX72kf2mOsmvtRdk4GMmpp4bUsvaI0cLJrUsvltMXvELiJPYsy6ikoHqzx40kKdw/3DEBgA8wqCLzJxA==} + engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0} + peerDependencies: + '@babel/eslint-parser': ^7.11.0 + eslint: ^7.5.0 || ^8.0.0 || ^9.0.0 + + '@babel/generator@7.27.0': + resolution: {integrity: sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.25.9': + resolution: {integrity: sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.27.0': + resolution: {integrity: sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.27.0': + resolution: {integrity: sha512-vSGCvMecvFCd/BdpGlhpXYNhhC4ccxyvQWpbGL4CWbvfEoLFWUZuSuf7s9Aw70flgQF+6vptvgK2IfOnKlRmBg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-create-regexp-features-plugin@7.27.0': + resolution: {integrity: sha512-fO8l08T76v48BhpNRW/nQ0MxfnSdoSKUJBMjubOAYffsVuGG5qOfMq7N6Es7UJvi7Y8goXXo07EfcHZXDPuELQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-define-polyfill-provider@0.6.4': + resolution: {integrity: sha512-jljfR1rGnXXNWnmQg2K3+bvhkxB51Rl32QRaOTuwwjviGrHzIbSc8+x9CpraDtbT7mfyjXObULP4w/adunNwAw==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + '@babel/helper-member-expression-to-functions@7.25.9': + resolution: {integrity: sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.25.9': + resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.26.0': + resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.25.9': + resolution: {integrity: sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.26.5': + resolution: {integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-remap-async-to-generator@7.25.9': + resolution: {integrity: sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-replace-supers@7.26.5': + resolution: {integrity: sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.25.9': + resolution: {integrity: sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.25.9': + resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-wrap-function@7.25.9': + resolution: {integrity: sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.27.0': + resolution: {integrity: sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==} + engines: {node: '>=6.9.0'} + + '@babel/node@7.26.0': + resolution: {integrity: sha512-5ASMjh42hbnqyCOK68Q5chh1jKAqn91IswFTN+niwt4FLABhEWCT1tEuuo6mlNQ4WG/oFQLvJ71PaHAKtWtJyA==} + engines: {node: '>=6.9.0'} + hasBin: true + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/parser@7.27.0': + resolution: {integrity: sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9': + resolution: {integrity: sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.9': + resolution: {integrity: sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.9': + resolution: {integrity: sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.9': + resolution: {integrity: sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.13.0 + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.9': + resolution: {integrity: sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2': + resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-async-generators@7.8.4': + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-bigint@7.8.3': + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-properties@7.12.13': + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-static-block@7.14.5': + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-assertions@7.26.0': + resolution: {integrity: sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.26.0': + resolution: {integrity: sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-meta@7.10.4': + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-json-strings@7.8.3': + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.25.9': + resolution: {integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4': + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-numeric-separator@7.10.4': + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-object-rest-spread@7.8.3': + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3': + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-chaining@7.8.3': + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-private-property-in-object@7.14.5': + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-top-level-await@7.14.5': + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.25.9': + resolution: {integrity: sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6': + resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-arrow-functions@7.25.9': + resolution: {integrity: sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-generator-functions@7.26.8': + resolution: {integrity: sha512-He9Ej2X7tNf2zdKMAGOsmg2MrFc+hfoAhd3po4cWfo/NWjzEAKa0oQruj1ROVUdl0e6fb6/kE/G3SSxE0lRJOg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-to-generator@7.25.9': + resolution: {integrity: sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoped-functions@7.26.5': + resolution: {integrity: sha512-chuTSY+hq09+/f5lMj8ZSYgCFpppV2CbYrhNFJ1BFoXpiWPnnAb7R0MqrafCpN8E1+YRrtM1MXZHJdIx8B6rMQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoping@7.27.0': + resolution: {integrity: sha512-u1jGphZ8uDI2Pj/HJj6YQ6XQLZCNjOlprjxB5SVz6rq2T6SwAR+CdrWK0CP7F+9rDVMXdB0+r6Am5G5aobOjAQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-properties@7.25.9': + resolution: {integrity: sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-static-block@7.26.0': + resolution: {integrity: sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.12.0 + + '@babel/plugin-transform-classes@7.25.9': + resolution: {integrity: sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-computed-properties@7.25.9': + resolution: {integrity: sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-destructuring@7.25.9': + resolution: {integrity: sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-dotall-regex@7.25.9': + resolution: {integrity: sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-keys@7.25.9': + resolution: {integrity: sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.9': + resolution: {integrity: sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-dynamic-import@7.25.9': + resolution: {integrity: sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-exponentiation-operator@7.26.3': + resolution: {integrity: sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-export-namespace-from@7.25.9': + resolution: {integrity: sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-for-of@7.26.9': + resolution: {integrity: sha512-Hry8AusVm8LW5BVFgiyUReuoGzPUpdHQQqJY5bZnbbf+ngOHWuCuYFKw/BqaaWlvEUrF91HMhDtEaI1hZzNbLg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-function-name@7.25.9': + resolution: {integrity: sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-json-strings@7.25.9': + resolution: {integrity: sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-literals@7.25.9': + resolution: {integrity: sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-logical-assignment-operators@7.25.9': + resolution: {integrity: sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-member-expression-literals@7.25.9': + resolution: {integrity: sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-amd@7.25.9': + resolution: {integrity: sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-commonjs@7.26.3': + resolution: {integrity: sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-systemjs@7.25.9': + resolution: {integrity: sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-umd@7.25.9': + resolution: {integrity: sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-named-capturing-groups-regex@7.25.9': + resolution: {integrity: sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-new-target@7.25.9': + resolution: {integrity: sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-nullish-coalescing-operator@7.26.6': + resolution: {integrity: sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-numeric-separator@7.25.9': + resolution: {integrity: sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-rest-spread@7.25.9': + resolution: {integrity: sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-super@7.25.9': + resolution: {integrity: sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-catch-binding@7.25.9': + resolution: {integrity: sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-chaining@7.25.9': + resolution: {integrity: sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-parameters@7.25.9': + resolution: {integrity: sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-methods@7.25.9': + resolution: {integrity: sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-property-in-object@7.25.9': + resolution: {integrity: sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-property-literals@7.25.9': + resolution: {integrity: sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regenerator@7.27.0': + resolution: {integrity: sha512-LX/vCajUJQDqE7Aum/ELUMZAY19+cDpghxrnyt5I1tV6X5PyC86AOoWXWFYFeIvauyeSA6/ktn4tQVn/3ZifsA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regexp-modifiers@7.26.0': + resolution: {integrity: sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-reserved-words@7.25.9': + resolution: {integrity: sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-shorthand-properties@7.25.9': + resolution: {integrity: sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-spread@7.25.9': + resolution: {integrity: sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-sticky-regex@7.25.9': + resolution: {integrity: sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-template-literals@7.26.8': + resolution: {integrity: sha512-OmGDL5/J0CJPJZTHZbi2XpO0tyT2Ia7fzpW5GURwdtp2X3fMmN8au/ej6peC/T33/+CRiIpA8Krse8hFGVmT5Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typeof-symbol@7.27.0': + resolution: {integrity: sha512-+LLkxA9rKJpNoGsbLnAgOCdESl73vwYn+V6b+5wHbrE7OGKVDPHIQvbFSzqE6rwqaCw2RE+zdJrlLkcf8YOA0w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-escapes@7.25.9': + resolution: {integrity: sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-property-regex@7.25.9': + resolution: {integrity: sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-regex@7.25.9': + resolution: {integrity: sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-sets-regex@7.25.9': + resolution: {integrity: sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/preset-env@7.26.9': + resolution: {integrity: sha512-vX3qPGE8sEKEAZCWk05k3cpTAE3/nOYca++JA+Rd0z2NCNzabmYvEiSShKzm10zdquOIAVXsy2Ei/DTW34KlKQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-modules@0.1.6-no-external-plugins': + resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} + peerDependencies: + '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 + + '@babel/register@7.25.9': + resolution: {integrity: sha512-8D43jXtGsYmEeDvm4MWHYUpWf8iiXgWYx3fW7E7Wb7Oe6FWqJPl5K6TuFW0dOwNZzEE5rjlaSJYH9JjrUKJszA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.27.0': + resolution: {integrity: sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.27.0': + resolution: {integrity: sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.27.0': + resolution: {integrity: sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.27.0': + resolution: {integrity: sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + + '@eslint-community/eslint-utils@4.5.1': + resolution: {integrity: sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.0': + resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.3.1': + resolution: {integrity: sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.15.2': + resolution: {integrity: sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.24.0': + resolution: {integrity: sha512-uIY/y3z0uvOGX8cp1C2fiC4+ZmBhp6yZWkojtHL1YEMnRt1Y63HB9TM17proGEmeG7HeUY+UP36F0aknKYTpYA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.33.0': + resolution: {integrity: sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.6': + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.3.5': + resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@exercism/babel-preset-javascript@0.5.1': + resolution: {integrity: sha512-6NywGKngMLmuDhDVLov1fm6O8MTtirKfQlDmg3q/3cnP4ElErtqzyOoBoI4Om54hHrTfHXlw+UQxQ7NkKeRAfA==} + + '@exercism/eslint-config-javascript@0.8.1': + resolution: {integrity: sha512-KFk43KvV4lUArh/1RUmFMTGXWGp6Pqqs3eXlDXpHQ7xhBKUatbTIL7xbhUB8o366DDyqkcmlxnhOnDsbnL66Qg==} + peerDependencies: + '@exercism/babel-preset-javascript': '>= 0.5.1' + eslint: '>= 9.17' + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} + engines: {node: '>=18.18'} + + '@humanwhocodes/retry@0.4.2': + resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==} + engines: {node: '>=18.18'} + + '@isaacs/balanced-match@4.0.1': + resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} + engines: {node: 20 || >=22} + + '@isaacs/brace-expansion@5.0.0': + resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} + engines: {node: 20 || >=22} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@istanbuljs/load-nyc-config@1.1.0': + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + engines: {node: '>=8'} + + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + + '@jest/console@29.7.0': + resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/core@29.7.0': + resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/environment@29.7.0': + resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect-utils@29.7.0': + resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect@29.7.0': + resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/fake-timers@29.7.0': + resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/globals@29.7.0': + resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/reporters@29.7.0': + resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/schemas@29.6.3': + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/source-map@29.6.3': + resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/test-result@29.7.0': + resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/test-sequencer@29.7.0': + resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/transform@29.7.0': + resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/types@29.6.3': + resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jridgewell/gen-mapping@0.3.8': + resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': + resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@sinclair/typebox@0.27.8': + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + + '@sinonjs/commons@3.0.1': + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + + '@sinonjs/fake-timers@10.3.0': + resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.20.7': + resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==} + + '@types/estree@1.0.7': + resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + + '@types/graceful-fs@4.1.9': + resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} + + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@22.17.2': + resolution: {integrity: sha512-gL6z5N9Jm9mhY+U2KXZpteb+09zyffliRkZyZOHODGATyC5B1Jt/7TzuuiLkFsSUMLbS1OLmlj/E+/3KF4Q/4w==} + + '@types/shelljs@0.8.17': + resolution: {integrity: sha512-IDksKYmQA2W9MkQjiyptbMmcQx+8+Ol6b7h6dPU5S05JyiQDSb/nZKnrMrZqGwgV6VkVdl6/SPCKPDlMRvqECg==} + + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.33': + resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} + + '@typescript-eslint/scope-manager@8.29.1': + resolution: {integrity: sha512-2nggXGX5F3YrsGN08pw4XpMLO1Rgtnn4AzTegC2MDesv6q3QaTU5yU7IbS1tf1IwCR0Hv/1EFygLn9ms6LIpDA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/types@8.29.1': + resolution: {integrity: sha512-VT7T1PuJF1hpYC3AGm2rCgJBjHL3nc+A/bhOp9sGMKfi5v0WufsX/sHCFBfNTx2F+zA6qBc/PD0/kLRLjdt8mQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.29.1': + resolution: {integrity: sha512-l1enRoSaUkQxOQnbi0KPUtqeZkSiFlqrx9/3ns2rEDhGKfTa+88RmXqedC1zmVTOWrLc2e6DEJrTA51C9iLH5g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/utils@8.29.1': + resolution: {integrity: sha512-QAkFEbytSaB8wnmB+DflhUPz6CLbFWE2SnSCrRMEa+KnXIzDYbpsn++1HGvnfAsUY44doDXmvRkO5shlM/3UfA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/visitor-keys@8.29.1': + resolution: {integrity: sha512-RGLh5CRaUEf02viP5c1Vh1cMGffQscyHe7HPAzGpfmfflFg1wUz2rYxd+OZqwpeypYvZ8UxSxuIpF++fmOzEcg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.0: + resolution: {integrity: sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array.prototype.reduce@1.0.8: + resolution: {integrity: sha512-DwuEqgXFBwbmZSRqt3BpQigWNUoqw9Ml2dTWdF3B2zQlQX4OeUE0zyuzX0fX0IbTvjdkZbcBTU3idgpO78qkTw==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + babel-jest@29.7.0: + resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.8.0 + + babel-plugin-istanbul@6.1.1: + resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} + engines: {node: '>=8'} + + babel-plugin-jest-hoist@29.6.3: + resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + babel-plugin-polyfill-corejs2@0.4.13: + resolution: {integrity: sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-corejs3@0.11.1: + resolution: {integrity: sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-regenerator@0.6.4: + resolution: {integrity: sha512-7gD3pRadPrbjhjLyxebmx/WrFYcuSjZ0XbdUujQMZ/fcE9oeewk2U/7PCvez84UeuK3oSjmPZ0Ch0dlupQvGzw==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-preset-current-node-syntax@1.1.0: + resolution: {integrity: sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==} + peerDependencies: + '@babel/core': ^7.0.0 + + babel-preset-jest@29.6.3: + resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.0.0 + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.24.4: + resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bser@2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + caniuse-lite@1.0.30001712: + resolution: {integrity: sha512-MBqPpGYYdQ7/hfKiet9SCI+nmN5/hp4ZzveOJubl5DTAMa5oggjAuoi0Z4onBpKPFI2ePGnQuQIzF3VxDjDJig==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + + cjs-module-lexer@1.4.3: + resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clone-deep@4.0.1: + resolution: {integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==} + engines: {node: '>=6'} + + co@4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + + collect-v8-coverage@1.0.2: + resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commander@6.2.1: + resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} + engines: {node: '>= 6'} + + commondir@1.0.1: + resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + core-js-compat@3.41.0: + resolution: {integrity: sha512-RFsU9LySVue9RTwdDVX/T0e2Y6jRYWXERKElIjpuEOEnxaXffI0X7RUwVzfYLfzuLXSNJDYoRYUAmRUcyln20A==} + + core-js@3.38.1: + resolution: {integrity: sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==} + + core-js@3.42.0: + resolution: {integrity: sha512-Sz4PP4ZA+Rq4II21qkNqOEDTDrCvcANId3xpIgB34NDkWc3UduWj2dqEtN9yZIq8Dk3HyPI33x9sqqU5C8sr0g==} + + create-jest@29.7.0: + resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + dedent@1.5.3: + resolution: {integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + detect-newline@3.1.0: + resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} + engines: {node: '>=8'} + + diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + diff@8.0.2: + resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==} + engines: {node: '>=0.3.1'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + electron-to-chromium@1.5.134: + resolution: {integrity: sha512-zSwzrLg3jNP3bwsLqWHmS5z2nIOQ5ngMnfMZOWWtXnqqQkPVyOipxK98w+1beLw1TB+EImPNcG8wVP/cLVs2Og==} + + emittery@0.13.1: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} + engines: {node: '>=12'} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + + es-abstract@1.23.9: + resolution: {integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==} + engines: {node: '>= 0.4'} + + es-array-method-boxes-properly@1.0.0: + resolution: {integrity: sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-prettier@9.1.0: + resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-plugin-jest@28.11.0: + resolution: {integrity: sha512-QAfipLcNCWLVocVbZW8GimKn5p5iiMcgGbRzz8z/P5q7xw+cNEpYqyzFMtIF/ZgF2HLOyy+dYBut+DoYolvqig==} + engines: {node: ^16.10.0 || ^18.12.0 || >=20.0.0} + peerDependencies: + '@typescript-eslint/eslint-plugin': ^6.0.0 || ^7.0.0 || ^8.0.0 + eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 + jest: '*' + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + jest: + optional: true + + eslint-rule-composer@0.3.0: + resolution: {integrity: sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==} + engines: {node: '>=4.0.0'} + + eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@2.1.0: + resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} + engines: {node: '>=10'} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.0: + resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.33.0: + resolution: {integrity: sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + exit@0.1.2: + resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} + engines: {node: '>= 0.8.0'} + + expect@29.7.0: + resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + + fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-cache-dir@2.1.0: + resolution: {integrity: sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==} + engines: {node: '>=6'} + + find-up@3.0.0: + resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} + engines: {node: '>=6'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@11.0.3: + resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==} + engines: {node: 20 || >=22} + hasBin: true + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@15.15.0: + resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} + engines: {node: '>=18'} + + globals@16.3.0: + resolution: {integrity: sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==} + engines: {node: '>=18'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + homedir-polyfill@1.0.3: + resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} + engines: {node: '>=0.10.0'} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + import-local@3.2.0: + resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} + engines: {node: '>=8'} + hasBin: true + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-fn@2.1.0: + resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + engines: {node: '>=6'} + + is-generator-function@1.1.0: + resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-plain-object@2.0.4: + resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} + engines: {node: '>=0.10.0'} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + isobject@3.0.1: + resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} + engines: {node: '>=0.10.0'} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@5.2.1: + resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@6.0.3: + resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} + engines: {node: '>=10'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@4.0.1: + resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} + engines: {node: '>=10'} + + istanbul-reports@3.1.7: + resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + engines: {node: '>=8'} + + jackspeak@4.1.1: + resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==} + engines: {node: 20 || >=22} + + jest-changed-files@29.7.0: + resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-circus@29.7.0: + resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-cli@29.7.0: + resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jest-config@29.7.0: + resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + + jest-diff@29.7.0: + resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-docblock@29.7.0: + resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-each@29.7.0: + resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-environment-node@29.7.0: + resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-get-type@29.6.3: + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-haste-map@29.7.0: + resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-leak-detector@29.7.0: + resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-matcher-utils@29.7.0: + resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-message-util@29.7.0: + resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-mock@29.7.0: + resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-pnp-resolver@1.2.3: + resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} + engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + + jest-regex-util@29.6.3: + resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-resolve-dependencies@29.7.0: + resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-resolve@29.7.0: + resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-runner@29.7.0: + resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-runtime@29.7.0: + resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-snapshot@29.7.0: + resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-util@29.7.0: + resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-validate@29.7.0: + resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-watcher@29.7.0: + resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-worker@29.7.0: + resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest@29.7.0: + resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsesc@3.0.2: + resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} + engines: {node: '>=6'} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + locate-path@3.0.0: + resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} + engines: {node: '>=6'} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lru-cache@11.1.0: + resolution: {integrity: sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==} + engines: {node: 20 || >=22} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + make-dir@2.1.0: + resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} + engines: {node: '>=6'} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + makeerror@1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + minimatch@10.0.3: + resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==} + engines: {node: 20 || >=22} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + node-environment-flags@1.0.6: + resolution: {integrity: sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==} + + node-int64@0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + + node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.getownpropertydescriptors@2.1.8: + resolution: {integrity: sha512-qkHIGe4q0lSYMv0XI4SsBTJz3WaURhLvd0lKSgtVuOsJ2krg4SgMw3PIRQFMp07yi++UR3se2mkcLqsBNpBb/A==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@3.0.0: + resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} + engines: {node: '>=6'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parse-passwd@1.0.0: + resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} + engines: {node: '>=0.10.0'} + + path-exists@3.0.0: + resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} + engines: {node: '>=4'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@2.0.0: + resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} + engines: {node: 20 || >=22} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + pify@4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + pkg-dir@3.0.0: + resolution: {integrity: sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==} + engines: {node: '>=6'} + + pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + + regenerate-unicode-properties@10.2.0: + resolution: {integrity: sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==} + engines: {node: '>=4'} + + regenerate@1.4.2: + resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} + + regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + + regenerator-transform@0.15.2: + resolution: {integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==} + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + + regexpu-core@6.2.0: + resolution: {integrity: sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==} + engines: {node: '>=4'} + + regjsgen@0.8.0: + resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==} + + regjsparser@0.12.0: + resolution: {integrity: sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==} + hasBin: true + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + resolve-cwd@3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve.exports@2.0.3: + resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} + engines: {node: '>=10'} + + resolve@1.22.10: + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} + hasBin: true + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + engines: {node: '>=0.4'} + + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.1: + resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==} + engines: {node: '>=10'} + hasBin: true + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + + shallow-clone@3.0.1: + resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==} + engines: {node: '>=8'} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + source-map-support@0.5.13: + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + + string-length@4.0.2: + resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} + engines: {node: '>=10'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-bom@4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + + tmpl@1.0.5: + resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + ts-api-utils@2.1.0: + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: '>= 0.4'} + + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} + hasBin: true + + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + unicode-canonical-property-names-ecmascript@2.0.1: + resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} + engines: {node: '>=4'} + + unicode-match-property-ecmascript@2.0.0: + resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} + engines: {node: '>=4'} + + unicode-match-property-value-ecmascript@2.2.0: + resolution: {integrity: sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==} + engines: {node: '>=4'} + + unicode-property-aliases-ecmascript@2.1.0: + resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==} + engines: {node: '>=4'} + + update-browserslist-db@1.1.3: + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + v8-to-istanbul@9.3.0: + resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} + engines: {node: '>=10.12.0'} + + v8flags@3.2.0: + resolution: {integrity: sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==} + engines: {node: '>= 0.10'} + + walker@1.0.8: + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.19: + resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + write-file-atomic@4.0.2: + resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + + '@babel/code-frame@7.26.2': + dependencies: + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.26.8': {} + + '@babel/core@7.26.10': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.27.0 + '@babel/helper-compilation-targets': 7.27.0 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) + '@babel/helpers': 7.27.0 + '@babel/parser': 7.27.0 + '@babel/template': 7.27.0 + '@babel/traverse': 7.27.0 + '@babel/types': 7.27.0 + convert-source-map: 2.0.0 + debug: 4.4.0 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/eslint-parser@7.27.0(@babel/core@7.26.10)(eslint@9.33.0)': + dependencies: + '@babel/core': 7.26.10 + '@nicolo-ribaudo/eslint-scope-5-internals': 5.1.1-v1 + eslint: 9.33.0 + eslint-visitor-keys: 2.1.0 + semver: 6.3.1 + + '@babel/eslint-plugin@7.27.0(@babel/eslint-parser@7.27.0(@babel/core@7.26.10)(eslint@9.33.0))(eslint@9.33.0)': + dependencies: + '@babel/eslint-parser': 7.27.0(@babel/core@7.26.10)(eslint@9.33.0) + eslint: 9.33.0 + eslint-rule-composer: 0.3.0 + + '@babel/generator@7.27.0': + dependencies: + '@babel/parser': 7.27.0 + '@babel/types': 7.27.0 + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.25.9': + dependencies: + '@babel/types': 7.27.0 + + '@babel/helper-compilation-targets@7.27.0': + dependencies: + '@babel/compat-data': 7.26.8 + '@babel/helper-validator-option': 7.25.9 + browserslist: 4.24.4 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.27.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-member-expression-to-functions': 7.25.9 + '@babel/helper-optimise-call-expression': 7.25.9 + '@babel/helper-replace-supers': 7.26.5(@babel/core@7.26.10) + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + '@babel/traverse': 7.27.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-create-regexp-features-plugin@7.27.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-annotate-as-pure': 7.25.9 + regexpu-core: 6.2.0 + semver: 6.3.1 + + '@babel/helper-define-polyfill-provider@0.6.4(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-compilation-targets': 7.27.0 + '@babel/helper-plugin-utils': 7.26.5 + debug: 4.4.0 + lodash.debounce: 4.0.8 + resolve: 1.22.10 + transitivePeerDependencies: + - supports-color + + '@babel/helper-member-expression-to-functions@7.25.9': + dependencies: + '@babel/traverse': 7.27.0 + '@babel/types': 7.27.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.25.9': + dependencies: + '@babel/traverse': 7.27.0 + '@babel/types': 7.27.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.27.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.25.9': + dependencies: + '@babel/types': 7.27.0 + + '@babel/helper-plugin-utils@7.26.5': {} + + '@babel/helper-remap-async-to-generator@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-wrap-function': 7.25.9 + '@babel/traverse': 7.27.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-replace-supers@7.26.5(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-member-expression-to-functions': 7.25.9 + '@babel/helper-optimise-call-expression': 7.25.9 + '@babel/traverse': 7.27.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.25.9': + dependencies: + '@babel/traverse': 7.27.0 + '@babel/types': 7.27.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.25.9': {} + + '@babel/helper-validator-identifier@7.25.9': {} + + '@babel/helper-validator-option@7.25.9': {} + + '@babel/helper-wrap-function@7.25.9': + dependencies: + '@babel/template': 7.27.0 + '@babel/traverse': 7.27.0 + '@babel/types': 7.27.0 + transitivePeerDependencies: + - supports-color + + '@babel/helpers@7.27.0': + dependencies: + '@babel/template': 7.27.0 + '@babel/types': 7.27.0 + + '@babel/node@7.26.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/register': 7.25.9(@babel/core@7.26.10) + commander: 6.2.1 + core-js: 3.42.0 + node-environment-flags: 1.0.6 + regenerator-runtime: 0.14.1 + v8flags: 3.2.0 + + '@babel/parser@7.27.0': + dependencies: + '@babel/types': 7.27.0 + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/traverse': 7.27.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.26.10) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/traverse': 7.27.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-import-assertions@7.26.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.27.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-arrow-functions@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-async-generator-functions@7.26.8(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.10) + '@babel/traverse': 7.27.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-async-to-generator@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.10) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-block-scoped-functions@7.26.5(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-block-scoping@7.27.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-class-properties@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-class-features-plugin': 7.27.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-class-static-block@7.26.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-class-features-plugin': 7.27.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-classes@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-compilation-targets': 7.27.0 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-replace-supers': 7.26.5(@babel/core@7.26.10) + '@babel/traverse': 7.27.0 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-computed-properties@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/template': 7.27.0 + + '@babel/plugin-transform-destructuring@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-dotall-regex@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.27.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-duplicate-keys@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.27.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-dynamic-import@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-exponentiation-operator@7.26.3(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-export-namespace-from@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-for-of@7.26.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-function-name@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-compilation-targets': 7.27.0 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/traverse': 7.27.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-json-strings@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-literals@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-logical-assignment-operators@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-member-expression-literals@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-modules-amd@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-commonjs@7.26.3(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-systemjs@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.27.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-umd@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-named-capturing-groups-regex@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.27.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-new-target@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-nullish-coalescing-operator@7.26.6(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-numeric-separator@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-object-rest-spread@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-compilation-targets': 7.27.0 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.26.10) + + '@babel/plugin-transform-object-super@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-replace-supers': 7.26.5(@babel/core@7.26.10) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-optional-catch-binding@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-optional-chaining@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-parameters@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-private-methods@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-class-features-plugin': 7.27.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-private-property-in-object@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-create-class-features-plugin': 7.27.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-property-literals@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-regenerator@7.27.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + regenerator-transform: 0.15.2 + + '@babel/plugin-transform-regexp-modifiers@7.26.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.27.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-reserved-words@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-shorthand-properties@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-spread@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-sticky-regex@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-template-literals@7.26.8(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-typeof-symbol@7.27.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-unicode-escapes@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-unicode-property-regex@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.27.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-unicode-regex@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.27.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-unicode-sets-regex@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.27.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/preset-env@7.26.9(@babel/core@7.26.10)': + dependencies: + '@babel/compat-data': 7.26.8 + '@babel/core': 7.26.10 + '@babel/helper-compilation-targets': 7.27.0 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-validator-option': 7.25.9 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.26.10) + '@babel/plugin-syntax-import-assertions': 7.26.0(@babel/core@7.26.10) + '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.10) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.26.10) + '@babel/plugin-transform-arrow-functions': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-async-generator-functions': 7.26.8(@babel/core@7.26.10) + '@babel/plugin-transform-async-to-generator': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-block-scoped-functions': 7.26.5(@babel/core@7.26.10) + '@babel/plugin-transform-block-scoping': 7.27.0(@babel/core@7.26.10) + '@babel/plugin-transform-class-properties': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-class-static-block': 7.26.0(@babel/core@7.26.10) + '@babel/plugin-transform-classes': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-computed-properties': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-destructuring': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-dotall-regex': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-duplicate-keys': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-dynamic-import': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-exponentiation-operator': 7.26.3(@babel/core@7.26.10) + '@babel/plugin-transform-export-namespace-from': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-for-of': 7.26.9(@babel/core@7.26.10) + '@babel/plugin-transform-function-name': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-json-strings': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-literals': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-logical-assignment-operators': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-member-expression-literals': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-modules-amd': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-modules-commonjs': 7.26.3(@babel/core@7.26.10) + '@babel/plugin-transform-modules-systemjs': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-modules-umd': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-named-capturing-groups-regex': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-new-target': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-nullish-coalescing-operator': 7.26.6(@babel/core@7.26.10) + '@babel/plugin-transform-numeric-separator': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-object-rest-spread': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-object-super': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-optional-catch-binding': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-private-property-in-object': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-property-literals': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-regenerator': 7.27.0(@babel/core@7.26.10) + '@babel/plugin-transform-regexp-modifiers': 7.26.0(@babel/core@7.26.10) + '@babel/plugin-transform-reserved-words': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-shorthand-properties': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-spread': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-sticky-regex': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-template-literals': 7.26.8(@babel/core@7.26.10) + '@babel/plugin-transform-typeof-symbol': 7.27.0(@babel/core@7.26.10) + '@babel/plugin-transform-unicode-escapes': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-unicode-property-regex': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-unicode-regex': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-unicode-sets-regex': 7.25.9(@babel/core@7.26.10) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.26.10) + babel-plugin-polyfill-corejs2: 0.4.13(@babel/core@7.26.10) + babel-plugin-polyfill-corejs3: 0.11.1(@babel/core@7.26.10) + babel-plugin-polyfill-regenerator: 0.6.4(@babel/core@7.26.10) + core-js-compat: 3.41.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/types': 7.27.0 + esutils: 2.0.3 + + '@babel/register@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + clone-deep: 4.0.1 + find-cache-dir: 2.1.0 + make-dir: 2.1.0 + pirates: 4.0.7 + source-map-support: 0.5.21 + + '@babel/runtime@7.27.0': + dependencies: + regenerator-runtime: 0.14.1 + + '@babel/template@7.27.0': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/parser': 7.27.0 + '@babel/types': 7.27.0 + + '@babel/traverse@7.27.0': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.27.0 + '@babel/parser': 7.27.0 + '@babel/template': 7.27.0 + '@babel/types': 7.27.0 + debug: 4.4.0 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.27.0': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + + '@bcoe/v8-coverage@0.2.3': {} + + '@eslint-community/eslint-utils@4.5.1(eslint@9.33.0)': + dependencies: + eslint: 9.33.0 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/config-array@0.21.0': + dependencies: + '@eslint/object-schema': 2.1.6 + debug: 4.4.0 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.3.1': {} + + '@eslint/core@0.15.2': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.1': + dependencies: + ajv: 6.12.6 + debug: 4.4.0 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.24.0': {} + + '@eslint/js@9.33.0': {} + + '@eslint/object-schema@2.1.6': {} + + '@eslint/plugin-kit@0.3.5': + dependencies: + '@eslint/core': 0.15.2 + levn: 0.4.1 + + '@exercism/babel-preset-javascript@0.5.1': + dependencies: + '@babel/core': 7.26.10 + '@babel/node': 7.26.0(@babel/core@7.26.10) + '@babel/preset-env': 7.26.9(@babel/core@7.26.10) + core-js: 3.38.1 + transitivePeerDependencies: + - supports-color + + '@exercism/eslint-config-javascript@0.8.1(@babel/core@7.26.10)(@exercism/babel-preset-javascript@0.5.1)(eslint@9.33.0)(jest@29.7.0(@types/node@22.17.2))(typescript@5.8.3)': + dependencies: + '@babel/eslint-parser': 7.27.0(@babel/core@7.26.10)(eslint@9.33.0) + '@babel/eslint-plugin': 7.27.0(@babel/eslint-parser@7.27.0(@babel/core@7.26.10)(eslint@9.33.0))(eslint@9.33.0) + '@eslint/js': 9.24.0 + '@exercism/babel-preset-javascript': 0.5.1 + eslint: 9.33.0 + eslint-config-prettier: 9.1.0(eslint@9.33.0) + eslint-plugin-jest: 28.11.0(eslint@9.33.0)(jest@29.7.0(@types/node@22.17.2))(typescript@5.8.3) + globals: 15.15.0 + transitivePeerDependencies: + - '@babel/core' + - '@typescript-eslint/eslint-plugin' + - jest + - supports-color + - typescript + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.6': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.3.1': {} + + '@humanwhocodes/retry@0.4.2': {} + + '@isaacs/balanced-match@4.0.1': {} + + '@isaacs/brace-expansion@5.0.0': + dependencies: + '@isaacs/balanced-match': 4.0.1 + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@istanbuljs/load-nyc-config@1.1.0': + dependencies: + camelcase: 5.3.1 + find-up: 4.1.0 + get-package-type: 0.1.0 + js-yaml: 3.14.1 + resolve-from: 5.0.0 + + '@istanbuljs/schema@0.1.3': {} + + '@jest/console@29.7.0': + dependencies: + '@jest/types': 29.6.3 + '@types/node': 22.17.2 + chalk: 4.1.2 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + + '@jest/core@29.7.0': + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.17.2 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@22.17.2) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + + '@jest/environment@29.7.0': + dependencies: + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.17.2 + jest-mock: 29.7.0 + + '@jest/expect-utils@29.7.0': + dependencies: + jest-get-type: 29.6.3 + + '@jest/expect@29.7.0': + dependencies: + expect: 29.7.0 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + + '@jest/fake-timers@29.7.0': + dependencies: + '@jest/types': 29.6.3 + '@sinonjs/fake-timers': 10.3.0 + '@types/node': 22.17.2 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + '@jest/globals@29.7.0': + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/types': 29.6.3 + jest-mock: 29.7.0 + transitivePeerDependencies: + - supports-color + + '@jest/reporters@29.7.0': + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.25 + '@types/node': 22.17.2 + chalk: 4.1.2 + collect-v8-coverage: 1.0.2 + exit: 0.1.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-instrument: 6.0.3 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 4.0.1 + istanbul-reports: 3.1.7 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + jest-worker: 29.7.0 + slash: 3.0.0 + string-length: 4.0.2 + strip-ansi: 6.0.1 + v8-to-istanbul: 9.3.0 + transitivePeerDependencies: + - supports-color + + '@jest/schemas@29.6.3': + dependencies: + '@sinclair/typebox': 0.27.8 + + '@jest/source-map@29.6.3': + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + callsites: 3.1.0 + graceful-fs: 4.2.11 + + '@jest/test-result@29.7.0': + dependencies: + '@jest/console': 29.7.0 + '@jest/types': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + collect-v8-coverage: 1.0.2 + + '@jest/test-sequencer@29.7.0': + dependencies: + '@jest/test-result': 29.7.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + slash: 3.0.0 + + '@jest/transform@29.7.0': + dependencies: + '@babel/core': 7.26.10 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.25 + babel-plugin-istanbul: 6.1.1 + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + micromatch: 4.0.8 + pirates: 4.0.7 + slash: 3.0.0 + write-file-atomic: 4.0.2 + transitivePeerDependencies: + - supports-color + + '@jest/types@29.6.3': + dependencies: + '@jest/schemas': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 22.17.2 + '@types/yargs': 17.0.33 + chalk: 4.1.2 + + '@jridgewell/gen-mapping@0.3.8': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': + dependencies: + eslint-scope: 5.1.1 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + + '@sinclair/typebox@0.27.8': {} + + '@sinonjs/commons@3.0.1': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/fake-timers@10.3.0': + dependencies: + '@sinonjs/commons': 3.0.1 + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.27.0 + '@babel/types': 7.27.0 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.20.7 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.27.0 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.27.0 + '@babel/types': 7.27.0 + + '@types/babel__traverse@7.20.7': + dependencies: + '@babel/types': 7.27.0 + + '@types/estree@1.0.7': {} + + '@types/graceful-fs@4.1.9': + dependencies: + '@types/node': 22.17.2 + + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/istanbul-lib-report@3.0.3': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + + '@types/istanbul-reports@3.0.4': + dependencies: + '@types/istanbul-lib-report': 3.0.3 + + '@types/json-schema@7.0.15': {} + + '@types/node@22.17.2': + dependencies: + undici-types: 6.21.0 + + '@types/shelljs@0.8.17': + dependencies: + '@types/node': 22.17.2 + glob: 11.0.3 + + '@types/stack-utils@2.0.3': {} + + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.33': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@typescript-eslint/scope-manager@8.29.1': + dependencies: + '@typescript-eslint/types': 8.29.1 + '@typescript-eslint/visitor-keys': 8.29.1 + + '@typescript-eslint/types@8.29.1': {} + + '@typescript-eslint/typescript-estree@8.29.1(typescript@5.8.3)': + dependencies: + '@typescript-eslint/types': 8.29.1 + '@typescript-eslint/visitor-keys': 8.29.1 + debug: 4.4.0 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.1 + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.29.1(eslint@9.33.0)(typescript@5.8.3)': + dependencies: + '@eslint-community/eslint-utils': 4.5.1(eslint@9.33.0) + '@typescript-eslint/scope-manager': 8.29.1 + '@typescript-eslint/types': 8.29.1 + '@typescript-eslint/typescript-estree': 8.29.1(typescript@5.8.3) + eslint: 9.33.0 + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.29.1': + dependencies: + '@typescript-eslint/types': 8.29.1 + eslint-visitor-keys: 4.2.0 + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.0: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + ansi-styles@6.2.1: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array.prototype.reduce@1.0.8: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-array-method-boxes-properly: 1.0.0 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + is-string: 1.1.1 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + + async-function@1.0.0: {} + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + babel-jest@29.7.0(@babel/core@7.26.10): + dependencies: + '@babel/core': 7.26.10 + '@jest/transform': 29.7.0 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.6.3(@babel/core@7.26.10) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-istanbul@6.1.1: + dependencies: + '@babel/helper-plugin-utils': 7.26.5 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-instrument: 5.2.1 + test-exclude: 6.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-jest-hoist@29.6.3: + dependencies: + '@babel/template': 7.27.0 + '@babel/types': 7.27.0 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.20.7 + + babel-plugin-polyfill-corejs2@0.4.13(@babel/core@7.26.10): + dependencies: + '@babel/compat-data': 7.26.8 + '@babel/core': 7.26.10 + '@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.26.10) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-corejs3@0.11.1(@babel/core@7.26.10): + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.26.10) + core-js-compat: 3.41.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-regenerator@0.6.4(@babel/core@7.26.10): + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.26.10) + transitivePeerDependencies: + - supports-color + + babel-preset-current-node-syntax@1.1.0(@babel/core@7.26.10): + dependencies: + '@babel/core': 7.26.10 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.26.10) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.26.10) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.26.10) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.26.10) + '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.10) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.26.10) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.26.10) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.26.10) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.26.10) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.26.10) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.26.10) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.26.10) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.26.10) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.26.10) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.26.10) + + babel-preset-jest@29.6.3(@babel/core@7.26.10): + dependencies: + '@babel/core': 7.26.10 + babel-plugin-jest-hoist: 29.6.3 + babel-preset-current-node-syntax: 1.1.0(@babel/core@7.26.10) + + balanced-match@1.0.2: {} + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.24.4: + dependencies: + caniuse-lite: 1.0.30001712 + electron-to-chromium: 1.5.134 + node-releases: 2.0.19 + update-browserslist-db: 1.1.3(browserslist@4.24.4) + + bser@2.1.1: + dependencies: + node-int64: 0.4.0 + + buffer-from@1.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + camelcase@5.3.1: {} + + camelcase@6.3.0: {} + + caniuse-lite@1.0.30001712: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + char-regex@1.0.2: {} + + ci-info@3.9.0: {} + + cjs-module-lexer@1.4.3: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clone-deep@4.0.1: + dependencies: + is-plain-object: 2.0.4 + kind-of: 6.0.3 + shallow-clone: 3.0.1 + + co@4.6.0: {} + + collect-v8-coverage@1.0.2: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + commander@6.2.1: {} + + commondir@1.0.1: {} + + concat-map@0.0.1: {} + + convert-source-map@2.0.0: {} + + core-js-compat@3.41.0: + dependencies: + browserslist: 4.24.4 + + core-js@3.38.1: {} + + core-js@3.42.0: {} + + create-jest@29.7.0(@types/node@22.17.2): + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@22.17.2) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + debug@4.4.0: + dependencies: + ms: 2.1.3 + + dedent@1.5.3: {} + + deep-is@0.1.4: {} + + deepmerge@4.3.1: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + detect-newline@3.1.0: {} + + diff-sequences@29.6.3: {} + + diff@8.0.2: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + eastasianwidth@0.2.0: {} + + electron-to-chromium@1.5.134: {} + + emittery@0.13.1: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + error-ex@1.3.2: + dependencies: + is-arrayish: 0.2.1 + + es-abstract@1.23.9: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-regex: 1.2.1 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.19 + + es-array-method-boxes-properly@1.0.0: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + + escalade@3.2.0: {} + + escape-string-regexp@2.0.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-config-prettier@9.1.0(eslint@9.33.0): + dependencies: + eslint: 9.33.0 + + eslint-plugin-jest@28.11.0(eslint@9.33.0)(jest@29.7.0(@types/node@22.17.2))(typescript@5.8.3): + dependencies: + '@typescript-eslint/utils': 8.29.1(eslint@9.33.0)(typescript@5.8.3) + eslint: 9.33.0 + optionalDependencies: + jest: 29.7.0(@types/node@22.17.2) + transitivePeerDependencies: + - supports-color + - typescript + + eslint-rule-composer@0.3.0: {} + + eslint-scope@5.1.1: + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@2.1.0: {} + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.0: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@9.33.0: + dependencies: + '@eslint-community/eslint-utils': 4.5.1(eslint@9.33.0) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.21.0 + '@eslint/config-helpers': 0.3.1 + '@eslint/core': 0.15.2 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.33.0 + '@eslint/plugin-kit': 0.3.5 + '@humanfs/node': 0.16.6 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.2 + '@types/estree': 1.0.7 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.0 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + esprima@4.0.1: {} + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@4.3.0: {} + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + exit@0.1.2: {} + + expect@29.7.0: + dependencies: + '@jest/expect-utils': 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + fb-watchman@2.0.2: + dependencies: + bser: 2.1.1 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-cache-dir@2.1.0: + dependencies: + commondir: 1.0.1 + make-dir: 2.1.0 + pkg-dir: 3.0.0 + + find-up@3.0.0: + dependencies: + locate-path: 3.0.0 + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.2 + is-callable: 1.2.7 + + functions-have-names@1.2.3: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-package-type@0.1.0: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@6.0.1: {} + + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@11.0.3: + dependencies: + foreground-child: 3.3.1 + jackspeak: 4.1.1 + minimatch: 10.0.3 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 2.0.0 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + globals@11.12.0: {} + + globals@14.0.0: {} + + globals@15.15.0: {} + + globals@16.3.0: {} + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + has-bigints@1.1.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + homedir-polyfill@1.0.3: + dependencies: + parse-passwd: 1.0.0 + + html-escaper@2.0.2: {} + + human-signals@2.1.0: {} + + ignore@5.3.2: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-local@3.2.0: + dependencies: + pkg-dir: 4.2.0 + resolve-cwd: 3.0.0 + + imurmurhash@0.1.4: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-arrayish@0.2.1: {} + + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-callable@1.2.7: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-fullwidth-code-point@3.0.0: {} + + is-generator-fn@2.1.0: {} + + is-generator-function@1.1.0: + dependencies: + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-map@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-number@7.0.0: {} + + is-plain-object@2.0.4: + dependencies: + isobject: 3.0.1 + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-stream@2.0.1: {} + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.19 + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + isobject@3.0.1: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-instrument@5.2.1: + dependencies: + '@babel/core': 7.26.10 + '@babel/parser': 7.27.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + istanbul-lib-instrument@6.0.3: + dependencies: + '@babel/core': 7.26.10 + '@babel/parser': 7.27.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 7.7.1 + transitivePeerDependencies: + - supports-color + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@4.0.1: + dependencies: + debug: 4.4.0 + istanbul-lib-coverage: 3.2.2 + source-map: 0.6.1 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.1.7: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + jackspeak@4.1.1: + dependencies: + '@isaacs/cliui': 8.0.2 + + jest-changed-files@29.7.0: + dependencies: + execa: 5.1.1 + jest-util: 29.7.0 + p-limit: 3.1.0 + + jest-circus@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.17.2 + chalk: 4.1.2 + co: 4.6.0 + dedent: 1.5.3 + is-generator-fn: 2.1.0 + jest-each: 29.7.0 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + p-limit: 3.1.0 + pretty-format: 29.7.0 + pure-rand: 6.1.0 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-cli@29.7.0(@types/node@22.17.2): + dependencies: + '@jest/core': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0(@types/node@22.17.2) + exit: 0.1.2 + import-local: 3.2.0 + jest-config: 29.7.0(@types/node@22.17.2) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + jest-config@29.7.0(@types/node@22.17.2): + dependencies: + '@babel/core': 7.26.10 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.26.10) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 22.17.2 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-diff@29.7.0: + dependencies: + chalk: 4.1.2 + diff-sequences: 29.6.3 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-docblock@29.7.0: + dependencies: + detect-newline: 3.1.0 + + jest-each@29.7.0: + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + jest-get-type: 29.6.3 + jest-util: 29.7.0 + pretty-format: 29.7.0 + + jest-environment-node@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.17.2 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + jest-get-type@29.6.3: {} + + jest-haste-map@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/graceful-fs': 4.1.9 + '@types/node': 22.17.2 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + jest-worker: 29.7.0 + micromatch: 4.0.8 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 + + jest-leak-detector@29.7.0: + dependencies: + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-matcher-utils@29.7.0: + dependencies: + chalk: 4.1.2 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-message-util@29.7.0: + dependencies: + '@babel/code-frame': 7.26.2 + '@jest/types': 29.6.3 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + stack-utils: 2.0.6 + + jest-mock@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 22.17.2 + jest-util: 29.7.0 + + jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): + optionalDependencies: + jest-resolve: 29.7.0 + + jest-regex-util@29.6.3: {} + + jest-resolve-dependencies@29.7.0: + dependencies: + jest-regex-util: 29.6.3 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + + jest-resolve@29.7.0: + dependencies: + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0) + jest-util: 29.7.0 + jest-validate: 29.7.0 + resolve: 1.22.10 + resolve.exports: 2.0.3 + slash: 3.0.0 + + jest-runner@29.7.0: + dependencies: + '@jest/console': 29.7.0 + '@jest/environment': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.17.2 + chalk: 4.1.2 + emittery: 0.13.1 + graceful-fs: 4.2.11 + jest-docblock: 29.7.0 + jest-environment-node: 29.7.0 + jest-haste-map: 29.7.0 + jest-leak-detector: 29.7.0 + jest-message-util: 29.7.0 + jest-resolve: 29.7.0 + jest-runtime: 29.7.0 + jest-util: 29.7.0 + jest-watcher: 29.7.0 + jest-worker: 29.7.0 + p-limit: 3.1.0 + source-map-support: 0.5.13 + transitivePeerDependencies: + - supports-color + + jest-runtime@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/globals': 29.7.0 + '@jest/source-map': 29.6.3 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.17.2 + chalk: 4.1.2 + cjs-module-lexer: 1.4.3 + collect-v8-coverage: 1.0.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color + + jest-snapshot@29.7.0: + dependencies: + '@babel/core': 7.26.10 + '@babel/generator': 7.27.0 + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.10) + '@babel/types': 7.27.0 + '@jest/expect-utils': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-preset-current-node-syntax: 1.1.0(@babel/core@7.26.10) + chalk: 4.1.2 + expect: 29.7.0 + graceful-fs: 4.2.11 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + natural-compare: 1.4.0 + pretty-format: 29.7.0 + semver: 7.7.1 + transitivePeerDependencies: + - supports-color + + jest-util@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 22.17.2 + chalk: 4.1.2 + ci-info: 3.9.0 + graceful-fs: 4.2.11 + picomatch: 2.3.1 + + jest-validate@29.7.0: + dependencies: + '@jest/types': 29.6.3 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 29.6.3 + leven: 3.1.0 + pretty-format: 29.7.0 + + jest-watcher@29.7.0: + dependencies: + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.17.2 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.13.1 + jest-util: 29.7.0 + string-length: 4.0.2 + + jest-worker@29.7.0: + dependencies: + '@types/node': 22.17.2 + jest-util: 29.7.0 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + jest@29.7.0(@types/node@22.17.2): + dependencies: + '@jest/core': 29.7.0 + '@jest/types': 29.6.3 + import-local: 3.2.0 + jest-cli: 29.7.0(@types/node@22.17.2) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + js-tokens@4.0.0: {} + + js-yaml@3.14.1: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsesc@3.0.2: {} + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kind-of@6.0.3: {} + + kleur@3.0.3: {} + + leven@3.1.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lines-and-columns@1.2.4: {} + + locate-path@3.0.0: + dependencies: + p-locate: 3.0.0 + path-exists: 3.0.0 + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.debounce@4.0.8: {} + + lodash.merge@4.6.2: {} + + lru-cache@11.1.0: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + make-dir@2.1.0: + dependencies: + pify: 4.0.1 + semver: 5.7.2 + + make-dir@4.0.0: + dependencies: + semver: 7.7.1 + + makeerror@1.0.12: + dependencies: + tmpl: 1.0.5 + + math-intrinsics@1.1.0: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mimic-fn@2.1.0: {} + + minimatch@10.0.3: + dependencies: + '@isaacs/brace-expansion': 5.0.0 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + + minipass@7.1.2: {} + + ms@2.1.3: {} + + natural-compare@1.4.0: {} + + node-environment-flags@1.0.6: + dependencies: + object.getownpropertydescriptors: 2.1.8 + semver: 5.7.2 + + node-int64@0.4.0: {} + + node-releases@2.0.19: {} + + normalize-path@3.0.0: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.getownpropertydescriptors@2.1.8: + dependencies: + array.prototype.reduce: 1.0.8 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-object-atoms: 1.1.1 + gopd: 1.2.0 + safe-array-concat: 1.1.3 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@3.0.0: + dependencies: + p-limit: 2.3.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-try@2.2.0: {} + + package-json-from-dist@1.0.1: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.26.2 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse-passwd@1.0.0: {} + + path-exists@3.0.0: {} + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@2.0.0: + dependencies: + lru-cache: 11.1.0 + minipass: 7.1.2 + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + pify@4.0.1: {} + + pirates@4.0.7: {} + + pkg-dir@3.0.0: + dependencies: + find-up: 3.0.0 + + pkg-dir@4.2.0: + dependencies: + find-up: 4.1.0 + + possible-typed-array-names@1.1.0: {} + + prelude-ls@1.2.1: {} + + pretty-format@29.7.0: + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.3.1 + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + punycode@2.3.1: {} + + pure-rand@6.1.0: {} + + queue-microtask@1.2.3: {} + + react-is@18.3.1: {} + + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regenerate-unicode-properties@10.2.0: + dependencies: + regenerate: 1.4.2 + + regenerate@1.4.2: {} + + regenerator-runtime@0.14.1: {} + + regenerator-transform@0.15.2: + dependencies: + '@babel/runtime': 7.27.0 + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + + regexpu-core@6.2.0: + dependencies: + regenerate: 1.4.2 + regenerate-unicode-properties: 10.2.0 + regjsgen: 0.8.0 + regjsparser: 0.12.0 + unicode-match-property-ecmascript: 2.0.0 + unicode-match-property-value-ecmascript: 2.2.0 + + regjsgen@0.8.0: {} + + regjsparser@0.12.0: + dependencies: + jsesc: 3.0.2 + + require-directory@2.1.1: {} + + resolve-cwd@3.0.0: + dependencies: + resolve-from: 5.0.0 + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve.exports@2.0.3: {} + + resolve@1.22.10: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.1.0: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-array-concat@1.1.3: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + semver@5.7.2: {} + + semver@6.3.1: {} + + semver@7.7.1: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + + shallow-clone@3.0.1: + dependencies: + kind-of: 6.0.3 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + sisteransi@1.0.5: {} + + slash@3.0.0: {} + + source-map-support@0.5.13: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + sprintf-js@1.0.3: {} + + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + + string-length@4.0.2: + dependencies: + char-regex: 1.0.2 + strip-ansi: 6.0.1 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-object-atoms: 1.1.1 + has-property-descriptors: 1.0.2 + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.2.0 + + strip-bom@4.0.0: {} + + strip-final-newline@2.0.0: {} + + strip-json-comments@3.1.1: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + test-exclude@6.0.0: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 7.2.3 + minimatch: 3.1.2 + + tmpl@1.0.5: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + ts-api-utils@2.1.0(typescript@5.8.3): + dependencies: + typescript: 5.8.3 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-detect@4.0.8: {} + + type-fest@0.21.3: {} + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.7: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + + typescript@5.8.3: {} + + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + + undici-types@6.21.0: {} + + unicode-canonical-property-names-ecmascript@2.0.1: {} + + unicode-match-property-ecmascript@2.0.0: + dependencies: + unicode-canonical-property-names-ecmascript: 2.0.1 + unicode-property-aliases-ecmascript: 2.1.0 + + unicode-match-property-value-ecmascript@2.2.0: {} + + unicode-property-aliases-ecmascript@2.1.0: {} + + update-browserslist-db@1.1.3(browserslist@4.24.4): + dependencies: + browserslist: 4.24.4 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + v8-to-istanbul@9.3.0: + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + '@types/istanbul-lib-coverage': 2.0.6 + convert-source-map: 2.0.0 + + v8flags@3.2.0: + dependencies: + homedir-polyfill: 1.0.3 + + walker@1.0.8: + dependencies: + makeerror: 1.0.12 + + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.0 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.19 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.19: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + wrappy@1.0.2: {} + + write-file-atomic@4.0.2: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 3.0.7 + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yocto-queue@0.1.0: {} diff --git a/exercises/practice/gigasecond/.docs/instructions.md b/exercises/practice/gigasecond/.docs/instructions.md index 680870f3a8..1e20f0022e 100644 --- a/exercises/practice/gigasecond/.docs/instructions.md +++ b/exercises/practice/gigasecond/.docs/instructions.md @@ -1,6 +1,8 @@ # Instructions -Given a moment, determine the moment that would be after a gigasecond -has passed. +Your task is to determine the date and time one gigasecond after a certain date. -A gigasecond is 10^9 (1,000,000,000) seconds. +A gigasecond is one thousand million seconds. +That is a one with nine zeros after it. + +If you were born on _January 24th, 2015 at 22:00 (10:00:00pm)_, then you would be a gigasecond old on _October 2nd, 2046 at 23:46:40 (11:46:40pm)_. diff --git a/exercises/practice/gigasecond/.docs/introduction.md b/exercises/practice/gigasecond/.docs/introduction.md new file mode 100644 index 0000000000..18a3dc2005 --- /dev/null +++ b/exercises/practice/gigasecond/.docs/introduction.md @@ -0,0 +1,24 @@ +# Introduction + +The way we measure time is kind of messy. +We have 60 seconds in a minute, and 60 minutes in an hour. +This comes from ancient Babylon, where they used 60 as the basis for their number system. +We have 24 hours in a day, 7 days in a week, and how many days in a month? +Well, for days in a month it depends not only on which month it is, but also on what type of calendar is used in the country you live in. + +What if, instead, we only use seconds to express time intervals? +Then we can use metric system prefixes for writing large numbers of seconds in more easily comprehensible quantities. + +- A food recipe might explain that you need to let the brownies cook in the oven for two kiloseconds (that's two thousand seconds). +- Perhaps you and your family would travel to somewhere exotic for two megaseconds (that's two million seconds). +- And if you and your spouse were married for _a thousand million_ seconds, you would celebrate your one gigasecond anniversary. + +~~~~exercism/note +If we ever colonize Mars or some other planet, measuring time is going to get even messier. +If someone says "year" do they mean a year on Earth or a year on Mars? + +The idea for this exercise came from the science fiction novel ["A Deepness in the Sky"][vinge-novel] by author Vernor Vinge. +In it the author uses the metric system as the basis for time measurements. + +[vinge-novel]: https://www.tor.com/2017/08/03/science-fiction-with-something-for-everyone-a-deepness-in-the-sky-by-vernor-vinge/ +~~~~ diff --git a/exercises/practice/gigasecond/.eslintrc b/exercises/practice/gigasecond/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/gigasecond/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/gigasecond/.gitignore b/exercises/practice/gigasecond/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/gigasecond/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/gigasecond/.meta/config.json b/exercises/practice/gigasecond/.meta/config.json index 3ed87f06d8..3f378534c5 100644 --- a/exercises/practice/gigasecond/.meta/config.json +++ b/exercises/practice/gigasecond/.meta/config.json @@ -1,9 +1,11 @@ { - "blurb": "Given a moment, determine the moment that would be after a gigasecond has passed.", - "authors": ["rchavarria"], + "authors": [ + "rchavarria" + ], "contributors": [ "ankorGH", "draalger", + "jagdish-15", "kytrinyx", "matthewmorgan", "paparomeo", @@ -13,10 +15,23 @@ "xarxziux" ], "files": { - "solution": ["gigasecond.js"], - "test": ["gigasecond.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "gigasecond.js" + ], + "test": [ + "gigasecond.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Given a moment, determine the moment that would be after a gigasecond has passed.", "source": "Chapter 9 in Chris Pine's online Learn to Program tutorial.", - "source_url": "http://pine.fm/LearnToProgram/?Chapter=09" + "source_url": "https://pine.fm/LearnToProgram/?Chapter=09", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/gigasecond/.meta/tests.toml b/exercises/practice/gigasecond/.meta/tests.toml index 18672327f3..a7caf00dbc 100644 --- a/exercises/practice/gigasecond/.meta/tests.toml +++ b/exercises/practice/gigasecond/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [92fbe71c-ea52-4fac-bd77-be38023cacf7] description = "date only specification of time" @@ -16,3 +23,6 @@ description = "full time specified" [09d4e30e-728a-4b52-9005-be44a58d9eba] description = "full time with day roll-over" + +[fcec307c-7529-49ab-b0fe-20309197618a] +description = "does not mutate the input" diff --git a/exercises/practice/gigasecond/babel.config.js b/exercises/practice/gigasecond/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/gigasecond/babel.config.js +++ b/exercises/practice/gigasecond/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/gigasecond/eslint.config.mjs b/exercises/practice/gigasecond/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/gigasecond/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/gigasecond/gigasecond.js b/exercises/practice/gigasecond/gigasecond.js index 01df97fb98..320fe43d53 100644 --- a/exercises/practice/gigasecond/gigasecond.js +++ b/exercises/practice/gigasecond/gigasecond.js @@ -4,5 +4,5 @@ // export const gigasecond = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/gigasecond/gigasecond.spec.js b/exercises/practice/gigasecond/gigasecond.spec.js index c514a35e63..f107c3ec5e 100644 --- a/exercises/practice/gigasecond/gigasecond.spec.js +++ b/exercises/practice/gigasecond/gigasecond.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { gigasecond } from './gigasecond'; describe('Gigasecond', () => { @@ -37,8 +38,8 @@ describe('Gigasecond', () => { }); xtest('does not mutate the input', () => { - const input = new Date(Date.UTC(2020, 0, 4, 20, 28, 30)); + const input = new Date(Date.UTC(2015, 1, 24, 23, 59, 59)); gigasecond(input); - expect(input).toEqual(new Date(Date.UTC(2020, 0, 4, 20, 28, 30))); + expect(input).toEqual(new Date(Date.UTC(2015, 1, 24, 23, 59, 59))); }); }); diff --git a/exercises/practice/gigasecond/jest.config.js b/exercises/practice/gigasecond/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/gigasecond/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/gigasecond/package.json b/exercises/practice/gigasecond/package.json index 68cc9334bc..badd43eb29 100644 --- a/exercises/practice/gigasecond/package.json +++ b/exercises/practice/gigasecond/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/gigasecond" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/go-counting/.docs/instructions.md b/exercises/practice/go-counting/.docs/instructions.md index 035bffa308..e4b143f2da 100644 --- a/exercises/practice/go-counting/.docs/instructions.md +++ b/exercises/practice/go-counting/.docs/instructions.md @@ -1,22 +1,18 @@ -# Description +# Instructions Count the scored points on a Go board. -In the game of go (also known as baduk, igo, cờ vây and wéiqí) points -are gained by completely encircling empty intersections with your -stones. The encircled intersections of a player are known as its -territory. +In the game of go (also known as baduk, igo, cờ vây and wéiqí) points are gained by completely encircling empty intersections with your stones. +The encircled intersections of a player are known as its territory. -Write a function that determines the territory of each player. You may -assume that any stones that have been stranded in enemy territory have -already been taken off the board. +Calculate the territory of each player. +You may assume that any stones that have been stranded in enemy territory have already been taken off the board. -Write a function that determines the territory which includes a specified coordinate. +Determine the territory which includes a specified coordinate. -Multiple empty intersections may be encircled at once and for encircling -only horizontal and vertical neighbors count. In the following diagram -the stones which matter are marked "O" and the stones that don't are -marked "I" (ignored). Empty spaces represent empty intersections. +Multiple empty intersections may be encircled at once and for encircling only horizontal and vertical neighbors count. +In the following diagram the stones which matter are marked "O" and the stones that don't are marked "I" (ignored). +Empty spaces represent empty intersections. ```text +----+ @@ -27,10 +23,9 @@ marked "I" (ignored). Empty spaces represent empty intersections. +----+ ``` -To be more precise an empty intersection is part of a player's territory -if all of its neighbors are either stones of that player or empty -intersections that are part of that player's territory. +To be more precise an empty intersection is part of a player's territory if all of its neighbors are either stones of that player or empty intersections that are part of that player's territory. -For more information see -[wikipedia](https://en.wikipedia.org/wiki/Go_%28game%29) or [Sensei's -Library](http://senseis.xmp.net/). +For more information see [Wikipedia][go-wikipedia] or [Sensei's Library][go-sensei]. + +[go-wikipedia]: https://en.wikipedia.org/wiki/Go_%28game%29 +[go-sensei]: https://senseis.xmp.net/ diff --git a/exercises/practice/go-counting/.eslintrc b/exercises/practice/go-counting/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/go-counting/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/go-counting/.gitignore b/exercises/practice/go-counting/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/go-counting/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/go-counting/.meta/config.json b/exercises/practice/go-counting/.meta/config.json index 83561be16e..a1b576a8c9 100644 --- a/exercises/practice/go-counting/.meta/config.json +++ b/exercises/practice/go-counting/.meta/config.json @@ -1,10 +1,23 @@ { - "blurb": "Count the scored points on a Go board.", - "authors": ["lpizzinidev"], - "contributors": [], + "authors": [ + "lpizzinidev" + ], "files": { - "solution": ["go-counting.js"], - "test": ["go-counting.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "go-counting.js" + ], + "test": [ + "go-counting.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] + }, + "blurb": "Count the scored points on a Go board.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false } } diff --git a/exercises/practice/go-counting/.meta/proof.ci.js b/exercises/practice/go-counting/.meta/proof.ci.js index 1bc01766bc..bcd29aa175 100644 --- a/exercises/practice/go-counting/.meta/proof.ci.js +++ b/exercises/practice/go-counting/.meta/proof.ci.js @@ -61,6 +61,7 @@ export class GoCounting { default: return value; } + // eslint-disable-next-line no-unused-vars } catch (err) { return undefined; } @@ -84,7 +85,7 @@ export class GoCounting { case this.OPEN: if ( !territory.some( - (value) => value[0] === cell.x && value[1] === cell.y + (value) => value[0] === cell.x && value[1] === cell.y, ) ) { territory.push([cell.x, cell.y]); diff --git a/exercises/practice/go-counting/babel.config.js b/exercises/practice/go-counting/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/go-counting/babel.config.js +++ b/exercises/practice/go-counting/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/go-counting/eslint.config.mjs b/exercises/practice/go-counting/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/go-counting/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/go-counting/go-counting.js b/exercises/practice/go-counting/go-counting.js index aa756eb8f5..8c14bc1016 100644 --- a/exercises/practice/go-counting/go-counting.js +++ b/exercises/practice/go-counting/go-counting.js @@ -5,14 +5,14 @@ export class GoCounting { constructor(board) { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } getTerritory(x, y) { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } getTerritories() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/go-counting/go-counting.spec.js b/exercises/practice/go-counting/go-counting.spec.js index df4a7faf68..2338855894 100644 --- a/exercises/practice/go-counting/go-counting.spec.js +++ b/exercises/practice/go-counting/go-counting.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { GoCounting } from './go-counting'; describe('Go Counting', () => { @@ -16,14 +17,14 @@ describe('Go Counting', () => { expect(goCounting.getTerritory(0, 1)).toEqual(expectedTerritory); }); - test('White center territory on 5x5 board', () => { + xtest('White center territory on 5x5 board', () => { const board = [' B ', ' B B ', 'B W B', ' W W ', ' W ']; const goCounting = new GoCounting(board); const expectedTerritory = { owner: 'WHITE', territory: [[2, 3]] }; expect(goCounting.getTerritory(2, 3)).toEqual(expectedTerritory); }); - test('Open corner territory on 5x5 board', () => { + xtest('Open corner territory on 5x5 board', () => { const board = [' B ', ' B B ', 'B W B', ' W W ', ' W ']; const goCounting = new GoCounting(board); const expectedTerritory = { @@ -37,35 +38,35 @@ describe('Go Counting', () => { expect(goCounting.getTerritory(1, 4)).toEqual(expectedTerritory); }); - test('A stone and not a territory on 5x5 board', () => { + xtest('A stone and not a territory on 5x5 board', () => { const board = [' B ', ' B B ', 'B W B', ' W W ', ' W ']; const goCounting = new GoCounting(board); const expectedTerritory = { owner: 'NONE', territory: [] }; expect(goCounting.getTerritory(1, 1)).toEqual(expectedTerritory); }); - test('Invalid because X is too low for 5x5 board', () => { + xtest('Invalid because X is too low for 5x5 board', () => { const board = [' B ', ' B B ', 'B W B', ' W W ', ' W ']; const goCounting = new GoCounting(board); const expectedTerritory = { error: 'Invalid coordinate' }; expect(goCounting.getTerritory(-1, 1)).toEqual(expectedTerritory); }); - test('Invalid because X is too high for 5x5 board', () => { + xtest('Invalid because X is too high for 5x5 board', () => { const board = [' B ', ' B B ', 'B W B', ' W W ', ' W ']; const goCounting = new GoCounting(board); const expectedTerritory = { error: 'Invalid coordinate' }; expect(goCounting.getTerritory(5, 1)).toEqual(expectedTerritory); }); - test('Invalid because Y is too low for 5x5 board', () => { + xtest('Invalid because Y is too low for 5x5 board', () => { const board = [' B ', ' B B ', 'B W B', ' W W ', ' W ']; const goCounting = new GoCounting(board); const expectedTerritory = { error: 'Invalid coordinate' }; expect(goCounting.getTerritory(1, -1)).toEqual(expectedTerritory); }); - test('Invalid because Y is too high for 5x5 board', () => { + xtest('Invalid because Y is too high for 5x5 board', () => { const board = [' B ', ' B B ', 'B W B', ' W W ', ' W ']; const goCounting = new GoCounting(board); const expectedTerritory = { error: 'Invalid coordinate' }; @@ -74,7 +75,7 @@ describe('Go Counting', () => { }); describe('getTerritories', () => { - test('One territory is the whole board', () => { + xtest('One territory is the whole board', () => { const board = [' ']; const goCounting = new GoCounting(board); const expectedTerritories = { @@ -85,7 +86,7 @@ describe('Go Counting', () => { expect(goCounting.getTerritories()).toEqual(expectedTerritories); }); - test('Two territory rectangular board', () => { + xtest('Two territory rectangular board', () => { const board = [' BW ', ' BW ']; const goCounting = new GoCounting(board); const expectedTerritories = { @@ -102,7 +103,7 @@ describe('Go Counting', () => { expect(goCounting.getTerritories()).toEqual(expectedTerritories); }); - test('Two region rectangular board', () => { + xtest('Two region rectangular board', () => { const board = [' B ']; const goCounting = new GoCounting(board); const expectedTerritories = { diff --git a/exercises/practice/go-counting/jest.config.js b/exercises/practice/go-counting/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/go-counting/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/go-counting/package.json b/exercises/practice/go-counting/package.json index de4d892230..a63929db22 100644 --- a/exercises/practice/go-counting/package.json +++ b/exercises/practice/go-counting/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/go-counting" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/grade-school/.docs/instructions.md b/exercises/practice/grade-school/.docs/instructions.md index e87c0edd72..3cb1b5d5f9 100644 --- a/exercises/practice/grade-school/.docs/instructions.md +++ b/exercises/practice/grade-school/.docs/instructions.md @@ -1,38 +1,21 @@ # Instructions -Given students' names along with the grade that they are in, create a roster -for the school. +Given students' names along with the grade they are in, create a roster for the school. In the end, you should be able to: -- Add a student's name to the roster for a grade +- Add a student's name to the roster for a grade: - "Add Jim to grade 2." - "OK." -- Get a list of all students enrolled in a grade +- Get a list of all students enrolled in a grade: - "Which students are in grade 2?" - - "We've only got Jim just now." -- Get a sorted list of all students in all grades. Grades should sort - as 1, 2, 3, etc., and students within a grade should be sorted - alphabetically by name. - - "Who all is enrolled in school right now?" - - "Let me think. We have - Anna, Barb, and Charlie in grade 1, - Alex, Peter, and Zoe in grade 2 - and Jim in grade 5. - So the answer is: Anna, Barb, Charlie, Alex, Peter, Zoe and Jim" - -Note that all our students only have one name. (It's a small town, what -do you want?) - -## For bonus points - -Did you get the tests passing and the code clean? If you want to, these -are some additional things you could try: - -- If you're working in a language with mutable data structures and your - implementation allows outside code to mutate the school's internal DB - directly, see if you can prevent this. Feel free to introduce additional - tests. - -Then please share your thoughts in a comment on the submission. Did this -experiment make the code better? Worse? Did you learn anything from it? + - "We've only got Jim right now." +- Get a sorted list of all students in all grades. + Grades should be sorted as 1, 2, 3, etc., and students within a grade should be sorted alphabetically by name. + - "Who is enrolled in school right now?" + - "Let me think. + We have Anna, Barb, and Charlie in grade 1, Alex, Peter, and Zoe in grade 2, and Jim in grade 5. + So the answer is: Anna, Barb, Charlie, Alex, Peter, Zoe, and Jim." + +Note that all our students only have one name (it's a small town, what do you want?), and each student cannot be added more than once to a grade or the roster. +If a test attempts to add the same student more than once, your implementation should indicate that this is incorrect. diff --git a/exercises/practice/grade-school/.eslintrc b/exercises/practice/grade-school/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/grade-school/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/grade-school/.gitignore b/exercises/practice/grade-school/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/grade-school/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/grade-school/.meta/config.json b/exercises/practice/grade-school/.meta/config.json index 19f8cbe827..9041af604d 100644 --- a/exercises/practice/grade-school/.meta/config.json +++ b/exercises/practice/grade-school/.meta/config.json @@ -1,10 +1,12 @@ { - "blurb": "Given students' names along with the grade that they are in, create a roster for the school", - "authors": ["rchavarria"], + "authors": [ + "rchavarria" + ], "contributors": [ "ankorGH", "draalger", "ee7", + "jagdish-15", "kytrinyx", "matthewmorgan", "mgmatola", @@ -14,10 +16,22 @@ "xarxziux" ], "files": { - "solution": ["grade-school.js"], - "test": ["grade-school.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "grade-school.js" + ], + "test": [ + "grade-school.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Given students' names along with the grade that they are in, create a roster for the school.", "source": "A pairing session with Phil Battos at gSchool", - "source_url": "http://gschool.it" + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/grade-school/.meta/proof.ci.js b/exercises/practice/grade-school/.meta/proof.ci.js index 3c9855054c..3798691b18 100644 --- a/exercises/practice/grade-school/.meta/proof.ci.js +++ b/exercises/practice/grade-school/.meta/proof.ci.js @@ -4,25 +4,32 @@ export class GradeSchool { } add(student, level) { - this.students.set(student, level); + for (const names of this.students.values()) { + if (names.has(student)) { + return false; + } + } + + if (!this.students.has(level)) { + this.students.set(level, new Set()); + } + + this.students.get(level).add(student); + return true; } grade(level) { - return Array.from(this.students.entries()) - .filter(([, studentGrade]) => studentGrade === level) - .map(([student]) => student) - .sort(); + if (!this.students.has(level)) { + return []; + } + return [...this.students.get(level)].sort(); } roster() { - const result = {}; - - Array.from(this.students.entries()).forEach(([, studentGrade]) => { - if (!result[studentGrade]) { - result[studentGrade] = this.grade(studentGrade); - } - }); - + const result = []; + for (const level of [...this.students.keys()].sort((a, b) => a - b)) { + result.push(...[...this.students.get(level)].sort()); + } return result; } } diff --git a/exercises/practice/grade-school/.meta/tests.toml b/exercises/practice/grade-school/.meta/tests.toml index 4eaa5f0d85..50c9e2e599 100644 --- a/exercises/practice/grade-school/.meta/tests.toml +++ b/exercises/practice/grade-school/.meta/tests.toml @@ -1,27 +1,86 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[a3f0fb58-f240-4723-8ddc-e644666b85cc] +description = "Roster is empty when no student is added" + +[9337267f-7793-4b90-9b4a-8e3978408824] +description = "Add a student" [6d0a30e4-1b4e-472e-8e20-c41702125667] -description = "Adding a student adds them to the sorted roster" +description = "Student is added to the roster" + +[73c3ca75-0c16-40d7-82f5-ed8fe17a8e4a] +description = "Adding multiple students in the same grade in the roster" + +[233be705-dd58-4968-889d-fb3c7954c9cc] +description = "Multiple students in the same grade are added to the roster" + +[87c871c1-6bde-4413-9c44-73d59a259d83] +description = "Cannot add student to same grade in the roster more than once" [c125dab7-2a53-492f-a99a-56ad511940d8] description = "A student can't be in two different grades" +include = false -[233be705-dd58-4968-889d-fb3c7954c9cc] -description = "Adding more students adds them to the sorted roster" +[a0c7b9b8-0e89-47f8-8b4a-c50f885e79d1] +description = "A student can only be added to the same grade in the roster once" +include = false +reimplements = "c125dab7-2a53-492f-a99a-56ad511940d8" + +[d7982c4f-1602-49f6-a651-620f2614243a] +description = "Student not added to same grade in the roster more than once" +reimplements = "a0c7b9b8-0e89-47f8-8b4a-c50f885e79d1" + +[e70d5d8f-43a9-41fd-94a4-1ea0fa338056] +description = "Adding students in multiple grades" [75a51579-d1d7-407c-a2f8-2166e984e8ab] -description = "Adding students to different grades adds them to the same sorted roster" +description = "Students in multiple grades are added to the roster" -[a3f0fb58-f240-4723-8ddc-e644666b85cc] -description = "Roster returns an empty list if there are no students enrolled" +[7df542f1-57ce-433c-b249-ff77028ec479] +description = "Cannot add same student to multiple grades in the roster" -[180a8ff9-5b94-43fc-9db1-d46b4a8c93b6] -description = "Student names with grades are displayed in the same sorted roster" +[6a03b61e-1211-4783-a3cc-fc7f773fba3f] +description = "A student cannot be added to more than one grade in the sorted roster" +include = false +reimplements = "c125dab7-2a53-492f-a99a-56ad511940d8" -[1bfbcef1-e4a3-49e8-8d22-f6f9f386187e] -description = "Grade returns the students in that grade in alphabetical order" +[c7ec1c5e-9ab7-4d3b-be5c-29f2f7a237c5] +description = "Student not added to multiple grades in the roster" +reimplements = "6a03b61e-1211-4783-a3cc-fc7f773fba3f" + +[d9af4f19-1ba1-48e7-94d0-dabda4e5aba6] +description = "Students are sorted by grades in the roster" + +[d9fb5bea-f5aa-4524-9d61-c158d8906807] +description = "Students are sorted by name in the roster" + +[180a8ff9-5b94-43fc-9db1-d46b4a8c93b6] +description = "Students are sorted by grades and then by name in the roster" [5e67aa3c-a3c6-4407-a183-d8fe59cd1630] -description = "Grade returns an empty list if there are no students in that grade" +description = "Grade is empty if no students in the roster" + +[1e0cf06b-26e0-4526-af2d-a2e2df6a51d6] +description = "Grade is empty if no students in that grade" + +[2bfc697c-adf2-4b65-8d0f-c46e085f796e] +description = "Student not added to same grade more than once" + +[66c8e141-68ab-4a04-a15a-c28bc07fe6b9] +description = "Student not added to multiple grades" + +[c9c1fc2f-42e0-4d2c-b361-99271f03eda7] +description = "Student not added to other grade for multiple grades" + +[1bfbcef1-e4a3-49e8-8d22-f6f9f386187e] +description = "Students are sorted by name in a grade" diff --git a/exercises/practice/grade-school/babel.config.js b/exercises/practice/grade-school/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/grade-school/babel.config.js +++ b/exercises/practice/grade-school/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/grade-school/eslint.config.mjs b/exercises/practice/grade-school/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/grade-school/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/grade-school/grade-school.js b/exercises/practice/grade-school/grade-school.js index 6385d9668f..04e9febbd7 100644 --- a/exercises/practice/grade-school/grade-school.js +++ b/exercises/practice/grade-school/grade-school.js @@ -5,14 +5,14 @@ export class GradeSchool { roster() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } add() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } grade() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/grade-school/grade-school.spec.js b/exercises/practice/grade-school/grade-school.spec.js index 125b29c1d9..7dca986a41 100644 --- a/exercises/practice/grade-school/grade-school.spec.js +++ b/exercises/practice/grade-school/grade-school.spec.js @@ -1,86 +1,178 @@ +import { beforeEach, describe, expect, test, xtest } from '@jest/globals'; import { GradeSchool } from './grade-school'; -describe('School', () => { +describe('Grade School', () => { let school; beforeEach(() => { school = new GradeSchool(); }); - test('a new school has an empty roster', () => { - expect(school.roster()).toEqual({}); + test('Roster is empty when no student is added', () => { + expect(school.roster()).toEqual([]); }); - xtest('adding a student adds them to the roster for the given grade', () => { + xtest('Add a student', () => { + expect(school.add('Aimee', 2)).toEqual(true); + }); + + xtest('Student is added to the roster', () => { school.add('Aimee', 2); - const expectedDb = { 2: ['Aimee'] }; + const expectedDb = ['Aimee']; expect(school.roster()).toEqual(expectedDb); }); - xtest('adding more students to the same grade adds them to the roster', () => { + xtest('Adding multiple students in the same grade in the roster', () => { + expect(school.add('Blair', 2)).toEqual(true); + expect(school.add('James', 2)).toEqual(true); + expect(school.add('Paul', 2)).toEqual(true); + }); + + xtest('Multiple students in the same grade are added to the roster', () => { school.add('Blair', 2); school.add('James', 2); school.add('Paul', 2); - const expectedDb = { 2: ['Blair', 'James', 'Paul'] }; + const expectedDb = ['Blair', 'James', 'Paul']; expect(school.roster()).toEqual(expectedDb); }); - xtest('adding students to different grades adds them to the roster', () => { + xtest('Cannot add student to same grade in the roster more than once', () => { + expect(school.add('Blair', 2)).toEqual(true); + expect(school.add('James', 2)).toEqual(true); + expect(school.add('James', 2)).toEqual(false); + expect(school.add('Paul', 2)).toEqual(true); + }); + + xtest('Student not added to same grade in the roster more than once', () => { + school.add('Blair', 2); + school.add('James', 2); + school.add('James', 2); + school.add('Paul', 2); + + const expectedDb = ['Blair', 'James', 'Paul']; + expect(school.roster()).toEqual(expectedDb); + }); + + xtest('Adding students in multiple grades', () => { + expect(school.add('Chelsea', 3)).toEqual(true); + expect(school.add('Logan', 7)).toEqual(true); + }); + + xtest('Students in multiple grades are added to the roster', () => { school.add('Chelsea', 3); school.add('Logan', 7); - const expectedDb = { 3: ['Chelsea'], 7: ['Logan'] }; + const expectedDb = ['Chelsea', 'Logan']; expect(school.roster()).toEqual(expectedDb); }); - xtest('grade returns the students in that grade in alphabetical order', () => { - school.add('Franklin', 5); - school.add('Bradley', 5); - school.add('Jeff', 1); - - const expectedStudents = ['Bradley', 'Franklin']; - expect(school.grade(5)).toEqual(expectedStudents); + xtest('Cannot add same student to multiple grades in the roster', () => { + expect(school.add('Blair', 2)).toEqual(true); + expect(school.add('James', 2)).toEqual(true); + expect(school.add('James', 3)).toEqual(false); + expect(school.add('Paul', 3)).toEqual(true); }); - xtest('grade returns an empty array if there are no students in that grade', () => { - expect(school.grade(1)).toEqual([]); + xtest('Student not added to multiple grades in the roster', () => { + school.add('Blair', 2); + school.add('James', 2); + school.add('James', 3); + school.add('Paul', 3); + + const expectedDb = ['Blair', 'James', 'Paul']; + expect(school.roster()).toEqual(expectedDb); }); - xtest('the students names in each grade in the roster are sorted', () => { - school.add('Jennifer', 4); - school.add('Kareem', 6); - school.add('Christopher', 4); - school.add('Kyle', 3); + xtest('Students are sorted by grades in the roster', () => { + school.add('Jim', 3); + school.add('Peter', 2); + school.add('Anna', 1); - const expectedSortedStudents = { - 3: ['Kyle'], - 4: ['Christopher', 'Jennifer'], - 6: ['Kareem'], - }; - expect(school.roster()).toEqual(expectedSortedStudents); + const expectedDb = ['Anna', 'Peter', 'Jim']; + expect(school.roster()).toEqual(expectedDb); }); - xtest('roster cannot be modified outside of module', () => { - school.add('Aimee', 2); - const roster = school.roster(); - roster[2].push('Oops.'); - const expectedDb = { 2: ['Aimee'] }; + xtest('Students are sorted by name in the roster', () => { + school.add('Peter', 2); + school.add('Zoe', 2); + school.add('Alex', 2); + + const expectedDb = ['Alex', 'Peter', 'Zoe']; expect(school.roster()).toEqual(expectedDb); }); - xtest('roster cannot be modified outside of module using grade()', () => { - school.add('Aimee', 2); - school.grade(2).push('Oops.'); - const expectedDb = { 2: ['Aimee'] }; + xtest('Students are sorted by grades and then by name in the roster', () => { + school.add('Peter', 2); + school.add('Anna', 1); + school.add('Barb', 1); + school.add('Zoe', 2); + school.add('Alex', 2); + school.add('Jim', 3); + school.add('Charlie', 1); + + const expectedDb = [ + 'Anna', + 'Barb', + 'Charlie', + 'Alex', + 'Peter', + 'Zoe', + 'Jim', + ]; expect(school.roster()).toEqual(expectedDb); }); - xtest("a student can't be in two different grades", () => { - school.add('Aimee', 2); - school.add('Aimee', 1); + xtest('Grade is empty if no students in the roster', () => { + expect(school.grade(1)).toEqual([]); + }); + + xtest('Grade is empty if no students in that grade', () => { + school.add('Peter', 2); + school.add('Zoe', 2); + school.add('Alex', 2); + school.add('Jim', 3); + + expect(school.grade(1)).toEqual([]); + }); + + xtest('Student not added to same grade more than once', () => { + school.add('Blair', 2); + school.add('James', 2); + school.add('James', 2); + school.add('Paul', 2); + + const expectedDb = ['Blair', 'James', 'Paul']; + expect(school.grade(2)).toEqual(expectedDb); + }); + + xtest('Student not added to multiple grades', () => { + school.add('Blair', 2); + school.add('James', 2); + school.add('James', 3); + school.add('Paul', 3); + + const expectedDb = ['Blair', 'James']; + expect(school.grade(2)).toEqual(expectedDb); + }); + + xtest('Student not added to other grade for multiple grades', () => { + school.add('Blair', 2); + school.add('James', 2); + school.add('James', 3); + school.add('Paul', 3); + + const expectedDb = ['Paul']; + expect(school.grade(3)).toEqual(expectedDb); + }); + + xtest('Students are sorted by name in a grade', () => { + school.add('Franklin', 5); + school.add('Bradley', 5); + school.add('Jeff', 1); - expect(school.grade(2)).toEqual([]); + const expectedDb = ['Bradley', 'Franklin']; + expect(school.grade(5)).toEqual(expectedDb); }); }); diff --git a/exercises/practice/grade-school/jest.config.js b/exercises/practice/grade-school/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/grade-school/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/grade-school/package.json b/exercises/practice/grade-school/package.json index 8b85b84a88..2d31c05a16 100644 --- a/exercises/practice/grade-school/package.json +++ b/exercises/practice/grade-school/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/grade-school" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/grains/.approaches/bit-shifting/content.md b/exercises/practice/grains/.approaches/bit-shifting/content.md new file mode 100644 index 0000000000..0dc63e9548 --- /dev/null +++ b/exercises/practice/grains/.approaches/bit-shifting/content.md @@ -0,0 +1,67 @@ +# Bit-shifting + +```javascript +export function square(num) { + if (num < 1 || num > 64) { + throw new Error('square must be between 1 and 64'); + } + return 1n << (BigInt(num) - 1n); +} + +export function total() { + return (1n << 64n) - 1n; +} +``` + +Instead of using math to calculate the number of grains on a square, you can set a bit in the correct position of a [`BigInt`][bigint] value. + + +~~~~exercism/note +Note that a `BigInt` literal can be specified by appending `n` to the value. +~~~~ + + +To understand how this works, consider just two squares that are represented in binary bits as `00`. + +You use the [left-shift operator][left-shift-operator] to set `1n` at the position needed to make the correct decimal value. + +- To set the one grain on Square One you shift `1n` for `0` positions to the left. + So, if `num` is `1` for square One, you subtract `num` by `1` to get `0`, which will not move it any positions to the left. + The result is binary `01`, which is decimal `1`. +- To set the two grains on Square Two you shift `1n` for `1` position to the left. + So, if `num` is `2` for square Two, you subtract `num` by `1` to get `1`, which will move it `1` position to the left. + The result is binary `10`, which is decimal `2`. + +| Square | Shift Left By | Binary Value | Decimal Value | +| ------ | ------------- | ------------ | ------------- | +| 1 | 0 | 0001 | 1 | +| 2 | 1 | 0010 | 2 | +| 3 | 2 | 0100 | 4 | +| 4 | 3 | 1000 | 8 | + +For `total` we want all of the 64 bits set to `1` to get the sum of grains on all sixty-four squares. +The easy way to do this is to set the 65th bit to `1` and then subtract `1`. +To go back to our two-square example, if we can grow to three squares, then we can shift `1n` two positions to the left for binary `100`, +which is decimal `4`. +By subtracting `1` we get `3`, which is the total amount of grains on the two squares. + +| Square | Binary Value | Decimal Value | +| ------ | ------------ | ------------- | +| 3 | 0100 | 4 | + +| Square | Sum Binary Value | Sum Decimal Value | +| ------ | ---------------- | ----------------- | +| 1 | 0001 | 1 | +| 2 | 0011 | 3 | + +## Shortening + +When the body of a function is a single expression, the function can be implemented as an [arrow function][arrow-function], like so + +```javascript +export const total = () => (1n << 64n) - 1n; +``` + +[bigint]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt +[left-shift-operator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Left_shift +[arrow-function]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions diff --git a/exercises/practice/grains/.approaches/bit-shifting/snippet.txt b/exercises/practice/grains/.approaches/bit-shifting/snippet.txt new file mode 100644 index 0000000000..0c29f9a2d1 --- /dev/null +++ b/exercises/practice/grains/.approaches/bit-shifting/snippet.txt @@ -0,0 +1,8 @@ +export function square(num) { + if (num < 1 || num > 64) { + throw new Error('square must be between 1 and 64'); + } + return 1n << (BigInt(num) - 1n); +} + +export const total = () => (1n << 64n) - 1n; diff --git a/exercises/practice/grains/.approaches/config.json b/exercises/practice/grains/.approaches/config.json new file mode 100644 index 0000000000..1b09065282 --- /dev/null +++ b/exercises/practice/grains/.approaches/config.json @@ -0,0 +1,27 @@ +{ + "introduction": { + "authors": [ + "bobahop" + ] + }, + "approaches": [ + { + "uuid": "55cf8e16-236e-445f-a6b9-131c225caf7c", + "slug": "exponentiation", + "title": "exponentiation", + "blurb": "Use exponentiation to raise 2 by a specified power.", + "authors": [ + "bobahop" + ] + }, + { + "uuid": "019b01f3-1955-45b1-96e9-06a233627f3e", + "slug": "bit-shifting", + "title": "Bit-shifting", + "blurb": "Use bit-shifting to set the correct value.", + "authors": [ + "bobahop" + ] + } + ] +} diff --git a/exercises/practice/grains/.approaches/exponentiation/content.md b/exercises/practice/grains/.approaches/exponentiation/content.md new file mode 100644 index 0000000000..bc6b9457dd --- /dev/null +++ b/exercises/practice/grains/.approaches/exponentiation/content.md @@ -0,0 +1,53 @@ +## Exponentiation + +```javascript +export function square(num) { + if (num < 1 || num > 64) { + throw new Error('square must be between 1 and 64'); + } + return 2n ** BigInt(num - 1); +} + +export function total() { + return 2n ** 64n - 1n; +} +``` + +JavaScript uses the [exponential operator][exponentiation] to raise a number by a certain exponent. +[`Math.pow()`][pow] can also be used to raise a number by an exponent, but it does not work with a [`BigInt`][bigint]. + +Exponentiation is nicely suited to the problem, since we start with one grain and keep doubling the number of grains on each successive square. +`1` grain is `2n ** 0`, `2` grains is `2n ** 1`, `4` is `2n ** 2`, and so on. + + +~~~~exercism/note +Note that a [`BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) literal can be specified by appending `n` to the value. +~~~~ + + +So, to get the right exponent, we subtract `1` from the square number `num`. + +The easiest way to get `total` is to get the value for an imaginary 65th square, +and then subtract `1` from it. +To understand how that works, consider a board that has only two squares. +If we could grow the board to three squares, then we could get the number of grains on the imaginary third square, +which would be `4.` +You could then subtract `4` by `1` to get `3`, which is the number of grains on the first square (`1`) and the second square (`2`). +Remembering that the exponent must be one less than the square you want, +you can call `2n ** 64` to get the number of grains on the imaginary 65th square. +Subtracting that value by `1` gives the values on all `64` squares. + +## Shortening + +When the body of a function is a single expression, the function can be implemented as an [arrow function][arrow-function], like so + +```javascript +export const total = () => 2n ** 64n - 1n; +``` + +Notice that `return` and the curly braces are not needed. + +[exponentiation]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Exponentiation +[bigint]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt +[pow]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/pow +[arrow-function]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions diff --git a/exercises/practice/grains/.approaches/exponentiation/snippet.txt b/exercises/practice/grains/.approaches/exponentiation/snippet.txt new file mode 100644 index 0000000000..ba851c1772 --- /dev/null +++ b/exercises/practice/grains/.approaches/exponentiation/snippet.txt @@ -0,0 +1,8 @@ +export function square(num) { + if (num < 1 || num > 64) { + throw new Error("square must be between 1 and 64"); + } + return 2n ** BigInt(num - 1); +} + +export const total = () => 2n ** 64n - 1n; diff --git a/exercises/practice/grains/.approaches/introduction.md b/exercises/practice/grains/.approaches/introduction.md new file mode 100644 index 0000000000..e9ce5b2e74 --- /dev/null +++ b/exercises/practice/grains/.approaches/introduction.md @@ -0,0 +1,69 @@ +# Introduction + +There are various idiomatic approaches to solve Grains. +You can use [exponentiation][exponentiation] to calculate the number on grains on a square. +Or you can use bit-shifting. + +## General guidance + +The key to solving Grains is to focus on each square having double the amount of grains as the square before it. +This means that the amount of grains grows exponentially. +The first square has one grain, which is `2` to the power of `0`. +The second square has two grains, which is `2` to the power of `1`. +The third square has four grains, which is `2` to the power of `2`. +You can see that the exponent, or power, that `2` is raised by is always one less than the square number. + +| Square | Power | Value | +| ------ | ----- | ----------------------- | +| 1 | 0 | 2 to the power of 0 = 1 | +| 2 | 1 | 2 to the power of 1 = 2 | +| 3 | 2 | 2 to the power of 2 = 4 | +| 4 | 3 | 2 to the power of 3 = 8 | + +You can use the `bigint` type and [BigInt][bigint] global object to support numbers above [`NUMBER.MAX_SAFE_INTEGER`][max-safe-integer]. + +## Approach: Exponentiation + +```javascript +export function square(num) { + if (num < 1 || num > 64) { + throw new Error('square must be between 1 and 64'); + } + return 2n ** BigInt(num - 1); +} + +export function total() { + return 2n ** 64n - 1n; +} +``` + +For more information, check the [exponentiation approach][approach-exponentiation]. + +## Approach: Bit-shifting + +```javascript +export function square(num) { + if (num < 1 || num > 64) { + throw new Error('square must be between 1 and 64'); + } + return 1n << (BigInt(num) - 1n); +} + +export function total() { + return (1n << 64n) - 1n; +} +``` + +For more information, check the [Bit-shifting approach][approach-bit-shifting]. + +## Which approach to use? + +Exponentiation may be considered the most readable solution. + +Bit-shifting may be considered less readable, and is about 30% slower than exponentiation. + +[exponentiation]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Exponentiation +[bigint]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt +[max-safe-integer]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER +[approach-exponentiation]: https://exercism.org/tracks/javascript/exercises/grains/approaches/exponentiation +[approach-bit-shifting]: https://exercism.org/tracks/javascript/exercises/grains/approaches/bit-shifting diff --git a/exercises/practice/grains/.docs/instructions.md b/exercises/practice/grains/.docs/instructions.md index d955f12230..f5b752a817 100644 --- a/exercises/practice/grains/.docs/instructions.md +++ b/exercises/practice/grains/.docs/instructions.md @@ -1,28 +1,11 @@ # Instructions -Calculate the number of grains of wheat on a chessboard given that the number -on each square doubles. +Calculate the number of grains of wheat on a chessboard. -There once was a wise servant who saved the life of a prince. The king -promised to pay whatever the servant could dream up. Knowing that the -king loved chess, the servant told the king he would like to have grains -of wheat. One grain on the first square of a chess board, with the number -of grains doubling on each successive square. +A chessboard has 64 squares. +Square 1 has one grain, square 2 has two grains, square 3 has four grains, and so on, doubling each time. -There are 64 squares on a chessboard (where square 1 has one grain, square 2 has two grains, and so on). +Write code that calculates: -Write code that shows: - -- how many grains were on a given square, and +- the number of grains on a given square - the total number of grains on the chessboard - -## For bonus points - -Did you get the tests passing and the code clean? If you want to, these -are some additional things you could try: - -- Optimize for speed. -- Optimize for readability. - -Then please share your thoughts in a comment on the submission. Did this -experiment make the code better? Worse? Did you learn anything from it? diff --git a/exercises/practice/grains/.docs/introduction.md b/exercises/practice/grains/.docs/introduction.md new file mode 100644 index 0000000000..0df4f46f72 --- /dev/null +++ b/exercises/practice/grains/.docs/introduction.md @@ -0,0 +1,6 @@ +# Introduction + +There once was a wise servant who saved the life of a prince. +The king promised to pay whatever the servant could dream up. +Knowing that the king loved chess, the servant told the king he would like to have grains of wheat. +One grain on the first square of a chessboard, with the number of grains doubling on each successive square. diff --git a/exercises/practice/grains/.eslintrc b/exercises/practice/grains/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/grains/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/grains/.gitignore b/exercises/practice/grains/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/grains/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/grains/.meta/config.json b/exercises/practice/grains/.meta/config.json index a1ffab7e34..c7b021091c 100644 --- a/exercises/practice/grains/.meta/config.json +++ b/exercises/practice/grains/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Calculate the number of grains of wheat on a chessboard given that the number on each square doubles.", - "authors": ["rchavarria"], + "authors": [ + "rchavarria" + ], "contributors": [ "ankorGH", "drericrobinson", @@ -14,10 +15,23 @@ "xarxziux" ], "files": { - "solution": ["grains.js"], - "test": ["grains.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "grains.js" + ], + "test": [ + "grains.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, - "source": "JavaRanch Cattle Drive, exercise 6", - "source_url": "http://www.javaranch.com/grains.jsp" + "blurb": "Calculate the number of grains of wheat on a chessboard given that the number on each square doubles.", + "source": "The CodeRanch Cattle Drive, Assignment 6", + "source_url": "https://web.archive.org/web/20240908084142/https://coderanch.com/wiki/718824/Grains", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/grains/babel.config.js b/exercises/practice/grains/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/grains/babel.config.js +++ b/exercises/practice/grains/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/grains/eslint.config.mjs b/exercises/practice/grains/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/grains/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/grains/grains.js b/exercises/practice/grains/grains.js index a9dfc0e1d9..bc58f87aad 100644 --- a/exercises/practice/grains/grains.js +++ b/exercises/practice/grains/grains.js @@ -10,9 +10,9 @@ // convenience to get you started writing code faster. // export const square = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; export const total = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/grains/grains.spec.js b/exercises/practice/grains/grains.spec.js index 3f10a2ab57..e36019cccb 100644 --- a/exercises/practice/grains/grains.spec.js +++ b/exercises/practice/grains/grains.spec.js @@ -27,6 +27,7 @@ * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt */ +import { describe, expect, test, xtest } from '@jest/globals'; import { square, total } from './grains'; describe('Grains', () => { @@ -61,19 +62,19 @@ describe('Grains', () => { xtest('square 0 raises an exception', () => { expect(() => square(0)).toThrow( - new Error('square must be between 1 and 64') + new Error('square must be between 1 and 64'), ); }); xtest('negative square raises an exception', () => { expect(() => square(-1)).toThrow( - new Error('square must be between 1 and 64') + new Error('square must be between 1 and 64'), ); }); xtest('square greater than 64 raises an exception', () => { expect(() => square(65)).toThrow( - new Error('square must be between 1 and 64') + new Error('square must be between 1 and 64'), ); }); }); diff --git a/exercises/practice/grains/jest.config.js b/exercises/practice/grains/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/grains/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/grains/package.json b/exercises/practice/grains/package.json index 1da0510c41..da56eae10d 100644 --- a/exercises/practice/grains/package.json +++ b/exercises/practice/grains/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/grains" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/grep/.docs/instructions.md b/exercises/practice/grep/.docs/instructions.md index 0c487373ba..004f28acd5 100644 --- a/exercises/practice/grep/.docs/instructions.md +++ b/exercises/practice/grep/.docs/instructions.md @@ -1,65 +1,27 @@ # Instructions -Search a file for lines matching a regular expression pattern. Return the line -number and contents of each matching line. +Search files for lines matching a search string and return all matching lines. -The Unix [`grep`](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/grep.html) command can be used to search for lines in one or more files -that match a user-provided search query (known as the _pattern_). +The Unix [`grep`][grep] command searches files for lines that match a regular expression. +Your task is to implement a simplified `grep` command, which supports searching for fixed strings. The `grep` command takes three arguments: -1. The pattern used to match lines in a file. -2. Zero or more flags to customize the matching behavior. -3. One or more files in which to search for matching lines. +1. The string to search for. +2. Zero or more flags for customizing the command's behavior. +3. One or more files to search in. -Your task is to implement the `grep` function, which should read the contents -of the specified files, find the lines that match the specified pattern -and then output those lines as a single string. Note that the lines should -be output in the order in which they were found, with the first matching line -in the first file being output first. - -As an example, suppose there is a file named "input.txt" with the following contents: - -```text -hello -world -hello again -``` - -If we were to call `grep "hello" input.txt`, the returned string should be: - -```text -hello -hello again -``` +It then reads the contents of the specified files (in the order specified), finds the lines that contain the search string, and finally returns those lines in the order in which they were found. +When searching in multiple files, each matching line is prepended by the file name and a colon (':'). ## Flags -As said earlier, the `grep` command should also support the following flags: - -- `-n` Print the line numbers of each matching line. -- `-l` Print only the names of files that contain at least one matching line. -- `-i` Match line using a case-insensitive comparison. -- `-v` Invert the program -- collect all lines that fail to match the pattern. -- `-x` Only match entire lines, instead of lines that contain a match. - -If we run `grep -n "hello" input.txt`, the `-n` flag will require the matching -lines to be prefixed with its line number: - -```text -1:hello -3:hello again -``` - -And if we run `grep -i "HELLO" input.txt`, we'll do a case-insensitive match, -and the output will be: - -```text -hello -hello again -``` +The `grep` command supports the following flags: -The `grep` command should support multiple flags at once. +- `-n` Prepend the line number and a colon (':') to each line in the output, placing the number after the filename (if present). +- `-l` Output only the names of the files that contain at least one matching line. +- `-i` Match using a case-insensitive comparison. +- `-v` Invert the program -- collect all lines that fail to match. +- `-x` Search only for lines where the search string matches the entire line. -For example, running `grep -l -v "hello" file1.txt file2.txt` should -print the names of files that do not contain the string "hello". +[grep]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/grep.html diff --git a/exercises/practice/grep/.eslintrc b/exercises/practice/grep/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/grep/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/grep/.gitignore b/exercises/practice/grep/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/grep/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/grep/.meta/config.json b/exercises/practice/grep/.meta/config.json index 9529023e08..7fdcac0960 100644 --- a/exercises/practice/grep/.meta/config.json +++ b/exercises/practice/grep/.meta/config.json @@ -1,12 +1,29 @@ { - "blurb": "Search a file for lines matching a regular expression pattern. Return the line number and contents of each matching line.", - "authors": ["TomPradat"], - "contributors": ["iHiD", "SleeplessByte"], + "authors": [ + "TomPradat" + ], + "contributors": [ + "iHiD", + "SleeplessByte" + ], "files": { - "solution": ["grep.js"], - "test": ["grep.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "grep.js" + ], + "test": [ + "grep.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Search a file for lines matching a regular expression pattern. Return the line number and contents of each matching line.", "source": "Conversation with Nate Foster.", - "source_url": "http://www.cs.cornell.edu/Courses/cs3110/2014sp/hw/0/ps0.pdf" + "source_url": "https://www.cs.cornell.edu/Courses/cs3110/2014sp/hw/0/ps0.pdf", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/grep/babel.config.js b/exercises/practice/grep/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/grep/babel.config.js +++ b/exercises/practice/grep/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/grep/eslint.config.mjs b/exercises/practice/grep/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/grep/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/grep/grep.spec.js b/exercises/practice/grep/grep.spec.js index 98ef1dafe0..f953136ee6 100644 --- a/exercises/practice/grep/grep.spec.js +++ b/exercises/practice/grep/grep.spec.js @@ -1,5 +1,7 @@ // @ts-check +const { test, xtest, describe, expect } = require('@jest/globals'); + const { spawnSync } = require('child_process'); const { resolve: resolvePath, relative } = require('path'); @@ -18,7 +20,7 @@ function spawnGrep(config) { ...config.flags, config.pattern, ...config.files.map((file) => - relative(BASE_DIR, resolvePath(BASE_DIR, 'data', file)) + relative(BASE_DIR, resolvePath(BASE_DIR, 'data', file)), ), ]; @@ -29,7 +31,7 @@ function spawnGrep(config) { // If anything is written to stderr, consider the entire process as failed. // - // Normally you'd check the status code (exit code) and reject/fail if it's + // Normally you'd check the status code (extest code) and reject/fail if it's // not equal to "0". if (stderr) { reject(stderr); @@ -61,160 +63,160 @@ function resolveDataFile(file) { describe('grep exercise', () => { describe('Test grepping a single file', () => { - it('One file, one match, no flags', () => { + test('One file, one match, no flags', () => { return expect( spawnGrep({ pattern: 'Agamemnon', flags: [], files: ['iliad.txt'], - }) + }), ).resolves.toBe('Of Atreus, Agamemnon, King of men.'); }); - xit('One file, one match, print line numbers flag', () => { + xtest('One file, one match, print line numbers flag', () => { return expect( spawnGrep({ pattern: 'Forbidden', flags: ['-n'], files: ['paradise-lost.txt'], - }) + }), ).resolves.toBe('2:Of that Forbidden Tree, whose mortal tast'); }); - xit('One file, one match, case-insensitive flag', () => { + xtest('One file, one match, case-insensitive flag', () => { return expect( spawnGrep({ pattern: 'FORBIDDEN', flags: ['-i'], files: ['paradise-lost.txt'], - }) + }), ).resolves.toBe('Of that Forbidden Tree, whose mortal tast'); }); - xit('One file, one match, print file names flag', () => { + xtest('One file, one match, print file names flag', () => { return expect( spawnGrep({ pattern: 'Forbidden', flags: ['-l'], files: ['paradise-lost.txt'], - }) + }), ).resolves.toBe(resolveDataFile('paradise-lost.txt')); }); - xit('One file, one match, match entire lines flag', () => { + xtest('One file, one match, match entire lines flag', () => { return expect( spawnGrep({ pattern: 'With loss of Eden, till one greater Man', flags: ['-x'], files: ['paradise-lost.txt'], - }) + }), ).resolves.toBe('With loss of Eden, till one greater Man'); }); - xit('One file, one match, multiple flags', () => { + xtest('One file, one match, multiple flags', () => { return expect( spawnGrep({ pattern: 'OF ATREUS, Agamemnon, KIng of MEN.', flags: ['-n', '-i', '-x'], files: ['iliad.txt'], - }) + }), ).resolves.toBe('9:Of Atreus, Agamemnon, King of men.'); }); - xit('One file, several matches, no flags', () => { + xtest('One file, several matches, no flags', () => { return expect( spawnGrep({ pattern: 'may', flags: [], files: ['midsummer-night.txt'], - }) + }), ).resolves.toBe( formatStringTemplate(`Nor how it may concern my modesty, But I beseech your grace that I may know - The worst that may befall me in this case,`) + The worst that may befall me in this case,`), ); }); - xit('One file, several matches, print line numbers flag', () => { + xtest('One file, several matches, print line numbers flag', () => { return expect( spawnGrep({ pattern: 'may', flags: ['-n'], files: ['midsummer-night.txt'], - }) + }), ).resolves.toBe( formatStringTemplate(`3:Nor how it may concern my modesty, 5:But I beseech your grace that I may know - 6:The worst that may befall me in this case,`) + 6:The worst that may befall me in this case,`), ); }); - xit('One file, several matches, match entire lines flag', () => { + xtest('One file, several matches, match entire lines flag', () => { return expect( spawnGrep({ pattern: 'may', flags: ['-x'], files: ['midsummer-night.txt'], - }) + }), ).resolves.toBe(''); }); - xit('One file, several matches, case-insensitive flag', () => { + xtest('One file, several matches, case-insensitive flag', () => { return expect( spawnGrep({ pattern: 'ACHILLES', flags: ['-i'], files: ['iliad.txt'], - }) + }), ).resolves.toBe( formatStringTemplate(`Achilles sing, O Goddess! Peleus' son; - The noble Chief Achilles from the son`) + The noble Chief Achilles from the son`), ); }); - xit('One file, several matches, inverted flag', () => { + xtest('One file, several matches, inverted flag', () => { return expect( spawnGrep({ pattern: 'Of', flags: ['-v'], files: ['paradise-lost.txt'], - }) + }), ).resolves.toBe( formatStringTemplate(`Brought Death into the World, and all our woe, With loss of Eden, till one greater Man Restore us, and regain the blissful Seat, Sing Heav'nly Muse, that on the secret top - That Shepherd, who first taught the chosen Seed`) + That Shepherd, who first taught the chosen Seed`), ); }); - xit('One file, no matches, various flags', () => { + xtest('One file, no matches, various flags', () => { return expect( spawnGrep({ pattern: 'Gandalf', flags: ['-n', '-l', '-x', '-i'], files: ['iliad.txt'], - }) + }), ).resolves.toBe(''); }); - xit('One file, one match, file flag takes precedence over line flag', () => { + xtest('One file, one match, file flag takes precedence over line flag', () => { return expect( spawnGrep({ pattern: 'ten', flags: ['-n', '-l'], files: ['iliad.txt'], - }) + }), ).resolves.toBe(resolveDataFile('iliad.txt')); }); - xit('One file, several matches, inverted and match entire lines flags', () => { + xtest('One file, several matches, inverted and match entire lines flags', () => { return expect( spawnGrep({ pattern: 'Illustrious into Ades premature,', flags: ['-x', '-v'], files: ['iliad.txt'], - }) + }), ).resolves.toBe( formatStringTemplate(`Achilles sing, O Goddess! Peleus' son; His wrath pernicious, who ten thousand woes @@ -223,255 +225,255 @@ describe('grep exercise', () => { To dogs and to all ravening fowls a prey, When fierce dispute had separated once The noble Chief Achilles from the son - Of Atreus, Agamemnon, King of men.`) + Of Atreus, Agamemnon, King of men.`), ); }); }); describe('Test grepping multiples files at once', () => { - xit('Multiple files, one match, no flags', () => { + xtest('Multiple files, one match, no flags', () => { return expect( spawnGrep({ pattern: 'Agamemnon', flags: [], files: ['iliad.txt', 'midsummer-night.txt', 'paradise-lost.txt'], - }) + }), ).resolves.toBe( - `${resolveDataFile('iliad.txt')}:Of Atreus, Agamemnon, King of men.` + `${resolveDataFile('iliad.txt')}:Of Atreus, Agamemnon, King of men.`, ); }); - xit('Multiple files, several matches, no flags', () => { + xtest('Multiple files, several matches, no flags', () => { return expect( spawnGrep({ pattern: 'may', flags: [], files: ['iliad.txt', 'midsummer-night.txt', 'paradise-lost.txt'], - }) + }), ).resolves.toBe( formatStringTemplate(`${resolveDataFile( - 'midsummer-night.txt' + 'midsummer-night.txt', )}:Nor how it may concern my modesty, ${resolveDataFile( - 'midsummer-night.txt' + 'midsummer-night.txt', )}:But I beseech your grace that I may know ${resolveDataFile( - 'midsummer-night.txt' - )}:The worst that may befall me in this case,`) + 'midsummer-night.txt', + )}:The worst that may befall me in this case,`), ); }); - xit('Multiple files, several matches, print line numbers flag', () => { + xtest('Multiple files, several matches, print line numbers flag', () => { return expect( spawnGrep({ pattern: 'that', flags: ['-n'], files: ['iliad.txt', 'midsummer-night.txt', 'paradise-lost.txt'], - }) + }), ).resolves.toBe( formatStringTemplate(`${resolveDataFile( - 'midsummer-night.txt' + 'midsummer-night.txt', )}:5:But I beseech your grace that I may know ${resolveDataFile( - 'midsummer-night.txt' + 'midsummer-night.txt', )}:6:The worst that may befall me in this case, ${resolveDataFile( - 'paradise-lost.txt' + 'paradise-lost.txt', )}:2:Of that Forbidden Tree, whose mortal tast ${resolveDataFile( - 'paradise-lost.txt' - )}:6:Sing Heav'nly Muse, that on the secret top`) + 'paradise-lost.txt', + )}:6:Sing Heav'nly Muse, that on the secret top`), ); }); - it('Multiple files, one match, print file names flag', () => { + xtest('Multiple files, one match, print file names flag', () => { return expect( spawnGrep({ pattern: 'who', flags: ['-l'], files: ['iliad.txt', 'midsummer-night.txt', 'paradise-lost.txt'], - }) + }), ).resolves.toBe( formatStringTemplate(`${resolveDataFile('iliad.txt')} - ${resolveDataFile('paradise-lost.txt')}`) + ${resolveDataFile('paradise-lost.txt')}`), ); }); - xit('Multiple files, several matches, case-insensitive flag', () => { + xtest('Multiple files, several matches, case-insensitive flag', () => { return expect( spawnGrep({ pattern: 'TO', flags: ['-i'], files: ['iliad.txt', 'midsummer-night.txt', 'paradise-lost.txt'], - }) + }), ).resolves.toBe( formatStringTemplate(`${resolveDataFile( - 'iliad.txt' + 'iliad.txt', )}:Caused to Achaia's host, sent many a soul ${resolveDataFile('iliad.txt')}:Illustrious into Ades premature, ${resolveDataFile( - 'iliad.txt' + 'iliad.txt', )}:And Heroes gave (so stood the will of Jove) ${resolveDataFile( - 'iliad.txt' + 'iliad.txt', )}:To dogs and to all ravening fowls a prey, ${resolveDataFile( - 'midsummer-night.txt' + 'midsummer-night.txt', )}:I do entreat your grace to pardon me. ${resolveDataFile( - 'midsummer-night.txt' + 'midsummer-night.txt', )}:In such a presence here to plead my thoughts; ${resolveDataFile('midsummer-night.txt')}:If I refuse to wed Demetrius. ${resolveDataFile( - 'paradise-lost.txt' + 'paradise-lost.txt', )}:Brought Death into the World, and all our woe, ${resolveDataFile( - 'paradise-lost.txt' + 'paradise-lost.txt', )}:Restore us, and regain the blissful Seat, ${resolveDataFile( - 'paradise-lost.txt' - )}:Sing Heav'nly Muse, that on the secret top`) + 'paradise-lost.txt', + )}:Sing Heav'nly Muse, that on the secret top`), ); }); - xit('Multiple files, several matches, inverted flag', () => { + xtest('Multiple files, several matches, inverted flag', () => { return expect( spawnGrep({ pattern: 'a', flags: ['-v'], files: ['iliad.txt', 'midsummer-night.txt', 'paradise-lost.txt'], - }) + }), ).resolves.toBe( formatStringTemplate(`${resolveDataFile( - 'iliad.txt' + 'iliad.txt', )}:Achilles sing, O Goddess! Peleus' son; ${resolveDataFile('iliad.txt')}:The noble Chief Achilles from the son ${resolveDataFile( - 'midsummer-night.txt' - )}:If I refuse to wed Demetrius.`) + 'midsummer-night.txt', + )}:If I refuse to wed Demetrius.`), ); }); - xit('Multiple files, one match, match entire lines flag', () => { + xtest('Multiple files, one match, match entire lines flag', () => { return expect( spawnGrep({ pattern: 'But I beseech your grace that I may know', flags: ['-x'], files: ['iliad.txt', 'midsummer-night.txt', 'paradise-lost.txt'], - }) + }), ).resolves.toBe( `${resolveDataFile( - 'midsummer-night.txt:But I beseech your grace that I may know' - )}` + 'midsummer-night.txt:But I beseech your grace that I may know', + )}`, ); }); - xit('Multiple files, one match, multiple flags', () => { + xtest('Multiple files, one match, multiple flags', () => { return expect( spawnGrep({ pattern: 'WITH LOSS OF EDEN, TILL ONE GREATER MAN', flags: ['-n', '-i', '-x'], files: ['iliad.txt', 'midsummer-night.txt', 'paradise-lost.txt'], - }) + }), ).resolves.toBe( `${resolveDataFile( - 'paradise-lost.txt' - )}:4:With loss of Eden, till one greater Man` + 'paradise-lost.txt', + )}:4:With loss of Eden, till one greater Man`, ); }); - xit('Multiple files, no matches, various flags', () => { + xtest('Multiple files, no matches, various flags', () => { return expect( spawnGrep({ pattern: 'Frodo', flags: ['-n', '-l', '-x', '-i'], files: ['iliad.txt', 'midsummer-night.txt', 'paradise-lost.txt'], - }) + }), ).resolves.toBe(''); }); - xit('Multiple files, several matches, file flag takes precedence over line number flag', () => { + xtest('Multiple files, several matches, file flag takes precedence over line number flag', () => { return expect( spawnGrep({ pattern: 'who', flags: ['-n', '-l'], files: ['iliad.txt', 'midsummer-night.txt', 'paradise-lost.txt'], - }) + }), ).resolves.toBe( formatStringTemplate(`${resolveDataFile('iliad.txt')} - ${resolveDataFile('paradise-lost.txt')}`) + ${resolveDataFile('paradise-lost.txt')}`), ); }); - xit('Multiple files, several matches, inverted and match entire lines flags', () => { + xtest('Multiple files, several matches, inverted and match entire lines flags', () => { return expect( spawnGrep({ pattern: 'Illustrious into Ades premature,', flags: ['-x', '-v'], files: ['iliad.txt', 'midsummer-night.txt', 'paradise-lost.txt'], - }) + }), ).resolves.toBe( formatStringTemplate(`${resolveDataFile( - 'iliad.txt' + 'iliad.txt', )}:Achilles sing, O Goddess! Peleus' son; ${resolveDataFile( - 'iliad.txt' + 'iliad.txt', )}:His wrath pernicious, who ten thousand woes ${resolveDataFile( - 'iliad.txt' + 'iliad.txt', )}:Caused to Achaia's host, sent many a soul ${resolveDataFile( - 'iliad.txt' + 'iliad.txt', )}:And Heroes gave (so stood the will of Jove) ${resolveDataFile( - 'iliad.txt' + 'iliad.txt', )}:To dogs and to all ravening fowls a prey, ${resolveDataFile('iliad.txt')}:When fierce dispute had separated once ${resolveDataFile('iliad.txt')}:The noble Chief Achilles from the son ${resolveDataFile('iliad.txt')}:Of Atreus, Agamemnon, King of men. ${resolveDataFile( - 'midsummer-night.txt' + 'midsummer-night.txt', )}:I do entreat your grace to pardon me. ${resolveDataFile( - 'midsummer-night.txt' + 'midsummer-night.txt', )}:I know not by what power I am made bold, ${resolveDataFile( - 'midsummer-night.txt' + 'midsummer-night.txt', )}:Nor how it may concern my modesty, ${resolveDataFile( - 'midsummer-night.txt' + 'midsummer-night.txt', )}:In such a presence here to plead my thoughts; ${resolveDataFile( - 'midsummer-night.txt' + 'midsummer-night.txt', )}:But I beseech your grace that I may know ${resolveDataFile( - 'midsummer-night.txt' + 'midsummer-night.txt', )}:The worst that may befall me in this case, ${resolveDataFile('midsummer-night.txt')}:If I refuse to wed Demetrius. ${resolveDataFile( - 'paradise-lost.txt' + 'paradise-lost.txt', )}:Of Mans First Disobedience, and the Fruit ${resolveDataFile( - 'paradise-lost.txt' + 'paradise-lost.txt', )}:Of that Forbidden Tree, whose mortal tast ${resolveDataFile( - 'paradise-lost.txt' + 'paradise-lost.txt', )}:Brought Death into the World, and all our woe, ${resolveDataFile( - 'paradise-lost.txt' + 'paradise-lost.txt', )}:With loss of Eden, till one greater Man ${resolveDataFile( - 'paradise-lost.txt' + 'paradise-lost.txt', )}:Restore us, and regain the blissful Seat, ${resolveDataFile( - 'paradise-lost.txt' + 'paradise-lost.txt', )}:Sing Heav'nly Muse, that on the secret top ${resolveDataFile( - 'paradise-lost.txt' + 'paradise-lost.txt', )}:Of Oreb, or of Sinai, didst inspire ${resolveDataFile( - 'paradise-lost.txt' - )}:That Shepherd, who first taught the chosen Seed`) + 'paradise-lost.txt', + )}:That Shepherd, who first taught the chosen Seed`), ); }); }); diff --git a/exercises/practice/grep/jest.config.js b/exercises/practice/grep/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/grep/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/grep/package.json b/exercises/practice/grep/package.json index 3f52ca6730..ca9d03a928 100644 --- a/exercises/practice/grep/package.json +++ b/exercises/practice/grep/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/grep" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/hamming/.docs/instructions.md b/exercises/practice/hamming/.docs/instructions.md index 56c5696de1..8f47a179e0 100644 --- a/exercises/practice/hamming/.docs/instructions.md +++ b/exercises/practice/hamming/.docs/instructions.md @@ -1,24 +1,16 @@ # Instructions -Calculate the Hamming Distance between two DNA strands. +Calculate the Hamming distance between two DNA strands. -Your body is made up of cells that contain DNA. Those cells regularly wear out and need replacing, which they achieve by dividing into daughter cells. In fact, the average human body experiences about 10 quadrillion cell divisions in a lifetime! - -When cells divide, their DNA replicates too. Sometimes during this process mistakes happen and single pieces of DNA get encoded with the incorrect information. If we compare two strands of DNA and count the differences between them we can see how many mistakes occurred. This is known as the "Hamming Distance". - -We read DNA using the letters C,A,G and T. Two strands might look like this: +We read DNA using the letters C, A, G and T. +Two strands might look like this: GAGCCTACTAACGGGAT CATCGTAATGACGGCCT ^ ^ ^ ^ ^ ^^ -They have 7 differences, and therefore the Hamming Distance is 7. - -The Hamming Distance is useful for lots of things in science, not just biology, so it's a nice phrase to be familiar with :) +They have 7 differences, and therefore the Hamming distance is 7. -# Implementation notes +## Implementation notes -The Hamming distance is only defined for sequences of equal length, so -an attempt to calculate it between sequences of different lengths should -not work. The general handling of this situation (e.g., raising an -exception vs returning a special value) may differ between languages. +The Hamming distance is only defined for sequences of equal length, so an attempt to calculate it between sequences of different lengths should not work. diff --git a/exercises/practice/hamming/.docs/introduction.md b/exercises/practice/hamming/.docs/introduction.md new file mode 100644 index 0000000000..8419bf479e --- /dev/null +++ b/exercises/practice/hamming/.docs/introduction.md @@ -0,0 +1,12 @@ +# Introduction + +Your body is made up of cells that contain DNA. +Those cells regularly wear out and need replacing, which they achieve by dividing into daughter cells. +In fact, the average human body experiences about 10 quadrillion cell divisions in a lifetime! + +When cells divide, their DNA replicates too. +Sometimes during this process mistakes happen and single pieces of DNA get encoded with the incorrect information. +If we compare two strands of DNA and count the differences between them, we can see how many mistakes occurred. +This is known as the "Hamming distance". + +The Hamming distance is useful in many areas of science, not just biology, so it's a nice phrase to be familiar with :) diff --git a/exercises/practice/hamming/.eslintrc b/exercises/practice/hamming/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/hamming/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/hamming/.gitignore b/exercises/practice/hamming/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/hamming/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/hamming/.meta/config.json b/exercises/practice/hamming/.meta/config.json index e7332f13d1..9bd49695d5 100644 --- a/exercises/practice/hamming/.meta/config.json +++ b/exercises/practice/hamming/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Calculate the Hamming difference between two DNA strands.", - "authors": ["rchavarria"], + "authors": [ + "rchavarria" + ], "contributors": [ "ankorGH", "draalger", @@ -13,10 +14,23 @@ "tejasbubane" ], "files": { - "solution": ["hamming.js"], - "test": ["hamming.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "hamming.js" + ], + "test": [ + "hamming.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Calculate the Hamming distance between two DNA strands.", "source": "The Calculating Point Mutations problem at Rosalind", - "source_url": "http://rosalind.info/problems/hamm/" + "source_url": "https://rosalind.info/problems/hamm/", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/hamming/babel.config.js b/exercises/practice/hamming/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/hamming/babel.config.js +++ b/exercises/practice/hamming/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/hamming/eslint.config.mjs b/exercises/practice/hamming/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/hamming/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/hamming/hamming.js b/exercises/practice/hamming/hamming.js index 7eb271aad3..d4c04aa31f 100644 --- a/exercises/practice/hamming/hamming.js +++ b/exercises/practice/hamming/hamming.js @@ -4,5 +4,5 @@ // export const compute = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/hamming/hamming.spec.js b/exercises/practice/hamming/hamming.spec.js index b6218cfaf4..f8c083dca9 100644 --- a/exercises/practice/hamming/hamming.spec.js +++ b/exercises/practice/hamming/hamming.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { compute } from './hamming'; describe('Hamming', () => { @@ -23,25 +24,25 @@ describe('Hamming', () => { xtest('disallow first strand longer', () => { expect(() => compute('AATG', 'AAA')).toThrow( - new Error('strands must be of equal length') + new Error('strands must be of equal length'), ); }); xtest('disallow second strand longer', () => { expect(() => compute('ATA', 'AGTG')).toThrow( - new Error('strands must be of equal length') + new Error('strands must be of equal length'), ); }); xtest('disallow empty first strand', () => { expect(() => compute('', 'G')).toThrow( - new Error('strands must be of equal length') + new Error('strands must be of equal length'), ); }); xtest('disallow empty second strand', () => { expect(() => compute('G', '')).toThrow( - new Error('strands must be of equal length') + new Error('strands must be of equal length'), ); }); }); diff --git a/exercises/practice/hamming/jest.config.js b/exercises/practice/hamming/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/hamming/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/hamming/package.json b/exercises/practice/hamming/package.json index a67083d2d1..ef7baff27d 100644 --- a/exercises/practice/hamming/package.json +++ b/exercises/practice/hamming/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/hamming" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/hello-world/.docs/instructions.md b/exercises/practice/hello-world/.docs/instructions.md index 6e08ebba53..c9570e48a9 100644 --- a/exercises/practice/hello-world/.docs/instructions.md +++ b/exercises/practice/hello-world/.docs/instructions.md @@ -1,15 +1,16 @@ # Instructions -The classical introductory exercise. Just say "Hello, World!". +The classical introductory exercise. +Just say "Hello, World!". -["Hello, World!"](http://en.wikipedia.org/wiki/%22Hello,_world!%22_program) is -the traditional first program for beginning programming in a new language -or environment. +["Hello, World!"][hello-world] is the traditional first program for beginning programming in a new language or environment. The objectives are simple: -- Write a function that returns the string "Hello, World!". +- Modify the provided code so that it produces the string "Hello, World!". - Run the test suite and make sure that it succeeds. - Submit your solution and check it at the website. If everything goes well, you will be ready to fetch your first real exercise. + +[hello-world]: https://en.wikipedia.org/wiki/%22Hello,_world!%22_program diff --git a/exercises/practice/hello-world/.eslintrc b/exercises/practice/hello-world/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/hello-world/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/hello-world/.gitignore b/exercises/practice/hello-world/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/hello-world/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/hello-world/.meta/config.json b/exercises/practice/hello-world/.meta/config.json index abbe766b3b..43ba0acb67 100644 --- a/exercises/practice/hello-world/.meta/config.json +++ b/exercises/practice/hello-world/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "The classical introductory exercise. Just say \"Hello, World!\"", - "authors": ["rchavarria"], + "authors": [ + "rchavarria" + ], "contributors": [ "austinratcliff", "designfrontier", @@ -12,10 +13,23 @@ "tejasbubane" ], "files": { - "solution": ["hello-world.js"], - "test": ["hello-world.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "hello-world.js" + ], + "test": [ + "hello-world.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Exercism's classic introductory exercise. Just say \"Hello, World!\".", "source": "This is an exercise to introduce users to using Exercism", - "source_url": "http://en.wikipedia.org/wiki/%22Hello,_world!%22_program" + "source_url": "https://en.wikipedia.org/wiki/%22Hello,_world!%22_program", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/hello-world/babel.config.js b/exercises/practice/hello-world/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/hello-world/babel.config.js +++ b/exercises/practice/hello-world/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/hello-world/eslint.config.mjs b/exercises/practice/hello-world/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/hello-world/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/hello-world/hello-world.spec.js b/exercises/practice/hello-world/hello-world.spec.js index cd0b7ec1c0..1e8de8d78d 100644 --- a/exercises/practice/hello-world/hello-world.spec.js +++ b/exercises/practice/hello-world/hello-world.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test } from '@jest/globals'; import { hello } from './hello-world'; describe('Hello World', () => { diff --git a/exercises/practice/hello-world/jest.config.js b/exercises/practice/hello-world/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/hello-world/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/hello-world/package.json b/exercises/practice/hello-world/package.json index a00bb295c7..63032d3b05 100644 --- a/exercises/practice/hello-world/package.json +++ b/exercises/practice/hello-world/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/hello-world" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/hexadecimal/.eslintrc b/exercises/practice/hexadecimal/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/hexadecimal/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/hexadecimal/.gitignore b/exercises/practice/hexadecimal/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/hexadecimal/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/hexadecimal/.meta/config.json b/exercises/practice/hexadecimal/.meta/config.json index e349ef1d8b..7c4acb1d01 100644 --- a/exercises/practice/hexadecimal/.meta/config.json +++ b/exercises/practice/hexadecimal/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Convert a hexadecimal number, represented as a string (e.g. \"10af8c\"), to its decimal equivalent using first principles (i.e. no, you may not use built-in or external libraries to accomplish the conversion).", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "ankorGH", "rchavarria", @@ -10,10 +11,23 @@ "xarxziux" ], "files": { - "solution": ["hexadecimal.js"], - "test": ["hexadecimal.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "hexadecimal.js" + ], + "test": [ + "hexadecimal.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Convert a hexadecimal number, represented as a string (e.g. \"10af8c\"), to its decimal equivalent using first principles (i.e. no, you may not use built-in or external libraries to accomplish the conversion).", "source": "All of Computer Science", - "source_url": "http://www.wolframalpha.com/examples/NumberBases.html" + "source_url": "http://www.wolframalpha.com/examples/NumberBases.html", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/hexadecimal/babel.config.js b/exercises/practice/hexadecimal/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/hexadecimal/babel.config.js +++ b/exercises/practice/hexadecimal/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/hexadecimal/eslint.config.mjs b/exercises/practice/hexadecimal/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/hexadecimal/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/hexadecimal/hexadecimal.js b/exercises/practice/hexadecimal/hexadecimal.js index 36bec68d75..a0d5e967aa 100644 --- a/exercises/practice/hexadecimal/hexadecimal.js +++ b/exercises/practice/hexadecimal/hexadecimal.js @@ -4,5 +4,5 @@ // export const toDecimal = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/hexadecimal/hexadecimal.spec.js b/exercises/practice/hexadecimal/hexadecimal.spec.js index aacde8aba7..80556433dd 100644 --- a/exercises/practice/hexadecimal/hexadecimal.spec.js +++ b/exercises/practice/hexadecimal/hexadecimal.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { toDecimal } from './hexadecimal'; describe('Hexadecimal', () => { diff --git a/exercises/practice/hexadecimal/jest.config.js b/exercises/practice/hexadecimal/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/hexadecimal/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/hexadecimal/package.json b/exercises/practice/hexadecimal/package.json index 96d9afd6fa..f39d2357cd 100644 --- a/exercises/practice/hexadecimal/package.json +++ b/exercises/practice/hexadecimal/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/hexadecimal" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/high-scores/.docs/instructions.md b/exercises/practice/high-scores/.docs/instructions.md index 1f8154d5f5..55802488c7 100644 --- a/exercises/practice/high-scores/.docs/instructions.md +++ b/exercises/practice/high-scores/.docs/instructions.md @@ -2,4 +2,5 @@ Manage a game player's High Score list. -Your task is to build a high-score component of the classic Frogger game, one of the highest selling and addictive games of all time, and a classic of the arcade era. Your task is to write methods that return the highest score from the list, the last added score and the three highest scores. +Your task is to build a high-score component of the classic Frogger game, one of the highest selling and most addictive games of all time, and a classic of the arcade era. +Your task is to write methods that return the highest score from the list, the last added score and the three highest scores. diff --git a/exercises/practice/high-scores/.eslintrc b/exercises/practice/high-scores/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/high-scores/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/high-scores/.gitignore b/exercises/practice/high-scores/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/high-scores/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/high-scores/.meta/config.json b/exercises/practice/high-scores/.meta/config.json index a3771c465a..c328c18b91 100644 --- a/exercises/practice/high-scores/.meta/config.json +++ b/exercises/practice/high-scores/.meta/config.json @@ -1,17 +1,32 @@ { - "blurb": "Manage a player's High Score list", - "authors": ["PakkuDon"], + "authors": [ + "PakkuDon" + ], "contributors": [ "ankorGH", "cmccandless", "ffflorian", "hayashi-ay", + "jagdish-15", "SleeplessByte" ], "files": { - "solution": ["high-scores.js"], - "test": ["high-scores.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "high-scores.js" + ], + "test": [ + "high-scores.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, - "source": "Tribute to the eighties' arcade game Frogger" + "blurb": "Manage a player's High Score list.", + "source": "Tribute to the eighties' arcade game Frogger", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/high-scores/.meta/tests.toml b/exercises/practice/high-scores/.meta/tests.toml index 4008e01258..7c94633801 100644 --- a/exercises/practice/high-scores/.meta/tests.toml +++ b/exercises/practice/high-scores/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [1035eb93-2208-4c22-bab8-fef06769a73c] description = "List of scores" @@ -12,16 +19,28 @@ description = "Latest score" description = "Personal best" [3d996a97-c81c-4642-9afc-80b80dc14015] -description = "Personal top three from a list of scores" +description = "Top 3 scores -> Personal top three from a list of scores" [1084ecb5-3eb4-46fe-a816-e40331a4e83a] -description = "Personal top highest to lowest" +description = "Top 3 scores -> Personal top highest to lowest" [e6465b6b-5a11-4936-bfe3-35241c4f4f16] -description = "Personal top when there is a tie" +description = "Top 3 scores -> Personal top when there is a tie" [f73b02af-c8fd-41c9-91b9-c86eaa86bce2] -description = "Personal top when there are less than 3" +description = "Top 3 scores -> Personal top when there are less than 3" [16608eae-f60f-4a88-800e-aabce5df2865] -description = "Personal top when there is only one" +description = "Top 3 scores -> Personal top when there is only one" + +[2df075f9-fec9-4756-8f40-98c52a11504f] +description = "Top 3 scores -> Latest score after personal top scores" + +[809c4058-7eb1-4206-b01e-79238b9b71bc] +description = "Top 3 scores -> Scores after personal top scores" + +[ddb0efc0-9a86-4f82-bc30-21ae0bdc6418] +description = "Top 3 scores -> Latest score after personal best" + +[6a0fd2d1-4cc4-46b9-a5bb-2fb667ca2364] +description = "Top 3 scores -> Scores after personal best" diff --git a/exercises/practice/high-scores/babel.config.js b/exercises/practice/high-scores/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/high-scores/babel.config.js +++ b/exercises/practice/high-scores/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/high-scores/eslint.config.mjs b/exercises/practice/high-scores/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/high-scores/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/high-scores/high-scores.js b/exercises/practice/high-scores/high-scores.js index 19084d6714..47b216792a 100644 --- a/exercises/practice/high-scores/high-scores.js +++ b/exercises/practice/high-scores/high-scores.js @@ -5,22 +5,22 @@ export class HighScores { constructor() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get scores() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get latest() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get personalBest() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get personalTopThree() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/high-scores/high-scores.spec.js b/exercises/practice/high-scores/high-scores.spec.js index 301c86fea8..682a8e9778 100644 --- a/exercises/practice/high-scores/high-scores.spec.js +++ b/exercises/practice/high-scores/high-scores.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { HighScores } from './high-scores'; describe('High Scores Test Suite', () => { @@ -41,5 +42,33 @@ describe('High Scores Test Suite', () => { const input = [40]; expect(new HighScores(input).personalTopThree).toEqual([40]); }); + + xtest('Latest score after personal top scores', () => { + const input = [70, 50, 20, 30]; + const highScores = new HighScores(input); + highScores.personalTopThree; + expect(highScores.latest).toEqual(30); + }); + + xtest('Scores after personal top scores', () => { + const input = [30, 50, 20, 70]; + const highScores = new HighScores(input); + highScores.personalTopThree; + expect(highScores.scores).toEqual(input); + }); + + xtest('Latest score after personal best', () => { + const input = [20, 70, 15, 25, 30]; + const highScores = new HighScores(input); + highScores.personalBest; + expect(highScores.latest).toEqual(30); + }); + + xtest('Scores after personal best', () => { + const input = [20, 70, 15, 25, 30]; + const highScores = new HighScores(input); + highScores.personalBest; + expect(highScores.scores).toEqual(input); + }); }); }); diff --git a/exercises/practice/high-scores/jest.config.js b/exercises/practice/high-scores/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/high-scores/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/high-scores/package.json b/exercises/practice/high-scores/package.json index a6652d5bcf..37a08291d4 100644 --- a/exercises/practice/high-scores/package.json +++ b/exercises/practice/high-scores/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/high-scores" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/house/.docs/instructions.md b/exercises/practice/house/.docs/instructions.md index 92174617f3..88928c5fa2 100644 --- a/exercises/practice/house/.docs/instructions.md +++ b/exercises/practice/house/.docs/instructions.md @@ -2,14 +2,11 @@ Recite the nursery rhyme 'This is the House that Jack Built'. -> [The] process of placing a phrase of clause within another phrase of -> clause is called embedding. It is through the processes of recursion -> and embedding that we are able to take a finite number of forms (words -> and phrases) and construct an infinite number of expressions. -> Furthermore, embedding also allows us to construct an infinitely long -> structure, in theory anyway. +> [The] process of placing a phrase of clause within another phrase of clause is called embedding. +> It is through the processes of recursion and embedding that we are able to take a finite number of forms (words and phrases) and construct an infinite number of expressions. +> Furthermore, embedding also allows us to construct an infinitely long structure, in theory anyway. -- [papyr.com](http://papyr.com/hypertextbooks/grammar/ph_noun.htm) +- [papyr.com][papyr] The nursery rhyme reads as follows: @@ -104,3 +101,5 @@ that killed the rat that ate the malt that lay in the house that Jack built. ``` + +[papyr]: https://papyr.com/hypertextbooks/grammar/ph_noun.htm diff --git a/exercises/practice/house/.eslintrc b/exercises/practice/house/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/house/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/house/.gitignore b/exercises/practice/house/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/house/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/house/.meta/config.json b/exercises/practice/house/.meta/config.json index 7e55d24005..60ca2244d7 100644 --- a/exercises/practice/house/.meta/config.json +++ b/exercises/practice/house/.meta/config.json @@ -1,12 +1,31 @@ { - "blurb": "Output the nursery rhyme 'This is the House that Jack Built'.", - "authors": ["laurmurclar"], - "contributors": ["ankorGH", "rchavarria", "SleeplessByte", "tejasbubane"], + "authors": [ + "laurmurclar" + ], + "contributors": [ + "ankorGH", + "rchavarria", + "SleeplessByte", + "tejasbubane" + ], "files": { - "solution": ["house.js"], - "test": ["house.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "house.js" + ], + "test": [ + "house.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Output the nursery rhyme 'This is the House that Jack Built'.", "source": "British nursery rhyme", - "source_url": "http://en.wikipedia.org/wiki/This_Is_The_House_That_Jack_Built" + "source_url": "https://en.wikipedia.org/wiki/This_Is_The_House_That_Jack_Built", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/house/babel.config.js b/exercises/practice/house/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/house/babel.config.js +++ b/exercises/practice/house/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/house/eslint.config.mjs b/exercises/practice/house/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/house/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/house/house.js b/exercises/practice/house/house.js index 989997901f..ee4e6bf996 100644 --- a/exercises/practice/house/house.js +++ b/exercises/practice/house/house.js @@ -5,10 +5,10 @@ export class House { static verse() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } static verses() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/house/house.spec.js b/exercises/practice/house/house.spec.js index 496ffa1b59..b720dc807b 100644 --- a/exercises/practice/house/house.spec.js +++ b/exercises/practice/house/house.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { House } from './house'; describe('House', () => { diff --git a/exercises/practice/house/jest.config.js b/exercises/practice/house/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/house/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/house/package.json b/exercises/practice/house/package.json index 2911fa4457..be5b333820 100644 --- a/exercises/practice/house/package.json +++ b/exercises/practice/house/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/house" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/isbn-verifier/.docs/instructions.md b/exercises/practice/isbn-verifier/.docs/instructions.md index 8c2e40196d..4a0244e552 100644 --- a/exercises/practice/isbn-verifier/.docs/instructions.md +++ b/exercises/practice/isbn-verifier/.docs/instructions.md @@ -1,23 +1,26 @@ # Instructions -The [ISBN-10 verification process](https://en.wikipedia.org/wiki/International_Standard_Book_Number) is used to validate book identification -numbers. These normally contain dashes and look like: `3-598-21508-8` +The [ISBN-10 verification process][isbn-verification] is used to validate book identification numbers. +These normally contain dashes and look like: `3-598-21508-8` ## ISBN -The ISBN-10 format is 9 digits (0 to 9) plus one check character (either a digit or an X only). In the case the check character is an X, this represents the value '10'. These may be communicated with or without hyphens, and can be checked for their validity by the following formula: +The ISBN-10 format is 9 digits (0 to 9) plus one check character (either a digit or an X only). +In the case the check character is an X, this represents the value '10'. +These may be communicated with or without hyphens, and can be checked for their validity by the following formula: -``` -(x1 * 10 + x2 * 9 + x3 * 8 + x4 * 7 + x5 * 6 + x6 * 5 + x7 * 4 + x8 * 3 + x9 * 2 + x10 * 1) mod 11 == 0 +```text +(d₁ * 10 + d₂ * 9 + d₃ * 8 + d₄ * 7 + d₅ * 6 + d₆ * 5 + d₇ * 4 + d₈ * 3 + d₉ * 2 + d₁₀ * 1) mod 11 == 0 ``` If the result is 0, then it is a valid ISBN-10, otherwise it is invalid. ## Example -Let's take the ISBN-10 `3-598-21508-8`. We plug it in to the formula, and get: +Let's take the ISBN-10 `3-598-21508-8`. +We plug it in to the formula, and get: -``` +```text (3 * 10 + 5 * 9 + 9 * 8 + 8 * 7 + 2 * 6 + 1 * 5 + 5 * 4 + 0 * 3 + 8 * 2 + 8 * 1) mod 11 == 0 ``` @@ -33,10 +36,7 @@ The program should be able to verify ISBN-10 both with and without separating da ## Caveats Converting from strings to numbers can be tricky in certain languages. -Now, it's even trickier since the check digit of an ISBN-10 may be 'X' (representing '10'). For instance `3-598-21507-X` is a valid ISBN-10. - -## Bonus tasks - -- Generate a valid ISBN-13 from the input ISBN-10 (and maybe verify it again with a derived verifier). +Now, it's even trickier since the check digit of an ISBN-10 may be 'X' (representing '10'). +For instance `3-598-21507-X` is a valid ISBN-10. -- Generate valid ISBN, maybe even from a given starting ISBN. +[isbn-verification]: https://en.wikipedia.org/wiki/International_Standard_Book_Number diff --git a/exercises/practice/isbn-verifier/.eslintrc b/exercises/practice/isbn-verifier/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/isbn-verifier/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/isbn-verifier/.gitignore b/exercises/practice/isbn-verifier/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/isbn-verifier/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/isbn-verifier/.meta/config.json b/exercises/practice/isbn-verifier/.meta/config.json index a8c6176363..0960e5869c 100644 --- a/exercises/practice/isbn-verifier/.meta/config.json +++ b/exercises/practice/isbn-verifier/.meta/config.json @@ -1,8 +1,10 @@ { - "blurb": "Check if a given string is a valid ISBN-10 number.", - "authors": ["MattH-be"], + "authors": [ + "MattH-be" + ], "contributors": [ "ankorGH", + "jagdish-15", "ovidiu141", "pyko", "SleeplessByte", @@ -10,10 +12,23 @@ "xarxziux" ], "files": { - "solution": ["isbn-verifier.js"], - "test": ["isbn-verifier.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "isbn-verifier.js" + ], + "test": [ + "isbn-verifier.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Check if a given string is a valid ISBN-10 number.", "source": "Converting a string into a number and some basic processing utilizing a relatable real world example.", - "source_url": "https://en.wikipedia.org/wiki/International_Standard_Book_Number#ISBN-10_check_digit_calculation" + "source_url": "https://en.wikipedia.org/wiki/International_Standard_Book_Number#ISBN-10_check_digit_calculation", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/isbn-verifier/.meta/tests.toml b/exercises/practice/isbn-verifier/.meta/tests.toml index 5d2c0c3fef..6d5a845990 100644 --- a/exercises/practice/isbn-verifier/.meta/tests.toml +++ b/exercises/practice/isbn-verifier/.meta/tests.toml @@ -1,21 +1,31 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [0caa3eac-d2e3-4c29-8df8-b188bc8c9292] -description = "valid isbn number" +description = "valid isbn" [19f76b53-7c24-45f8-87b8-4604d0ccd248] description = "invalid isbn check digit" [4164bfee-fb0a-4a1c-9f70-64c6a1903dcd] -description = "valid isbn number with a check digit of 10" +description = "valid isbn with a check digit of 10" [3ed50db1-8982-4423-a993-93174a20825c] description = "check digit is a character other than X" +[9416f4a5-fe01-4b61-a07b-eb75892ef562] +description = "invalid check digit in isbn is not treated as zero" + [c19ba0c4-014f-4dc3-a63f-ff9aefc9b5ec] -description = "invalid character in isbn" +description = "invalid character in isbn is not treated as zero" [28025280-2c39-4092-9719-f3234b89c627] description = "X is only valid as a check digit" @@ -48,7 +58,10 @@ description = "empty isbn" description = "input is 9 characters" [ed6e8d1b-382c-4081-8326-8b772c581fec] -description = "invalid characters are not ignored" +description = "invalid characters are not ignored after checking length" + +[daad3e58-ce00-4395-8a8e-e3eded1cdc86] +description = "invalid characters are not ignored before checking length" [fb5e48d8-7c03-4bfb-a088-b101df16fdc3] description = "input is too long but contains a valid isbn" diff --git a/exercises/practice/isbn-verifier/babel.config.js b/exercises/practice/isbn-verifier/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/isbn-verifier/babel.config.js +++ b/exercises/practice/isbn-verifier/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/isbn-verifier/eslint.config.mjs b/exercises/practice/isbn-verifier/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/isbn-verifier/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/isbn-verifier/isbn-verifier.js b/exercises/practice/isbn-verifier/isbn-verifier.js index 2f5877112c..f4f6f12a12 100644 --- a/exercises/practice/isbn-verifier/isbn-verifier.js +++ b/exercises/practice/isbn-verifier/isbn-verifier.js @@ -4,5 +4,5 @@ // export const isValid = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/isbn-verifier/isbn-verifier.spec.js b/exercises/practice/isbn-verifier/isbn-verifier.spec.js index 5232cf315d..6ff9dedbf8 100644 --- a/exercises/practice/isbn-verifier/isbn-verifier.spec.js +++ b/exercises/practice/isbn-verifier/isbn-verifier.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { isValid } from './isbn-verifier'; describe('ISBN Verifier', () => { @@ -9,7 +10,7 @@ describe('ISBN Verifier', () => { expect(isValid('3-598-21508-9')).toEqual(false); }); - xtest('valid isbn number with a check digit of 10', () => { + xtest('valid isbn with a check digit of 10', () => { expect(isValid('3-598-21507-X')).toEqual(true); }); @@ -17,7 +18,11 @@ describe('ISBN Verifier', () => { expect(isValid('3-598-21507-A')).toEqual(false); }); - xtest('invalid character in isbn', () => { + xtest('invalid check digit in isbn is not treated as zero', () => { + expect(isValid('4-598-21507-B')).toEqual(false); + }); + + xtest('invalid character in isbn is not treated as zero', () => { expect(isValid('3-598-P1581-X')).toEqual(false); }); @@ -61,10 +66,14 @@ describe('ISBN Verifier', () => { expect(isValid('134456729')).toEqual(false); }); - xtest('invalid characters are not ignored', () => { + xtest('invalid characters are not ignored after checking length', () => { expect(isValid('3132P34035')).toEqual(false); }); + xtest('invalid characters are not ignored before checking length', () => { + expect(isValid('3598P215088')).toEqual(false); + }); + xtest('input is too long but contains a valid isbn', () => { expect(isValid('98245726788')).toEqual(false); }); diff --git a/exercises/practice/isbn-verifier/jest.config.js b/exercises/practice/isbn-verifier/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/isbn-verifier/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/isbn-verifier/package.json b/exercises/practice/isbn-verifier/package.json index 867b9ffc7d..3ed9f52678 100644 --- a/exercises/practice/isbn-verifier/package.json +++ b/exercises/practice/isbn-verifier/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/isbn-verifier" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/isogram/.approaches/bitfield/content.md b/exercises/practice/isogram/.approaches/bitfield/content.md new file mode 100644 index 0000000000..b51d469b09 --- /dev/null +++ b/exercises/practice/isogram/.approaches/bitfield/content.md @@ -0,0 +1,70 @@ +# Bit field + +```javascript +const A_LCASE = 97; +const A_UCASE = 65; + +export function isIsogram(word) { + let letter_flags = 0; + + for (const letter of [...word]) { + if (letter >= 'a' && letter <= 'z') { + if ((letter_flags & (1 << (letter.charCodeAt(0) - A_LCASE))) != 0) + return false; + else letter_flags |= 1 << (letter.charCodeAt(0) - A_LCASE); + } else if (letter >= 'A' && letter <= 'Z') { + if ((letter_flags & (1 << (letter.charCodeAt(0) - A_UCASE))) != 0) + return false; + else letter_flags |= 1 << (letter.charCodeAt(0) - A_UCASE); + } + } + return true; +} +``` + +This solution uses the [ASCII][ascii] value of the letter to set the corresponding bit position. + +Some [const][const]ants are defined for readability in the function. +The ASCII value for `a` is `97`. +The ASCII value for `A` is `65`. + +- [Spread syntax][spread-syntax] is used to make an [`Array`][array] of the characters in the `word`. +- The `string` loops through its characters and looks for a character being `a` through `z` or `A` through `Z`. +- If a letter is found, then its ASCII value is taken by the [`charCodeAt`][charcodeat] method. + + +~~~~exercism/note +`charCodeAt` actually returns the UTF-16 code unit for the character, which is an integer between `0` and `65535`. +For the letters `a`-`z` and `A`-`Z`, the UTF-16 number is the same value as the ASCII value. +~~~~ + + +- If the lowercase letter is subtracted by `97`, then `a` will result in `0`, because `97` minus `97` equals `0`. + `z` would result in `25`, because `122` minus `97` equals `25`. + So `a` would have `1` [shifted left][shift-left] 0 places (so not shifted at all) and `z` would have `1` shifted left 25 places. +- If the uppercase letter is subtracted by `A`, then `A` will result in `0`, because `65` minus `65` equals `0`. + `Z` would result in `25`, because `90` minus `65` equals `25`. + So `A` would have `1` [shifted left][shift-left] 0 places (so not shifted at all) and `Z` would have `1` shifted left 25 places. + +In that way, both a lower-cased `z` and an upper-cased `Z` can share the same position in the bit field. + +So, for an unsigned thirty-two bit integer, if the values for `a` and `Z` were both set, the bits would look like + +``` + zyxwvutsrqponmlkjihgfedcba +00000010000000000000000000000001 +``` + +We can use the [bitwise AND operator][and] to check if a bit has already been set. +If it has been set, we know the letter is duplicated and we can immediately return `false`. +If it has not been set, we can use the [bitwise OR operator][or] to set the bit. +If the loop completes without finding a duplicate letter (and returning `false`), the function returns `true`. + +[ascii]: https://www.asciitable.com/ +[const]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const +[spread-syntax]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax +[array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array +[charcodeat]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charCodeAt +[shift-left]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Left_shift +[and]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_AND +[or]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_OR diff --git a/exercises/practice/isogram/.approaches/bitfield/snippet.txt b/exercises/practice/isogram/.approaches/bitfield/snippet.txt new file mode 100644 index 0000000000..79a94387af --- /dev/null +++ b/exercises/practice/isogram/.approaches/bitfield/snippet.txt @@ -0,0 +1,8 @@ +for (const letter of [...word]) { + if (letter >= "a" && letter <= "z") { + if ((letter_flags & (1 << (letter.charCodeAt(0) - A_LCASE))) != 0) + return false; + else letter_flags |= 1 << (letter.charCodeAt(0) - A_LCASE); + } + // code snipped +} diff --git a/exercises/practice/isogram/.approaches/config.json b/exercises/practice/isogram/.approaches/config.json new file mode 100644 index 0000000000..79a3e31493 --- /dev/null +++ b/exercises/practice/isogram/.approaches/config.json @@ -0,0 +1,36 @@ +{ + "introduction": { + "authors": [ + "bobahop" + ] + }, + "approaches": [ + { + "uuid": "ea821693-46a5-4f50-8f73-acd919346bdd", + "slug": "regex-match-dupe", + "title": "regex match dupe", + "blurb": "Use regex to match a duplicated letter.", + "authors": [ + "bobahop" + ] + }, + { + "uuid": "fbdf0f92-77d0-4b8b-be96-372ac7a4911b", + "slug": "filter-set", + "title": "filter with Set", + "blurb": "Use filter to populate a Set.", + "authors": [ + "bobahop" + ] + }, + { + "uuid": "1748fd8c-d72e-4cf5-a92d-dd63d64a83ec", + "slug": "bitfield", + "title": "Bit field", + "blurb": "Use a bit field to keep track of used letters.", + "authors": [ + "bobahop" + ] + } + ] +} diff --git a/exercises/practice/isogram/.approaches/filter-set/content.md b/exercises/practice/isogram/.approaches/filter-set/content.md new file mode 100644 index 0000000000..70d5fe1e94 --- /dev/null +++ b/exercises/practice/isogram/.approaches/filter-set/content.md @@ -0,0 +1,29 @@ +# `filter` with `Set` + +```javascript +export function isIsogram(string) { + let word = [...string.toLowerCase()].filter( + (letter) => letter >= 'a' && letter <= 'z', + ); + return new Set(word).size == word.length; +} +``` + +With this approach you will instantiate a [`Set`][set] of the used letters and compare its [`size`][size] with the filtered word [`length`][length]. + +- First, the [lowercased][tolowercase] input string is made into an [Array][array] of its characters using [spread syntax][spread-syntax]. +- The [`filter`][filter] method is then called on the `Array` to filter out any character that is not `a`-`z`. +- The letters that survive the filter are assigned to `word`. +- A `Set` is initialized from the letters in `word`. + The `size` of unique letters is compared with the `length` of the filtered word `length`. + +If the number of unique letters in the `Set` of used letters is the same as the number of filtered letters, +then the function returns true. + +[set]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set +[size]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/size +[length]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/length +[tolowercase]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toLowerCase +[array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array +[spread-syntax]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax +[filter]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter diff --git a/exercises/practice/isogram/.approaches/filter-set/snippet.txt b/exercises/practice/isogram/.approaches/filter-set/snippet.txt new file mode 100644 index 0000000000..c6e4432484 --- /dev/null +++ b/exercises/practice/isogram/.approaches/filter-set/snippet.txt @@ -0,0 +1,6 @@ +export function isIsogram(string) { + let word = [...string.toLowerCase()].filter( + (letter) => letter >= "a" && letter <= "z" + ); + return new Set(word).size == word.length; +} diff --git a/exercises/practice/isogram/.approaches/introduction.md b/exercises/practice/isogram/.approaches/introduction.md new file mode 100644 index 0000000000..a5aff7c1cc --- /dev/null +++ b/exercises/practice/isogram/.approaches/introduction.md @@ -0,0 +1,56 @@ +# Introduction + +There are various idiomatic ways to solve Isogram. +You can use a regular expression pattern to match a duplicated letter. +Or you could use the `filter` method to populate a `Set` and compare the `size` of the `Set` with the `length` of the original phrase. + +## General guidance + +The key to solving Isogram is to determine if any of the letters in the `string` being checked are repeated. +A repeated letter means the `string` is not an Isogram. +The occurrence of the letter `a` and the letter `A` count as a repeated letter, so `Alpha` would not be an isogram. + +## Approach: regular expression to match a duplicated letter + +```javascript +export function isIsogram(word) { + return !/([a-z]).*?\1/i.test(word); +} +``` + +For more information, check the [regex approach][approach-regex]. + +## Approach: `filter` with `Set` + +```javascript +export function isIsogram(string) { + let word = [...string.toLowerCase()].filter( + (letter) => letter >= 'a' && letter <= 'z', + ); + return new Set(word).size == word.length; +} +``` + +For more information, check the [`filter` with `Set` approach][approach-filter-set]. + +## Other approaches + +Besides the aforementioned, idiomatic approaches, you could also approach the exercise as follows: + +### Other approach: Bit field + +Another approach can use a bit field to keep track of used letters. +For more information, check the [Bit field approach][approach-bitfield]. + +## Which approach to use? + +Testing `"thumbscrew-Jappingly"` on [JSBench.me][jsbench-me]: + +- The bit field approach was fastest. +- The regular expression to match a duplicated letter approach was about 55% slower. +- The `filter` with `Set` approach was about 66% slower. + +[approach-regex]: https://exercism.org/tracks/javascript/exercises/isogram/approaches/regex-match-dupe +[approach-filter-set]: https://exercism.org/tracks/javascript/exercises/isogram/approaches/filter-set +[approach-bitfield]: https://exercism.org/tracks/javascript/exercises/isogram/approaches/bitfield +[jsbench-me]: https://jsbench.me/ diff --git a/exercises/practice/isogram/.approaches/regex-match-dupe/content.md b/exercises/practice/isogram/.approaches/regex-match-dupe/content.md new file mode 100644 index 0000000000..86d0ceb4e2 --- /dev/null +++ b/exercises/practice/isogram/.approaches/regex-match-dupe/content.md @@ -0,0 +1,33 @@ +# Regular expression to match a duplicate letter + +```javascript +export function isIsogram(word) { + return !/([a-z]).*?\1/i.test(word); +} +``` + +- This solution uses a [regular expression][regex] (also known as "regex') [`test`][test] method to see if there is a duplicated letter. +- The `[a-z]` [character class][char-class] looks for any `a`-`z` letter. +- The parentheses "capture" the letter and remember it in a [capturing group][group]. + +In the next part of the pattern `.*?`: + +- `.` looks for another character which is not a line terminator. +- `*` looks for zero or more of those characters. +- `?` looks for as few of those characters as possible, _until_ + +`\1` finds a repeat of the first captured character. +If no repeat is found for the first captured character, +then the regex tries again and matches the next `a`-`z` as the first captured character, and so on. + +The `i` at the end of the pattern means to [ignore case][ignore-case] when matching, so `[a-z]` will also find `[A-Z]`. + +The `!` at the beginning of the pattern is the [logical NOT][logical-not] operator. +The function returns `true` if the regex does _not_ find a repeated letter. + +[regex]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions +[test]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test +[char-class]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Character_Classes +[group]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Groups_and_Backreferences +[ignore-case]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/ignoreCase +[logical-not]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_NOT diff --git a/exercises/practice/isogram/.approaches/regex-match-dupe/snippet.txt b/exercises/practice/isogram/.approaches/regex-match-dupe/snippet.txt new file mode 100644 index 0000000000..08b4825bfb --- /dev/null +++ b/exercises/practice/isogram/.approaches/regex-match-dupe/snippet.txt @@ -0,0 +1,3 @@ +export function isIsogram(word) { + return !/([a-z]).*?\1/i.test(word); +} diff --git a/exercises/practice/isogram/.docs/instructions.md b/exercises/practice/isogram/.docs/instructions.md index 4eed59e585..2e8df851a9 100644 --- a/exercises/practice/isogram/.docs/instructions.md +++ b/exercises/practice/isogram/.docs/instructions.md @@ -2,7 +2,7 @@ Determine if a word or phrase is an isogram. -An isogram (also known as a "nonpattern word") is a word or phrase without a repeating letter, however spaces and hyphens are allowed to appear multiple times. +An isogram (also known as a "non-pattern word") is a word or phrase without a repeating letter, however spaces and hyphens are allowed to appear multiple times. Examples of isograms: diff --git a/exercises/practice/isogram/.eslintrc b/exercises/practice/isogram/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/isogram/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/isogram/.gitignore b/exercises/practice/isogram/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/isogram/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/isogram/.meta/config.json b/exercises/practice/isogram/.meta/config.json index a7c7f5ada6..486f11ad0b 100644 --- a/exercises/practice/isogram/.meta/config.json +++ b/exercises/practice/isogram/.meta/config.json @@ -1,9 +1,9 @@ { - "blurb": "Determine if a word or phrase is an isogram.", "authors": [], "contributors": [ "amscotti", "ankorGH", + "jagdish-15", "matthewmorgan", "ovidiu141", "PakkuDon", @@ -12,10 +12,23 @@ "SleeplessByte" ], "files": { - "solution": ["isogram.js"], - "test": ["isogram.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "isogram.js" + ], + "test": [ + "isogram.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Determine if a word or phrase is an isogram.", "source": "Wikipedia", - "source_url": "https://en.wikipedia.org/wiki/Isogram" + "source_url": "https://en.wikipedia.org/wiki/Isogram", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/isogram/.meta/tests.toml b/exercises/practice/isogram/.meta/tests.toml index 7187c340a3..ba04c6645d 100644 --- a/exercises/practice/isogram/.meta/tests.toml +++ b/exercises/practice/isogram/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [a0e97d2d-669e-47c7-8134-518a1e2c4555] description = "empty string" @@ -40,3 +47,6 @@ description = "duplicated character in the middle" [310ac53d-8932-47bc-bbb4-b2b94f25a83e] description = "same first and last characters" + +[0d0b8644-0a1e-4a31-a432-2b3ee270d847] +description = "word with duplicated character and with two hyphens" diff --git a/exercises/practice/isogram/babel.config.js b/exercises/practice/isogram/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/isogram/babel.config.js +++ b/exercises/practice/isogram/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/isogram/eslint.config.mjs b/exercises/practice/isogram/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/isogram/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/isogram/isogram.js b/exercises/practice/isogram/isogram.js index 9da2ad43b7..9779818aca 100644 --- a/exercises/practice/isogram/isogram.js +++ b/exercises/practice/isogram/isogram.js @@ -4,5 +4,5 @@ // export const isIsogram = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/isogram/isogram.spec.js b/exercises/practice/isogram/isogram.spec.js index 92165ae448..1b0162c717 100644 --- a/exercises/practice/isogram/isogram.spec.js +++ b/exercises/practice/isogram/isogram.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { isIsogram } from './isogram'; describe('Isogram', () => { @@ -53,5 +54,9 @@ describe('Isogram', () => { xtest('same first and last characters', () => { expect(isIsogram('angola')).toEqual(false); }); + + xtest('word with duplicated character and with two hyphens', () => { + expect(isIsogram('up-to-date')).toEqual(false); + }); }); }); diff --git a/exercises/practice/isogram/jest.config.js b/exercises/practice/isogram/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/isogram/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/isogram/package.json b/exercises/practice/isogram/package.json index 517a2ddb62..5a1759921b 100644 --- a/exercises/practice/isogram/package.json +++ b/exercises/practice/isogram/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/isogram" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/killer-sudoku-helper/.docs/instructions.md b/exercises/practice/killer-sudoku-helper/.docs/instructions.md new file mode 100644 index 0000000000..fdafdca8fb --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/.docs/instructions.md @@ -0,0 +1,85 @@ +# Instructions + +A friend of yours is learning how to solve Killer Sudokus (rules below) but struggling to figure out which digits can go in a cage. +They ask you to help them out by writing a small program that lists all valid combinations for a given cage, and any constraints that affect the cage. + +To make the output of your program easy to read, the combinations it returns must be sorted. + +## Killer Sudoku Rules + +- [Standard Sudoku rules][sudoku-rules] apply. +- The digits in a cage, usually marked by a dotted line, add up to the small number given in the corner of the cage. +- A digit may only occur once in a cage. + +For a more detailed explanation, check out [this guide][killer-guide]. + +## Example 1: Cage with only 1 possible combination + +In a 3-digit cage with a sum of 7, there is only one valid combination: 124. + +- 1 + 2 + 4 = 7 +- Any other combination that adds up to 7, e.g. 232, would violate the rule of not repeating digits within a cage. + +![Sudoku grid, with three killer cages that are marked as grouped together. +The first killer cage is in the 3×3 box in the top left corner of the grid. +The middle column of that box forms the cage, with the followings cells from top to bottom: first cell contains a 1 and a pencil mark of 7, indicating a cage sum of 7, second cell contains a 2, third cell contains a 5. +The numbers are highlighted in red to indicate a mistake. +The second killer cage is in the central 3×3 box of the grid. +The middle column of that box forms the cage, with the followings cells from top to bottom: first cell contains a 1 and a pencil mark of 7, indicating a cage sum of 7, second cell contains a 2, third cell contains a 4. +None of the numbers in this cage are highlighted and therefore don't contain any mistakes. +The third killer cage follows the outside corner of the central 3×3 box of the grid. +It is made up of the following three cells: the top left cell of the cage contains a 2, highlighted in red, and a cage sum of 7. +The top right cell of the cage contains a 3. +The bottom right cell of the cage contains a 2, highlighted in red. All other cells are empty.][one-solution-img] + +## Example 2: Cage with several combinations + +In a 2-digit cage with a sum 10, there are 4 possible combinations: + +- 19 +- 28 +- 37 +- 46 + +![Sudoku grid, all squares empty except for the middle column, column 5, which has 8 rows filled. +Each continguous two rows form a killer cage and are marked as grouped together. +From top to bottom: first group is a cell with value 1 and a pencil mark indicating a cage sum of 10, cell with value 9. +Second group is a cell with value 2 and a pencil mark of 10, cell with value 8. +Third group is a cell with value 3 and a pencil mark of 10, cell with value 7. +Fourth group is a cell with value 4 and a pencil mark of 10, cell with value 6. +The last cell in the column is empty.][four-solutions-img] + +## Example 3: Cage with several combinations that is restricted + +In a 2-digit cage with a sum 10, where the column already contains a 1 and a 4, there are 2 possible combinations: + +- 28 +- 37 + +19 and 46 are not possible due to the 1 and 4 in the column according to standard Sudoku rules. + +![Sudoku grid, all squares empty except for the middle column, column 5, which has 8 rows filled. +The first row contains a 4, the second is empty, and the third contains a 1. +The 1 is highlighted in red to indicate a mistake. +The last 6 rows in the column form killer cages of two cells each. +From top to bottom: first group is a cell with value 2 and a pencil mark indicating a cage sum of 10, cell with value 8. +Second group is a cell with value 3 and a pencil mark of 10, cell with value 7. +Third group is a cell with value 1, highlighted in red, and a pencil mark of 10, cell with value 9.][not-possible-img] + +## Trying it yourself + +If you want to give an approachable Killer Sudoku a go, you can try out [this puzzle][clover-puzzle] by Clover, featured by [Mark Goodliffe on Cracking The Cryptic on the 21st of June 2021][goodliffe-video]. + +You can also find Killer Sudokus in varying difficulty in numerous newspapers, as well as Sudoku apps, books and websites. + +## Credit + +The screenshots above have been generated using [F-Puzzles.com](https://www.f-puzzles.com/), a Puzzle Setting Tool by Eric Fox. + +[sudoku-rules]: https://masteringsudoku.com/sudoku-rules-beginners/ +[killer-guide]: https://masteringsudoku.com/killer-sudoku/ +[one-solution-img]: https://assets.exercism.org/images/exercises/killer-sudoku-helper/example1.png +[four-solutions-img]: https://assets.exercism.org/images/exercises/killer-sudoku-helper/example2.png +[not-possible-img]: https://assets.exercism.org/images/exercises/killer-sudoku-helper/example3.png +[clover-puzzle]: https://app.crackingthecryptic.com/sudoku/HqTBn3Pr6R +[goodliffe-video]: https://youtu.be/c_NjEbFEeW0?t=1180 diff --git a/exercises/practice/killer-sudoku-helper/.gitignore b/exercises/practice/killer-sudoku-helper/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/killer-sudoku-helper/.meta/config.json b/exercises/practice/killer-sudoku-helper/.meta/config.json new file mode 100644 index 0000000000..41bf7f8933 --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "Cool-Katt" + ], + "files": { + "solution": [ + "killer-sudoku-helper.js" + ], + "test": [ + "killer-sudoku-helper.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] + }, + "blurb": "Write a tool that makes it easier to solve Killer Sudokus", + "source": "Created by Sascha Mann, Jeremy Walker, and BethanyG for the Julia track on Exercism.", + "source_url": "https://github.com/exercism/julia/pull/413" +} diff --git a/exercises/practice/killer-sudoku-helper/.meta/proof.ci.js b/exercises/practice/killer-sudoku-helper/.meta/proof.ci.js new file mode 100644 index 0000000000..570c87c060 --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/.meta/proof.ci.js @@ -0,0 +1,28 @@ +export const combinations = (cage) => { + const { sum, size, exclude } = cage; + const result = []; + const digits = [...Array(10).keys()] + .slice(1) + .filter((d) => !exclude.includes(d)); + + function findCombinations(remainingSum, index, combination) { + if (remainingSum === 0 && combination.length === size) { + result.push(combination); + return; + } + + for (let i = index; i < digits.length; i++) { + const digit = digits[i]; + if (digit > remainingSum) { + break; + } + if (combination.includes(digit)) { + continue; + } + findCombinations(remainingSum - digit, i + 1, [...combination, digit]); + } + } + + findCombinations(sum, 0, []); + return result; +}; diff --git a/exercises/practice/killer-sudoku-helper/.meta/tests.toml b/exercises/practice/killer-sudoku-helper/.meta/tests.toml new file mode 100644 index 0000000000..19c23e8a92 --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/.meta/tests.toml @@ -0,0 +1,49 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[2aaa8f13-11b5-4054-b95c-a906e4d79fb6] +description = "Trivial 1-digit cages -> 1" + +[4645da19-9fdd-4087-a910-a6ed66823563] +description = "Trivial 1-digit cages -> 2" + +[07cfc704-f8aa-41b2-8f9a-cbefb674cb48] +description = "Trivial 1-digit cages -> 3" + +[22b8b2ba-c4fd-40b3-b1bf-40aa5e7b5f24] +description = "Trivial 1-digit cages -> 4" + +[b75d16e2-ff9b-464d-8578-71f73094cea7] +description = "Trivial 1-digit cages -> 5" + +[bcbf5afc-4c89-4ff6-9357-07ab4d42788f] +description = "Trivial 1-digit cages -> 6" + +[511b3bf8-186f-4e35-844f-c804d86f4a7a] +description = "Trivial 1-digit cages -> 7" + +[bd09a60d-3aca-43bd-b6aa-6ccad01bedda] +description = "Trivial 1-digit cages -> 8" + +[9b539f27-44ea-4ff8-bd3d-c7e136bee677] +description = "Trivial 1-digit cages -> 9" + +[0a8b2078-b3a4-4dbd-be0d-b180f503d5c3] +description = "Cage with sum 45 contains all digits 1:9" + +[2635d7c9-c716-4da1-84f1-c96e03900142] +description = "Cage with only 1 possible combination" + +[a5bde743-e3a2-4a0c-8aac-e64fceea4228] +description = "Cage with several combinations" + +[dfbf411c-737d-465a-a873-ca556360c274] +description = "Cage with several combinations that is restricted" diff --git a/exercises/practice/killer-sudoku-helper/.npmrc b/exercises/practice/killer-sudoku-helper/.npmrc new file mode 100644 index 0000000000..d26df800bb --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/.npmrc @@ -0,0 +1 @@ +audit=false diff --git a/exercises/practice/killer-sudoku-helper/LICENSE b/exercises/practice/killer-sudoku-helper/LICENSE new file mode 100644 index 0000000000..90e73be03b --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Exercism + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/exercises/practice/killer-sudoku-helper/babel.config.js b/exercises/practice/killer-sudoku-helper/babel.config.js new file mode 100644 index 0000000000..a638497df1 --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/babel.config.js @@ -0,0 +1,4 @@ +module.exports = { + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], +}; diff --git a/exercises/practice/killer-sudoku-helper/eslint.config.mjs b/exercises/practice/killer-sudoku-helper/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/killer-sudoku-helper/jest.config.js b/exercises/practice/killer-sudoku-helper/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/killer-sudoku-helper/killer-sudoku-helper.js b/exercises/practice/killer-sudoku-helper/killer-sudoku-helper.js new file mode 100644 index 0000000000..1894c4261c --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/killer-sudoku-helper.js @@ -0,0 +1,8 @@ +// +// This is only a SKELETON file for the 'Killer Sudoku Helper' exercise. It's been provided as a +// convenience to get you started writing code faster. +// + +export const combinations = (cage) => { + throw new Error('Remove this line and implement the function'); +}; diff --git a/exercises/practice/killer-sudoku-helper/killer-sudoku-helper.spec.js b/exercises/practice/killer-sudoku-helper/killer-sudoku-helper.spec.js new file mode 100644 index 0000000000..61167f7e6a --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/killer-sudoku-helper.spec.js @@ -0,0 +1,157 @@ +import { describe, expect, test, xtest } from '@jest/globals'; +import { combinations } from './killer-sudoku-helper'; + +describe('Trivial 1-digit cages', () => { + test('1', () => { + const inputCage = { + sum: 1, + size: 1, + exclude: [], + }; + const expected = [[1]]; + const actual = combinations(inputCage); + expect(actual).toEqual(expected); + }); + + xtest('2', () => { + const inputCage = { + sum: 2, + size: 1, + exclude: [], + }; + const expected = [[2]]; + const actual = combinations(inputCage); + expect(actual).toEqual(expected); + }); + + xtest('3', () => { + const inputCage = { + sum: 3, + size: 1, + exclude: [], + }; + const expected = [[3]]; + const actual = combinations(inputCage); + expect(actual).toEqual(expected); + }); + + xtest('4', () => { + const inputCage = { + sum: 4, + size: 1, + exclude: [], + }; + const expected = [[4]]; + const actual = combinations(inputCage); + expect(actual).toEqual(expected); + }); + + xtest('5', () => { + const inputCage = { + sum: 5, + size: 1, + exclude: [], + }; + const expected = [[5]]; + const actual = combinations(inputCage); + expect(actual).toEqual(expected); + }); + + xtest('6', () => { + const inputCage = { + sum: 6, + size: 1, + exclude: [], + }; + const expected = [[6]]; + const actual = combinations(inputCage); + expect(actual).toEqual(expected); + }); + + xtest('7', () => { + const inputCage = { + sum: 7, + size: 1, + exclude: [], + }; + const expected = [[7]]; + const actual = combinations(inputCage); + expect(actual).toEqual(expected); + }); + + xtest('8', () => { + const inputCage = { + sum: 8, + size: 1, + exclude: [], + }; + const expected = [[8]]; + const actual = combinations(inputCage); + expect(actual).toEqual(expected); + }); + + xtest('9', () => { + const inputCage = { + sum: 9, + size: 1, + exclude: [], + }; + const expected = [[9]]; + const actual = combinations(inputCage); + expect(actual).toEqual(expected); + }); +}); + +describe('Other cages', () => { + xtest('Cage with sum 45 contains all digits 1:9', () => { + const inputCage = { + sum: 45, + size: 9, + exclude: [], + }; + const expected = [[1, 2, 3, 4, 5, 6, 7, 8, 9]]; + const actual = combinations(inputCage); + expect(actual).toEqual(expected); + }); + + xtest('Cage with only 1 possible combination', () => { + const inputCage = { + sum: 7, + size: 3, + exclude: [], + }; + const expected = [[1, 2, 4]]; + const actual = combinations(inputCage); + expect(actual).toEqual(expected); + }); + + xtest('Cage with several combinations', () => { + const inputCage = { + sum: 10, + size: 2, + exclude: [], + }; + const expected = [ + [1, 9], + [2, 8], + [3, 7], + [4, 6], + ]; + const actual = combinations(inputCage); + expect(actual).toEqual(expected); + }); + + xtest('Cage with several combinations that is restricted', () => { + const inputCage = { + sum: 10, + size: 2, + exclude: [1, 4], + }; + const expected = [ + [2, 8], + [3, 7], + ]; + const actual = combinations(inputCage); + expect(actual).toEqual(expected); + }); +}); diff --git a/exercises/practice/killer-sudoku-helper/package.json b/exercises/practice/killer-sudoku-helper/package.json new file mode 100644 index 0000000000..20a90e694d --- /dev/null +++ b/exercises/practice/killer-sudoku-helper/package.json @@ -0,0 +1,39 @@ +{ + "name": "@exercism/javascript-killer-sudoku-helper", + "description": "Exercism practice exercise on killer-sudoku-helper", + "author": "Katrina Owen", + "contributors": [ + "Derk-Jan Karrenbeld (https://derk-jan.com)", + "Tejas Bubane (https://tejasbubane.github.io/)", + "Cool-Katt (https://github.com/Cool-Katt)" + ], + "private": true, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/exercism/javascript", + "directory": "exercises/practice/killer-sudoku-helper" + }, + "devDependencies": { + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" + }, + "dependencies": {}, + "scripts": { + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" +} diff --git a/exercises/practice/kindergarten-garden/.docs/instructions.md b/exercises/practice/kindergarten-garden/.docs/instructions.md index 4d91e715d2..6fe11a58ce 100644 --- a/exercises/practice/kindergarten-garden/.docs/instructions.md +++ b/exercises/practice/kindergarten-garden/.docs/instructions.md @@ -1,17 +1,21 @@ # Instructions -Given a diagram, determine which plants each child in the kindergarten class is -responsible for. +Your task is to, given a diagram, determine which plants each child in the kindergarten class is responsible for. -The kindergarten class is learning about growing plants. The teacher -thought it would be a good idea to give them actual seeds, plant them in -actual dirt, and grow actual plants. +There are 12 children in the class: + +- Alice, Bob, Charlie, David, Eve, Fred, Ginny, Harriet, Ileana, Joseph, Kincaid, and Larry. + +Four different types of seeds are planted: -They've chosen to grow grass, clover, radishes, and violets. +| Plant | Diagram encoding | +| ------ | ---------------- | +| Grass | G | +| Clover | C | +| Radish | R | +| Violet | V | -To this end, the children have put little cups along the window sills, and -planted one type of plant in each cup, choosing randomly from the available -types of seeds. +Each child gets four cups, two on each row: ```text [window][window][window] @@ -19,16 +23,9 @@ types of seeds. ........................ ``` -There are 12 children in the class: - -- Alice, Bob, Charlie, David, -- Eve, Fred, Ginny, Harriet, -- Ileana, Joseph, Kincaid, and Larry. - -Each child gets 4 cups, two on each row. Their teacher assigns cups to -the children alphabetically by their names. +Their teacher assigns cups to the children alphabetically by their names, which means that Alice comes first and Larry comes last. -The following diagram represents Alice's plants: +Here is an example diagram representing Alice's plants: ```text [window][window][window] @@ -36,12 +33,11 @@ VR...................... RG...................... ``` -In the first row, nearest the windows, she has a violet and a radish. In the -second row she has a radish and some grass. +In the first row, nearest the windows, she has a violet and a radish. +In the second row she has a radish and some grass. -Your program will be given the plants from left-to-right starting with -the row nearest the windows. From this, it should be able to determine -which plants belong to each student. +Your program will be given the plants from left-to-right starting with the row nearest the windows. +From this, it should be able to determine which plants belong to each student. For example, if it's told that the garden looks like so: diff --git a/exercises/practice/kindergarten-garden/.docs/introduction.md b/exercises/practice/kindergarten-garden/.docs/introduction.md new file mode 100644 index 0000000000..5ad97d23ec --- /dev/null +++ b/exercises/practice/kindergarten-garden/.docs/introduction.md @@ -0,0 +1,6 @@ +# Introduction + +The kindergarten class is learning about growing plants. +The teacher thought it would be a good idea to give the class seeds to plant and grow in the dirt. +To this end, the children have put little cups along the window sills and planted one type of plant in each cup. +The children got to pick their favorites from four available types of seeds: grass, clover, radishes, and violets. diff --git a/exercises/practice/kindergarten-garden/.eslintrc b/exercises/practice/kindergarten-garden/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/kindergarten-garden/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/kindergarten-garden/.gitignore b/exercises/practice/kindergarten-garden/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/kindergarten-garden/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/kindergarten-garden/.meta/config.json b/exercises/practice/kindergarten-garden/.meta/config.json index 5f3619602d..a5f555ad04 100644 --- a/exercises/practice/kindergarten-garden/.meta/config.json +++ b/exercises/practice/kindergarten-garden/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Given a diagram, determine which plants each child in the kindergarten class is responsible for.", - "authors": ["rchavarria"], + "authors": [ + "rchavarria" + ], "contributors": [ "brendanmckeown", "matthewmorgan", @@ -10,10 +11,23 @@ "xarxziux" ], "files": { - "solution": ["kindergarten-garden.js"], - "test": ["kindergarten-garden.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "kindergarten-garden.js" + ], + "test": [ + "kindergarten-garden.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, - "source": "Random musings during airplane trip.", - "source_url": "http://jumpstartlab.com" + "blurb": "Given a diagram, determine which plants each child in the kindergarten class is responsible for.", + "source": "Exercise by the JumpstartLab team for students at The Turing School of Software and Design.", + "source_url": "https://turing.edu", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/kindergarten-garden/.meta/tests.toml b/exercises/practice/kindergarten-garden/.meta/tests.toml index 1778a61530..0cdd9ad64d 100644 --- a/exercises/practice/kindergarten-garden/.meta/tests.toml +++ b/exercises/practice/kindergarten-garden/.meta/tests.toml @@ -1,30 +1,61 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [1fc316ed-17ab-4fba-88ef-3ae78296b692] -description = "garden with single student" +description = "partial garden -> garden with single student" [acd19dc1-2200-4317-bc2a-08f021276b40] -description = "different garden with single student" +description = "partial garden -> different garden with single student" [c376fcc8-349c-446c-94b0-903947315757] -description = "garden with two students" +description = "partial garden -> garden with two students" [2d620f45-9617-4924-9d27-751c80d17db9] -description = "second student's garden" +description = "partial garden -> multiple students for the same garden with three students -> second student's garden" [57712331-4896-4364-89f8-576421d69c44] -description = "third student's garden" +description = "partial garden -> multiple students for the same garden with three students -> third student's garden" [149b4290-58e1-40f2-8ae4-8b87c46e765b] -description = "first student's garden" +description = "full garden -> for Alice, first student's garden" [ba25dbbc-10bd-4a37-b18e-f89ecd098a5e] -description = "second student's garden" +description = "full garden -> for Bob, second student's garden" + +[566b621b-f18e-4c5f-873e-be30544b838c] +description = "full garden -> for Charlie" + +[3ad3df57-dd98-46fc-9269-1877abf612aa] +description = "full garden -> for David" + +[0f0a55d1-9710-46ed-a0eb-399ba8c72db2] +description = "full garden -> for Eve" + +[a7e80c90-b140-4ea1-aee3-f4625365c9a4] +description = "full garden -> for Fred" + +[9d94b273-2933-471b-86e8-dba68694c615] +description = "full garden -> for Ginny" + +[f55bc6c2-ade8-4844-87c4-87196f1b7258] +description = "full garden -> for Harriet" + +[759070a3-1bb1-4dd4-be2c-7cce1d7679ae] +description = "full garden -> for Ileana" + +[78578123-2755-4d4a-9c7d-e985b8dda1c6] +description = "full garden -> for Joseph" [6bb66df7-f433-41ab-aec2-3ead6e99f65b] -description = "second to last student's garden" +description = "full garden -> for Kincaid, second to last student's garden" [d7edec11-6488-418a-94e6-ed509e0fa7eb] -description = "last student's garden" +description = "full garden -> for Larry, last student's garden" diff --git a/exercises/practice/kindergarten-garden/babel.config.js b/exercises/practice/kindergarten-garden/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/kindergarten-garden/babel.config.js +++ b/exercises/practice/kindergarten-garden/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/kindergarten-garden/eslint.config.mjs b/exercises/practice/kindergarten-garden/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/kindergarten-garden/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/kindergarten-garden/jest.config.js b/exercises/practice/kindergarten-garden/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/kindergarten-garden/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/kindergarten-garden/kindergarten-garden.js b/exercises/practice/kindergarten-garden/kindergarten-garden.js index 97bf47a57c..f211a99ada 100644 --- a/exercises/practice/kindergarten-garden/kindergarten-garden.js +++ b/exercises/practice/kindergarten-garden/kindergarten-garden.js @@ -27,10 +27,10 @@ const PLANT_CODES = { export class Garden { constructor(diagram, students = DEFAULT_STUDENTS) { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } plants(student) { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/kindergarten-garden/kindergarten-garden.spec.js b/exercises/practice/kindergarten-garden/kindergarten-garden.spec.js index ddfb434bf6..3d980989e8 100644 --- a/exercises/practice/kindergarten-garden/kindergarten-garden.spec.js +++ b/exercises/practice/kindergarten-garden/kindergarten-garden.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { Garden } from './kindergarten-garden'; describe('partial Garden', () => { diff --git a/exercises/practice/kindergarten-garden/package.json b/exercises/practice/kindergarten-garden/package.json index d864504b82..47af2ab20d 100644 --- a/exercises/practice/kindergarten-garden/package.json +++ b/exercises/practice/kindergarten-garden/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/kindergarten-garden" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/knapsack/.docs/instructions.md b/exercises/practice/knapsack/.docs/instructions.md index a41a2bb185..0ebf7914c5 100644 --- a/exercises/practice/knapsack/.docs/instructions.md +++ b/exercises/practice/knapsack/.docs/instructions.md @@ -1,37 +1,25 @@ -# Description +# Instructions -In this exercise, let's try to solve a classic problem. +Your task is to determine which items to take so that the total value of her selection is maximized, taking into account the knapsack's carrying capacity. -Bob is a thief. After months of careful planning, he finally manages to -crack the security systems of a high-class apartment. - -In front of him are many items, each with a value (v) and weight (w). Bob, -of course, wants to maximize the total value he can get; he would gladly -take all of the items if he could. However, to his horror, he realizes that -the knapsack he carries with him can only hold so much weight (W). - -Given a knapsack with a specific carrying capacity (W), help Bob determine -the maximum value he can get from the items in the house. Note that Bob can -take only one of each item. - -All values given will be strictly positive. Items will be represented as a -list of pairs, `wi` and `vi`, where the first element `wi` is the weight of -the *i*th item and `vi` is the value for that item. +Items will be represented as a list of items. +Each item will have a weight and value. +All values given will be strictly positive. +Lhakpa can take only one of each item. For example: +```text Items: [ -{ "weight": 5, "value": 10 }, -{ "weight": 4, "value": 40 }, -{ "weight": 6, "value": 30 }, -{ "weight": 4, "value": 50 } + { "weight": 5, "value": 10 }, + { "weight": 4, "value": 40 }, + { "weight": 6, "value": 30 }, + { "weight": 4, "value": 50 } ] -Knapsack Limit: 10 - -For the above, the first item has weight 5 and value 10, the second item has -weight 4 and value 40, and so on. +Knapsack Maximum Weight: 10 +``` -In this example, Bob should take the second and fourth item to maximize his -value, which, in this case, is 90. He cannot get more than 90 as his -knapsack has a weight limit of 10. +For the above, the first item has weight 5 and value 10, the second item has weight 4 and value 40, and so on. +In this example, Lhakpa should take the second and fourth item to maximize her value, which, in this case, is 90. +She cannot get more than 90 as her knapsack has a weight limit of 10. diff --git a/exercises/practice/knapsack/.docs/introduction.md b/exercises/practice/knapsack/.docs/introduction.md new file mode 100644 index 0000000000..9ac9df596b --- /dev/null +++ b/exercises/practice/knapsack/.docs/introduction.md @@ -0,0 +1,10 @@ +# Introduction + +Lhakpa is a [Sherpa][sherpa] mountain guide and porter. +After months of careful planning, the expedition Lhakpa works for is about to leave. +She will be paid the value she carried to the base camp. + +In front of her are many items, each with a value and weight. +Lhakpa would gladly take all of the items, but her knapsack can only hold so much weight. + +[sherpa]: https://en.wikipedia.org/wiki/Sherpa_people#Mountaineering diff --git a/exercises/practice/knapsack/.eslintrc b/exercises/practice/knapsack/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/knapsack/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/knapsack/.gitignore b/exercises/practice/knapsack/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/knapsack/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/knapsack/.meta/config.json b/exercises/practice/knapsack/.meta/config.json index d31cf05bcc..5c2255798d 100644 --- a/exercises/practice/knapsack/.meta/config.json +++ b/exercises/practice/knapsack/.meta/config.json @@ -1,12 +1,28 @@ { - "blurb": "Given a knapsack that can only carry a certain weight, determine which items to put in the knapsack in order to maximize their combined value.", - "authors": ["lpizzinidev"], - "contributors": [], + "authors": [ + "lpizzinidev" + ], + "contributors": [ + "jagdish-15" + ], "files": { - "solution": ["knapsack.js"], - "test": ["knapsack.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "knapsack.js" + ], + "test": [ + "knapsack.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Given a knapsack that can only carry a certain weight, determine which items to put in the knapsack in order to maximize their combined value.", "source": "Wikipedia", - "source_url": "https://en.wikipedia.org/wiki/Knapsack_problem" + "source_url": "https://en.wikipedia.org/wiki/Knapsack_problem", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/knapsack/.meta/proof.ci.js b/exercises/practice/knapsack/.meta/proof.ci.js index e73c1866f7..c0dbf83a41 100644 --- a/exercises/practice/knapsack/.meta/proof.ci.js +++ b/exercises/practice/knapsack/.meta/proof.ci.js @@ -11,7 +11,7 @@ export const knapsack = (maximumWeight, items) => { } else { table[i + 1][capacity] = Math.max( table[i][capacity], - value + table[i][capacity - weight] + value + table[i][capacity - weight], ); } } diff --git a/exercises/practice/knapsack/.meta/tests.toml b/exercises/practice/knapsack/.meta/tests.toml index 5a7805b017..8e013ef199 100644 --- a/exercises/practice/knapsack/.meta/tests.toml +++ b/exercises/practice/knapsack/.meta/tests.toml @@ -1,9 +1,21 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [a4d7d2f0-ad8a-460c-86f3-88ba709d41a7] description = "no items" +include = false + +[3993a824-c20e-493d-b3c9-ee8a7753ee59] +description = "no items" +reimplements = "a4d7d2f0-ad8a-460c-86f3-88ba709d41a7" [1d39e98c-6249-4a8b-912f-87cb12e506b0] description = "one item, too heavy" diff --git a/exercises/practice/knapsack/babel.config.js b/exercises/practice/knapsack/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/knapsack/babel.config.js +++ b/exercises/practice/knapsack/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/knapsack/eslint.config.mjs b/exercises/practice/knapsack/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/knapsack/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/knapsack/jest.config.js b/exercises/practice/knapsack/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/knapsack/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/knapsack/knapsack.js b/exercises/practice/knapsack/knapsack.js index 968d40138c..7cf940991e 100644 --- a/exercises/practice/knapsack/knapsack.js +++ b/exercises/practice/knapsack/knapsack.js @@ -4,5 +4,5 @@ // export const knapsack = (maximumWeight, items) => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/knapsack/knapsack.spec.js b/exercises/practice/knapsack/knapsack.spec.js index ccac3291cb..cbf4b9c64d 100644 --- a/exercises/practice/knapsack/knapsack.spec.js +++ b/exercises/practice/knapsack/knapsack.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test } from '@jest/globals'; import { knapsack } from './knapsack'; describe('Knapsack', () => { @@ -5,12 +6,12 @@ describe('Knapsack', () => { expect(knapsack(100, [])).toEqual(0); }); - test('one item, too heavy', () => { + xtest('one item, too heavy', () => { const items = [{ weight: 100, value: 1 }]; expect(knapsack(10, items)).toEqual(0); }); - test('five items (cannot be greedy by weight)', () => { + xtest('five items (cannot be greedy by weight)', () => { const items = [ { weight: 2, value: 5 }, { weight: 2, value: 5 }, @@ -21,7 +22,7 @@ describe('Knapsack', () => { expect(knapsack(10, items)).toEqual(21); }); - test('five items (cannot be greedy by value)', () => { + xtest('five items (cannot be greedy by value)', () => { const items = [ { weight: 2, value: 20 }, { weight: 2, value: 20 }, @@ -32,7 +33,7 @@ describe('Knapsack', () => { expect(knapsack(10, items)).toEqual(80); }); - test('example knapsack', () => { + xtest('example knapsack', () => { const items = [ { weight: 5, value: 10 }, { weight: 4, value: 40 }, @@ -42,7 +43,7 @@ describe('Knapsack', () => { expect(knapsack(10, items)).toEqual(90); }); - test('8 items', () => { + xtest('8 items', () => { const items = [ { weight: 25, value: 350 }, { weight: 35, value: 400 }, @@ -56,7 +57,7 @@ describe('Knapsack', () => { expect(knapsack(104, items)).toEqual(900); }); - test('15 items', () => { + xtest('15 items', () => { const items = [ { weight: 70, value: 135 }, { weight: 73, value: 139 }, diff --git a/exercises/practice/knapsack/package.json b/exercises/practice/knapsack/package.json index 0f714df4ee..a9040fa087 100644 --- a/exercises/practice/knapsack/package.json +++ b/exercises/practice/knapsack/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/knapsack" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/largest-series-product/.docs/instructions.md b/exercises/practice/largest-series-product/.docs/instructions.md index 0d5956454b..f297b57f7c 100644 --- a/exercises/practice/largest-series-product/.docs/instructions.md +++ b/exercises/practice/largest-series-product/.docs/instructions.md @@ -1,14 +1,26 @@ # Instructions -Given a string of digits, calculate the largest product for a contiguous -substring of digits of length n. +Your task is to look for patterns in the long sequence of digits in the encrypted signal. -For example, for the input `'1027839564'`, the largest product for a -series of 3 digits is 270 (9 _ 5 _ 6), and the largest product for a -series of 5 digits is 7560 (7 _ 8 _ 3 _ 9 _ 5). +The technique you're going to use here is called the largest series product. -Note that these series are only required to occupy _adjacent positions_ -in the input; the digits need not be _numerically consecutive_. +Let's define a few terms, first. -For the input `'73167176531330624919225119674426574742355349194934'`, -the largest product for a series of 6 digits is 23520. +- **input**: the sequence of digits that you need to analyze +- **series**: a sequence of adjacent digits (those that are next to each other) that is contained within the input +- **span**: how many digits long each series is +- **product**: what you get when you multiply numbers together + +Let's work through an example, with the input `"63915"`. + +- To form a series, take adjacent digits in the original input. +- If you are working with a span of `3`, there will be three possible series: + - `"639"` + - `"391"` + - `"915"` +- Then we need to calculate the product of each series: + - The product of the series `"639"` is 162 (`6 × 3 × 9 = 162`) + - The product of the series `"391"` is 27 (`3 × 9 × 1 = 27`) + - The product of the series `"915"` is 45 (`9 × 1 × 5 = 45`) +- 162 is bigger than both 27 and 45, so the largest series product of `"63915"` is from the series `"639"`. + So the answer is **162**. diff --git a/exercises/practice/largest-series-product/.docs/introduction.md b/exercises/practice/largest-series-product/.docs/introduction.md new file mode 100644 index 0000000000..597bb5fa15 --- /dev/null +++ b/exercises/practice/largest-series-product/.docs/introduction.md @@ -0,0 +1,5 @@ +# Introduction + +You work for a government agency that has intercepted a series of encrypted communication signals from a group of bank robbers. +The signals contain a long sequence of digits. +Your team needs to use various digital signal processing techniques to analyze the signals and identify any patterns that may indicate the planning of a heist. diff --git a/exercises/practice/largest-series-product/.eslintrc b/exercises/practice/largest-series-product/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/largest-series-product/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/largest-series-product/.gitignore b/exercises/practice/largest-series-product/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/largest-series-product/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/largest-series-product/.meta/config.json b/exercises/practice/largest-series-product/.meta/config.json index 16fbc6972b..9f78ec6a9a 100644 --- a/exercises/practice/largest-series-product/.meta/config.json +++ b/exercises/practice/largest-series-product/.meta/config.json @@ -1,8 +1,10 @@ { - "blurb": "Given a string of digits, calculate the largest product for a contiguous substring of digits of length n.", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "ankorGH", + "jagdish-15", "ovidiu141", "petertseng", "rchavarria", @@ -12,10 +14,23 @@ "xarxziux" ], "files": { - "solution": ["largest-series-product.js"], - "test": ["largest-series-product.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "largest-series-product.js" + ], + "test": [ + "largest-series-product.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Given a string of digits, calculate the largest product for a contiguous substring of digits of length n.", "source": "A variation on Problem 8 at Project Euler", - "source_url": "http://projecteuler.net/problem=8" + "source_url": "https://projecteuler.net/problem=8", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/largest-series-product/.meta/proof.ci.js b/exercises/practice/largest-series-product/.meta/proof.ci.js index 077d6d9f69..4f34d946f8 100644 --- a/exercises/practice/largest-series-product/.meta/proof.ci.js +++ b/exercises/practice/largest-series-product/.meta/proof.ci.js @@ -3,14 +3,14 @@ export const largestProduct = (digits, seriesLength) => { return 1; } if (seriesLength > digits.length) { - throw new Error('Span must be smaller than string length'); + throw new Error('span must not exceed string length'); } if (seriesLength < 0) { - throw new Error('Span must be greater than zero'); + throw new Error('span must not be negative'); } if (!/^[0-9]+$/g.test(digits)) { - throw new Error('Digits input must only contain digits'); + throw new Error('digits input must only contain digits'); } let result = 0; diff --git a/exercises/practice/largest-series-product/.meta/tests.toml b/exercises/practice/largest-series-product/.meta/tests.toml index 931c941180..982f517cc3 100644 --- a/exercises/practice/largest-series-product/.meta/tests.toml +++ b/exercises/practice/largest-series-product/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [7c82f8b7-e347-48ee-8a22-f672323324d4] description = "finds the largest product if span equals length" @@ -31,18 +38,35 @@ description = "reports zero if all spans include zero" [5d81aaf7-4f67-4125-bf33-11493cc7eab7] description = "rejects span longer than string length" +include = false + +[0ae1ce53-d9ba-41bb-827f-2fceb64f058b] +description = "rejects span longer than string length" +reimplements = "5d81aaf7-4f67-4125-bf33-11493cc7eab7" [06bc8b90-0c51-4c54-ac22-3ec3893a079e] description = "reports 1 for empty string and empty product (0 span)" +include = false [3ec0d92e-f2e2-4090-a380-70afee02f4c0] description = "reports 1 for nonempty string and empty product (0 span)" +include = false [6d96c691-4374-4404-80ee-2ea8f3613dd4] description = "rejects empty string and nonzero span" +include = false + +[6cf66098-a6af-4223-aab1-26aeeefc7402] +description = "rejects empty string and nonzero span" +reimplements = "6d96c691-4374-4404-80ee-2ea8f3613dd4" [7a38f2d6-3c35-45f6-8d6f-12e6e32d4d74] description = "rejects invalid character in digits" [5fe3c0e5-a945-49f2-b584-f0814b4dd1ef] description = "rejects negative span" +include = false + +[c859f34a-9bfe-4897-9c2f-6d7f8598e7f0] +description = "rejects negative span" +reimplements = "5fe3c0e5-a945-49f2-b584-f0814b4dd1ef" diff --git a/exercises/practice/largest-series-product/babel.config.js b/exercises/practice/largest-series-product/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/largest-series-product/babel.config.js +++ b/exercises/practice/largest-series-product/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/largest-series-product/eslint.config.mjs b/exercises/practice/largest-series-product/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/largest-series-product/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/largest-series-product/jest.config.js b/exercises/practice/largest-series-product/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/largest-series-product/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/largest-series-product/largest-series-product.js b/exercises/practice/largest-series-product/largest-series-product.js index 0965c9cf9e..4b377c2523 100644 --- a/exercises/practice/largest-series-product/largest-series-product.js +++ b/exercises/practice/largest-series-product/largest-series-product.js @@ -4,5 +4,5 @@ // export const largestProduct = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/largest-series-product/largest-series-product.spec.js b/exercises/practice/largest-series-product/largest-series-product.spec.js index 213625d003..bfb6560b5c 100644 --- a/exercises/practice/largest-series-product/largest-series-product.spec.js +++ b/exercises/practice/largest-series-product/largest-series-product.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { largestProduct } from './largest-series-product'; describe('Largest Series Product', () => { @@ -27,7 +28,7 @@ describe('Largest Series Product', () => { xtest('can get the largest product of a big number', () => { expect( - largestProduct('73167176531330624919225119674426574742355349194934', 6) + largestProduct('73167176531330624919225119674426574742355349194934', 6), ).toEqual(23520); }); @@ -41,33 +42,25 @@ describe('Largest Series Product', () => { xtest('rejects span longer than string length', () => { expect(() => largestProduct('123', 4)).toThrow( - new Error('Span must be smaller than string length') + new Error('span must not exceed string length'), ); }); - xtest('reports 1 for empty string and empty product (0 span)', () => { - expect(largestProduct('', 0)).toEqual(1); - }); - - xtest('reports 1 for nonempty string and empty product (0 span)', () => { - expect(largestProduct('123', 0)).toEqual(1); - }); - xtest('rejects empty string and nonzero span', () => { expect(() => largestProduct('', 1)).toThrow( - new Error('Span must be smaller than string length') + new Error('span must not exceed string length'), ); }); xtest('rejects invalid character in digits', () => { expect(() => largestProduct('1234a5', 2)).toThrow( - new Error('Digits input must only contain digits') + new Error('digits input must only contain digits'), ); }); xtest('rejects negative span', () => { expect(() => largestProduct('12345', -1)).toThrow( - new Error('Span must be greater than zero') + new Error('span must not be negative'), ); }); }); diff --git a/exercises/practice/largest-series-product/package.json b/exercises/practice/largest-series-product/package.json index 591fdb9d79..b9e1f12260 100644 --- a/exercises/practice/largest-series-product/package.json +++ b/exercises/practice/largest-series-product/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/largest-series-product" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/leap/.approaches/boolean-chain/content.md b/exercises/practice/leap/.approaches/boolean-chain/content.md new file mode 100644 index 0000000000..6783617701 --- /dev/null +++ b/exercises/practice/leap/.approaches/boolean-chain/content.md @@ -0,0 +1,61 @@ +# Chain of boolean expressions + +```javascript +export function isLeap(year) { + return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); +} +``` + +The first boolean expression uses the [remainder operator][remainder-operator] to check if the year is evenly divided by `4`. + +- If the year is not evenly divisible by `4`, then the chain will "short circuit" due to the next operator being a [logical AND][logical-and] (`&&`), + and will return `false`. +- If the year _is_ evenly divisible by `4`, then the [logical NOT operator][logical-not] is used to check if the year is _not_ evenly divisible by `100`. +- If the year is not evenly divisible by `100`, then the expression is `true` and the chain will "short-circuit" to return `true`, + since the next operator is a [logical OR][logical-or] (`||`). +- If the year _is_ evenly divisible by `100`, then the expression is `false`, and the returned value from the chain will be if the year is evenly divisible by `400`. + +| year | year % 4 == 0 | year % 100 != 0 | year % 400 == 0 | is leap year | +| ---- | ------------- | --------------- | --------------- | ------------ | +| 2020 | true | true | not evaluated | true | +| 2019 | false | not evaluated | not evaluated | false | +| 2000 | true | false | true | true | +| 1900 | true | false | false | false | + +The chain of boolean expressions is efficient, as it proceeds from testing the most likely to least likely conditions. + +## Shortening + +By using the [falsiness][falsey] of `0`, a test for a value equaling `0` can be shortened using the logical NOT operator, +like so + +```javascript +export function isLeap(year) { + return !(year % 4) && (year % 100 != 0 || !(year % 400)); +} +``` + +It can be thought of as the expression _not_ having a remainder. + +When the body of a function is a single expression, the function can be implemented as an [arrow function][arrow-function], like so + +```javascript +export const isLeap = (year) => + year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); +``` + +or + +```javascript +export const isLeap = (year) => + !(year % 4) && (year % 100 != 0 || !(year % 400)); +``` + +Notice that `return` and the curly braces are not needed. + +[remainder-operator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Remainder +[logical-not]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_NOT +[logical-and]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_AND +[logical-or]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_OR +[falsey]: https://developer.mozilla.org/en-US/docs/Glossary/Falsy +[arrow-function]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions diff --git a/exercises/practice/leap/.approaches/boolean-chain/snippet.txt b/exercises/practice/leap/.approaches/boolean-chain/snippet.txt new file mode 100644 index 0000000000..096a665321 --- /dev/null +++ b/exercises/practice/leap/.approaches/boolean-chain/snippet.txt @@ -0,0 +1,3 @@ +export function isLeap(year) { + return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); +} diff --git a/exercises/practice/leap/.approaches/config.json b/exercises/practice/leap/.approaches/config.json new file mode 100644 index 0000000000..7bba7b7e9d --- /dev/null +++ b/exercises/practice/leap/.approaches/config.json @@ -0,0 +1,45 @@ +{ + "introduction": { + "authors": [ + "bobahop" + ] + }, + "approaches": [ + { + "uuid": "8d500d37-9bf0-4c92-a1f8-1b8e5e688d11", + "slug": "boolean-chain", + "title": "Boolean chain", + "blurb": "Use a chain of boolean expressions.", + "authors": [ + "bobahop" + ] + }, + { + "uuid": "05a2ccf2-0878-48ed-b0dc-72c214813a8e", + "slug": "ternary-operator", + "title": "Ternary operator", + "blurb": "Use a ternary operator of boolean expressions.", + "authors": [ + "bobahop" + ] + }, + { + "uuid": "a891670c-ea07-4171-bb91-06321663b6b1", + "slug": "switch-statement", + "title": "switch statement", + "blurb": "Use a switch statement.", + "authors": [ + "bobahop" + ] + }, + { + "uuid": "eec0b9f3-331b-4df9-b33d-75d19f78c03e", + "slug": "new-date-getmonth", + "title": "new Date getMonth", + "blurb": "Get the month for a new Date.", + "authors": [ + "bobahop" + ] + } + ] +} diff --git a/exercises/practice/leap/.approaches/introduction.md b/exercises/practice/leap/.approaches/introduction.md new file mode 100644 index 0000000000..bd53faec17 --- /dev/null +++ b/exercises/practice/leap/.approaches/introduction.md @@ -0,0 +1,73 @@ +# Introduction + +There are various idiomatic approaches to solve Leap. +You can use a chain of boolean expressions to test the conditions. +Or you can use a [ternary operator][ternary-operator]. +Another approach you can use is a [switch][switch] statement. + +## General guidance + +The key to solving Leap is to know if the year is evenly divisible by `4`, `100` and `400`. +For determining that, you will use the [remainder operator][remainder-operator]. + +## Approach: Chain of Boolean expressions + +```javascript +export function isLeap(year) { + return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); +} +``` + +For more information, check the [Boolean chain approach][approach-boolean-chain]. + +## Approach: Ternary operator of Boolean expressions + +```javascript +export function isLeap(year) { + return year % 100 == 0 ? year % 400 == 0 : year % 4 == 0; +} +``` + +For more information, check the [Ternary operator approach][approach-ternary-operator]. + +## Approach: `switch` statement + +```javascript +export function isLeap(year) { + switch (true) { + case year % 400 == 0: + return true; + case year % 100 == 0: + return false; + default: + return year % 4 == 0; + } +} +``` + +For more information, check the [`switch` statement approach][approach-switch-statement]. + +## Other approaches + +Besides the aforementioned, idiomatic approaches, you could also approach the exercise as follows: + +## Approach: `new Date` `getMonth` approach: + +Create a new `Date` from February 29th for the year and see if the month is still February. +For more information, see the [`new Date` `getMonth` approach][approach-new-date-getmonth]. + +## Which approach to use? + +- The chain of boolean expressions is most efficient, as it proceeds from the most likely to least likely conditions. + It has a maximum of three checks. +- The ternary operator has a maximum of only two checks, but it starts from a less likely condition. +- The `switch` statement is more verbose and may be considered less readable. +- Using a new `Date` with `getMonth` may be considered a "cheat" for the exercise. + +[remainder-operator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Remainder +[switch]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch +[ternary-operator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator +[approach-boolean-chain]: https://exercism.org/tracks/javascript/exercises/leap/approaches/boolean-chain +[approach-ternary-operator]: https://exercism.org/tracks/javascript/exercises/leap/approaches/ternary-operator +[approach-switch-statement]: https://exercism.org/tracks/javascript/exercises/leap/approaches/switch-statement +[approach-new-date-getmonth]: https://exercism.org/tracks/javascript/exercises/leap/approaches/new-date-getmonth diff --git a/exercises/practice/leap/.approaches/new-date-getmonth/content.md b/exercises/practice/leap/.approaches/new-date-getmonth/content.md new file mode 100644 index 0000000000..475a914ee7 --- /dev/null +++ b/exercises/practice/leap/.approaches/new-date-getmonth/content.md @@ -0,0 +1,37 @@ +# `new Date` `getMonth` + +```javascript +export function isLeap(year) { + return new Date(year, 1, 29).getMonth() == 1; +} +``` + + +~~~~exercism/caution +This approach may be considered a "cheat" for this exercise. +~~~~ + + +By creating a `new` [`Date`][date] from February 29th for the year, you can see if the month is still February. +If it is, then the year is a leap year. +This is checked by using the [getMonth][getmonth] method of the `Date` object. + + +~~~~exercism/note +Note that the value returned from the `getMonth` method is zero-based, meaning that February is `1`, not `2`. +~~~~ + + +## Shortening + +When the body of a function is a single expression, the function can be implemented as an [arrow function][arrow-function], like so + +```javascript +export const isLeap = (year) => new Date(year, 1, 29).getMonth() == 1; +``` + +Notice that `return` and the curly braces are not needed. + +[date]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date +[getmonth]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getMonth +[arrow-function]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions diff --git a/exercises/practice/leap/.approaches/new-date-getmonth/snippet.txt b/exercises/practice/leap/.approaches/new-date-getmonth/snippet.txt new file mode 100644 index 0000000000..9681d736f5 --- /dev/null +++ b/exercises/practice/leap/.approaches/new-date-getmonth/snippet.txt @@ -0,0 +1,3 @@ +export function isLeap(year) { + return new Date(year, 1, 29).getMonth() == 1; +} diff --git a/exercises/practice/leap/.approaches/switch-statement/content.md b/exercises/practice/leap/.approaches/switch-statement/content.md new file mode 100644 index 0000000000..91a39ad1d5 --- /dev/null +++ b/exercises/practice/leap/.approaches/switch-statement/content.md @@ -0,0 +1,51 @@ +# `switch` statement + +```javascript +export function isLeap(year) { + switch (true) { + case year % 400 == 0: + return true; + case year % 100 == 0: + return false; + default: + return year % 4 == 0; + } +} +``` + +The [switch][switch] statement tests the value `true`, which leaves the actual testing up to the `case` arms. +The `default` arm of the `switch` returns whether the year is evenly divisable by `4` when none of the previous arms match. + +| year | year % 4 | year % 100 | year % 400 | is leap year | +| ---- | -------- | ---------- | ---------- | ------------ | +| 2020 | 0 | 20 | 20 | true | +| 2019 | 3 | 19 | 19 | false | +| 2000 | 0 | 0 | 0 | true | +| 1900 | 0 | 0 | 300 | false | + +The `switch` statement is somewhat more verbose than other approaches, +and may also be considered less readable. + +## Shortening + +By using the [falsiness][falsey] of `0`, a test for a value equaling `0` can be shortened using the logical NOT operator, +like so + +```javascript +export function isLeap(year) { + switch (true) { + case !(year % 400): + return true; + case !(year % 100): + return false; + default: + return !(year % 4); + } +} +``` + +It can be thought of as the expression _not_ having a remainder. + +[switch]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch#an_alternative_to_if...else_chains +[falsey]: https://developer.mozilla.org/en-US/docs/Glossary/Falsy +[logical-not]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_NOT diff --git a/exercises/practice/leap/.approaches/switch-statement/snippet.txt b/exercises/practice/leap/.approaches/switch-statement/snippet.txt new file mode 100644 index 0000000000..fa583b69be --- /dev/null +++ b/exercises/practice/leap/.approaches/switch-statement/snippet.txt @@ -0,0 +1,8 @@ +switch (true) { + case year % 400 == 0: + return true; + case year % 100 == 0: + return false; + default: + return year % 4 == 0; +} diff --git a/exercises/practice/leap/.approaches/ternary-operator/content.md b/exercises/practice/leap/.approaches/ternary-operator/content.md new file mode 100644 index 0000000000..e0ba3c6cb4 --- /dev/null +++ b/exercises/practice/leap/.approaches/ternary-operator/content.md @@ -0,0 +1,60 @@ +# Ternary operator + +```javascript +export function isLeap(year) { + return year % 100 == 0 ? year % 400 == 0 : year % 4 == 0; +} +``` + +A [conditional operator][ternary-operator], also known as a "ternary conditional operator", or just "ternary operator", +uses a maximum of two checks to determine if a year is a leap year. + +It starts by testing the outlier condition of the year being evenly divisible by `100`. +It does this by using the [remainder operator][remainder-operator]. +If the year is evenly divisible by `100`, then the expression is `true`, and the ternary operator returns if the year is evenly divisible by `400`. +If the year is _not_ evenly divisible by `100`, then the expression is `false`, and the ternary operator returns if the year is evenly divisible by `4`. + +| year | year % 100 == 0 | year % 400 == 0 | year % 4 == 0 | is leap year | +| ---- | --------------- | --------------- | ------------- | ------------ | +| 2020 | false | not evaluated | true | true | +| 2019 | false | not evaluated | false | false | +| 2000 | true | true | not evaluated | true | +| 1900 | true | false | not evaluated | false | + +Although it uses a maximum of only two checks, the ternary operator tests an outlier condition first, +making it less efficient than another approach that would first test if the year is evenly divisible by `4`, +which is more likely than the year being evenly divisible by `100`. + +## Shortening + +By using the [falsiness][falsey] of `0`, a test for a value equaling `0` can be shortened using the [logical NOT operator][logical-not], +like so + +```javascript +export function isLeap(year) { + !(year % 100) ? !(year % 400) : !(year % 4); +} +``` + +It can be thought of as the expression _not_ having a remainder. + +When the body of a function is a single expression, the function can be implemented as an [arrow function][arrow-function], like so + +```javascript +export const isLeap = (year) => + year % 100 == 0 ? year % 400 == 0 : year % 4 == 0; +``` + +or + +```javascript +export const isLeap = (year) => (!(year % 100) ? !(year % 400) : !(year % 4)); +``` + +Notice that `return` and the curly braces are not needed. + +[ternary-operator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator +[remainder-operator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Remainder +[falsey]: https://developer.mozilla.org/en-US/docs/Glossary/Falsy +[logical-not]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_NOT +[arrow-function]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions diff --git a/exercises/practice/leap/.approaches/ternary-operator/snippet.txt b/exercises/practice/leap/.approaches/ternary-operator/snippet.txt new file mode 100644 index 0000000000..98cd0b9efd --- /dev/null +++ b/exercises/practice/leap/.approaches/ternary-operator/snippet.txt @@ -0,0 +1,3 @@ +export function isLeap(year) { + return year % 100 == 0 ? year % 400 == 0 : year % 4 == 0; +} diff --git a/exercises/practice/leap/.docs/instructions.md b/exercises/practice/leap/.docs/instructions.md index dcd92502c6..b14f8565d6 100644 --- a/exercises/practice/leap/.docs/instructions.md +++ b/exercises/practice/leap/.docs/instructions.md @@ -1,24 +1,3 @@ # Instructions -Given a year, report if it is a leap year. - -The tricky thing here is that a leap year in the Gregorian calendar occurs: - -```text -on every year that is evenly divisible by 4 - except every year that is evenly divisible by 100 - unless the year is also evenly divisible by 400 -``` - -For example, 1997 is not a leap year, but 1996 is. 1900 is not a leap -year, but 2000 is. - -## Notes - -Though our exercise adopts some very simple rules, there is more to -learn! - -For a delightful, four minute explanation of the whole leap year -phenomenon, go watch [this youtube video][video]. - -[video]: http://www.youtube.com/watch?v=xX96xng7sAE +Your task is to determine whether a given year is a leap year. diff --git a/exercises/practice/leap/.docs/introduction.md b/exercises/practice/leap/.docs/introduction.md new file mode 100644 index 0000000000..4ffd2da594 --- /dev/null +++ b/exercises/practice/leap/.docs/introduction.md @@ -0,0 +1,16 @@ +# Introduction + +A leap year (in the Gregorian calendar) occurs: + +- In every year that is evenly divisible by 4. +- Unless the year is evenly divisible by 100, in which case it's only a leap year if the year is also evenly divisible by 400. + +Some examples: + +- 1997 was not a leap year as it's not divisible by 4. +- 1900 was not a leap year as it's not divisible by 400. +- 2000 was a leap year! + +~~~~exercism/note +For a delightful, four-minute explanation of the whole phenomenon of leap years, check out [this YouTube video](https://www.youtube.com/watch?v=xX96xng7sAE). +~~~~ diff --git a/exercises/practice/leap/.eslintrc b/exercises/practice/leap/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/leap/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/leap/.gitignore b/exercises/practice/leap/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/leap/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/leap/.meta/config.json b/exercises/practice/leap/.meta/config.json index df271b7190..31a9ebb6f6 100644 --- a/exercises/practice/leap/.meta/config.json +++ b/exercises/practice/leap/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Given a year, report if it is a leap year.", - "authors": ["rchavarria"], + "authors": [ + "rchavarria" + ], "contributors": [ "ankorGH", "draalger", @@ -14,10 +15,23 @@ "xarxziux" ], "files": { - "solution": ["leap.js"], - "test": ["leap.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "leap.js" + ], + "test": [ + "leap.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, - "source": "JavaRanch Cattle Drive, exercise 3", - "source_url": "http://www.javaranch.com/leap.jsp" + "blurb": "Determine whether a given year is a leap year.", + "source": "CodeRanch Cattle Drive, Assignment 3", + "source_url": "https://web.archive.org/web/20240907033714/https://coderanch.com/t/718816/Leap", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/leap/babel.config.js b/exercises/practice/leap/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/leap/babel.config.js +++ b/exercises/practice/leap/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/leap/eslint.config.mjs b/exercises/practice/leap/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/leap/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/leap/jest.config.js b/exercises/practice/leap/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/leap/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/leap/leap.js b/exercises/practice/leap/leap.js index 1b8ccc9f87..1e481585af 100644 --- a/exercises/practice/leap/leap.js +++ b/exercises/practice/leap/leap.js @@ -4,5 +4,5 @@ // export const isLeap = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/leap/leap.spec.js b/exercises/practice/leap/leap.spec.js index b0d3c7b02f..3aba100a2c 100644 --- a/exercises/practice/leap/leap.spec.js +++ b/exercises/practice/leap/leap.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { isLeap } from './leap'; describe('A leap year', () => { diff --git a/exercises/practice/leap/package.json b/exercises/practice/leap/package.json index 99ef7e57ef..510fbfefff 100644 --- a/exercises/practice/leap/package.json +++ b/exercises/practice/leap/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/leap" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/ledger/.docs/instructions.md b/exercises/practice/ledger/.docs/instructions.md new file mode 100644 index 0000000000..a53e5c15e3 --- /dev/null +++ b/exercises/practice/ledger/.docs/instructions.md @@ -0,0 +1,14 @@ +# Instructions + +Refactor a ledger printer. + +The ledger exercise is a refactoring exercise. +There is code that prints a nicely formatted ledger, given a locale (American or Dutch) and a currency (US dollar or euro). +The code however is rather badly written, though (somewhat surprisingly) it consistently passes the test suite. + +Rewrite this code. +Remember that in refactoring the trick is to make small steps that keep the tests passing. +That way you can always quickly go back to a working version. +Version control tools like git can help here as well. + +Please keep a log of what changes you've made and make a comment on the exercise containing that log, this will help reviewers. diff --git a/exercises/practice/ledger/.gitignore b/exercises/practice/ledger/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/ledger/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/ledger/.meta/config.json b/exercises/practice/ledger/.meta/config.json new file mode 100644 index 0000000000..4e0ae63eb6 --- /dev/null +++ b/exercises/practice/ledger/.meta/config.json @@ -0,0 +1,17 @@ +{ + "authors": [ + "Cool-Katt" + ], + "files": { + "solution": [ + "ledger.js" + ], + "test": [ + "ledger.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] + }, + "blurb": "Refactor a ledger printer." +} diff --git a/exercises/practice/ledger/.meta/proof.ci.js b/exercises/practice/ledger/.meta/proof.ci.js new file mode 100644 index 0000000000..bfb4709ed3 --- /dev/null +++ b/exercises/practice/ledger/.meta/proof.ci.js @@ -0,0 +1,99 @@ +class LedgerEntry { + constructor(date, description, change) { + this.date = new Date(date); + this.description = description; + this.change = change; + } +} + +class FormattedLedgerEntry { + constructor(entry, locale, dateFormat, currencyFormat) { + this.entry = entry; + this.locale = locale; + this.dateFormat = dateFormat; + this.currencyFormat = currencyFormat; + } + + date() { + return this.entry.date.toLocaleDateString(this.locale, this.dateFormat); + } + + description(length = 25) { + if (this.entry.description.length > length) { + return `${this.entry.description.substring(0, length - 3)}...`; + } + + return this.entry.description.padEnd(length, ' '); + } + + change(offset = 13) { + const formatted = (this.entry.change / 100).toLocaleString( + this.locale, + this.currencyFormat, + ); + + const trailingSpace = formatted.includes(')') ? '' : ' '; + return `${formatted}${trailingSpace}`.padStart(offset, ' '); + } + + toTableRow() { + return [this.date(), this.description(), this.change()].join(' | '); + } +} + +const OPTIONS = { + HEADERS: { + 'en-US': ['Date', 'Description', 'Change'], + 'nl-NL': ['Datum', 'Omschrijving', 'Verandering'], + }, + headerRow: function (locale) { + const [date, description, change] = this.HEADERS[locale]; + return [ + date.padEnd(10, ' '), + description.padEnd(25, ' '), + change.padEnd(13, ' '), + ].join(' | '); + }, + dateFormatOptions: function () { + return { + day: '2-digit', + month: '2-digit', + year: 'numeric', + }; + }, + currencyFormatOptions: function (currency, locale) { + return { + style: 'currency', + currency: currency, + currencySign: locale === 'en-US' ? 'accounting' : 'standard', + currencyDisplay: locale === 'en-US' ? 'symbol' : 'narrowSymbol', + }; + }, +}; + +export const createEntry = (date, description, change) => + new LedgerEntry(date, description, change); + +export function formatEntries(currency, locale, entries) { + let dateFormat = OPTIONS.dateFormatOptions(); + let currencyFormat = OPTIONS.currencyFormatOptions(currency, locale); + + let rows = entries + .sort( + (a, b) => + a.date - b.date || + a.change - b.change || + a.description.localeCompare(b.description), + ) + .map((entry) => { + let formattedEntry = new FormattedLedgerEntry( + entry, + locale, + dateFormat, + currencyFormat, + ); + return formattedEntry.toTableRow(); + }); + + return [OPTIONS.headerRow(locale), ...rows].join('\n'); +} diff --git a/exercises/practice/ledger/.meta/tests.toml b/exercises/practice/ledger/.meta/tests.toml new file mode 100644 index 0000000000..4ea45ceb12 --- /dev/null +++ b/exercises/practice/ledger/.meta/tests.toml @@ -0,0 +1,48 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[d131ecae-a30e-436c-b8f3-858039a27234] +description = "empty ledger" + +[ce4618d2-9379-4eca-b207-9df1c4ec8aaa] +description = "one entry" + +[8d02e9cb-e6ee-4b77-9ce4-e5aec8eb5ccb] +description = "credit and debit" + +[502c4106-0371-4e7c-a7d8-9ce33f16ccb1] +description = "multiple entries on same date ordered by description" +include = false + +[29dd3659-6c2d-4380-94a8-6d96086e28e1] +description = "final order tie breaker is change" + +[9b9712a6-f779-4f5c-a759-af65615fcbb9] +description = "overlong description is truncated" + +[67318aad-af53-4f3d-aa19-1293b4d4c924] +description = "euros" + +[bdc499b6-51f5-4117-95f2-43cb6737208e] +description = "Dutch locale" + +[86591cd4-1379-4208-ae54-0ee2652b4670] +description = "Dutch locale and euros" + +[876bcec8-d7d7-4ba4-82bd-b836ac87c5d2] +description = "Dutch negative number with 3 digits before decimal point" + +[29670d1c-56be-492a-9c5e-427e4b766309] +description = "American negative number with 3 digits before decimal point" + +[9c70709f-cbbd-4b3b-b367-81d7c6101de4] +description = "multiple entries on same date ordered by description" +reimplements = "502c4106-0371-4e7c-a7d8-9ce33f16ccb1" diff --git a/exercises/practice/ledger/.npmrc b/exercises/practice/ledger/.npmrc new file mode 100644 index 0000000000..d26df800bb --- /dev/null +++ b/exercises/practice/ledger/.npmrc @@ -0,0 +1 @@ +audit=false diff --git a/exercises/practice/ledger/LICENSE b/exercises/practice/ledger/LICENSE new file mode 100644 index 0000000000..90e73be03b --- /dev/null +++ b/exercises/practice/ledger/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Exercism + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/exercises/practice/ledger/babel.config.js b/exercises/practice/ledger/babel.config.js new file mode 100644 index 0000000000..a638497df1 --- /dev/null +++ b/exercises/practice/ledger/babel.config.js @@ -0,0 +1,4 @@ +module.exports = { + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], +}; diff --git a/exercises/practice/ledger/eslint.config.mjs b/exercises/practice/ledger/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/ledger/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/ledger/jest.config.js b/exercises/practice/ledger/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/ledger/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/ledger/ledger.js b/exercises/practice/ledger/ledger.js new file mode 100644 index 0000000000..97bf4e0175 --- /dev/null +++ b/exercises/practice/ledger/ledger.js @@ -0,0 +1,163 @@ +class LedgerEntry { + constructor() { + this.date = undefined; + this.description = undefined; + this.change = undefined; + } +} + +export function createEntry(date, description, change) { + let entry = new LedgerEntry(); + entry.date = new Date(date); + entry.description = description; + entry.change = change; + return entry; +} + +export function formatEntries(currency, locale, entries) { + let table = ''; + if (locale === 'en-US') { + // Generate Header Row + table += + 'Date'.padEnd(10, ' ') + + ' | ' + + 'Description'.padEnd(25, ' ') + + ' | ' + + 'Change'.padEnd(13, ' ') + + '\n'; + + // Sort entries + entries.sort( + (a, b) => + a.date - b.date || + a.change - b.change || + a.description.localeCompare(b.description), + ); + + entries.forEach((entry) => { + // Write entry date to table + const dateStr = `${(entry.date.getMonth() + 1) + .toString() + .padStart(2, '0')}/${entry.date + .getDate() + .toString() + .padStart(2, '0')}/${entry.date.getFullYear()}`; + table += `${dateStr} | `; + + // Write entry description to table + const truncatedDescription = + entry.description.length > 25 + ? `${entry.description.substring(0, 22)}...` + : entry.description.padEnd(25, ' '); + table += `${truncatedDescription} | `; + + // Write entry change to table + let changeStr = ''; + if (currency === 'USD') { + let formatingOptions = { + style: 'currency', + currency: 'USD', + //currencySign: 'accounting', + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }; + if (entry.change < 0) { + changeStr = `(${Math.abs(entry.change / 100).toLocaleString( + 'en-US', + formatingOptions, + )})`; + } else { + changeStr = `${(entry.change / 100).toLocaleString( + 'en-US', + formatingOptions, + )} `; + } + } else if (currency === 'EUR') { + let formatingOptions = { + style: 'currency', + currency: 'EUR', + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }; + if (entry.change < 0) { + changeStr = `(${Math.abs(entry.change / 100).toLocaleString( + 'en-US', + formatingOptions, + )})`; + } else { + changeStr = `${(entry.change / 100).toLocaleString( + 'en-US', + formatingOptions, + )} `; + } + } + table += changeStr.padStart(13, ' '); + table += '\n'; + }); + } else if (locale === 'nl-NL') { + // Generate Header Row + table += + 'Datum'.padEnd(10, ' ') + + ' | ' + + 'Omschrijving'.padEnd(25, ' ') + + ' | ' + + 'Verandering'.padEnd(13, ' ') + + '\n'; + + // Sort entries + entries.sort( + (a, b) => + a.date - b.date || + a.change - b.change || + a.description.localeCompare(b.description), + ); + + entries.forEach((entry) => { + // Write entry date to table + const dateStr = `${entry.date.getDate().toString().padStart(2, '0')}-${( + entry.date.getMonth() + 1 + ) + .toString() + .padStart(2, '0')}-${entry.date.getFullYear()}`; + table += `${dateStr} | `; + + // Write entry description to table + const truncatedDescription = + entry.description.length > 25 + ? `${entry.description.substring(0, 22)}...` + : entry.description.padEnd(25, ' '); + table += `${truncatedDescription} | `; + + // Write entry change to table + let changeStr = ''; + if (currency === 'USD') { + let formatingOptions = { + style: 'currency', + currency: 'USD', + currencyDisplay: 'narrowSymbol', + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }; + changeStr = `${(entry.change / 100).toLocaleString( + 'nl-NL', + formatingOptions, + )} `; + } else if (currency === 'EUR') { + let formatingOptions = { + style: 'currency', + currency: 'EUR', + currencyDisplay: 'narrowSymbol', + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }; + changeStr = `${(entry.change / 100).toLocaleString( + 'nl-NL', + formatingOptions, + )} `; + } + table += changeStr.padStart(13, ' '); + table += '\n'; + }); + } + return table.replace(/\n$/, ''); +} diff --git a/exercises/practice/ledger/ledger.spec.js b/exercises/practice/ledger/ledger.spec.js new file mode 100644 index 0000000000..1d9fa4053f --- /dev/null +++ b/exercises/practice/ledger/ledger.spec.js @@ -0,0 +1,140 @@ +import { describe, expect, test, xtest } from '@jest/globals'; +import { createEntry, formatEntries } from './ledger'; + +describe('Ledger', () => { + test('empty ledger', () => { + let currency = 'USD'; + let locale = 'en-US'; + let entries = []; + let expected = [ + 'Date | Description | Change ', + ].join('\n'); + expect(formatEntries(currency, locale, entries)).toEqual(expected); + }); + + xtest('one entry', () => { + let currency = 'USD'; + let locale = 'en-US'; + let entries = [createEntry('2015-01-01', 'Buy present', -1000)]; + let expected = [ + 'Date | Description | Change ', + '01/01/2015 | Buy present | ($10.00)', + ].join('\n'); + expect(formatEntries(currency, locale, entries)).toEqual(expected); + }); + + xtest('credit and debit', () => { + let currency = 'USD'; + let locale = 'en-US'; + let entries = [ + createEntry('2015-01-02', 'Get present', 1000), + createEntry('2015-01-01', 'Buy present', -1000), + ]; + let expected = [ + 'Date | Description | Change ', + '01/01/2015 | Buy present | ($10.00)', + '01/02/2015 | Get present | $10.00 ', + ].join('\n'); + expect(formatEntries(currency, locale, entries)).toEqual(expected); + }); + + xtest('final order tie breaker is change', () => { + let currency = 'USD'; + let locale = 'en-US'; + let entries = [ + createEntry('2015-01-01', 'Something', 0), + createEntry('2015-01-01', 'Something', -1), + createEntry('2015-01-01', 'Something', 1), + ]; + let expected = [ + 'Date | Description | Change ', + '01/01/2015 | Something | ($0.01)', + '01/01/2015 | Something | $0.00 ', + '01/01/2015 | Something | $0.01 ', + ].join('\n'); + expect(formatEntries(currency, locale, entries)).toEqual(expected); + }); + + xtest('overlong description is truncated', () => { + let currency = 'USD'; + let locale = 'en-US'; + let entries = [ + createEntry('2015-01-01', 'Freude schoner Gotterfunken', -123456), + ]; + let expected = [ + 'Date | Description | Change ', + '01/01/2015 | Freude schoner Gotterf... | ($1,234.56)', + ].join('\n'); + expect(formatEntries(currency, locale, entries)).toEqual(expected); + }); + + xtest('euros', () => { + let currency = 'EUR'; + let locale = 'en-US'; + let entries = [createEntry('2015-01-01', 'Buy present', -1000)]; + let expected = [ + 'Date | Description | Change ', + '01/01/2015 | Buy present | (€10.00)', + ].join('\n'); + expect(formatEntries(currency, locale, entries)).toEqual(expected); + }); + + xtest('Dutch locale', () => { + let currency = 'USD'; + let locale = 'nl-NL'; + let entries = [createEntry('2015-03-12', 'Buy present', 123456)]; + let expected = [ + 'Datum | Omschrijving | Verandering ', + '12-03-2015 | Buy present | $ 1.234,56 ', + ].join('\n'); + expect(formatEntries(currency, locale, entries)).toEqual(expected); + }); + + xtest('Dutch locale and euros', () => { + let currency = 'EUR'; + let locale = 'nl-NL'; + let entries = [createEntry('2015-03-12', 'Buy present', 123456)]; + let expected = [ + 'Datum | Omschrijving | Verandering ', + '12-03-2015 | Buy present | € 1.234,56 ', + ].join('\n'); + expect(formatEntries(currency, locale, entries)).toEqual(expected); + }); + + xtest('Dutch negative number with 3 digits before decimal point', () => { + let currency = 'USD'; + let locale = 'nl-NL'; + let entries = [createEntry('2015-03-12', 'Buy present', -12345)]; + let expected = [ + 'Datum | Omschrijving | Verandering ', + '12-03-2015 | Buy present | $ -123,45 ', + ].join('\n'); + expect(formatEntries(currency, locale, entries)).toEqual(expected); + }); + + xtest('American negative number with 3 digits before decimal point', () => { + let currency = 'USD'; + let locale = 'en-US'; + let entries = [createEntry('2015-03-12', 'Buy present', -12345)]; + let expected = [ + 'Date | Description | Change ', + '03/12/2015 | Buy present | ($123.45)', + ].join('\n'); + expect(formatEntries(currency, locale, entries)).toEqual(expected); + }); + + xtest('multiple entries on same date ordered by description', () => { + let currency = 'USD'; + let locale = 'en-US'; + let entries = [ + createEntry('2015-01-01', 'Get present', 1000), + createEntry('2015-01-01', 'Buy present', -1000), + ]; + let expected = [ + 'Date | Description | Change ', + '01/01/2015 | Buy present | ($10.00)', + '01/01/2015 | Get present | $10.00 ', + ].join('\n'); + expect(formatEntries(currency, locale, entries)).toEqual(expected); + }); +}); diff --git a/exercises/practice/ledger/package.json b/exercises/practice/ledger/package.json new file mode 100644 index 0000000000..7016e140d8 --- /dev/null +++ b/exercises/practice/ledger/package.json @@ -0,0 +1,39 @@ +{ + "name": "@exercism/javascript-ledger", + "description": "Exercism practice exercise on ledger", + "author": "Katrina Owen", + "contributors": [ + "Cool-Katt (https://github.com/Cool-Katt)", + "Derk-Jan Karrenbeld (https://derk-jan.com)", + "Tejas Bubane (https://tejasbubane.github.io/)" + ], + "private": true, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/exercism/javascript", + "directory": "exercises/practice/ledger" + }, + "devDependencies": { + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" + }, + "dependencies": {}, + "scripts": { + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" +} diff --git a/exercises/practice/lens-person/.docs/instructions.md b/exercises/practice/lens-person/.docs/instructions.md new file mode 100644 index 0000000000..c7c707e249 --- /dev/null +++ b/exercises/practice/lens-person/.docs/instructions.md @@ -0,0 +1,15 @@ +# Instructions + +Use lenses to update nested records (specific to languages with immutable data). + +Updating fields of nested, immutable records is kind of annoying. +The code for such cases is as cumbersome as the structure is deep. +If you have, say, a Person, that contains an Address, which has a Street, that has a Number, updating the Number requires creating a new Street with the new Number, then a new Address with the new Street and, finally, a new Person with the new Address. +Confused already? + +One solution to this problem is to use [lenses][lenses]. + +Implement several record accessing functions using lenses. +The test suite also allows you to avoid lenses altogether so you can experiment with different approaches. + +[lenses]: https://en.wikibooks.org/wiki/Haskell/Lenses_and_functional_references diff --git a/exercises/practice/lens-person/.docs/introduction.md b/exercises/practice/lens-person/.docs/introduction.md new file mode 100644 index 0000000000..2cc3a206d7 --- /dev/null +++ b/exercises/practice/lens-person/.docs/introduction.md @@ -0,0 +1,7 @@ +In JavaScript, lenses are a functional programming concept that allows you to access and modify data in a modular and immutable way. They are essentially composable pairs of pure getter and setter functions that focus on a particular field inside an object. + +Lenses can be used to simplify code, make it more reusable, and avoid common programming errors. For example, lenses can be used to: + +- Access and modify nested data structures without having to worry about the specific structure of the data. +- Update data in a pure way, without mutating the original object. +- Compose multiple lenses together to create more complex lenses that can access and modify data in a variety of ways. \ No newline at end of file diff --git a/exercises/practice/lens-person/.gitignore b/exercises/practice/lens-person/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/lens-person/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/lens-person/.meta/config.json b/exercises/practice/lens-person/.meta/config.json new file mode 100644 index 0000000000..3209c732ad --- /dev/null +++ b/exercises/practice/lens-person/.meta/config.json @@ -0,0 +1,28 @@ +{ + "authors": [ + "sarava338", + "Cool-Katt" + ], + "contributors": [ + "themetar" + ], + "files": { + "solution": [ + "lens-person.js" + ], + "test": [ + "lens-person.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ], + "editor": [ + "address.js", + "born.js", + "lens.js", + "name.js", + "person.js" + ] + }, + "blurb": "Use lenses to update nested records (specific to languages with immutable data)." +} diff --git a/exercises/practice/lens-person/.meta/proof.ci.js b/exercises/practice/lens-person/.meta/proof.ci.js new file mode 100644 index 0000000000..10e80198ca --- /dev/null +++ b/exercises/practice/lens-person/.meta/proof.ci.js @@ -0,0 +1,39 @@ +/* eslint-disable no-unused-vars */ +import { Address } from '../address'; +import { Born } from '../born'; +import { Lens } from '../lens'; +import { Name } from '../name'; +import { Person } from '../person'; + +// Implement the nameLens with the getter and setter +export const nameLens = new Lens( + (person) => person.name, + (person, name) => new Person(name, person.born, person.address), +); + +// Implement the bornAtLens with the getter and setter +export const bornAtLens = new Lens( + (person) => person.born.bornAt, + (person, bornAt) => + new Person( + person.name, + new Born(bornAt, person.born.bornOn), + person.address, + ), +); + +// Implement the streetLens with the getter and setter +export const streetLens = new Lens( + (person) => person.address.street, + (person, street) => + new Person( + person.name, + person.born, + new Address( + person.address.houseNumber, + street, + person.address.place, + person.address.country, + ), + ), +); diff --git a/exercises/practice/lens-person/.npmrc b/exercises/practice/lens-person/.npmrc new file mode 100644 index 0000000000..d26df800bb --- /dev/null +++ b/exercises/practice/lens-person/.npmrc @@ -0,0 +1 @@ +audit=false diff --git a/exercises/practice/lens-person/LICENSE b/exercises/practice/lens-person/LICENSE new file mode 100644 index 0000000000..90e73be03b --- /dev/null +++ b/exercises/practice/lens-person/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Exercism + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/exercises/practice/lens-person/address.js b/exercises/practice/lens-person/address.js new file mode 100644 index 0000000000..8920561a85 --- /dev/null +++ b/exercises/practice/lens-person/address.js @@ -0,0 +1,15 @@ +export class Address { + /** + * + * @param {number} houseNumber + * @param {string} street + * @param {string} place + * @param {string} country + */ + constructor(houseNumber, street, place, country) { + this.houseNumber = houseNumber; + this.street = street; + this.place = place; + this.country = country; + } +} diff --git a/exercises/practice/lens-person/babel.config.js b/exercises/practice/lens-person/babel.config.js new file mode 100644 index 0000000000..a638497df1 --- /dev/null +++ b/exercises/practice/lens-person/babel.config.js @@ -0,0 +1,4 @@ +module.exports = { + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], +}; diff --git a/exercises/practice/lens-person/born.js b/exercises/practice/lens-person/born.js new file mode 100644 index 0000000000..a949d89daa --- /dev/null +++ b/exercises/practice/lens-person/born.js @@ -0,0 +1,11 @@ +export class Born { + /** + * + * @param {Address} bornAt + * @param {Date} bornOn + */ + constructor(bornAt, bornOn) { + this.bornAt = bornAt; + this.bornOn = bornOn; + } +} diff --git a/exercises/practice/lens-person/eslint.config.mjs b/exercises/practice/lens-person/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/lens-person/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/lens-person/jest.config.js b/exercises/practice/lens-person/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/lens-person/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/lens-person/lens-person.js b/exercises/practice/lens-person/lens-person.js new file mode 100644 index 0000000000..e5fb820a18 --- /dev/null +++ b/exercises/practice/lens-person/lens-person.js @@ -0,0 +1,41 @@ +// +// This is only a SKELETON file for the 'Lens Person' exercise. It's been provided as a +// convenience to get you started writing code faster. +// + +/* eslint-disable no-unused-vars */ +import { Person } from './person'; +import { Name } from './name'; +import { Born } from './born'; +import { Address } from './address'; +import { Lens } from './lens'; + +// Implement the nameLens with the getter and setter +export const nameLens = new Lens( + () => { + throw new Error('Remove this line and implement the function'); + }, + () => { + throw new Error('Remove this line and implement the function'); + }, +); + +// Implement the bornAtLens with the getter and setter +export const bornAtLens = new Lens( + () => { + throw new Error('Remove this line and implement the function'); + }, + () => { + throw new Error('Remove this line and implement the function'); + }, +); + +// Implement the streetLens with the getter and setter +export const streetLens = new Lens( + () => { + throw new Error('Remove this line and implement the function'); + }, + () => { + throw new Error('Remove this line and implement the function'); + }, +); diff --git a/exercises/practice/lens-person/lens-person.spec.js b/exercises/practice/lens-person/lens-person.spec.js new file mode 100644 index 0000000000..6788eb043e --- /dev/null +++ b/exercises/practice/lens-person/lens-person.spec.js @@ -0,0 +1,89 @@ +import { describe, expect, test, xtest } from '@jest/globals'; +import { Address } from './address'; +import { Born } from './born'; +import { Name } from './name'; +import { Person } from './person'; + +import { bornAtLens, nameLens, streetLens } from './lens-person'; + +// test data +const person = new Person( + new Name('Saravanan', 'Lakshmanan'), + new Born( + new Address(100, 'Hospital street', 'Tamil Nadu', 'India'), + new Date(), + ), + new Address(1, 'Coder street', 'Tamil Nadu', 'India'), +); + +// test suite for nameLens +describe('nameLens', () => { + test('should get the name of the person', () => { + expect(nameLens.get(person)).toEqual(person.name); + }); + + xtest('should set a new forename for the person', () => { + const updatedPerson = nameLens.set(person, new Name('Sara', 'Lakshmanan')); + expect(nameLens.get(updatedPerson)).toEqual(updatedPerson.name); + }); + + xtest('should set a new surname for the person', () => { + const updatedPerson = nameLens.set(person, new Name('Saravanan', 'Laksh')); + expect(nameLens.get(updatedPerson)).toEqual(updatedPerson.name); + }); + + xtest('should ensure immutability when setting a new name', () => { + const originalName = new Name('Saravanan', 'Lakshmanan'); + nameLens.set(person, new Name('Subhash', 'Forst')); + expect(person.name).toStrictEqual(originalName); + }); +}); + +// Test suite for bornAtLens +describe('bornAtLens', () => { + xtest('should get the address for where the person was born', () => { + expect(bornAtLens.get(person)).toEqual(person.born.bornAt); + }); + + xtest('should set a new address for where the person was born', () => { + const updatedPerson = bornAtLens.set( + person, + new Address(2, 'Exercism street', 'Tamil Nadu', 'India'), + ); + expect(bornAtLens.get(updatedPerson)).toEqual(updatedPerson.born.bornAt); + }); + + xtest('should ensure immutability when setting a new birth address', () => { + const originalBirthAddress = new Address( + 100, + 'Hospital street', + 'Tamil Nadu', + 'India', + ); + bornAtLens.set(person, new Address(15, 'Clinic street', 'Kerala', 'India')); + expect(person.born.bornAt).toStrictEqual(originalBirthAddress); + }); +}); + +// Test suite for streetLens +describe('streetLens', () => { + xtest('should get the current street of the person', () => { + expect(streetLens.get(person)).toEqual(person.address.street); + }); + + xtest('should set a new street for the current address of the person', () => { + const updatedPerson = streetLens.set(person, 'Exercism street'); + expect(streetLens.get(updatedPerson)).toEqual(updatedPerson.address.street); + }); + + xtest('should ensure immutability when setting a new street', () => { + const originalAddress = new Address( + 1, + 'Coder street', + 'Tamil Nadu', + 'India', + ); + streetLens.set(person, 'Mimic street'); + expect(person.address).toStrictEqual(originalAddress); + }); +}); diff --git a/exercises/practice/lens-person/lens.js b/exercises/practice/lens-person/lens.js new file mode 100644 index 0000000000..7e6163cc12 --- /dev/null +++ b/exercises/practice/lens-person/lens.js @@ -0,0 +1,11 @@ +export class Lens { + /** + * + * @param {Function} getter - Function to get the value using a lens + * @param {Function} setter - Function to set the value using a lens + */ + constructor(getter, setter) { + this.get = getter; + this.set = setter; + } +} diff --git a/exercises/practice/lens-person/name.js b/exercises/practice/lens-person/name.js new file mode 100644 index 0000000000..d254e2552a --- /dev/null +++ b/exercises/practice/lens-person/name.js @@ -0,0 +1,11 @@ +export class Name { + /** + * + * @param {string} forename + * @param {string} surname + */ + constructor(forename, surname) { + this.forename = forename; + this.surname = surname; + } +} diff --git a/exercises/practice/lens-person/package.json b/exercises/practice/lens-person/package.json new file mode 100644 index 0000000000..051b86eba4 --- /dev/null +++ b/exercises/practice/lens-person/package.json @@ -0,0 +1,40 @@ +{ + "name": "@exercism/javascript-lens-person", + "description": "Exercism practice exercise on lens-person", + "author": "Katrina Owen", + "contributors": [ + "Derk-Jan Karrenbeld (https://derk-jan.com)", + "Tejas Bubane (https://tejasbubane.github.io/)", + "Cool-Katt (https://github.com/Cool-Katt)", + "sarava338 (https://github.com/sarava338)" + ], + "private": true, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/exercism/javascript", + "directory": "exercises/practice/lens-person" + }, + "devDependencies": { + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" + }, + "dependencies": {}, + "scripts": { + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" +} diff --git a/exercises/practice/lens-person/person.js b/exercises/practice/lens-person/person.js new file mode 100644 index 0000000000..0cce5f6b3b --- /dev/null +++ b/exercises/practice/lens-person/person.js @@ -0,0 +1,12 @@ +export class Person { + /** + * @param {Name} name + * @param {Born} born + * @param {Address} address + */ + constructor(name, born, address) { + this.name = name; + this.born = born; + this.address = address; + } +} diff --git a/exercises/practice/linked-list/.docs/instructions.md b/exercises/practice/linked-list/.docs/instructions.md index 5cb6a9b4e9..edf4055b38 100644 --- a/exercises/practice/linked-list/.docs/instructions.md +++ b/exercises/practice/linked-list/.docs/instructions.md @@ -1,28 +1,26 @@ # Instructions -Implement a doubly linked list. +Your team has decided to use a doubly linked list to represent each train route in the schedule. +Each station along the train's route will be represented by a node in the linked list. -Like an array, a linked list is a simple linear data structure. Several -common data types can be implemented using linked lists, like queues, -stacks, and associative arrays. +You don't need to worry about arrival and departure times at the stations. +Each station will simply be represented by a number. -A linked list is a collection of data elements called _nodes_. In a -_singly linked list_ each node holds a value and a link to the next node. -In a _doubly linked list_ each node also holds a link to the previous -node. +Routes can be extended, adding stations to the beginning or end of a route. +They can also be shortened by removing stations from the beginning or the end of a route. -You will write an implementation of a doubly linked list. Implement a -Node to hold a value and pointers to the next and previous nodes. Then -implement a List which holds references to the first and last node and -offers an array-like interface for adding and removing items: +Sometimes a station gets closed down, and in that case the station needs to be removed from the route, even if it is not at the beginning or end of the route. -- `push` (_insert value at back_); -- `pop` (_remove value at back_); -- `shift` (_remove value at front_). -- `unshift` (_insert value at front_); +The size of a route is measured not by how far the train travels, but by how many stations it stops at. -To keep your implementation simple, the tests will not cover error -conditions. Specifically: `pop` or `shift` will never be called on an -empty list. +~~~~exercism/note +The linked list is a fundamental data structure in computer science, often used in the implementation of other data structures. +As the name suggests, it is a list of nodes that are linked together. +It is a list of "nodes", where each node links to its neighbor or neighbors. +In a **singly linked list** each node links only to the node that follows it. +In a **doubly linked list** each node links to both the node that comes before, as well as the node that comes after. -If you want to know more about linked lists, check [Wikipedia](https://en.wikipedia.org/wiki/Linked_list). +If you want to dig deeper into linked lists, check out [this article][intro-linked-list] that explains it using nice drawings. + +[intro-linked-list]: https://medium.com/basecs/whats-a-linked-list-anyway-part-1-d8b7e6508b9d +~~~~ diff --git a/exercises/practice/linked-list/.docs/introduction.md b/exercises/practice/linked-list/.docs/introduction.md new file mode 100644 index 0000000000..6e83ae7b6e --- /dev/null +++ b/exercises/practice/linked-list/.docs/introduction.md @@ -0,0 +1,6 @@ +# Introduction + +You are working on a project to develop a train scheduling system for a busy railway network. + +You've been asked to develop a prototype for the train routes in the scheduling system. +Each route consists of a sequence of train stations that a given train stops at. diff --git a/exercises/practice/linked-list/.eslintrc b/exercises/practice/linked-list/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/linked-list/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/linked-list/.gitignore b/exercises/practice/linked-list/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/linked-list/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/linked-list/.meta/config.json b/exercises/practice/linked-list/.meta/config.json index 303baa9708..47f4e478fa 100644 --- a/exercises/practice/linked-list/.meta/config.json +++ b/exercises/practice/linked-list/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Implement a doubly linked list", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "ankorGH", "bdjnk", @@ -14,9 +15,22 @@ "thanhcng" ], "files": { - "solution": ["linked-list.js"], - "test": ["linked-list.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "linked-list.js" + ], + "test": [ + "linked-list.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, - "source": "Classic computer science topic" + "blurb": "Implement a doubly linked list.", + "source": "Classic computer science topic", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/linked-list/.meta/proof.ci.js b/exercises/practice/linked-list/.meta/proof.ci.js index b957a7b688..cf0640085b 100644 --- a/exercises/practice/linked-list/.meta/proof.ci.js +++ b/exercises/practice/linked-list/.meta/proof.ci.js @@ -92,7 +92,6 @@ export class LinkedList { while (element) { if (element.value !== value) { element = element.next; - // eslint-disable-next-line no-continue continue; } diff --git a/exercises/practice/linked-list/.meta/tests.toml b/exercises/practice/linked-list/.meta/tests.toml new file mode 100644 index 0000000000..96906d2cc7 --- /dev/null +++ b/exercises/practice/linked-list/.meta/tests.toml @@ -0,0 +1,67 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[7f7e3987-b954-41b8-8084-99beca08752c] +description = "pop gets element from the list" + +[c3f67e5d-cfa2-4c3e-a18f-7ce999c3c885] +description = "push/pop respectively add/remove at the end of the list" + +[00ea24ce-4f5c-4432-abb4-cc6e85462657] +description = "shift gets an element from the list" + +[37962ee0-3324-4a29-b588-5a4c861e6564] +description = "shift gets first element from the list" + +[30a3586b-e9dc-43fb-9a73-2770cec2c718] +description = "unshift adds element at start of the list" + +[042f71e4-a8a7-4cf0-8953-7e4f3a21c42d] +description = "pop, push, shift, and unshift can be used in any order" + +[88f65c0c-4532-4093-8295-2384fb2f37df] +description = "count an empty list" + +[fc055689-5cbe-4cd9-b994-02e2abbb40a5] +description = "count a list with items" + +[8272cef5-130d-40ea-b7f6-5ffd0790d650] +description = "count is correct after mutation" + +[229b8f7a-bd8a-4798-b64f-0dc0bb356d95] +description = "popping to empty doesn't break the list" + +[4e1948b4-514e-424b-a3cf-a1ebbfa2d1ad] +description = "shifting to empty doesn't break the list" + +[e8f7c600-d597-4f79-949d-8ad8bae895a6] +description = "deletes the only element" + +[fd65e422-51f3-45c0-9fd0-c33da638f89b] +description = "deletes the element with the specified value from the list" + +[59db191a-b17f-4ab7-9c5c-60711ec1d013] +description = "deletes the element with the specified value from the list, re-assigns tail" + +[58242222-5d39-415b-951d-8128247f8993] +description = "deletes the element with the specified value from the list, re-assigns head" + +[ee3729ee-3405-4bd2-9bad-de0d4aa5d647] +description = "deletes the first of two elements" + +[47e3b3b4-b82c-4c23-8c1a-ceb9b17cb9fb] +description = "deletes the second of two elements" + +[7b420958-f285-4922-b8f9-10d9dcab5179] +description = "delete does not modify the list if the element is not found" + +[7e04828f-6082-44e3-a059-201c63252a76] +description = "deletes only the first occurrence" diff --git a/exercises/practice/linked-list/babel.config.js b/exercises/practice/linked-list/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/linked-list/babel.config.js +++ b/exercises/practice/linked-list/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/linked-list/eslint.config.mjs b/exercises/practice/linked-list/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/linked-list/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/linked-list/jest.config.js b/exercises/practice/linked-list/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/linked-list/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/linked-list/linked-list.js b/exercises/practice/linked-list/linked-list.js index dc63705d8f..3fa5221d20 100644 --- a/exercises/practice/linked-list/linked-list.js +++ b/exercises/practice/linked-list/linked-list.js @@ -5,26 +5,26 @@ export class LinkedList { push() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } pop() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } shift() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } unshift() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } delete() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } count() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/linked-list/linked-list.spec.js b/exercises/practice/linked-list/linked-list.spec.js index d6a08dd5bd..feb682d58b 100644 --- a/exercises/practice/linked-list/linked-list.spec.js +++ b/exercises/practice/linked-list/linked-list.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { LinkedList } from './linked-list'; describe('LinkedList', () => { diff --git a/exercises/practice/linked-list/package.json b/exercises/practice/linked-list/package.json index f163417301..99318a1f74 100644 --- a/exercises/practice/linked-list/package.json +++ b/exercises/practice/linked-list/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/linked-list" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/list-ops/.docs/instructions.md b/exercises/practice/list-ops/.docs/instructions.md index 14e8f0d0ce..ebc5dffed0 100644 --- a/exercises/practice/list-ops/.docs/instructions.md +++ b/exercises/practice/list-ops/.docs/instructions.md @@ -2,19 +2,18 @@ Implement basic list operations. -In functional languages list operations like `length`, `map`, and -`reduce` are very common. Implement a series of basic list operations, -without using existing functions. +In functional languages list operations like `length`, `map`, and `reduce` are very common. +Implement a series of basic list operations, without using existing functions. -The precise number and names of the operations to be implemented will be -track dependent to avoid conflicts with existing names, but the general -operations you will implement include: +The precise number and names of the operations to be implemented will be track dependent to avoid conflicts with existing names, but the general operations you will implement include: - `append` (_given two lists, add all items in the second list to the end of the first list_); - `concatenate` (_given a series of lists, combine all items in all lists into one flattened list_); - `filter` (_given a predicate and a list, return the list of all items for which `predicate(item)` is True_); - `length` (_given a list, return the total number of items within it_); - `map` (_given a function and a list, return the list of the results of applying `function(item)` on all items_); -- `foldl` (_given a function, a list, and initial accumulator, fold (reduce) each item into the accumulator from the left using `function(accumulator, item)`_); -- `foldr` (_given a function, a list, and an initial accumulator, fold (reduce) each item into the accumulator from the right using `function(item, accumulator)`_); -- `reverse` (_given a list, return a list with all the original items, but in reversed order_); +- `foldl` (_given a function, a list, and initial accumulator, fold (reduce) each item into the accumulator from the left_); +- `foldr` (_given a function, a list, and an initial accumulator, fold (reduce) each item into the accumulator from the right_); +- `reverse` (_given a list, return a list with all the original items, but in reversed order_). + +Note, the ordering in which arguments are passed to the fold functions (`foldl`, `foldr`) is significant. diff --git a/exercises/practice/list-ops/.eslintrc b/exercises/practice/list-ops/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/list-ops/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/list-ops/.gitignore b/exercises/practice/list-ops/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/list-ops/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/list-ops/.meta/config.json b/exercises/practice/list-ops/.meta/config.json index 520c9ad1ef..ec8ee2f684 100644 --- a/exercises/practice/list-ops/.meta/config.json +++ b/exercises/practice/list-ops/.meta/config.json @@ -1,10 +1,12 @@ { - "blurb": "Implement basic list operations", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "ankorGH", "archanid", "hayashi-ay", + "jagdish-15", "paparomeo", "rchavarria", "SleeplessByte", @@ -12,8 +14,21 @@ "xarxziux" ], "files": { - "solution": ["list-ops.js"], - "test": ["list-ops.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "list-ops.js" + ], + "test": [ + "list-ops.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] + }, + "blurb": "Implement basic list operations.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false } } diff --git a/exercises/practice/list-ops/.meta/proof.ci.js b/exercises/practice/list-ops/.meta/proof.ci.js index ba18271555..1804f163ef 100644 --- a/exercises/practice/list-ops/.meta/proof.ci.js +++ b/exercises/practice/list-ops/.meta/proof.ci.js @@ -98,7 +98,7 @@ class Cons { filter(predicate) { return this.foldl( (result, item) => (predicate(item) && result.push(item)) || result, - Null + Null, ); } diff --git a/exercises/practice/list-ops/.meta/tests.toml b/exercises/practice/list-ops/.meta/tests.toml index fbe852b607..08b1edc044 100644 --- a/exercises/practice/list-ops/.meta/tests.toml +++ b/exercises/practice/list-ops/.meta/tests.toml @@ -1,69 +1,106 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [485b9452-bf94-40f7-a3db-c3cf4850066a] -description = "empty lists" +description = "append entries to a list and return the new list -> empty lists" [2c894696-b609-4569-b149-8672134d340a] -description = "list to empty list" +description = "append entries to a list and return the new list -> list to empty list" [e842efed-3bf6-4295-b371-4d67a4fdf19c] -description = "empty list to list" +description = "append entries to a list and return the new list -> empty list to list" [71dcf5eb-73ae-4a0e-b744-a52ee387922f] -description = "non-empty lists" +description = "append entries to a list and return the new list -> non-empty lists" [28444355-201b-4af2-a2f6-5550227bde21] -description = "empty list" +description = "concatenate a list of lists -> empty list" [331451c1-9573-42a1-9869-2d06e3b389a9] -description = "list of lists" +description = "concatenate a list of lists -> list of lists" [d6ecd72c-197f-40c3-89a4-aa1f45827e09] -description = "list of nested lists" +description = "concatenate a list of lists -> list of nested lists" [0524fba8-3e0f-4531-ad2b-f7a43da86a16] -description = "empty list" +description = "filter list returning only values that satisfy the filter function -> empty list" [88494bd5-f520-4edb-8631-88e415b62d24] -description = "non-empty list" +description = "filter list returning only values that satisfy the filter function -> non-empty list" [1cf0b92d-8d96-41d5-9c21-7b3c37cb6aad] -description = "empty list" +description = "returns the length of a list -> empty list" [d7b8d2d9-2d16-44c4-9a19-6e5f237cb71e] -description = "non-empty list" +description = "returns the length of a list -> non-empty list" [c0bc8962-30e2-4bec-9ae4-668b8ecd75aa] -description = "empty list" +description = "return a list of elements whose values equal the list value transformed by the mapping function -> empty list" [11e71a95-e78b-4909-b8e4-60cdcaec0e91] -description = "non-empty list" +description = "return a list of elements whose values equal the list value transformed by the mapping function -> non-empty list" [613b20b7-1873-4070-a3a6-70ae5f50d7cc] -description = "empty list" +description = "folds (reduces) the given list from the left with a function -> empty list" +include = false [e56df3eb-9405-416a-b13a-aabb4c3b5194] -description = "direction independent function applied to non-empty list" +description = "folds (reduces) the given list from the left with a function -> direction independent function applied to non-empty list" +include = false [d2cf5644-aee1-4dfc-9b88-06896676fe27] -description = "direction dependent function applied to non-empty list" +description = "folds (reduces) the given list from the left with a function -> direction dependent function applied to non-empty list" +include = false + +[36549237-f765-4a4c-bfd9-5d3a8f7b07d2] +description = "folds (reduces) the given list from the left with a function -> empty list" +reimplements = "613b20b7-1873-4070-a3a6-70ae5f50d7cc" + +[7a626a3c-03ec-42bc-9840-53f280e13067] +description = "folds (reduces) the given list from the left with a function -> direction independent function applied to non-empty list" +reimplements = "e56df3eb-9405-416a-b13a-aabb4c3b5194" + +[d7fcad99-e88e-40e1-a539-4c519681f390] +description = "folds (reduces) the given list from the left with a function -> direction dependent function applied to non-empty list" +reimplements = "d2cf5644-aee1-4dfc-9b88-06896676fe27" [aeb576b9-118e-4a57-a451-db49fac20fdc] -description = "empty list" +description = "folds (reduces) the given list from the right with a function -> empty list" +include = false [c4b64e58-313e-4c47-9c68-7764964efb8e] -description = "direction independent function applied to non-empty list" +description = "folds (reduces) the given list from the right with a function -> direction independent function applied to non-empty list" +include = false [be396a53-c074-4db3-8dd6-f7ed003cce7c] -description = "direction dependent function applied to non-empty list" +description = "folds (reduces) the given list from the right with a function -> direction dependent function applied to non-empty list" +include = false + +[17214edb-20ba-42fc-bda8-000a5ab525b0] +description = "folds (reduces) the given list from the right with a function -> empty list" +reimplements = "aeb576b9-118e-4a57-a451-db49fac20fdc" + +[e1c64db7-9253-4a3d-a7c4-5273b9e2a1bd] +description = "folds (reduces) the given list from the right with a function -> direction independent function applied to non-empty list" +reimplements = "c4b64e58-313e-4c47-9c68-7764964efb8e" + +[8066003b-f2ff-437e-9103-66e6df474844] +description = "folds (reduces) the given list from the right with a function -> direction dependent function applied to non-empty list" +reimplements = "be396a53-c074-4db3-8dd6-f7ed003cce7c" [94231515-050e-4841-943d-d4488ab4ee30] -description = "empty list" +description = "reverse the elements of the list -> empty list" [fcc03d1e-42e0-4712-b689-d54ad761f360] -description = "non-empty list" +description = "reverse the elements of the list -> non-empty list" [40872990-b5b8-4cb8-9085-d91fc0d05d26] -description = "list of lists is not flattened" +description = "reverse the elements of the list -> list of lists is not flattened" diff --git a/exercises/practice/list-ops/babel.config.js b/exercises/practice/list-ops/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/list-ops/babel.config.js +++ b/exercises/practice/list-ops/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/list-ops/eslint.config.mjs b/exercises/practice/list-ops/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/list-ops/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/list-ops/jest.config.js b/exercises/practice/list-ops/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/list-ops/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/list-ops/list-ops.js b/exercises/practice/list-ops/list-ops.js index ab0dcdd079..e6da91cf23 100644 --- a/exercises/practice/list-ops/list-ops.js +++ b/exercises/practice/list-ops/list-ops.js @@ -5,38 +5,38 @@ export class List { constructor() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } append() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } concat() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } filter() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } map() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } length() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } foldl() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } foldr() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } reverse() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/list-ops/list-ops.spec.js b/exercises/practice/list-ops/list-ops.spec.js index c69ad9ae6f..7ba71ef2bb 100644 --- a/exercises/practice/list-ops/list-ops.spec.js +++ b/exercises/practice/list-ops/list-ops.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { List } from './list-ops'; describe('append entries to a list and return the new list', () => { @@ -7,6 +8,12 @@ describe('append entries to a list and return the new list', () => { expect(list1.append(list2)).toEqual(new List()); }); + xtest('list to empty list', () => { + const list1 = new List(); + const list2 = new List([1, 2, 3, 4]); + expect(list1.append(list2)).toEqual(list2); + }); + xtest('empty list to list', () => { const list1 = new List([1, 2, 3, 4]); const list2 = new List(); @@ -35,6 +42,21 @@ describe('concat lists and lists of lists into new list', () => { const listOfLists = new List([list2, list3, list4]); expect(list1.concat(listOfLists).values).toEqual([1, 2, 3, 4, 5, 6]); }); + + xtest('list of nested lists', () => { + const list1 = new List([[1], [2]]); + const list2 = new List([[3]]); + const list3 = new List([[]]); + const list4 = new List([[4, 5, 6]]); + const listOfNestedLists = new List([list2, list3, list4]); + expect(list1.concat(listOfNestedLists).values).toEqual([ + [1], + [2], + [3], + [], + [4, 5, 6], + ]); + }); }); describe('filter list returning only values that satisfy the filter function', () => { diff --git a/exercises/practice/list-ops/package.json b/exercises/practice/list-ops/package.json index b679f0bc9c..e6d91b32ce 100644 --- a/exercises/practice/list-ops/package.json +++ b/exercises/practice/list-ops/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/list-ops" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/luhn/.docs/instructions.md b/exercises/practice/luhn/.docs/instructions.md index d732791b8d..7702c6bbb5 100644 --- a/exercises/practice/luhn/.docs/instructions.md +++ b/exercises/practice/luhn/.docs/instructions.md @@ -1,64 +1,68 @@ # Instructions -Given a number determine whether or not it is valid per the Luhn formula. +Determine whether a number is valid according to the [Luhn formula][luhn]. -The [Luhn algorithm](https://en.wikipedia.org/wiki/Luhn_algorithm) is -a simple checksum formula used to validate a variety of identification -numbers, such as credit card numbers and Canadian Social Insurance -Numbers. +The number will be provided as a string. -The task is to check if a given string is valid. +## Validating a number -## Validating a Number +Strings of length 1 or less are not valid. +Spaces are allowed in the input, but they should be stripped before checking. +All other non-digit characters are disallowed. -Strings of length 1 or less are not valid. Spaces are allowed in the input, -but they should be stripped before checking. All other non-digit characters -are disallowed. +## Examples -## Example 1: valid credit card number +### Valid credit card number -```text -4539 3195 0343 6467 -``` +The number to be checked is `4539 3195 0343 6467`. -The first step of the Luhn algorithm is to double every second digit, -starting from the right. We will be doubling +The first step of the Luhn algorithm is to start at the end of the number and double every second digit, beginning with the second digit from the right and moving left. ```text -4_3_ 3_9_ 0_4_ 6_6_ +4539 3195 0343 6467 +↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ (double these) ``` -If doubling the number results in a number greater than 9 then subtract 9 -from the product. The results of our doubling: +If the result of doubling a digit is greater than 9, we subtract 9 from that result. +We end up with: ```text 8569 6195 0383 3437 ``` -Then sum all of the digits: +Finally, we sum all digits. +If the sum is evenly divisible by 10, the original number is valid. ```text -8+5+6+9+6+1+9+5+0+3+8+3+3+4+3+7 = 80 +8 + 5 + 6 + 9 + 6 + 1 + 9 + 5 + 0 + 3 + 8 + 3 + 3 + 4 + 3 + 7 = 80 ``` -If the sum is evenly divisible by 10, then the number is valid. This number is valid! +80 is evenly divisible by 10, so number `4539 3195 0343 6467` is valid! -## Example 2: invalid credit card number +### Invalid Canadian SIN + +The number to be checked is `066 123 478`. + +We start at the end of the number and double every second digit, beginning with the second digit from the right and moving left. ```text -8273 1232 7352 0569 +066 123 478 + ↑ ↑ ↑ ↑ (double these) ``` -Double the second digits, starting from the right +If the result of doubling a digit is greater than 9, we subtract 9 from that result. +We end up with: ```text -7253 2262 5312 0539 +036 226 458 ``` -Sum the digits +We sum the digits: ```text -7+2+5+3+2+2+6+2+5+3+1+2+0+5+3+9 = 57 +0 + 3 + 6 + 2 + 2 + 6 + 4 + 5 + 8 = 36 ``` -57 is not evenly divisible by 10, so this number is not valid. +36 is not evenly divisible by 10, so number `066 123 478` is not valid! + +[luhn]: https://en.wikipedia.org/wiki/Luhn_algorithm diff --git a/exercises/practice/luhn/.docs/introduction.md b/exercises/practice/luhn/.docs/introduction.md new file mode 100644 index 0000000000..dee48006ed --- /dev/null +++ b/exercises/practice/luhn/.docs/introduction.md @@ -0,0 +1,11 @@ +# Introduction + +At the Global Verification Authority, you've just been entrusted with a critical assignment. +Across the city, from online purchases to secure logins, countless operations rely on the accuracy of numerical identifiers like credit card numbers, bank account numbers, transaction codes, and tracking IDs. +The Luhn algorithm is a simple checksum formula used to help identify mistyped numbers. + +A batch of identifiers has just arrived on your desk. +All of them must pass the Luhn test to ensure they're legitimate. +If any fail, they'll be flagged as invalid, preventing mistakes such as incorrect transactions or failed account verifications. + +Can you ensure this is done right? The integrity of many services depends on you. diff --git a/exercises/practice/luhn/.eslintrc b/exercises/practice/luhn/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/luhn/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/luhn/.gitignore b/exercises/practice/luhn/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/luhn/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/luhn/.meta/config.json b/exercises/practice/luhn/.meta/config.json index 305d260ce4..d444265375 100644 --- a/exercises/practice/luhn/.meta/config.json +++ b/exercises/practice/luhn/.meta/config.json @@ -1,9 +1,11 @@ { - "blurb": "Given a number determine whether or not it is valid per the Luhn formula.", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "ankorGH", "gabriel376", + "jagdish-15", "ovidiu141", "rchavarria", "ryanplusplus", @@ -12,10 +14,23 @@ "xarxziux" ], "files": { - "solution": ["luhn.js"], - "test": ["luhn.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "luhn.js" + ], + "test": [ + "luhn.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Given a number determine whether or not it is valid per the Luhn formula.", "source": "The Luhn Algorithm on Wikipedia", - "source_url": "http://en.wikipedia.org/wiki/Luhn_algorithm" + "source_url": "https://en.wikipedia.org/wiki/Luhn_algorithm", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/luhn/.meta/tests.toml b/exercises/practice/luhn/.meta/tests.toml index 9bb82241f8..c0be0c4d9d 100644 --- a/exercises/practice/luhn/.meta/tests.toml +++ b/exercises/practice/luhn/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [792a7082-feb7-48c7-b88b-bbfec160865e] description = "single digit strings can not be valid" @@ -26,6 +33,9 @@ description = "invalid credit card" [20e67fad-2121-43ed-99a8-14b5b856adb9] description = "invalid long number with an even remainder" +[7e7c9fc1-d994-457c-811e-d390d52fba5e] +description = "invalid long number with a remainder divisible by 5" + [ad2a0c5f-84ed-4e5b-95da-6011d6f4f0aa] description = "valid number with an even number of digits" @@ -50,8 +60,17 @@ description = "more than a single zero is valid" [ab56fa80-5de8-4735-8a4a-14dae588663e] description = "input digit 9 is correctly converted to output digit 9" +[b9887ee8-8337-46c5-bc45-3bcab51bc36f] +description = "very long input is valid" + +[8a7c0e24-85ea-4154-9cf1-c2db90eabc08] +description = "valid luhn with an odd number of digits and non zero first digit" + [39a06a5a-5bad-4e0f-b215-b042d46209b1] description = "using ascii value for non-doubled non-digit isn't allowed" [f94cf191-a62f-4868-bc72-7253114aa157] description = "using ascii value for doubled non-digit isn't allowed" + +[8b72ad26-c8be-49a2-b99c-bcc3bf631b33] +description = "non-numeric, non-space char in the middle with a sum that's divisible by 10 isn't allowed" diff --git a/exercises/practice/luhn/babel.config.js b/exercises/practice/luhn/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/luhn/babel.config.js +++ b/exercises/practice/luhn/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/luhn/eslint.config.mjs b/exercises/practice/luhn/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/luhn/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/luhn/jest.config.js b/exercises/practice/luhn/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/luhn/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/luhn/luhn.js b/exercises/practice/luhn/luhn.js index ba457e73f4..adb4717ad9 100644 --- a/exercises/practice/luhn/luhn.js +++ b/exercises/practice/luhn/luhn.js @@ -4,5 +4,5 @@ // export const valid = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/luhn/luhn.spec.js b/exercises/practice/luhn/luhn.spec.js index 58f8466fe3..6cd37fe16f 100644 --- a/exercises/practice/luhn/luhn.spec.js +++ b/exercises/practice/luhn/luhn.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { valid } from './luhn'; describe('Luhn', () => { @@ -33,6 +34,10 @@ describe('Luhn', () => { expect(valid('1 2345 6789 1234 5678 9012')).toEqual(false); }); + xtest('invalid long number with a remainder divisible by 5', () => { + expect(valid('1 2345 6789 1234 5678 9013')).toEqual(false); + }); + xtest('valid number with an even number of digits', () => { expect(valid('095 245 88')).toEqual(true); }); @@ -65,6 +70,14 @@ describe('Luhn', () => { expect(valid('091')).toEqual(true); }); + xtest('very long input is valid', () => { + expect(valid('9999999999 9999999999 9999999999 9999999999')).toEqual(true); + }); + + xtest('valid luhn with an odd number of digits and non zero first digit', () => { + expect(valid('109')).toEqual(true); + }); + xtest("using ascii value for non-doubled non-digit isn't allowed", () => { expect(valid('055b 444 285')).toEqual(false); }); @@ -72,4 +85,8 @@ describe('Luhn', () => { xtest("using ascii value for doubled non-digit isn't allowed", () => { expect(valid(':9')).toEqual(false); }); + + xtest("non-numeric, non-space char in the middle with a sum that's divisible by 10 isn't allowed", () => { + expect(valid('59%59')).toEqual(false); + }); }); diff --git a/exercises/practice/luhn/package.json b/exercises/practice/luhn/package.json index 2c4ea70b54..2c1e28e457 100644 --- a/exercises/practice/luhn/package.json +++ b/exercises/practice/luhn/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/luhn" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/markdown/.docs/instructions.md b/exercises/practice/markdown/.docs/instructions.md new file mode 100644 index 0000000000..9b756d9917 --- /dev/null +++ b/exercises/practice/markdown/.docs/instructions.md @@ -0,0 +1,13 @@ +# Instructions + +Refactor a Markdown parser. + +The markdown exercise is a refactoring exercise. +There is code that parses a given string with [Markdown syntax][markdown] and returns the associated HTML for that string. +Even though this code is confusingly written and hard to follow, somehow it works and all the tests are passing! +Your challenge is to re-write this code to make it easier to read and maintain while still making sure that all the tests keep passing. + +It would be helpful if you made notes of what you did in your refactoring in comments so reviewers can see that, but it isn't strictly necessary. +The most important thing is to make the code better! + +[markdown]: https://guides.github.com/features/mastering-markdown/ diff --git a/exercises/practice/markdown/.gitignore b/exercises/practice/markdown/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/markdown/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/markdown/.meta/config.json b/exercises/practice/markdown/.meta/config.json new file mode 100644 index 0000000000..63d94efeb4 --- /dev/null +++ b/exercises/practice/markdown/.meta/config.json @@ -0,0 +1,17 @@ +{ + "authors": [ + "Cool-Katt" + ], + "files": { + "solution": [ + "markdown.js" + ], + "test": [ + "markdown.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] + }, + "blurb": "Refactor a Markdown parser." +} diff --git a/exercises/practice/markdown/.meta/proof.ci.js b/exercises/practice/markdown/.meta/proof.ci.js new file mode 100644 index 0000000000..6d7ccde979 --- /dev/null +++ b/exercises/practice/markdown/.meta/proof.ci.js @@ -0,0 +1,55 @@ +const wrapInTag = (tag, text) => `<${tag}>${text}`; + +const REPLACERS = { + paragraph: { + operationNumber: 1, + regexPattern: /^(.+)$/gim, + replacer: (match, p1) => + match.startsWith('*') || match.startsWith('#') + ? match + : wrapInTag('p', p1), + }, + heading: { + operationNumber: 2, + regexPattern: /^(#{1,7})\s*(.+)/gim, + replacer: (match, p1, p2) => + p1.length > 6 ? wrapInTag('p', match) : wrapInTag(`h${p1.length}`, p2), + }, + bold: { + operationNumber: 3, + regexPattern: /__(.+)__/gim, + replacer: (match, p1) => wrapInTag('strong', p1), + }, + italic: { + operationNumber: 4, + regexPattern: /_(.+)_/gim, + replacer: (match, p1) => wrapInTag('em', p1), + }, + listItem: { + operationNumber: 5, + regexPattern: /^\*\s(.+)/gim, + replacer: (match, p1) => wrapInTag('li', p1), + }, + newLine: { + operationNumber: 6, + regexPattern: /\n/gim, + replacer: () => '', + }, + list: { + operationNumber: 7, + regexPattern: /(
  • .+<\/li>)/gim, + replacer: (match, p1) => wrapInTag('ul', p1), + }, +}; + +const sortedOperations = Object.values(REPLACERS).sort( + (a, b) => a.operationNumber - b.operationNumber, +); + +export function parse(markdown) { + return sortedOperations.reduce( + (text, { regexPattern, replacer }) => + text.replaceAll(new RegExp(regexPattern), replacer), + markdown, + ); +} diff --git a/exercises/practice/markdown/.meta/tests.toml b/exercises/practice/markdown/.meta/tests.toml new file mode 100644 index 0000000000..28b7baa720 --- /dev/null +++ b/exercises/practice/markdown/.meta/tests.toml @@ -0,0 +1,66 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[e75c8103-a6b8-45d9-84ad-e68520545f6e] +description = "parses normal text as a paragraph" + +[69a4165d-9bf8-4dd7-bfdc-536eaca80a6a] +description = "parsing italics" + +[ec345a1d-db20-4569-a81a-172fe0cad8a1] +description = "parsing bold text" + +[51164ed4-5641-4909-8fab-fbaa9d37d5a8] +description = "mixed normal, italics and bold text" + +[ad85f60d-0edd-4c6a-a9b1-73e1c4790d15] +description = "with h1 header level" + +[d0f7a31f-6935-44ac-8a9a-1e8ab16af77f] +description = "with h2 header level" + +[9df3f500-0622-4696-81a7-d5babd9b5f49] +description = "with h3 header level" + +[50862777-a5e8-42e9-a3b8-4ba6fcd0ed03] +description = "with h4 header level" + +[ee1c23ac-4c86-4f2a-8b9c-403548d4ab82] +description = "with h5 header level" + +[13b5f410-33f5-44f0-a6a7-cfd4ab74b5d5] +description = "with h6 header level" + +[6dca5d10-5c22-4e2a-ac2b-bd6f21e61939] +description = "with h7 header level" +include = false + +[81c0c4db-435e-4d77-860d-45afacdad810] +description = "h7 header level is a paragraph" +reimplements = "6dca5d10-5c22-4e2a-ac2b-bd6f21e61939" + +[25288a2b-8edc-45db-84cf-0b6c6ee034d6] +description = "unordered lists" + +[7bf92413-df8f-4de8-9184-b724f363c3da] +description = "With a little bit of everything" + +[0b3ed1ec-3991-4b8b-8518-5cb73d4a64fe] +description = "with markdown symbols in the header text that should not be interpreted" + +[113a2e58-78de-4efa-90e9-20972224d759] +description = "with markdown symbols in the list item text that should not be interpreted" + +[e65e46e2-17b7-4216-b3ac-f44a1b9bcdb4] +description = "with markdown symbols in the paragraph text that should not be interpreted" + +[f0bbbbde-0f52-4c0c-99ec-be4c60126dd4] +description = "unordered lists close properly with preceding and following lines" diff --git a/exercises/practice/markdown/.npmrc b/exercises/practice/markdown/.npmrc new file mode 100644 index 0000000000..d26df800bb --- /dev/null +++ b/exercises/practice/markdown/.npmrc @@ -0,0 +1 @@ +audit=false diff --git a/exercises/practice/markdown/LICENSE b/exercises/practice/markdown/LICENSE new file mode 100644 index 0000000000..90e73be03b --- /dev/null +++ b/exercises/practice/markdown/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Exercism + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/exercises/practice/markdown/babel.config.js b/exercises/practice/markdown/babel.config.js new file mode 100644 index 0000000000..a638497df1 --- /dev/null +++ b/exercises/practice/markdown/babel.config.js @@ -0,0 +1,4 @@ +module.exports = { + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], +}; diff --git a/exercises/practice/markdown/eslint.config.mjs b/exercises/practice/markdown/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/markdown/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/markdown/jest.config.js b/exercises/practice/markdown/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/markdown/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/markdown/markdown.js b/exercises/practice/markdown/markdown.js new file mode 100644 index 0000000000..e5bf7eecc6 --- /dev/null +++ b/exercises/practice/markdown/markdown.js @@ -0,0 +1,101 @@ +function wrap(text, tag) { + return `<${tag}>${text}`; +} + +function isTag(text, tag) { + return text.startsWith(`<${tag}>`); +} + +function parser(markdown, delimiter, tag) { + const pattern = new RegExp(`${delimiter}(.+)${delimiter}`); + const replacement = `<${tag}>$1`; + return markdown.replace(pattern, replacement); +} + +function parse__(markdown) { + return parser(markdown, '__', 'strong'); +} + +function parse_(markdown) { + return parser(markdown, '_', 'em'); +} + +function parseText(markdown, list) { + const parsedText = parse_(parse__(markdown)); + if (list) { + return parsedText; + } else { + return wrap(parsedText, 'p'); + } +} + +function parseHeader(markdown, list) { + let count = 0; + for (let i = 0; i < markdown.length; i++) { + if (markdown[i] === '#') { + count += 1; + } else { + break; + } + } + if (count === 0 || count > 6) { + return [null, list]; + } + const headerTag = `h${count}`; + const headerHtml = wrap(markdown.substring(count + 1), headerTag); + if (list) { + return [`${headerHtml}`, false]; + } else { + return [headerHtml, false]; + } +} + +function parseLineItem(markdown, list) { + if (markdown.startsWith('*')) { + const innerHtml = wrap(parseText(markdown.substring(2), true), 'li'); + if (list) { + return [innerHtml, true]; + } else { + return [`
      ${innerHtml}`, true]; + } + } + return [null, list]; +} + +function parseParagraph(markdown, list) { + if (!list) { + return [parseText(markdown, false), false]; + } else { + return [`
    ${parseText(markdown, false)}`, false]; + } +} + +function parseLine(markdown, list) { + let [result, inListAfter] = parseHeader(markdown, list); + if (result === null) { + [result, inListAfter] = parseLineItem(markdown, list); + } + if (result === null) { + [result, inListAfter] = parseParagraph(markdown, list); + } + if (result === null) { + throw new Error('Remove this line and implement the function'); + } + return [result, inListAfter]; +} + +export function parse(markdown) { + const lines = markdown.split('\n'); + let result = ''; + let list = false; + for (let i = 0; i < lines.length; i++) { + let [lineResult, newList] = parseLine(lines[i], list); + result += lineResult; + list = newList; + } + if (list) { + return result + ''; + } else { + return result; + } +} diff --git a/exercises/practice/markdown/markdown.spec.js b/exercises/practice/markdown/markdown.spec.js new file mode 100644 index 0000000000..ee7417c100 --- /dev/null +++ b/exercises/practice/markdown/markdown.spec.js @@ -0,0 +1,128 @@ +import { describe, expect, test, xtest } from '@jest/globals'; +import { parse } from './markdown'; + +describe('Markdown', () => { + test('parses normal text as a paragraph', () => { + const markdown = 'This will be a paragraph'; + const expected = '

    This will be a paragraph

    '; + const actual = parse(markdown); + expect(actual).toEqual(expected); + }); + + xtest('parsing italics', () => { + const markdown = '_This will be italic_'; + const expected = '

    This will be italic

    '; + const actual = parse(markdown); + expect(actual).toEqual(expected); + }); + + xtest('parsing bold text', () => { + const markdown = '__This will be bold__'; + const expected = '

    This will be bold

    '; + const actual = parse(markdown); + expect(actual).toEqual(expected); + }); + + xtest('mixed normal, italics and bold text', () => { + const markdown = 'This will _be_ __mixed__'; + const expected = '

    This will be mixed

    '; + const actual = parse(markdown); + expect(actual).toEqual(expected); + }); + + xtest('with h1 header level', () => { + const markdown = '# This will be an h1'; + const expected = '

    This will be an h1

    '; + const actual = parse(markdown); + expect(actual).toEqual(expected); + }); + + xtest('with h2 header level', () => { + const markdown = '## This will be an h2'; + const expected = '

    This will be an h2

    '; + const actual = parse(markdown); + expect(actual).toEqual(expected); + }); + + xtest('with h3 header level', () => { + const markdown = '### This will be an h3'; + const expected = '

    This will be an h3

    '; + const actual = parse(markdown); + expect(actual).toEqual(expected); + }); + + xtest('with h4 header level', () => { + const markdown = '#### This will be an h4'; + const expected = '

    This will be an h4

    '; + const actual = parse(markdown); + expect(actual).toEqual(expected); + }); + + xtest('with h5 header level', () => { + const markdown = '##### This will be an h5'; + const expected = '
    This will be an h5
    '; + const actual = parse(markdown); + expect(actual).toEqual(expected); + }); + + xtest('with h6 header level', () => { + const markdown = '###### This will be an h6'; + const expected = '
    This will be an h6
    '; + const actual = parse(markdown); + expect(actual).toEqual(expected); + }); + + xtest('with h7 header level', () => { + const markdown = '####### This will not be an h7'; + const expected = '

    ####### This will not be an h7

    '; + const actual = parse(markdown); + expect(actual).toEqual(expected); + }); + + xtest('unordered lists', () => { + const markdown = '* Item 1\n' + '* Item 2'; + const expected = '
    • Item 1
    • Item 2
    '; + const actual = parse(markdown); + expect(actual).toEqual(expected); + }); + + xtest('with a little bit of everything', () => { + const markdown = '# Header!\n' + '* __Bold Item__\n' + '* _Italic Item_'; + const expected = + '

    Header!

    • Bold Item
    • Italic Item
    '; + const actual = parse(markdown); + expect(actual).toEqual(expected); + }); + + xtest('with markdown symbols in the header text that should not be interpreted', () => { + const markdown = '# This is a header with # and * in the text'; + const expected = '

    This is a header with # and * in the text

    '; + const actual = parse(markdown); + expect(actual).toEqual(expected); + }); + + xtest('with markdown symbols in the list item text that should not be interpreted', () => { + const markdown = + '* Item 1 with a # in the text\n' + '* Item 2 with * in the text'; + const expected = + '
    • Item 1 with a # in the text
    • Item 2 with * in the text
    '; + const actual = parse(markdown); + expect(actual).toEqual(expected); + }); + + xtest('with markdown symbols in the paragraph text that should not be interpreted', () => { + const markdown = 'This is a paragraph with # and * in the text'; + const expected = '

    This is a paragraph with # and * in the text

    '; + const actual = parse(markdown); + expect(actual).toEqual(expected); + }); + + xtest('unordered lists close properly with preceding and following lines', () => { + const markdown = + '# Start a list\n' + '* Item 1\n' + '* Item 2\n' + 'End a list'; + const expected = + '

    Start a list

    • Item 1
    • Item 2

    End a list

    '; + const actual = parse(markdown); + expect(actual).toEqual(expected); + }); +}); diff --git a/exercises/practice/markdown/package.json b/exercises/practice/markdown/package.json new file mode 100644 index 0000000000..20c08532e5 --- /dev/null +++ b/exercises/practice/markdown/package.json @@ -0,0 +1,39 @@ +{ + "name": "@exercism/javascript-markdown", + "description": "Exercism practice exercise on markdown", + "author": "Katrina Owen", + "contributors": [ + "Cool-Katt (https://github.com/Cool-Katt)", + "Derk-Jan Karrenbeld (https://derk-jan.com)", + "Tejas Bubane (https://tejasbubane.github.io/)" + ], + "private": true, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/exercism/javascript", + "directory": "exercises/practice/markdown" + }, + "devDependencies": { + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" + }, + "dependencies": {}, + "scripts": { + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" +} diff --git a/exercises/practice/matching-brackets/.docs/instructions.md b/exercises/practice/matching-brackets/.docs/instructions.md index 364ecad213..ea17084232 100644 --- a/exercises/practice/matching-brackets/.docs/instructions.md +++ b/exercises/practice/matching-brackets/.docs/instructions.md @@ -1,5 +1,5 @@ # Instructions -Given a string containing brackets `[]`, braces `{}`, parentheses `()`, -or any combination thereof, verify that any and all pairs are matched -and nested correctly. +Given a string containing brackets `[]`, braces `{}`, parentheses `()`, or any combination thereof, verify that any and all pairs are matched and nested correctly. +Any other characters should be ignored. +For example, `"{what is (42)}?"` is balanced and `"[text}"` is not. diff --git a/exercises/practice/matching-brackets/.docs/introduction.md b/exercises/practice/matching-brackets/.docs/introduction.md new file mode 100644 index 0000000000..0618221b21 --- /dev/null +++ b/exercises/practice/matching-brackets/.docs/introduction.md @@ -0,0 +1,8 @@ +# Introduction + +You're given the opportunity to write software for the Bracketeer™, an ancient but powerful mainframe. +The software that runs on it is written in a proprietary language. +Much of its syntax is familiar, but you notice _lots_ of brackets, braces and parentheses. +Despite the Bracketeer™ being powerful, it lacks flexibility. +If the source code has any unbalanced brackets, braces or parentheses, the Bracketeer™ crashes and must be rebooted. +To avoid such a scenario, you start writing code that can verify that brackets, braces, and parentheses are balanced before attempting to run it on the Bracketeer™. diff --git a/exercises/practice/matching-brackets/.eslintrc b/exercises/practice/matching-brackets/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/matching-brackets/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/matching-brackets/.gitignore b/exercises/practice/matching-brackets/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/matching-brackets/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/matching-brackets/.meta/config.json b/exercises/practice/matching-brackets/.meta/config.json index b9ec1c4165..c355f03bf0 100644 --- a/exercises/practice/matching-brackets/.meta/config.json +++ b/exercises/practice/matching-brackets/.meta/config.json @@ -1,9 +1,11 @@ { - "blurb": "Make sure the brackets and braces all match.", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "ankorGH", "Futuro212", + "jagdish-15", "ovidiu141", "rchavarria", "ryanplusplus", @@ -11,9 +13,22 @@ "xarxziux" ], "files": { - "solution": ["matching-brackets.js"], - "test": ["matching-brackets.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "matching-brackets.js" + ], + "test": [ + "matching-brackets.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, - "source": "Ginna Baker" + "blurb": "Make sure the brackets and braces all match.", + "source": "Ginna Baker", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/matching-brackets/.meta/tests.toml b/exercises/practice/matching-brackets/.meta/tests.toml index cc9e471a42..35a98a0421 100644 --- a/exercises/practice/matching-brackets/.meta/tests.toml +++ b/exercises/practice/matching-brackets/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [81ec11da-38dd-442a-bcf9-3de7754609a5] description = "paired square brackets" @@ -41,12 +48,21 @@ description = "unpaired and nested brackets" [a0205e34-c2ac-49e6-a88a-899508d7d68e] description = "paired and wrong nested brackets" +[1d5c093f-fc84-41fb-8c2a-e052f9581602] +description = "paired and wrong nested brackets but innermost are correct" + [ef47c21b-bcfd-4998-844c-7ad5daad90a8] description = "paired and incomplete brackets" [a4675a40-a8be-4fc2-bc47-2a282ce6edbe] description = "too many closing brackets" +[a345a753-d889-4b7e-99ae-34ac85910d1a] +description = "early unexpected brackets" + +[21f81d61-1608-465a-b850-baa44c5def83] +description = "early mismatched brackets" + [99255f93-261b-4435-a352-02bdecc9bdf2] description = "math expression" diff --git a/exercises/practice/matching-brackets/babel.config.js b/exercises/practice/matching-brackets/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/matching-brackets/babel.config.js +++ b/exercises/practice/matching-brackets/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/matching-brackets/eslint.config.mjs b/exercises/practice/matching-brackets/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/matching-brackets/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/matching-brackets/jest.config.js b/exercises/practice/matching-brackets/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/matching-brackets/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/matching-brackets/matching-brackets.js b/exercises/practice/matching-brackets/matching-brackets.js index 8a0ef3cf07..572ebb6f3b 100644 --- a/exercises/practice/matching-brackets/matching-brackets.js +++ b/exercises/practice/matching-brackets/matching-brackets.js @@ -4,5 +4,5 @@ // export const isPaired = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/matching-brackets/matching-brackets.spec.js b/exercises/practice/matching-brackets/matching-brackets.spec.js index 74c89ea63c..2f08561b28 100644 --- a/exercises/practice/matching-brackets/matching-brackets.spec.js +++ b/exercises/practice/matching-brackets/matching-brackets.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { isPaired } from './matching-brackets'; describe('Matching Brackets', () => { @@ -53,6 +54,10 @@ describe('Matching Brackets', () => { expect(isPaired('[({]})')).toEqual(false); }); + xtest('paired and wrong nested brackets but innermost are correct', () => { + expect(isPaired('[({}])')).toEqual(false); + }); + xtest('paired and incomplete brackets', () => { expect(isPaired('{}[')).toEqual(false); }); @@ -61,6 +66,14 @@ describe('Matching Brackets', () => { expect(isPaired('[]]')).toEqual(false); }); + xtest('early unexpected brackets', () => { + expect(isPaired(')()')).toEqual(false); + }); + + xtest('early mismatched brackets', () => { + expect(isPaired('{)()')).toEqual(false); + }); + xtest('math expression', () => { expect(isPaired('(((185 + 223.85) * 15) - 543)/2')).toEqual(true); }); @@ -68,8 +81,8 @@ describe('Matching Brackets', () => { xtest('complex latex expression', () => { expect( isPaired( - '\\left(\\begin{array}{cc} \\frac{1}{3} & x\\\\ \\mathrm{e}^{x} &... x^2 \\end{array}\\right)' - ) + '\\left(\\begin{array}{cc} \\frac{1}{3} & x\\\\ \\mathrm{e}^{x} &... x^2 \\end{array}\\right)', + ), ).toEqual(true); }); }); diff --git a/exercises/practice/matching-brackets/package.json b/exercises/practice/matching-brackets/package.json index e937c39f3c..7d64c71b1d 100644 --- a/exercises/practice/matching-brackets/package.json +++ b/exercises/practice/matching-brackets/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/matching-brackets" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/matrix/.docs/instructions.md b/exercises/practice/matrix/.docs/instructions.md index 1b2d0f84b0..dadea8acb5 100644 --- a/exercises/practice/matrix/.docs/instructions.md +++ b/exercises/practice/matrix/.docs/instructions.md @@ -1,7 +1,6 @@ # Instructions -Given a string representing a matrix of numbers, return the rows and columns of -that matrix. +Given a string representing a matrix of numbers, return the rows and columns of that matrix. So given a string with embedded newlines like: @@ -23,10 +22,8 @@ representing this matrix: your code should be able to spit out: -- A list of the rows, reading each row left-to-right while moving - top-to-bottom across the rows, -- A list of the columns, reading each column top-to-bottom while moving - from left-to-right. +- A list of the rows, reading each row left-to-right while moving top-to-bottom across the rows, +- A list of the columns, reading each column top-to-bottom while moving from left-to-right. The rows for our example matrix: diff --git a/exercises/practice/matrix/.eslintrc b/exercises/practice/matrix/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/matrix/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/matrix/.gitignore b/exercises/practice/matrix/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/matrix/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/matrix/.meta/config.json b/exercises/practice/matrix/.meta/config.json index c9df8c09c4..29ad82dd02 100644 --- a/exercises/practice/matrix/.meta/config.json +++ b/exercises/practice/matrix/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Given a string representing a matrix of numbers, return the rows and columns of that matrix.", - "authors": ["rchavarria"], + "authors": [ + "rchavarria" + ], "contributors": [ "amscotti", "ankorGH", @@ -13,10 +14,23 @@ "xarxziux" ], "files": { - "solution": ["matrix.js"], - "test": ["matrix.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "matrix.js" + ], + "test": [ + "matrix.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, - "source": "Warmup to the `saddle-points` warmup.", - "source_url": "http://jumpstartlab.com" + "blurb": "Given a string representing a matrix of numbers, return the rows and columns of that matrix.", + "source": "Exercise by the JumpstartLab team for students at The Turing School of Software and Design.", + "source_url": "https://turing.edu", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/matrix/babel.config.js b/exercises/practice/matrix/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/matrix/babel.config.js +++ b/exercises/practice/matrix/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/matrix/eslint.config.mjs b/exercises/practice/matrix/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/matrix/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/matrix/jest.config.js b/exercises/practice/matrix/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/matrix/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/matrix/matrix.js b/exercises/practice/matrix/matrix.js index 1eca092ec9..ebef791f08 100644 --- a/exercises/practice/matrix/matrix.js +++ b/exercises/practice/matrix/matrix.js @@ -5,14 +5,14 @@ export class Matrix { constructor() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get rows() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get columns() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/matrix/matrix.spec.js b/exercises/practice/matrix/matrix.spec.js index 691171d183..594c376a34 100644 --- a/exercises/practice/matrix/matrix.spec.js +++ b/exercises/practice/matrix/matrix.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { Matrix } from './matrix'; describe('Matrix', () => { diff --git a/exercises/practice/matrix/package.json b/exercises/practice/matrix/package.json index cdc7d9b760..fdf9342a34 100644 --- a/exercises/practice/matrix/package.json +++ b/exercises/practice/matrix/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/matrix" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/meetup/.docs/instructions.md b/exercises/practice/meetup/.docs/instructions.md index 41a13e4e86..8b1bda5eb4 100644 --- a/exercises/practice/meetup/.docs/instructions.md +++ b/exercises/practice/meetup/.docs/instructions.md @@ -1,27 +1,34 @@ # Instructions -Calculate the date of meetups. +Your task is to find the exact date of a meetup, given a month, year, weekday and week. -Typically meetups happen on the same day of the week. In this exercise, you -will take a description of a meetup date, and return the actual meetup date. +There are six week values to consider: `first`, `second`, `third`, `fourth`, `last`, `teenth`. -Examples of general descriptions are: +For example, you might be asked to find the date for the meetup on the first Monday in January 2018 (January 1, 2018). -- The first Monday of January 2017 -- The third Tuesday of January 2017 -- The wednesteenth of January 2017 -- The last Thursday of January 2017 +Similarly, you might be asked to find: -The descriptors you are expected to parse are: -first, second, third, fourth, fifth, last, monteenth, tuesteenth, wednesteenth, -thursteenth, friteenth, saturteenth, sunteenth +- the third Tuesday of August 2019 (August 20, 2019) +- the teenth Wednesday of May 2020 (May 13, 2020) +- the fourth Sunday of July 2021 (July 25, 2021) +- the last Thursday of November 2022 (November 24, 2022) +- the teenth Saturday of August 1953 (August 15, 1953) -Note that "monteenth", "tuesteenth", etc are all made up words. There was a -meetup whose members realized that there are exactly 7 numbered days in a month -that end in '-teenth'. Therefore, one is guaranteed that each day of the week -(Monday, Tuesday, ...) will have exactly one date that is named with '-teenth' -in every month. +## Teenth -Given examples of a meetup dates, each containing a month, day, year, and -descriptor calculate the date of the actual meetup. For example, if given -"The first Monday of January 2017", the correct meetup date is 2017/1/2. +The teenth week refers to the seven days in a month that end in '-teenth' (13th, 14th, 15th, 16th, 17th, 18th and 19th). + +If asked to find the teenth Saturday of August, 1953, we check its calendar: + +```plaintext + August 1953 +Su Mo Tu We Th Fr Sa + 1 + 2 3 4 5 6 7 8 + 9 10 11 12 13 14 15 +16 17 18 19 20 21 22 +23 24 25 26 27 28 29 +30 31 +``` + +From this we find that the teenth Saturday is August 15, 1953. diff --git a/exercises/practice/meetup/.docs/introduction.md b/exercises/practice/meetup/.docs/introduction.md new file mode 100644 index 0000000000..29170ef1fd --- /dev/null +++ b/exercises/practice/meetup/.docs/introduction.md @@ -0,0 +1,29 @@ +# Introduction + +Every month, your partner meets up with their best friend. +Both of them have very busy schedules, making it challenging to find a suitable date! +Given your own busy schedule, your partner always double-checks potential meetup dates with you: + +- "Can I meet up on the first Friday of next month?" +- "What about the third Wednesday?" +- "Maybe the last Sunday?" + +In this month's call, your partner asked you this question: + +- "I'd like to meet up on the teenth Thursday; is that okay?" + +Confused, you ask what a "teenth" day is. +Your partner explains that a teenth day, a concept they made up, refers to the days in a month that end in '-teenth': + +- 13th (thirteenth) +- 14th (fourteenth) +- 15th (fifteenth) +- 16th (sixteenth) +- 17th (seventeenth) +- 18th (eighteenth) +- 19th (nineteenth) + +As there are also seven weekdays, it is guaranteed that each day of the week has _exactly one_ teenth day each month. + +Now that you understand the concept of a teenth day, you check your calendar. +You don't have anything planned on the teenth Thursday, so you happily confirm the date with your partner. diff --git a/exercises/practice/meetup/.eslintrc b/exercises/practice/meetup/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/meetup/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/meetup/.gitignore b/exercises/practice/meetup/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/meetup/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/meetup/.meta/config.json b/exercises/practice/meetup/.meta/config.json index 3415f59216..ad9b0a5cbd 100644 --- a/exercises/practice/meetup/.meta/config.json +++ b/exercises/practice/meetup/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Calculate the date of meetups.", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "ovidiu141", "rchavarria", @@ -10,10 +11,22 @@ "SleeplessByte" ], "files": { - "solution": ["meetup.js"], - "test": ["meetup.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "meetup.js" + ], + "test": [ + "meetup.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Calculate the date of meetups.", "source": "Jeremy Hinegardner mentioned a Boulder meetup that happens on the Wednesteenth of every month", - "source_url": "https://twitter.com/copiousfreetime" + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/meetup/babel.config.js b/exercises/practice/meetup/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/meetup/babel.config.js +++ b/exercises/practice/meetup/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/meetup/eslint.config.mjs b/exercises/practice/meetup/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/meetup/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/meetup/jest.config.js b/exercises/practice/meetup/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/meetup/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/meetup/meetup.js b/exercises/practice/meetup/meetup.js index 1eb17a0b19..768b2a6534 100644 --- a/exercises/practice/meetup/meetup.js +++ b/exercises/practice/meetup/meetup.js @@ -4,5 +4,5 @@ // export const meetup = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/meetup/meetup.spec.js b/exercises/practice/meetup/meetup.spec.js index 6f0d16d8e0..540da01264 100644 --- a/exercises/practice/meetup/meetup.spec.js +++ b/exercises/practice/meetup/meetup.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { meetup } from './meetup'; describe('Meetup', () => { @@ -27,37 +28,37 @@ describe('Meetup', () => { xtest('wednesteenth of January 2013', () => { expect(meetup(2013, 1, 'teenth', 'Wednesday')).toEqual( - new Date(2013, 0, 16) + new Date(2013, 0, 16), ); }); xtest('wednesteenth of February 2013', () => { expect(meetup(2013, 2, 'teenth', 'Wednesday')).toEqual( - new Date(2013, 1, 13) + new Date(2013, 1, 13), ); }); xtest('wednesteenth of June 2013', () => { expect(meetup(2013, 6, 'teenth', 'Wednesday')).toEqual( - new Date(2013, 5, 19) + new Date(2013, 5, 19), ); }); xtest('thursteenth of May 2013', () => { expect(meetup(2013, 5, 'teenth', 'Thursday')).toEqual( - new Date(2013, 4, 16) + new Date(2013, 4, 16), ); }); xtest('thursteenth of June 2013', () => { expect(meetup(2013, 6, 'teenth', 'Thursday')).toEqual( - new Date(2013, 5, 13) + new Date(2013, 5, 13), ); }); xtest('thursteenth of September 2013', () => { expect(meetup(2013, 9, 'teenth', 'Thursday')).toEqual( - new Date(2013, 8, 19) + new Date(2013, 8, 19), ); }); @@ -75,19 +76,19 @@ describe('Meetup', () => { xtest('saturteenth of February 2013', () => { expect(meetup(2013, 2, 'teenth', 'Saturday')).toEqual( - new Date(2013, 1, 16) + new Date(2013, 1, 16), ); }); xtest('saturteenth of April 2013', () => { expect(meetup(2013, 4, 'teenth', 'Saturday')).toEqual( - new Date(2013, 3, 13) + new Date(2013, 3, 13), ); }); xtest('saturteenth of October 2013', () => { expect(meetup(2013, 10, 'teenth', 'Saturday')).toEqual( - new Date(2013, 9, 19) + new Date(2013, 9, 19), ); }); @@ -177,25 +178,25 @@ describe('Meetup', () => { xtest('second Wednesday of July 2013', () => { expect(meetup(2013, 7, 'second', 'Wednesday')).toEqual( - new Date(2013, 6, 10) + new Date(2013, 6, 10), ); }); xtest('second Wednesday of August 2013', () => { expect(meetup(2013, 8, 'second', 'Wednesday')).toEqual( - new Date(2013, 7, 14) + new Date(2013, 7, 14), ); }); xtest('second Thursday of September 2013', () => { expect(meetup(2013, 9, 'second', 'Thursday')).toEqual( - new Date(2013, 8, 12) + new Date(2013, 8, 12), ); }); xtest('second Thursday of October 2013', () => { expect(meetup(2013, 10, 'second', 'Thursday')).toEqual( - new Date(2013, 9, 10) + new Date(2013, 9, 10), ); }); @@ -205,13 +206,13 @@ describe('Meetup', () => { xtest('second Friday of December 2013', () => { expect(meetup(2013, 12, 'second', 'Friday')).toEqual( - new Date(2013, 11, 13) + new Date(2013, 11, 13), ); }); xtest('second Saturday of January 2013', () => { expect(meetup(2013, 1, 'second', 'Saturday')).toEqual( - new Date(2013, 0, 12) + new Date(2013, 0, 12), ); }); @@ -245,13 +246,13 @@ describe('Meetup', () => { xtest('third Wednesday of July 2013', () => { expect(meetup(2013, 7, 'third', 'Wednesday')).toEqual( - new Date(2013, 6, 17) + new Date(2013, 6, 17), ); }); xtest('third Wednesday of August 2013', () => { expect(meetup(2013, 8, 'third', 'Wednesday')).toEqual( - new Date(2013, 7, 21) + new Date(2013, 7, 21), ); }); @@ -261,7 +262,7 @@ describe('Meetup', () => { xtest('third Thursday of October 2013', () => { expect(meetup(2013, 10, 'third', 'Thursday')).toEqual( - new Date(2013, 9, 17) + new Date(2013, 9, 17), ); }); @@ -307,49 +308,49 @@ describe('Meetup', () => { xtest('fourth Wednesday of July 2013', () => { expect(meetup(2013, 7, 'fourth', 'Wednesday')).toEqual( - new Date(2013, 6, 24) + new Date(2013, 6, 24), ); }); xtest('fourth Wednesday of August 2013', () => { expect(meetup(2013, 8, 'fourth', 'Wednesday')).toEqual( - new Date(2013, 7, 28) + new Date(2013, 7, 28), ); }); xtest('fourth Thursday of September 2013', () => { expect(meetup(2013, 9, 'fourth', 'Thursday')).toEqual( - new Date(2013, 8, 26) + new Date(2013, 8, 26), ); }); xtest('fourth Thursday of October 2013', () => { expect(meetup(2013, 10, 'fourth', 'Thursday')).toEqual( - new Date(2013, 9, 24) + new Date(2013, 9, 24), ); }); xtest('fourth Friday of November 2013', () => { expect(meetup(2013, 11, 'fourth', 'Friday')).toEqual( - new Date(2013, 10, 22) + new Date(2013, 10, 22), ); }); xtest('fourth Friday of December 2013', () => { expect(meetup(2013, 12, 'fourth', 'Friday')).toEqual( - new Date(2013, 11, 27) + new Date(2013, 11, 27), ); }); xtest('fourth Saturday of January 2013', () => { expect(meetup(2013, 1, 'fourth', 'Saturday')).toEqual( - new Date(2013, 0, 26) + new Date(2013, 0, 26), ); }); xtest('fourth Saturday of February 2013', () => { expect(meetup(2013, 2, 'fourth', 'Saturday')).toEqual( - new Date(2013, 1, 23) + new Date(2013, 1, 23), ); }); @@ -423,7 +424,7 @@ describe('Meetup', () => { xtest('last Wednesday of December 2014', () => { expect(meetup(2014, 12, 'last', 'Wednesday')).toEqual( - new Date(2014, 11, 31) + new Date(2014, 11, 31), ); }); diff --git a/exercises/practice/meetup/package.json b/exercises/practice/meetup/package.json index ab68d08f01..46e0f6c632 100644 --- a/exercises/practice/meetup/package.json +++ b/exercises/practice/meetup/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/meetup" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/micro-blog/.approaches/config.json b/exercises/practice/micro-blog/.approaches/config.json new file mode 100644 index 0000000000..48c2f79aa1 --- /dev/null +++ b/exercises/practice/micro-blog/.approaches/config.json @@ -0,0 +1,36 @@ +{ + "introduction": { + "authors": [ + "Cool-Katt" + ] + }, + "approaches": [ + { + "uuid": "ebd5893b-1f62-4634-a086-414338da1842", + "slug": "regex", + "title": "Regex", + "blurb": "Split a Unicode string using a RegEx.", + "authors": [ + "Cool-Katt" + ] + }, + { + "uuid": "a12fa836-201c-43bb-b7e2-28f441c270db", + "slug": "iterators", + "title": "Iterators", + "blurb": "Split a Unicode string using a string iterator.", + "authors": [ + "Cool-Katt" + ] + }, + { + "uuid": "c8b58d62-a129-41ad-afa6-cc6afb5b284c", + "slug": "intl-segmenter", + "title": "Intl.Segmenter", + "blurb": "Split a Unicode string using Intl.Segmenter.", + "authors": [ + "Cool-Katt" + ] + } + ] +} diff --git a/exercises/practice/micro-blog/.approaches/intl-segmenter/content.md b/exercises/practice/micro-blog/.approaches/intl-segmenter/content.md new file mode 100644 index 0000000000..26fcc70103 --- /dev/null +++ b/exercises/practice/micro-blog/.approaches/intl-segmenter/content.md @@ -0,0 +1,27 @@ +# Intl.Segmenter + +```javascript +let string = '👨‍👨‍👧‍👧💜🤧🤒🏥😀'; + +const splitWithSegmenter = (s) => + Array.from(new Intl.Segmenter().segment(String(s)), (x) => x.segment) + .slice(0, 5) + .join(''); + +console.log(splitWithSegmenter(string)); // will be "👨‍👨‍👧‍👧💜🤧🤒🏥" - correct, yay! +``` + +This solution: + +- Uses the [Intl.Segmenter object][segmenter] to split the string by graphemes and form an array from the result. +- Then it separates the first 5 graphemes. +- Finally, it joins them back into a string. + + +~~~~exercism/note +At the time of writing (February 2024) this method is not fully supported by the stable release of the Mozilla Firefox browser. +However, support for the Intl.Segmenter object is being worked on in the Nightly release of the browser. +~~~~ + + +[segmenter]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter diff --git a/exercises/practice/micro-blog/.approaches/intl-segmenter/snippet.txt b/exercises/practice/micro-blog/.approaches/intl-segmenter/snippet.txt new file mode 100644 index 0000000000..9778039865 --- /dev/null +++ b/exercises/practice/micro-blog/.approaches/intl-segmenter/snippet.txt @@ -0,0 +1,7 @@ +let string = '👨‍👨‍👧‍👧💜🤧🤒🏥😀'; + +const splitWithSegmenter = (s) => + Array.from(new Intl.Segmenter().segment(String(s)), (x) => x.segment) + .slice(0, 5) + .join(''); +console.log(splitWithSegmenter(string)); // will be "👨‍👨‍👧‍👧💜🤧🤒🏥" - correct, yay! \ No newline at end of file diff --git a/exercises/practice/micro-blog/.approaches/introduction.md b/exercises/practice/micro-blog/.approaches/introduction.md new file mode 100644 index 0000000000..fa943f5e43 --- /dev/null +++ b/exercises/practice/micro-blog/.approaches/introduction.md @@ -0,0 +1,64 @@ +# Introduction + +As noted in this exercise's introduction, most built-in Javascript methods for working with strings are Unicode-aware, but work with UTF-16 code units. +This might not be a problem, if all of the input contains characters represented by one code unit and you might not even notice it. +Unfortunately, this isn't the case with our micro-blog. + +Different approaches we'll compare include: + +- Using a `String iterator` +- Using a `Regular Expression` +- Using `Intl.Segmenter` + +## General guidance + +The main part of this exercise is figuring out how to split a Unicode encoded string and count up to 5 characters of it. + +## Approach: `String iterator` + +```javascript +function splitWithIterator(string) { + return [...string].slice(0, 5).join(''); +} +``` + +For more information, and a detailed explanation, check the [`String iterator` approach][iterator]. + +## Approach: `Regular Expression` + +```javascript +function splitWithRegex(string) { + return string.match(/.{0,5}/gu)[0]; +} +``` + +For more information, and a detailed explanation, check the [`Regular Expression` approach][regex] + +## Other approaches + +The aformentioned approaches both use UTF-16 code points, so character made of multiple code units aren't a problem. +But what about characters made of multiple code _points_, like some emoji? + +### Other approach: `Intl.Segmenter` + +The `Intl.Segmenter` object enables locale-sensitive string splitting and by default splits by graphemes, +so it should work well with symbols like emoji made of multiple code points. +For more information, and a detailed explanation, check the [`Intl.Segmenter` approach][separator]. + +## Which approach is the best in terms of performance? + +Testing with the following two strings on [JSBench.me][jsbench-me] yielded: + +```javascript +let string1 = '👨‍👨‍👧‍👧💜🤧🤒🏥😀'; +let string2 = 'The quick brown fox jumps over the lazy dog. It barked.'; +``` + +- The `String iterator` approach benched fastest. +- The `RegEx` approach was about 12% slower than the first. +- The `Intl.Segmenter` approach was the slowest of the three, by a considerable margin. + +[iterator]: https://exercism.org/tracks/javascript/exercises/micro-blog/approaches/iterators +[regex]: https://exercism.org/tracks/javascript/exercises/micro-blog/approaches/regex +[separator]: https://exercism.org/tracks/javascript/exercises/micro-blog/approaches/intl-segmenter +[jsbench-me]: https://jsbench.me/ diff --git a/exercises/practice/micro-blog/.approaches/iterators/content.md b/exercises/practice/micro-blog/.approaches/iterators/content.md new file mode 100644 index 0000000000..7f0a8e2a7c --- /dev/null +++ b/exercises/practice/micro-blog/.approaches/iterators/content.md @@ -0,0 +1,27 @@ +# Iterators + +```javascript +let string = '👨‍👨‍👧‍👧💜🤧🤒🏥😀'; +let string2 = 'The quick brown fox jumps over the lazy dog. It barked.'; + +const splitWithIterator = (s) => [...s].slice(0, 5).join(''); + +console.log(splitWithIterator(string)); // will be "👨‍👨‍👧" - incorrect +console.log(splitWithIterator(string2)); // will be "‍The q" +``` + +This solution: + +- Uses [spread syntax][spread] to unpack the string into an array of its characters. + - internaly, the spread operator works with iterators to separate the string by its code points. +- Then it separates the first 5 characters (code points). +- Finally, it joins them back into a string. + + +~~~~exercism/note +This approach will not yield the correct result when applied to characters that are made of multiple +graphere clusters and are meant to represent a single visual unit, such as some emoji. +~~~~ + + +[spread]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax diff --git a/exercises/practice/micro-blog/.approaches/iterators/snippet.txt b/exercises/practice/micro-blog/.approaches/iterators/snippet.txt new file mode 100644 index 0000000000..5bc8f56548 --- /dev/null +++ b/exercises/practice/micro-blog/.approaches/iterators/snippet.txt @@ -0,0 +1,4 @@ +let string = '👨‍👨‍👧‍👧💜🤧🤒🏥😀'; + +const splitWithIterator = (s) => [...s].slice(0, 5).join(''); +console.log(splitWithIterator(string)) // will be "👨‍👨‍👧" - incorrect \ No newline at end of file diff --git a/exercises/practice/micro-blog/.approaches/regex/content.md b/exercises/practice/micro-blog/.approaches/regex/content.md new file mode 100644 index 0000000000..70b7216568 --- /dev/null +++ b/exercises/practice/micro-blog/.approaches/regex/content.md @@ -0,0 +1,27 @@ +# Regex + +```javascript +let string = '👨‍👨‍👧‍👧💜🤧🤒🏥😀'; +let string2 = 'The quick brown fox jumps over the lazy dog. It barked.'; + +const splitWithRegEx = (s) => s.match(/.{0,5}/gu)[0]; + +console.log(splitWithRegEx(string)); // will be "👨‍👨‍👧" - incorrect +console.log(splitWithIterator(string2)); // will be "‍The q" +``` + +This solution: + +- Uses the [String.match() method][match] with a supplied RegEx + - The RegEx supplied matches any character `.`, between 0 and 5 times `{0, 5}`. The `u` flag enables Unicode support. + - This matches characters by code points as well. +- Then it returns the first match as the output string. + + +~~~~exercism/note +This approach will not yield the correct result when applied to characters that are made of multiple +graphere clusters and are meant to represent a single visual unit, such as some emoji. +~~~~ + + +[match]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match diff --git a/exercises/practice/micro-blog/.approaches/regex/snippet.txt b/exercises/practice/micro-blog/.approaches/regex/snippet.txt new file mode 100644 index 0000000000..48961397da --- /dev/null +++ b/exercises/practice/micro-blog/.approaches/regex/snippet.txt @@ -0,0 +1,4 @@ +let string = '👨‍👨‍👧‍👧💜🤧🤒🏥😀'; + +const splitWithRegEx = (s) => s.match(/.{0,5}/gu)[0]; +console.log(splitWithRegEx(string)); // will be "👨‍👨‍👧" - incorrect \ No newline at end of file diff --git a/exercises/practice/micro-blog/.docs/instructions.append.md b/exercises/practice/micro-blog/.docs/instructions.append.md new file mode 100644 index 0000000000..cb285c5758 --- /dev/null +++ b/exercises/practice/micro-blog/.docs/instructions.append.md @@ -0,0 +1,33 @@ +# Instruction append + +## Unicode code points vs code units. + +A "normal" UTF-16 encoded string can be represented as a series of characters, where each character can be up to 16 bits long (hence, the name UTF-16). +This means there are a maximum of 2¹⁶ (two to the power of sixteen), or 65536 possible characters representable with 16 bits, or 1 code **unit**. +These 65536 characters form what's known as the [Basic Multilingual Set][basic-multilingual-set], which is large enough for the most common characters of most languages. + +However, some symbols, can't fit in just 1 code unit. The solution is to represent them with two code units. +These two UTF-16 code units, often also reffered to as a _surrogate pair_, form a code **point**. + +So, in summary, when reffering to UTF-16 encoding: + +- A `code unit` is 16 (or less) bits representing a single character. +- A `code point` is one or two code units representing a single character. + +To add more confusion to the mix, theres also _grapheme clusters_, +that are basically sequences of Unicode characters (code points) that should be treated as a single visual unit. +For example, some emojis, like this one 👨‍👦. + +## UTF-16 in Javascript + +Most built-in Javascript methods will work with UTF-16 encoded strings, however they work based on UTF-16 code units. +For example, a [`String.prototype.split("")`][split] method will separate a string by code units. + +On the other hand, [`String iterators`][iterator] iterate by code points. + +You can read a lot more, and find examples about Unicode strings, on [MDN][MDN]. + +[basic-multilingual-set]: https://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane +[split]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/split +[iterator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/@@iterator +[MDN]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#utf-16_characters_unicode_code_points_and_grapheme_clusters diff --git a/exercises/practice/micro-blog/.docs/instructions.md b/exercises/practice/micro-blog/.docs/instructions.md new file mode 100644 index 0000000000..d6c6cf6561 --- /dev/null +++ b/exercises/practice/micro-blog/.docs/instructions.md @@ -0,0 +1,37 @@ +# Instructions + +You have identified a gap in the social media market for very very short posts. +Now that Twitter allows 280 character posts, people wanting quick social media updates aren't being served. +You decide to create your own social media network. + +To make your product noteworthy, you make it extreme and only allow posts of 5 or less characters. +Any posts of more than 5 characters should be truncated to 5. + +To allow your users to express themselves fully, you allow Emoji and other Unicode. + +The task is to truncate input strings to 5 characters. + +## Text Encodings + +Text stored digitally has to be converted to a series of bytes. +There are 3 ways to map characters to bytes in common use. + +- **ASCII** can encode English language characters. + All characters are precisely 1 byte long. +- **UTF-8** is a Unicode text encoding. + Characters take between 1 and 4 bytes. +- **UTF-16** is a Unicode text encoding. + Characters are either 2 or 4 bytes long. + +UTF-8 and UTF-16 are both Unicode encodings which means they're capable of representing a massive range of characters including: + +- Text in most of the world's languages and scripts +- Historic text +- Emoji + +UTF-8 and UTF-16 are both variable length encodings, which means that different characters take up different amounts of space. + +Consider the letter 'a' and the emoji '😛'. +In UTF-16 the letter takes 2 bytes but the emoji takes 4 bytes. + +The trick to this exercise is to use APIs designed around Unicode characters (codepoints) instead of Unicode codeunits. diff --git a/exercises/practice/micro-blog/.gitignore b/exercises/practice/micro-blog/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/micro-blog/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/micro-blog/.meta/config.json b/exercises/practice/micro-blog/.meta/config.json new file mode 100644 index 0000000000..e33da00673 --- /dev/null +++ b/exercises/practice/micro-blog/.meta/config.json @@ -0,0 +1,17 @@ +{ + "authors": [ + "Cool-Katt" + ], + "files": { + "solution": [ + "micro-blog.js" + ], + "test": [ + "micro-blog.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] + }, + "blurb": "Given an input string, truncate it to 5 characters." +} diff --git a/exercises/practice/micro-blog/.meta/proof.ci.js b/exercises/practice/micro-blog/.meta/proof.ci.js new file mode 100644 index 0000000000..943829e3d4 --- /dev/null +++ b/exercises/practice/micro-blog/.meta/proof.ci.js @@ -0,0 +1,4 @@ +export const truncate = (input) => + Array.from(new Intl.Segmenter().segment(String(input)), (x) => x.segment) + .slice(0, 5) + .join(''); diff --git a/exercises/practice/micro-blog/.meta/tests.toml b/exercises/practice/micro-blog/.meta/tests.toml new file mode 100644 index 0000000000..f23ff0bc22 --- /dev/null +++ b/exercises/practice/micro-blog/.meta/tests.toml @@ -0,0 +1,46 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[b927b57f-7c98-42fd-8f33-fae091dc1efc] +description = "English language short" + +[a3fcdc5b-0ed4-4f49-80f5-b1a293eac2a0] +description = "English language long" + +[01910864-8e15-4007-9c7c-ac956c686e60] +description = "German language short (broth)" + +[f263e488-aefb-478f-a671-b6ba99722543] +description = "German language long (bear carpet → beards)" + +[0916e8f1-41d7-4402-a110-b08aa000342c] +description = "Bulgarian language short (good)" + +[bed6b89c-03df-4154-98e6-a61a74f61b7d] +description = "Greek language short (health)" + +[485a6a70-2edb-424d-b999-5529dbc8e002] +description = "Maths short" + +[8b4b7b51-8f48-4fbe-964e-6e4e6438be28] +description = "Maths long" + +[71f4a192-0566-4402-a512-fe12878be523] +description = "English and emoji short" + +[6f0f71f3-9806-4759-a844-fa182f7bc203] +description = "Emoji short" + +[ce71fb92-5214-46d0-a7f8-d5ba56b4cc6e] +description = "Emoji long" + +[5dee98d2-d56e-468a-a1f2-121c3f7c5a0b] +description = "Royal Flush?" diff --git a/exercises/practice/micro-blog/.npmrc b/exercises/practice/micro-blog/.npmrc new file mode 100644 index 0000000000..d26df800bb --- /dev/null +++ b/exercises/practice/micro-blog/.npmrc @@ -0,0 +1 @@ +audit=false diff --git a/exercises/practice/micro-blog/LICENSE b/exercises/practice/micro-blog/LICENSE new file mode 100644 index 0000000000..90e73be03b --- /dev/null +++ b/exercises/practice/micro-blog/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Exercism + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/exercises/practice/micro-blog/babel.config.js b/exercises/practice/micro-blog/babel.config.js new file mode 100644 index 0000000000..a638497df1 --- /dev/null +++ b/exercises/practice/micro-blog/babel.config.js @@ -0,0 +1,4 @@ +module.exports = { + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], +}; diff --git a/exercises/practice/micro-blog/eslint.config.mjs b/exercises/practice/micro-blog/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/micro-blog/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/micro-blog/jest.config.js b/exercises/practice/micro-blog/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/micro-blog/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/micro-blog/micro-blog.js b/exercises/practice/micro-blog/micro-blog.js new file mode 100644 index 0000000000..c33965ca96 --- /dev/null +++ b/exercises/practice/micro-blog/micro-blog.js @@ -0,0 +1,8 @@ +// +// This is only a SKELETON file for the 'Micro-blog' exercise. It's been provided as a +// convenience to get you started writing code faster. +// + +export const truncate = (input) => { + throw new Error('Remove this line and implement the function'); +}; diff --git a/exercises/practice/micro-blog/micro-blog.spec.js b/exercises/practice/micro-blog/micro-blog.spec.js new file mode 100644 index 0000000000..692b283b0b --- /dev/null +++ b/exercises/practice/micro-blog/micro-blog.spec.js @@ -0,0 +1,88 @@ +import { describe, expect, test, xtest } from '@jest/globals'; +import { truncate } from './micro-blog'; + +describe('Micro-blog', () => { + test('English language short', () => { + const inputString = 'Hi'; + const expected = 'Hi'; + const actual = truncate(inputString); + expect(actual).toEqual(expected); + }); + + xtest('English language long', () => { + const inputString = 'Hello there'; + const expected = 'Hello'; + const actual = truncate(inputString); + expect(actual).toEqual(expected); + }); + + xtest('German language short (broth)', () => { + const inputString = 'brühe'; + const expected = 'brühe'; + const actual = truncate(inputString); + expect(actual).toEqual(expected); + }); + + xtest('German language long (bear carpet → beards)', () => { + const inputString = 'Bärteppich'; + const expected = 'Bärte'; + const actual = truncate(inputString); + expect(actual).toEqual(expected); + }); + + xtest('Bulgarian language short (good)', () => { + const inputString = 'Добър'; + const expected = 'Добър'; + const actual = truncate(inputString); + expect(actual).toEqual(expected); + }); + + xtest('Greek language short (health)', () => { + const inputString = 'υγειά'; + const expected = 'υγειά'; + const actual = truncate(inputString); + expect(actual).toEqual(expected); + }); + + xtest('Maths short', () => { + const inputString = 'a=πr²'; + const expected = 'a=πr²'; + const actual = truncate(inputString); + expect(actual).toEqual(expected); + }); + + xtest('Maths long', () => { + const inputString = '∅⊊ℕ⊊ℤ⊊ℚ⊊ℝ⊊ℂ'; + const expected = '∅⊊ℕ⊊ℤ'; + const actual = truncate(inputString); + expect(actual).toEqual(expected); + }); + + xtest('English and emoji short', () => { + const inputString = 'Fly 🛫'; + const expected = 'Fly 🛫'; + const actual = truncate(inputString); + expect(actual).toEqual(expected); + }); + + xtest('Emoji short', () => { + const inputString = '💇'; + const expected = '💇'; + const actual = truncate(inputString); + expect(actual).toEqual(expected); + }); + + xtest('Emoji long', () => { + const inputString = '❄🌡🤧🤒🏥🕰😀'; + const expected = '❄🌡🤧🤒🏥'; + const actual = truncate(inputString); + expect(actual).toEqual(expected); + }); + + xtest('Royal Flush?', () => { + const inputString = '🃎🂸🃅🃋🃍🃁🃊'; + const expected = '🃎🂸🃅🃋🃍'; + const actual = truncate(inputString); + expect(actual).toEqual(expected); + }); +}); diff --git a/exercises/practice/micro-blog/package.json b/exercises/practice/micro-blog/package.json new file mode 100644 index 0000000000..fccce366bf --- /dev/null +++ b/exercises/practice/micro-blog/package.json @@ -0,0 +1,39 @@ +{ + "name": "@exercism/javascript-micro-blog", + "description": "Exercism practice exercise on micro-blog", + "author": "Katrina Owen", + "contributors": [ + "Cool-Katt (https://github.com/Cool-Katt)", + "Derk-Jan Karrenbeld (https://derk-jan.com)", + "Tejas Bubane (https://tejasbubane.github.io/)" + ], + "private": true, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/exercism/javascript", + "directory": "exercises/practice/micro-blog" + }, + "devDependencies": { + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" + }, + "dependencies": {}, + "scripts": { + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" +} diff --git a/exercises/practice/minesweeper/.docs/instructions.md b/exercises/practice/minesweeper/.docs/instructions.md index 1114cc95de..7c1df2e4ba 100644 --- a/exercises/practice/minesweeper/.docs/instructions.md +++ b/exercises/practice/minesweeper/.docs/instructions.md @@ -1,35 +1,24 @@ # Instructions -Add the mine counts to a completed Minesweeper board. +Your task is to add the mine counts to empty squares in a completed Minesweeper board. +The board itself is a rectangle composed of squares that are either empty (`' '`) or a mine (`'*'`). -Minesweeper is a popular game where the user has to find the mines using -numeric hints that indicate how many mines are directly adjacent -(horizontally, vertically, diagonally) to a square. +For each empty square, count the number of mines adjacent to it (horizontally, vertically, diagonally). +If the empty square has no adjacent mines, leave it empty. +Otherwise replace it with the adjacent mines count. -In this exercise you have to create some code that counts the number of -mines adjacent to a given empty square and replaces that square with the -count. +For example, you may receive a 5 x 4 board like this (empty spaces are represented here with the '·' character for display on screen): -The board is a rectangle composed of blank space (' ') characters. A mine -is represented by an asterisk ('\*') character. - -If a given space has no adjacent mines at all, leave that square blank. - -## Examples - -For example you may receive a 5 x 4 board like this (empty spaces are -represented here with the '·' character for display on screen): - -``` +```text ·*·*· ··*·· ··*·· ····· ``` -And your code will transform it into this: +Which your code should transform into this: -``` +```text 1*3*1 13*31 ·2*2· diff --git a/exercises/practice/minesweeper/.docs/introduction.md b/exercises/practice/minesweeper/.docs/introduction.md new file mode 100644 index 0000000000..5f74a742b0 --- /dev/null +++ b/exercises/practice/minesweeper/.docs/introduction.md @@ -0,0 +1,5 @@ +# Introduction + +[Minesweeper][wikipedia] is a popular game where the user has to find the mines using numeric hints that indicate how many mines are directly adjacent (horizontally, vertically, diagonally) to a square. + +[wikipedia]: https://en.wikipedia.org/wiki/Minesweeper_(video_game) diff --git a/exercises/practice/minesweeper/.eslintrc b/exercises/practice/minesweeper/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/minesweeper/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/minesweeper/.gitignore b/exercises/practice/minesweeper/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/minesweeper/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/minesweeper/.meta/config.json b/exercises/practice/minesweeper/.meta/config.json index d48208d9d5..3aaf0645c7 100644 --- a/exercises/practice/minesweeper/.meta/config.json +++ b/exercises/practice/minesweeper/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Add the numbers to a minesweeper board", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "brendanmckeown", "cr0t", @@ -10,8 +11,21 @@ "xarxziux" ], "files": { - "solution": ["minesweeper.js"], - "test": ["minesweeper.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "minesweeper.js" + ], + "test": [ + "minesweeper.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] + }, + "blurb": "Add the numbers to a minesweeper board.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false } } diff --git a/exercises/practice/minesweeper/.meta/proof.ci.js b/exercises/practice/minesweeper/.meta/proof.ci.js index 3a760ea2c7..8509f91a24 100644 --- a/exercises/practice/minesweeper/.meta/proof.ci.js +++ b/exercises/practice/minesweeper/.meta/proof.ci.js @@ -21,7 +21,7 @@ function adjacentSquareHasMine(board, x, y, d) { function countAdjacentMines(board, x, y) { return DELTAS.filter((d) => adjacentSquareIsOnBoard(board, x, d)).filter( - (d) => adjacentSquareHasMine(board, x, y, d) + (d) => adjacentSquareHasMine(board, x, y, d), ).length; } @@ -50,7 +50,7 @@ export function annotate(rows) { return stringify( inputBoard.map((row, x) => - [...row].map((cell, y) => cellToMineOrCount(cell, inputBoard, x, y)) - ) + [...row].map((cell, y) => cellToMineOrCount(cell, inputBoard, x, y)), + ), ); } diff --git a/exercises/practice/minesweeper/babel.config.js b/exercises/practice/minesweeper/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/minesweeper/babel.config.js +++ b/exercises/practice/minesweeper/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/minesweeper/eslint.config.mjs b/exercises/practice/minesweeper/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/minesweeper/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/minesweeper/jest.config.js b/exercises/practice/minesweeper/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/minesweeper/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/minesweeper/minesweeper.js b/exercises/practice/minesweeper/minesweeper.js index ced01d454c..109f9f450b 100644 --- a/exercises/practice/minesweeper/minesweeper.js +++ b/exercises/practice/minesweeper/minesweeper.js @@ -4,5 +4,5 @@ // export const annotate = (input) => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/minesweeper/minesweeper.spec.js b/exercises/practice/minesweeper/minesweeper.spec.js index cb6663e20b..ff2250ade2 100644 --- a/exercises/practice/minesweeper/minesweeper.spec.js +++ b/exercises/practice/minesweeper/minesweeper.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { annotate } from './minesweeper'; describe(')', () => { diff --git a/exercises/practice/minesweeper/package.json b/exercises/practice/minesweeper/package.json index ed36acedb0..157bd378c9 100644 --- a/exercises/practice/minesweeper/package.json +++ b/exercises/practice/minesweeper/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/minesweeper" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/nth-prime/.docs/instructions.md b/exercises/practice/nth-prime/.docs/instructions.md index 30a75216fd..065e323ab2 100644 --- a/exercises/practice/nth-prime/.docs/instructions.md +++ b/exercises/practice/nth-prime/.docs/instructions.md @@ -2,8 +2,6 @@ Given a number n, determine what the nth prime is. -By listing the first six prime numbers: 2, 3, 5, 7, 11, and 13, we can see that -the 6th prime is 13. +By listing the first six prime numbers: 2, 3, 5, 7, 11, and 13, we can see that the 6th prime is 13. -If your language provides methods in the standard library to deal with prime -numbers, pretend they don't exist and implement them yourself. +If your language provides methods in the standard library to deal with prime numbers, pretend they don't exist and implement them yourself. diff --git a/exercises/practice/nth-prime/.eslintrc b/exercises/practice/nth-prime/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/nth-prime/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/nth-prime/.gitignore b/exercises/practice/nth-prime/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/nth-prime/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/nth-prime/.meta/config.json b/exercises/practice/nth-prime/.meta/config.json index 77a41b9e7b..758c2bba03 100644 --- a/exercises/practice/nth-prime/.meta/config.json +++ b/exercises/practice/nth-prime/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Given a number n, determine what the nth prime is.", - "authors": ["rchavarria"], + "authors": [ + "rchavarria" + ], "contributors": [ "ankorGH", "hayashi-ay", @@ -12,10 +13,23 @@ "xarxziux" ], "files": { - "solution": ["nth-prime.js"], - "test": ["nth-prime.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "nth-prime.js" + ], + "test": [ + "nth-prime.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Given a number n, determine what the nth prime is.", "source": "A variation on Problem 7 at Project Euler", - "source_url": "http://projecteuler.net/problem=7" + "source_url": "https://projecteuler.net/problem=7", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/nth-prime/babel.config.js b/exercises/practice/nth-prime/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/nth-prime/babel.config.js +++ b/exercises/practice/nth-prime/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/nth-prime/eslint.config.mjs b/exercises/practice/nth-prime/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/nth-prime/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/nth-prime/jest.config.js b/exercises/practice/nth-prime/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/nth-prime/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/nth-prime/nth-prime.js b/exercises/practice/nth-prime/nth-prime.js index 6dbabc01d2..861fc4eec4 100644 --- a/exercises/practice/nth-prime/nth-prime.js +++ b/exercises/practice/nth-prime/nth-prime.js @@ -4,5 +4,5 @@ // export const prime = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/nth-prime/nth-prime.spec.js b/exercises/practice/nth-prime/nth-prime.spec.js index 5ebfd52ed2..59613d9f12 100644 --- a/exercises/practice/nth-prime/nth-prime.spec.js +++ b/exercises/practice/nth-prime/nth-prime.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { prime } from './nth-prime'; describe('nth-prime', () => { diff --git a/exercises/practice/nth-prime/package.json b/exercises/practice/nth-prime/package.json index 1b3aa957d6..8702968f83 100644 --- a/exercises/practice/nth-prime/package.json +++ b/exercises/practice/nth-prime/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/nth-prime" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/nucleotide-count/.docs/instructions.md b/exercises/practice/nucleotide-count/.docs/instructions.md index cd08758943..548d9ba5a5 100644 --- a/exercises/practice/nucleotide-count/.docs/instructions.md +++ b/exercises/practice/nucleotide-count/.docs/instructions.md @@ -1,10 +1,12 @@ # Instructions -Each of us inherits from our biological parents a set of chemical instructions known as DNA that influence how our bodies are constructed. All known life depends on DNA! +Each of us inherits from our biological parents a set of chemical instructions known as DNA that influence how our bodies are constructed. +All known life depends on DNA! > Note: You do not need to understand anything about nucleotides or DNA to complete this exercise. -DNA is a long chain of other chemicals and the most important are the four nucleotides, adenine, cytosine, guanine and thymine. A single DNA chain can contain billions of these four nucleotides and the order in which they occur is important! +DNA is a long chain of other chemicals and the most important are the four nucleotides, adenine, cytosine, guanine and thymine. +A single DNA chain can contain billions of these four nucleotides and the order in which they occur is important! We call the order of these nucleotides in a bit of DNA a "DNA sequence". We represent a DNA sequence as an ordered collection of these four nucleotides and a common way to do that is with a string of characters such as "ATTACG" for a DNA sequence of 6 nucleotides. @@ -15,7 +17,7 @@ If the string contains characters that aren't A, C, G, or T then it is invalid a For example: -``` +```text "GATTACA" -> 'A': 3, 'C': 1, 'G': 1, 'T': 2 "INVALID" -> error ``` diff --git a/exercises/practice/nucleotide-count/.eslintrc b/exercises/practice/nucleotide-count/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/nucleotide-count/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/nucleotide-count/.gitignore b/exercises/practice/nucleotide-count/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/nucleotide-count/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/nucleotide-count/.meta/config.json b/exercises/practice/nucleotide-count/.meta/config.json index c07ec576ff..d2f001af9d 100644 --- a/exercises/practice/nucleotide-count/.meta/config.json +++ b/exercises/practice/nucleotide-count/.meta/config.json @@ -1,12 +1,31 @@ { - "blurb": "Given a DNA string, compute how many times each nucleotide occurs in the string.", - "authors": ["tarunvelli"], - "contributors": ["ankorGH", "hayashi-ay", "SleeplessByte", "tejasbubane"], + "authors": [ + "tarunvelli" + ], + "contributors": [ + "ankorGH", + "hayashi-ay", + "SleeplessByte", + "tejasbubane" + ], "files": { - "solution": ["nucleotide-count.js"], - "test": ["nucleotide-count.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "nucleotide-count.js" + ], + "test": [ + "nucleotide-count.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Given a DNA string, compute how many times each nucleotide occurs in the string.", "source": "The Calculating DNA Nucleotides_problem at Rosalind", - "source_url": "http://rosalind.info/problems/dna/" + "source_url": "https://rosalind.info/problems/dna/", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/nucleotide-count/.meta/proof.ci.js b/exercises/practice/nucleotide-count/.meta/proof.ci.js index ceceff145a..5245c52de7 100644 --- a/exercises/practice/nucleotide-count/.meta/proof.ci.js +++ b/exercises/practice/nucleotide-count/.meta/proof.ci.js @@ -8,6 +8,6 @@ export function countNucleotides(strand) { return `${count(strand, 'A')} ${count(strand, 'C')} ${count( strand, - 'G' + 'G', )} ${count(strand, 'T')}`; } diff --git a/exercises/practice/nucleotide-count/babel.config.js b/exercises/practice/nucleotide-count/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/nucleotide-count/babel.config.js +++ b/exercises/practice/nucleotide-count/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/nucleotide-count/eslint.config.mjs b/exercises/practice/nucleotide-count/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/nucleotide-count/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/nucleotide-count/jest.config.js b/exercises/practice/nucleotide-count/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/nucleotide-count/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/nucleotide-count/nucleotide-count.js b/exercises/practice/nucleotide-count/nucleotide-count.js index 7f45aa1fca..75538ce38b 100644 --- a/exercises/practice/nucleotide-count/nucleotide-count.js +++ b/exercises/practice/nucleotide-count/nucleotide-count.js @@ -4,5 +4,5 @@ // export function countNucleotides(strand) { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } diff --git a/exercises/practice/nucleotide-count/nucleotide-count.spec.js b/exercises/practice/nucleotide-count/nucleotide-count.spec.js index fa297837f0..d767e01040 100644 --- a/exercises/practice/nucleotide-count/nucleotide-count.spec.js +++ b/exercises/practice/nucleotide-count/nucleotide-count.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { countNucleotides } from './nucleotide-count'; describe('count all nucleotides in a strand', () => { @@ -16,14 +17,14 @@ describe('count all nucleotides in a strand', () => { xtest('strand with multiple nucleotides', () => { expect( countNucleotides( - 'AGCTTTTCATTCTGACTGCAACGGGCAATATGTCTCTGTGTGGATTAAAAAAAGAGTGTCTGATAGCAGC' - ) + 'AGCTTTTCATTCTGACTGCAACGGGCAATATGTCTCTGTGTGGATTAAAAAAAGAGTGTCTGATAGCAGC', + ), ).toEqual('20 12 17 21'); }); xtest('strand with invalid nucleotides', () => { expect(() => countNucleotides('AGXXACT')).toThrow( - new Error('Invalid nucleotide in strand') + new Error('Invalid nucleotide in strand'), ); }); }); diff --git a/exercises/practice/nucleotide-count/package.json b/exercises/practice/nucleotide-count/package.json index 80c48992c3..bec032c29c 100644 --- a/exercises/practice/nucleotide-count/package.json +++ b/exercises/practice/nucleotide-count/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/nucleotide-count" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/ocr-numbers/.docs/instructions.md b/exercises/practice/ocr-numbers/.docs/instructions.md index a246b898aa..7beb257795 100644 --- a/exercises/practice/ocr-numbers/.docs/instructions.md +++ b/exercises/practice/ocr-numbers/.docs/instructions.md @@ -1,9 +1,8 @@ # Instructions -Given a 3 x 4 grid of pipes, underscores, and spaces, determine which number is -represented, or whether it is garbled. +Given a 3 x 4 grid of pipes, underscores, and spaces, determine which number is represented, or whether it is garbled. -# Step One +## Step One To begin with, convert a simple binary font to a string containing 0 or 1. @@ -31,11 +30,11 @@ If the input is the correct size, but not recognizable, your program should retu If the input is the incorrect size, your program should return an error. -# Step Two +## Step Two Update your program to recognize multi-character binary strings, replacing garbled numbers with ? -# Step Three +## Step Three Update your program to recognize all numbers 0 through 9, both individually and as part of a larger string. @@ -57,9 +56,10 @@ Is converted to "2" Is converted to "1234567890" -# Step Four +## Step Four -Update your program to handle multiple numbers, one per line. When converting several lines, join the lines with commas. +Update your program to handle multiple numbers, one per line. +When converting several lines, join the lines with commas. ```text _ _ @@ -76,4 +76,4 @@ Update your program to handle multiple numbers, one per line. When converting se ``` -Is converted to "123,456,789" +Is converted to "123,456,789". diff --git a/exercises/practice/ocr-numbers/.eslintrc b/exercises/practice/ocr-numbers/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/ocr-numbers/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/ocr-numbers/.gitignore b/exercises/practice/ocr-numbers/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/ocr-numbers/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/ocr-numbers/.meta/config.json b/exercises/practice/ocr-numbers/.meta/config.json index 32607bcf32..02124d6ec4 100644 --- a/exercises/practice/ocr-numbers/.meta/config.json +++ b/exercises/practice/ocr-numbers/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Given a 3 x 4 grid of pipes, underscores, and spaces, determine which number is represented, or whether it is garbled.", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "ankorGH", "rchavarria", @@ -9,10 +10,23 @@ "xarxziux" ], "files": { - "solution": ["ocr-numbers.js"], - "test": ["ocr-numbers.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "ocr-numbers.js" + ], + "test": [ + "ocr-numbers.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Given a 3 x 4 grid of pipes, underscores, and spaces, determine which number is represented, or whether it is garbled.", "source": "Inspired by the Bank OCR kata", - "source_url": "http://codingdojo.org/cgi-bin/wiki.pl?KataBankOCR" + "source_url": "https://codingdojo.org/kata/BankOCR/", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/ocr-numbers/.meta/proof.ci.js b/exercises/practice/ocr-numbers/.meta/proof.ci.js index 34ae6ec18e..bf3a124be9 100644 --- a/exercises/practice/ocr-numbers/.meta/proof.ci.js +++ b/exercises/practice/ocr-numbers/.meta/proof.ci.js @@ -30,7 +30,7 @@ const splitIntoDigits = (row) => { for (let digitNumber = 0; digitNumber < rows[0].length; digitNumber += 3) { let digit = ''; for (let rowNumber = 0; rowNumber < rows.length; rowNumber += 1) { - digit += rows[rowNumber].substr(digitNumber, 3); + digit += rows[rowNumber].substring(digitNumber, digitNumber + 3); } digits.push(digit); } diff --git a/exercises/practice/ocr-numbers/babel.config.js b/exercises/practice/ocr-numbers/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/ocr-numbers/babel.config.js +++ b/exercises/practice/ocr-numbers/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/ocr-numbers/eslint.config.mjs b/exercises/practice/ocr-numbers/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/ocr-numbers/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/ocr-numbers/jest.config.js b/exercises/practice/ocr-numbers/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/ocr-numbers/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/ocr-numbers/ocr-numbers.js b/exercises/practice/ocr-numbers/ocr-numbers.js index bc04770bfb..2d238ca461 100644 --- a/exercises/practice/ocr-numbers/ocr-numbers.js +++ b/exercises/practice/ocr-numbers/ocr-numbers.js @@ -4,5 +4,5 @@ // export const convert = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/ocr-numbers/ocr-numbers.spec.js b/exercises/practice/ocr-numbers/ocr-numbers.spec.js index b9a18cf1e4..890e7052b3 100644 --- a/exercises/practice/ocr-numbers/ocr-numbers.spec.js +++ b/exercises/practice/ocr-numbers/ocr-numbers.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { convert } from './ocr-numbers'; describe('ocr', () => { @@ -9,7 +10,7 @@ describe('ocr', () => { '| |\n' + '|_|\n' + ' ' - ) + ), ).toBe('0'); }); @@ -21,7 +22,7 @@ describe('ocr', () => { ' |\n' + ' |\n' + ' ' - ) + ), ).toBe('1'); }); @@ -33,7 +34,7 @@ describe('ocr', () => { ' _|\n' + '|_ \n' + ' ' - ) + ), ).toBe('2'); }); @@ -45,7 +46,7 @@ describe('ocr', () => { ' _|\n' + ' _|\n' + ' ' - ) + ), ).toBe('3'); }); @@ -57,7 +58,7 @@ describe('ocr', () => { '|_|\n' + ' |\n' + ' ' - ) + ), ).toBe('4'); }); @@ -69,7 +70,7 @@ describe('ocr', () => { '|_ \n' + ' _|\n' + ' ' - ) + ), ).toBe('5'); }); @@ -81,7 +82,7 @@ describe('ocr', () => { '|_ \n' + '|_|\n' + ' ' - ) + ), ).toBe('6'); }); @@ -93,7 +94,7 @@ describe('ocr', () => { ' |\n' + ' |\n' + ' ' - ) + ), ).toBe('7'); }); @@ -105,7 +106,7 @@ describe('ocr', () => { '|_|\n' + '|_|\n' + ' ' - ) + ), ).toBe('8'); }); @@ -117,7 +118,7 @@ describe('ocr', () => { '|_|\n' + ' _|\n' + ' ' - ) + ), ).toBe('9'); }); @@ -129,7 +130,7 @@ describe('ocr', () => { ' || |\n' + ' ||_|\n' + ' ' - ) + ), ).toBe('10'); }); @@ -141,7 +142,7 @@ describe('ocr', () => { '| |\n' + '| |\n' + ' ' - ) + ), ).toBe('?'); }); @@ -153,7 +154,7 @@ describe('ocr', () => { ' | || | || | | || || |\n' + ' | ||_| ||_| | ||_||_|\n' + ' ' - ) + ), ).toBe('110101100'); }); @@ -165,7 +166,7 @@ describe('ocr', () => { ' | || | || | || || |\n' + ' | | _| ||_| | ||_||_|\n' + ' ' - ) + ), ).toBe('11?10?1?0'); }); @@ -177,7 +178,7 @@ describe('ocr', () => { ' | _| _||_||_ |_ ||_||_|| |\n' + ' ||_ _| | _||_| ||_| _||_|\n' + ' ' - ) + ), ).toBe('1234567890'); }); @@ -197,7 +198,7 @@ describe('ocr', () => { ' ||_||_|\n' + ' ||_| _|\n' + ' ' - ) + ), ).toBe('123,456,789'); }); }); diff --git a/exercises/practice/ocr-numbers/package.json b/exercises/practice/ocr-numbers/package.json index 020f6c7c63..8ca59b7538 100644 --- a/exercises/practice/ocr-numbers/package.json +++ b/exercises/practice/ocr-numbers/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/ocr-numbers" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/octal/.eslintrc b/exercises/practice/octal/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/octal/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/octal/.gitignore b/exercises/practice/octal/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/octal/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/octal/.meta/config.json b/exercises/practice/octal/.meta/config.json index c6ccf59cdf..c22041f9c2 100644 --- a/exercises/practice/octal/.meta/config.json +++ b/exercises/practice/octal/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Convert a octal number, represented as a string (e.g. '1735263'), to its decimal equivalent using first principles (i.e. no, you may not use built-in or external libraries to accomplish the conversion).", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "ankorGH", "msomji", @@ -10,10 +11,23 @@ "tejasbubane" ], "files": { - "solution": ["octal.js"], - "test": ["octal.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "octal.js" + ], + "test": [ + "octal.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Convert a octal number, represented as a string (e.g. '1735263'), to its decimal equivalent using first principles (i.e. no, you may not use built-in or external libraries to accomplish the conversion).", "source": "All of Computer Science", - "source_url": "http://www.wolframalpha.com/input/?i=base+8" + "source_url": "http://www.wolframalpha.com/input/?i=base+8", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/octal/babel.config.js b/exercises/practice/octal/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/octal/babel.config.js +++ b/exercises/practice/octal/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/octal/eslint.config.mjs b/exercises/practice/octal/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/octal/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/octal/jest.config.js b/exercises/practice/octal/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/octal/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/octal/octal.js b/exercises/practice/octal/octal.js index 5531d250aa..aeb91fa97d 100644 --- a/exercises/practice/octal/octal.js +++ b/exercises/practice/octal/octal.js @@ -5,10 +5,10 @@ export class Octal { constructor() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } toDecimal() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/octal/octal.spec.js b/exercises/practice/octal/octal.spec.js index 0074612e84..a6a9eae55e 100644 --- a/exercises/practice/octal/octal.spec.js +++ b/exercises/practice/octal/octal.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { Octal } from './octal'; describe('octal', () => { diff --git a/exercises/practice/octal/package.json b/exercises/practice/octal/package.json index 1dc242fa9e..9f42836089 100644 --- a/exercises/practice/octal/package.json +++ b/exercises/practice/octal/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/octal" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/palindrome-products/.docs/instructions.md b/exercises/practice/palindrome-products/.docs/instructions.md index fd9a441247..aac66521ce 100644 --- a/exercises/practice/palindrome-products/.docs/instructions.md +++ b/exercises/practice/palindrome-products/.docs/instructions.md @@ -2,15 +2,14 @@ Detect palindrome products in a given range. -A palindromic number is a number that remains the same when its digits are -reversed. For example, `121` is a palindromic number but `112` is not. +A palindromic number is a number that remains the same when its digits are reversed. +For example, `121` is a palindromic number but `112` is not. Given a range of numbers, find the largest and smallest palindromes which are products of two numbers within that range. -Your solution should return the largest and smallest palindromes, along with the -factors of each within the range. If the largest or smallest palindrome has more -than one pair of factors within the range, then return all the pairs. +Your solution should return the largest and smallest palindromes, along with the factors of each within the range. +If the largest or smallest palindrome has more than one pair of factors within the range, then return all the pairs. ## Example 1 @@ -22,12 +21,16 @@ And given the list of all possible products within this range: The palindrome products are all single digit numbers (in this case): `[1, 2, 3, 4, 5, 6, 7, 8, 9]` -The smallest palindrome product is `1`. Its factors are `(1, 1)`. -The largest palindrome product is `9`. Its factors are `(1, 9)` and `(3, 3)`. +The smallest palindrome product is `1`. +Its factors are `(1, 1)`. +The largest palindrome product is `9`. +Its factors are `(1, 9)` and `(3, 3)`. ## Example 2 Given the range `[10, 99]` (both inclusive)... -The smallest palindrome product is `121`. Its factors are `(11, 11)`. -The largest palindrome product is `9009`. Its factors are `(91, 99)`. +The smallest palindrome product is `121`. +Its factors are `(11, 11)`. +The largest palindrome product is `9009`. +Its factors are `(91, 99)`. diff --git a/exercises/practice/palindrome-products/.eslintrc b/exercises/practice/palindrome-products/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/palindrome-products/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/palindrome-products/.gitignore b/exercises/practice/palindrome-products/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/palindrome-products/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/palindrome-products/.meta/config.json b/exercises/practice/palindrome-products/.meta/config.json index b671116bb1..a814a8e32b 100644 --- a/exercises/practice/palindrome-products/.meta/config.json +++ b/exercises/practice/palindrome-products/.meta/config.json @@ -1,11 +1,12 @@ { - "blurb": "Detect palindrome products in a given range.", "authors": [], "contributors": [ "ankorGH", "cmccandless", + "Cool-Katt", "draalger", "ErikSchierboom", + "jagdish-15", "javaeeeee", "kytrinyx", "matthewmorgan", @@ -16,10 +17,23 @@ "xarxziux" ], "files": { - "solution": ["palindrome-products.js"], - "test": ["palindrome-products.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "palindrome-products.js" + ], + "test": [ + "palindrome-products.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Detect palindrome products in a given range.", "source": "Problem 4 at Project Euler", - "source_url": "http://projecteuler.net/problem=4" + "source_url": "https://projecteuler.net/problem=4", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": true + } } diff --git a/exercises/practice/palindrome-products/.meta/proof.ci.js b/exercises/practice/palindrome-products/.meta/proof.ci.js index 7bd25bf724..5e574e37cc 100644 --- a/exercises/practice/palindrome-products/.meta/proof.ci.js +++ b/exercises/practice/palindrome-products/.meta/proof.ci.js @@ -1,88 +1,38 @@ -const reverseString = (str) => str.split('').reverse().join(''); - -class Palindrome { - constructor(factor1, factor2) { - this.value = factor1 * factor2; - this.factors = [[factor1, factor2]]; - } - - withFactors(factors) { - this.factors.push(factors); - return this; - } - - valid() { - const s = `${this.value}`; - return s === reverseString(s); - } - - merge(other) { - other.factors.forEach((f) => { - this.factors.push(f); - }); - return this; - } -} - export class Palindromes { - constructor(maxFactor, minFactor = 1) { - this.maxFactor = maxFactor; - this.minFactor = minFactor; - } - - get largest() { - let left = this.maxFactor, - right = this.maxFactor, - best = new Palindrome(this.minFactor, this.minFactor); - - while (right >= this.minFactor) { - let p = new Palindrome(left, right); - - if (best.value && p.value < best.value) { - right--; - left = right; - continue; - } - - if (p.valid()) { - if (best.value < p.value) { - best = p; - } else if (best.value === p.value) { - best = p.merge(best); + static generate({ minFactor, maxFactor }) { + if (minFactor > maxFactor) throw new Error('min must be <= max'); + let isPalindrome = (n) => + [...n.toString()].reverse().join('') === n.toString(); + let search = (n, pred, fn) => { + while (pred(n)) { + if (!isPalindrome(n)) { + n = fn(n); + continue; } - } - - if (left <= this.minFactor) { - right--; - left = right; - } else { - left--; - } - } - - if (best.valid()) { - return best; - } - - return { value: null, factors: [] }; - } - - get smallest() { - for (let m = this.minFactor; m <= this.maxFactor; m += 1) { - for (let n = m; n <= this.maxFactor; n += 1) { - const p = new Palindrome(m, n); - if (p.valid()) { - return p; + let factors = []; + for (let p = minFactor; p <= n / p; p++) { + if (n % p === 0) { + let q = n / p; + if (q <= maxFactor) factors.push([p, q]); + } } + if (factors.length > 0) return { value: n, factors }; + n = fn(n); } - } - return { value: null, factors: [] }; - } - - static generate(params) { - if ((params.minFactor || 1) > params.maxFactor) { - throw new Error('min must be <= max'); - } - return new Palindromes(params.maxFactor, params.minFactor || 1); + return { value: null, factors: [] }; + }; + let [lower, upper] = [minFactor * minFactor, maxFactor * maxFactor]; + return { + largest: search( + upper, + (n) => n >= lower, + (x) => x - 1, + ), + smallest: search( + lower, + (n) => n <= upper, + (x) => x + 1, + ), + }; } } diff --git a/exercises/practice/palindrome-products/.meta/tests.toml b/exercises/practice/palindrome-products/.meta/tests.toml index b34cb0d475..a3bc41750a 100644 --- a/exercises/practice/palindrome-products/.meta/tests.toml +++ b/exercises/practice/palindrome-products/.meta/tests.toml @@ -1,12 +1,19 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [5cff78fe-cf02-459d-85c2-ce584679f887] -description = "finds the smallest palindrome from single digit factors" +description = "find the smallest palindrome from single digit factors" [0853f82c-5fc4-44ae-be38-fadb2cced92d] -description = "finds the largest palindrome from single digit factors" +description = "find the largest palindrome from single digit factors" [66c3b496-bdec-4103-9129-3fcb5a9063e1] description = "find the smallest palindrome from double digit factors" @@ -15,13 +22,13 @@ description = "find the smallest palindrome from double digit factors" description = "find the largest palindrome from double digit factors" [cecb5a35-46d1-4666-9719-fa2c3af7499d] -description = "find smallest palindrome from triple digit factors" +description = "find the smallest palindrome from triple digit factors" [edab43e1-c35f-4ea3-8c55-2f31dddd92e5] description = "find the largest palindrome from triple digit factors" [4f802b5a-9d74-4026-a70f-b53ff9234e4e] -description = "find smallest palindrome from four digit factors" +description = "find the smallest palindrome from four digit factors" [787525e0-a5f9-40f3-8cb2-23b52cf5d0be] description = "find the largest palindrome from four digit factors" @@ -37,3 +44,6 @@ description = "error result for smallest if min is more than max" [eeeb5bff-3f47-4b1e-892f-05829277bd74] description = "error result for largest if min is more than max" + +[16481711-26c4-42e0-9180-e2e4e8b29c23] +description = "smallest product does not use the smallest factor" diff --git a/exercises/practice/palindrome-products/babel.config.js b/exercises/practice/palindrome-products/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/palindrome-products/babel.config.js +++ b/exercises/practice/palindrome-products/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/palindrome-products/eslint.config.mjs b/exercises/practice/palindrome-products/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/palindrome-products/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/palindrome-products/jest.config.js b/exercises/practice/palindrome-products/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/palindrome-products/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/palindrome-products/package.json b/exercises/practice/palindrome-products/package.json index 6c0bdd3389..d30b35ef19 100644 --- a/exercises/practice/palindrome-products/package.json +++ b/exercises/practice/palindrome-products/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/palindrome-products" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/palindrome-products/palindrome-products.js b/exercises/practice/palindrome-products/palindrome-products.js index 9af6b5d15a..a8e298da32 100644 --- a/exercises/practice/palindrome-products/palindrome-products.js +++ b/exercises/practice/palindrome-products/palindrome-products.js @@ -5,6 +5,6 @@ export class Palindromes { static generate() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/palindrome-products/palindrome-products.spec.js b/exercises/practice/palindrome-products/palindrome-products.spec.js index 74a864d8cb..d3b60b9da0 100644 --- a/exercises/practice/palindrome-products/palindrome-products.spec.js +++ b/exercises/practice/palindrome-products/palindrome-products.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { Palindromes } from './palindrome-products'; describe('Palindromes', () => { @@ -79,17 +80,23 @@ describe('Palindromes', () => { expect(sortFactors(smallest.factors)).toEqual(expected.factors); }); - test.skip('largest palindrome from four digit factors', () => { - const palindromes = Palindromes.generate({ - maxFactor: 9999, - minFactor: 1000, - }); - const largest = palindromes.largest; - const expected = { value: 99000099, factors: [[9901, 9999]] }; + // This test doesn't run on our online test runner because it will time-out + // with most implementations. It's up to you to test your solution locally. + test.skip( + 'largest palindrome from four digit factors', + () => { + const palindromes = Palindromes.generate({ + maxFactor: 9999, + minFactor: 1000, + }); + const largest = palindromes.largest; + const expected = { value: 99000099, factors: [[9901, 9999]] }; - expect(largest.value).toEqual(expected.value); - expect(sortFactors(largest.factors)).toEqual(expected.factors); - }); + expect(largest.value).toEqual(expected.value); + expect(sortFactors(largest.factors)).toEqual(expected.factors); + }, + 20 * 1000, + ); xtest('empty result for smallest if no palindrome in range', () => { const palindromes = Palindromes.generate({ @@ -128,6 +135,18 @@ describe('Palindromes', () => { }); }); +xtest('smallest product does not use the smallest factor', () => { + const palindromes = Palindromes.generate({ + maxFactor: 4000, + minFactor: 3215, + }); + const smallest = palindromes.smallest; + const expected = { value: 10988901, factors: [[3297, 3333]] }; + + expect(smallest.value).toEqual(expected.value); + expect(sortFactors(smallest.factors)).toEqual(expected.factors); +}); + function sortFactors(factors) { return factors.map((f) => f.sort()).sort(); } diff --git a/exercises/practice/pangram/.approaches/bitfield/content.md b/exercises/practice/pangram/.approaches/bitfield/content.md new file mode 100644 index 0000000000..37854bde87 --- /dev/null +++ b/exercises/practice/pangram/.approaches/bitfield/content.md @@ -0,0 +1,64 @@ +# Bit field + +```javascript +const A_LCASE = 97; +const A_UCASE = 65; +const ALL_26_BITS_SET = 67108863; + +export function isPangram(input) { + let phrasemask = 0; + [...input].forEach((letter) => { + if (letter >= 'a' && letter <= 'z') + phrasemask |= 1 << (letter.charCodeAt(0) - A_LCASE); + else if (letter >= 'A' && letter <= 'Z') + phrasemask |= 1 << (letter.charCodeAt(0) - A_UCASE); + }); + return phrasemask == ALL_26_BITS_SET; +} +``` + +This solution uses the [ASCII][ascii] value of the letter to set the corresponding bit position. + +Some [const][const]ants are defined for readability in the function. +The ASCII value for `a` is `97`. +The ASCII value for `A` is `65`. +The value for all of the rightmost 26 bits being set is `67108863`. + +- [Spread syntax][spread-syntax] is used to make an [`Array`][array] of the characters in the `input`. +- The `Array` method [`forEach`][foreach] loops through the characters and looks for a character being `a` through `z` or `A` through `Z`. +- If a letter is found, then its ASCII value is taken by the [`charCodeAt`][charcodeat] method. + + +~~~~exercism/note +`charCodeAt` actually returns the UTF-16 code unit for the character, which is an integer between `0` and `65535`. +For the letters `a`-`z` and `A`-`Z`, the UTF-16 number is the same value as the ASCII value. +~~~~ + + +- If the lowercase letter is subtracted by `97`, then `a` will result in `0`, because `97` minus `97` equals `0`. + `z` would result in `25`, because `122` minus `97` equals `25`. + So `a` would have `1` [shifted left][shift-left] 0 places (so not shifted at all) and `z` would have `1` shifted left 25 places. +- If the uppercase letter is subtracted by `A`, then `A` will result in `0`, because `65` minus `65` equals `0`. + `Z` would result in `25`, because `90` minus `65` equals `25`. + So `A` would have `1` [shifted left][shift-left] 0 places (so not shifted at all) and `Z` would have `1` shifted left 25 places. + +In that way, both a lowercase `z` and an uppercase `Z` can share the same position in the bit field. + +So, for an integer, if the values for `a` and `Z` were both set, the bits would look like + +``` + zyxwvutsrqponmlkjihgfedcba +00000010000000000000000000000001 +``` + +We can use the [bitwise OR operator][or] to set the bit. +After the loop completes, the function returns if the `phrasemask` value is the same value as when all `26` bits are set, which is `67108863`. + +[ascii]: https://www.asciitable.com/ +[const]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/const +[spread-syntax]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax +[array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array +[foreach]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach +[charcodeat]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charCodeAt +[shift-left]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Left_shift +[or]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_OR diff --git a/exercises/practice/pangram/.approaches/bitfield/snippet.txt b/exercises/practice/pangram/.approaches/bitfield/snippet.txt new file mode 100644 index 0000000000..6e42f05298 --- /dev/null +++ b/exercises/practice/pangram/.approaches/bitfield/snippet.txt @@ -0,0 +1,7 @@ +[...input].forEach((letter) => { + if (letter >= "a" && letter <= "z") + phrasemask |= 1 << (letter.charCodeAt(0) - A_LCASE); + else if (letter >= "A" && letter <= "Z") + phrasemask |= 1 << (letter.charCodeAt(0) - A_UCASE); +}); +return phrasemask == ALL_26_BITS_SET; diff --git a/exercises/practice/pangram/.approaches/config.json b/exercises/practice/pangram/.approaches/config.json new file mode 100644 index 0000000000..167677512a --- /dev/null +++ b/exercises/practice/pangram/.approaches/config.json @@ -0,0 +1,36 @@ +{ + "introduction": { + "authors": [ + "bobahop" + ] + }, + "approaches": [ + { + "uuid": "2495f79d-9ef9-4f38-abad-f3af8f1f217e", + "slug": "every-includes", + "title": "every with includes", + "blurb": "Use every with includes.", + "authors": [ + "bobahop" + ] + }, + { + "uuid": "5d19ca5a-9eb2-42fc-bf23-ebf66b7e4959", + "slug": "set-size", + "title": "Set with size", + "blurb": "Use Set with size.", + "authors": [ + "bobahop" + ] + }, + { + "uuid": "44eff693-c28f-4928-9f45-7a9c7e79b843", + "slug": "bitfield", + "title": "Bit field", + "blurb": "Use a bit field to keep track of used letters.", + "authors": [ + "bobahop" + ] + } + ] +} diff --git a/exercises/practice/pangram/.approaches/every-includes/content.md b/exercises/practice/pangram/.approaches/every-includes/content.md new file mode 100644 index 0000000000..d0b8443105 --- /dev/null +++ b/exercises/practice/pangram/.approaches/every-includes/content.md @@ -0,0 +1,24 @@ +# `every` with `includes` on lowercased letters + +```javascript +export function isPangram(input) { + const inputLowered = input.toLowerCase(); + return [...'abcdefghijklmnopqrstuvwxyz'].every((c) => + inputLowered.includes(c), + ); +} +``` + +- This begins by lowercasing the input by using the [`String`][string] [`toLowerCase`][tolower] method. +- It uses [spread syntax][spread-syntax] to make an [Array][array] out of a `string` of the alphabet. +- It then checks if all letters in the alphabet are contained in the input, + using the `Array` method [`every`][every] with the `String` method [`includes`][includes]. + +If all letters of the alphabet are in the `input`, then the function returns `true`. + +[string]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String +[tolower]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toLowerCase +[spread-syntax]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax +[array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array +[every]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every +[includes]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes diff --git a/exercises/practice/pangram/.approaches/every-includes/snippet.txt b/exercises/practice/pangram/.approaches/every-includes/snippet.txt new file mode 100644 index 0000000000..a7d32e5688 --- /dev/null +++ b/exercises/practice/pangram/.approaches/every-includes/snippet.txt @@ -0,0 +1,6 @@ +export function isPangram(input) { + const inputLowered = input.toLowerCase(); + return [..."abcdefghijklmnopqrstuvwxyz"].every((c) => + inputLowered.includes(c) + ); +} diff --git a/exercises/practice/pangram/.approaches/introduction.md b/exercises/practice/pangram/.approaches/introduction.md new file mode 100644 index 0000000000..f47bc73631 --- /dev/null +++ b/exercises/practice/pangram/.approaches/introduction.md @@ -0,0 +1,55 @@ +# Introduction + +There are various idomatic approaches to Pangram. +You can use the `every` method with the `includes` method. +Or you can use the `Set` object with the `size` property. + +## General guidance + +The key to solving Pangram is determining if all of the letters in the alphabet are in the `string` being tested. +The occurrence of either the letter `a` or the letter `A` would count as the same letter. + +## Approach: `every` with `includes` + +```javascript +export function isPangram(input) { + const inputLowered = input.toLowerCase(); + return [...'abcdefghijklmnopqrstuvwxyz'].every((c) => + inputLowered.includes(c), + ); +} +``` + +For more information, check the [`every` with `includes` approach][approach-every-includes]. + +## Approach: `Set` with `size` + +```javascript +export function isPangram(input) { + return new Set(input.toLowerCase().match(/[a-z]/g)).size === 26; +} +``` + +For more information, check the [`Set` with `size`approach][approach-set-size] + +## Other approaches + +Besides the aforementioned, idiomatic approaches, you could also approach the exercise as follows: + +### Other approach: Bit field + +Another approach can use a bit field to keep track of used letters. +For more information, check the [Bit field approach][approach-bitfield]. + +## Which approach to use? + +Testing `"the _1_ quick brown fox jumps over the _2_ lazy dogs"` on [JSBench.me][jsbench-me]: + +- The `every` with `includes` approach benched fastest. +- Although the bit field approach is often faster in other languages, it was about 45% slower. +- `Set` with `size` was about 75% slower. + +[approach-every-includes]: https://exercism.org/tracks/javascript/exercises/pangram/approaches/every-includes +[approach-set-size]: https://exercism.org/tracks/javascript/exercises/pangram/approaches/set-size +[approach-bitfield]: https://exercism.org/tracks/javascript/exercises/pangram/approaches/bitfield +[jsbench-me]: https://jsbench.me/ diff --git a/exercises/practice/pangram/.approaches/set-size/content.md b/exercises/practice/pangram/.approaches/set-size/content.md new file mode 100644 index 0000000000..b7060c325a --- /dev/null +++ b/exercises/practice/pangram/.approaches/set-size/content.md @@ -0,0 +1,32 @@ +# `Set` with `size` + +```javascript +export function isPangram(input) { + return new Set(input.toLowerCase().match(/[a-z]/g)).size === 26; +} +``` + +This approach creates a [`Set`][set] of the unique letters in the `input` and tests its [size][size] to determine the result. + +- It first creates a new `Set` made from the [lowercased][tolowercase] characters of the `input` + that only [match][match] the [regular expression][regex] pattern for letters from `a`-`z`. +- The function returns if the `size` of the `Set` is `26`. + If the number of unique letters in the `Set` is equal to the `26` letters in the alphabet, then the function will return `true`. + +## Shortening + +When the body of a function is a single expression, the function can be implemented as an [arrow function][arrow-function], like so + +```javascript +export const isPangram = (input) => + new Set(input.toLowerCase().match(/[a-z]/g)).size === 26; +``` + +Notice that `return` and the curly braces are not needed. + +[set]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set +[size]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/size +[tolowercase]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toLowerCase +[match]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match +[regex]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions +[arrow-function]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions diff --git a/exercises/practice/pangram/.approaches/set-size/snippet.txt b/exercises/practice/pangram/.approaches/set-size/snippet.txt new file mode 100644 index 0000000000..11a9048d3e --- /dev/null +++ b/exercises/practice/pangram/.approaches/set-size/snippet.txt @@ -0,0 +1,3 @@ +export function isPangram(input) { + return new Set(input.toLowerCase().match(/[a-z]/g)).size === 26; +} diff --git a/exercises/practice/pangram/.docs/instructions.md b/exercises/practice/pangram/.docs/instructions.md index a4a152dcb9..817c872d90 100644 --- a/exercises/practice/pangram/.docs/instructions.md +++ b/exercises/practice/pangram/.docs/instructions.md @@ -1,10 +1,8 @@ # Instructions -Determine if a sentence is a pangram. A pangram (Greek: παν γράμμα, pan gramma, -"every letter") is a sentence using every letter of the alphabet at least once. -The best known English pangram is: +Your task is to figure out if a sentence is a pangram. -> The quick brown fox jumps over the lazy dog. +A pangram is a sentence using every letter of the alphabet at least once. +It is case insensitive, so it doesn't matter if a letter is lower-case (e.g. `k`) or upper-case (e.g. `K`). -The alphabet used consists of ASCII letters `a` to `z`, inclusive, and is case -insensitive. Input will not contain non-ASCII symbols. +For this exercise, a sentence is a pangram if it contains each of the 26 letters in the English alphabet. diff --git a/exercises/practice/pangram/.docs/introduction.md b/exercises/practice/pangram/.docs/introduction.md new file mode 100644 index 0000000000..32b6f1fc31 --- /dev/null +++ b/exercises/practice/pangram/.docs/introduction.md @@ -0,0 +1,16 @@ +# Introduction + +You work for a company that sells fonts through their website. +They'd like to show a different sentence each time someone views a font on their website. +To give a comprehensive sense of the font, the random sentences should use **all** the letters in the English alphabet. + +They're running a competition to get suggestions for sentences that they can use. +You're in charge of checking the submissions to see if they are valid. + +~~~~exercism/note +Pangram comes from Greek, παν γράμμα, pan gramma, which means "every letter". + +The best known English pangram is: + +> The quick brown fox jumps over the lazy dog. +~~~~ diff --git a/exercises/practice/pangram/.eslintrc b/exercises/practice/pangram/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/pangram/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/pangram/.gitignore b/exercises/practice/pangram/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/pangram/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/pangram/.meta/config.json b/exercises/practice/pangram/.meta/config.json index b1c216e092..13c4d2e850 100644 --- a/exercises/practice/pangram/.meta/config.json +++ b/exercises/practice/pangram/.meta/config.json @@ -1,9 +1,11 @@ { - "blurb": "Determine if a sentence is a pangram.", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "amscotti", "ankorGH", + "jagdish-15", "PakkuDon", "rchavarria", "ryanplusplus", @@ -12,10 +14,23 @@ "xarxziux" ], "files": { - "solution": ["pangram.js"], - "test": ["pangram.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "pangram.js" + ], + "test": [ + "pangram.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Determine if a sentence is a pangram.", "source": "Wikipedia", - "source_url": "https://en.wikipedia.org/wiki/Pangram" + "source_url": "https://en.wikipedia.org/wiki/Pangram", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/pangram/.meta/tests.toml b/exercises/practice/pangram/.meta/tests.toml index 8075c5ba36..10b5a335a4 100644 --- a/exercises/practice/pangram/.meta/tests.toml +++ b/exercises/practice/pangram/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [64f61791-508e-4f5c-83ab-05de042b0149] description = "empty sentence" @@ -31,3 +38,8 @@ description = "mixed case and punctuation" [2577bf54-83c8-402d-a64b-a2c0f7bb213a] description = "case insensitive" +include = false + +[7138e389-83e4-4c6e-8413-1e40a0076951] +description = "a-m and A-M are 26 different characters but not a pangram" +reimplements = "2577bf54-83c8-402d-a64b-a2c0f7bb213a" diff --git a/exercises/practice/pangram/babel.config.js b/exercises/practice/pangram/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/pangram/babel.config.js +++ b/exercises/practice/pangram/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/pangram/eslint.config.mjs b/exercises/practice/pangram/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/pangram/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/pangram/jest.config.js b/exercises/practice/pangram/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/pangram/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/pangram/package.json b/exercises/practice/pangram/package.json index f79c076a80..e70f565b28 100644 --- a/exercises/practice/pangram/package.json +++ b/exercises/practice/pangram/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/pangram" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/pangram/pangram.js b/exercises/practice/pangram/pangram.js index 47769cc4d5..627cdd0996 100644 --- a/exercises/practice/pangram/pangram.js +++ b/exercises/practice/pangram/pangram.js @@ -4,5 +4,5 @@ // export const isPangram = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/pangram/pangram.spec.js b/exercises/practice/pangram/pangram.spec.js index 302c8875ad..9f3b7c5c9f 100644 --- a/exercises/practice/pangram/pangram.spec.js +++ b/exercises/practice/pangram/pangram.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { isPangram } from './pangram'; describe('Pangram()', () => { @@ -15,7 +16,7 @@ describe('Pangram()', () => { xtest("missing the letter 'x'", () => { expect( - isPangram('a quick movement of the enemy will jeopardize five gunboats') + isPangram('a quick movement of the enemy will jeopardize five gunboats'), ).toBe(false); }); @@ -29,13 +30,13 @@ describe('Pangram()', () => { xtest('with numbers', () => { expect(isPangram('the 1 quick brown fox jumps over the 2 lazy dogs')).toBe( - true + true, ); }); xtest('missing letters replaced by numbers', () => { expect(isPangram('7h3 qu1ck brown fox jumps ov3r 7h3 lazy dog')).toBe( - false + false, ); }); @@ -43,9 +44,7 @@ describe('Pangram()', () => { expect(isPangram('"Five quacking Zephyrs jolt my wax bed."')).toBe(true); }); - xtest('case insensitive', () => { - expect(isPangram('the quick brown fox jumps over with lazy FX')).toBe( - false - ); + xtest('a-m and A-M are 26 different characters but not a pangram', () => { + expect(isPangram('abcdefghijklm ABCDEFGHIJKLM')).toBe(false); }); }); diff --git a/exercises/practice/parallel-letter-frequency/.docs/instructions.append.md b/exercises/practice/parallel-letter-frequency/.docs/instructions.append.md new file mode 100644 index 0000000000..f17f872d0d --- /dev/null +++ b/exercises/practice/parallel-letter-frequency/.docs/instructions.append.md @@ -0,0 +1,126 @@ +# Instructions append + +JavaScript is single-threaded by nature, so it lacks many of the language features that other languages have in order to handle parallel code execution. +In fact, the only way to achieve "real" parallel code execution is through `Worker threads` (also reffered to as `Web Workers`). + +Almost always, code that appears to execute in parallel, +such as `async functions` or `Promises`, will actually execute concurrently instead. +This is often better, since modern JavaScript is optimized for such use, +and you will often see code that "emulates" (or "cheats") parallel execution by the use of `Promise.all()` and other concurrent execution methods. + + +~~~@exercism/caution +To pass the tests for this exercise, your solution needs to execute _concurrently_ (or in parallel), +meaning that synchronous solutions (e.g. a simple `for` loop) will not pass. +~~~ + +## Concurency vs. Parallelism + +Here's a quick definition for each that illustrates the diferences between the two: + +- Concurrency is when two or more tasks can start, run and complete in overlapping time periods, being executed by the same processing unit. +- Parallelism is when two or more tasks can start and run at the same time, being executed independently of eachother by separate processing units. + +For the sake of completeness, here's a definition for synchronous execution: + +- Synchronous execution is when a task has to wait for another running task to complete, before it can run. + +## Parallelism in JavaScript + +Even though JavaScript by default is single-threaded, there is a way to execute code in parallel fashion. + +If you are running JavaScript in the browser (e.g. in a web app), +then the way to achieve parallelism is through the [Web Worker API][mdn-demo]. +As described by MDN: + +> Web Workers makes it possible to run a script operation in a background thread separate from the main execution thread of an application. + +On the other hand, if your JavaScript is running in Node.js, which is Exercism's target runtime, +this same concept is known as [Worker threads][node]. + + +~~~@exercism/caution +Be aware that the implementation of the worker API differs largely between browsers and other JavaScript environments. + +Make sure to read the documentation for your specific runtime! +~~~ + +Here's a simple demo of the `Web Worker API` (taken from [Medium][medium-demo]) + +```js +// main.js +const myWorker = new Worker('worker.js'); + +myWorker.postMessage(5); + +myWorker.onmessage = function (event) { + console.log('Received result from worker:', event.data); +}; +``` + +```js +// worker.js +onmessage = function (event) { + console.log('Received number from main thread:', event.data); + + // Perform computation + const result = event.data * 2; + + // Send result back to the main thread + postMessage(result); +}; +``` + +And here is a demo of the `Worker threads API` (taken from the [docs][node]) + +```js +const { + Worker, + isMainThread, + parentPort, + workerData, +} = require('node:worker_threads'); + +if (isMainThread) { + module.exports = function parseJSAsync(script) { + return new Promise((resolve, reject) => { + const worker = new Worker(__filename, { + workerData: script, + }); + worker.on('message', resolve); + worker.on('error', reject); + worker.on('exit', (code) => { + if (code !== 0) + reject(new Error(`Worker stopped with exit code ${code}`)); + }); + }); + }; +} else { + const { parse } = require('some-js-parsing-library'); + const script = workerData; + parentPort.postMessage(parse(script)); +} +``` + + +~~~@exercism/caution +Currently it is not possible to implement parallelism using the online editor. + +Please implement `Worker threads` using Node.js locally and submit your solution via CLI! +~~~ + +As a stretch goal, consider if your implementation can be adapted to make use of `Worker threads`. + +--- + +## Further reading + +- [Node.js docs](https://nodejs.org/api/worker_threads.html#worker-threads) +- [Another MDN demo](https://mdn.github.io/dom-examples/web-workers/simple-web-worker/) +- [MDN - Web Workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) +- [Article about multi-threading in JS](https://medium.com/techtrument/multithreading-javascript-46156179cf9a) +- [Web Worker primer](https://medium.com/@ns-tech-learn/what-is-a-web-worker-how-to-use-it-and-example-2273de521f04) + +[mdn-demo]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API +[medium-demo]: https://medium.com/@ns-tech-learn/what-is-a-web-worker-how-to-use-it-and-example-2273de521f04 +[node]: https://nodejs.org/api/worker_threads.html#worker-threads diff --git a/exercises/practice/parallel-letter-frequency/.docs/instructions.md b/exercises/practice/parallel-letter-frequency/.docs/instructions.md new file mode 100644 index 0000000000..6147b90af7 --- /dev/null +++ b/exercises/practice/parallel-letter-frequency/.docs/instructions.md @@ -0,0 +1,7 @@ +# Instructions + +Count the frequency of letters in texts using parallel computation. + +Parallelism is about doing things in parallel that can also be done sequentially. +A common example is counting the frequency of letters. +Employ parallelism to calculate the total frequency of each letter in a list of texts. diff --git a/exercises/practice/parallel-letter-frequency/.gitignore b/exercises/practice/parallel-letter-frequency/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/parallel-letter-frequency/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/parallel-letter-frequency/.meta/config.json b/exercises/practice/parallel-letter-frequency/.meta/config.json new file mode 100644 index 0000000000..b2a5c28652 --- /dev/null +++ b/exercises/practice/parallel-letter-frequency/.meta/config.json @@ -0,0 +1,21 @@ +{ + "authors": [ + "Cool-Katt" + ], + "contributors": [ + "mk-mxp", + "themetar" + ], + "files": { + "solution": [ + "parallel-letter-frequency.js" + ], + "test": [ + "parallel-letter-frequency.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] + }, + "blurb": "Count the frequency of letters in texts using parallel computation." +} diff --git a/exercises/practice/parallel-letter-frequency/.meta/proof.ci.js b/exercises/practice/parallel-letter-frequency/.meta/proof.ci.js new file mode 100644 index 0000000000..0cf92cd8eb --- /dev/null +++ b/exercises/practice/parallel-letter-frequency/.meta/proof.ci.js @@ -0,0 +1,48 @@ +const { + Worker, + isMainThread, + workerData, + parentPort, +} = require('node:worker_threads'); + +if (isMainThread) { + module.exports.parallelLetterFrequency = async function (texts) { + let formatedTexts = texts + .map( + (x) => + x + .toLowerCase() + .match(/\p{Letter}+/gu) + ?.join('') ?? [], + ) + .flat(); + + return Promise.all(formatedTexts.map((t) => processSingleText(t))).then( + (results) => + results.reduce((acc, cur) => { + for (const letter in cur) { + acc[letter] = (acc[letter] || 0) + cur[letter]; + } + return acc; + }, {}), + ); + }; + + const processSingleText = (text) => { + return new Promise((resolve, reject) => { + const worker = new Worker(__filename, { + workerData: text, + }); + worker.on('message', resolve); + worker.on('error', reject); + }); + }; +} else { + const countInWorker = (data) => + [...data].reduce((acc, cur) => { + acc[cur] = (acc[cur] || 0) + 1; + return acc; + }, {}); + + parentPort.postMessage(countInWorker(workerData)); +} diff --git a/exercises/practice/parallel-letter-frequency/.meta/tests.toml b/exercises/practice/parallel-letter-frequency/.meta/tests.toml new file mode 100644 index 0000000000..0c974f7fd7 --- /dev/null +++ b/exercises/practice/parallel-letter-frequency/.meta/tests.toml @@ -0,0 +1,49 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[c054d642-c1fa-4234-8007-9339f2337886] +description = "no texts" + +[818031be-49dc-4675-b2f9-c4047f638a2a] +description = "one text with one letter" + +[c0b81d1b-940d-4cea-9f49-8445c69c17ae] +description = "one text with multiple letters" + +[708ff1e0-f14a-43fd-adb5-e76750dcf108] +description = "two texts with one letter" + +[1b5c28bb-4619-4c9d-8db9-a4bb9c3bdca0] +description = "two texts with multiple letters" + +[6366e2b8-b84c-4334-a047-03a00a656d63] +description = "ignore letter casing" + +[92ebcbb0-9181-4421-a784-f6f5aa79f75b] +description = "ignore whitespace" + +[bc5f4203-00ce-4acc-a5fa-f7b865376fd9] +description = "ignore punctuation" + +[68032b8b-346b-4389-a380-e397618f6831] +description = "ignore numbers" + +[aa9f97ac-3961-4af1-88e7-6efed1bfddfd] +description = "Unicode letters" + +[7b1da046-701b-41fc-813e-dcfb5ee51813] +description = "combination of lower- and uppercase letters, punctuation and white space" + +[4727f020-df62-4dcf-99b2-a6e58319cb4f] +description = "large texts" + +[adf8e57b-8e54-4483-b6b8-8b32c115884c] +description = "many small texts" diff --git a/exercises/practice/parallel-letter-frequency/.npmrc b/exercises/practice/parallel-letter-frequency/.npmrc new file mode 100644 index 0000000000..d26df800bb --- /dev/null +++ b/exercises/practice/parallel-letter-frequency/.npmrc @@ -0,0 +1 @@ +audit=false diff --git a/exercises/practice/parallel-letter-frequency/LICENSE b/exercises/practice/parallel-letter-frequency/LICENSE new file mode 100644 index 0000000000..90e73be03b --- /dev/null +++ b/exercises/practice/parallel-letter-frequency/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Exercism + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/exercises/practice/parallel-letter-frequency/babel.config.js b/exercises/practice/parallel-letter-frequency/babel.config.js new file mode 100644 index 0000000000..a638497df1 --- /dev/null +++ b/exercises/practice/parallel-letter-frequency/babel.config.js @@ -0,0 +1,4 @@ +module.exports = { + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], +}; diff --git a/exercises/practice/parallel-letter-frequency/eslint.config.mjs b/exercises/practice/parallel-letter-frequency/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/parallel-letter-frequency/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/parallel-letter-frequency/jest.config.js b/exercises/practice/parallel-letter-frequency/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/parallel-letter-frequency/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/parallel-letter-frequency/package.json b/exercises/practice/parallel-letter-frequency/package.json new file mode 100644 index 0000000000..b9929fe2bd --- /dev/null +++ b/exercises/practice/parallel-letter-frequency/package.json @@ -0,0 +1,39 @@ +{ + "name": "@exercism/javascript-parallel-letter-frequency", + "description": "Exercism practice exercise on parallel-letter-frequency", + "author": "Katrina Owen", + "contributors": [ + "Cool-Katt (https://github.com/Cool-Katt)", + "Derk-Jan Karrenbeld (https://derk-jan.com)", + "Tejas Bubane (https://tejasbubane.github.io/)" + ], + "private": true, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/exercism/javascript", + "directory": "exercises/practice/parallel-letter-frequency" + }, + "devDependencies": { + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" + }, + "dependencies": {}, + "scripts": { + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" +} diff --git a/exercises/practice/parallel-letter-frequency/parallel-letter-frequency.js b/exercises/practice/parallel-letter-frequency/parallel-letter-frequency.js new file mode 100644 index 0000000000..17ddf574da --- /dev/null +++ b/exercises/practice/parallel-letter-frequency/parallel-letter-frequency.js @@ -0,0 +1,8 @@ +// +// This is only a SKELETON file for the 'Parallel Letter Frequency' exercise. It's been provided as a +// convenience to get you started writing code faster. +// + +export const parallelLetterFrequency = async (texts) => { + throw new Error('Remove this line and implement the function'); +}; diff --git a/exercises/practice/parallel-letter-frequency/parallel-letter-frequency.spec.js b/exercises/practice/parallel-letter-frequency/parallel-letter-frequency.spec.js new file mode 100644 index 0000000000..c032236994 --- /dev/null +++ b/exercises/practice/parallel-letter-frequency/parallel-letter-frequency.spec.js @@ -0,0 +1,174 @@ +import { describe, expect, test, xtest } from '@jest/globals'; +import { parallelLetterFrequency } from './parallel-letter-frequency'; + +describe('ParallelLetterFrequency', () => { + test('no texts', async () => { + const expected = {}; + const actual = parallelLetterFrequency([]); + await expect(actual).resolves.toEqual(expected); + }); + + xtest('one text with one letter', async () => { + const texts = ['a']; + const expected = { + a: 1, + }; + const actual = parallelLetterFrequency(texts); + await expect(actual).resolves.toEqual(expected); + }); + + xtest('one text with multiple letters', async () => { + const texts = ['bbcccd']; + const expected = { + b: 2, + c: 3, + d: 1, + }; + const actual = parallelLetterFrequency(texts); + await expect(actual).resolves.toEqual(expected); + }); + + xtest('two texts with one letter', async () => { + const texts = ['e', 'f']; + const expected = { + e: 1, + f: 1, + }; + const actual = parallelLetterFrequency(texts); + await expect(actual).resolves.toEqual(expected); + }); + + xtest('two texts with multiple letters', async () => { + const texts = ['ggh', 'hhi']; + const expected = { + g: 2, + h: 3, + i: 1, + }; + const actual = parallelLetterFrequency(texts); + await expect(actual).resolves.toEqual(expected); + }); + + xtest('ignore letter casing', async () => { + const texts = ['m', 'M']; + const expected = { + m: 2, + }; + const actual = parallelLetterFrequency(texts); + await expect(actual).resolves.toEqual(expected); + }); + + xtest('ignore whitespace', async () => { + const texts = [' ', '\t', '\r\n']; + const expected = {}; + const actual = parallelLetterFrequency(texts); + await expect(actual).resolves.toEqual(expected); + }); + + xtest('ignore punctuation', async () => { + const texts = ['!', '?', ';', ',', '.']; + const expected = {}; + const actual = parallelLetterFrequency(texts); + await expect(actual).resolves.toEqual(expected); + }); + + xtest('ignore numbers', async () => { + const texts = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']; + const expected = {}; + const actual = parallelLetterFrequency(texts); + await expect(actual).resolves.toEqual(expected); + }); + + xtest('Unicode letters', async () => { + const texts = ['本', 'φ', 'ほ', 'ø']; + const expected = { + 本: 1, + φ: 1, + ほ: 1, + ø: 1, + }; + const actual = parallelLetterFrequency(texts); + await expect(actual).resolves.toEqual(expected); + }); + + xtest('combination of lower- and uppercase letters, punctuation and white space', async () => { + const texts = [ + 'There, peeping among the cloud-wrack above a dark tower high up in the mountains, Sam saw a white star twinkle for a while. The beauty of it smote his heart, as he looked up out of the forsaken land, and hope returned to him. For like a shaft, clear and cold, the thought pierced him that in the end, the shadow was only a small and passing thing: there was light and high beauty forever beyond its reach.', + ]; + const expected = { + a: 32, + b: 4, + c: 6, + d: 14, + e: 37, + f: 7, + g: 8, + h: 29, + i: 19, + k: 6, + l: 12, + m: 7, + n: 19, + o: 22, + p: 7, + r: 17, + s: 16, + t: 30, + u: 9, + v: 2, + w: 9, + y: 4, + }; + const actual = parallelLetterFrequency(texts); + await expect(actual).resolves.toEqual(expected); + }); + + xtest('large texts', async () => { + const texts = [ + "I am a sick man.... I am a spiteful man. I am an unattractive man.\nI believe my liver is diseased. However, I know nothing at all about my disease, and do not\nknow for certain what ails me. I don't consult a doctor for it,\nand never have, though I have a respect for medicine and doctors.\nBesides, I am extremely superstitious, sufficiently so to respect medicine,\nanyway (I am well-educated enough not to be superstitious, but I am superstitious).\nNo, I refuse to consult a doctor from spite.\nThat you probably will not understand. Well, I understand it, though.\nOf course, I can't explain who it is precisely that I am mortifying in this case by my spite:\nI am perfectly well aware that I cannot \"pay out\" the doctors by not consulting them;\nI know better than anyone that by all this I am only injuring myself and no one else.\nBut still, if I don't consult a doctor it is from spite.\nMy liver is bad, well - let it get worse!\nI have been going on like that for a long time - twenty years. Now I am forty.\nI used to be in the government service, but am no longer.\nI was a spiteful official. I was rude and took pleasure in being so.\nI did not take bribes, you see, so I was bound to find a recompense in that, at least.\n(A poor jest, but I will not scratch it out. I wrote it thinking it would sound very witty;\nbut now that I have seen myself that I only wanted to show off in a despicable way -\nI will not scratch it out on purpose!) When petitioners used to come for\ninformation to the table at which I sat, I used to grind my teeth at them,\nand felt intense enjoyment when I succeeded in making anybody unhappy.\nI almost did succeed. For the most part they were all timid people - of course,\nthey were petitioners. But of the uppish ones there was one officer in particular\nI could not endure. He simply would not be humble, and clanked his sword in a disgusting way.\nI carried on a feud with him for eighteen months over that sword. At last I got the better of him.\nHe left off clanking it. That happened in my youth, though. But do you know,\ngentlemen, what was the chief point about my spite? Why, the whole point,\nthe real sting of it lay in the fact that continually, even in the moment of the acutest spleen,\nI was inwardly conscious with shame that I was not only not a spiteful but not even an embittered man,\nthat I was simply scaring sparrows at random and amusing myself by it.\nI might foam at the mouth, but bring me a doll to play with, give me a cup of tea with sugar in it,\nand maybe I should be appeased. I might even be genuinely touched,\nthough probably I should grind my teeth at myself afterwards and lie awake at night with shame for\nmonths after. That was my way. I was lying when I said just now that I was a spiteful official.\nI was lying from spite. I was simply amusing myself with the petitioners and with the officer,\nand in reality I never could become spiteful. I was conscious every moment in myself of many,\nvery many elements absolutely opposite to that. I felt them positively swarming in me,\nthese opposite elements. I knew that they had been swarming in me all my life and craving some outlet from me,\nbut I would not let them, would not let them, purposely would not let them come out.\nThey tormented me till I was ashamed: they drove me to convulsions and - sickened me, at last,\nhow they sickened me!", + 'Gentlemen, I am joking, and I know myself that my jokes are not brilliant\n,but you know one can take everything as a joke. I am, perhaps, jesting against the grain.\nGentlemen, I am tormented by questions; answer them for me. You, for instance, want to cure men of their\nold habits and reform their will in accordance with science and good sense.\nBut how do you know, not only that it is possible, but also that it is\ndesirable to reform man in that way? And what leads you to the conclusion that man\'s\ninclinations need reforming? In short, how do you know that such a reformation will be a benefit to man?\nAnd to go to the root of the matter, why are you so positively convinced that not to act against\nhis real normal interests guaranteed by the conclusions of reason and arithmetic is certainly always\nadvantageous for man and must always be a law for mankind? So far, you know,\nthis is only your supposition. It may be the law of logic, but not the law of humanity.\nYou think, gentlemen, perhaps that I am mad? Allow me to defend myself. I agree that man\nis pre-eminently a creative animal, predestined to strive consciously for an object and to engage in engineering -\nthat is, incessantly and eternally to make new roads, wherever\nthey may lead. But the reason why he wants sometimes to go off at a tangent may just be that he is\npredestined to make the road, and perhaps, too, that however stupid the "direct"\npractical man may be, the thought sometimes will occur to him that the road almost always does lead\nsomewhere, and that the destination it leads to is less important than the process\nof making it, and that the chief thing is to save the well-conducted child from despising engineering,\nand so giving way to the fatal idleness, which, as we all know,\nis the mother of all the vices. Man likes to make roads and to create, that is a fact beyond dispute.\nBut why has he such a passionate love for destruction and chaos also?\nTell me that! But on that point I want to say a couple of words myself. May it not be that he loves\nchaos and destruction (there can be no disputing that he does sometimes love it)\nbecause he is instinctively afraid of attaining his object and completing the edifice he is constructing?\nWho knows, perhaps he only loves that edifice from a distance, and is by no means\nin love with it at close quarters; perhaps he only loves building it and does not want to live in it,\nbut will leave it, when completed, for the use of les animaux domestiques -\nsuch as the ants, the sheep, and so on. Now the ants have quite a different taste.\nThey have a marvellous edifice of that pattern which endures for ever - the ant-heap.\nWith the ant-heap the respectable race of ants began and with the ant-heap they will probably end,\nwhich does the greatest credit to their perseverance and good sense. But man is a frivolous and\nincongruous creature, and perhaps, like a chess player, loves the process of the game, not the end of it.\nAnd who knows (there is no saying with certainty), perhaps the only goal on earth\nto which mankind is striving lies in this incessant process of attaining, in other words,\nin life itself, and not in the thing to be attained, which must always be expressed as a formula,\nas positive as twice two makes four, and such positiveness is not life, gentlemen,\nbut is the beginning of death.', + "But these are all golden dreams. Oh, tell me, who was it first announced,\nwho was it first proclaimed, that man only does nasty things because he does not know his own interests;\nand that if he were enlightened, if his eyes were opened to his real normal interests,\nman would at once cease to do nasty things, would at once become good and noble because,\nbeing enlightened and understanding his real advantage, he would see his own advantage in the\ngood and nothing else, and we all know that not one man can, consciously, act against his own interests,\nconsequently, so to say, through necessity, he would begin doing good? Oh, the babe! Oh, the pure,\ninnocent child! Why, in the first place, when in all these thousands of years has there been a time\nwhen man has acted only from his own interest? What is to be done with the millions of facts that bear\nwitness that men, consciously, that is fully understanding their real interests, have left them in the\nbackground and have rushed headlong on another path, to meet peril and danger,\ncompelled to this course by nobody and by nothing, but, as it were, simply disliking the beaten track,\nand have obstinately, wilfully, struck out another difficult, absurd way, seeking it almost in the darkness.\nSo, I suppose, this obstinacy and perversity were pleasanter to them than any advantage....\nAdvantage! What is advantage? And will you take it upon yourself to define with perfect accuracy in what the\nadvantage of man consists? And what if it so happens that a man's advantage, sometimes, not only may,\nbut even must, consist in his desiring in certain cases what is harmful to himself and not advantageous.\nAnd if so, if there can be such a case, the whole principle falls into dust. What do you think -\nare there such cases? You laugh; laugh away, gentlemen, but only answer me: have man's advantages been\nreckoned up with perfect certainty? Are there not some which not only have not been included but cannot\npossibly be included under any classification? You see, you gentlemen have, to the best of my knowledge,\ntaken your whole register of human advantages from the averages of statistical figures and\npolitico-economical formulas. Your advantages are prosperity, wealth, freedom, peace - and so on, and so on.\nSo that the man who should, for instance, go openly and knowingly in opposition to all that list would to your thinking,\nand indeed mine, too, of course, be an obscurantist or an absolute madman: would not he? But, you know, this is\nwhat is surprising: why does it so happen that all these statisticians, sages and lovers of humanity,\nwhen they reckon up human advantages invariably leave out one? They don't even take it into their reckoning\nin the form in which it should be taken, and the whole reckoning depends upon that. It would be no greater matter,\nthey would simply have to take it, this advantage, and add it to the list. But the trouble is, that this strange\nadvantage does not fall under any classification and is not in place in any list. I have a friend for instance ...\nEch! gentlemen, but of course he is your friend, too; and indeed there is no one, no one to whom he is not a friend!", + "Yes, but here I come to a stop! Gentlemen, you must excuse me for being over-philosophical;\nit's the result of forty years underground! Allow me to indulge my fancy. You see, gentlemen, reason is an excellent thing,\nthere's no disputing that, but reason is nothing but reason and satisfies only the rational side of man's nature,\nwhile will is a manifestation of the whole life, that is, of the whole human life including reason and all the impulses.\nAnd although our life, in this manifestation of it, is often worthless, yet it is life and not simply extracting square roots.\nHere I, for instance, quite naturally want to live, in order to satisfy all my capacities for life, and not simply my capacity\nfor reasoning, that is, not simply one twentieth of my capacity for life. What does reason know? Reason only knows what it has\nsucceeded in learning (some things, perhaps, it will never learn; this is a poor comfort, but why not say so frankly?)\nand human nature acts as a whole, with everything that is in it, consciously or unconsciously, and, even it if goes wrong, it lives.\nI suspect, gentlemen, that you are looking at me with compassion; you tell me again that an enlightened and developed man,\nsuch, in short, as the future man will be, cannot consciously desire anything disadvantageous to himself, that that can be proved mathematically.\nI thoroughly agree, it can - by mathematics. But I repeat for the hundredth time, there is one case, one only, when man may consciously, purposely,\ndesire what is injurious to himself, what is stupid, very stupid - simply in order to have the right to desire for himself even what is very stupid\nand not to be bound by an obligation to desire only what is sensible. Of course, this very stupid thing, this caprice of ours, may be in reality,\ngentlemen, more advantageous for us than anything else on earth, especially in certain cases. And in particular it may be more advantageous than\nany advantage even when it does us obvious harm, and contradicts the soundest conclusions of our reason concerning our advantage -\nfor in any circumstances it preserves for us what is most precious and most important - that is, our personality, our individuality.\nSome, you see, maintain that this really is the most precious thing for mankind; choice can, of course, if it chooses, be in agreement\nwith reason; and especially if this be not abused but kept within bounds. It is profitable and some- times even praiseworthy.\nBut very often, and even most often, choice is utterly and stubbornly opposed to reason ... and ... and ... do you know that that,\ntoo, is profitable, sometimes even praiseworthy? Gentlemen, let us suppose that man is not stupid. (Indeed one cannot refuse to suppose that,\nif only from the one consideration, that, if man is stupid, then who is wise?) But if he is not stupid, he is monstrously ungrateful!\nPhenomenally ungrateful. In fact, I believe that the best definition of man is the ungrateful biped. But that is not all, that is not his worst defect;\nhis worst defect is his perpetual moral obliquity, perpetual - from the days of the Flood to the Schleswig-Holstein period.", + ]; + const expected = { + a: 845, + b: 155, + c: 278, + d: 359, + e: 1143, + f: 222, + g: 187, + h: 507, + i: 791, + j: 12, + k: 67, + l: 423, + m: 288, + n: 833, + o: 791, + p: 197, + q: 8, + r: 432, + s: 700, + t: 1043, + u: 325, + v: 111, + w: 223, + x: 7, + y: 251, + }; + const actual = parallelLetterFrequency(texts); + await expect(actual).resolves.toEqual(expected); + }); + + xtest('many small texts', async () => { + const texts = Array(50).fill('abbccc'); + const expected = { + a: 50, + b: 100, + c: 150, + }; + const actual = parallelLetterFrequency(texts); + await expect(actual).resolves.toEqual(expected); + }); +}); diff --git a/exercises/practice/pascals-triangle/.docs/instructions.md b/exercises/practice/pascals-triangle/.docs/instructions.md index 7109334fbd..0f58f00696 100644 --- a/exercises/practice/pascals-triangle/.docs/instructions.md +++ b/exercises/practice/pascals-triangle/.docs/instructions.md @@ -1,9 +1,20 @@ # Instructions -Compute Pascal's triangle up to a given number of rows. +Your task is to output the first N rows of Pascal's triangle. -In Pascal's Triangle each number is computed by adding the numbers to -the right and left of the current position in the previous row. +[Pascal's triangle][wikipedia] is a triangular array of positive integers. + +In Pascal's triangle, the number of values in a row is equal to its row number (which starts at one). +Therefore, the first row has one value, the second row has two values, and so on. + +The first (topmost) row has a single value: `1`. +Subsequent rows' values are computed by adding the numbers directly to the right and left of the current position in the previous row. + +If the previous row does _not_ have a value to the left or right of the current position (which only happens for the leftmost and rightmost positions), treat that position's value as zero (effectively "ignoring" it in the summation). + +## Example + +Let's look at the first 5 rows of Pascal's Triangle: ```text 1 @@ -11,5 +22,14 @@ the right and left of the current position in the previous row. 1 2 1 1 3 3 1 1 4 6 4 1 -# ... etc ``` + +The topmost row has one value, which is `1`. + +The leftmost and rightmost values have only one preceding position to consider, which is the position to its right respectively to its left. +With the topmost value being `1`, it follows from this that all the leftmost and rightmost values are also `1`. + +The other values all have two positions to consider. +For example, the fifth row's (`1 4 6 4 1`) middle value is `6`, as the values to its left and right in the preceding row are `3` and `3`: + +[wikipedia]: https://en.wikipedia.org/wiki/Pascal%27s_triangle diff --git a/exercises/practice/pascals-triangle/.docs/introduction.md b/exercises/practice/pascals-triangle/.docs/introduction.md new file mode 100644 index 0000000000..eab454e5a6 --- /dev/null +++ b/exercises/practice/pascals-triangle/.docs/introduction.md @@ -0,0 +1,22 @@ +# Introduction + +With the weather being great, you're not looking forward to spending an hour in a classroom. +Annoyed, you enter the class room, where you notice a strangely satisfying triangle shape on the blackboard. +Whilst waiting for your math teacher to arrive, you can't help but notice some patterns in the triangle: the outer values are all ones, each subsequent row has one more value than its previous row and the triangle is symmetrical. +Weird! + +Not long after you sit down, your teacher enters the room and explains that this triangle is the famous [Pascal's triangle][wikipedia]. + +Over the next hour, your teacher reveals some amazing things hidden in this triangle: + +- It can be used to compute how many ways you can pick K elements from N values. +- It contains the Fibonacci sequence. +- If you color odd and even numbers differently, you get a beautiful pattern called the [Sierpiński triangle][wikipedia-sierpinski-triangle]. + +The teacher implores you and your classmates to look up other uses, and assures you that there are lots more! +At that moment, the school bell rings. +You realize that for the past hour, you were completely absorbed in learning about Pascal's triangle. +You quickly grab your laptop from your bag and go outside, ready to enjoy both the sunshine _and_ the wonders of Pascal's triangle. + +[wikipedia]: https://en.wikipedia.org/wiki/Pascal%27s_triangle +[wikipedia-sierpinski-triangle]: https://en.wikipedia.org/wiki/Sierpi%C5%84ski_triangle diff --git a/exercises/practice/pascals-triangle/.eslintrc b/exercises/practice/pascals-triangle/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/pascals-triangle/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/pascals-triangle/.gitignore b/exercises/practice/pascals-triangle/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/pascals-triangle/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/pascals-triangle/.meta/config.json b/exercises/practice/pascals-triangle/.meta/config.json index 4cb6918a68..0820d079b0 100644 --- a/exercises/practice/pascals-triangle/.meta/config.json +++ b/exercises/practice/pascals-triangle/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Compute Pascal's triangle up to a given number of rows.", - "authors": ["rchavarria"], + "authors": [ + "rchavarria" + ], "contributors": [ "ankorGH", "matthewmorgan", @@ -10,10 +11,23 @@ "SleeplessByte" ], "files": { - "solution": ["pascals-triangle.js"], - "test": ["pascals-triangle.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "pascals-triangle.js" + ], + "test": [ + "pascals-triangle.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Compute Pascal's triangle up to a given number of rows.", "source": "Pascal's Triangle at Wolfram Math World", - "source_url": "http://mathworld.wolfram.com/PascalsTriangle.html" + "source_url": "https://www.wolframalpha.com/input/?i=Pascal%27s+triangle", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/pascals-triangle/babel.config.js b/exercises/practice/pascals-triangle/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/pascals-triangle/babel.config.js +++ b/exercises/practice/pascals-triangle/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/pascals-triangle/eslint.config.mjs b/exercises/practice/pascals-triangle/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/pascals-triangle/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/pascals-triangle/jest.config.js b/exercises/practice/pascals-triangle/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/pascals-triangle/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/pascals-triangle/package.json b/exercises/practice/pascals-triangle/package.json index 818d0dee60..bafcd7a6ad 100644 --- a/exercises/practice/pascals-triangle/package.json +++ b/exercises/practice/pascals-triangle/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/pascals-triangle" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/pascals-triangle/pascals-triangle.js b/exercises/practice/pascals-triangle/pascals-triangle.js index 699a87abe3..a11f7bf194 100644 --- a/exercises/practice/pascals-triangle/pascals-triangle.js +++ b/exercises/practice/pascals-triangle/pascals-triangle.js @@ -4,5 +4,5 @@ // export const rows = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/pascals-triangle/pascals-triangle.spec.js b/exercises/practice/pascals-triangle/pascals-triangle.spec.js index 4641921044..e910370fb3 100644 --- a/exercises/practice/pascals-triangle/pascals-triangle.spec.js +++ b/exercises/practice/pascals-triangle/pascals-triangle.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { rows } from './pascals-triangle'; describe('Pascals Triangle', () => { diff --git a/exercises/practice/perfect-numbers/.docs/instructions.md b/exercises/practice/perfect-numbers/.docs/instructions.md index 144c9133e4..b2bc82ca3e 100644 --- a/exercises/practice/perfect-numbers/.docs/instructions.md +++ b/exercises/practice/perfect-numbers/.docs/instructions.md @@ -1,18 +1,39 @@ # Instructions -Determine if a number is perfect, abundant, or deficient based on -Nicomachus' (60 - 120 CE) classification scheme for positive integers. - -The Greek mathematician [Nicomachus](https://en.wikipedia.org/wiki/Nicomachus) devised a classification scheme for positive integers, identifying each as belonging uniquely to the categories of **perfect**, **abundant**, or **deficient** based on their [aliquot sum](https://en.wikipedia.org/wiki/Aliquot_sum). The aliquot sum is defined as the sum of the factors of a number not including the number itself. For example, the aliquot sum of 15 is (1 + 3 + 5) = 9 - -- **Perfect**: aliquot sum = number - - 6 is a perfect number because (1 + 2 + 3) = 6 - - 28 is a perfect number because (1 + 2 + 4 + 7 + 14) = 28 -- **Abundant**: aliquot sum > number - - 12 is an abundant number because (1 + 2 + 3 + 4 + 6) = 16 - - 24 is an abundant number because (1 + 2 + 3 + 4 + 6 + 8 + 12) = 36 -- **Deficient**: aliquot sum < number - - 8 is a deficient number because (1 + 2 + 4) = 7 - - Prime numbers are deficient - -Implement a way to determine whether a given number is **perfect**. Depending on your language track, you may also need to implement a way to determine whether a given number is **abundant** or **deficient**. +Determine if a number is perfect, abundant, or deficient based on Nicomachus' (60 - 120 CE) classification scheme for positive integers. + +The Greek mathematician [Nicomachus][nicomachus] devised a classification scheme for positive integers, identifying each as belonging uniquely to the categories of [perfect](#perfect), [abundant](#abundant), or [deficient](#deficient) based on their [aliquot sum][aliquot-sum]. +The _aliquot sum_ is defined as the sum of the factors of a number not including the number itself. +For example, the aliquot sum of `15` is `1 + 3 + 5 = 9`. + +## Perfect + +A number is perfect when it equals its aliquot sum. +For example: + +- `6` is a perfect number because `1 + 2 + 3 = 6` +- `28` is a perfect number because `1 + 2 + 4 + 7 + 14 = 28` + +## Abundant + +A number is abundant when it is less than its aliquot sum. +For example: + +- `12` is an abundant number because `1 + 2 + 3 + 4 + 6 = 16` +- `24` is an abundant number because `1 + 2 + 3 + 4 + 6 + 8 + 12 = 36` + +## Deficient + +A number is deficient when it is greater than its aliquot sum. +For example: + +- `8` is a deficient number because `1 + 2 + 4 = 7` +- Prime numbers are deficient + +## Task + +Implement a way to determine whether a given number is [perfect](#perfect). +Depending on your language track, you may also need to implement a way to determine whether a given number is [abundant](#abundant) or [deficient](#deficient). + +[nicomachus]: https://en.wikipedia.org/wiki/Nicomachus +[aliquot-sum]: https://en.wikipedia.org/wiki/Aliquot_sum diff --git a/exercises/practice/perfect-numbers/.eslintrc b/exercises/practice/perfect-numbers/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/perfect-numbers/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/perfect-numbers/.gitignore b/exercises/practice/perfect-numbers/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/perfect-numbers/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/perfect-numbers/.meta/config.json b/exercises/practice/perfect-numbers/.meta/config.json index 7efcde92f0..834b86858e 100644 --- a/exercises/practice/perfect-numbers/.meta/config.json +++ b/exercises/practice/perfect-numbers/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Determine if a number is perfect, abundant, or deficient based on Nicomachus' (60 - 120 CE) classification scheme for positive integers.", - "authors": ["komyg"], + "authors": [ + "komyg" + ], "contributors": [ "ankorGH", "matthewmorgan", @@ -10,10 +11,23 @@ "tejasbubane" ], "files": { - "solution": ["perfect-numbers.js"], - "test": ["perfect-numbers.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "perfect-numbers.js" + ], + "test": [ + "perfect-numbers.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Determine if a number is perfect, abundant, or deficient based on Nicomachus' (60 - 120 CE) classification scheme for positive integers.", "source": "Taken from Chapter 2 of Functional Thinking by Neal Ford.", - "source_url": "http://shop.oreilly.com/product/0636920029687.do" + "source_url": "https://www.oreilly.com/library/view/functional-thinking/9781449365509/", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/perfect-numbers/babel.config.js b/exercises/practice/perfect-numbers/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/perfect-numbers/babel.config.js +++ b/exercises/practice/perfect-numbers/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/perfect-numbers/eslint.config.mjs b/exercises/practice/perfect-numbers/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/perfect-numbers/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/perfect-numbers/jest.config.js b/exercises/practice/perfect-numbers/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/perfect-numbers/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/perfect-numbers/package.json b/exercises/practice/perfect-numbers/package.json index a597a671f7..333c1d5bb2 100644 --- a/exercises/practice/perfect-numbers/package.json +++ b/exercises/practice/perfect-numbers/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/perfect-numbers" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/perfect-numbers/perfect-numbers.js b/exercises/practice/perfect-numbers/perfect-numbers.js index d2c0e5a701..a3d0c98b26 100644 --- a/exercises/practice/perfect-numbers/perfect-numbers.js +++ b/exercises/practice/perfect-numbers/perfect-numbers.js @@ -4,5 +4,5 @@ // export const classify = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/perfect-numbers/perfect-numbers.spec.js b/exercises/practice/perfect-numbers/perfect-numbers.spec.js index ffe4638928..3889240c8f 100644 --- a/exercises/practice/perfect-numbers/perfect-numbers.spec.js +++ b/exercises/practice/perfect-numbers/perfect-numbers.spec.js @@ -1,16 +1,17 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { classify } from './perfect-numbers'; describe('Exercise - Perfect Numbers', () => { describe('Invalid Inputs', () => { test('Zero is rejected (not a natural number)', () => { expect(() => classify(0)).toThrow( - new Error('Classification is only possible for natural numbers.') + new Error('Classification is only possible for natural numbers.'), ); }); xtest('Negative integer is rejected (not a natural number)', () => { expect(() => classify(-1)).toThrow( - new Error('Classification is only possible for natural numbers.') + new Error('Classification is only possible for natural numbers.'), ); }); }); diff --git a/exercises/practice/phone-number/.docs/instructions.md b/exercises/practice/phone-number/.docs/instructions.md index 608bcc03ed..5d4d3739f4 100644 --- a/exercises/practice/phone-number/.docs/instructions.md +++ b/exercises/practice/phone-number/.docs/instructions.md @@ -1,20 +1,24 @@ # Instructions -Clean up user-entered phone numbers so that they can be sent SMS messages. +Clean up phone numbers so that they can be sent SMS messages. -The **North American Numbering Plan (NANP)** is a telephone numbering system used by many countries in North America like the United States, Canada or Bermuda. All NANP-countries share the same international country code: `1`. +The **North American Numbering Plan (NANP)** is a telephone numbering system used by many countries in North America like the United States, Canada or Bermuda. +All NANP-countries share the same international country code: `1`. -NANP numbers are ten-digit numbers consisting of a three-digit Numbering Plan Area code, commonly known as _area code_, followed by a seven-digit local number. The first three digits of the local number represent the _exchange code_, followed by the unique four-digit number which is the _subscriber number_. +NANP numbers are ten-digit numbers consisting of a three-digit Numbering Plan Area code, commonly known as _area code_, followed by a seven-digit local number. +The first three digits of the local number represent the _exchange code_, followed by the unique four-digit number which is the _subscriber number_. The format is usually represented as ```text -(NXX)-NXX-XXXX +NXX NXX-XXXX ``` where `N` is any digit from 2 through 9 and `X` is any digit from 0 through 9. -Your task is to clean up differently formatted telephone numbers by removing punctuation and the country code (1) if present. +Sometimes they also have the country code (represented as `1` or `+1`) prefixed. + +Your task is to clean up differently formatted telephone numbers by removing punctuation and the country code if present. For example, the inputs diff --git a/exercises/practice/phone-number/.docs/introduction.md b/exercises/practice/phone-number/.docs/introduction.md new file mode 100644 index 0000000000..c4142c5af7 --- /dev/null +++ b/exercises/practice/phone-number/.docs/introduction.md @@ -0,0 +1,12 @@ +# Introduction + +You've joined LinkLine, a leading communications company working to ensure reliable connections for everyone. +The team faces a big challenge: users submit phone numbers in all sorts of formats — dashes, spaces, dots, parentheses, and even prefixes. +Some numbers are valid, while others are impossible to use. + +Your mission is to turn this chaos into order. +You'll clean up valid numbers, formatting them appropriately for use in the system. +At the same time, you'll identify and filter out any invalid entries. + +The success of LinkLine's operations depends on your ability to separate the useful from the unusable. +Are you ready to take on the challenge and keep the connections running smoothly? diff --git a/exercises/practice/phone-number/.eslintrc b/exercises/practice/phone-number/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/phone-number/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/phone-number/.gitignore b/exercises/practice/phone-number/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/phone-number/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/phone-number/.meta/config.json b/exercises/practice/phone-number/.meta/config.json index 4254ed11ea..66b2bd7569 100644 --- a/exercises/practice/phone-number/.meta/config.json +++ b/exercises/practice/phone-number/.meta/config.json @@ -1,9 +1,11 @@ { - "blurb": "Clean up user-entered phone numbers so that they can be sent SMS messages.", - "authors": ["rchavarria"], + "authors": [ + "rchavarria" + ], "contributors": [ "ankorGH", "draalger", + "jagdish-15", "kytrinyx", "LyleCharlesScott", "matthewmorgan", @@ -14,10 +16,23 @@ "tejasbubane" ], "files": { - "solution": ["phone-number.js"], - "test": ["phone-number.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "phone-number.js" + ], + "test": [ + "phone-number.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, - "source": "Event Manager by JumpstartLab", - "source_url": "http://tutorials.jumpstartlab.com/projects/eventmanager.html" + "blurb": "Clean up user-entered phone numbers so that they can be sent SMS messages.", + "source": "Exercise by the JumpstartLab team for students at The Turing School of Software and Design.", + "source_url": "https://turing.edu", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/phone-number/.meta/proof.ci.js b/exercises/practice/phone-number/.meta/proof.ci.js index d72bfbc643..ba7cdb3f92 100644 --- a/exercises/practice/phone-number/.meta/proof.ci.js +++ b/exercises/practice/phone-number/.meta/proof.ci.js @@ -17,11 +17,11 @@ export const clean = (number) => { } if (numberLength < 10) { - throw new Error('Incorrect number of digits'); + throw new Error('Must not be fewer than 10 digits'); } if (numberLength > 11) { - throw new Error('More than 11 digits'); + throw new Error('Must not be greater than 11 digits'); } if (strippedNumber.substring(0, 1) === '0') { diff --git a/exercises/practice/phone-number/.meta/tests.toml b/exercises/practice/phone-number/.meta/tests.toml index 6365e12c08..24dbf07a76 100644 --- a/exercises/practice/phone-number/.meta/tests.toml +++ b/exercises/practice/phone-number/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [79666dce-e0f1-46de-95a1-563802913c35] description = "cleans the number" @@ -13,6 +20,11 @@ description = "cleans numbers with multiple spaces" [598d8432-0659-4019-a78b-1c6a73691d21] description = "invalid when 9 digits" +include = false + +[2de74156-f646-42b5-8638-0ef1d8b58bc2] +description = "invalid when 9 digits" +reimplements = "598d8432-0659-4019-a78b-1c6a73691d21" [57061c72-07b5-431f-9766-d97da7c4399d] description = "invalid when 11 digits does not start with a 1" @@ -25,12 +37,27 @@ description = "valid when 11 digits and starting with 1 even with punctuation" [c6a5f007-895a-4fc5-90bc-a7e70f9b5cad] description = "invalid when more than 11 digits" +include = false + +[4a1509b7-8953-4eec-981b-c483358ff531] +description = "invalid when more than 11 digits" +reimplements = "c6a5f007-895a-4fc5-90bc-a7e70f9b5cad" [63f38f37-53f6-4a5f-bd86-e9b404f10a60] description = "invalid with letters" +include = false + +[eb8a1fc0-64e5-46d3-b0c6-33184208e28a] +description = "invalid with letters" +reimplements = "63f38f37-53f6-4a5f-bd86-e9b404f10a60" [4bd97d90-52fd-45d3-b0db-06ab95b1244e] description = "invalid with punctuations" +include = false + +[065f6363-8394-4759-b080-e6c8c351dd1f] +description = "invalid with punctuations" +reimplements = "4bd97d90-52fd-45d3-b0db-06ab95b1244e" [d77d07f8-873c-4b17-8978-5f66139bf7d7] description = "invalid if area code starts with 0" diff --git a/exercises/practice/phone-number/babel.config.js b/exercises/practice/phone-number/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/phone-number/babel.config.js +++ b/exercises/practice/phone-number/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/phone-number/eslint.config.mjs b/exercises/practice/phone-number/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/phone-number/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/phone-number/jest.config.js b/exercises/practice/phone-number/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/phone-number/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/phone-number/package.json b/exercises/practice/phone-number/package.json index 99140cec30..1b06afa641 100644 --- a/exercises/practice/phone-number/package.json +++ b/exercises/practice/phone-number/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/phone-number" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/phone-number/phone-number.js b/exercises/practice/phone-number/phone-number.js index ba60516cfb..678532221a 100644 --- a/exercises/practice/phone-number/phone-number.js +++ b/exercises/practice/phone-number/phone-number.js @@ -4,5 +4,5 @@ // export const clean = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/phone-number/phone-number.spec.js b/exercises/practice/phone-number/phone-number.spec.js index 323f956022..e3ae5be50b 100644 --- a/exercises/practice/phone-number/phone-number.spec.js +++ b/exercises/practice/phone-number/phone-number.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { clean } from './phone-number'; describe('Phone Number', () => { @@ -16,13 +17,13 @@ describe('Phone Number', () => { xtest('invalid when 9 digits', () => { expect(() => clean('123456789')).toThrow( - new Error('Incorrect number of digits') + new Error('Must not be fewer than 10 digits'), ); }); xtest('invalid when 11 digits does not start with a 1', () => { expect(() => clean('22234567890')).toThrow( - new Error('11 digits must start with 1') + new Error('11 digits must start with 1'), ); }); @@ -36,67 +37,67 @@ describe('Phone Number', () => { xtest('invalid when more than 11 digits', () => { expect(() => clean('321234567890')).toThrow( - new Error('More than 11 digits') + new Error('Must not be greater than 11 digits'), ); }); xtest('invalid with letters', () => { - expect(() => clean('123-abc-7890')).toThrow( - new Error('Letters not permitted') + expect(() => clean('523-abc-7890')).toThrow( + new Error('Letters not permitted'), ); }); xtest('invalid with punctuations', () => { - expect(() => clean('123-@:!-7890')).toThrow( - new Error('Punctuations not permitted') + expect(() => clean('523-@:!-7890')).toThrow( + new Error('Punctuations not permitted'), ); }); xtest('invalid if area code starts with 0', () => { expect(() => clean('(023) 456-7890')).toThrow( - new Error('Area code cannot start with zero') + new Error('Area code cannot start with zero'), ); }); xtest('invalid if area code starts with 1', () => { expect(() => clean('(123) 456-7890')).toThrow( - new Error('Area code cannot start with one') + new Error('Area code cannot start with one'), ); }); xtest('invalid if exchange code starts with 0', () => { expect(() => clean('(223) 056-7890')).toThrow( - new Error('Exchange code cannot start with zero') + new Error('Exchange code cannot start with zero'), ); }); xtest('invalid if exchange code starts with 1', () => { expect(() => clean('(223) 156-7890')).toThrow( - new Error('Exchange code cannot start with one') + new Error('Exchange code cannot start with one'), ); }); xtest('invalid if area code starts with 0 on valid 11-digit number', () => { expect(() => clean('1 (023) 456-7890')).toThrow( - new Error('Area code cannot start with zero') + new Error('Area code cannot start with zero'), ); }); xtest('invalid if area code starts with 1 on valid 11-digit number', () => { expect(() => clean('1 (123) 456-7890')).toThrow( - new Error('Area code cannot start with one') + new Error('Area code cannot start with one'), ); }); xtest('invalid if exchange code starts with 0 on valid 11-digit number', () => { expect(() => clean('1 (223) 056-7890')).toThrow( - new Error('Exchange code cannot start with zero') + new Error('Exchange code cannot start with zero'), ); }); xtest('invalid if exchange code starts with 1 on valid 11-digit number', () => { expect(() => clean('1 (223) 156-7890')).toThrow( - new Error('Exchange code cannot start with one') + new Error('Exchange code cannot start with one'), ); }); }); diff --git a/exercises/practice/pig-latin/.docs/instructions.md b/exercises/practice/pig-latin/.docs/instructions.md index bcb1251176..a9645ac236 100644 --- a/exercises/practice/pig-latin/.docs/instructions.md +++ b/exercises/practice/pig-latin/.docs/instructions.md @@ -1,18 +1,46 @@ # Instructions -Implement a program that translates from English to Pig Latin. +Your task is to translate text from English to Pig Latin. +The translation is defined using four rules, which look at the pattern of vowels and consonants at the beginning of a word. +These rules look at each word's use of vowels and consonants: -Pig Latin is a made-up children's language that's intended to be -confusing. It obeys a few simple rules (below), but when it's spoken -quickly it's really difficult for non-children (and non-native speakers) -to understand. +- vowels: the letters `a`, `e`, `i`, `o`, and `u` +- consonants: the other 21 letters of the English alphabet -- **Rule 1**: If a word begins with a vowel sound, add an "ay" sound to the end of the word. Please note that "xr" and "yt" at the beginning of a word make vowel sounds (e.g. "xray" -> "xrayay", "yttria" -> "yttriaay"). -- **Rule 2**: If a word begins with a consonant sound, move it to the end of the word and then add an "ay" sound to the end of the word. Consonant sounds can be made up of multiple consonants, a.k.a. a consonant cluster (e.g. "chair" -> "airchay"). -- **Rule 3**: If a word starts with a consonant sound followed by "qu", move it to the end of the word, and then add an "ay" sound to the end of the word (e.g. "square" -> "aresquay"). -- **Rule 4**: If a word contains a "y" after a consonant cluster or as the second letter in a two letter word it makes a vowel sound (e.g. "rhythm" -> "ythmrhay", "my" -> "ymay"). +## Rule 1 -There are a few more rules for edge cases, and there are regional -variants too. +If a word begins with a vowel, or starts with `"xr"` or `"yt"`, add an `"ay"` sound to the end of the word. -See for more details. +For example: + +- `"apple"` -> `"appleay"` (starts with vowel) +- `"xray"` -> `"xrayay"` (starts with `"xr"`) +- `"yttria"` -> `"yttriaay"` (starts with `"yt"`) + +## Rule 2 + +If a word begins with one or more consonants, first move those consonants to the end of the word and then add an `"ay"` sound to the end of the word. + +For example: + +- `"pig"` -> `"igp"` -> `"igpay"` (starts with single consonant) +- `"chair"` -> `"airch"` -> `"airchay"` (starts with multiple consonants) +- `"thrush"` -> `"ushthr"` -> `"ushthray"` (starts with multiple consonants) + +## Rule 3 + +If a word starts with zero or more consonants followed by `"qu"`, first move those consonants (if any) and the `"qu"` part to the end of the word, and then add an `"ay"` sound to the end of the word. + +For example: + +- `"quick"` -> `"ickqu"` -> `"ickquay"` (starts with `"qu"`, no preceding consonants) +- `"square"` -> `"aresqu"` -> `"aresquay"` (starts with one consonant followed by `"qu`") + +## Rule 4 + +If a word starts with one or more consonants followed by `"y"`, first move the consonants preceding the `"y"`to the end of the word, and then add an `"ay"` sound to the end of the word. + +Some examples: + +- `"my"` -> `"ym"` -> `"ymay"` (starts with single consonant followed by `"y"`) +- `"rhythm"` -> `"ythmrh"` -> `"ythmrhay"` (starts with multiple consonants followed by `"y"`) diff --git a/exercises/practice/pig-latin/.docs/introduction.md b/exercises/practice/pig-latin/.docs/introduction.md new file mode 100644 index 0000000000..04baa47586 --- /dev/null +++ b/exercises/practice/pig-latin/.docs/introduction.md @@ -0,0 +1,8 @@ +# Introduction + +Your parents have challenged you and your sibling to a game of two-on-two basketball. +Confident they'll win, they let you score the first couple of points, but then start taking over the game. +Needing a little boost, you start speaking in [Pig Latin][pig-latin], which is a made-up children's language that's difficult for non-children to understand. +This will give you the edge to prevail over your parents! + +[pig-latin]: https://en.wikipedia.org/wiki/Pig_latin diff --git a/exercises/practice/pig-latin/.eslintrc b/exercises/practice/pig-latin/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/pig-latin/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/pig-latin/.gitignore b/exercises/practice/pig-latin/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/pig-latin/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/pig-latin/.meta/config.json b/exercises/practice/pig-latin/.meta/config.json index 613253b3e8..a1fc612643 100644 --- a/exercises/practice/pig-latin/.meta/config.json +++ b/exercises/practice/pig-latin/.meta/config.json @@ -1,8 +1,10 @@ { - "blurb": "Implement a program that translates from English to Pig Latin", - "authors": ["rchavarria"], + "authors": [ + "rchavarria" + ], "contributors": [ "ankorGH", + "jagdish-15", "konni2020", "matthewmorgan", "ntshcalleia", @@ -11,10 +13,23 @@ "tejasbubane" ], "files": { - "solution": ["pig-latin.js"], - "test": ["pig-latin.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "pig-latin.js" + ], + "test": [ + "pig-latin.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Implement a program that translates from English to Pig Latin.", "source": "The Pig Latin exercise at Test First Teaching by Ultrasaurus", - "source_url": "https://github.com/ultrasaurus/test-first-teaching/blob/master/learn_ruby/pig_latin/" + "source_url": "https://github.com/ultrasaurus/test-first-teaching/blob/master/learn_ruby/pig_latin/", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/pig-latin/.meta/tests.toml b/exercises/practice/pig-latin/.meta/tests.toml index 49ce6e110e..d524305b45 100644 --- a/exercises/practice/pig-latin/.meta/tests.toml +++ b/exercises/practice/pig-latin/.meta/tests.toml @@ -1,69 +1,79 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [11567f84-e8c6-4918-aedb-435f0b73db57] -description = "word beginning with a" +description = "ay is added to words that start with vowels -> word beginning with a" [f623f581-bc59-4f45-9032-90c3ca9d2d90] -description = "word beginning with e" +description = "ay is added to words that start with vowels -> word beginning with e" [7dcb08b3-23a6-4e8a-b9aa-d4e859450d58] -description = "word beginning with i" +description = "ay is added to words that start with vowels -> word beginning with i" [0e5c3bff-266d-41c8-909f-364e4d16e09c] -description = "word beginning with o" +description = "ay is added to words that start with vowels -> word beginning with o" [614ba363-ca3c-4e96-ab09-c7320799723c] -description = "word beginning with u" +description = "ay is added to words that start with vowels -> word beginning with u" [bf2538c6-69eb-4fa7-a494-5a3fec911326] -description = "word beginning with a vowel and followed by a qu" +description = "ay is added to words that start with vowels -> word beginning with a vowel and followed by a qu" [e5be8a01-2d8a-45eb-abb4-3fcc9582a303] -description = "word beginning with p" +description = "first letter and ay are moved to the end of words that start with consonants -> word beginning with p" [d36d1e13-a7ed-464d-a282-8820cb2261ce] -description = "word beginning with k" +description = "first letter and ay are moved to the end of words that start with consonants -> word beginning with k" [d838b56f-0a89-4c90-b326-f16ff4e1dddc] -description = "word beginning with x" +description = "first letter and ay are moved to the end of words that start with consonants -> word beginning with x" [bce94a7a-a94e-4e2b-80f4-b2bb02e40f71] -description = "word beginning with q without a following u" +description = "first letter and ay are moved to the end of words that start with consonants -> word beginning with q without a following u" + +[e59dbbe8-ccee-4619-a8e9-ce017489bfc0] +description = "first letter and ay are moved to the end of words that start with consonants -> word beginning with consonant and vowel containing qu" [c01e049a-e3e2-451c-bf8e-e2abb7e438b8] -description = "word beginning with ch" +description = "some letter clusters are treated like a single consonant -> word beginning with ch" [9ba1669e-c43f-4b93-837a-cfc731fd1425] -description = "word beginning with qu" +description = "some letter clusters are treated like a single consonant -> word beginning with qu" [92e82277-d5e4-43d7-8dd3-3a3b316c41f7] -description = "word beginning with qu and a preceding consonant" +description = "some letter clusters are treated like a single consonant -> word beginning with qu and a preceding consonant" [79ae4248-3499-4d5b-af46-5cb05fa073ac] -description = "word beginning with th" +description = "some letter clusters are treated like a single consonant -> word beginning with th" [e0b3ae65-f508-4de3-8999-19c2f8e243e1] -description = "word beginning with thr" +description = "some letter clusters are treated like a single consonant -> word beginning with thr" [20bc19f9-5a35-4341-9d69-1627d6ee6b43] -description = "word beginning with sch" +description = "some letter clusters are treated like a single consonant -> word beginning with sch" [54b796cb-613d-4509-8c82-8fbf8fc0af9e] -description = "word beginning with yt" +description = "some letter clusters are treated like a single vowel -> word beginning with yt" [8c37c5e1-872e-4630-ba6e-d20a959b67f6] -description = "word beginning with xr" +description = "some letter clusters are treated like a single vowel -> word beginning with xr" [a4a36d33-96f3-422c-a233-d4021460ff00] -description = "y is treated like a consonant at the beginning of a word" +description = "position of y in a word determines if it is a consonant or a vowel -> y is treated like a consonant at the beginning of a word" [adc90017-1a12-4100-b595-e346105042c7] -description = "y is treated like a vowel at the end of a consonant cluster" +description = "position of y in a word determines if it is a consonant or a vowel -> y is treated like a vowel at the end of a consonant cluster" [29b4ca3d-efe5-4a95-9a54-8467f2e5e59a] -description = "y as second letter in two letter word" +description = "position of y in a word determines if it is a consonant or a vowel -> y as second letter in two letter word" [44616581-5ce3-4a81-82d0-40c7ab13d2cf] -description = "a whole phrase" +description = "phrases are translated -> a whole phrase" diff --git a/exercises/practice/pig-latin/babel.config.js b/exercises/practice/pig-latin/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/pig-latin/babel.config.js +++ b/exercises/practice/pig-latin/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/pig-latin/eslint.config.mjs b/exercises/practice/pig-latin/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/pig-latin/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/pig-latin/jest.config.js b/exercises/practice/pig-latin/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/pig-latin/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/pig-latin/package.json b/exercises/practice/pig-latin/package.json index a834bc36ee..c1032a4979 100644 --- a/exercises/practice/pig-latin/package.json +++ b/exercises/practice/pig-latin/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/pig-latin" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/pig-latin/pig-latin.js b/exercises/practice/pig-latin/pig-latin.js index 66ca7b0f4d..d1526260a3 100644 --- a/exercises/practice/pig-latin/pig-latin.js +++ b/exercises/practice/pig-latin/pig-latin.js @@ -4,5 +4,5 @@ // export const translate = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/pig-latin/pig-latin.spec.js b/exercises/practice/pig-latin/pig-latin.spec.js index d98f2e9745..204b0344d6 100644 --- a/exercises/practice/pig-latin/pig-latin.spec.js +++ b/exercises/practice/pig-latin/pig-latin.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { translate } from './pig-latin'; describe('Pig Latin', () => { @@ -43,6 +44,10 @@ describe('Pig Latin', () => { xtest('word beginning with q without a following u', () => { expect(translate('qat')).toEqual('atqay'); }); + + xtest('word beginning with consonant and vowel containing qu', () => { + expect(translate('liquid')).toEqual('iquidlay'); + }); }); describe('some letter clusters are treated like a single consonant', () => { diff --git a/exercises/practice/point-mutations/.eslintrc b/exercises/practice/point-mutations/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/point-mutations/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/point-mutations/.gitignore b/exercises/practice/point-mutations/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/point-mutations/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/point-mutations/.meta/config.json b/exercises/practice/point-mutations/.meta/config.json index 58186764fb..027f200d13 100644 --- a/exercises/practice/point-mutations/.meta/config.json +++ b/exercises/practice/point-mutations/.meta/config.json @@ -1,12 +1,31 @@ { - "blurb": "Calculate the Hamming difference between two DNA strands.", - "authors": ["matthewmorgan"], - "contributors": ["ankorGH", "SleeplessByte", "tejasbubane", "thanhcng"], + "authors": [ + "matthewmorgan" + ], + "contributors": [ + "ankorGH", + "SleeplessByte", + "tejasbubane", + "thanhcng" + ], "files": { - "solution": ["point-mutations.js"], - "test": ["point-mutations.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "point-mutations.js" + ], + "test": [ + "point-mutations.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Calculate the Hamming difference between two DNA strands.", "source": "The Calculating Point Mutations problem at Rosalind", - "source_url": "http://rosalind.info/problems/hamm/" + "source_url": "http://rosalind.info/problems/hamm/", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/point-mutations/.meta/proof.ci.js b/exercises/practice/point-mutations/.meta/proof.ci.js index 03f2a5ab73..90b64a005c 100644 --- a/exercises/practice/point-mutations/.meta/proof.ci.js +++ b/exercises/practice/point-mutations/.meta/proof.ci.js @@ -7,7 +7,7 @@ export class DNA { let distance = 0; const calculationDistance = Math.min( this.nucleotides.length, - comparison.length + comparison.length, ); for (let i = 0; i < calculationDistance; i += 1) { diff --git a/exercises/practice/point-mutations/babel.config.js b/exercises/practice/point-mutations/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/point-mutations/babel.config.js +++ b/exercises/practice/point-mutations/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/point-mutations/eslint.config.mjs b/exercises/practice/point-mutations/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/point-mutations/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/point-mutations/jest.config.js b/exercises/practice/point-mutations/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/point-mutations/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/point-mutations/package.json b/exercises/practice/point-mutations/package.json index 9623e5c66d..e03b8ed8f1 100644 --- a/exercises/practice/point-mutations/package.json +++ b/exercises/practice/point-mutations/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/point-mutations" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/point-mutations/point-mutations.js b/exercises/practice/point-mutations/point-mutations.js index a3fe4a8812..055bcaaeac 100644 --- a/exercises/practice/point-mutations/point-mutations.js +++ b/exercises/practice/point-mutations/point-mutations.js @@ -5,10 +5,10 @@ export class DNA { constructor() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } hammingDistance() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/point-mutations/point-mutations.spec.js b/exercises/practice/point-mutations/point-mutations.spec.js index fcc85decb3..b81c66f394 100644 --- a/exercises/practice/point-mutations/point-mutations.spec.js +++ b/exercises/practice/point-mutations/point-mutations.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { DNA } from './point-mutations'; describe('DNA', () => { diff --git a/exercises/practice/poker/.docs/instructions.md b/exercises/practice/poker/.docs/instructions.md new file mode 100644 index 0000000000..107cd49d66 --- /dev/null +++ b/exercises/practice/poker/.docs/instructions.md @@ -0,0 +1,7 @@ +# Instructions + +Pick the best hand(s) from a list of poker hands. + +See [Wikipedia][poker-hands] for an overview of poker hands. + +[poker-hands]: https://en.wikipedia.org/wiki/List_of_poker_hands diff --git a/exercises/practice/poker/.gitignore b/exercises/practice/poker/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/poker/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/poker/.meta/config.json b/exercises/practice/poker/.meta/config.json new file mode 100644 index 0000000000..394c08fd91 --- /dev/null +++ b/exercises/practice/poker/.meta/config.json @@ -0,0 +1,22 @@ +{ + "authors": [ + "lpizzinidev" + ], + "contributors": [ + "jagdish-15" + ], + "files": { + "solution": [ + "poker.js" + ], + "test": [ + "poker.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] + }, + "blurb": "Pick the best hand(s) from a list of poker hands.", + "source": "Inspired by the training course from Udacity.", + "source_url": "https://www.udacity.com/course/design-of-computer-programs--cs212" +} diff --git a/exercises/practice/poker/.meta/proof.ci.js b/exercises/practice/poker/.meta/proof.ci.js new file mode 100644 index 0000000000..ec7134e440 --- /dev/null +++ b/exercises/practice/poker/.meta/proof.ci.js @@ -0,0 +1,194 @@ +const HIGH_CARD = 1; +const PAIR = 2; +const TWO_PAIR = 3; +const THREE_OF_A_KIND = 4; +const STRAIGHT = 5; +const FLUSH = 6; +const FULL_HOUSE = 7; +const POKER = 8; +const STRAIGHT_FLUSH = 9; + +export const bestHands = (hands) => { + let maxRank = 0; + let maxValue = {}; + let result = []; + for (const hand of hands) { + const handValue = evaluateHand(hand); + if (handValue.rank > maxRank) { + maxRank = handValue.rank; + maxValue = handValue; + result = [hand]; + } else if (handValue.rank === maxRank) { + const handValueComparison = compareHandValues( + maxRank, + maxValue, + handValue, + ); + if (handValueComparison === 2) { + maxValue = handValue; + result = [hand]; + } else if (handValueComparison === 0) { + result.push(hand); + } + } + } + return result; +}; + +const evaluateHand = (hand) => { + const cards = hand.split(' ').map((card) => { + const [value, suit] = card.replace('10', 'T').split(''); + return { value, suit }; + }); + const cardSuits = cards.map((card) => card.suit); + const cardValues = cards + .map((card) => { + switch (card.value) { + case 'A': + return 14; + case 'K': + return 13; + case 'Q': + return 12; + case 'J': + return 11; + case 'T': + return 10; + default: + return Number(card.value); + } + }) + .sort((a, b) => a - b); + const isFlush = new Set(cardSuits).size === 1; + + const isLowestStraight = cardValues.join(',') === '2,3,4,5,14'; + if (isLowestStraight) { + // Ace has a value of 1 in the lowest straight + cardValues.pop(); + cardValues.unshift(1); + } + const isStraight = + Math.abs(cardValues[0] - cardValues[cardValues.length - 1]) === 4 && + new Set(cardValues).size === 5; + + const cardValuesCount = Array(15).fill(0); + for (const value of cardValues) { + cardValuesCount[value]++; + } + + const handValue = { rank: -1 }; + + if (isStraight && isFlush) handValue.rank = STRAIGHT_FLUSH; + else if (cardValuesCount.includes(4)) handValue.rank = POKER; + else if (cardValuesCount.includes(3) && cardValuesCount.includes(2)) + handValue.rank = FULL_HOUSE; + else if (isFlush) handValue.rank = FLUSH; + else if (isStraight) handValue.rank = STRAIGHT; + else if (cardValuesCount.includes(3)) handValue.rank = THREE_OF_A_KIND; + else if (cardValuesCount.filter((value) => value === 2).length === 2) + handValue.rank = TWO_PAIR; + else if (cardValuesCount.includes(2)) handValue.rank = PAIR; + else handValue.rank = HIGH_CARD; + + switch (handValue.rank) { + case STRAIGHT_FLUSH: + case STRAIGHT: + handValue.highestCard = Math.max(...cardValues); + break; + case POKER: + case FULL_HOUSE: + case THREE_OF_A_KIND: + case TWO_PAIR: + case PAIR: + cardValuesCount.forEach((value, index) => { + if (value === 4) handValue.quad = index; + else if (value === 3) handValue.triplet = index; + else if (value === 2) { + if (handValue.pair === undefined) handValue.pair = index; + else handValue.pair2 = index; + } else if (value === 1) { + if (handValue.kicker === undefined) handValue.kicker = index; + else if (handValue.kicker2 === undefined) handValue.kicker2 = index; + else handValue.kicker3 = index; + } + }); + break; + case FLUSH: + case HIGH_CARD: + cardValuesCount.forEach((value, index) => { + if (value === 1) { + if (handValue.card5 === undefined) handValue.card5 = index; + else if (handValue.card4 === undefined) handValue.card4 = index; + else if (handValue.card3 === undefined) handValue.card3 = index; + else if (handValue.card2 === undefined) handValue.card2 = index; + else handValue.card1 = index; + } + }); + break; + } + + return handValue; +}; + +const compareHandValues = (rank, handValue1, handValue2) => { + switch (rank) { + case STRAIGHT_FLUSH: + case STRAIGHT: + if (handValue1.highestCard > handValue2.highestCard) return 1; + if (handValue1.highestCard < handValue2.highestCard) return 2; + return 0; + case FLUSH: + case HIGH_CARD: + if (handValue1.card1 < handValue2.card1) return 2; + if (handValue1.card2 > handValue2.card2) return 1; + if (handValue1.card2 < handValue2.card2) return 2; + if (handValue1.card3 > handValue2.card3) return 1; + if (handValue1.card3 < handValue2.card3) return 2; + if (handValue1.card4 > handValue2.card4) return 1; + if (handValue1.card4 < handValue2.card4) return 2; + if (handValue1.card5 > handValue2.card5) return 1; + if (handValue1.card5 < handValue2.card5) return 2; + return 0; + case POKER: + if (handValue1.quad > handValue2.quad) return 1; + if (handValue1.quad < handValue2.quad) return 2; + if (handValue1.kicker > handValue2.kicker) return 1; + if (handValue1.kicker < handValue2.kicker) return 2; + return 0; + case FULL_HOUSE: + if (handValue1.triplet > handValue2.triplet) return 1; + if (handValue1.triplet < handValue2.triplet) return 2; + if (handValue1.pair > handValue2.pair) return 1; + if (handValue1.pair < handValue2.pair) return 2; + if (handValue1.kicker > handValue2.kicker) return 1; + if (handValue1.kicker < handValue2.kicker) return 2; + return 0; + case THREE_OF_A_KIND: + if (handValue1.triplet > handValue2.triplet) return 1; + if (handValue1.triplet < handValue2.triplet) return 2; + if (handValue1.kicker2 > handValue2.kicker2) return 1; + if (handValue1.kicker2 < handValue2.kicker2) return 2; + if (handValue1.kicker > handValue2.kicker) return 1; + if (handValue1.kicker < handValue2.kicker) return 2; + return 0; + case TWO_PAIR: + if (handValue1.pair2 > handValue2.pair2) return 1; + if (handValue1.pair2 < handValue2.pair2) return 2; + if (handValue1.pair > handValue2.pair) return 1; + if (handValue1.pair < handValue2.pair) return 2; + if (handValue1.kicker > handValue2.kicker) return 1; + if (handValue1.kicker < handValue2.kicker) return 2; + return 0; + case PAIR: + if (handValue1.pair > handValue2.pair) return 1; + if (handValue1.pair < handValue2.pair) return 2; + if (handValue1.kicker3 > handValue2.kicker3) return 1; + if (handValue1.kicker3 < handValue2.kicker3) return 2; + if (handValue1.kicker2 > handValue2.kicker2) return 1; + if (handValue1.kicker2 < handValue2.kicker2) return 2; + if (handValue1.kicker > handValue2.kicker) return 1; + if (handValue1.kicker < handValue2.kicker) return 2; + return 0; + } + return -1; +}; diff --git a/exercises/practice/poker/.meta/tests.toml b/exercises/practice/poker/.meta/tests.toml new file mode 100644 index 0000000000..2e654ef63b --- /dev/null +++ b/exercises/practice/poker/.meta/tests.toml @@ -0,0 +1,131 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[161f485e-39c2-4012-84cf-bec0c755b66c] +description = "single hand always wins" + +[370ac23a-a00f-48a9-9965-6f3fb595cf45] +description = "highest card out of all hands wins" + +[d94ad5a7-17df-484b-9932-c64fc26cff52] +description = "a tie has multiple winners" + +[61ed83a9-cfaa-40a5-942a-51f52f0a8725] +description = "multiple hands with the same high cards, tie compares next highest ranked, down to last card" + +[da01becd-f5b0-4342-b7f3-1318191d0580] +description = "winning high card hand also has the lowest card" + +[f7175a89-34ff-44de-b3d7-f6fd97d1fca4] +description = "one pair beats high card" + +[e114fd41-a301-4111-a9e7-5a7f72a76561] +description = "highest pair wins" + +[b3acd3a7-f9fa-4647-85ab-e0a9e07d1365] +description = "both hands have the same pair, high card wins" + +[935bb4dc-a622-4400-97fa-86e7d06b1f76] +description = "two pairs beats one pair" + +[c8aeafe1-6e3d-4711-a6de-5161deca91fd] +description = "both hands have two pairs, highest ranked pair wins" + +[88abe1ba-7ad7-40f3-847e-0a26f8e46a60] +description = "both hands have two pairs, with the same highest ranked pair, tie goes to low pair" + +[15a7a315-0577-47a3-9981-d6cf8e6f387b] +description = "both hands have two identically ranked pairs, tie goes to remaining card (kicker)" + +[f761e21b-2560-4774-a02a-b3e9366a51ce] +description = "both hands have two pairs that add to the same value, win goes to highest pair" + +[fc6277ac-94ac-4078-8d39-9d441bc7a79e] +description = "two pairs first ranked by largest pair" + +[21e9f1e6-2d72-49a1-a930-228e5e0195dc] +description = "three of a kind beats two pair" + +[c2fffd1f-c287-480f-bf2d-9628e63bbcc3] +description = "both hands have three of a kind, tie goes to highest ranked triplet" + +[eb856cc2-481c-4b0d-9835-4d75d07a5d9d] +description = "with multiple decks, two players can have same three of a kind, ties go to highest remaining cards" +include = false + +[26a4a7d4-34a2-4f18-90b4-4a8dd35d2bb1] +description = "with multiple decks, two players can have same three of a kind, ties go to highest remaining cards" +reimplements = "eb856cc2-481c-4b0d-9835-4d75d07a5d9d" + +[a858c5d9-2f28-48e7-9980-b7fa04060a60] +description = "a straight beats three of a kind" + +[73c9c756-e63e-4b01-a88d-0d4491a7a0e3] +description = "aces can end a straight (10 J Q K A)" + +[76856b0d-35cd-49ce-a492-fe5db53abc02] +description = "aces can start a straight (A 2 3 4 5)" + +[e214b7df-dcba-45d3-a2e5-342d8c46c286] +description = "aces cannot be in the middle of a straight (Q K A 2 3)" + +[6980c612-bbff-4914-b17a-b044e4e69ea1] +description = "both hands with a straight, tie goes to highest ranked card" + +[5135675c-c2fc-4e21-9ba3-af77a32e9ba4] +description = "even though an ace is usually high, a 5-high straight is the lowest-scoring straight" + +[c601b5e6-e1df-4ade-b444-b60ce13b2571] +description = "flush beats a straight" + +[4d90261d-251c-49bd-a468-896bf10133de] +description = "both hands have a flush, tie goes to high card, down to the last one if necessary" +include = false + +[e04137c5-c19a-4dfc-97a1-9dfe9baaa2ff] +description = "both hands have a flush, tie goes to high card, down to the last one if necessary" +reimplements = "4d90261d-251c-49bd-a468-896bf10133de" + +[3a19361d-8974-455c-82e5-f7152f5dba7c] +description = "full house beats a flush" + +[eb73d0e6-b66c-4f0f-b8ba-bf96bc0a67f0] +description = "both hands have a full house, tie goes to highest-ranked triplet" + +[34b51168-1e43-4c0d-9b32-e356159b4d5d] +description = "with multiple decks, both hands have a full house with the same triplet, tie goes to the pair" + +[d61e9e99-883b-4f99-b021-18f0ae50c5f4] +description = "four of a kind beats a full house" + +[2e1c8c63-e0cb-4214-a01b-91954490d2fe] +description = "both hands have four of a kind, tie goes to high quad" + +[892ca75d-5474-495d-9f64-a6ce2dcdb7e1] +description = "with multiple decks, both hands with identical four of a kind, tie determined by kicker" + +[923bd910-dc7b-4f7d-a330-8b42ec10a3ac] +description = "straight flush beats four of a kind" + +[d9629e22-c943-460b-a951-2134d1b43346] +description = "aces can end a straight flush (10 J Q K A)" + +[05d5ede9-64a5-4678-b8ae-cf4c595dc824] +description = "aces can start a straight flush (A 2 3 4 5)" + +[ad655466-6d04-49e8-a50c-0043c3ac18ff] +description = "aces cannot be in the middle of a straight flush (Q K A 2 3)" + +[d0927f70-5aec-43db-aed8-1cbd1b6ee9ad] +description = "both hands have a straight flush, tie goes to highest-ranked card" + +[be620e09-0397-497b-ac37-d1d7a4464cfc] +description = "even though an ace is usually high, a 5-high straight flush is the lowest-scoring straight flush" diff --git a/exercises/practice/poker/.npmrc b/exercises/practice/poker/.npmrc new file mode 100644 index 0000000000..d26df800bb --- /dev/null +++ b/exercises/practice/poker/.npmrc @@ -0,0 +1 @@ +audit=false diff --git a/exercises/practice/poker/LICENSE b/exercises/practice/poker/LICENSE new file mode 100644 index 0000000000..90e73be03b --- /dev/null +++ b/exercises/practice/poker/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Exercism + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/exercises/practice/poker/babel.config.js b/exercises/practice/poker/babel.config.js new file mode 100644 index 0000000000..a638497df1 --- /dev/null +++ b/exercises/practice/poker/babel.config.js @@ -0,0 +1,4 @@ +module.exports = { + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], +}; diff --git a/exercises/practice/poker/eslint.config.mjs b/exercises/practice/poker/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/poker/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/poker/jest.config.js b/exercises/practice/poker/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/poker/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/poker/package.json b/exercises/practice/poker/package.json new file mode 100644 index 0000000000..09b8bbc78e --- /dev/null +++ b/exercises/practice/poker/package.json @@ -0,0 +1,34 @@ +{ + "name": "@exercism/javascript-poker", + "description": "Exercism exercises in Javascript.", + "author": "lpizzinidev", + "private": true, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/exercism/javascript", + "directory": "exercises/practice/poker" + }, + "devDependencies": { + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" + }, + "dependencies": {}, + "scripts": { + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" +} diff --git a/exercises/practice/poker/poker.js b/exercises/practice/poker/poker.js new file mode 100644 index 0000000000..0cc6d274d1 --- /dev/null +++ b/exercises/practice/poker/poker.js @@ -0,0 +1,8 @@ +// +// This is only a SKELETON file for the 'Poker' exercise. It's been provided as a +// convenience to get you started writing code faster. +// + +export const bestHands = (hands) => { + throw new Error('Remove this line and implement the function'); +}; diff --git a/exercises/practice/poker/poker.spec.js b/exercises/practice/poker/poker.spec.js new file mode 100644 index 0000000000..97d8dd4737 --- /dev/null +++ b/exercises/practice/poker/poker.spec.js @@ -0,0 +1,231 @@ +import { describe, expect, test, xtest } from '@jest/globals'; +import { bestHands } from './poker'; + +describe('Poker', () => { + test('single hand always wins', () => { + const hands = ['4S 5S 7H 8D JC']; + const expected = ['4S 5S 7H 8D JC']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('highest card out of all hands wins', () => { + const hands = ['4D 5S 6S 8D 3C', '2S 4C 7S 9H 10H', '3S 4S 5D 6H JH']; + const expected = ['3S 4S 5D 6H JH']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('a tie has multiple winners', () => { + const hands = [ + '4D 5S 6S 8D 3C', + '2S 4C 7S 9H 10H', + '3S 4S 5D 6H JH', + '3H 4H 5C 6C JD', + ]; + const expected = ['3S 4S 5D 6H JH', '3H 4H 5C 6C JD']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('multiple hands with the same high cards, tie compares next highest ranked, down to last card', () => { + const hands = ['3S 5H 6S 8D 7H', '2S 5D 6D 8C 7S']; + const expected = ['3S 5H 6S 8D 7H']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('winning high card hand also has the lowest card', () => { + const hands = ['2S 5H 6S 8D 7H', '3S 4D 6D 8C 7S']; + const expected = ['2S 5H 6S 8D 7H']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('one pair beats high card', () => { + const hands = ['4S 5H 6C 8D KH', '2S 4H 6S 4D JH']; + const expected = ['2S 4H 6S 4D JH']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('highest pair wins', () => { + const hands = ['4S 2H 6S 2D JH', '2S 4H 6C 4D JD']; + const expected = ['2S 4H 6C 4D JD']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('both hands have the same pair, high card wins', () => { + const hands = ['4H 4S AH JC 3D', '4C 4D AS 5D 6C']; + const expected = ['4H 4S AH JC 3D']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('two pairs beats one pair', () => { + const hands = ['2S 8H 6S 8D JH', '4S 5H 4C 8C 5C']; + const expected = ['4S 5H 4C 8C 5C']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('both hands have two pairs, highest ranked pair wins', () => { + const hands = ['2S 8H 2D 8D 3H', '4S 5H 4C 8S 5D']; + const expected = ['2S 8H 2D 8D 3H']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('both hands have two pairs, with the same highest ranked pair, tie goes to low pair', () => { + const hands = ['2S QS 2C QD JH', 'JD QH JS 8D QC']; + const expected = ['JD QH JS 8D QC']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('both hands have two identically ranked pairs, tie goes to remaining card (kicker)', () => { + const hands = ['JD QH JS 8D QC', 'JS QS JC 2D QD']; + const expected = ['JD QH JS 8D QC']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('both hands have two pairs that add to the same value, win goes to highest pair', () => { + const hands = ['6S 6H 3S 3H AS', '7H 7S 2H 2S AC']; + const expected = ['7H 7S 2H 2S AC']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('two pairs first ranked by largest pair', () => { + const hands = ['5C 2S 5S 4H 4C', '6S 2S 6H 7C 2C']; + const expected = ['6S 2S 6H 7C 2C']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('three of a kind beats two pair', () => { + const hands = ['2S 8H 2H 8D JH', '4S 5H 4C 8S 4H']; + const expected = ['4S 5H 4C 8S 4H']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('both hands have three of a kind, tie goes to highest ranked triplet', () => { + const hands = ['2S 2H 2C 8D JH', '4S AH AS 8C AD']; + const expected = ['4S AH AS 8C AD']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('with multiple decks, two players can have same three of a kind, ties go to highest remaining cards', () => { + const hands = ['5S AH AS 7C AD', '4S AH AS 8C AD']; + const expected = ['4S AH AS 8C AD']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('a straight beats three of a kind', () => { + const hands = ['4S 5H 4C 8D 4H', '3S 4D 2S 6D 5C']; + const expected = ['3S 4D 2S 6D 5C']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('aces can end a straight (10 J Q K A)', () => { + const hands = ['4S 5H 4C 8D 4H', '10D JH QS KD AC']; + const expected = ['10D JH QS KD AC']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('aces can start a straight (A 2 3 4 5)', () => { + const hands = ['4S 5H 4C 8D 4H', '4D AH 3S 2D 5C']; + const expected = ['4D AH 3S 2D 5C']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('aces cannot be in the middle of a straight (Q K A 2 3)', () => { + const hands = ['2C 3D 7H 5H 2S', 'QS KH AC 2D 3S']; + const expected = ['2C 3D 7H 5H 2S']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('both hands with a straight, tie goes to highest ranked card', () => { + const hands = ['4S 6C 7S 8D 5H', '5S 7H 8S 9D 6H']; + const expected = ['5S 7H 8S 9D 6H']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('even though an ace is usually high, a 5-high straight is the lowest-scoring straight', () => { + const hands = ['2H 3C 4D 5D 6H', '4S AH 3S 2D 5H']; + const expected = ['2H 3C 4D 5D 6H']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('flush beats a straight', () => { + const hands = ['4C 6H 7D 8D 5H', '2S 4S 5S 6S 7S']; + const expected = ['2S 4S 5S 6S 7S']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('both hands have a flush, tie goes to high card, down to the last one if necessary', () => { + const hands = ['2H 7H 8H 9H 6H', '3S 5S 6S 7S 8S']; + const expected = ['2H 7H 8H 9H 6H']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('full house beats a flush', () => { + const hands = ['3H 6H 7H 8H 5H', '4S 5H 4C 5D 4H']; + const expected = ['4S 5H 4C 5D 4H']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('both hands have a full house, tie goes to highest-ranked triplet', () => { + const hands = ['4H 4S 4D 9S 9D', '5H 5S 5D 8S 8D']; + const expected = ['5H 5S 5D 8S 8D']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('with multiple decks, both hands have a full house with the same triplet, tie goes to the pair', () => { + const hands = ['5H 5S 5D 9S 9D', '5H 5S 5D 8S 8D']; + const expected = ['5H 5S 5D 9S 9D']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('four of a kind beats a full house', () => { + const hands = ['4S 5H 4D 5D 4H', '3S 3H 2S 3D 3C']; + const expected = ['3S 3H 2S 3D 3C']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('both hands have four of a kind, tie goes to high quad', () => { + const hands = ['2S 2H 2C 8D 2D', '4S 5H 5S 5D 5C']; + const expected = ['4S 5H 5S 5D 5C']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('with multiple decks, both hands with identical four of a kind, tie determined by kicker', () => { + const hands = ['3S 3H 2S 3D 3C', '3S 3H 4S 3D 3C']; + const expected = ['3S 3H 4S 3D 3C']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('straight flush beats four of a kind', () => { + const hands = ['4S 5H 5S 5D 5C', '7S 8S 9S 6S 10S']; + const expected = ['7S 8S 9S 6S 10S']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('aces can end a straight flush (10 J Q K A)', () => { + const hands = ['KC AH AS AD AC', '10C JC QC KC AC']; + const expected = ['10C JC QC KC AC']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('aces can start a straight flush (A 2 3 4 5)', () => { + const hands = ['KS AH AS AD AC', '4H AH 3H 2H 5H']; + const expected = ['4H AH 3H 2H 5H']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('aces cannot be in the middle of a straight flush (Q K A 2 3)', () => { + const hands = ['2C AC QC 10C KC', 'QH KH AH 2H 3H']; + const expected = ['2C AC QC 10C KC']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('both hands have straight flush, tie goes to highest-ranked card', () => { + const hands = ['4H 6H 7H 8H 5H', '5S 7S 8S 9S 6S']; + const expected = ['5S 7S 8S 9S 6S']; + expect(bestHands(hands)).toEqual(expected); + }); + + xtest('even though an ace is usually high, a 5-high straight flush is the lowest-scoring straight flush', () => { + const hands = ['2H 3H 4H 5H 6H', '4D AD 3D 2D 5D']; + const expected = ['2H 3H 4H 5H 6H']; + expect(bestHands(hands)).toEqual(expected); + }); +}); diff --git a/exercises/practice/prime-factors/.docs/instructions.md b/exercises/practice/prime-factors/.docs/instructions.md index df7acd0fd6..252cc8ee18 100644 --- a/exercises/practice/prime-factors/.docs/instructions.md +++ b/exercises/practice/prime-factors/.docs/instructions.md @@ -10,21 +10,27 @@ Note that 1 is not a prime number. What are the prime factors of 60? -- Our first divisor is 2. 2 goes into 60, leaving 30. +- Our first divisor is 2. + 2 goes into 60, leaving 30. - 2 goes into 30, leaving 15. - - 2 doesn't go cleanly into 15. So let's move on to our next divisor, 3. + - 2 doesn't go cleanly into 15. + So let's move on to our next divisor, 3. - 3 goes cleanly into 15, leaving 5. - - 3 does not go cleanly into 5. The next possible factor is 4. - - 4 does not go cleanly into 5. The next possible factor is 5. + - 3 does not go cleanly into 5. + The next possible factor is 4. + - 4 does not go cleanly into 5. + The next possible factor is 5. - 5 does go cleanly into 5. - We're left only with 1, so now, we're done. -Our successful divisors in that computation represent the list of prime -factors of 60: 2, 2, 3, and 5. +Our successful divisors in that computation represent the list of prime factors of 60: 2, 2, 3, and 5. You can check this yourself: -- 2 _ 2 _ 3 \* 5 -- = 4 \* 15 -- = 60 -- Success! +```text +2 * 2 * 3 * 5 += 4 * 15 += 60 +``` + +Success! diff --git a/exercises/practice/prime-factors/.eslintrc b/exercises/practice/prime-factors/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/prime-factors/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/prime-factors/.gitignore b/exercises/practice/prime-factors/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/prime-factors/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/prime-factors/.meta/config.json b/exercises/practice/prime-factors/.meta/config.json index d01ea7de6f..5791788796 100644 --- a/exercises/practice/prime-factors/.meta/config.json +++ b/exercises/practice/prime-factors/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Compute the prime factors of a given natural number.", - "authors": ["rchavarria"], + "authors": [ + "rchavarria" + ], "contributors": [ "ankorGH", "matthewmorgan", @@ -9,10 +10,23 @@ "xarxziux" ], "files": { - "solution": ["prime-factors.js"], - "test": ["prime-factors.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "prime-factors.js" + ], + "test": [ + "prime-factors.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Compute the prime factors of a given natural number.", "source": "The Prime Factors Kata by Uncle Bob", - "source_url": "http://butunclebob.com/ArticleS.UncleBob.ThePrimeFactorsKata" + "source_url": "https://web.archive.org/web/20221026171801/http://butunclebob.com/ArticleS.UncleBob.ThePrimeFactorsKata", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/prime-factors/.meta/tests.toml b/exercises/practice/prime-factors/.meta/tests.toml index f3f05a3ea5..6f9cc8ceda 100644 --- a/exercises/practice/prime-factors/.meta/tests.toml +++ b/exercises/practice/prime-factors/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [924fc966-a8f5-4288-82f2-6b9224819ccd] description = "no factors" @@ -8,12 +15,27 @@ description = "no factors" [17e30670-b105-4305-af53-ddde182cb6ad] description = "prime number" +[238d57c8-4c12-42ef-af34-ae4929f94789] +description = "another prime number" + [f59b8350-a180-495a-8fb1-1712fbee1158] description = "square of a prime" +[756949d3-3158-4e3d-91f2-c4f9f043ee70] +description = "product of first prime" + [bc8c113f-9580-4516-8669-c5fc29512ceb] description = "cube of a prime" +[7d6a3300-a4cb-4065-bd33-0ced1de6cb44] +description = "product of second prime" + +[073ac0b2-c915-4362-929d-fc45f7b9a9e4] +description = "product of third prime" + +[6e0e4912-7fb6-47f3-a9ad-dbcd79340c75] +description = "product of first and second prime" + [00485cd3-a3fe-4fbe-a64a-a4308fc1f870] description = "product of primes and non-primes" diff --git a/exercises/practice/prime-factors/babel.config.js b/exercises/practice/prime-factors/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/prime-factors/babel.config.js +++ b/exercises/practice/prime-factors/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/prime-factors/eslint.config.mjs b/exercises/practice/prime-factors/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/prime-factors/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/prime-factors/jest.config.js b/exercises/practice/prime-factors/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/prime-factors/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/prime-factors/package.json b/exercises/practice/prime-factors/package.json index 1a530c5617..af8156e22f 100644 --- a/exercises/practice/prime-factors/package.json +++ b/exercises/practice/prime-factors/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/prime-factors" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/prime-factors/prime-factors.js b/exercises/practice/prime-factors/prime-factors.js index 3d0f819c36..75dad73cdf 100644 --- a/exercises/practice/prime-factors/prime-factors.js +++ b/exercises/practice/prime-factors/prime-factors.js @@ -4,5 +4,5 @@ // export const primeFactors = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/prime-factors/prime-factors.spec.js b/exercises/practice/prime-factors/prime-factors.spec.js index be8d7d33b4..b1849a49b6 100644 --- a/exercises/practice/prime-factors/prime-factors.spec.js +++ b/exercises/practice/prime-factors/prime-factors.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { primeFactors } from './prime-factors'; describe('returns prime factors for the given input number', () => { diff --git a/exercises/practice/promises/.eslintrc b/exercises/practice/promises/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/promises/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/promises/.gitignore b/exercises/practice/promises/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/promises/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/promises/.meta/config.json b/exercises/practice/promises/.meta/config.json index dd93ccb77c..4ce553726d 100644 --- a/exercises/practice/promises/.meta/config.json +++ b/exercises/practice/promises/.meta/config.json @@ -1,10 +1,27 @@ { - "blurb": "Practice promises by implementing some common promise functions.", - "authors": ["slaymance"], - "contributors": ["SleeplessByte", "TomPradat"], + "authors": [ + "slaymance" + ], + "contributors": [ + "SleeplessByte", + "TomPradat" + ], "files": { - "solution": ["promises.js"], - "test": ["promises.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "promises.js" + ], + "test": [ + "promises.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] + }, + "blurb": "Practice promises by implementing some common promise functions.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false } } diff --git a/exercises/practice/promises/.meta/proof.ci.js b/exercises/practice/promises/.meta/proof.ci.js index a0b36fe722..9ea7f2d9d2 100644 --- a/exercises/practice/promises/.meta/proof.ci.js +++ b/exercises/practice/promises/.meta/proof.ci.js @@ -10,7 +10,7 @@ export const all = (promises) => { if (promises.length === 0) return Promise.resolve([]); return promises.reduce( async (acc, promise) => (await acc).concat(await promise), - Promise.resolve([]) + Promise.resolve([]), ); }; @@ -20,7 +20,7 @@ export const allSettled = (promises) => { return promises.reduce( async (acc, promise) => (await acc).concat(await promise.catch((err) => err)), - Promise.resolve([]) + Promise.resolve([]), ); }; diff --git a/exercises/practice/promises/babel.config.js b/exercises/practice/promises/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/promises/babel.config.js +++ b/exercises/practice/promises/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/promises/eslint.config.mjs b/exercises/practice/promises/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/promises/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/promises/jest.config.js b/exercises/practice/promises/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/promises/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/promises/package.json b/exercises/practice/promises/package.json index 4d4e06ca86..896211d773 100644 --- a/exercises/practice/promises/package.json +++ b/exercises/practice/promises/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/promises" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/promises/promises.js b/exercises/practice/promises/promises.js index 3e80fdf690..fe4b9204fd 100644 --- a/exercises/practice/promises/promises.js +++ b/exercises/practice/promises/promises.js @@ -1,24 +1,24 @@ // -// This is only a SKELETON file for the 'Pascals Triangle' exercise. It's been provided as a +// This is only a SKELETON file for the 'Promises' exercise. It's been provided as a // convenience to get you started writing code faster. // export const promisify = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; export const all = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; export const allSettled = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; export const race = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; export const any = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/promises/promises.spec.js b/exercises/practice/promises/promises.spec.js index 0d16a92886..1b5c721bf3 100644 --- a/exercises/practice/promises/promises.spec.js +++ b/exercises/practice/promises/promises.spec.js @@ -1,4 +1,5 @@ -import { promisify, all, allSettled, race, any } from './promises'; +import { describe, expect, test, xtest } from '@jest/globals'; +import { all, allSettled, any, promisify, race } from './promises'; describe('promises', () => { const failedCallback = new Error('Failed callback'); @@ -139,20 +140,20 @@ describe('promises', () => { slowestPromise('SLOWEST'), slowerPromise('SLOWER'), fastPromise(FAST), - ]) + ]), ).resolves.toEqual(FAST); }); xtest('resolves with value of the fastest promise even if other slower promises fail', () => { const FAST = 'FAST'; return expect( - race([failedPromise(null), fastPromise(FAST)]) + race([failedPromise(null), fastPromise(FAST)]), ).resolves.toEqual(FAST); }); xtest('rejects if the fastest promise fails even if other slower promises succeed', () => { return expect( - race([slowestPromise('SLOWEST'), failedPromise(null)]) + race([slowestPromise('SLOWEST'), failedPromise(null)]), ).rejects.toEqual(failedCallback); }); }); @@ -184,27 +185,27 @@ describe('promises', () => { slowestPromise('SLOWEST'), slowerPromise('SLOWER'), fastPromise(FAST), - ]) + ]), ).resolves.toEqual(FAST); }); xtest('resolves with value of the fastest successful promise even if slower promises fail', () => { const FAST = 'FAST'; return expect( - any([failedPromise(null), fastPromise(FAST)]) + any([failedPromise(null), fastPromise(FAST)]), ).resolves.toEqual(FAST); }); xtest('resolves with value of fastest successful promise even if faster promises fail', () => { const SLOWEST = 'SLOWEST'; return expect( - any([failedPromise(null), slowestPromise(SLOWEST)]) + any([failedPromise(null), slowestPromise(SLOWEST)]), ).resolves.toEqual(SLOWEST); }); xtest('rejects with array of errors if all promises fail', () => { return expect( - any([failedPromise(null), failedPromise(null)]) + any([failedPromise(null), failedPromise(null)]), ).rejects.toEqual([failedCallback, failedCallback]); }); }); diff --git a/exercises/practice/protein-translation/.docs/instructions.md b/exercises/practice/protein-translation/.docs/instructions.md index 7eaa5603cc..35c953b11f 100644 --- a/exercises/practice/protein-translation/.docs/instructions.md +++ b/exercises/practice/protein-translation/.docs/instructions.md @@ -1,35 +1,17 @@ # Instructions -Translate RNA sequences into proteins. +Your job is to translate RNA sequences into proteins. -RNA can be broken into three nucleotide sequences called codons, and then translated to a polypeptide like so: +RNA strands are made up of three-nucleotide sequences called **codons**. +Each codon translates to an **amino acid**. +When joined together, those amino acids make a protein. -RNA: `"AUGUUUUCU"` => translates to +In the real world, there are 64 codons, which in turn correspond to 20 amino acids. +However, for this exercise, you’ll only use a few of the possible 64. +They are listed below: -Codons: `"AUG", "UUU", "UCU"` -=> which become a polypeptide with the following sequence => - -Protein: `"Methionine", "Phenylalanine", "Serine"` - -There are 64 codons which in turn correspond to 20 amino acids; however, all of the codon sequences and resulting amino acids are not important in this exercise. If it works for one codon, the program should work for all of them. -However, feel free to expand the list in the test suite to include them all. - -There are also three terminating codons (also known as 'STOP' codons); if any of these codons are encountered (by the ribosome), all translation ends and the protein is terminated. - -All subsequent codons after are ignored, like this: - -RNA: `"AUGUUUUCUUAAAUG"` => - -Codons: `"AUG", "UUU", "UCU", "UAA", "AUG"` => - -Protein: `"Methionine", "Phenylalanine", "Serine"` - -Note the stop codon `"UAA"` terminates the translation and the final methionine is not translated into the protein sequence. - -Below are the codons and resulting Amino Acids needed for the exercise. - -| Codon | Protein | -| :----------------- | :------------ | +| Codon | Amino Acid | +| ------------------ | ------------- | | AUG | Methionine | | UUU, UUC | Phenylalanine | | UUA, UUG | Leucine | @@ -39,4 +21,18 @@ Below are the codons and resulting Amino Acids needed for the exercise. | UGG | Tryptophan | | UAA, UAG, UGA | STOP | -Learn more about [protein translation on Wikipedia]() +For example, the RNA string “AUGUUUUCU” has three codons: “AUG”, “UUU” and “UCU”. +These map to Methionine, Phenylalanine, and Serine. + +## “STOP” Codons + +You’ll note from the table above that there are three **“STOP” codons**. +If you encounter any of these codons, ignore the rest of the sequence — the protein is complete. + +For example, “AUGUUUUCUUAAAUG” contains a STOP codon (“UAA”). +Once we reach that point, we stop processing. +We therefore only consider the part before it (i.e. “AUGUUUUCU”), not any further codons after it (i.e. “AUG”). + +Learn more about [protein translation on Wikipedia][protein-translation]. + +[protein-translation]: https://en.wikipedia.org/wiki/Translation_(biology) diff --git a/exercises/practice/protein-translation/.eslintrc b/exercises/practice/protein-translation/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/protein-translation/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/protein-translation/.gitignore b/exercises/practice/protein-translation/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/protein-translation/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/protein-translation/.meta/config.json b/exercises/practice/protein-translation/.meta/config.json index 4ef39e4838..31f3c72c81 100644 --- a/exercises/practice/protein-translation/.meta/config.json +++ b/exercises/practice/protein-translation/.meta/config.json @@ -1,11 +1,31 @@ { - "blurb": "Translate RNA sequences into proteins.", - "authors": ["RobinCsl"], - "contributors": ["ankorGH", "SleeplessByte", "tejasbubane", "WebCu"], + "authors": [ + "RobinCsl" + ], + "contributors": [ + "ankorGH", + "jagdish-15", + "SleeplessByte", + "tejasbubane", + "WebCu" + ], "files": { - "solution": ["protein-translation.js"], - "test": ["protein-translation.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "protein-translation.js" + ], + "test": [ + "protein-translation.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, - "source": "Tyler Long" + "blurb": "Translate RNA sequences into proteins.", + "source": "Tyler Long", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/protein-translation/.meta/tests.toml b/exercises/practice/protein-translation/.meta/tests.toml index 02a54c3446..de680e39ef 100644 --- a/exercises/practice/protein-translation/.meta/tests.toml +++ b/exercises/practice/protein-translation/.meta/tests.toml @@ -1,6 +1,16 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[2c44f7bf-ba20-43f7-a3bf-f2219c0c3f98] +description = "Empty RNA sequence results in no proteins" [96d3d44f-34a2-4db4-84cd-fff523e069be] description = "Methionine RNA sequence" @@ -53,6 +63,12 @@ description = "STOP codon RNA sequence 2" [9c2ad527-ebc9-4ace-808b-2b6447cb54cb] description = "STOP codon RNA sequence 3" +[f4d9d8ee-00a8-47bf-a1e3-1641d4428e54] +description = "Sequence of two protein codons translates into proteins" + +[dd22eef3-b4f1-4ad6-bb0b-27093c090a9d] +description = "Sequence of two different protein codons translates into proteins" + [d0f295df-fb70-425c-946c-ec2ec185388e] description = "Translate RNA strand into correct protein list" @@ -70,3 +86,20 @@ description = "Translation stops if STOP codon in middle of three-codon sequence [2c2a2a60-401f-4a80-b977-e0715b23b93d] description = "Translation stops if STOP codon in middle of six-codon sequence" + +[f6f92714-769f-4187-9524-e353e8a41a80] +description = "Sequence of two non-STOP codons does not translate to a STOP codon" + +[1e75ea2a-f907-4994-ae5c-118632a1cb0f] +description = "Non-existing codon can't translate" +include = false + +[9eac93f3-627a-4c90-8653-6d0a0595bc6f] +description = "Unknown amino acids, not part of a codon, can't translate" +reimplements = "1e75ea2a-f907-4994-ae5c-118632a1cb0f" + +[9d73899f-e68e-4291-b1e2-7bf87c00f024] +description = "Incomplete RNA sequence can't translate" + +[43945cf7-9968-402d-ab9f-b8a28750b050] +description = "Incomplete RNA sequence can translate if valid until a STOP codon" diff --git a/exercises/practice/protein-translation/babel.config.js b/exercises/practice/protein-translation/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/protein-translation/babel.config.js +++ b/exercises/practice/protein-translation/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/protein-translation/eslint.config.mjs b/exercises/practice/protein-translation/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/protein-translation/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/protein-translation/jest.config.js b/exercises/practice/protein-translation/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/protein-translation/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/protein-translation/package.json b/exercises/practice/protein-translation/package.json index 270e10f963..e722b47146 100644 --- a/exercises/practice/protein-translation/package.json +++ b/exercises/practice/protein-translation/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/protein-translation" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/protein-translation/protein-translation.js b/exercises/practice/protein-translation/protein-translation.js index c2686a6dad..5e2535a2dc 100644 --- a/exercises/practice/protein-translation/protein-translation.js +++ b/exercises/practice/protein-translation/protein-translation.js @@ -4,5 +4,5 @@ // export const translate = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/protein-translation/protein-translation.spec.js b/exercises/practice/protein-translation/protein-translation.spec.js index 79f3972897..63359ec9ca 100644 --- a/exercises/practice/protein-translation/protein-translation.spec.js +++ b/exercises/practice/protein-translation/protein-translation.spec.js @@ -1,36 +1,69 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { translate } from './protein-translation'; describe('ProteinTranslation', () => { - test('Empty RNA has no proteins', () => { + test('Empty RNA sequence results in no proteins', () => { expect(translate()).toEqual([]); }); describe('Single codons', () => { - const mapping = [ - ['Methionine', ['AUG']], - ['Phenylalanine', ['UUU', 'UUC']], - ['Leucine', ['UUA', 'UUG']], - ['Serine', ['UCU', 'UCC', 'UCA', 'UCG']], - ['Tyrosine', ['UAU', 'UAC']], - ['Cysteine', ['UGU', 'UGC']], - ['Tryptophan', ['UGG']], - ]; - - mapping.forEach(([protein, codons]) => { - codons.forEach((codon, index) => { - const seq = index + 1; - xtest(`${protein} RNA sequence ${seq} translates into ${protein}`, () => { - expect(translate(codon)).toEqual([protein]); - }); - }); - }); - - const stopCodons = ['UAA', 'UAG', 'UGA']; - - stopCodons.forEach((codon, index) => { - xtest(`STOP codon RNA sequence ${index + 1}`, () => { - expect(translate(codon)).toEqual([]); - }); + xtest('Methionine RNA sequence', () => { + expect(translate('AUG')).toEqual(['Methionine']); + }); + + xtest('Phenylalanine RNA sequence 1', () => { + expect(translate('UUU')).toEqual(['Phenylalanine']); + }); + xtest('Phenylalanine RNA sequence 2', () => { + expect(translate('UUC')).toEqual(['Phenylalanine']); + }); + + xtest('Leucine RNA sequence 1', () => { + expect(translate('UUA')).toEqual(['Leucine']); + }); + xtest('Leucine RNA sequence 2', () => { + expect(translate('UUG')).toEqual(['Leucine']); + }); + + xtest('Serine RNA sequence 1', () => { + expect(translate('UCU')).toEqual(['Serine']); + }); + xtest('Serine RNA sequence 2', () => { + expect(translate('UCC')).toEqual(['Serine']); + }); + xtest('Serine RNA sequence 3', () => { + expect(translate('UCA')).toEqual(['Serine']); + }); + xtest('Serine RNA sequence 4', () => { + expect(translate('UCG')).toEqual(['Serine']); + }); + + xtest('Tyrosine RNA sequence 1', () => { + expect(translate('UAU')).toEqual(['Tyrosine']); + }); + xtest('Tyrosine RNA sequence 2', () => { + expect(translate('UAC')).toEqual(['Tyrosine']); + }); + + xtest('Cysteine RNA sequence 1', () => { + expect(translate('UGU')).toEqual(['Cysteine']); + }); + xtest('Cysteine RNA sequence 2', () => { + expect(translate('UGC')).toEqual(['Cysteine']); + }); + + xtest('Tryptophan RNA sequence', () => { + expect(translate('UGG')).toEqual(['Tryptophan']); + }); + + xtest('STOP codon RNA sequence 1', () => { + expect(translate('UAA')).toEqual([]); + }); + xtest('STOP codon RNA sequence 2', () => { + expect(translate('UAG')).toEqual([]); + }); + xtest('STOP codon RNA sequence 3', () => { + expect(translate('UGA')).toEqual([]); }); }); @@ -70,13 +103,13 @@ describe('ProteinTranslation', () => { 'Tyrosine', ]); }); - }); - describe('Unexpected strands', () => { - xtest("Non-existing codon can't translate", () => { - expect(() => translate('AAA')).toThrow(new Error('Invalid codon')); + xtest('Sequence of two non-STOP codons does not translate to a STOP codon', () => { + expect(translate('AUGAUG')).toEqual(['Methionine', 'Methionine']); }); + }); + describe('Unexpected strands', () => { xtest("Unknown amino acids, not part of a codon, can't translate", () => { expect(() => translate('XYZ')).toThrow(new Error('Invalid codon')); }); diff --git a/exercises/practice/proverb/.docs/instructions.md b/exercises/practice/proverb/.docs/instructions.md index cf3b4c8b28..f6fb859325 100644 --- a/exercises/practice/proverb/.docs/instructions.md +++ b/exercises/practice/proverb/.docs/instructions.md @@ -2,7 +2,8 @@ For want of a horseshoe nail, a kingdom was lost, or so the saying goes. -Given a list of inputs, generate the relevant proverb. For example, given the list `["nail", "shoe", "horse", "rider", "message", "battle", "kingdom"]`, you will output the full text of this proverbial rhyme: +Given a list of inputs, generate the relevant proverb. +For example, given the list `["nail", "shoe", "horse", "rider", "message", "battle", "kingdom"]`, you will output the full text of this proverbial rhyme: ```text For want of a nail the shoe was lost. @@ -14,4 +15,5 @@ For want of a battle the kingdom was lost. And all for the want of a nail. ``` -Note that the list of inputs may vary; your solution should be able to handle lists of arbitrary length and content. No line of the output text should be a static, unchanging string; all should vary according to the input given. +Note that the list of inputs may vary; your solution should be able to handle lists of arbitrary length and content. +No line of the output text should be a static, unchanging string; all should vary according to the input given. diff --git a/exercises/practice/proverb/.eslintrc b/exercises/practice/proverb/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/proverb/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/proverb/.gitignore b/exercises/practice/proverb/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/proverb/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/proverb/.meta/config.json b/exercises/practice/proverb/.meta/config.json index 836264938c..c3c4818b97 100644 --- a/exercises/practice/proverb/.meta/config.json +++ b/exercises/practice/proverb/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "For want of a horseshoe nail, a kingdom was lost, or so the saying goes. Output the full text of this proverbial rhyme.", - "authors": ["skabbass1"], + "authors": [ + "skabbass1" + ], "contributors": [ "ankorGH", "serixscorpio", @@ -9,10 +10,23 @@ "xarxziux" ], "files": { - "solution": ["proverb.js"], - "test": ["proverb.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "proverb.js" + ], + "test": [ + "proverb.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "For want of a horseshoe nail, a kingdom was lost, or so the saying goes. Output the full text of this proverbial rhyme.", "source": "Wikipedia", - "source_url": "http://en.wikipedia.org/wiki/For_Want_of_a_Nail" + "source_url": "https://en.wikipedia.org/wiki/For_Want_of_a_Nail", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/proverb/.meta/proof.ci.js b/exercises/practice/proverb/.meta/proof.ci.js index 4c04a3c32e..7e9da596f5 100644 --- a/exercises/practice/proverb/.meta/proof.ci.js +++ b/exercises/practice/proverb/.meta/proof.ci.js @@ -18,7 +18,7 @@ export const proverb = (...args) => { const allExceptLastArg = args.slice(0, -1); const chainOfEvents = allExceptLastArg.map( - (arg, index) => `For want of a ${arg} the ${args[index + 1]} was lost.` + (arg, index) => `For want of a ${arg} the ${args[index + 1]} was lost.`, ); const qualifier = options.qualifier diff --git a/exercises/practice/proverb/babel.config.js b/exercises/practice/proverb/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/proverb/babel.config.js +++ b/exercises/practice/proverb/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/proverb/eslint.config.mjs b/exercises/practice/proverb/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/proverb/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/proverb/jest.config.js b/exercises/practice/proverb/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/proverb/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/proverb/package.json b/exercises/practice/proverb/package.json index d9fa02ee30..dabff2282b 100644 --- a/exercises/practice/proverb/package.json +++ b/exercises/practice/proverb/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/proverb" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/proverb/proverb.js b/exercises/practice/proverb/proverb.js index 8bb9a4b9fc..5ce893625b 100644 --- a/exercises/practice/proverb/proverb.js +++ b/exercises/practice/proverb/proverb.js @@ -4,5 +4,5 @@ // export const proverb = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/proverb/proverb.spec.js b/exercises/practice/proverb/proverb.spec.js index 82367e5f92..2e33118231 100644 --- a/exercises/practice/proverb/proverb.spec.js +++ b/exercises/practice/proverb/proverb.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { proverb } from './proverb'; describe('Proverb', () => { @@ -18,7 +19,7 @@ describe('Proverb', () => { expect(result).toEqual( `For want of a nail the shoe was lost. -And all for the want of a nail.` +And all for the want of a nail.`, ); }); @@ -28,7 +29,7 @@ And all for the want of a nail.` expect(result).toEqual( `For want of a nail the shoe was lost. For want of a shoe the horse was lost. -And all for the want of a nail.` +And all for the want of a nail.`, ); }); @@ -40,7 +41,7 @@ And all for the want of a nail.` 'rider', 'message', 'battle', - 'kingdom' + 'kingdom', ); expect(result).toEqual( @@ -50,7 +51,7 @@ For want of a horse the rider was lost. For want of a rider the message was lost. For want of a message the battle was lost. For want of a battle the kingdom was lost. -And all for the want of a nail.` +And all for the want of a nail.`, ); }); @@ -65,7 +66,7 @@ And all for the want of a nail.` `For want of a pin the gun was lost. For want of a gun the soldier was lost. For want of a soldier the battle was lost. -And all for the want of a pin.` +And all for the want of a pin.`, ); }); @@ -78,7 +79,7 @@ And all for the want of a pin.` 'message', 'battle', 'kingdom', - { qualifier: 'horseshoe' } + { qualifier: 'horseshoe' }, ); expect(result).toEqual( @@ -88,7 +89,7 @@ For want of a horse the rider was lost. For want of a rider the message was lost. For want of a message the battle was lost. For want of a battle the kingdom was lost. -And all for the want of a horseshoe nail.` +And all for the want of a horseshoe nail.`, ); }); }); diff --git a/exercises/practice/pythagorean-triplet/.docs/instructions.append.md b/exercises/practice/pythagorean-triplet/.docs/instructions.append.md index 32ca5089a1..9fc66f98fc 100644 --- a/exercises/practice/pythagorean-triplet/.docs/instructions.append.md +++ b/exercises/practice/pythagorean-triplet/.docs/instructions.append.md @@ -1,3 +1,10 @@ # Instructions append By default, only `sum` is given to the `triplets` function, but it may optionally also receive `minFactor` and/or `maxFactor`. When these are given, make sure _each_ factor of the triplet is at least `minFactor` and at most `maxFactor`. + + +~~~exercism/advanced +If you're solving this using the CLI, there's a test case involving large numbers that's currently skipped to avoid timeouts in our test runner. +You can enable it if you want by removing the `.skip`, just be aware that it may take a while to run. +~~~ + diff --git a/exercises/practice/pythagorean-triplet/.docs/instructions.md b/exercises/practice/pythagorean-triplet/.docs/instructions.md index 395ff6a550..ced833d7a5 100644 --- a/exercises/practice/pythagorean-triplet/.docs/instructions.md +++ b/exercises/practice/pythagorean-triplet/.docs/instructions.md @@ -1,10 +1,9 @@ -# Instructions +# Description -A Pythagorean triplet is a set of three natural numbers, {a, b, c}, for -which, +A Pythagorean triplet is a set of three natural numbers, {a, b, c}, for which, ```text -a**2 + b**2 = c**2 +a² + b² = c² ``` and such that, @@ -16,7 +15,7 @@ a < b < c For example, ```text -3**2 + 4**2 = 9 + 16 = 25 = 5**2. +3² + 4² = 5². ``` Given an input integer N, find all Pythagorean triplets for which `a + b + c = N`. diff --git a/exercises/practice/pythagorean-triplet/.docs/introduction.md b/exercises/practice/pythagorean-triplet/.docs/introduction.md new file mode 100644 index 0000000000..3453c6ed48 --- /dev/null +++ b/exercises/practice/pythagorean-triplet/.docs/introduction.md @@ -0,0 +1,19 @@ +# Introduction + +You are an accomplished problem-solver, known for your ability to tackle the most challenging mathematical puzzles. +One evening, you receive an urgent letter from an inventor called the Triangle Tinkerer, who is working on a groundbreaking new project. +The letter reads: + +> Dear Mathematician, +> +> I need your help. +> I am designing a device that relies on the unique properties of Pythagorean triplets — sets of three integers that satisfy the equation a² + b² = c². +> This device will revolutionize navigation, but for it to work, I must program it with every possible triplet where the sum of a, b, and c equals a specific number, N. +> Calculating these triplets by hand would take me years, but I hear you are more than up to the task. +> +> Time is of the essence. +> The future of my invention — and perhaps even the future of mathematical innovation — rests on your ability to solve this problem. + +Motivated by the importance of the task, you set out to find all Pythagorean triplets that satisfy the condition. +Your work could have far-reaching implications, unlocking new possibilities in science and engineering. +Can you rise to the challenge and make history? diff --git a/exercises/practice/pythagorean-triplet/.eslintrc b/exercises/practice/pythagorean-triplet/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/pythagorean-triplet/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/pythagorean-triplet/.gitignore b/exercises/practice/pythagorean-triplet/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/pythagorean-triplet/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/pythagorean-triplet/.meta/config.json b/exercises/practice/pythagorean-triplet/.meta/config.json index f66915c239..6a0220f678 100644 --- a/exercises/practice/pythagorean-triplet/.meta/config.json +++ b/exercises/practice/pythagorean-triplet/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "There exists exactly one Pythagorean triplet for which a + b + c = 1000. Find the product a * b * c.", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "ankorGH", "rchavarria", @@ -10,10 +11,23 @@ "xarxziux" ], "files": { - "solution": ["pythagorean-triplet.js"], - "test": ["pythagorean-triplet.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "pythagorean-triplet.js" + ], + "test": [ + "pythagorean-triplet.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, - "source": "Problem 9 at Project Euler", - "source_url": "http://projecteuler.net/problem=9" + "blurb": "Given an integer N, find all Pythagorean triplets for which a + b + c = N.", + "source": "A variation of Problem 9 from Project Euler", + "source_url": "https://projecteuler.net/problem=9", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": true + } } diff --git a/exercises/practice/pythagorean-triplet/babel.config.js b/exercises/practice/pythagorean-triplet/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/pythagorean-triplet/babel.config.js +++ b/exercises/practice/pythagorean-triplet/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/pythagorean-triplet/eslint.config.mjs b/exercises/practice/pythagorean-triplet/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/pythagorean-triplet/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/pythagorean-triplet/jest.config.js b/exercises/practice/pythagorean-triplet/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/pythagorean-triplet/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/pythagorean-triplet/package.json b/exercises/practice/pythagorean-triplet/package.json index 53992d7a16..62b4c63ce9 100644 --- a/exercises/practice/pythagorean-triplet/package.json +++ b/exercises/practice/pythagorean-triplet/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/pythagorean-triplet" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/pythagorean-triplet/pythagorean-triplet.js b/exercises/practice/pythagorean-triplet/pythagorean-triplet.js index c382aaac94..afaadf1333 100644 --- a/exercises/practice/pythagorean-triplet/pythagorean-triplet.js +++ b/exercises/practice/pythagorean-triplet/pythagorean-triplet.js @@ -4,15 +4,15 @@ // export function triplets({ minFactor, maxFactor, sum }) { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } class Triplet { constructor(a, b, c) { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } toArray() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/pythagorean-triplet/pythagorean-triplet.spec.js b/exercises/practice/pythagorean-triplet/pythagorean-triplet.spec.js index 58ae766d8a..0db403e95b 100644 --- a/exercises/practice/pythagorean-triplet/pythagorean-triplet.spec.js +++ b/exercises/practice/pythagorean-triplet/pythagorean-triplet.spec.js @@ -1,8 +1,9 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { triplets } from './pythagorean-triplet'; function tripletsWithSum(sum, options = {}) { return triplets({ ...options, sum }).map((triplet) => - triplet.toArray().sort((a, b) => a - b) + triplet.toArray().sort((a, b) => a - b), ); } @@ -58,13 +59,19 @@ describe('Triplet', () => { ]); }); - test.skip('triplets for large number', () => { - expect(tripletsWithSum(30000)).toEqual([ - [1200, 14375, 14425], - [1875, 14000, 14125], - [5000, 12000, 13000], - [6000, 11250, 12750], - [7500, 10000, 12500], - ]); - }); + // This test doesn't run on our online test runner because it will time-out + // with most implementations. It's up to you to test your solution locally. + test.skip( + 'triplets for large number', + () => { + expect(tripletsWithSum(30000)).toEqual([ + [1200, 14375, 14425], + [1875, 14000, 14125], + [5000, 12000, 13000], + [6000, 11250, 12750], + [7500, 10000, 12500], + ]); + }, + 20 * 1000, + ); }); diff --git a/exercises/practice/queen-attack/.docs/instructions.md b/exercises/practice/queen-attack/.docs/instructions.md index 1f8e61a684..97f22a0aee 100644 --- a/exercises/practice/queen-attack/.docs/instructions.md +++ b/exercises/practice/queen-attack/.docs/instructions.md @@ -1,27 +1,21 @@ # Instructions -Given the position of two queens on a chess board, indicate whether or not they -are positioned so that they can attack each other. +Given the position of two queens on a chess board, indicate whether or not they are positioned so that they can attack each other. -In the game of chess, a queen can attack pieces which are on the same -row, column, or diagonal. +In the game of chess, a queen can attack pieces which are on the same row, column, or diagonal. A chessboard can be represented by an 8 by 8 array. -So if you're told the white queen is at (2, 3) and the black queen at -(5, 6), then you'd know you've got a set-up like so: - -```text -_ _ _ _ _ _ _ _ -_ _ _ _ _ _ _ _ -_ _ _ W _ _ _ _ -_ _ _ _ _ _ _ _ -_ _ _ _ _ _ _ _ -_ _ _ _ _ _ B _ -_ _ _ _ _ _ _ _ -_ _ _ _ _ _ _ _ -``` - -You'd also be able to answer whether the queens can attack each other. -In this case, that answer would be yes, they can, because both pieces -share a diagonal. +So if you are told the white queen is at `c5` (zero-indexed at column 2, row 3) and the black queen at `f2` (zero-indexed at column 5, row 6), then you know that the set-up is like so: + +![A chess board with two queens. Arrows emanating from the queen at c5 indicate possible directions of capture along file, rank and diagonal.](https://assets.exercism.org/images/exercises/queen-attack/queen-capture.svg) + +You are also able to answer whether the queens can attack each other. +In this case, that answer would be yes, they can, because both pieces share a diagonal. + +## Credit + +The chessboard image was made by [habere-et-dispertire][habere-et-dispertire] using LaTeX and the [chessboard package][chessboard-package] by Ulrike Fischer. + +[habere-et-dispertire]: https://exercism.org/profiles/habere-et-dispertire +[chessboard-package]: https://github.com/u-fischer/chessboard diff --git a/exercises/practice/queen-attack/.eslintrc b/exercises/practice/queen-attack/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/queen-attack/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/queen-attack/.gitignore b/exercises/practice/queen-attack/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/queen-attack/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/queen-attack/.meta/config.json b/exercises/practice/queen-attack/.meta/config.json index e9fa629c02..63c1563dd2 100644 --- a/exercises/practice/queen-attack/.meta/config.json +++ b/exercises/practice/queen-attack/.meta/config.json @@ -1,12 +1,14 @@ { - "blurb": "Given the position of two queens on a chess board, indicate whether or not they are positioned so that they can attack each other.", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "abhnvgupta", "alexashley", "BoDaly", "ErikSchierboom", "IndelicateArgot", + "jagdish-15", "javaeeeee", "rchavarria", "ryanplusplus", @@ -14,10 +16,23 @@ "smb26" ], "files": { - "solution": ["queen-attack.js"], - "test": ["queen-attack.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "queen-attack.js" + ], + "test": [ + "queen-attack.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Given the position of two queens on a chess board, indicate whether or not they are positioned so that they can attack each other.", "source": "J Dalbey's Programming Practice problems", - "source_url": "http://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html" + "source_url": "https://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/queen-attack/.meta/proof.ci.js b/exercises/practice/queen-attack/.meta/proof.ci.js index 2ffa5aeff2..ac7bb0ae85 100644 --- a/exercises/practice/queen-attack/.meta/proof.ci.js +++ b/exercises/practice/queen-attack/.meta/proof.ci.js @@ -66,7 +66,7 @@ export class QueenAttack { toString() { return Array.from({ length: H }, (_, row) => - this.board.slice(row * H, row * H + W).join(' ') + this.board.slice(row * H, row * H + W).join(' '), ).join('\n'); } } diff --git a/exercises/practice/queen-attack/.meta/tests.toml b/exercises/practice/queen-attack/.meta/tests.toml index 8a2f794c0e..e0624123d7 100644 --- a/exercises/practice/queen-attack/.meta/tests.toml +++ b/exercises/practice/queen-attack/.meta/tests.toml @@ -1,39 +1,49 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [3ac4f735-d36c-44c4-a3e2-316f79704203] -description = "queen with a valid position" +description = "Test creation of Queens with valid and invalid positions -> queen with a valid position" [4e812d5d-b974-4e38-9a6b-8e0492bfa7be] -description = "queen must have positive row" +description = "Test creation of Queens with valid and invalid positions -> queen must have positive row" [f07b7536-b66b-4f08-beb9-4d70d891d5c8] -description = "queen must have row on board" +description = "Test creation of Queens with valid and invalid positions -> queen must have row on board" [15a10794-36d9-4907-ae6b-e5a0d4c54ebe] -description = "queen must have positive column" +description = "Test creation of Queens with valid and invalid positions -> queen must have positive column" [6907762d-0e8a-4c38-87fb-12f2f65f0ce4] -description = "queen must have column on board" +description = "Test creation of Queens with valid and invalid positions -> queen must have column on board" [33ae4113-d237-42ee-bac1-e1e699c0c007] -description = "can not attack" +description = "Test the ability of one queen to attack another -> cannot attack" [eaa65540-ea7c-4152-8c21-003c7a68c914] -description = "can attack on same row" +description = "Test the ability of one queen to attack another -> can attack on same row" [bae6f609-2c0e-4154-af71-af82b7c31cea] -description = "can attack on same column" +description = "Test the ability of one queen to attack another -> can attack on same column" [0e1b4139-b90d-4562-bd58-dfa04f1746c7] -description = "can attack on first diagonal" +description = "Test the ability of one queen to attack another -> can attack on first diagonal" [ff9b7ed4-e4b6-401b-8d16-bc894d6d3dcd] -description = "can attack on second diagonal" +description = "Test the ability of one queen to attack another -> can attack on second diagonal" [0a71e605-6e28-4cc2-aa47-d20a2e71037a] -description = "can attack on third diagonal" +description = "Test the ability of one queen to attack another -> can attack on third diagonal" [0790b588-ae73-4f1f-a968-dd0b34f45f86] -description = "can attack on fourth diagonal" +description = "Test the ability of one queen to attack another -> can attack on fourth diagonal" + +[543f8fd4-2597-4aad-8d77-cbdab63619f8] +description = "Test the ability of one queen to attack another -> cannot attack if falling diagonals are only the same when reflected across the longest falling diagonal" diff --git a/exercises/practice/queen-attack/babel.config.js b/exercises/practice/queen-attack/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/queen-attack/babel.config.js +++ b/exercises/practice/queen-attack/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/queen-attack/eslint.config.mjs b/exercises/practice/queen-attack/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/queen-attack/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/queen-attack/jest.config.js b/exercises/practice/queen-attack/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/queen-attack/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/queen-attack/package.json b/exercises/practice/queen-attack/package.json index e2fe7e2d85..019162dbd3 100644 --- a/exercises/practice/queen-attack/package.json +++ b/exercises/practice/queen-attack/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/queen-attack" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/queen-attack/queen-attack.js b/exercises/practice/queen-attack/queen-attack.js index 16f26a8932..f05b6044e5 100644 --- a/exercises/practice/queen-attack/queen-attack.js +++ b/exercises/practice/queen-attack/queen-attack.js @@ -8,14 +8,14 @@ export class QueenAttack { black: [blackRow, blackColumn] = [], white: [whiteRow, whiteColumn] = [], } = {}) { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } toString() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get canAttack() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/queen-attack/queen-attack.spec.js b/exercises/practice/queen-attack/queen-attack.spec.js index 8922bcb91e..97794800b4 100644 --- a/exercises/practice/queen-attack/queen-attack.spec.js +++ b/exercises/practice/queen-attack/queen-attack.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { QueenAttack } from './queen-attack'; describe('Queens', () => { @@ -39,50 +40,55 @@ describe('Queens', () => { }); describe('Test the ability of one queen to attack another', () => { - xtest('queens cannot attack', () => { + xtest('cannot attack', () => { const queens = new QueenAttack({ white: [2, 4], black: [6, 6] }); expect(queens.canAttack).toEqual(false); }); - xtest('queens can attack when they are on the same row', () => { + xtest('can attack on same row', () => { const queens = new QueenAttack({ white: [2, 4], black: [2, 6] }); expect(queens.canAttack).toEqual(true); }); - xtest('queens can attack when they are on the same column', () => { + xtest('can attack on same column', () => { const queens = new QueenAttack({ white: [4, 5], black: [2, 5] }); expect(queens.canAttack).toEqual(true); }); - xtest('queens can attack diagonally', () => { + xtest('can attack on first diagonal', () => { const queens = new QueenAttack({ white: [2, 2], black: [0, 4] }); expect(queens.canAttack).toEqual(true); }); - xtest('queens can attack another diagonally', () => { + xtest('can attack on second diagonal', () => { const queens = new QueenAttack({ white: [2, 2], black: [3, 1] }); expect(queens.canAttack).toEqual(true); }); - xtest('queens can attack yet another diagonally', () => { + xtest('can attack on third diagonal', () => { const queens = new QueenAttack({ white: [2, 2], black: [1, 1] }); expect(queens.canAttack).toEqual(true); }); - xtest('queens can attack diagonally, really', () => { + xtest('can attack on fourth diagonal', () => { const queens = new QueenAttack({ white: [1, 7], black: [0, 6] }); expect(queens.canAttack).toEqual(true); }); - xtest('queens can attack on a north-east/south-west diagonal', () => { + xtest('can attack on fifth diagonal', () => { const queens = new QueenAttack({ white: [7, 0], black: [0, 7] }); expect(queens.canAttack).toEqual(true); }); - xtest('queens can attack on another ne/sw diagonal', () => { + xtest('can attack on sixth diagonal', () => { const queens = new QueenAttack({ white: [2, 6], black: [5, 3] }); expect(queens.canAttack).toEqual(true); }); + + xtest('cannot attack if falling diagonals are only the same when reflected across the longest falling diagonal', () => { + const queens = new QueenAttack({ white: [4, 1], black: [2, 5] }); + expect(queens.canAttack).toEqual(false); + }); }); describe('Test the board visualisation', () => { diff --git a/exercises/practice/rail-fence-cipher/.docs/instructions.md b/exercises/practice/rail-fence-cipher/.docs/instructions.md index 647f21bad2..e311de6cdf 100644 --- a/exercises/practice/rail-fence-cipher/.docs/instructions.md +++ b/exercises/practice/rail-fence-cipher/.docs/instructions.md @@ -1,16 +1,14 @@ -# Description +# Instructions Implement encoding and decoding for the rail fence cipher. -The Rail Fence cipher is a form of transposition cipher that gets its name from -the way in which it's encoded. It was already used by the ancient Greeks. +The Rail Fence cipher is a form of transposition cipher that gets its name from the way in which it's encoded. +It was already used by the ancient Greeks. -In the Rail Fence cipher, the message is written downwards on successive "rails" -of an imaginary fence, then moving up when we get to the bottom (like a zig-zag). +In the Rail Fence cipher, the message is written downwards on successive "rails" of an imaginary fence, then moving up when we get to the bottom (like a zig-zag). Finally the message is then read off in rows. -For example, using three "rails" and the message "WE ARE DISCOVERED FLEE AT ONCE", -the cipherer writes out: +For example, using three "rails" and the message "WE ARE DISCOVERED FLEE AT ONCE", the cipherer writes out: ```text W . . . E . . . C . . . R . . . L . . . T . . . E diff --git a/exercises/practice/rail-fence-cipher/.eslintrc b/exercises/practice/rail-fence-cipher/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/rail-fence-cipher/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/rail-fence-cipher/.gitignore b/exercises/practice/rail-fence-cipher/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/rail-fence-cipher/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/rail-fence-cipher/.meta/config.json b/exercises/practice/rail-fence-cipher/.meta/config.json index 4875594b85..859ed66388 100644 --- a/exercises/practice/rail-fence-cipher/.meta/config.json +++ b/exercises/practice/rail-fence-cipher/.meta/config.json @@ -1,12 +1,25 @@ { - "blurb": "Implement encoding and decoding for the rail fence cipher.", - "authors": ["lpizzinidev"], - "contributors": [], + "authors": [ + "lpizzinidev" + ], "files": { - "solution": ["rail-fence-cipher.js"], - "test": ["rail-fence-cipher.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "rail-fence-cipher.js" + ], + "test": [ + "rail-fence-cipher.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Implement encoding and decoding for the rail fence cipher.", "source": "Wikipedia", - "source_url": "https://en.wikipedia.org/wiki/Transposition_cipher#Rail_Fence_cipher" + "source_url": "https://en.wikipedia.org/wiki/Transposition_cipher#Rail_Fence_cipher", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/rail-fence-cipher/.meta/proof.ci.js b/exercises/practice/rail-fence-cipher/.meta/proof.ci.js index 7ace51c83c..e57bb0e53b 100644 --- a/exercises/practice/rail-fence-cipher/.meta/proof.ci.js +++ b/exercises/practice/rail-fence-cipher/.meta/proof.ci.js @@ -29,7 +29,7 @@ export const decode = (msg, rails) => { }); return cycles.reduce( (str, cycle, index) => str + stringRails[cycle][index], - '' + '', ); }; diff --git a/exercises/practice/rail-fence-cipher/babel.config.js b/exercises/practice/rail-fence-cipher/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/rail-fence-cipher/babel.config.js +++ b/exercises/practice/rail-fence-cipher/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/rail-fence-cipher/eslint.config.mjs b/exercises/practice/rail-fence-cipher/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/rail-fence-cipher/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/rail-fence-cipher/jest.config.js b/exercises/practice/rail-fence-cipher/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/rail-fence-cipher/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/rail-fence-cipher/package.json b/exercises/practice/rail-fence-cipher/package.json index 89266c1a74..cbc4dda490 100644 --- a/exercises/practice/rail-fence-cipher/package.json +++ b/exercises/practice/rail-fence-cipher/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/rail-fence-cipher" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/rail-fence-cipher/rail-fence-cipher.js b/exercises/practice/rail-fence-cipher/rail-fence-cipher.js index 6020b98750..2ebf84b7ee 100644 --- a/exercises/practice/rail-fence-cipher/rail-fence-cipher.js +++ b/exercises/practice/rail-fence-cipher/rail-fence-cipher.js @@ -4,9 +4,9 @@ // export const encode = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; export const decode = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/rail-fence-cipher/rail-fence-cipher.spec.js b/exercises/practice/rail-fence-cipher/rail-fence-cipher.spec.js index deb8928888..00e9fb378b 100644 --- a/exercises/practice/rail-fence-cipher/rail-fence-cipher.spec.js +++ b/exercises/practice/rail-fence-cipher/rail-fence-cipher.spec.js @@ -1,4 +1,5 @@ -import { encode, decode } from './rail-fence-cipher'; +import { describe, expect, test, xtest } from '@jest/globals'; +import { decode, encode } from './rail-fence-cipher'; describe('Rail Fence Cipher', () => { describe('encode', () => { @@ -6,25 +7,25 @@ describe('Rail Fence Cipher', () => { const fence = encode('XOXOXOXOXOXOXOXOXO', 2); expect(fence).toEqual('XXXXXXXXXOOOOOOOOO'); }); - test('encode with three rails', () => { + xtest('encode with three rails', () => { const fence = encode('WEAREDISCOVEREDFLEEATONCE', 3); expect(fence).toEqual('WECRLTEERDSOEEFEAOCAIVDEN'); }); - test('encode with ending in the middle', () => { + xtest('encode with ending in the middle', () => { const fence = encode('EXERCISES', 4); expect(fence).toEqual('ESXIEECSR'); }); }); describe('decode', () => { - test('decode with three rails', () => { + xtest('decode with three rails', () => { const fence = decode('TEITELHDVLSNHDTISEIIEA', 3); expect(fence).toEqual('THEDEVILISINTHEDETAILS'); }); - test('decode with five rails', () => { + xtest('decode with five rails', () => { const fence = decode('EIEXMSMESAORIWSCE', 5); expect(fence).toEqual('EXERCISMISAWESOME'); }); - test('decode with six rails', () => { + xtest('decode with six rails', () => { const encodedString = '133714114238148966225439541018335470986172518171757571896261'; const fence = decode(encodedString, 6); diff --git a/exercises/practice/raindrops/.docs/instructions.md b/exercises/practice/raindrops/.docs/instructions.md index a78585df2e..df64410751 100644 --- a/exercises/practice/raindrops/.docs/instructions.md +++ b/exercises/practice/raindrops/.docs/instructions.md @@ -1,16 +1,24 @@ # Instructions -Your task is to convert a number into a string that contains raindrop sounds corresponding to certain potential factors. A factor is a number that evenly divides into another number, leaving no remainder. The simplest way to test if a one number is a factor of another is to use the [modulo operation](https://en.wikipedia.org/wiki/Modulo_operation). +Your task is to convert a number into its corresponding raindrop sounds. -The rules of `raindrops` are that if a given number: +If a given number: -- has 3 as a factor, add 'Pling' to the result. -- has 5 as a factor, add 'Plang' to the result. -- has 7 as a factor, add 'Plong' to the result. -- _does not_ have any of 3, 5, or 7 as a factor, the result should be the digits of the number. +- is divisible by 3, add "Pling" to the result. +- is divisible by 5, add "Plang" to the result. +- is divisible by 7, add "Plong" to the result. +- **is not** divisible by 3, 5, or 7, the result should be the number as a string. ## Examples -- 28 has 7 as a factor, but not 3 or 5, so the result would be "Plong". -- 30 has both 3 and 5 as factors, but not 7, so the result would be "PlingPlang". -- 34 is not factored by 3, 5, or 7, so the result would be "34". +- 28 is divisible by 7, but not 3 or 5, so the result would be `"Plong"`. +- 30 is divisible by 3 and 5, but not 7, so the result would be `"PlingPlang"`. +- 34 is not divisible by 3, 5, or 7, so the result would be `"34"`. + +~~~~exercism/note +A common way to test if one number is evenly divisible by another is to compare the [remainder][remainder] or [modulus][modulo] to zero. +Most languages provide operators or functions for one (or both) of these. + +[remainder]: https://exercism.org/docs/programming/operators/remainder +[modulo]: https://en.wikipedia.org/wiki/Modulo_operation +~~~~ diff --git a/exercises/practice/raindrops/.docs/introduction.md b/exercises/practice/raindrops/.docs/introduction.md new file mode 100644 index 0000000000..ba12100f3b --- /dev/null +++ b/exercises/practice/raindrops/.docs/introduction.md @@ -0,0 +1,3 @@ +# Introduction + +Raindrops is a slightly more complex version of the FizzBuzz challenge, a classic interview question. diff --git a/exercises/practice/raindrops/.eslintrc b/exercises/practice/raindrops/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/raindrops/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/raindrops/.gitignore b/exercises/practice/raindrops/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/raindrops/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/raindrops/.meta/config.json b/exercises/practice/raindrops/.meta/config.json index 102cfd3473..7a3170b876 100644 --- a/exercises/practice/raindrops/.meta/config.json +++ b/exercises/practice/raindrops/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Convert a number to a string, the content of which depends on the number's factors.", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "ankorGH", "cmccandless", @@ -11,10 +12,23 @@ "SleeplessByte" ], "files": { - "solution": ["raindrops.js"], - "test": ["raindrops.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "raindrops.js" + ], + "test": [ + "raindrops.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Convert a number into its corresponding raindrop sounds - Pling, Plang and Plong.", "source": "A variation on FizzBuzz, a famous technical interview question that is intended to weed out potential candidates. That question is itself derived from Fizz Buzz, a popular children's game for teaching division.", - "source_url": "https://en.wikipedia.org/wiki/Fizz_buzz" + "source_url": "https://en.wikipedia.org/wiki/Fizz_buzz", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/raindrops/babel.config.js b/exercises/practice/raindrops/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/raindrops/babel.config.js +++ b/exercises/practice/raindrops/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/raindrops/eslint.config.mjs b/exercises/practice/raindrops/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/raindrops/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/raindrops/jest.config.js b/exercises/practice/raindrops/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/raindrops/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/raindrops/package.json b/exercises/practice/raindrops/package.json index bb43a10a5a..89cfca88ea 100644 --- a/exercises/practice/raindrops/package.json +++ b/exercises/practice/raindrops/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/raindrops" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/raindrops/raindrops.js b/exercises/practice/raindrops/raindrops.js index fa738c0c2c..4695a3e78b 100644 --- a/exercises/practice/raindrops/raindrops.js +++ b/exercises/practice/raindrops/raindrops.js @@ -4,5 +4,5 @@ // export const convert = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/raindrops/raindrops.spec.js b/exercises/practice/raindrops/raindrops.spec.js index 82bd34bbfc..946fb849d8 100644 --- a/exercises/practice/raindrops/raindrops.spec.js +++ b/exercises/practice/raindrops/raindrops.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { convert } from './raindrops'; describe('Raindrops', () => { diff --git a/exercises/practice/rational-numbers/.docs/instructions.md b/exercises/practice/rational-numbers/.docs/instructions.md index 29fbcdc374..f64fc0f28e 100644 --- a/exercises/practice/rational-numbers/.docs/instructions.md +++ b/exercises/practice/rational-numbers/.docs/instructions.md @@ -2,6 +2,12 @@ A rational number is defined as the quotient of two integers `a` and `b`, called the numerator and denominator, respectively, where `b != 0`. +~~~~exercism/note +Note that mathematically, the denominator can't be zero. +However in many implementations of rational numbers, you will find that the denominator is allowed to be zero with behaviour similar to positive or negative infinity in floating point numbers. +In those cases, the denominator and numerator generally still can't both be zero at once. +~~~~ + The absolute value `|r|` of the rational number `r = a/b` is equal to `|a|/|b|`. The sum of two rational numbers `r₁ = a₁/b₁` and `r₂ = a₂/b₂` is `r₁ + r₂ = a₁/b₁ + a₂/b₂ = (a₁ * b₂ + a₂ * b₁) / (b₁ * b₂)`. @@ -25,6 +31,12 @@ Implement the following operations: - addition, subtraction, multiplication and division of two rational numbers, - absolute value, exponentiation of a given rational number to an integer power, exponentiation of a given rational number to a real (floating-point) power, exponentiation of a real number to a rational number. -Your implementation of rational numbers should always be reduced to lowest terms. For example, `4/4` should reduce to `1/1`, `30/60` should reduce to `1/2`, `12/8` should reduce to `3/2`, etc. To reduce a rational number `r = a/b`, divide `a` and `b` by the greatest common divisor (gcd) of `a` and `b`. So, for example, `gcd(12, 8) = 4`, so `r = 12/8` can be reduced to `(12/4)/(8/4) = 3/2`. +Your implementation of rational numbers should always be reduced to lowest terms. +For example, `4/4` should reduce to `1/1`, `30/60` should reduce to `1/2`, `12/8` should reduce to `3/2`, etc. +To reduce a rational number `r = a/b`, divide `a` and `b` by the greatest common divisor (gcd) of `a` and `b`. +So, for example, `gcd(12, 8) = 4`, so `r = 12/8` can be reduced to `(12/4)/(8/4) = 3/2`. +The reduced form of a rational number should be in "standard form" (the denominator should always be a positive integer). +If a denominator with a negative integer is present, multiply both numerator and denominator by `-1` to ensure standard form is reached. +For example, `3/-4` should be reduced to `-3/4` Assume that the programming language you are using does not have an implementation of rational numbers. diff --git a/exercises/practice/rational-numbers/.eslintrc b/exercises/practice/rational-numbers/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/rational-numbers/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/rational-numbers/.gitignore b/exercises/practice/rational-numbers/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/rational-numbers/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/rational-numbers/.meta/config.json b/exercises/practice/rational-numbers/.meta/config.json index e355081924..940e6a7ec1 100644 --- a/exercises/practice/rational-numbers/.meta/config.json +++ b/exercises/practice/rational-numbers/.meta/config.json @@ -1,12 +1,31 @@ { - "blurb": "Implement rational numbers.", - "authors": ["matthewmorgan"], - "contributors": ["ankorGH", "SleeplessByte", "tejasbubane"], + "authors": [ + "matthewmorgan" + ], + "contributors": [ + "ankorGH", + "jagdish-15", + "SleeplessByte", + "tejasbubane" + ], "files": { - "solution": ["rational-numbers.js"], - "test": ["rational-numbers.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "rational-numbers.js" + ], + "test": [ + "rational-numbers.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Implement rational numbers.", "source": "Wikipedia", - "source_url": "https://en.wikipedia.org/wiki/Rational_number" + "source_url": "https://en.wikipedia.org/wiki/Rational_number", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/rational-numbers/.meta/proof.ci.js b/exercises/practice/rational-numbers/.meta/proof.ci.js index eceb27e5da..8dc9ae5e30 100644 --- a/exercises/practice/rational-numbers/.meta/proof.ci.js +++ b/exercises/practice/rational-numbers/.meta/proof.ci.js @@ -14,41 +14,48 @@ class Rational { const commonDenominator = this.denominator * that.denominator; return new Rational( this.numerator * that.denominator + that.numerator * this.denominator, - commonDenominator + commonDenominator, ); } sub(that) { const commonDenominator = this.denominator * that.denominator; return new Rational( this.numerator * that.denominator - that.numerator * this.denominator, - commonDenominator + commonDenominator, ); } mul(that) { return new Rational( this.numerator * that.numerator, - this.denominator * that.denominator + this.denominator * that.denominator, ); } div(that) { return new Rational( this.numerator * that.denominator, - this.denominator * that.numerator + this.denominator * that.numerator, ); } abs() { return new Rational(Math.abs(this.numerator), Math.abs(this.denominator)); } exprational(n) { - return new Rational( - Math.pow(this.numerator, n), - Math.pow(this.denominator, n) - ); + if (n >= 0) { + return new Rational( + Math.pow(this.numerator, n), + Math.pow(this.denominator, n), + ); + } else { + return new Rational( + Math.pow(this.denominator, -n), + Math.pow(this.numerator, -n), + ); + } } expreal(base) { return Math.pow( 10.0, - Math.log10(Math.pow(base, this.numerator)) / this.denominator + Math.log10(Math.pow(base, this.numerator)) / this.denominator, ); } reduce() { diff --git a/exercises/practice/rational-numbers/.meta/tests.toml b/exercises/practice/rational-numbers/.meta/tests.toml index 2cf56c0877..ddea7145cd 100644 --- a/exercises/practice/rational-numbers/.meta/tests.toml +++ b/exercises/practice/rational-numbers/.meta/tests.toml @@ -1,117 +1,139 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [0ba4d988-044c-4ed5-9215-4d0bb8d0ae9f] -description = "Add two positive rational numbers" +description = "Arithmetic -> Addition -> Add two positive rational numbers" [88ebc342-a2ac-4812-a656-7b664f718b6a] -description = "Add a positive rational number and a negative rational number" +description = "Arithmetic -> Addition -> Add a positive rational number and a negative rational number" [92ed09c2-991e-4082-a602-13557080205c] -description = "Add two negative rational numbers" +description = "Arithmetic -> Addition -> Add two negative rational numbers" [6e58999e-3350-45fb-a104-aac7f4a9dd11] -description = "Add a rational number to its additive inverse" +description = "Arithmetic -> Addition -> Add a rational number to its additive inverse" [47bba350-9db1-4ab9-b412-4a7e1f72a66e] -description = "Subtract two positive rational numbers" +description = "Arithmetic -> Subtraction -> Subtract two positive rational numbers" [93926e2a-3e82-4aee-98a7-fc33fb328e87] -description = "Subtract a positive rational number and a negative rational number" +description = "Arithmetic -> Subtraction -> Subtract a positive rational number and a negative rational number" [a965ba45-9b26-442b-bdc7-7728e4b8d4cc] -description = "Subtract two negative rational numbers" +description = "Arithmetic -> Subtraction -> Subtract two negative rational numbers" [0df0e003-f68e-4209-8c6e-6a4e76af5058] -description = "Subtract a rational number from itself" +description = "Arithmetic -> Subtraction -> Subtract a rational number from itself" [34fde77a-75f4-4204-8050-8d3a937958d3] -description = "Multiply two positive rational numbers" +description = "Arithmetic -> Multiplication -> Multiply two positive rational numbers" [6d015cf0-0ea3-41f1-93de-0b8e38e88bae] -description = "Multiply a negative rational number by a positive rational number" +description = "Arithmetic -> Multiplication -> Multiply a negative rational number by a positive rational number" [d1bf1b55-954e-41b1-8c92-9fc6beeb76fa] -description = "Multiply two negative rational numbers" +description = "Arithmetic -> Multiplication -> Multiply two negative rational numbers" [a9b8f529-9ec7-4c79-a517-19365d779040] -description = "Multiply a rational number by its reciprocal" +description = "Arithmetic -> Multiplication -> Multiply a rational number by its reciprocal" [d89d6429-22fa-4368-ab04-9e01a44d3b48] -description = "Multiply a rational number by 1" +description = "Arithmetic -> Multiplication -> Multiply a rational number by 1" [0d95c8b9-1482-4ed7-bac9-b8694fa90145] -description = "Multiply a rational number by 0" +description = "Arithmetic -> Multiplication -> Multiply a rational number by 0" [1de088f4-64be-4e6e-93fd-5997ae7c9798] -description = "Divide two positive rational numbers" +description = "Arithmetic -> Division -> Divide two positive rational numbers" [7d7983db-652a-4e66-981a-e921fb38d9a9] -description = "Divide a positive rational number by a negative rational number" +description = "Arithmetic -> Division -> Divide a positive rational number by a negative rational number" [1b434d1b-5b38-4cee-aaf5-b9495c399e34] -description = "Divide two negative rational numbers" +description = "Arithmetic -> Division -> Divide two negative rational numbers" [d81c2ebf-3612-45a6-b4e0-f0d47812bd59] -description = "Divide a rational number by 1" +description = "Arithmetic -> Division -> Divide a rational number by 1" [5fee0d8e-5955-4324-acbe-54cdca94ddaa] -description = "Absolute value of a positive rational number" +description = "Absolute value -> Absolute value of a positive rational number" [3cb570b6-c36a-4963-a380-c0834321bcaa] -description = "Absolute value of a positive rational number with negative numerator and denominator" +description = "Absolute value -> Absolute value of a positive rational number with negative numerator and denominator" [6a05f9a0-1f6b-470b-8ff7-41af81773f25] -description = "Absolute value of a negative rational number" +description = "Absolute value -> Absolute value of a negative rational number" [5d0f2336-3694-464f-8df9-f5852fda99dd] -description = "Absolute value of a negative rational number with negative denominator" +description = "Absolute value -> Absolute value of a negative rational number with negative denominator" [f8e1ed4b-9dca-47fb-a01e-5311457b3118] -description = "Absolute value of zero" +description = "Absolute value -> Absolute value of zero" + +[4a8c939f-f958-473b-9f88-6ad0f83bb4c4] +description = "Absolute value -> Absolute value of a rational number is reduced to lowest terms" [ea2ad2af-3dab-41e7-bb9f-bd6819668a84] -description = "Raise a positive rational number to a positive integer power" +description = "Exponentiation of a rational number -> Raise a positive rational number to a positive integer power" [8168edd2-0af3-45b1-b03f-72c01332e10a] -description = "Raise a negative rational number to a positive integer power" +description = "Exponentiation of a rational number -> Raise a negative rational number to a positive integer power" + +[c291cfae-cfd8-44f5-aa6c-b175c148a492] +description = "Exponentiation of a rational number -> Raise a positive rational number to a negative integer power" + +[45cb3288-4ae4-4465-9ae5-c129de4fac8e] +description = "Exponentiation of a rational number -> Raise a negative rational number to an even negative integer power" + +[2d47f945-ffe1-4916-a399-c2e8c27d7f72] +description = "Exponentiation of a rational number -> Raise a negative rational number to an odd negative integer power" [e2f25b1d-e4de-4102-abc3-c2bb7c4591e4] -description = "Raise zero to an integer power" +description = "Exponentiation of a rational number -> Raise zero to an integer power" [431cac50-ab8b-4d58-8e73-319d5404b762] -description = "Raise one to an integer power" +description = "Exponentiation of a rational number -> Raise one to an integer power" [7d164739-d68a-4a9c-b99f-dd77ce5d55e6] -description = "Raise a positive rational number to the power of zero" +description = "Exponentiation of a rational number -> Raise a positive rational number to the power of zero" [eb6bd5f5-f880-4bcd-8103-e736cb6e41d1] -description = "Raise a negative rational number to the power of zero" +description = "Exponentiation of a rational number -> Raise a negative rational number to the power of zero" [30b467dd-c158-46f5-9ffb-c106de2fd6fa] -description = "Raise a real number to a positive rational number" +description = "Exponentiation of a real number to a rational number -> Raise a real number to a positive rational number" [6e026bcc-be40-4b7b-ae22-eeaafc5a1789] -description = "Raise a real number to a negative rational number" +description = "Exponentiation of a real number to a rational number -> Raise a real number to a negative rational number" [9f866da7-e893-407f-8cd2-ee85d496eec5] -description = "Raise a real number to a zero rational number" +description = "Exponentiation of a real number to a rational number -> Raise a real number to a zero rational number" [0a63fbde-b59c-4c26-8237-1e0c73354d0a] -description = "Reduce a positive rational number to lowest terms" +description = "Reduction to lowest terms -> Reduce a positive rational number to lowest terms" + +[5ed6f248-ad8d-4d4e-a545-9146c6727f33] +description = "Reduction to lowest terms -> Reduce places the minus sign on the numerator" [f87c2a4e-d29c-496e-a193-318c503e4402] -description = "Reduce a negative rational number to lowest terms" +description = "Reduction to lowest terms -> Reduce a negative rational number to lowest terms" [3b92ffc0-5b70-4a43-8885-8acee79cdaaf] -description = "Reduce a rational number with a negative denominator to lowest terms" +description = "Reduction to lowest terms -> Reduce a rational number with a negative denominator to lowest terms" [c9dbd2e6-5ac0-4a41-84c1-48b645b4f663] -description = "Reduce zero to lowest terms" +description = "Reduction to lowest terms -> Reduce zero to lowest terms" [297b45ad-2054-4874-84d4-0358dc1b8887] -description = "Reduce an integer to lowest terms" +description = "Reduction to lowest terms -> Reduce an integer to lowest terms" [a73a17fe-fe8c-4a1c-a63b-e7579e333d9e] -description = "Reduce one to lowest terms" +description = "Reduction to lowest terms -> Reduce one to lowest terms" diff --git a/exercises/practice/rational-numbers/babel.config.js b/exercises/practice/rational-numbers/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/rational-numbers/babel.config.js +++ b/exercises/practice/rational-numbers/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/rational-numbers/eslint.config.mjs b/exercises/practice/rational-numbers/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/rational-numbers/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/rational-numbers/jest.config.js b/exercises/practice/rational-numbers/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/rational-numbers/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/rational-numbers/package.json b/exercises/practice/rational-numbers/package.json index d5ffb505ee..ea8408e309 100644 --- a/exercises/practice/rational-numbers/package.json +++ b/exercises/practice/rational-numbers/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/rational-numbers" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/rational-numbers/rational-numbers.js b/exercises/practice/rational-numbers/rational-numbers.js index 6d34af593f..69aab82c5c 100644 --- a/exercises/practice/rational-numbers/rational-numbers.js +++ b/exercises/practice/rational-numbers/rational-numbers.js @@ -5,38 +5,38 @@ export class Rational { constructor() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } add() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } sub() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } mul() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } div() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } abs() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } exprational() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } expreal() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } reduce() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/rational-numbers/rational-numbers.spec.js b/exercises/practice/rational-numbers/rational-numbers.spec.js index 73c3c01dee..5118277a25 100644 --- a/exercises/practice/rational-numbers/rational-numbers.spec.js +++ b/exercises/practice/rational-numbers/rational-numbers.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { Rational } from './rational-numbers'; describe('Addition', () => { @@ -104,15 +105,30 @@ describe('Absolute value', () => { expect(new Rational(1, 2).abs()).toEqual(expected); }); + xtest('Absolute value of a positive rational number with negative numerator and denominator', () => { + const expected = new Rational(1, 2); + expect(new Rational(-1, -2).abs()).toEqual(expected); + }); + xtest('Absolute value of a negative rational number', () => { const expected = new Rational(1, 2); expect(new Rational(-1, 2).abs()).toEqual(expected); }); + xtest('Absolute value of a negative rational number with negative denominator', () => { + const expected = new Rational(1, 2); + expect(new Rational(1, -2).abs()).toEqual(expected); + }); + xtest('Absolute value of zero', () => { const expected = new Rational(0, 1); expect(new Rational(0, 1).abs()).toEqual(expected); }); + + xtest('Absolute value of a rational number is reduced to lowest terms', () => { + const expected = new Rational(1, 2); + expect(new Rational(2, 4).abs()).toEqual(expected); + }); }); describe('Exponentiation of a rational number', () => { @@ -126,6 +142,21 @@ describe('Exponentiation of a rational number', () => { expect(new Rational(-1, 2).exprational(3)).toEqual(expected); }); + xtest('Raise a positive rational number to a negative integer power', () => { + const expected = new Rational(25, 9); + expect(new Rational(3, 5).exprational(-2)).toEqual(expected); + }); + + xtest('Raise a negative rational number to an even negative integer power', () => { + const expected = new Rational(25, 9); + expect(new Rational(-3, 5).exprational(-2)).toEqual(expected); + }); + + xtest('Raise a negative rational number to an odd negative integer power', () => { + const expected = new Rational(-125, 27); + expect(new Rational(-3, 5).exprational(-3)).toEqual(expected); + }); + xtest('Raise zero to an integer power', () => { const expected = new Rational(0, 1); expect(new Rational(0, 1).exprational(5)).toEqual(expected); @@ -169,6 +200,11 @@ describe('Reduction to lowest terms', () => { expect(new Rational(2, 4).reduce()).toEqual(expected); }); + xtest('Reduce places the minus sign on the numerator', () => { + const expected = new Rational(-3, 4); + expect(new Rational(3, -4).reduce()).toEqual(expected); + }); + xtest('Reduce a negative rational number to lowest terms', () => { const expected = new Rational(-2, 3); expect(new Rational(-4, 6).reduce()).toEqual(expected); diff --git a/exercises/practice/react/.docs/instructions.md b/exercises/practice/react/.docs/instructions.md index 0e9038f4fe..1b9a175d0b 100644 --- a/exercises/practice/react/.docs/instructions.md +++ b/exercises/practice/react/.docs/instructions.md @@ -2,15 +2,10 @@ Implement a basic reactive system. -Reactive programming is a programming paradigm that focuses on how values -are computed in terms of each other to allow a change to one value to -automatically propagate to other values, like in a spreadsheet. +Reactive programming is a programming paradigm that focuses on how values are computed in terms of each other to allow a change to one value to automatically propagate to other values, like in a spreadsheet. -Implement a basic reactive system with cells with settable values ("input" -cells) and cells with values computed in terms of other cells ("compute" -cells). Implement updates so that when an input value is changed, values -propagate to reach a new stable system state. +Implement a basic reactive system with cells with settable values ("input" cells) and cells with values computed in terms of other cells ("compute" cells). +Implement updates so that when an input value is changed, values propagate to reach a new stable system state. -In addition, compute cells should allow for registering change notification -callbacks. Call a cell’s callbacks when the cell’s value in a new stable -state has changed from the previous stable state. +In addition, compute cells should allow for registering change notification callbacks. +Call a cell’s callbacks when the cell’s value in a new stable state has changed from the previous stable state. diff --git a/exercises/practice/react/.eslintrc b/exercises/practice/react/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/react/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/react/.gitignore b/exercises/practice/react/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/react/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/react/.meta/config.json b/exercises/practice/react/.meta/config.json index e602b5ce0f..e823f94257 100644 --- a/exercises/practice/react/.meta/config.json +++ b/exercises/practice/react/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Implement a basic reactive system.", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "Boto1", "joshgoebel", @@ -9,8 +10,21 @@ "SleeplessByte" ], "files": { - "solution": ["react.js"], - "test": ["react.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "react.js" + ], + "test": [ + "react.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] + }, + "blurb": "Implement a basic reactive system.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false } } diff --git a/exercises/practice/react/babel.config.js b/exercises/practice/react/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/react/babel.config.js +++ b/exercises/practice/react/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/react/eslint.config.mjs b/exercises/practice/react/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/react/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/react/jest.config.js b/exercises/practice/react/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/react/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/react/package.json b/exercises/practice/react/package.json index 6e69c53a6d..786fa56353 100644 --- a/exercises/practice/react/package.json +++ b/exercises/practice/react/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/react" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/react/react.js b/exercises/practice/react/react.js index dfbd975b46..730fc7477f 100644 --- a/exercises/practice/react/react.js +++ b/exercises/practice/react/react.js @@ -5,30 +5,30 @@ export class InputCell { constructor(value) { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } setValue(value) { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } export class ComputeCell { constructor(inputCells, fn) { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } addCallback(cb) { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } removeCallback(cb) { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } export class CallbackCell { constructor(fn) { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/react/react.spec.js b/exercises/practice/react/react.spec.js index d59110e84b..2ad0a8e5f8 100644 --- a/exercises/practice/react/react.spec.js +++ b/exercises/practice/react/react.spec.js @@ -1,4 +1,5 @@ -import { InputCell, ComputeCell, CallbackCell } from './react'; +import { describe, expect, test, xtest } from '@jest/globals'; +import { CallbackCell, ComputeCell, InputCell } from './react'; describe('React module', () => { test('accepts input', () => { @@ -24,7 +25,7 @@ describe('React module', () => { const computeCell = new ComputeCell( inputCells, - (inputs) => inputs[0].value + inputs[1].value * 10 + (inputs) => inputs[0].value + inputs[1].value * 10, ); expect(computeCell.value).toEqual(21); @@ -34,7 +35,7 @@ describe('React module', () => { const inputCell = new InputCell(1); const computeCell = new ComputeCell( [inputCell], - (inputs) => inputs[0].value + 1 + (inputs) => inputs[0].value + 1, ); inputCell.setValue(3); expect(computeCell.value).toEqual(4); @@ -44,17 +45,17 @@ describe('React module', () => { const inputCell = new InputCell(1); const timesTwo = new ComputeCell( [inputCell], - (inputs) => inputs[0].value * 2 + (inputs) => inputs[0].value * 2, ); const timesThirty = new ComputeCell( [inputCell], - (inputs) => inputs[0].value * 30 + (inputs) => inputs[0].value * 30, ); const sum = new ComputeCell( [timesTwo, timesThirty], - (inputs) => inputs[0].value + inputs[1].value + (inputs) => inputs[0].value + inputs[1].value, ); expect(sum.value).toEqual(32); @@ -67,7 +68,7 @@ describe('React module', () => { const inputCell = new InputCell(1); const output = new ComputeCell( [inputCell], - (inputs) => inputs[0].value + 1 + (inputs) => inputs[0].value + 1, ); const callback = new CallbackCell((cell) => cell.value); @@ -80,7 +81,7 @@ describe('React module', () => { xtest('callbacks fire only when output values change', () => { const inputCell = new InputCell(1); const output = new ComputeCell([inputCell], (inputs) => - inputs[0].value < 3 ? 111 : 222 + inputs[0].value < 3 ? 111 : 222, ); const callback = new CallbackCell((cell) => cell.value); @@ -96,7 +97,7 @@ describe('React module', () => { xtest('static callbacks fire even if their own value has not changed', () => { const inputCell = new InputCell(1); const output = new ComputeCell([inputCell], (inputs) => - inputs[0].value < 3 ? 111 : 222 + inputs[0].value < 3 ? 111 : 222, ); const callback = new CallbackCell(() => 'cell changed'); @@ -119,7 +120,7 @@ describe('React module', () => { const inputCell = new InputCell(1); const output = new ComputeCell( [inputCell], - (inputs) => inputs[0].value + 1 + (inputs) => inputs[0].value + 1, ); const callback1 = new CallbackCell((cell) => cell.value); @@ -146,7 +147,7 @@ describe('React module', () => { const inputCell = new InputCell(1); const output = new ComputeCell( [inputCell], - (inputs) => inputs[0].value + 1 + (inputs) => inputs[0].value + 1, ); const callback1 = new CallbackCell((cell) => cell.value); @@ -169,22 +170,22 @@ describe('React module', () => { const inputCell = new InputCell(1); const plusOne = new ComputeCell( [inputCell], - (inputs) => inputs[0].value + 1 + (inputs) => inputs[0].value + 1, ); const minusOne1 = new ComputeCell( [inputCell], - (inputs) => inputs[0].value - 1 + (inputs) => inputs[0].value - 1, ); const minusOne2 = new ComputeCell( [minusOne1], - (inputs) => inputs[0].value - 1 + (inputs) => inputs[0].value - 1, ); const output = new ComputeCell( [plusOne, minusOne2], - (inputs) => inputs[0].value * inputs[1].value + (inputs) => inputs[0].value * inputs[1].value, ); const callback1 = new CallbackCell((cell) => cell.value); @@ -199,17 +200,17 @@ describe('React module', () => { const inputCell = new InputCell(1); const plusOne = new ComputeCell( [inputCell], - (inputs) => inputs[0].value + 1 + (inputs) => inputs[0].value + 1, ); const minusOne = new ComputeCell( [inputCell], - (inputs) => inputs[0].value - 1 + (inputs) => inputs[0].value - 1, ); const alwaysTwo = new ComputeCell( [plusOne, minusOne], - (inputs) => inputs[0].value - inputs[1].value + (inputs) => inputs[0].value - inputs[1].value, ); const callback = new CallbackCell((cell) => cell.value); diff --git a/exercises/practice/rectangles/.docs/instructions.md b/exercises/practice/rectangles/.docs/instructions.md index e1efd7473e..8eb4ed470e 100644 --- a/exercises/practice/rectangles/.docs/instructions.md +++ b/exercises/practice/rectangles/.docs/instructions.md @@ -10,7 +10,7 @@ Count the rectangles in an ASCII diagram like the one below. +--+--+ ``` -The above diagram contains 6 rectangles: +The above diagram contains these 6 rectangles: ```text @@ -60,5 +60,4 @@ The above diagram contains 6 rectangles: ``` -You may assume that the input is always a proper rectangle (i.e. the length of -every line equals the length of the first line). +You may assume that the input is always a proper rectangle (i.e. the length of every line equals the length of the first line). diff --git a/exercises/practice/rectangles/.eslintrc b/exercises/practice/rectangles/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/rectangles/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/rectangles/.gitignore b/exercises/practice/rectangles/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/rectangles/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/rectangles/.meta/config.json b/exercises/practice/rectangles/.meta/config.json index b71a002ad2..312cda8153 100644 --- a/exercises/practice/rectangles/.meta/config.json +++ b/exercises/practice/rectangles/.meta/config.json @@ -1,10 +1,30 @@ { - "blurb": "Count the rectangles in an ASCII diagram.", - "authors": ["MattH-be"], - "contributors": ["ankorGH", "SleeplessByte", "tejasbubane", "xarxziux"], + "authors": [ + "MattH-be" + ], + "contributors": [ + "ankorGH", + "SleeplessByte", + "tejasbubane", + "xarxziux", + "themetar" + ], "files": { - "solution": ["rectangles.js"], - "test": ["rectangles.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "rectangles.js" + ], + "test": [ + "rectangles.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] + }, + "blurb": "Count the rectangles in an ASCII diagram.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false } } diff --git a/exercises/practice/rectangles/.meta/tests.toml b/exercises/practice/rectangles/.meta/tests.toml index 63cd6c4d9a..282015033a 100644 --- a/exercises/practice/rectangles/.meta/tests.toml +++ b/exercises/practice/rectangles/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [485b7bab-4150-40aa-a8db-73013427d08c] description = "no rows" @@ -40,3 +47,6 @@ description = "corner is required for a rectangle to be complete" [d78fe379-8c1b-4d3c-bdf7-29bfb6f6dc66] description = "large input with many rectangles" + +[6ef24e0f-d191-46da-b929-4faca24b4cd2] +description = "rectangles must have four sides" diff --git a/exercises/practice/rectangles/babel.config.js b/exercises/practice/rectangles/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/rectangles/babel.config.js +++ b/exercises/practice/rectangles/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/rectangles/eslint.config.mjs b/exercises/practice/rectangles/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/rectangles/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/rectangles/jest.config.js b/exercises/practice/rectangles/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/rectangles/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/rectangles/package.json b/exercises/practice/rectangles/package.json index 72ac0ec7dc..9e25ba26a0 100644 --- a/exercises/practice/rectangles/package.json +++ b/exercises/practice/rectangles/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/rectangles" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/rectangles/rectangles.js b/exercises/practice/rectangles/rectangles.js index 332cbbd4b5..0fe7c81ff7 100644 --- a/exercises/practice/rectangles/rectangles.js +++ b/exercises/practice/rectangles/rectangles.js @@ -4,5 +4,5 @@ // export function count() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } diff --git a/exercises/practice/rectangles/rectangles.spec.js b/exercises/practice/rectangles/rectangles.spec.js index 9ee7fa4c01..6e1ee3919c 100644 --- a/exercises/practice/rectangles/rectangles.spec.js +++ b/exercises/practice/rectangles/rectangles.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { count } from './rectangles'; describe('Rectangles', () => { @@ -112,4 +113,19 @@ describe('Rectangles', () => { expect(actual).toEqual(expected); }); + + xtest('rectangles must have four sides', () => { + const expected = 5; + const actual = count([ + '+-+ +-+', + '| | | |', + '+-+-+-+', + ' | | ', + '+-+-+-+', + '| | | |', + '+-+ +-+', + ]); + + expect(actual).toEqual(expected); + }); }); diff --git a/exercises/practice/relative-distance/.docs/instructions.md b/exercises/practice/relative-distance/.docs/instructions.md new file mode 100644 index 0000000000..9046aee7c8 --- /dev/null +++ b/exercises/practice/relative-distance/.docs/instructions.md @@ -0,0 +1,39 @@ +# Instructions + +Your task is to determine the degree of separation between two individuals in a family tree. +This is similar to the pop culture idea that every Hollywood actor is [within six degrees of Kevin Bacon][six-bacons]. + +- You will be given an input, with all parent names and their children. +- Each name is unique, a child _can_ have one or two parents. +- The degree of separation is defined as the shortest number of connections from one person to another. +- If two individuals are not connected, return a value that represents "no known relationship." + Please see the test cases for the actual implementation. + +## Example + +Given the following family tree: + +```text + ┌──────────┐ ┌──────────┐ ┌───────────┐ + │ Helena │ │ Erdős ├─────┤ Shusaku │ + └───┬───┬──┘ └─────┬────┘ └────┬──────┘ + ┌───┘ └───────┐ └───────┬───────┘ +┌─────┴────┐ ┌────┴───┐ ┌─────┴────┐ +│ Isla ├─────┤ Tariq │ │ Kevin │ +└────┬─────┘ └────┬───┘ └──────────┘ + │ │ +┌────┴────┐ ┌────┴───┐ +│ Uma │ │ Morphy │ +└─────────┘ └────────┘ +``` + +The degree of separation between Tariq and Uma is 2 (Tariq → Isla → Uma). +There's no known relationship between Isla and Kevin, as there is no connection in the given data. +The degree of separation between Uma and Isla is 1. + +~~~~exercism/note +Isla and Tariq are siblings and have a separation of 1. +Similarly, this implementation would report a separation of 2 from you to your father's brother. +~~~~ + +[six-bacons]: https://en.m.wikipedia.org/wiki/Six_Degrees_of_Kevin_Bacon diff --git a/exercises/practice/relative-distance/.docs/introduction.md b/exercises/practice/relative-distance/.docs/introduction.md new file mode 100644 index 0000000000..34073b40ac --- /dev/null +++ b/exercises/practice/relative-distance/.docs/introduction.md @@ -0,0 +1,12 @@ +# Introduction + +You've been hired to develop **Noble Knots**, the hottest new dating app for nobility! +With centuries of royal intermarriage, things have gotten… _complicated_. +To avoid any _oops-we're-twins_ situations, your job is to build a system that checks how closely two people are related. + +Noble Knots is inspired by Iceland's "[Islendinga-App][islendiga-app]," which is backed up by a database that traces all known family connections between Icelanders from the time of the settlement of Iceland. +Your algorithm will determine the **degree of separation** between two individuals in the royal family tree. + +Will your app help crown a perfect match? + +[islendiga-app]: https://web.archive.org/web/20250816223614/http://www.islendingaapp.is/information-in-english/ diff --git a/exercises/practice/relative-distance/.gitignore b/exercises/practice/relative-distance/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/relative-distance/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/relative-distance/.meta/config.json b/exercises/practice/relative-distance/.meta/config.json new file mode 100644 index 0000000000..aba7f629cb --- /dev/null +++ b/exercises/practice/relative-distance/.meta/config.json @@ -0,0 +1,25 @@ +{ + "authors": [ + "BNAndras" + ], + "files": { + "solution": [ + "relative-distance.js" + ], + "test": [ + "relative-distance.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] + }, + "blurb": "Given a family tree, calculate the degree of separation.", + "source": "vaeng", + "source_url": "https://github.com/exercism/problem-specifications/pull/2537", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } +} diff --git a/exercises/practice/relative-distance/.meta/proof.ci.js b/exercises/practice/relative-distance/.meta/proof.ci.js new file mode 100644 index 0000000000..943cf1179f --- /dev/null +++ b/exercises/practice/relative-distance/.meta/proof.ci.js @@ -0,0 +1,52 @@ +export const degreesOfSeparation = (familyTree, personA, personB) => { + const neighbors = new Map(); + + for (const [parent, children] of Object.entries(familyTree)) { + if (!neighbors.has(parent)) { + neighbors.set(parent, new Set()); + } + + for (const child of children) { + if (!neighbors.has(child)) { + neighbors.set(child, new Set()); + } + + neighbors.get(parent).add(child); + neighbors.get(child).add(parent); + } + + // + for (const childA of children) { + for (const childB of children) { + if (childA !== childB) { + neighbors.get(childA).add(childB); + neighbors.get(childB).add(childA); + } + } + } + } + + if (!neighbors.has(personA) || !neighbors.has(personB)) { + return -1; + } + + const queue = [[personA, 0]]; + const visited = new Set([personA]); + + while (queue.length > 0) { + const [current, degree] = queue.shift(); + + if (current === personB) { + return degree; + } + + for (const neighbor of neighbors.get(current)) { + if (!visited.has(neighbor)) { + visited.add(neighbor); + queue.push([neighbor, degree + 1]); + } + } + } + + return -1; +}; diff --git a/exercises/practice/relative-distance/.meta/tests.toml b/exercises/practice/relative-distance/.meta/tests.toml new file mode 100644 index 0000000000..66c91ba096 --- /dev/null +++ b/exercises/practice/relative-distance/.meta/tests.toml @@ -0,0 +1,31 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[4a1ded74-5d32-47fb-8ae5-321f51d06b5b] +description = "Direct parent-child relation" + +[30d17269-83e9-4f82-a0d7-8ef9656d8dce] +description = "Sibling relationship" + +[8dffa27d-a8ab-496d-80b3-2f21c77648b5] +description = "Two degrees of separation, grandchild" + +[34e56ec1-d528-4a42-908e-020a4606ee60] +description = "Unrelated individuals" + +[93ffe989-bad2-48c4-878f-3acb1ce2611b] +description = "Complex graph, cousins" + +[2cc2e76b-013a-433c-9486-1dbe29bf06e5] +description = "Complex graph, no shortcut, far removed nephew" + +[46c9fbcb-e464-455f-a718-049ea3c7400a] +description = "Complex graph, some shortcuts, cross-down and cross-up, cousins several times removed, with unrelated family tree" diff --git a/exercises/practice/relative-distance/.npmrc b/exercises/practice/relative-distance/.npmrc new file mode 100644 index 0000000000..d26df800bb --- /dev/null +++ b/exercises/practice/relative-distance/.npmrc @@ -0,0 +1 @@ +audit=false diff --git a/exercises/practice/relative-distance/LICENSE b/exercises/practice/relative-distance/LICENSE new file mode 100644 index 0000000000..90e73be03b --- /dev/null +++ b/exercises/practice/relative-distance/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Exercism + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/exercises/practice/relative-distance/babel.config.js b/exercises/practice/relative-distance/babel.config.js new file mode 100644 index 0000000000..a638497df1 --- /dev/null +++ b/exercises/practice/relative-distance/babel.config.js @@ -0,0 +1,4 @@ +module.exports = { + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], +}; diff --git a/exercises/practice/relative-distance/eslint.config.mjs b/exercises/practice/relative-distance/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/relative-distance/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/relative-distance/jest.config.js b/exercises/practice/relative-distance/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/relative-distance/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/relative-distance/package.json b/exercises/practice/relative-distance/package.json new file mode 100644 index 0000000000..4b35330716 --- /dev/null +++ b/exercises/practice/relative-distance/package.json @@ -0,0 +1,34 @@ +{ + "name": "@exercism/javascript-relative-distance", + "description": "Exercism exercises in Javascript.", + "author": "Katrina Owen", + "private": true, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/exercism/javascript", + "directory": "exercises/practice/relative-distance" + }, + "devDependencies": { + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" + }, + "dependencies": {}, + "scripts": { + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" +} diff --git a/exercises/practice/relative-distance/pnpm-lock.yaml b/exercises/practice/relative-distance/pnpm-lock.yaml new file mode 100644 index 0000000000..9b52cbebca --- /dev/null +++ b/exercises/practice/relative-distance/pnpm-lock.yaml @@ -0,0 +1,7145 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + .: + devDependencies: + '@exercism/babel-preset-javascript': + specifier: ^0.5.1 + version: 0.5.1 + '@exercism/eslint-config-javascript': + specifier: ^0.8.1 + version: 0.8.1(@babel/core@7.26.10)(@exercism/babel-preset-javascript@0.5.1)(eslint@9.25.1)(jest@29.7.0(@types/node@22.15.2))(typescript@5.8.3) + '@jest/globals': + specifier: ^29.7.0 + version: 29.7.0 + '@types/node': + specifier: ^22.10.3 + version: 22.15.2 + '@types/shelljs': + specifier: ^0.8.15 + version: 0.8.15 + babel-jest: + specifier: ^29.7.0 + version: 29.7.0(@babel/core@7.26.10) + core-js: + specifier: ~3.40.0 + version: 3.40.0 + diff: + specifier: ^7.0.0 + version: 7.0.0 + eslint: + specifier: ^9.19.0 + version: 9.25.1 + expect: + specifier: ^29.7.0 + version: 29.7.0 + globals: + specifier: ^15.14.0 + version: 15.15.0 + jest: + specifier: ^29.7.0 + version: 29.7.0(@types/node@22.15.2) + +packages: + '@ampproject/remapping@2.3.0': + resolution: + { + integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==, + } + engines: { node: '>=6.0.0' } + + '@babel/code-frame@7.26.2': + resolution: + { + integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==, + } + engines: { node: '>=6.9.0' } + + '@babel/compat-data@7.26.8': + resolution: + { + integrity: sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==, + } + engines: { node: '>=6.9.0' } + + '@babel/core@7.26.10': + resolution: + { + integrity: sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==, + } + engines: { node: '>=6.9.0' } + + '@babel/eslint-parser@7.27.0': + resolution: + { + integrity: sha512-dtnzmSjXfgL/HDgMcmsLSzyGbEosi4DrGWoCNfuI+W4IkVJw6izpTe7LtOdwAXnkDqw5yweboYCTkM2rQizCng==, + } + engines: { node: ^10.13.0 || ^12.13.0 || >=14.0.0 } + peerDependencies: + '@babel/core': ^7.11.0 + eslint: ^7.5.0 || ^8.0.0 || ^9.0.0 + + '@babel/eslint-plugin@7.27.0': + resolution: + { + integrity: sha512-b8YXz2RX72kf2mOsmvtRdk4GMmpp4bUsvaI0cLJrUsvltMXvELiJPYsy6ikoHqzx40kKdw/3DEBgA8wqCLzJxA==, + } + engines: { node: ^10.13.0 || ^12.13.0 || >=14.0.0 } + peerDependencies: + '@babel/eslint-parser': ^7.11.0 + eslint: ^7.5.0 || ^8.0.0 || ^9.0.0 + + '@babel/generator@7.27.0': + resolution: + { + integrity: sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-annotate-as-pure@7.25.9': + resolution: + { + integrity: sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-compilation-targets@7.27.0': + resolution: + { + integrity: sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-create-class-features-plugin@7.27.0': + resolution: + { + integrity: sha512-vSGCvMecvFCd/BdpGlhpXYNhhC4ccxyvQWpbGL4CWbvfEoLFWUZuSuf7s9Aw70flgQF+6vptvgK2IfOnKlRmBg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-create-regexp-features-plugin@7.27.0': + resolution: + { + integrity: sha512-fO8l08T76v48BhpNRW/nQ0MxfnSdoSKUJBMjubOAYffsVuGG5qOfMq7N6Es7UJvi7Y8goXXo07EfcHZXDPuELQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-define-polyfill-provider@0.6.4': + resolution: + { + integrity: sha512-jljfR1rGnXXNWnmQg2K3+bvhkxB51Rl32QRaOTuwwjviGrHzIbSc8+x9CpraDtbT7mfyjXObULP4w/adunNwAw==, + } + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + '@babel/helper-member-expression-to-functions@7.25.9': + resolution: + { + integrity: sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-module-imports@7.25.9': + resolution: + { + integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-module-transforms@7.26.0': + resolution: + { + integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.25.9': + resolution: + { + integrity: sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-plugin-utils@7.26.5': + resolution: + { + integrity: sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-remap-async-to-generator@7.25.9': + resolution: + { + integrity: sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-replace-supers@7.26.5': + resolution: + { + integrity: sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.25.9': + resolution: + { + integrity: sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-string-parser@7.25.9': + resolution: + { + integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-validator-identifier@7.25.9': + resolution: + { + integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-validator-option@7.25.9': + resolution: + { + integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==, + } + engines: { node: '>=6.9.0' } + + '@babel/helper-wrap-function@7.25.9': + resolution: + { + integrity: sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==, + } + engines: { node: '>=6.9.0' } + + '@babel/helpers@7.27.0': + resolution: + { + integrity: sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==, + } + engines: { node: '>=6.9.0' } + + '@babel/node@7.26.0': + resolution: + { + integrity: sha512-5ASMjh42hbnqyCOK68Q5chh1jKAqn91IswFTN+niwt4FLABhEWCT1tEuuo6mlNQ4WG/oFQLvJ71PaHAKtWtJyA==, + } + engines: { node: '>=6.9.0' } + hasBin: true + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/parser@7.27.0': + resolution: + { + integrity: sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==, + } + engines: { node: '>=6.0.0' } + hasBin: true + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9': + resolution: + { + integrity: sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.9': + resolution: + { + integrity: sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.9': + resolution: + { + integrity: sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.9': + resolution: + { + integrity: sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.13.0 + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.9': + resolution: + { + integrity: sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2': + resolution: + { + integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-async-generators@7.8.4': + resolution: + { + integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-bigint@7.8.3': + resolution: + { + integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-properties@7.12.13': + resolution: + { + integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-static-block@7.14.5': + resolution: + { + integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-assertions@7.26.0': + resolution: + { + integrity: sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.26.0': + resolution: + { + integrity: sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-meta@7.10.4': + resolution: + { + integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-json-strings@7.8.3': + resolution: + { + integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.25.9': + resolution: + { + integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4': + resolution: + { + integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': + resolution: + { + integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-numeric-separator@7.10.4': + resolution: + { + integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-object-rest-spread@7.8.3': + resolution: + { + integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3': + resolution: + { + integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-chaining@7.8.3': + resolution: + { + integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-private-property-in-object@7.14.5': + resolution: + { + integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-top-level-await@7.14.5': + resolution: + { + integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.25.9': + resolution: + { + integrity: sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6': + resolution: + { + integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-arrow-functions@7.25.9': + resolution: + { + integrity: sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-generator-functions@7.26.8': + resolution: + { + integrity: sha512-He9Ej2X7tNf2zdKMAGOsmg2MrFc+hfoAhd3po4cWfo/NWjzEAKa0oQruj1ROVUdl0e6fb6/kE/G3SSxE0lRJOg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-to-generator@7.25.9': + resolution: + { + integrity: sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoped-functions@7.26.5': + resolution: + { + integrity: sha512-chuTSY+hq09+/f5lMj8ZSYgCFpppV2CbYrhNFJ1BFoXpiWPnnAb7R0MqrafCpN8E1+YRrtM1MXZHJdIx8B6rMQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoping@7.27.0': + resolution: + { + integrity: sha512-u1jGphZ8uDI2Pj/HJj6YQ6XQLZCNjOlprjxB5SVz6rq2T6SwAR+CdrWK0CP7F+9rDVMXdB0+r6Am5G5aobOjAQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-properties@7.25.9': + resolution: + { + integrity: sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-static-block@7.26.0': + resolution: + { + integrity: sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.12.0 + + '@babel/plugin-transform-classes@7.25.9': + resolution: + { + integrity: sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-computed-properties@7.25.9': + resolution: + { + integrity: sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-destructuring@7.25.9': + resolution: + { + integrity: sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-dotall-regex@7.25.9': + resolution: + { + integrity: sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-keys@7.25.9': + resolution: + { + integrity: sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.9': + resolution: + { + integrity: sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-dynamic-import@7.25.9': + resolution: + { + integrity: sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-exponentiation-operator@7.26.3': + resolution: + { + integrity: sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-export-namespace-from@7.25.9': + resolution: + { + integrity: sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-for-of@7.26.9': + resolution: + { + integrity: sha512-Hry8AusVm8LW5BVFgiyUReuoGzPUpdHQQqJY5bZnbbf+ngOHWuCuYFKw/BqaaWlvEUrF91HMhDtEaI1hZzNbLg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-function-name@7.25.9': + resolution: + { + integrity: sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-json-strings@7.25.9': + resolution: + { + integrity: sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-literals@7.25.9': + resolution: + { + integrity: sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-logical-assignment-operators@7.25.9': + resolution: + { + integrity: sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-member-expression-literals@7.25.9': + resolution: + { + integrity: sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-amd@7.25.9': + resolution: + { + integrity: sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-commonjs@7.26.3': + resolution: + { + integrity: sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-systemjs@7.25.9': + resolution: + { + integrity: sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-umd@7.25.9': + resolution: + { + integrity: sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-named-capturing-groups-regex@7.25.9': + resolution: + { + integrity: sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-new-target@7.25.9': + resolution: + { + integrity: sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-nullish-coalescing-operator@7.26.6': + resolution: + { + integrity: sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-numeric-separator@7.25.9': + resolution: + { + integrity: sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-rest-spread@7.25.9': + resolution: + { + integrity: sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-super@7.25.9': + resolution: + { + integrity: sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-catch-binding@7.25.9': + resolution: + { + integrity: sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-chaining@7.25.9': + resolution: + { + integrity: sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-parameters@7.25.9': + resolution: + { + integrity: sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-methods@7.25.9': + resolution: + { + integrity: sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-property-in-object@7.25.9': + resolution: + { + integrity: sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-property-literals@7.25.9': + resolution: + { + integrity: sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regenerator@7.27.0': + resolution: + { + integrity: sha512-LX/vCajUJQDqE7Aum/ELUMZAY19+cDpghxrnyt5I1tV6X5PyC86AOoWXWFYFeIvauyeSA6/ktn4tQVn/3ZifsA==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regexp-modifiers@7.26.0': + resolution: + { + integrity: sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-reserved-words@7.25.9': + resolution: + { + integrity: sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-shorthand-properties@7.25.9': + resolution: + { + integrity: sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-spread@7.25.9': + resolution: + { + integrity: sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-sticky-regex@7.25.9': + resolution: + { + integrity: sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-template-literals@7.26.8': + resolution: + { + integrity: sha512-OmGDL5/J0CJPJZTHZbi2XpO0tyT2Ia7fzpW5GURwdtp2X3fMmN8au/ej6peC/T33/+CRiIpA8Krse8hFGVmT5Q==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typeof-symbol@7.27.0': + resolution: + { + integrity: sha512-+LLkxA9rKJpNoGsbLnAgOCdESl73vwYn+V6b+5wHbrE7OGKVDPHIQvbFSzqE6rwqaCw2RE+zdJrlLkcf8YOA0w==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-escapes@7.25.9': + resolution: + { + integrity: sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-property-regex@7.25.9': + resolution: + { + integrity: sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-regex@7.25.9': + resolution: + { + integrity: sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-sets-regex@7.25.9': + resolution: + { + integrity: sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/preset-env@7.26.9': + resolution: + { + integrity: sha512-vX3qPGE8sEKEAZCWk05k3cpTAE3/nOYca++JA+Rd0z2NCNzabmYvEiSShKzm10zdquOIAVXsy2Ei/DTW34KlKQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-modules@0.1.6-no-external-plugins': + resolution: + { + integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==, + } + peerDependencies: + '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 + + '@babel/register@7.25.9': + resolution: + { + integrity: sha512-8D43jXtGsYmEeDvm4MWHYUpWf8iiXgWYx3fW7E7Wb7Oe6FWqJPl5K6TuFW0dOwNZzEE5rjlaSJYH9JjrUKJszA==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.27.0': + resolution: + { + integrity: sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==, + } + engines: { node: '>=6.9.0' } + + '@babel/template@7.27.0': + resolution: + { + integrity: sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==, + } + engines: { node: '>=6.9.0' } + + '@babel/traverse@7.27.0': + resolution: + { + integrity: sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==, + } + engines: { node: '>=6.9.0' } + + '@babel/types@7.27.0': + resolution: + { + integrity: sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==, + } + engines: { node: '>=6.9.0' } + + '@bcoe/v8-coverage@0.2.3': + resolution: + { + integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==, + } + + '@eslint-community/eslint-utils@4.6.1': + resolution: + { + integrity: sha512-KTsJMmobmbrFLe3LDh0PC2FXpcSYJt/MLjlkh/9LEnmKYLSYmT/0EW9JWANjeoemiuZrmogti0tW5Ch+qNUYDw==, + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: + { + integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==, + } + engines: { node: ^12.0.0 || ^14.0.0 || >=16.0.0 } + + '@eslint/config-array@0.20.0': + resolution: + { + integrity: sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + '@eslint/config-helpers@0.2.1': + resolution: + { + integrity: sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + '@eslint/core@0.13.0': + resolution: + { + integrity: sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + '@eslint/eslintrc@3.3.1': + resolution: + { + integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + '@eslint/js@9.25.1': + resolution: + { + integrity: sha512-dEIwmjntEx8u3Uvv+kr3PDeeArL8Hw07H9kyYxCjnM9pBjfEhk6uLXSchxxzgiwtRhhzVzqmUSDFBOi1TuZ7qg==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + '@eslint/object-schema@2.1.6': + resolution: + { + integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + '@eslint/plugin-kit@0.2.8': + resolution: + { + integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + '@exercism/babel-preset-javascript@0.5.1': + resolution: + { + integrity: sha512-6NywGKngMLmuDhDVLov1fm6O8MTtirKfQlDmg3q/3cnP4ElErtqzyOoBoI4Om54hHrTfHXlw+UQxQ7NkKeRAfA==, + } + + '@exercism/eslint-config-javascript@0.8.1': + resolution: + { + integrity: sha512-KFk43KvV4lUArh/1RUmFMTGXWGp6Pqqs3eXlDXpHQ7xhBKUatbTIL7xbhUB8o366DDyqkcmlxnhOnDsbnL66Qg==, + } + peerDependencies: + '@exercism/babel-preset-javascript': '>= 0.5.1' + eslint: '>= 9.17' + + '@humanfs/core@0.19.1': + resolution: + { + integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==, + } + engines: { node: '>=18.18.0' } + + '@humanfs/node@0.16.6': + resolution: + { + integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==, + } + engines: { node: '>=18.18.0' } + + '@humanwhocodes/module-importer@1.0.1': + resolution: + { + integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==, + } + engines: { node: '>=12.22' } + + '@humanwhocodes/retry@0.3.1': + resolution: + { + integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==, + } + engines: { node: '>=18.18' } + + '@humanwhocodes/retry@0.4.2': + resolution: + { + integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==, + } + engines: { node: '>=18.18' } + + '@istanbuljs/load-nyc-config@1.1.0': + resolution: + { + integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==, + } + engines: { node: '>=8' } + + '@istanbuljs/schema@0.1.3': + resolution: + { + integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==, + } + engines: { node: '>=8' } + + '@jest/console@29.7.0': + resolution: + { + integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + '@jest/core@29.7.0': + resolution: + { + integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/environment@29.7.0': + resolution: + { + integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + '@jest/expect-utils@29.7.0': + resolution: + { + integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + '@jest/expect@29.7.0': + resolution: + { + integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + '@jest/fake-timers@29.7.0': + resolution: + { + integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + '@jest/globals@29.7.0': + resolution: + { + integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + '@jest/reporters@29.7.0': + resolution: + { + integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/schemas@29.6.3': + resolution: + { + integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + '@jest/source-map@29.6.3': + resolution: + { + integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + '@jest/test-result@29.7.0': + resolution: + { + integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + '@jest/test-sequencer@29.7.0': + resolution: + { + integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + '@jest/transform@29.7.0': + resolution: + { + integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + '@jest/types@29.6.3': + resolution: + { + integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + '@jridgewell/gen-mapping@0.3.8': + resolution: + { + integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==, + } + engines: { node: '>=6.0.0' } + + '@jridgewell/resolve-uri@3.1.2': + resolution: + { + integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==, + } + engines: { node: '>=6.0.0' } + + '@jridgewell/set-array@1.2.1': + resolution: + { + integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==, + } + engines: { node: '>=6.0.0' } + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: + { + integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==, + } + + '@jridgewell/trace-mapping@0.3.25': + resolution: + { + integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==, + } + + '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': + resolution: + { + integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==, + } + + '@nodelib/fs.scandir@2.1.5': + resolution: + { + integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==, + } + engines: { node: '>= 8' } + + '@nodelib/fs.stat@2.0.5': + resolution: + { + integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==, + } + engines: { node: '>= 8' } + + '@nodelib/fs.walk@1.2.8': + resolution: + { + integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==, + } + engines: { node: '>= 8' } + + '@sinclair/typebox@0.27.8': + resolution: + { + integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==, + } + + '@sinonjs/commons@3.0.1': + resolution: + { + integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==, + } + + '@sinonjs/fake-timers@10.3.0': + resolution: + { + integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==, + } + + '@types/babel__core@7.20.5': + resolution: + { + integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==, + } + + '@types/babel__generator@7.27.0': + resolution: + { + integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==, + } + + '@types/babel__template@7.4.4': + resolution: + { + integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==, + } + + '@types/babel__traverse@7.20.7': + resolution: + { + integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==, + } + + '@types/estree@1.0.7': + resolution: + { + integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==, + } + + '@types/glob@7.2.0': + resolution: + { + integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==, + } + + '@types/graceful-fs@4.1.9': + resolution: + { + integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==, + } + + '@types/istanbul-lib-coverage@2.0.6': + resolution: + { + integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==, + } + + '@types/istanbul-lib-report@3.0.3': + resolution: + { + integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==, + } + + '@types/istanbul-reports@3.0.4': + resolution: + { + integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==, + } + + '@types/json-schema@7.0.15': + resolution: + { + integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==, + } + + '@types/minimatch@5.1.2': + resolution: + { + integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==, + } + + '@types/node@22.15.2': + resolution: + { + integrity: sha512-uKXqKN9beGoMdBfcaTY1ecwz6ctxuJAcUlwE55938g0ZJ8lRxwAZqRz2AJ4pzpt5dHdTPMB863UZ0ESiFUcP7A==, + } + + '@types/shelljs@0.8.15': + resolution: + { + integrity: sha512-vzmnCHl6hViPu9GNLQJ+DZFd6BQI2DBTUeOvYHqkWQLMfKAAQYMb/xAmZkTogZI/vqXHCWkqDRymDI5p0QTi5Q==, + } + + '@types/stack-utils@2.0.3': + resolution: + { + integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==, + } + + '@types/yargs-parser@21.0.3': + resolution: + { + integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==, + } + + '@types/yargs@17.0.33': + resolution: + { + integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==, + } + + '@typescript-eslint/scope-manager@8.31.0': + resolution: + { + integrity: sha512-knO8UyF78Nt8O/B64i7TlGXod69ko7z6vJD9uhSlm0qkAbGeRUSudcm0+K/4CrRjrpiHfBCjMWlc08Vav1xwcw==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + '@typescript-eslint/types@8.31.0': + resolution: + { + integrity: sha512-Ch8oSjVyYyJxPQk8pMiP2FFGYatqXQfQIaMp+TpuuLlDachRWpUAeEu1u9B/v/8LToehUIWyiKcA/w5hUFRKuQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + '@typescript-eslint/typescript-estree@8.31.0': + resolution: + { + integrity: sha512-xLmgn4Yl46xi6aDSZ9KkyfhhtnYI15/CvHbpOy/eR5NWhK/BK8wc709KKwhAR0m4ZKRP7h07bm4BWUYOCuRpQQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/utils@8.31.0': + resolution: + { + integrity: sha512-qi6uPLt9cjTFxAb1zGNgTob4x9ur7xC6mHQJ8GwEzGMGE9tYniublmJaowOJ9V2jUzxrltTPfdG2nKlWsq0+Ww==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.9.0' + + '@typescript-eslint/visitor-keys@8.31.0': + resolution: + { + integrity: sha512-QcGHmlRHWOl93o64ZUMNewCdwKGU6WItOU52H0djgNmn1EOrhVudrDzXz4OycCRSCPwFCDrE2iIt5vmuUdHxuQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + acorn-jsx@5.3.2: + resolution: + { + integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==, + } + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.14.1: + resolution: + { + integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==, + } + engines: { node: '>=0.4.0' } + hasBin: true + + ajv@6.12.6: + resolution: + { + integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==, + } + + ansi-escapes@4.3.2: + resolution: + { + integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==, + } + engines: { node: '>=8' } + + ansi-regex@5.0.1: + resolution: + { + integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==, + } + engines: { node: '>=8' } + + ansi-styles@4.3.0: + resolution: + { + integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==, + } + engines: { node: '>=8' } + + ansi-styles@5.2.0: + resolution: + { + integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==, + } + engines: { node: '>=10' } + + anymatch@3.1.3: + resolution: + { + integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==, + } + engines: { node: '>= 8' } + + argparse@1.0.10: + resolution: + { + integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==, + } + + argparse@2.0.1: + resolution: + { + integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==, + } + + array-buffer-byte-length@1.0.2: + resolution: + { + integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==, + } + engines: { node: '>= 0.4' } + + array.prototype.reduce@1.0.8: + resolution: + { + integrity: sha512-DwuEqgXFBwbmZSRqt3BpQigWNUoqw9Ml2dTWdF3B2zQlQX4OeUE0zyuzX0fX0IbTvjdkZbcBTU3idgpO78qkTw==, + } + engines: { node: '>= 0.4' } + + arraybuffer.prototype.slice@1.0.4: + resolution: + { + integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==, + } + engines: { node: '>= 0.4' } + + async-function@1.0.0: + resolution: + { + integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==, + } + engines: { node: '>= 0.4' } + + available-typed-arrays@1.0.7: + resolution: + { + integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==, + } + engines: { node: '>= 0.4' } + + babel-jest@29.7.0: + resolution: + { + integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + peerDependencies: + '@babel/core': ^7.8.0 + + babel-plugin-istanbul@6.1.1: + resolution: + { + integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==, + } + engines: { node: '>=8' } + + babel-plugin-jest-hoist@29.6.3: + resolution: + { + integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + babel-plugin-polyfill-corejs2@0.4.13: + resolution: + { + integrity: sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==, + } + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-corejs3@0.11.1: + resolution: + { + integrity: sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==, + } + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-regenerator@0.6.4: + resolution: + { + integrity: sha512-7gD3pRadPrbjhjLyxebmx/WrFYcuSjZ0XbdUujQMZ/fcE9oeewk2U/7PCvez84UeuK3oSjmPZ0Ch0dlupQvGzw==, + } + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-preset-current-node-syntax@1.1.0: + resolution: + { + integrity: sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==, + } + peerDependencies: + '@babel/core': ^7.0.0 + + babel-preset-jest@29.6.3: + resolution: + { + integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + peerDependencies: + '@babel/core': ^7.0.0 + + balanced-match@1.0.2: + resolution: + { + integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==, + } + + brace-expansion@1.1.11: + resolution: + { + integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==, + } + + brace-expansion@2.0.1: + resolution: + { + integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==, + } + + braces@3.0.3: + resolution: + { + integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==, + } + engines: { node: '>=8' } + + browserslist@4.24.4: + resolution: + { + integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==, + } + engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 } + hasBin: true + + bser@2.1.1: + resolution: + { + integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==, + } + + buffer-from@1.1.2: + resolution: + { + integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==, + } + + call-bind-apply-helpers@1.0.2: + resolution: + { + integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==, + } + engines: { node: '>= 0.4' } + + call-bind@1.0.8: + resolution: + { + integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==, + } + engines: { node: '>= 0.4' } + + call-bound@1.0.4: + resolution: + { + integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==, + } + engines: { node: '>= 0.4' } + + callsites@3.1.0: + resolution: + { + integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==, + } + engines: { node: '>=6' } + + camelcase@5.3.1: + resolution: + { + integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==, + } + engines: { node: '>=6' } + + camelcase@6.3.0: + resolution: + { + integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==, + } + engines: { node: '>=10' } + + caniuse-lite@1.0.30001715: + resolution: + { + integrity: sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==, + } + + chalk@4.1.2: + resolution: + { + integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==, + } + engines: { node: '>=10' } + + char-regex@1.0.2: + resolution: + { + integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==, + } + engines: { node: '>=10' } + + ci-info@3.9.0: + resolution: + { + integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==, + } + engines: { node: '>=8' } + + cjs-module-lexer@1.4.3: + resolution: + { + integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==, + } + + cliui@8.0.1: + resolution: + { + integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==, + } + engines: { node: '>=12' } + + clone-deep@4.0.1: + resolution: + { + integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==, + } + engines: { node: '>=6' } + + co@4.6.0: + resolution: + { + integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==, + } + engines: { iojs: '>= 1.0.0', node: '>= 0.12.0' } + + collect-v8-coverage@1.0.2: + resolution: + { + integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==, + } + + color-convert@2.0.1: + resolution: + { + integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==, + } + engines: { node: '>=7.0.0' } + + color-name@1.1.4: + resolution: + { + integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==, + } + + commander@6.2.1: + resolution: + { + integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==, + } + engines: { node: '>= 6' } + + commondir@1.0.1: + resolution: + { + integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==, + } + + concat-map@0.0.1: + resolution: + { + integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==, + } + + convert-source-map@2.0.0: + resolution: + { + integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==, + } + + core-js-compat@3.41.0: + resolution: + { + integrity: sha512-RFsU9LySVue9RTwdDVX/T0e2Y6jRYWXERKElIjpuEOEnxaXffI0X7RUwVzfYLfzuLXSNJDYoRYUAmRUcyln20A==, + } + + core-js@3.38.1: + resolution: + { + integrity: sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==, + } + + core-js@3.40.0: + resolution: + { + integrity: sha512-7vsMc/Lty6AGnn7uFpYT56QesI5D2Y/UkgKounk87OP9Z2H9Z8kj6jzcSGAxFmUtDOS0ntK6lbQz+Nsa0Jj6mQ==, + } + + create-jest@29.7.0: + resolution: + { + integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + hasBin: true + + cross-spawn@7.0.6: + resolution: + { + integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==, + } + engines: { node: '>= 8' } + + data-view-buffer@1.0.2: + resolution: + { + integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==, + } + engines: { node: '>= 0.4' } + + data-view-byte-length@1.0.2: + resolution: + { + integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==, + } + engines: { node: '>= 0.4' } + + data-view-byte-offset@1.0.1: + resolution: + { + integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==, + } + engines: { node: '>= 0.4' } + + debug@4.4.0: + resolution: + { + integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==, + } + engines: { node: '>=6.0' } + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + dedent@1.5.3: + resolution: + { + integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==, + } + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + + deep-is@0.1.4: + resolution: + { + integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==, + } + + deepmerge@4.3.1: + resolution: + { + integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==, + } + engines: { node: '>=0.10.0' } + + define-data-property@1.1.4: + resolution: + { + integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==, + } + engines: { node: '>= 0.4' } + + define-properties@1.2.1: + resolution: + { + integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==, + } + engines: { node: '>= 0.4' } + + detect-newline@3.1.0: + resolution: + { + integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==, + } + engines: { node: '>=8' } + + diff-sequences@29.6.3: + resolution: + { + integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + diff@7.0.0: + resolution: + { + integrity: sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==, + } + engines: { node: '>=0.3.1' } + + dunder-proto@1.0.1: + resolution: + { + integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==, + } + engines: { node: '>= 0.4' } + + electron-to-chromium@1.5.143: + resolution: + { + integrity: sha512-QqklJMOFBMqe46k8iIOwA9l2hz57V2OKMmP5eSWcUvwx+mASAsbU+wkF1pHjn9ZVSBPrsYWr4/W/95y5SwYg2g==, + } + + emittery@0.13.1: + resolution: + { + integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==, + } + engines: { node: '>=12' } + + emoji-regex@8.0.0: + resolution: + { + integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==, + } + + error-ex@1.3.2: + resolution: + { + integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==, + } + + es-abstract@1.23.9: + resolution: + { + integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==, + } + engines: { node: '>= 0.4' } + + es-array-method-boxes-properly@1.0.0: + resolution: + { + integrity: sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==, + } + + es-define-property@1.0.1: + resolution: + { + integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==, + } + engines: { node: '>= 0.4' } + + es-errors@1.3.0: + resolution: + { + integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==, + } + engines: { node: '>= 0.4' } + + es-object-atoms@1.1.1: + resolution: + { + integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==, + } + engines: { node: '>= 0.4' } + + es-set-tostringtag@2.1.0: + resolution: + { + integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==, + } + engines: { node: '>= 0.4' } + + es-to-primitive@1.3.0: + resolution: + { + integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==, + } + engines: { node: '>= 0.4' } + + escalade@3.2.0: + resolution: + { + integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==, + } + engines: { node: '>=6' } + + escape-string-regexp@2.0.0: + resolution: + { + integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==, + } + engines: { node: '>=8' } + + escape-string-regexp@4.0.0: + resolution: + { + integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==, + } + engines: { node: '>=10' } + + eslint-config-prettier@9.1.0: + resolution: + { + integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==, + } + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-plugin-jest@28.11.0: + resolution: + { + integrity: sha512-QAfipLcNCWLVocVbZW8GimKn5p5iiMcgGbRzz8z/P5q7xw+cNEpYqyzFMtIF/ZgF2HLOyy+dYBut+DoYolvqig==, + } + engines: { node: ^16.10.0 || ^18.12.0 || >=20.0.0 } + peerDependencies: + '@typescript-eslint/eslint-plugin': ^6.0.0 || ^7.0.0 || ^8.0.0 + eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 + jest: '*' + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + jest: + optional: true + + eslint-rule-composer@0.3.0: + resolution: + { + integrity: sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==, + } + engines: { node: '>=4.0.0' } + + eslint-scope@5.1.1: + resolution: + { + integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==, + } + engines: { node: '>=8.0.0' } + + eslint-scope@8.3.0: + resolution: + { + integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + eslint-visitor-keys@2.1.0: + resolution: + { + integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==, + } + engines: { node: '>=10' } + + eslint-visitor-keys@3.4.3: + resolution: + { + integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==, + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + + eslint-visitor-keys@4.2.0: + resolution: + { + integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + eslint@9.25.1: + resolution: + { + integrity: sha512-E6Mtz9oGQWDCpV12319d59n4tx9zOTXSTmc8BLVxBx+G/0RdM5MvEEJLU9c0+aleoePYYgVTOsRblx433qmhWQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.3.0: + resolution: + { + integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + esprima@4.0.1: + resolution: + { + integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==, + } + engines: { node: '>=4' } + hasBin: true + + esquery@1.6.0: + resolution: + { + integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==, + } + engines: { node: '>=0.10' } + + esrecurse@4.3.0: + resolution: + { + integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==, + } + engines: { node: '>=4.0' } + + estraverse@4.3.0: + resolution: + { + integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==, + } + engines: { node: '>=4.0' } + + estraverse@5.3.0: + resolution: + { + integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==, + } + engines: { node: '>=4.0' } + + esutils@2.0.3: + resolution: + { + integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==, + } + engines: { node: '>=0.10.0' } + + execa@5.1.1: + resolution: + { + integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==, + } + engines: { node: '>=10' } + + exit@0.1.2: + resolution: + { + integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==, + } + engines: { node: '>= 0.8.0' } + + expect@29.7.0: + resolution: + { + integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + fast-deep-equal@3.1.3: + resolution: + { + integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==, + } + + fast-glob@3.3.3: + resolution: + { + integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==, + } + engines: { node: '>=8.6.0' } + + fast-json-stable-stringify@2.1.0: + resolution: + { + integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==, + } + + fast-levenshtein@2.0.6: + resolution: + { + integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==, + } + + fastq@1.19.1: + resolution: + { + integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==, + } + + fb-watchman@2.0.2: + resolution: + { + integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==, + } + + file-entry-cache@8.0.0: + resolution: + { + integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==, + } + engines: { node: '>=16.0.0' } + + fill-range@7.1.1: + resolution: + { + integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==, + } + engines: { node: '>=8' } + + find-cache-dir@2.1.0: + resolution: + { + integrity: sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==, + } + engines: { node: '>=6' } + + find-up@3.0.0: + resolution: + { + integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==, + } + engines: { node: '>=6' } + + find-up@4.1.0: + resolution: + { + integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==, + } + engines: { node: '>=8' } + + find-up@5.0.0: + resolution: + { + integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==, + } + engines: { node: '>=10' } + + flat-cache@4.0.1: + resolution: + { + integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==, + } + engines: { node: '>=16' } + + flatted@3.3.3: + resolution: + { + integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==, + } + + for-each@0.3.5: + resolution: + { + integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==, + } + engines: { node: '>= 0.4' } + + fs.realpath@1.0.0: + resolution: + { + integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==, + } + + fsevents@2.3.3: + resolution: + { + integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==, + } + engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 } + os: [darwin] + + function-bind@1.1.2: + resolution: + { + integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==, + } + + function.prototype.name@1.1.8: + resolution: + { + integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==, + } + engines: { node: '>= 0.4' } + + functions-have-names@1.2.3: + resolution: + { + integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==, + } + + gensync@1.0.0-beta.2: + resolution: + { + integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==, + } + engines: { node: '>=6.9.0' } + + get-caller-file@2.0.5: + resolution: + { + integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==, + } + engines: { node: 6.* || 8.* || >= 10.* } + + get-intrinsic@1.3.0: + resolution: + { + integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==, + } + engines: { node: '>= 0.4' } + + get-package-type@0.1.0: + resolution: + { + integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==, + } + engines: { node: '>=8.0.0' } + + get-proto@1.0.1: + resolution: + { + integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==, + } + engines: { node: '>= 0.4' } + + get-stream@6.0.1: + resolution: + { + integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==, + } + engines: { node: '>=10' } + + get-symbol-description@1.1.0: + resolution: + { + integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==, + } + engines: { node: '>= 0.4' } + + glob-parent@5.1.2: + resolution: + { + integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==, + } + engines: { node: '>= 6' } + + glob-parent@6.0.2: + resolution: + { + integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==, + } + engines: { node: '>=10.13.0' } + + glob@7.2.3: + resolution: + { + integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==, + } + deprecated: Glob versions prior to v9 are no longer supported + + globals@11.12.0: + resolution: + { + integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==, + } + engines: { node: '>=4' } + + globals@14.0.0: + resolution: + { + integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==, + } + engines: { node: '>=18' } + + globals@15.15.0: + resolution: + { + integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==, + } + engines: { node: '>=18' } + + globalthis@1.0.4: + resolution: + { + integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==, + } + engines: { node: '>= 0.4' } + + gopd@1.2.0: + resolution: + { + integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==, + } + engines: { node: '>= 0.4' } + + graceful-fs@4.2.11: + resolution: + { + integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==, + } + + has-bigints@1.1.0: + resolution: + { + integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==, + } + engines: { node: '>= 0.4' } + + has-flag@4.0.0: + resolution: + { + integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==, + } + engines: { node: '>=8' } + + has-property-descriptors@1.0.2: + resolution: + { + integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==, + } + + has-proto@1.2.0: + resolution: + { + integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==, + } + engines: { node: '>= 0.4' } + + has-symbols@1.1.0: + resolution: + { + integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==, + } + engines: { node: '>= 0.4' } + + has-tostringtag@1.0.2: + resolution: + { + integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==, + } + engines: { node: '>= 0.4' } + + hasown@2.0.2: + resolution: + { + integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==, + } + engines: { node: '>= 0.4' } + + homedir-polyfill@1.0.3: + resolution: + { + integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==, + } + engines: { node: '>=0.10.0' } + + html-escaper@2.0.2: + resolution: + { + integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==, + } + + human-signals@2.1.0: + resolution: + { + integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==, + } + engines: { node: '>=10.17.0' } + + ignore@5.3.2: + resolution: + { + integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==, + } + engines: { node: '>= 4' } + + import-fresh@3.3.1: + resolution: + { + integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==, + } + engines: { node: '>=6' } + + import-local@3.2.0: + resolution: + { + integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==, + } + engines: { node: '>=8' } + hasBin: true + + imurmurhash@0.1.4: + resolution: + { + integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==, + } + engines: { node: '>=0.8.19' } + + inflight@1.0.6: + resolution: + { + integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==, + } + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: + { + integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==, + } + + internal-slot@1.1.0: + resolution: + { + integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==, + } + engines: { node: '>= 0.4' } + + is-array-buffer@3.0.5: + resolution: + { + integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==, + } + engines: { node: '>= 0.4' } + + is-arrayish@0.2.1: + resolution: + { + integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==, + } + + is-async-function@2.1.1: + resolution: + { + integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==, + } + engines: { node: '>= 0.4' } + + is-bigint@1.1.0: + resolution: + { + integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==, + } + engines: { node: '>= 0.4' } + + is-boolean-object@1.2.2: + resolution: + { + integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==, + } + engines: { node: '>= 0.4' } + + is-callable@1.2.7: + resolution: + { + integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==, + } + engines: { node: '>= 0.4' } + + is-core-module@2.16.1: + resolution: + { + integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==, + } + engines: { node: '>= 0.4' } + + is-data-view@1.0.2: + resolution: + { + integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==, + } + engines: { node: '>= 0.4' } + + is-date-object@1.1.0: + resolution: + { + integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==, + } + engines: { node: '>= 0.4' } + + is-extglob@2.1.1: + resolution: + { + integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==, + } + engines: { node: '>=0.10.0' } + + is-finalizationregistry@1.1.1: + resolution: + { + integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==, + } + engines: { node: '>= 0.4' } + + is-fullwidth-code-point@3.0.0: + resolution: + { + integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==, + } + engines: { node: '>=8' } + + is-generator-fn@2.1.0: + resolution: + { + integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==, + } + engines: { node: '>=6' } + + is-generator-function@1.1.0: + resolution: + { + integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==, + } + engines: { node: '>= 0.4' } + + is-glob@4.0.3: + resolution: + { + integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==, + } + engines: { node: '>=0.10.0' } + + is-map@2.0.3: + resolution: + { + integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==, + } + engines: { node: '>= 0.4' } + + is-number-object@1.1.1: + resolution: + { + integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==, + } + engines: { node: '>= 0.4' } + + is-number@7.0.0: + resolution: + { + integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==, + } + engines: { node: '>=0.12.0' } + + is-plain-object@2.0.4: + resolution: + { + integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==, + } + engines: { node: '>=0.10.0' } + + is-regex@1.2.1: + resolution: + { + integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==, + } + engines: { node: '>= 0.4' } + + is-set@2.0.3: + resolution: + { + integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==, + } + engines: { node: '>= 0.4' } + + is-shared-array-buffer@1.0.4: + resolution: + { + integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==, + } + engines: { node: '>= 0.4' } + + is-stream@2.0.1: + resolution: + { + integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==, + } + engines: { node: '>=8' } + + is-string@1.1.1: + resolution: + { + integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==, + } + engines: { node: '>= 0.4' } + + is-symbol@1.1.1: + resolution: + { + integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==, + } + engines: { node: '>= 0.4' } + + is-typed-array@1.1.15: + resolution: + { + integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==, + } + engines: { node: '>= 0.4' } + + is-weakmap@2.0.2: + resolution: + { + integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==, + } + engines: { node: '>= 0.4' } + + is-weakref@1.1.1: + resolution: + { + integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==, + } + engines: { node: '>= 0.4' } + + is-weakset@2.0.4: + resolution: + { + integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==, + } + engines: { node: '>= 0.4' } + + isarray@2.0.5: + resolution: + { + integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==, + } + + isexe@2.0.0: + resolution: + { + integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==, + } + + isobject@3.0.1: + resolution: + { + integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==, + } + engines: { node: '>=0.10.0' } + + istanbul-lib-coverage@3.2.2: + resolution: + { + integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==, + } + engines: { node: '>=8' } + + istanbul-lib-instrument@5.2.1: + resolution: + { + integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==, + } + engines: { node: '>=8' } + + istanbul-lib-instrument@6.0.3: + resolution: + { + integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==, + } + engines: { node: '>=10' } + + istanbul-lib-report@3.0.1: + resolution: + { + integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==, + } + engines: { node: '>=10' } + + istanbul-lib-source-maps@4.0.1: + resolution: + { + integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==, + } + engines: { node: '>=10' } + + istanbul-reports@3.1.7: + resolution: + { + integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==, + } + engines: { node: '>=8' } + + jest-changed-files@29.7.0: + resolution: + { + integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + jest-circus@29.7.0: + resolution: + { + integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + jest-cli@29.7.0: + resolution: + { + integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jest-config@29.7.0: + resolution: + { + integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + + jest-diff@29.7.0: + resolution: + { + integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + jest-docblock@29.7.0: + resolution: + { + integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + jest-each@29.7.0: + resolution: + { + integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + jest-environment-node@29.7.0: + resolution: + { + integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + jest-get-type@29.6.3: + resolution: + { + integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + jest-haste-map@29.7.0: + resolution: + { + integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + jest-leak-detector@29.7.0: + resolution: + { + integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + jest-matcher-utils@29.7.0: + resolution: + { + integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + jest-message-util@29.7.0: + resolution: + { + integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + jest-mock@29.7.0: + resolution: + { + integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + jest-pnp-resolver@1.2.3: + resolution: + { + integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==, + } + engines: { node: '>=6' } + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + + jest-regex-util@29.6.3: + resolution: + { + integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + jest-resolve-dependencies@29.7.0: + resolution: + { + integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + jest-resolve@29.7.0: + resolution: + { + integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + jest-runner@29.7.0: + resolution: + { + integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + jest-runtime@29.7.0: + resolution: + { + integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + jest-snapshot@29.7.0: + resolution: + { + integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + jest-util@29.7.0: + resolution: + { + integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + jest-validate@29.7.0: + resolution: + { + integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + jest-watcher@29.7.0: + resolution: + { + integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + jest-worker@29.7.0: + resolution: + { + integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + jest@29.7.0: + resolution: + { + integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + js-tokens@4.0.0: + resolution: + { + integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==, + } + + js-yaml@3.14.1: + resolution: + { + integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==, + } + hasBin: true + + js-yaml@4.1.0: + resolution: + { + integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==, + } + hasBin: true + + jsesc@3.0.2: + resolution: + { + integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==, + } + engines: { node: '>=6' } + hasBin: true + + jsesc@3.1.0: + resolution: + { + integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==, + } + engines: { node: '>=6' } + hasBin: true + + json-buffer@3.0.1: + resolution: + { + integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==, + } + + json-parse-even-better-errors@2.3.1: + resolution: + { + integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==, + } + + json-schema-traverse@0.4.1: + resolution: + { + integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==, + } + + json-stable-stringify-without-jsonify@1.0.1: + resolution: + { + integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==, + } + + json5@2.2.3: + resolution: + { + integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==, + } + engines: { node: '>=6' } + hasBin: true + + keyv@4.5.4: + resolution: + { + integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==, + } + + kind-of@6.0.3: + resolution: + { + integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==, + } + engines: { node: '>=0.10.0' } + + kleur@3.0.3: + resolution: + { + integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==, + } + engines: { node: '>=6' } + + leven@3.1.0: + resolution: + { + integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==, + } + engines: { node: '>=6' } + + levn@0.4.1: + resolution: + { + integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==, + } + engines: { node: '>= 0.8.0' } + + lines-and-columns@1.2.4: + resolution: + { + integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==, + } + + locate-path@3.0.0: + resolution: + { + integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==, + } + engines: { node: '>=6' } + + locate-path@5.0.0: + resolution: + { + integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==, + } + engines: { node: '>=8' } + + locate-path@6.0.0: + resolution: + { + integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==, + } + engines: { node: '>=10' } + + lodash.debounce@4.0.8: + resolution: + { + integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==, + } + + lodash.merge@4.6.2: + resolution: + { + integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==, + } + + lru-cache@5.1.1: + resolution: + { + integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==, + } + + make-dir@2.1.0: + resolution: + { + integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==, + } + engines: { node: '>=6' } + + make-dir@4.0.0: + resolution: + { + integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==, + } + engines: { node: '>=10' } + + makeerror@1.0.12: + resolution: + { + integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==, + } + + math-intrinsics@1.1.0: + resolution: + { + integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==, + } + engines: { node: '>= 0.4' } + + merge-stream@2.0.0: + resolution: + { + integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==, + } + + merge2@1.4.1: + resolution: + { + integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==, + } + engines: { node: '>= 8' } + + micromatch@4.0.8: + resolution: + { + integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==, + } + engines: { node: '>=8.6' } + + mimic-fn@2.1.0: + resolution: + { + integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==, + } + engines: { node: '>=6' } + + minimatch@3.1.2: + resolution: + { + integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==, + } + + minimatch@9.0.5: + resolution: + { + integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==, + } + engines: { node: '>=16 || 14 >=14.17' } + + ms@2.1.3: + resolution: + { + integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==, + } + + natural-compare@1.4.0: + resolution: + { + integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==, + } + + node-environment-flags@1.0.6: + resolution: + { + integrity: sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==, + } + + node-int64@0.4.0: + resolution: + { + integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==, + } + + node-releases@2.0.19: + resolution: + { + integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==, + } + + normalize-path@3.0.0: + resolution: + { + integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==, + } + engines: { node: '>=0.10.0' } + + npm-run-path@4.0.1: + resolution: + { + integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==, + } + engines: { node: '>=8' } + + object-inspect@1.13.4: + resolution: + { + integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==, + } + engines: { node: '>= 0.4' } + + object-keys@1.1.1: + resolution: + { + integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==, + } + engines: { node: '>= 0.4' } + + object.assign@4.1.7: + resolution: + { + integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==, + } + engines: { node: '>= 0.4' } + + object.getownpropertydescriptors@2.1.8: + resolution: + { + integrity: sha512-qkHIGe4q0lSYMv0XI4SsBTJz3WaURhLvd0lKSgtVuOsJ2krg4SgMw3PIRQFMp07yi++UR3se2mkcLqsBNpBb/A==, + } + engines: { node: '>= 0.8' } + + once@1.4.0: + resolution: + { + integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==, + } + + onetime@5.1.2: + resolution: + { + integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==, + } + engines: { node: '>=6' } + + optionator@0.9.4: + resolution: + { + integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==, + } + engines: { node: '>= 0.8.0' } + + own-keys@1.0.1: + resolution: + { + integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==, + } + engines: { node: '>= 0.4' } + + p-limit@2.3.0: + resolution: + { + integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==, + } + engines: { node: '>=6' } + + p-limit@3.1.0: + resolution: + { + integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==, + } + engines: { node: '>=10' } + + p-locate@3.0.0: + resolution: + { + integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==, + } + engines: { node: '>=6' } + + p-locate@4.1.0: + resolution: + { + integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==, + } + engines: { node: '>=8' } + + p-locate@5.0.0: + resolution: + { + integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==, + } + engines: { node: '>=10' } + + p-try@2.2.0: + resolution: + { + integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==, + } + engines: { node: '>=6' } + + parent-module@1.0.1: + resolution: + { + integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==, + } + engines: { node: '>=6' } + + parse-json@5.2.0: + resolution: + { + integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==, + } + engines: { node: '>=8' } + + parse-passwd@1.0.0: + resolution: + { + integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==, + } + engines: { node: '>=0.10.0' } + + path-exists@3.0.0: + resolution: + { + integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==, + } + engines: { node: '>=4' } + + path-exists@4.0.0: + resolution: + { + integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==, + } + engines: { node: '>=8' } + + path-is-absolute@1.0.1: + resolution: + { + integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==, + } + engines: { node: '>=0.10.0' } + + path-key@3.1.1: + resolution: + { + integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==, + } + engines: { node: '>=8' } + + path-parse@1.0.7: + resolution: + { + integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==, + } + + picocolors@1.1.1: + resolution: + { + integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==, + } + + picomatch@2.3.1: + resolution: + { + integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==, + } + engines: { node: '>=8.6' } + + pify@4.0.1: + resolution: + { + integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==, + } + engines: { node: '>=6' } + + pirates@4.0.7: + resolution: + { + integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==, + } + engines: { node: '>= 6' } + + pkg-dir@3.0.0: + resolution: + { + integrity: sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==, + } + engines: { node: '>=6' } + + pkg-dir@4.2.0: + resolution: + { + integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==, + } + engines: { node: '>=8' } + + possible-typed-array-names@1.1.0: + resolution: + { + integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==, + } + engines: { node: '>= 0.4' } + + prelude-ls@1.2.1: + resolution: + { + integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==, + } + engines: { node: '>= 0.8.0' } + + pretty-format@29.7.0: + resolution: + { + integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==, + } + engines: { node: ^14.15.0 || ^16.10.0 || >=18.0.0 } + + prompts@2.4.2: + resolution: + { + integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==, + } + engines: { node: '>= 6' } + + punycode@2.3.1: + resolution: + { + integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==, + } + engines: { node: '>=6' } + + pure-rand@6.1.0: + resolution: + { + integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==, + } + + queue-microtask@1.2.3: + resolution: + { + integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==, + } + + react-is@18.3.1: + resolution: + { + integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==, + } + + reflect.getprototypeof@1.0.10: + resolution: + { + integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==, + } + engines: { node: '>= 0.4' } + + regenerate-unicode-properties@10.2.0: + resolution: + { + integrity: sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==, + } + engines: { node: '>=4' } + + regenerate@1.4.2: + resolution: + { + integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==, + } + + regenerator-runtime@0.14.1: + resolution: + { + integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==, + } + + regenerator-transform@0.15.2: + resolution: + { + integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==, + } + + regexp.prototype.flags@1.5.4: + resolution: + { + integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==, + } + engines: { node: '>= 0.4' } + + regexpu-core@6.2.0: + resolution: + { + integrity: sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==, + } + engines: { node: '>=4' } + + regjsgen@0.8.0: + resolution: + { + integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==, + } + + regjsparser@0.12.0: + resolution: + { + integrity: sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==, + } + hasBin: true + + require-directory@2.1.1: + resolution: + { + integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==, + } + engines: { node: '>=0.10.0' } + + resolve-cwd@3.0.0: + resolution: + { + integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==, + } + engines: { node: '>=8' } + + resolve-from@4.0.0: + resolution: + { + integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==, + } + engines: { node: '>=4' } + + resolve-from@5.0.0: + resolution: + { + integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==, + } + engines: { node: '>=8' } + + resolve.exports@2.0.3: + resolution: + { + integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==, + } + engines: { node: '>=10' } + + resolve@1.22.10: + resolution: + { + integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==, + } + engines: { node: '>= 0.4' } + hasBin: true + + reusify@1.1.0: + resolution: + { + integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==, + } + engines: { iojs: '>=1.0.0', node: '>=0.10.0' } + + run-parallel@1.2.0: + resolution: + { + integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==, + } + + safe-array-concat@1.1.3: + resolution: + { + integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==, + } + engines: { node: '>=0.4' } + + safe-push-apply@1.0.0: + resolution: + { + integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==, + } + engines: { node: '>= 0.4' } + + safe-regex-test@1.1.0: + resolution: + { + integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==, + } + engines: { node: '>= 0.4' } + + semver@5.7.2: + resolution: + { + integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==, + } + hasBin: true + + semver@6.3.1: + resolution: + { + integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==, + } + hasBin: true + + semver@7.7.1: + resolution: + { + integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==, + } + engines: { node: '>=10' } + hasBin: true + + set-function-length@1.2.2: + resolution: + { + integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==, + } + engines: { node: '>= 0.4' } + + set-function-name@2.0.2: + resolution: + { + integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==, + } + engines: { node: '>= 0.4' } + + set-proto@1.0.0: + resolution: + { + integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==, + } + engines: { node: '>= 0.4' } + + shallow-clone@3.0.1: + resolution: + { + integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==, + } + engines: { node: '>=8' } + + shebang-command@2.0.0: + resolution: + { + integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==, + } + engines: { node: '>=8' } + + shebang-regex@3.0.0: + resolution: + { + integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==, + } + engines: { node: '>=8' } + + side-channel-list@1.0.0: + resolution: + { + integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==, + } + engines: { node: '>= 0.4' } + + side-channel-map@1.0.1: + resolution: + { + integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==, + } + engines: { node: '>= 0.4' } + + side-channel-weakmap@1.0.2: + resolution: + { + integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==, + } + engines: { node: '>= 0.4' } + + side-channel@1.1.0: + resolution: + { + integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==, + } + engines: { node: '>= 0.4' } + + signal-exit@3.0.7: + resolution: + { + integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==, + } + + sisteransi@1.0.5: + resolution: + { + integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==, + } + + slash@3.0.0: + resolution: + { + integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==, + } + engines: { node: '>=8' } + + source-map-support@0.5.13: + resolution: + { + integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==, + } + + source-map-support@0.5.21: + resolution: + { + integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==, + } + + source-map@0.6.1: + resolution: + { + integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==, + } + engines: { node: '>=0.10.0' } + + sprintf-js@1.0.3: + resolution: + { + integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==, + } + + stack-utils@2.0.6: + resolution: + { + integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==, + } + engines: { node: '>=10' } + + string-length@4.0.2: + resolution: + { + integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==, + } + engines: { node: '>=10' } + + string-width@4.2.3: + resolution: + { + integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==, + } + engines: { node: '>=8' } + + string.prototype.trim@1.2.10: + resolution: + { + integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==, + } + engines: { node: '>= 0.4' } + + string.prototype.trimend@1.0.9: + resolution: + { + integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==, + } + engines: { node: '>= 0.4' } + + string.prototype.trimstart@1.0.8: + resolution: + { + integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==, + } + engines: { node: '>= 0.4' } + + strip-ansi@6.0.1: + resolution: + { + integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==, + } + engines: { node: '>=8' } + + strip-bom@4.0.0: + resolution: + { + integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==, + } + engines: { node: '>=8' } + + strip-final-newline@2.0.0: + resolution: + { + integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==, + } + engines: { node: '>=6' } + + strip-json-comments@3.1.1: + resolution: + { + integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==, + } + engines: { node: '>=8' } + + supports-color@7.2.0: + resolution: + { + integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==, + } + engines: { node: '>=8' } + + supports-color@8.1.1: + resolution: + { + integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==, + } + engines: { node: '>=10' } + + supports-preserve-symlinks-flag@1.0.0: + resolution: + { + integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==, + } + engines: { node: '>= 0.4' } + + test-exclude@6.0.0: + resolution: + { + integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==, + } + engines: { node: '>=8' } + + tmpl@1.0.5: + resolution: + { + integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==, + } + + to-regex-range@5.0.1: + resolution: + { + integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==, + } + engines: { node: '>=8.0' } + + ts-api-utils@2.1.0: + resolution: + { + integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==, + } + engines: { node: '>=18.12' } + peerDependencies: + typescript: '>=4.8.4' + + type-check@0.4.0: + resolution: + { + integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==, + } + engines: { node: '>= 0.8.0' } + + type-detect@4.0.8: + resolution: + { + integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==, + } + engines: { node: '>=4' } + + type-fest@0.21.3: + resolution: + { + integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==, + } + engines: { node: '>=10' } + + typed-array-buffer@1.0.3: + resolution: + { + integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==, + } + engines: { node: '>= 0.4' } + + typed-array-byte-length@1.0.3: + resolution: + { + integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==, + } + engines: { node: '>= 0.4' } + + typed-array-byte-offset@1.0.4: + resolution: + { + integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==, + } + engines: { node: '>= 0.4' } + + typed-array-length@1.0.7: + resolution: + { + integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==, + } + engines: { node: '>= 0.4' } + + typescript@5.8.3: + resolution: + { + integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==, + } + engines: { node: '>=14.17' } + hasBin: true + + unbox-primitive@1.1.0: + resolution: + { + integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==, + } + engines: { node: '>= 0.4' } + + undici-types@6.21.0: + resolution: + { + integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==, + } + + unicode-canonical-property-names-ecmascript@2.0.1: + resolution: + { + integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==, + } + engines: { node: '>=4' } + + unicode-match-property-ecmascript@2.0.0: + resolution: + { + integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==, + } + engines: { node: '>=4' } + + unicode-match-property-value-ecmascript@2.2.0: + resolution: + { + integrity: sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==, + } + engines: { node: '>=4' } + + unicode-property-aliases-ecmascript@2.1.0: + resolution: + { + integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==, + } + engines: { node: '>=4' } + + update-browserslist-db@1.1.3: + resolution: + { + integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==, + } + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: + { + integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==, + } + + v8-to-istanbul@9.3.0: + resolution: + { + integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==, + } + engines: { node: '>=10.12.0' } + + v8flags@3.2.0: + resolution: + { + integrity: sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==, + } + engines: { node: '>= 0.10' } + + walker@1.0.8: + resolution: + { + integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==, + } + + which-boxed-primitive@1.1.1: + resolution: + { + integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==, + } + engines: { node: '>= 0.4' } + + which-builtin-type@1.2.1: + resolution: + { + integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==, + } + engines: { node: '>= 0.4' } + + which-collection@1.0.2: + resolution: + { + integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==, + } + engines: { node: '>= 0.4' } + + which-typed-array@1.1.19: + resolution: + { + integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==, + } + engines: { node: '>= 0.4' } + + which@2.0.2: + resolution: + { + integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==, + } + engines: { node: '>= 8' } + hasBin: true + + word-wrap@1.2.5: + resolution: + { + integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==, + } + engines: { node: '>=0.10.0' } + + wrap-ansi@7.0.0: + resolution: + { + integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==, + } + engines: { node: '>=10' } + + wrappy@1.0.2: + resolution: + { + integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==, + } + + write-file-atomic@4.0.2: + resolution: + { + integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==, + } + engines: { node: ^12.13.0 || ^14.15.0 || >=16.0.0 } + + y18n@5.0.8: + resolution: + { + integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==, + } + engines: { node: '>=10' } + + yallist@3.1.1: + resolution: + { + integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==, + } + + yargs-parser@21.1.1: + resolution: + { + integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==, + } + engines: { node: '>=12' } + + yargs@17.7.2: + resolution: + { + integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==, + } + engines: { node: '>=12' } + + yocto-queue@0.1.0: + resolution: + { + integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==, + } + engines: { node: '>=10' } + +snapshots: + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + + '@babel/code-frame@7.26.2': + dependencies: + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.26.8': {} + + '@babel/core@7.26.10': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.27.0 + '@babel/helper-compilation-targets': 7.27.0 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) + '@babel/helpers': 7.27.0 + '@babel/parser': 7.27.0 + '@babel/template': 7.27.0 + '@babel/traverse': 7.27.0 + '@babel/types': 7.27.0 + convert-source-map: 2.0.0 + debug: 4.4.0 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/eslint-parser@7.27.0(@babel/core@7.26.10)(eslint@9.25.1)': + dependencies: + '@babel/core': 7.26.10 + '@nicolo-ribaudo/eslint-scope-5-internals': 5.1.1-v1 + eslint: 9.25.1 + eslint-visitor-keys: 2.1.0 + semver: 6.3.1 + + '@babel/eslint-plugin@7.27.0(@babel/eslint-parser@7.27.0(@babel/core@7.26.10)(eslint@9.25.1))(eslint@9.25.1)': + dependencies: + '@babel/eslint-parser': 7.27.0(@babel/core@7.26.10)(eslint@9.25.1) + eslint: 9.25.1 + eslint-rule-composer: 0.3.0 + + '@babel/generator@7.27.0': + dependencies: + '@babel/parser': 7.27.0 + '@babel/types': 7.27.0 + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.25.9': + dependencies: + '@babel/types': 7.27.0 + + '@babel/helper-compilation-targets@7.27.0': + dependencies: + '@babel/compat-data': 7.26.8 + '@babel/helper-validator-option': 7.25.9 + browserslist: 4.24.4 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.27.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-member-expression-to-functions': 7.25.9 + '@babel/helper-optimise-call-expression': 7.25.9 + '@babel/helper-replace-supers': 7.26.5(@babel/core@7.26.10) + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + '@babel/traverse': 7.27.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-create-regexp-features-plugin@7.27.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-annotate-as-pure': 7.25.9 + regexpu-core: 6.2.0 + semver: 6.3.1 + + '@babel/helper-define-polyfill-provider@0.6.4(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-compilation-targets': 7.27.0 + '@babel/helper-plugin-utils': 7.26.5 + debug: 4.4.0 + lodash.debounce: 4.0.8 + resolve: 1.22.10 + transitivePeerDependencies: + - supports-color + + '@babel/helper-member-expression-to-functions@7.25.9': + dependencies: + '@babel/traverse': 7.27.0 + '@babel/types': 7.27.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.25.9': + dependencies: + '@babel/traverse': 7.27.0 + '@babel/types': 7.27.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.27.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.25.9': + dependencies: + '@babel/types': 7.27.0 + + '@babel/helper-plugin-utils@7.26.5': {} + + '@babel/helper-remap-async-to-generator@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-wrap-function': 7.25.9 + '@babel/traverse': 7.27.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-replace-supers@7.26.5(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-member-expression-to-functions': 7.25.9 + '@babel/helper-optimise-call-expression': 7.25.9 + '@babel/traverse': 7.27.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.25.9': + dependencies: + '@babel/traverse': 7.27.0 + '@babel/types': 7.27.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.25.9': {} + + '@babel/helper-validator-identifier@7.25.9': {} + + '@babel/helper-validator-option@7.25.9': {} + + '@babel/helper-wrap-function@7.25.9': + dependencies: + '@babel/template': 7.27.0 + '@babel/traverse': 7.27.0 + '@babel/types': 7.27.0 + transitivePeerDependencies: + - supports-color + + '@babel/helpers@7.27.0': + dependencies: + '@babel/template': 7.27.0 + '@babel/types': 7.27.0 + + '@babel/node@7.26.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/register': 7.25.9(@babel/core@7.26.10) + commander: 6.2.1 + core-js: 3.40.0 + node-environment-flags: 1.0.6 + regenerator-runtime: 0.14.1 + v8flags: 3.2.0 + + '@babel/parser@7.27.0': + dependencies: + '@babel/types': 7.27.0 + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/traverse': 7.27.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.26.10) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/traverse': 7.27.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-import-assertions@7.26.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.27.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-arrow-functions@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-async-generator-functions@7.26.8(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.10) + '@babel/traverse': 7.27.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-async-to-generator@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.10) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-block-scoped-functions@7.26.5(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-block-scoping@7.27.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-class-properties@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-class-features-plugin': 7.27.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-class-static-block@7.26.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-class-features-plugin': 7.27.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-classes@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-compilation-targets': 7.27.0 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-replace-supers': 7.26.5(@babel/core@7.26.10) + '@babel/traverse': 7.27.0 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-computed-properties@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/template': 7.27.0 + + '@babel/plugin-transform-destructuring@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-dotall-regex@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.27.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-duplicate-keys@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.27.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-dynamic-import@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-exponentiation-operator@7.26.3(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-export-namespace-from@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-for-of@7.26.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-function-name@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-compilation-targets': 7.27.0 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/traverse': 7.27.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-json-strings@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-literals@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-logical-assignment-operators@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-member-expression-literals@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-modules-amd@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-commonjs@7.26.3(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-systemjs@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.27.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-umd@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-named-capturing-groups-regex@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.27.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-new-target@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-nullish-coalescing-operator@7.26.6(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-numeric-separator@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-object-rest-spread@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-compilation-targets': 7.27.0 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.26.10) + + '@babel/plugin-transform-object-super@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-replace-supers': 7.26.5(@babel/core@7.26.10) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-optional-catch-binding@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-optional-chaining@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-parameters@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-private-methods@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-class-features-plugin': 7.27.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-private-property-in-object@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-create-class-features-plugin': 7.27.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-property-literals@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-regenerator@7.27.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + regenerator-transform: 0.15.2 + + '@babel/plugin-transform-regexp-modifiers@7.26.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.27.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-reserved-words@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-shorthand-properties@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-spread@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-sticky-regex@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-template-literals@7.26.8(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-typeof-symbol@7.27.0(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-unicode-escapes@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-unicode-property-regex@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.27.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-unicode-regex@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.27.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-unicode-sets-regex@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-create-regexp-features-plugin': 7.27.0(@babel/core@7.26.10) + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/preset-env@7.26.9(@babel/core@7.26.10)': + dependencies: + '@babel/compat-data': 7.26.8 + '@babel/core': 7.26.10 + '@babel/helper-compilation-targets': 7.27.0 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/helper-validator-option': 7.25.9 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.26.10) + '@babel/plugin-syntax-import-assertions': 7.26.0(@babel/core@7.26.10) + '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.10) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.26.10) + '@babel/plugin-transform-arrow-functions': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-async-generator-functions': 7.26.8(@babel/core@7.26.10) + '@babel/plugin-transform-async-to-generator': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-block-scoped-functions': 7.26.5(@babel/core@7.26.10) + '@babel/plugin-transform-block-scoping': 7.27.0(@babel/core@7.26.10) + '@babel/plugin-transform-class-properties': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-class-static-block': 7.26.0(@babel/core@7.26.10) + '@babel/plugin-transform-classes': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-computed-properties': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-destructuring': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-dotall-regex': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-duplicate-keys': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-dynamic-import': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-exponentiation-operator': 7.26.3(@babel/core@7.26.10) + '@babel/plugin-transform-export-namespace-from': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-for-of': 7.26.9(@babel/core@7.26.10) + '@babel/plugin-transform-function-name': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-json-strings': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-literals': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-logical-assignment-operators': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-member-expression-literals': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-modules-amd': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-modules-commonjs': 7.26.3(@babel/core@7.26.10) + '@babel/plugin-transform-modules-systemjs': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-modules-umd': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-named-capturing-groups-regex': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-new-target': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-nullish-coalescing-operator': 7.26.6(@babel/core@7.26.10) + '@babel/plugin-transform-numeric-separator': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-object-rest-spread': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-object-super': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-optional-catch-binding': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-private-property-in-object': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-property-literals': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-regenerator': 7.27.0(@babel/core@7.26.10) + '@babel/plugin-transform-regexp-modifiers': 7.26.0(@babel/core@7.26.10) + '@babel/plugin-transform-reserved-words': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-shorthand-properties': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-spread': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-sticky-regex': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-template-literals': 7.26.8(@babel/core@7.26.10) + '@babel/plugin-transform-typeof-symbol': 7.27.0(@babel/core@7.26.10) + '@babel/plugin-transform-unicode-escapes': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-unicode-property-regex': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-unicode-regex': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-unicode-sets-regex': 7.25.9(@babel/core@7.26.10) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.26.10) + babel-plugin-polyfill-corejs2: 0.4.13(@babel/core@7.26.10) + babel-plugin-polyfill-corejs3: 0.11.1(@babel/core@7.26.10) + babel-plugin-polyfill-regenerator: 0.6.4(@babel/core@7.26.10) + core-js-compat: 3.41.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + '@babel/types': 7.27.0 + esutils: 2.0.3 + + '@babel/register@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + clone-deep: 4.0.1 + find-cache-dir: 2.1.0 + make-dir: 2.1.0 + pirates: 4.0.7 + source-map-support: 0.5.21 + + '@babel/runtime@7.27.0': + dependencies: + regenerator-runtime: 0.14.1 + + '@babel/template@7.27.0': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/parser': 7.27.0 + '@babel/types': 7.27.0 + + '@babel/traverse@7.27.0': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.27.0 + '@babel/parser': 7.27.0 + '@babel/template': 7.27.0 + '@babel/types': 7.27.0 + debug: 4.4.0 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.27.0': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + + '@bcoe/v8-coverage@0.2.3': {} + + '@eslint-community/eslint-utils@4.6.1(eslint@9.25.1)': + dependencies: + eslint: 9.25.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/config-array@0.20.0': + dependencies: + '@eslint/object-schema': 2.1.6 + debug: 4.4.0 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.2.1': {} + + '@eslint/core@0.13.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.1': + dependencies: + ajv: 6.12.6 + debug: 4.4.0 + espree: 10.3.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.25.1': {} + + '@eslint/object-schema@2.1.6': {} + + '@eslint/plugin-kit@0.2.8': + dependencies: + '@eslint/core': 0.13.0 + levn: 0.4.1 + + '@exercism/babel-preset-javascript@0.5.1': + dependencies: + '@babel/core': 7.26.10 + '@babel/node': 7.26.0(@babel/core@7.26.10) + '@babel/preset-env': 7.26.9(@babel/core@7.26.10) + core-js: 3.38.1 + transitivePeerDependencies: + - supports-color + + '@exercism/eslint-config-javascript@0.8.1(@babel/core@7.26.10)(@exercism/babel-preset-javascript@0.5.1)(eslint@9.25.1)(jest@29.7.0(@types/node@22.15.2))(typescript@5.8.3)': + dependencies: + '@babel/eslint-parser': 7.27.0(@babel/core@7.26.10)(eslint@9.25.1) + '@babel/eslint-plugin': 7.27.0(@babel/eslint-parser@7.27.0(@babel/core@7.26.10)(eslint@9.25.1))(eslint@9.25.1) + '@eslint/js': 9.25.1 + '@exercism/babel-preset-javascript': 0.5.1 + eslint: 9.25.1 + eslint-config-prettier: 9.1.0(eslint@9.25.1) + eslint-plugin-jest: 28.11.0(eslint@9.25.1)(jest@29.7.0(@types/node@22.15.2))(typescript@5.8.3) + globals: 15.15.0 + transitivePeerDependencies: + - '@babel/core' + - '@typescript-eslint/eslint-plugin' + - jest + - supports-color + - typescript + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.6': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.3.1': {} + + '@humanwhocodes/retry@0.4.2': {} + + '@istanbuljs/load-nyc-config@1.1.0': + dependencies: + camelcase: 5.3.1 + find-up: 4.1.0 + get-package-type: 0.1.0 + js-yaml: 3.14.1 + resolve-from: 5.0.0 + + '@istanbuljs/schema@0.1.3': {} + + '@jest/console@29.7.0': + dependencies: + '@jest/types': 29.6.3 + '@types/node': 22.15.2 + chalk: 4.1.2 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + + '@jest/core@29.7.0': + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.15.2 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@22.15.2) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + + '@jest/environment@29.7.0': + dependencies: + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.15.2 + jest-mock: 29.7.0 + + '@jest/expect-utils@29.7.0': + dependencies: + jest-get-type: 29.6.3 + + '@jest/expect@29.7.0': + dependencies: + expect: 29.7.0 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + + '@jest/fake-timers@29.7.0': + dependencies: + '@jest/types': 29.6.3 + '@sinonjs/fake-timers': 10.3.0 + '@types/node': 22.15.2 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + '@jest/globals@29.7.0': + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/types': 29.6.3 + jest-mock: 29.7.0 + transitivePeerDependencies: + - supports-color + + '@jest/reporters@29.7.0': + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.25 + '@types/node': 22.15.2 + chalk: 4.1.2 + collect-v8-coverage: 1.0.2 + exit: 0.1.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-instrument: 6.0.3 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 4.0.1 + istanbul-reports: 3.1.7 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + jest-worker: 29.7.0 + slash: 3.0.0 + string-length: 4.0.2 + strip-ansi: 6.0.1 + v8-to-istanbul: 9.3.0 + transitivePeerDependencies: + - supports-color + + '@jest/schemas@29.6.3': + dependencies: + '@sinclair/typebox': 0.27.8 + + '@jest/source-map@29.6.3': + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + callsites: 3.1.0 + graceful-fs: 4.2.11 + + '@jest/test-result@29.7.0': + dependencies: + '@jest/console': 29.7.0 + '@jest/types': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + collect-v8-coverage: 1.0.2 + + '@jest/test-sequencer@29.7.0': + dependencies: + '@jest/test-result': 29.7.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + slash: 3.0.0 + + '@jest/transform@29.7.0': + dependencies: + '@babel/core': 7.26.10 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.25 + babel-plugin-istanbul: 6.1.1 + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + micromatch: 4.0.8 + pirates: 4.0.7 + slash: 3.0.0 + write-file-atomic: 4.0.2 + transitivePeerDependencies: + - supports-color + + '@jest/types@29.6.3': + dependencies: + '@jest/schemas': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 22.15.2 + '@types/yargs': 17.0.33 + chalk: 4.1.2 + + '@jridgewell/gen-mapping@0.3.8': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': + dependencies: + eslint-scope: 5.1.1 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + + '@sinclair/typebox@0.27.8': {} + + '@sinonjs/commons@3.0.1': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/fake-timers@10.3.0': + dependencies: + '@sinonjs/commons': 3.0.1 + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.27.0 + '@babel/types': 7.27.0 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.20.7 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.27.0 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.27.0 + '@babel/types': 7.27.0 + + '@types/babel__traverse@7.20.7': + dependencies: + '@babel/types': 7.27.0 + + '@types/estree@1.0.7': {} + + '@types/glob@7.2.0': + dependencies: + '@types/minimatch': 5.1.2 + '@types/node': 22.15.2 + + '@types/graceful-fs@4.1.9': + dependencies: + '@types/node': 22.15.2 + + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/istanbul-lib-report@3.0.3': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + + '@types/istanbul-reports@3.0.4': + dependencies: + '@types/istanbul-lib-report': 3.0.3 + + '@types/json-schema@7.0.15': {} + + '@types/minimatch@5.1.2': {} + + '@types/node@22.15.2': + dependencies: + undici-types: 6.21.0 + + '@types/shelljs@0.8.15': + dependencies: + '@types/glob': 7.2.0 + '@types/node': 22.15.2 + + '@types/stack-utils@2.0.3': {} + + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.33': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@typescript-eslint/scope-manager@8.31.0': + dependencies: + '@typescript-eslint/types': 8.31.0 + '@typescript-eslint/visitor-keys': 8.31.0 + + '@typescript-eslint/types@8.31.0': {} + + '@typescript-eslint/typescript-estree@8.31.0(typescript@5.8.3)': + dependencies: + '@typescript-eslint/types': 8.31.0 + '@typescript-eslint/visitor-keys': 8.31.0 + debug: 4.4.0 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.1 + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.31.0(eslint@9.25.1)(typescript@5.8.3)': + dependencies: + '@eslint-community/eslint-utils': 4.6.1(eslint@9.25.1) + '@typescript-eslint/scope-manager': 8.31.0 + '@typescript-eslint/types': 8.31.0 + '@typescript-eslint/typescript-estree': 8.31.0(typescript@5.8.3) + eslint: 9.25.1 + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.31.0': + dependencies: + '@typescript-eslint/types': 8.31.0 + eslint-visitor-keys: 4.2.0 + + acorn-jsx@5.3.2(acorn@8.14.1): + dependencies: + acorn: 8.14.1 + + acorn@8.14.1: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array.prototype.reduce@1.0.8: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-array-method-boxes-properly: 1.0.0 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + is-string: 1.1.1 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + + async-function@1.0.0: {} + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + babel-jest@29.7.0(@babel/core@7.26.10): + dependencies: + '@babel/core': 7.26.10 + '@jest/transform': 29.7.0 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.6.3(@babel/core@7.26.10) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-istanbul@6.1.1: + dependencies: + '@babel/helper-plugin-utils': 7.26.5 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-instrument: 5.2.1 + test-exclude: 6.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-jest-hoist@29.6.3: + dependencies: + '@babel/template': 7.27.0 + '@babel/types': 7.27.0 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.20.7 + + babel-plugin-polyfill-corejs2@0.4.13(@babel/core@7.26.10): + dependencies: + '@babel/compat-data': 7.26.8 + '@babel/core': 7.26.10 + '@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.26.10) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-corejs3@0.11.1(@babel/core@7.26.10): + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.26.10) + core-js-compat: 3.41.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-regenerator@0.6.4(@babel/core@7.26.10): + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.26.10) + transitivePeerDependencies: + - supports-color + + babel-preset-current-node-syntax@1.1.0(@babel/core@7.26.10): + dependencies: + '@babel/core': 7.26.10 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.26.10) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.26.10) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.26.10) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.26.10) + '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.10) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.26.10) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.26.10) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.26.10) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.26.10) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.26.10) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.26.10) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.26.10) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.26.10) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.26.10) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.26.10) + + babel-preset-jest@29.6.3(@babel/core@7.26.10): + dependencies: + '@babel/core': 7.26.10 + babel-plugin-jest-hoist: 29.6.3 + babel-preset-current-node-syntax: 1.1.0(@babel/core@7.26.10) + + balanced-match@1.0.2: {} + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.24.4: + dependencies: + caniuse-lite: 1.0.30001715 + electron-to-chromium: 1.5.143 + node-releases: 2.0.19 + update-browserslist-db: 1.1.3(browserslist@4.24.4) + + bser@2.1.1: + dependencies: + node-int64: 0.4.0 + + buffer-from@1.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + camelcase@5.3.1: {} + + camelcase@6.3.0: {} + + caniuse-lite@1.0.30001715: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + char-regex@1.0.2: {} + + ci-info@3.9.0: {} + + cjs-module-lexer@1.4.3: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clone-deep@4.0.1: + dependencies: + is-plain-object: 2.0.4 + kind-of: 6.0.3 + shallow-clone: 3.0.1 + + co@4.6.0: {} + + collect-v8-coverage@1.0.2: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + commander@6.2.1: {} + + commondir@1.0.1: {} + + concat-map@0.0.1: {} + + convert-source-map@2.0.0: {} + + core-js-compat@3.41.0: + dependencies: + browserslist: 4.24.4 + + core-js@3.38.1: {} + + core-js@3.40.0: {} + + create-jest@29.7.0(@types/node@22.15.2): + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@22.15.2) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + debug@4.4.0: + dependencies: + ms: 2.1.3 + + dedent@1.5.3: {} + + deep-is@0.1.4: {} + + deepmerge@4.3.1: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + detect-newline@3.1.0: {} + + diff-sequences@29.6.3: {} + + diff@7.0.0: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + electron-to-chromium@1.5.143: {} + + emittery@0.13.1: {} + + emoji-regex@8.0.0: {} + + error-ex@1.3.2: + dependencies: + is-arrayish: 0.2.1 + + es-abstract@1.23.9: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-regex: 1.2.1 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.19 + + es-array-method-boxes-properly@1.0.0: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + + escalade@3.2.0: {} + + escape-string-regexp@2.0.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-config-prettier@9.1.0(eslint@9.25.1): + dependencies: + eslint: 9.25.1 + + eslint-plugin-jest@28.11.0(eslint@9.25.1)(jest@29.7.0(@types/node@22.15.2))(typescript@5.8.3): + dependencies: + '@typescript-eslint/utils': 8.31.0(eslint@9.25.1)(typescript@5.8.3) + eslint: 9.25.1 + optionalDependencies: + jest: 29.7.0(@types/node@22.15.2) + transitivePeerDependencies: + - supports-color + - typescript + + eslint-rule-composer@0.3.0: {} + + eslint-scope@5.1.1: + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + + eslint-scope@8.3.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@2.1.0: {} + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.0: {} + + eslint@9.25.1: + dependencies: + '@eslint-community/eslint-utils': 4.6.1(eslint@9.25.1) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.20.0 + '@eslint/config-helpers': 0.2.1 + '@eslint/core': 0.13.0 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.25.1 + '@eslint/plugin-kit': 0.2.8 + '@humanfs/node': 0.16.6 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.2 + '@types/estree': 1.0.7 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.0 + escape-string-regexp: 4.0.0 + eslint-scope: 8.3.0 + eslint-visitor-keys: 4.2.0 + espree: 10.3.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.3.0: + dependencies: + acorn: 8.14.1 + acorn-jsx: 5.3.2(acorn@8.14.1) + eslint-visitor-keys: 4.2.0 + + esprima@4.0.1: {} + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@4.3.0: {} + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + exit@0.1.2: {} + + expect@29.7.0: + dependencies: + '@jest/expect-utils': 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + fb-watchman@2.0.2: + dependencies: + bser: 2.1.1 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-cache-dir@2.1.0: + dependencies: + commondir: 1.0.1 + make-dir: 2.1.0 + pkg-dir: 3.0.0 + + find-up@3.0.0: + dependencies: + locate-path: 3.0.0 + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.2 + is-callable: 1.2.7 + + functions-have-names@1.2.3: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-package-type@0.1.0: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@6.0.1: {} + + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + globals@11.12.0: {} + + globals@14.0.0: {} + + globals@15.15.0: {} + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + has-bigints@1.1.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + homedir-polyfill@1.0.3: + dependencies: + parse-passwd: 1.0.0 + + html-escaper@2.0.2: {} + + human-signals@2.1.0: {} + + ignore@5.3.2: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-local@3.2.0: + dependencies: + pkg-dir: 4.2.0 + resolve-cwd: 3.0.0 + + imurmurhash@0.1.4: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-arrayish@0.2.1: {} + + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-callable@1.2.7: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-fullwidth-code-point@3.0.0: {} + + is-generator-fn@2.1.0: {} + + is-generator-function@1.1.0: + dependencies: + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-map@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-number@7.0.0: {} + + is-plain-object@2.0.4: + dependencies: + isobject: 3.0.1 + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-stream@2.0.1: {} + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.19 + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + isobject@3.0.1: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-instrument@5.2.1: + dependencies: + '@babel/core': 7.26.10 + '@babel/parser': 7.27.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + istanbul-lib-instrument@6.0.3: + dependencies: + '@babel/core': 7.26.10 + '@babel/parser': 7.27.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 7.7.1 + transitivePeerDependencies: + - supports-color + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@4.0.1: + dependencies: + debug: 4.4.0 + istanbul-lib-coverage: 3.2.2 + source-map: 0.6.1 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.1.7: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + jest-changed-files@29.7.0: + dependencies: + execa: 5.1.1 + jest-util: 29.7.0 + p-limit: 3.1.0 + + jest-circus@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.15.2 + chalk: 4.1.2 + co: 4.6.0 + dedent: 1.5.3 + is-generator-fn: 2.1.0 + jest-each: 29.7.0 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + p-limit: 3.1.0 + pretty-format: 29.7.0 + pure-rand: 6.1.0 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-cli@29.7.0(@types/node@22.15.2): + dependencies: + '@jest/core': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0(@types/node@22.15.2) + exit: 0.1.2 + import-local: 3.2.0 + jest-config: 29.7.0(@types/node@22.15.2) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + jest-config@29.7.0(@types/node@22.15.2): + dependencies: + '@babel/core': 7.26.10 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.26.10) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 22.15.2 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-diff@29.7.0: + dependencies: + chalk: 4.1.2 + diff-sequences: 29.6.3 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-docblock@29.7.0: + dependencies: + detect-newline: 3.1.0 + + jest-each@29.7.0: + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + jest-get-type: 29.6.3 + jest-util: 29.7.0 + pretty-format: 29.7.0 + + jest-environment-node@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.15.2 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + jest-get-type@29.6.3: {} + + jest-haste-map@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/graceful-fs': 4.1.9 + '@types/node': 22.15.2 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + jest-worker: 29.7.0 + micromatch: 4.0.8 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 + + jest-leak-detector@29.7.0: + dependencies: + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-matcher-utils@29.7.0: + dependencies: + chalk: 4.1.2 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-message-util@29.7.0: + dependencies: + '@babel/code-frame': 7.26.2 + '@jest/types': 29.6.3 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + stack-utils: 2.0.6 + + jest-mock@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 22.15.2 + jest-util: 29.7.0 + + jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): + optionalDependencies: + jest-resolve: 29.7.0 + + jest-regex-util@29.6.3: {} + + jest-resolve-dependencies@29.7.0: + dependencies: + jest-regex-util: 29.6.3 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + + jest-resolve@29.7.0: + dependencies: + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0) + jest-util: 29.7.0 + jest-validate: 29.7.0 + resolve: 1.22.10 + resolve.exports: 2.0.3 + slash: 3.0.0 + + jest-runner@29.7.0: + dependencies: + '@jest/console': 29.7.0 + '@jest/environment': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.15.2 + chalk: 4.1.2 + emittery: 0.13.1 + graceful-fs: 4.2.11 + jest-docblock: 29.7.0 + jest-environment-node: 29.7.0 + jest-haste-map: 29.7.0 + jest-leak-detector: 29.7.0 + jest-message-util: 29.7.0 + jest-resolve: 29.7.0 + jest-runtime: 29.7.0 + jest-util: 29.7.0 + jest-watcher: 29.7.0 + jest-worker: 29.7.0 + p-limit: 3.1.0 + source-map-support: 0.5.13 + transitivePeerDependencies: + - supports-color + + jest-runtime@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/globals': 29.7.0 + '@jest/source-map': 29.6.3 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.15.2 + chalk: 4.1.2 + cjs-module-lexer: 1.4.3 + collect-v8-coverage: 1.0.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color + + jest-snapshot@29.7.0: + dependencies: + '@babel/core': 7.26.10 + '@babel/generator': 7.27.0 + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.10) + '@babel/types': 7.27.0 + '@jest/expect-utils': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-preset-current-node-syntax: 1.1.0(@babel/core@7.26.10) + chalk: 4.1.2 + expect: 29.7.0 + graceful-fs: 4.2.11 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + natural-compare: 1.4.0 + pretty-format: 29.7.0 + semver: 7.7.1 + transitivePeerDependencies: + - supports-color + + jest-util@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 22.15.2 + chalk: 4.1.2 + ci-info: 3.9.0 + graceful-fs: 4.2.11 + picomatch: 2.3.1 + + jest-validate@29.7.0: + dependencies: + '@jest/types': 29.6.3 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 29.6.3 + leven: 3.1.0 + pretty-format: 29.7.0 + + jest-watcher@29.7.0: + dependencies: + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 22.15.2 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.13.1 + jest-util: 29.7.0 + string-length: 4.0.2 + + jest-worker@29.7.0: + dependencies: + '@types/node': 22.15.2 + jest-util: 29.7.0 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + jest@29.7.0(@types/node@22.15.2): + dependencies: + '@jest/core': 29.7.0 + '@jest/types': 29.6.3 + import-local: 3.2.0 + jest-cli: 29.7.0(@types/node@22.15.2) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + js-tokens@4.0.0: {} + + js-yaml@3.14.1: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsesc@3.0.2: {} + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kind-of@6.0.3: {} + + kleur@3.0.3: {} + + leven@3.1.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lines-and-columns@1.2.4: {} + + locate-path@3.0.0: + dependencies: + p-locate: 3.0.0 + path-exists: 3.0.0 + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.debounce@4.0.8: {} + + lodash.merge@4.6.2: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + make-dir@2.1.0: + dependencies: + pify: 4.0.1 + semver: 5.7.2 + + make-dir@4.0.0: + dependencies: + semver: 7.7.1 + + makeerror@1.0.12: + dependencies: + tmpl: 1.0.5 + + math-intrinsics@1.1.0: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mimic-fn@2.1.0: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + + ms@2.1.3: {} + + natural-compare@1.4.0: {} + + node-environment-flags@1.0.6: + dependencies: + object.getownpropertydescriptors: 2.1.8 + semver: 5.7.2 + + node-int64@0.4.0: {} + + node-releases@2.0.19: {} + + normalize-path@3.0.0: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.getownpropertydescriptors@2.1.8: + dependencies: + array.prototype.reduce: 1.0.8 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-object-atoms: 1.1.1 + gopd: 1.2.0 + safe-array-concat: 1.1.3 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@3.0.0: + dependencies: + p-limit: 2.3.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-try@2.2.0: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.26.2 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse-passwd@1.0.0: {} + + path-exists@3.0.0: {} + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + pify@4.0.1: {} + + pirates@4.0.7: {} + + pkg-dir@3.0.0: + dependencies: + find-up: 3.0.0 + + pkg-dir@4.2.0: + dependencies: + find-up: 4.1.0 + + possible-typed-array-names@1.1.0: {} + + prelude-ls@1.2.1: {} + + pretty-format@29.7.0: + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.3.1 + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + punycode@2.3.1: {} + + pure-rand@6.1.0: {} + + queue-microtask@1.2.3: {} + + react-is@18.3.1: {} + + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regenerate-unicode-properties@10.2.0: + dependencies: + regenerate: 1.4.2 + + regenerate@1.4.2: {} + + regenerator-runtime@0.14.1: {} + + regenerator-transform@0.15.2: + dependencies: + '@babel/runtime': 7.27.0 + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + + regexpu-core@6.2.0: + dependencies: + regenerate: 1.4.2 + regenerate-unicode-properties: 10.2.0 + regjsgen: 0.8.0 + regjsparser: 0.12.0 + unicode-match-property-ecmascript: 2.0.0 + unicode-match-property-value-ecmascript: 2.2.0 + + regjsgen@0.8.0: {} + + regjsparser@0.12.0: + dependencies: + jsesc: 3.0.2 + + require-directory@2.1.1: {} + + resolve-cwd@3.0.0: + dependencies: + resolve-from: 5.0.0 + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve.exports@2.0.3: {} + + resolve@1.22.10: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.1.0: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-array-concat@1.1.3: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + semver@5.7.2: {} + + semver@6.3.1: {} + + semver@7.7.1: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + + shallow-clone@3.0.1: + dependencies: + kind-of: 6.0.3 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@3.0.7: {} + + sisteransi@1.0.5: {} + + slash@3.0.0: {} + + source-map-support@0.5.13: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + sprintf-js@1.0.3: {} + + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + + string-length@4.0.2: + dependencies: + char-regex: 1.0.2 + strip-ansi: 6.0.1 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-object-atoms: 1.1.1 + has-property-descriptors: 1.0.2 + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-bom@4.0.0: {} + + strip-final-newline@2.0.0: {} + + strip-json-comments@3.1.1: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + test-exclude@6.0.0: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 7.2.3 + minimatch: 3.1.2 + + tmpl@1.0.5: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + ts-api-utils@2.1.0(typescript@5.8.3): + dependencies: + typescript: 5.8.3 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-detect@4.0.8: {} + + type-fest@0.21.3: {} + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.7: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + + typescript@5.8.3: {} + + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + + undici-types@6.21.0: {} + + unicode-canonical-property-names-ecmascript@2.0.1: {} + + unicode-match-property-ecmascript@2.0.0: + dependencies: + unicode-canonical-property-names-ecmascript: 2.0.1 + unicode-property-aliases-ecmascript: 2.1.0 + + unicode-match-property-value-ecmascript@2.2.0: {} + + unicode-property-aliases-ecmascript@2.1.0: {} + + update-browserslist-db@1.1.3(browserslist@4.24.4): + dependencies: + browserslist: 4.24.4 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + v8-to-istanbul@9.3.0: + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + '@types/istanbul-lib-coverage': 2.0.6 + convert-source-map: 2.0.0 + + v8flags@3.2.0: + dependencies: + homedir-polyfill: 1.0.3 + + walker@1.0.8: + dependencies: + makeerror: 1.0.12 + + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.0 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.19 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.19: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrappy@1.0.2: {} + + write-file-atomic@4.0.2: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 3.0.7 + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yocto-queue@0.1.0: {} diff --git a/exercises/practice/relative-distance/relative-distance.js b/exercises/practice/relative-distance/relative-distance.js new file mode 100644 index 0000000000..83a8733fb2 --- /dev/null +++ b/exercises/practice/relative-distance/relative-distance.js @@ -0,0 +1,3 @@ +export const degreesOfSeparation = (familyTree, personA, personB) => { + throw new Error('Remove this line and implement the function'); +}; diff --git a/exercises/practice/relative-distance/relative-distance.spec.js b/exercises/practice/relative-distance/relative-distance.spec.js new file mode 100644 index 0000000000..57d5b7187f --- /dev/null +++ b/exercises/practice/relative-distance/relative-distance.spec.js @@ -0,0 +1,215 @@ +import { describe, expect, test, xtest } from '@jest/globals'; +import { degreesOfSeparation } from './relative-distance'; + +describe('RelativeDistance', () => { + test('Direct parent-child relation', () => { + const familyTree = { + Vera: ['Tomoko'], + Tomoko: ['Aditi'], + }; + const results = degreesOfSeparation(familyTree, 'Vera', 'Tomoko'); + expect(results).toEqual(1); + }); + + xtest('Sibling relationship', () => { + const familyTree = { + Dalia: ['Olga', 'Yassin'], + }; + const results = degreesOfSeparation(familyTree, 'Olga', 'Yassin'); + expect(results).toEqual(1); + }); + + xtest('Two degrees of separation, grandchild', () => { + const familyTree = { + Khadija: ['Mateo'], + Mateo: ['Rami'], + }; + const results = degreesOfSeparation(familyTree, 'Khadija', 'Rami'); + expect(results).toEqual(2); + }); + + xtest('Unrelated individuals', () => { + const familyTree = { + Priya: ['Rami'], + Kaito: ['Elif'], + }; + const results = degreesOfSeparation(familyTree, 'Priya', 'Kaito'); + expect(results).toEqual(-1); + }); + + xtest('Complex graph, cousins', () => { + const familyTree = { + Aiko: ['Bao', 'Carlos'], + Bao: ['Dalia', 'Elias'], + Carlos: ['Fatima', 'Gustavo'], + Dalia: ['Hassan', 'Isla'], + Elias: ['Javier'], + Fatima: ['Khadija', 'Liam'], + Gustavo: ['Mina'], + Hassan: ['Noah', 'Olga'], + Isla: ['Pedro'], + Javier: ['Quynh', 'Ravi'], + Khadija: ['Sofia'], + Liam: ['Tariq', 'Uma'], + Mina: ['Viktor', 'Wang'], + Noah: ['Xiomara'], + Olga: ['Yuki'], + Pedro: ['Zane', 'Aditi'], + Quynh: ['Boris'], + Ravi: ['Celine'], + Sofia: ['Diego', 'Elif'], + Tariq: ['Farah'], + Uma: ['Giorgio'], + Viktor: ['Hana', 'Ian'], + Wang: ['Jing'], + Xiomara: ['Kaito'], + Yuki: ['Leila'], + Zane: ['Mateo'], + Aditi: ['Nia'], + Boris: ['Oscar'], + Celine: ['Priya'], + Diego: ['Qi'], + Elif: ['Rami'], + Farah: ['Sven'], + Giorgio: ['Tomoko'], + Hana: ['Umar'], + Ian: ['Vera'], + Jing: ['Wyatt'], + Kaito: ['Xia'], + Leila: ['Yassin'], + Mateo: ['Zara'], + Nia: ['Antonio'], + Oscar: ['Bianca'], + Priya: ['Cai'], + Qi: ['Dimitri'], + Rami: ['Ewa'], + Sven: ['Fabio'], + Tomoko: ['Gabriela'], + Umar: ['Helena'], + Vera: ['Igor'], + Wyatt: ['Jun'], + Xia: ['Kim'], + Yassin: ['Lucia'], + Zara: ['Mohammed'], + }; + const results = degreesOfSeparation(familyTree, 'Dimitri', 'Fabio'); + expect(results).toEqual(9); + }); + + xtest('Complex graph, no shortcut, far removed nephew', () => { + const familyTree = { + Mina: ['Viktor', 'Wang'], + Olga: ['Yuki'], + Javier: ['Quynh', 'Ravi'], + Tariq: ['Farah'], + Viktor: ['Hana', 'Ian'], + Diego: ['Qi'], + Carlos: ['Fatima', 'Gustavo'], + Hana: ['Umar'], + Jing: ['Wyatt'], + Sven: ['Fabio'], + Zane: ['Mateo'], + Isla: ['Pedro'], + Quynh: ['Boris'], + Kaito: ['Xia'], + Liam: ['Tariq', 'Uma'], + Priya: ['Cai'], + Qi: ['Dimitri'], + Wang: ['Jing'], + Yuki: ['Leila'], + Xia: ['Kim'], + Pedro: ['Zane', 'Aditi'], + Uma: ['Giorgio'], + Giorgio: ['Tomoko'], + Gustavo: ['Mina'], + Sofia: ['Diego', 'Elif'], + Leila: ['Yassin'], + Umar: ['Helena'], + Aiko: ['Bao', 'Carlos'], + Fatima: ['Khadija', 'Liam'], + Oscar: ['Bianca'], + Wyatt: ['Jun'], + Ian: ['Vera'], + Mateo: ['Zara'], + Noah: ['Xiomara'], + Celine: ['Priya'], + Xiomara: ['Kaito'], + Bao: ['Dalia', 'Elias'], + Elif: ['Rami'], + Farah: ['Sven'], + Aditi: ['Nia'], + Vera: ['Igor'], + Boris: ['Oscar'], + Khadija: ['Sofia'], + Zara: ['Mohammed'], + Dalia: ['Hassan', 'Isla'], + Ravi: ['Celine'], + Yassin: ['Lucia'], + Elias: ['Javier'], + Nia: ['Antonio'], + Rami: ['Ewa'], + Hassan: ['Noah', 'Olga'], + Tomoko: ['Gabriela'], + }; + const results = degreesOfSeparation(familyTree, 'Lucia', 'Jun'); + expect(results).toEqual(14); + }); + + xtest('Complex graph, some shortcuts, cross-down and cross-up, cousins several times removed', () => { + const familyTree = { + Mina: ['Viktor', 'Wang'], + Olga: ['Yuki'], + Javier: ['Quynh', 'Ravi'], + Tariq: ['Farah'], + Viktor: ['Hana', 'Ian'], + Diego: ['Qi'], + Carlos: ['Fatima', 'Gustavo'], + Hana: ['Umar'], + Jing: ['Wyatt'], + Sven: ['Fabio'], + Zane: ['Mateo'], + Isla: ['Pedro'], + Quynh: ['Boris'], + Kaito: ['Xia'], + Liam: ['Tariq', 'Uma'], + Priya: ['Cai'], + Qi: ['Dimitri'], + Wang: ['Jing'], + Yuki: ['Leila'], + Xia: ['Kim'], + Pedro: ['Zane', 'Aditi'], + Uma: ['Giorgio'], + Giorgio: ['Tomoko'], + Gustavo: ['Mina'], + Sofia: ['Diego', 'Elif'], + Leila: ['Yassin'], + Umar: ['Helena'], + Aiko: ['Bao', 'Carlos'], + Fatima: ['Khadija', 'Liam'], + Oscar: ['Bianca'], + Wyatt: ['Jun'], + Ian: ['Vera'], + Mateo: ['Zara'], + Noah: ['Xiomara'], + Celine: ['Priya'], + Xiomara: ['Kaito'], + Bao: ['Dalia'], + Elif: ['Rami'], + Farah: ['Sven'], + Aditi: ['Nia'], + Vera: ['Igor'], + Boris: ['Oscar'], + Khadija: ['Sofia'], + Zara: ['Mohammed'], + Dalia: ['Hassan', 'Isla'], + Ravi: ['Celine'], + Yassin: ['Lucia'], + Nia: ['Antonio'], + Rami: ['Ewa'], + Hassan: ['Noah', 'Olga'], + Tomoko: ['Gabriela'], + }; + const results = degreesOfSeparation(familyTree, 'Wyatt', 'Xia'); + expect(results).toEqual(12); + }); +}); diff --git a/exercises/practice/resistor-color-duo/.docs/instructions.md b/exercises/practice/resistor-color-duo/.docs/instructions.md index bdcd549b1a..4ae694da02 100644 --- a/exercises/practice/resistor-color-duo/.docs/instructions.md +++ b/exercises/practice/resistor-color-duo/.docs/instructions.md @@ -17,17 +17,17 @@ The program will take color names as input and output a two digit number, even i The band colors are encoded as follows: -- Black: 0 -- Brown: 1 -- Red: 2 -- Orange: 3 -- Yellow: 4 -- Green: 5 -- Blue: 6 -- Violet: 7 -- Grey: 8 -- White: 9 +- black: 0 +- brown: 1 +- red: 2 +- orange: 3 +- yellow: 4 +- green: 5 +- blue: 6 +- violet: 7 +- grey: 8 +- white: 9 From the example above: -brown-green should return 15 +brown-green should return 15, and brown-green-violet should return 15 too, ignoring the third color. diff --git a/exercises/practice/resistor-color-duo/.eslintrc b/exercises/practice/resistor-color-duo/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/resistor-color-duo/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/resistor-color-duo/.gitignore b/exercises/practice/resistor-color-duo/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/resistor-color-duo/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/resistor-color-duo/.meta/config.json b/exercises/practice/resistor-color-duo/.meta/config.json index 24d38d8180..13aa13a47c 100644 --- a/exercises/practice/resistor-color-duo/.meta/config.json +++ b/exercises/practice/resistor-color-duo/.meta/config.json @@ -1,12 +1,31 @@ { - "blurb": "Convert color codes, as used on resistors, to a numeric value.", - "authors": ["tejasbubane"], - "contributors": ["ankorGH", "clockelliptic", "SleeplessByte"], + "authors": [ + "tejasbubane" + ], + "contributors": [ + "ankorGH", + "clockelliptic", + "jagdish-15", + "SleeplessByte" + ], "files": { - "solution": ["resistor-color-duo.js"], - "test": ["resistor-color-duo.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "resistor-color-duo.js" + ], + "test": [ + "resistor-color-duo.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Convert color codes, as used on resistors, to a numeric value.", "source": "Maud de Vries, Erik Schierboom", - "source_url": "https://github.com/exercism/problem-specifications/issues/1464" + "source_url": "https://github.com/exercism/problem-specifications/issues/1464", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/resistor-color-duo/.meta/tests.toml b/exercises/practice/resistor-color-duo/.meta/tests.toml index 862b57708d..9036fc787d 100644 --- a/exercises/practice/resistor-color-duo/.meta/tests.toml +++ b/exercises/practice/resistor-color-duo/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [ce11995a-5b93-4950-a5e9-93423693b2fc] description = "Brown and black" @@ -11,8 +18,14 @@ description = "Blue and grey" [f1886361-fdfd-4693-acf8-46726fe24e0c] description = "Yellow and violet" +[b7a6cbd2-ae3c-470a-93eb-56670b305640] +description = "White and red" + [77a8293d-2a83-4016-b1af-991acc12b9fe] description = "Orange and orange" [0c4fb44f-db7c-4d03-afa8-054350f156a8] description = "Ignore additional colors" + +[4a8ceec5-0ab4-4904-88a4-daf953a5e818] +description = "Black and brown, one-digit" diff --git a/exercises/practice/resistor-color-duo/babel.config.js b/exercises/practice/resistor-color-duo/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/resistor-color-duo/babel.config.js +++ b/exercises/practice/resistor-color-duo/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/resistor-color-duo/eslint.config.mjs b/exercises/practice/resistor-color-duo/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/resistor-color-duo/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/resistor-color-duo/jest.config.js b/exercises/practice/resistor-color-duo/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/resistor-color-duo/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/resistor-color-duo/package.json b/exercises/practice/resistor-color-duo/package.json index 0e48456d69..159b4fa8f2 100644 --- a/exercises/practice/resistor-color-duo/package.json +++ b/exercises/practice/resistor-color-duo/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/resistor-color-duo" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/resistor-color-duo/resistor-color-duo.js b/exercises/practice/resistor-color-duo/resistor-color-duo.js index b131f9251f..224c6311b4 100644 --- a/exercises/practice/resistor-color-duo/resistor-color-duo.js +++ b/exercises/practice/resistor-color-duo/resistor-color-duo.js @@ -4,5 +4,5 @@ // export const decodedValue = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/resistor-color-duo/resistor-color-duo.spec.js b/exercises/practice/resistor-color-duo/resistor-color-duo.spec.js index 9053b80a3a..9d3326e016 100644 --- a/exercises/practice/resistor-color-duo/resistor-color-duo.spec.js +++ b/exercises/practice/resistor-color-duo/resistor-color-duo.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { decodedValue } from './resistor-color-duo'; describe('Resistor Colors', () => { @@ -13,6 +14,10 @@ describe('Resistor Colors', () => { expect(decodedValue(['yellow', 'violet'])).toEqual(47); }); + xtest('White and red', () => { + expect(decodedValue(['white', 'red'])).toEqual(92); + }); + xtest('Orange and orange', () => { expect(decodedValue(['orange', 'orange'])).toEqual(33); }); @@ -20,4 +25,8 @@ describe('Resistor Colors', () => { xtest('Ignore additional colors', () => { expect(decodedValue(['green', 'brown', 'orange'])).toEqual(51); }); + + xtest('Black and brown, one-digit', () => { + expect(decodedValue(['black', 'brown'])).toEqual(1); + }); }); diff --git a/exercises/practice/resistor-color-trio/.docs/instructions.md b/exercises/practice/resistor-color-trio/.docs/instructions.md index 06a14d7e75..1ac5cf5e9f 100644 --- a/exercises/practice/resistor-color-trio/.docs/instructions.md +++ b/exercises/practice/resistor-color-trio/.docs/instructions.md @@ -1,27 +1,32 @@ # Instructions -If you want to build something using a Raspberry Pi, you'll probably use _resistors_. For this exercise, you need to know only three things about them: +If you want to build something using a Raspberry Pi, you'll probably use _resistors_. +For this exercise, you need to know only three things about them: - Each resistor has a resistance value. - Resistors are small - so small in fact that if you printed the resistance value on them, it would be hard to read. To get around this problem, manufacturers print color-coded bands onto the resistors to denote their resistance values. -- Each band acts as a digit of a number. For example, if they printed a brown band (value 1) followed by a green band (value 5), it would translate to the number 15. - In this exercise, you are going to create a helpful program so that you don't have to remember the values of the bands. The program will take 3 colors as input, and outputs the correct value, in ohms. +- Each band acts as a digit of a number. + For example, if they printed a brown band (value 1) followed by a green band (value 5), it would translate to the number 15. + In this exercise, you are going to create a helpful program so that you don't have to remember the values of the bands. + The program will take 3 colors as input, and outputs the correct value, in ohms. The color bands are encoded as follows: -* Black: 0 -* Brown: 1 -* Red: 2 -* Orange: 3 -* Yellow: 4 -* Green: 5 -* Blue: 6 -* Violet: 7 -* Grey: 8 -* White: 9 - -In `resistor-color duo` you decoded the first two colors. For instance: orange-orange got the main value `33`. -The third color stands for how many zeros need to be added to the main value. The main value plus the zeros gives us a value in ohms. +- black: 0 +- brown: 1 +- red: 2 +- orange: 3 +- yellow: 4 +- green: 5 +- blue: 6 +- violet: 7 +- grey: 8 +- white: 9 + +In Resistor Color Duo you decoded the first two colors. +For instance: orange-orange got the main value `33`. +The third color stands for how many zeros need to be added to the main value. +The main value plus the zeros gives us a value in ohms. For the exercise it doesn't matter what ohms really are. For example: @@ -29,7 +34,9 @@ For example: - orange-orange-red would be 33 and 2 zeros, which becomes 3300 ohms. - orange-orange-orange would be 33 and 3 zeros, which becomes 33000 ohms. -(If Math is your thing, you may want to think of the zeros as exponents of 10. If Math is not your thing, go with the zeros. It really is the same thing, just in plain English instead of Math lingo.) +(If Math is your thing, you may want to think of the zeros as exponents of 10. +If Math is not your thing, go with the zeros. +It really is the same thing, just in plain English instead of Math lingo.) This exercise is about translating the colors into a label: @@ -39,7 +46,11 @@ So an input of `"orange", "orange", "black"` should return: > "33 ohms" -When we get more than a thousand ohms, we say "kiloohms". That's similar to saying "kilometer" for 1000 meters, and "kilograms" for 1000 grams. -So an input of `"orange", "orange", "orange"` should return: +When we get to larger resistors, a [metric prefix][metric-prefix] is used to indicate a larger magnitude of ohms, such as "kiloohms". +That is similar to saying "2 kilometers" instead of "2000 meters", or "2 kilograms" for "2000 grams". + +For example, an input of `"orange", "orange", "orange"` should return: > "33 kiloohms" + +[metric-prefix]: https://en.wikipedia.org/wiki/Metric_prefix diff --git a/exercises/practice/resistor-color-trio/.eslintrc b/exercises/practice/resistor-color-trio/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/resistor-color-trio/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/resistor-color-trio/.gitignore b/exercises/practice/resistor-color-trio/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/resistor-color-trio/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/resistor-color-trio/.meta/config.json b/exercises/practice/resistor-color-trio/.meta/config.json index 77b8ce4a37..403b20a7fc 100644 --- a/exercises/practice/resistor-color-trio/.meta/config.json +++ b/exercises/practice/resistor-color-trio/.meta/config.json @@ -1,12 +1,29 @@ { - "blurb": "Convert color codes, as used on resistors, to a human-readable label.", - "authors": ["SleeplessByte"], - "contributors": ["hayashi-ay"], + "authors": [ + "SleeplessByte" + ], + "contributors": [ + "hayashi-ay", + "jagdish-15" + ], "files": { - "solution": ["resistor-color-trio.js"], - "test": ["resistor-color-trio.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "resistor-color-trio.js" + ], + "test": [ + "resistor-color-trio.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Convert color codes, as used on resistors, to a human-readable label.", "source": "Maud de Vries, Erik Schierboom", - "source_url": "https://github.com/exercism/problem-specifications/issues/1549" + "source_url": "https://github.com/exercism/problem-specifications/issues/1549", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/resistor-color-trio/.meta/proof.ci.js b/exercises/practice/resistor-color-trio/.meta/proof.ci.js index b421b68d4a..3b89a4d607 100644 --- a/exercises/practice/resistor-color-trio/.meta/proof.ci.js +++ b/exercises/practice/resistor-color-trio/.meta/proof.ci.js @@ -12,6 +12,8 @@ const COLORS = [ ]; const ONE_KILOOHM = 1000; +const ONE_MEGAOHM = 1000000; +const ONE_GIGAOHM = 1000000000; class ArgumentError extends Error {} @@ -44,9 +46,18 @@ export class ResistorColorTrio { toString() { const value = this.value; - return value < ONE_KILOOHM - ? `${value} ohms` - : `${Math.floor(value / ONE_KILOOHM)} kiloohms`; + + if (value >= ONE_GIGAOHM) { + return `${Math.floor(value / ONE_GIGAOHM)} gigaohms`; + } + if (value >= ONE_MEGAOHM) { + return `${Math.floor(value / ONE_MEGAOHM)} megaohms`; + } + if (value >= ONE_KILOOHM) { + return `${Math.floor(value / ONE_KILOOHM)} kiloohms`; + } + + return `${value} ohms`; } /** diff --git a/exercises/practice/resistor-color-trio/.meta/tests.toml b/exercises/practice/resistor-color-trio/.meta/tests.toml index beabab3df5..b7d45fa5d5 100644 --- a/exercises/practice/resistor-color-trio/.meta/tests.toml +++ b/exercises/practice/resistor-color-trio/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [d6863355-15b7-40bb-abe0-bfb1a25512ed] description = "Orange and orange and black" @@ -16,3 +23,18 @@ description = "Green and brown and orange" [f5d37ef9-1919-4719-a90d-a33c5a6934c9] description = "Yellow and violet and yellow" + +[5f6404a7-5bb3-4283-877d-3d39bcc33854] +description = "Blue and violet and blue" + +[7d3a6ab8-e40e-46c3-98b1-91639fff2344] +description = "Minimum possible value" + +[ca0aa0ac-3825-42de-9f07-dac68cc580fd] +description = "Maximum possible value" + +[0061a76c-903a-4714-8ce2-f26ce23b0e09] +description = "First two colors make an invalid octal number" + +[30872c92-f567-4b69-a105-8455611c10c4] +description = "Ignore extra colors" diff --git a/exercises/practice/resistor-color-trio/babel.config.js b/exercises/practice/resistor-color-trio/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/resistor-color-trio/babel.config.js +++ b/exercises/practice/resistor-color-trio/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/resistor-color-trio/eslint.config.mjs b/exercises/practice/resistor-color-trio/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/resistor-color-trio/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/resistor-color-trio/jest.config.js b/exercises/practice/resistor-color-trio/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/resistor-color-trio/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/resistor-color-trio/package.json b/exercises/practice/resistor-color-trio/package.json index 7138d9e7d4..557f9f130c 100644 --- a/exercises/practice/resistor-color-trio/package.json +++ b/exercises/practice/resistor-color-trio/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/resistor-color-trio" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/resistor-color-trio/resistor-color-trio.js b/exercises/practice/resistor-color-trio/resistor-color-trio.js index 087d1551fe..478e7d99bf 100644 --- a/exercises/practice/resistor-color-trio/resistor-color-trio.js +++ b/exercises/practice/resistor-color-trio/resistor-color-trio.js @@ -5,10 +5,10 @@ export class ResistorColorTrio { constructor() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } label() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/resistor-color-trio/resistor-color-trio.spec.js b/exercises/practice/resistor-color-trio/resistor-color-trio.spec.js index be86abf55c..26562bd05b 100644 --- a/exercises/practice/resistor-color-trio/resistor-color-trio.spec.js +++ b/exercises/practice/resistor-color-trio/resistor-color-trio.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { ResistorColorTrio } from './resistor-color-trio'; function makeLabel({ value, unit }) { @@ -7,38 +8,68 @@ function makeLabel({ value, unit }) { describe('Resistor Color Trio', () => { test('Orange and orange and black', () => { expect(new ResistorColorTrio(['orange', 'orange', 'black']).label).toEqual( - makeLabel({ value: 33, unit: 'ohms' }) + makeLabel({ value: 33, unit: 'ohms' }), ); }); xtest('Blue and grey and brown', () => { expect(new ResistorColorTrio(['blue', 'grey', 'brown']).label).toEqual( - makeLabel({ value: 680, unit: 'ohms' }) + makeLabel({ value: 680, unit: 'ohms' }), ); }); xtest('Red and black and red', () => { expect(new ResistorColorTrio(['red', 'black', 'red']).label).toEqual( - makeLabel({ value: 2, unit: 'kiloohms' }) + makeLabel({ value: 2, unit: 'kiloohms' }), ); }); xtest('Green and brown and orange', () => { expect(new ResistorColorTrio(['green', 'brown', 'orange']).label).toEqual( - makeLabel({ value: 51, unit: 'kiloohms' }) + makeLabel({ value: 51, unit: 'kiloohms' }), ); }); xtest('Yellow and violet and yellow', () => { expect(new ResistorColorTrio(['yellow', 'violet', 'yellow']).label).toEqual( - makeLabel({ value: 470, unit: 'kiloohms' }) + makeLabel({ value: 470, unit: 'kiloohms' }), ); }); + xtest('Blue and violet and blue', () => { + expect(new ResistorColorTrio(['blue', 'violet', 'blue']).label).toEqual( + makeLabel({ value: 67, unit: 'megaohms' }), + ); + }); + + xtest('Minimum possible value', () => { + expect(new ResistorColorTrio(['black', 'black', 'black']).label).toEqual( + makeLabel({ value: 0, unit: 'ohms' }), + ); + }); + + xtest('Maximum possible value', () => { + expect(new ResistorColorTrio(['white', 'white', 'white']).label).toEqual( + makeLabel({ value: 99, unit: 'gigaohms' }), + ); + }); + + xtest('First two colors make an invalid octal number', () => { + expect(new ResistorColorTrio(['black', 'grey', 'black']).label).toEqual( + makeLabel({ value: 8, unit: 'ohms' }), + ); + }); + + xtest('Ignore extra colors', () => { + expect( + new ResistorColorTrio(['blue', 'green', 'yellow', 'orange']).label, + ).toEqual(makeLabel({ value: 650, unit: 'kiloohms' })); + }); + // optional: error xtest('Invalid color', () => { expect( - () => new ResistorColorTrio(['yellow', 'purple', 'black']).label - ).toThrowError(/invalid color/); + () => new ResistorColorTrio(['yellow', 'purple', 'black']).label, + ).toThrow(/invalid color/); }); }); diff --git a/exercises/practice/resistor-color/.docs/instructions.md b/exercises/practice/resistor-color/.docs/instructions.md index 41ece3f809..0125e718b4 100644 --- a/exercises/practice/resistor-color/.docs/instructions.md +++ b/exercises/practice/resistor-color/.docs/instructions.md @@ -15,22 +15,25 @@ In this exercise you are going to create a helpful program so that you don't hav These colors are encoded as follows: -- Black: 0 -- Brown: 1 -- Red: 2 -- Orange: 3 -- Yellow: 4 -- Green: 5 -- Blue: 6 -- Violet: 7 -- Grey: 8 -- White: 9 +- black: 0 +- brown: 1 +- red: 2 +- orange: 3 +- yellow: 4 +- green: 5 +- blue: 6 +- violet: 7 +- grey: 8 +- white: 9 The goal of this exercise is to create a way: - to look up the numerical value associated with a particular color band - to list the different band colors -Mnemonics map the colors to the numbers, that, when stored as an array, happen to map to their index in the array: Better Be Right Or Your Great Big Values Go Wrong. +Mnemonics map the colors to the numbers, that, when stored as an array, happen to map to their index in the array: +Better Be Right Or Your Great Big Values Go Wrong. -More information on the color encoding of resistors can be found in the [Electronic color code Wikipedia article](https://en.wikipedia.org/wiki/Electronic_color_code) +More information on the color encoding of resistors can be found in the [Electronic color code Wikipedia article][e-color-code]. + +[e-color-code]: https://en.wikipedia.org/wiki/Electronic_color_code diff --git a/exercises/practice/resistor-color/.eslintrc b/exercises/practice/resistor-color/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/resistor-color/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/resistor-color/.gitignore b/exercises/practice/resistor-color/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/resistor-color/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/resistor-color/.meta/config.json b/exercises/practice/resistor-color/.meta/config.json index 6578653688..b865e91cdb 100644 --- a/exercises/practice/resistor-color/.meta/config.json +++ b/exercises/practice/resistor-color/.meta/config.json @@ -1,12 +1,29 @@ { - "blurb": "Convert a resistor band's color to its numeric representation", - "authors": ["SleeplessByte"], - "contributors": ["ankorGH", "TomPradat"], + "authors": [ + "SleeplessByte" + ], + "contributors": [ + "ankorGH", + "TomPradat" + ], "files": { - "solution": ["resistor-color.js"], - "test": ["resistor-color.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "resistor-color.js" + ], + "test": [ + "resistor-color.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Convert a resistor band's color to its numeric representation.", "source": "Maud de Vries, Erik Schierboom", - "source_url": "https://github.com/exercism/problem-specifications/issues/1458" + "source_url": "https://github.com/exercism/problem-specifications/issues/1458", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/resistor-color/babel.config.js b/exercises/practice/resistor-color/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/resistor-color/babel.config.js +++ b/exercises/practice/resistor-color/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/resistor-color/eslint.config.mjs b/exercises/practice/resistor-color/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/resistor-color/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/resistor-color/jest.config.js b/exercises/practice/resistor-color/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/resistor-color/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/resistor-color/package.json b/exercises/practice/resistor-color/package.json index e43ef1cba8..7014a1d2d8 100644 --- a/exercises/practice/resistor-color/package.json +++ b/exercises/practice/resistor-color/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/resistor-color" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/resistor-color/resistor-color.js b/exercises/practice/resistor-color/resistor-color.js index 867e9112d8..2d7a9e4282 100644 --- a/exercises/practice/resistor-color/resistor-color.js +++ b/exercises/practice/resistor-color/resistor-color.js @@ -4,7 +4,7 @@ // export const colorCode = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; export const COLORS = undefined; diff --git a/exercises/practice/resistor-color/resistor-color.spec.js b/exercises/practice/resistor-color/resistor-color.spec.js index 1d3c85328a..b9620593a4 100644 --- a/exercises/practice/resistor-color/resistor-color.spec.js +++ b/exercises/practice/resistor-color/resistor-color.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { colorCode, COLORS } from './resistor-color'; describe('ResistorColor', () => { diff --git a/exercises/practice/rest-api/.docs/instructions.md b/exercises/practice/rest-api/.docs/instructions.md index 4969e20a04..af223ba4b4 100644 --- a/exercises/practice/rest-api/.docs/instructions.md +++ b/exercises/practice/rest-api/.docs/instructions.md @@ -1,10 +1,10 @@ -# Description +# Instructions Implement a RESTful API for tracking IOUs. Four roommates have a habit of borrowing money from each other frequently, and have trouble remembering who owes whom, and how much. -Your task is to implement a simple [RESTful API](https://en.wikipedia.org/wiki/Representational_state_transfer) that receives [IOU](https://en.wikipedia.org/wiki/IOU)s as POST requests, and can deliver specified summary information via GET requests. +Your task is to implement a simple [RESTful API][restful-wikipedia] that receives [IOU][iou]s as POST requests, and can deliver specified summary information via GET requests. ## API Specification @@ -36,7 +36,13 @@ Your task is to implement a simple [RESTful API](https://en.wikipedia.org/wiki/R ## Other Resources -- [https://restfulapi.net/](https://restfulapi.net/) +- [REST API Tutorial][restfulapi] - Example RESTful APIs - - [GitHub](https://developer.github.com/v3/) - - [Reddit](https://www.reddit.com/dev/api/) + - [GitHub][github-rest] + - [Reddit][reddit-rest] + +[restful-wikipedia]: https://en.wikipedia.org/wiki/Representational_state_transfer +[iou]: https://en.wikipedia.org/wiki/IOU +[github-rest]: https://developer.github.com/v3/ +[reddit-rest]: https://web.archive.org/web/20231202231149/https://www.reddit.com/dev/api/ +[restfulapi]: https://restfulapi.net/ diff --git a/exercises/practice/rest-api/.eslintrc b/exercises/practice/rest-api/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/rest-api/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/rest-api/.gitignore b/exercises/practice/rest-api/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/rest-api/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/rest-api/.meta/config.json b/exercises/practice/rest-api/.meta/config.json index 2b08a3f253..2a02ee3824 100644 --- a/exercises/practice/rest-api/.meta/config.json +++ b/exercises/practice/rest-api/.meta/config.json @@ -1,10 +1,17 @@ { - "blurb": "Implement a RESTful API for tracking IOUs.", - "authors": ["lpizzinidev"], - "contributors": [], + "authors": [ + "lpizzinidev" + ], "files": { - "solution": ["rest-api.js"], - "test": ["rest-api.spec.js"], - "example": [".meta/proof.ci.js"] - } + "solution": [ + "rest-api.js" + ], + "test": [ + "rest-api.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] + }, + "blurb": "Implement a RESTful API for tracking IOUs." } diff --git a/exercises/practice/rest-api/.meta/proof.ci.js b/exercises/practice/rest-api/.meta/proof.ci.js index 036a0883da..90a071508a 100644 --- a/exercises/practice/rest-api/.meta/proof.ci.js +++ b/exercises/practice/rest-api/.meta/proof.ci.js @@ -17,7 +17,7 @@ export class RestAPI { if (pName === 'users') { return { users: this.database.users.filter((user) => - pData.includes(user.name) + pData.includes(user.name), ), }; } @@ -36,7 +36,7 @@ export class RestAPI { switch (url) { case '/add': { const existingUser = this.database.users.find( - (user) => user.name === payload.user + (user) => user.name === payload.user, ); if (existingUser) return existingUser; const newUser = { diff --git a/exercises/practice/rest-api/babel.config.js b/exercises/practice/rest-api/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/rest-api/babel.config.js +++ b/exercises/practice/rest-api/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/rest-api/eslint.config.mjs b/exercises/practice/rest-api/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/rest-api/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/rest-api/jest.config.js b/exercises/practice/rest-api/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/rest-api/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/rest-api/package.json b/exercises/practice/rest-api/package.json index cbb13a5f05..e8fca97457 100644 --- a/exercises/practice/rest-api/package.json +++ b/exercises/practice/rest-api/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/rest-api" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/rest-api/rest-api.js b/exercises/practice/rest-api/rest-api.js index bddc9c0bf6..2d3c7b0e54 100644 --- a/exercises/practice/rest-api/rest-api.js +++ b/exercises/practice/rest-api/rest-api.js @@ -5,14 +5,14 @@ export class RestAPI { constructor() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get(url) { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } post(url, payload) { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/rest-api/rest-api.spec.js b/exercises/practice/rest-api/rest-api.spec.js index aa40ab0e3e..5cf19f0f65 100644 --- a/exercises/practice/rest-api/rest-api.spec.js +++ b/exercises/practice/rest-api/rest-api.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { RestAPI } from './rest-api'; describe('Rest API', () => { @@ -7,7 +8,7 @@ describe('Rest API', () => { expect(restAPI.get('/users')).toEqual({ users: [] }); }); - test('add user', () => { + xtest('add user', () => { const restAPI = new RestAPI({ users: [] }); expect(restAPI.post('/add', { user: 'Adam' })).toEqual({ name: 'Adam', @@ -17,7 +18,7 @@ describe('Rest API', () => { }); }); - test('get single user', () => { + xtest('get single user', () => { const seedUsers = [ { name: 'Adam', owes: {}, owed_by: {}, balance: 0 }, { name: 'Bob', owes: {}, owed_by: {}, balance: 0 }, @@ -31,7 +32,7 @@ describe('Rest API', () => { }); describe('iou', () => { - test('both users have 0 balance', () => { + xtest('both users have 0 balance', () => { const seedUsers = [ { name: 'Adam', owes: {}, owed_by: {}, balance: 0 }, { name: 'Bob', owes: {}, owed_by: {}, balance: 0 }, @@ -45,7 +46,7 @@ describe('Rest API', () => { expect(restAPI.post('/iou', payload)).toEqual({ users: expectedUsers }); }); - test('borrower has negative balance', () => { + xtest('borrower has negative balance', () => { const seedUsers = [ { name: 'Adam', owes: {}, owed_by: {}, balance: 0 }, { name: 'Bob', owes: { Chuck: 3 }, owed_by: {}, balance: -3 }, @@ -60,7 +61,7 @@ describe('Rest API', () => { expect(restAPI.post('/iou', payload)).toEqual({ users: expectedUsers }); }); - test('lender has negative balance', () => { + xtest('lender has negative balance', () => { const seedUsers = [ { name: 'Adam', owes: {}, owed_by: {}, balance: 0 }, { name: 'Bob', owes: { Chuck: 3 }, owed_by: {}, balance: -3 }, @@ -75,7 +76,7 @@ describe('Rest API', () => { expect(restAPI.post('/iou', payload)).toEqual({ users: expectedUsers }); }); - test('lender owes borrower', () => { + xtest('lender owes borrower', () => { const seedUsers = [ { name: 'Adam', owes: { Bob: 3 }, owed_by: {}, balance: -3 }, { name: 'Bob', owes: {}, owed_by: { Adam: 3 }, balance: 3 }, @@ -89,7 +90,7 @@ describe('Rest API', () => { expect(restAPI.post('/iou', payload)).toEqual({ users: expectedUsers }); }); - test('lender owes borrower less than new loan', () => { + xtest('lender owes borrower less than new loan', () => { const seedUsers = [ { name: 'Adam', owes: { Bob: 3 }, owed_by: {}, balance: -3 }, { name: 'Bob', owes: {}, owed_by: { Adam: 3 }, balance: 3 }, @@ -103,7 +104,7 @@ describe('Rest API', () => { expect(restAPI.post('/iou', payload)).toEqual({ users: expectedUsers }); }); - test('lender owes borrower same as new loan', () => { + xtest('lender owes borrower same as new loan', () => { const seedUsers = [ { name: 'Adam', owes: { Bob: 3 }, owed_by: {}, balance: -3 }, { name: 'Bob', owes: {}, owed_by: { Adam: 3 }, balance: 3 }, diff --git a/exercises/practice/reverse-string/.docs/instructions.append.md b/exercises/practice/reverse-string/.docs/instructions.append.md new file mode 100644 index 0000000000..68688e11fa --- /dev/null +++ b/exercises/practice/reverse-string/.docs/instructions.append.md @@ -0,0 +1,6 @@ + +~~~exercism/advanced +If you solve this using the CLI, there are test cases that require you to deal with complex characters. +You can optionally enable these tests by removing `.skip` from the test. +~~~ + diff --git a/exercises/practice/reverse-string/.docs/instructions.md b/exercises/practice/reverse-string/.docs/instructions.md index 039ee33ae5..0ff4198e46 100644 --- a/exercises/practice/reverse-string/.docs/instructions.md +++ b/exercises/practice/reverse-string/.docs/instructions.md @@ -1,7 +1,9 @@ # Instructions -Reverse a string +Your task is to reverse a given string. -For example: -input: "cool" -output: "looc" +Some examples: + +- Turn `"stressed"` into `"desserts"`. +- Turn `"strops"` into `"sports"`. +- Turn `"racecar"` into `"racecar"`. diff --git a/exercises/practice/reverse-string/.docs/introduction.md b/exercises/practice/reverse-string/.docs/introduction.md new file mode 100644 index 0000000000..02233e0755 --- /dev/null +++ b/exercises/practice/reverse-string/.docs/introduction.md @@ -0,0 +1,5 @@ +# Introduction + +Reversing strings (reading them from right to left, rather than from left to right) is a surprisingly common task in programming. + +For example, in bioinformatics, reversing the sequence of DNA or RNA strings is often important for various analyses, such as finding complementary strands or identifying palindromic sequences that have biological significance. diff --git a/exercises/practice/reverse-string/.eslintrc b/exercises/practice/reverse-string/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/reverse-string/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/reverse-string/.gitignore b/exercises/practice/reverse-string/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/reverse-string/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/reverse-string/.meta/config.json b/exercises/practice/reverse-string/.meta/config.json index 982480c0a3..284ffe7700 100644 --- a/exercises/practice/reverse-string/.meta/config.json +++ b/exercises/practice/reverse-string/.meta/config.json @@ -1,12 +1,32 @@ { - "blurb": "Reverse a string", - "authors": ["gavinhenderson"], - "contributors": ["ankorGH", "d-vail", "ovidiu141", "SleeplessByte"], + "authors": [ + "gavinhenderson" + ], + "contributors": [ + "ankorGH", + "d-vail", + "jagdish-15", + "ovidiu141", + "SleeplessByte" + ], "files": { - "solution": ["reverse-string.js"], - "test": ["reverse-string.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "reverse-string.js" + ], + "test": [ + "reverse-string.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Reverse a given string.", "source": "Introductory challenge to reverse an input string", - "source_url": "https://medium.freecodecamp.org/how-to-reverse-a-string-in-javascript-in-3-different-ways-75e4763c68cb" + "source_url": "https://medium.freecodecamp.org/how-to-reverse-a-string-in-javascript-in-3-different-ways-75e4763c68cb", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": true + } } diff --git a/exercises/practice/reverse-string/.meta/proof.ci.js b/exercises/practice/reverse-string/.meta/proof.ci.js index 6cf696edca..68631c5aaa 100644 --- a/exercises/practice/reverse-string/.meta/proof.ci.js +++ b/exercises/practice/reverse-string/.meta/proof.ci.js @@ -1,7 +1,11 @@ export const reverseString = (string) => { let revString = ''; - for (let i = string.length - 1; i >= 0; i -= 1) { - revString += string[i]; + let characters = Array.from( + new Intl.Segmenter().segment(String(string)), + (x) => x.segment, + ); + for (let i = characters.length - 1; i >= 0; i--) { + revString += characters[i]; } return revString; }; diff --git a/exercises/practice/reverse-string/.meta/tests.toml b/exercises/practice/reverse-string/.meta/tests.toml index 2113a53364..0c313cc537 100644 --- a/exercises/practice/reverse-string/.meta/tests.toml +++ b/exercises/practice/reverse-string/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [c3b7d806-dced-49ee-8543-933fd1719b1c] description = "an empty string" @@ -19,3 +26,12 @@ description = "a palindrome" [b9e7dec1-c6df-40bd-9fa3-cd7ded010c4c] description = "an even-sized word" + +[1bed0f8a-13b0-4bd3-9d59-3d0593326fa2] +description = "wide characters" + +[93d7e1b8-f60f-4f3c-9559-4056e10d2ead] +description = "grapheme cluster with pre-combined form" + +[1028b2c1-6763-4459-8540-2da47ca512d9] +description = "grapheme clusters" diff --git a/exercises/practice/reverse-string/babel.config.js b/exercises/practice/reverse-string/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/reverse-string/babel.config.js +++ b/exercises/practice/reverse-string/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/reverse-string/eslint.config.mjs b/exercises/practice/reverse-string/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/reverse-string/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/reverse-string/jest.config.js b/exercises/practice/reverse-string/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/reverse-string/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/reverse-string/package.json b/exercises/practice/reverse-string/package.json index 0a48a247d1..58a6617239 100644 --- a/exercises/practice/reverse-string/package.json +++ b/exercises/practice/reverse-string/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/reverse-string" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/reverse-string/reverse-string.js b/exercises/practice/reverse-string/reverse-string.js index c5060d2f1e..e42fcc9136 100644 --- a/exercises/practice/reverse-string/reverse-string.js +++ b/exercises/practice/reverse-string/reverse-string.js @@ -4,5 +4,5 @@ // export const reverseString = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/reverse-string/reverse-string.spec.js b/exercises/practice/reverse-string/reverse-string.spec.js index 0f18315d96..d93cc04e6f 100644 --- a/exercises/practice/reverse-string/reverse-string.spec.js +++ b/exercises/practice/reverse-string/reverse-string.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { reverseString } from './reverse-string'; describe('ReverseString', () => { @@ -36,4 +37,25 @@ describe('ReverseString', () => { const actual = reverseString('drawer'); expect(actual).toEqual(expected); }); + + xtest('wide characters', () => { + const expected = '猫子'; + const actual = reverseString('子猫'); + expect(actual).toEqual(expected); + }); + + // The following test cases deal with complex characters. + // You can optionally enable these tests by removing `.skip` from the test. + + test.skip('grapheme cluster with pre-combined form', () => { + const expected = 'dnatsnehctsrüW'; + const actual = reverseString('Würstchenstand'); + expect(actual).toEqual(expected); + }); + + test.skip('grapheme clusters', () => { + const expected = 'มรกแรปโนยขีเผู้'; + const actual = reverseString('ผู้เขียนโปรแกรม'); + expect(actual).toEqual(expected); + }); }); diff --git a/exercises/practice/rna-transcription/.docs/instructions.md b/exercises/practice/rna-transcription/.docs/instructions.md index d1613b3573..4dbfd3a271 100644 --- a/exercises/practice/rna-transcription/.docs/instructions.md +++ b/exercises/practice/rna-transcription/.docs/instructions.md @@ -1,19 +1,20 @@ # Instructions -Given a DNA strand, return its RNA complement (per RNA transcription). +Your task is to determine the RNA complement of a given DNA sequence. Both DNA and RNA strands are a sequence of nucleotides. -The four nucleotides found in DNA are adenine (**A**), cytosine (**C**), -guanine (**G**) and thymine (**T**). +The four nucleotides found in DNA are adenine (**A**), cytosine (**C**), guanine (**G**), and thymine (**T**). -The four nucleotides found in RNA are adenine (**A**), cytosine (**C**), -guanine (**G**) and uracil (**U**). +The four nucleotides found in RNA are adenine (**A**), cytosine (**C**), guanine (**G**), and uracil (**U**). -Given a DNA strand, its transcribed RNA strand is formed by replacing -each nucleotide with its complement: +Given a DNA strand, its transcribed RNA strand is formed by replacing each nucleotide with its complement: - `G` -> `C` - `C` -> `G` - `T` -> `A` - `A` -> `U` + +~~~~exercism/note +If you want to look at how the inputs and outputs are structured, take a look at the examples in the test suite. +~~~~ diff --git a/exercises/practice/rna-transcription/.docs/introduction.md b/exercises/practice/rna-transcription/.docs/introduction.md new file mode 100644 index 0000000000..6b3f44b532 --- /dev/null +++ b/exercises/practice/rna-transcription/.docs/introduction.md @@ -0,0 +1,16 @@ +# Introduction + +You work for a bioengineering company that specializes in developing therapeutic solutions. + +Your team has just been given a new project to develop a targeted therapy for a rare type of cancer. + +~~~~exercism/note +It's all very complicated, but the basic idea is that sometimes people's bodies produce too much of a given protein. +That can cause all sorts of havoc. + +But if you can create a very specific molecule (called a micro-RNA), it can prevent the protein from being produced. + +This technique is called [RNA Interference][rnai]. + +[rnai]: https://admin.acceleratingscience.com/ask-a-scientist/what-is-rnai/ +~~~~ diff --git a/exercises/practice/rna-transcription/.eslintrc b/exercises/practice/rna-transcription/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/rna-transcription/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/rna-transcription/.gitignore b/exercises/practice/rna-transcription/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/rna-transcription/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/rna-transcription/.meta/config.json b/exercises/practice/rna-transcription/.meta/config.json index 60fe742125..c9ae09aa3b 100644 --- a/exercises/practice/rna-transcription/.meta/config.json +++ b/exercises/practice/rna-transcription/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Given a DNA strand, return its RNA Complement Transcription.", - "authors": ["rchavarria"], + "authors": [ + "rchavarria" + ], "contributors": [ "amscotti", "ankorGH", @@ -14,10 +15,23 @@ "xarxziux" ], "files": { - "solution": ["rna-transcription.js"], - "test": ["rna-transcription.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "rna-transcription.js" + ], + "test": [ + "rna-transcription.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Given a DNA strand, return its RNA complement.", "source": "Hyperphysics", - "source_url": "http://hyperphysics.phy-astr.gsu.edu/hbase/Organic/transcription.html" + "source_url": "https://web.archive.org/web/20220408112140/http://hyperphysics.phy-astr.gsu.edu/hbase/Organic/transcription.html", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/rna-transcription/babel.config.js b/exercises/practice/rna-transcription/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/rna-transcription/babel.config.js +++ b/exercises/practice/rna-transcription/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/rna-transcription/eslint.config.mjs b/exercises/practice/rna-transcription/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/rna-transcription/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/rna-transcription/jest.config.js b/exercises/practice/rna-transcription/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/rna-transcription/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/rna-transcription/package.json b/exercises/practice/rna-transcription/package.json index b99d6fd897..33d43c6f9e 100644 --- a/exercises/practice/rna-transcription/package.json +++ b/exercises/practice/rna-transcription/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/rna-transcription" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/rna-transcription/rna-transcription.js b/exercises/practice/rna-transcription/rna-transcription.js index 9a8343ed4a..3679dec81c 100644 --- a/exercises/practice/rna-transcription/rna-transcription.js +++ b/exercises/practice/rna-transcription/rna-transcription.js @@ -4,5 +4,5 @@ // export const toRna = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/rna-transcription/rna-transcription.spec.js b/exercises/practice/rna-transcription/rna-transcription.spec.js index 97b71faf9b..303073fa10 100644 --- a/exercises/practice/rna-transcription/rna-transcription.spec.js +++ b/exercises/practice/rna-transcription/rna-transcription.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { toRna } from './rna-transcription'; describe('Transcription', () => { diff --git a/exercises/practice/robot-name/.docs/instructions.md b/exercises/practice/robot-name/.docs/instructions.md index a0079a341e..fca3a41aec 100644 --- a/exercises/practice/robot-name/.docs/instructions.md +++ b/exercises/practice/robot-name/.docs/instructions.md @@ -4,13 +4,11 @@ Manage robot factory settings. When a robot comes off the factory floor, it has no name. -The first time you turn on a robot, a random name is generated in the format -of two uppercase letters followed by three digits, such as RX837 or BC811. +The first time you turn on a robot, a random name is generated in the format of two uppercase letters followed by three digits, such as RX837 or BC811. -Every once in a while we need to reset a robot to its factory settings, -which means that its name gets wiped. The next time you ask, that robot will -respond with a new random name. +Every once in a while we need to reset a robot to its factory settings, which means that its name gets wiped. +The next time you ask, that robot will respond with a new random name. The names must be random: they should not follow a predictable sequence. -Using random names means a risk of collisions. Your solution must ensure that -every existing robot has a unique name. +Using random names means a risk of collisions. +Your solution must ensure that every existing robot has a unique name. diff --git a/exercises/practice/robot-name/.eslintrc b/exercises/practice/robot-name/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/robot-name/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/robot-name/.gitignore b/exercises/practice/robot-name/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/robot-name/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/robot-name/.meta/config.json b/exercises/practice/robot-name/.meta/config.json index f88dc46d4b..59d09bd91c 100644 --- a/exercises/practice/robot-name/.meta/config.json +++ b/exercises/practice/robot-name/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Manage robot factory settings.", - "authors": ["rchavarria"], + "authors": [ + "rchavarria" + ], "contributors": [ "draalger", "kytrinyx", @@ -11,9 +12,22 @@ "SleeplessByte" ], "files": { - "solution": ["robot-name.js"], - "test": ["robot-name.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "robot-name.js" + ], + "test": [ + "robot-name.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, - "source": "A debugging session with Paul Blackwell at gSchool." + "blurb": "Manage robot factory settings.", + "source": "A debugging session with Paul Blackwell at gSchool.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": true + } } diff --git a/exercises/practice/robot-name/.meta/proof.ci.js b/exercises/practice/robot-name/.meta/proof.ci.js index 22cd5e0d52..4e4e1070b4 100644 --- a/exercises/practice/robot-name/.meta/proof.ci.js +++ b/exercises/practice/robot-name/.meta/proof.ci.js @@ -1,4 +1,3 @@ -/* eslint-disable no-underscore-dangle */ // This generates ALL the possible names in order to be able to satisfy the // final test. This also _ensures_ it _never_ has a duplicate. const LETTERS = [...'QWERTYUIOPASDFGHJKLZXCVBNM']; @@ -41,7 +40,7 @@ function generateName() { shuffledPointer += 1; if (shuffledPointer > shuffled.length) { throw new Error( - 'Can not generate another name because all the names have been used.' + 'Can not generate another name because all the names have been used.', ); } return shuffled[shuffledPointer]; diff --git a/exercises/practice/robot-name/babel.config.js b/exercises/practice/robot-name/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/robot-name/babel.config.js +++ b/exercises/practice/robot-name/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/robot-name/eslint.config.mjs b/exercises/practice/robot-name/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/robot-name/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/robot-name/jest.config.js b/exercises/practice/robot-name/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/robot-name/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/robot-name/package.json b/exercises/practice/robot-name/package.json index 1c7a4513be..590a6c4a48 100644 --- a/exercises/practice/robot-name/package.json +++ b/exercises/practice/robot-name/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/robot-name" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/robot-name/robot-name.spec.js b/exercises/practice/robot-name/robot-name.spec.js index f8b685fdc1..b6ea732a50 100644 --- a/exercises/practice/robot-name/robot-name.spec.js +++ b/exercises/practice/robot-name/robot-name.spec.js @@ -1,10 +1,18 @@ +import { + afterEach, + beforeEach, + describe, + expect, + test, + xtest, +} from '@jest/globals'; import { Robot } from './robot-name'; const areSequential = (name1, name2) => { - const alpha1 = name1.substr(0, 2); - const alpha2 = name2.substr(0, 2); - const num1 = Number(name1.substr(2, 3)); - const num2 = Number(name2.substr(2, 3)); + const alpha1 = name1.substring(0, 2); + const alpha2 = name2.substring(0, 2); + const num1 = Number(name1.substring(2, 5)); + const num2 = Number(name2.substring(2, 5)); const numDiff = num2 - num1; const alphaDiff = @@ -73,7 +81,7 @@ describe('Robot', () => { const modifyInternal = () => { robot.name += 'a modification'; }; - expect(modifyInternal).toThrow(); + expect(() => modifyInternal()).toThrow(); }); xtest('new names should not be sequential', () => { @@ -97,15 +105,22 @@ describe('Robot', () => { }); // This test is optional. - xtest('all the names can be generated', () => { - const usedNames = new Set(); - usedNames.add(robot.name); + // + // This test doesn't run on our online test runner because it will time-out + // with most implementations. It's up to you to test your solution locally. + test.skip( + 'all the names can be generated', + () => { + const usedNames = new Set(); + usedNames.add(robot.name); - for (let i = 0; i < TOTAL_NUMBER_OF_NAMES - 1; i += 1) { - const newRobot = new Robot(); - usedNames.add(newRobot.name); - } + for (let i = 0; i < TOTAL_NUMBER_OF_NAMES - 1; i += 1) { + const newRobot = new Robot(); + usedNames.add(newRobot.name); + } - expect(usedNames.size).toEqual(TOTAL_NUMBER_OF_NAMES); - }); + expect(usedNames.size).toEqual(TOTAL_NUMBER_OF_NAMES); + }, + 8 * 1000, + ); }); diff --git a/exercises/practice/robot-simulator/.docs/instructions.md b/exercises/practice/robot-simulator/.docs/instructions.md index 83be50ccc5..0ac96ce0bd 100644 --- a/exercises/practice/robot-simulator/.docs/instructions.md +++ b/exercises/practice/robot-simulator/.docs/instructions.md @@ -10,13 +10,10 @@ The robots have three possible movements: - turn left - advance -Robots are placed on a hypothetical infinite grid, facing a particular -direction (north, east, south, or west) at a set of {x,y} coordinates, +Robots are placed on a hypothetical infinite grid, facing a particular direction (north, east, south, or west) at a set of {x,y} coordinates, e.g., {3,8}, with coordinates increasing to the north and east. -The robot then receives a number of instructions, at which point the -testing facility verifies the robot's new position, and in which -direction it is pointing. +The robot then receives a number of instructions, at which point the testing facility verifies the robot's new position, and in which direction it is pointing. - The letter-string "RAALAL" means: - Turn right @@ -24,5 +21,5 @@ direction it is pointing. - Turn left - Advance once - Turn left yet again -- Say a robot starts at {7, 3} facing north. Then running this stream - of instructions should leave it at {9, 4} facing west. +- Say a robot starts at {7, 3} facing north. + Then running this stream of instructions should leave it at {9, 4} facing west. diff --git a/exercises/practice/robot-simulator/.eslintrc b/exercises/practice/robot-simulator/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/robot-simulator/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/robot-simulator/.gitignore b/exercises/practice/robot-simulator/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/robot-simulator/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/robot-simulator/.meta/config.json b/exercises/practice/robot-simulator/.meta/config.json index b78a3ef90a..52b3f8c224 100644 --- a/exercises/practice/robot-simulator/.meta/config.json +++ b/exercises/practice/robot-simulator/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Write a robot simulator.", - "authors": ["rchavarria"], + "authors": [ + "rchavarria" + ], "contributors": [ "ankorGH", "jscheffner", @@ -11,9 +12,22 @@ "tejasbubane" ], "files": { - "solution": ["robot-simulator.js"], - "test": ["robot-simulator.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "robot-simulator.js" + ], + "test": [ + "robot-simulator.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, - "source": "Inspired by an interview question at a famous company." + "blurb": "Write a robot simulator.", + "source": "Inspired by an interview question at a famous company.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/robot-simulator/.meta/proof.ci.js b/exercises/practice/robot-simulator/.meta/proof.ci.js index fe2db5094f..cc7f4ce9b2 100644 --- a/exercises/practice/robot-simulator/.meta/proof.ci.js +++ b/exercises/practice/robot-simulator/.meta/proof.ci.js @@ -17,7 +17,7 @@ export class Robot { return 'advance'; default: throw new InvalidInputError( - `${character} is not a valid instruction character.` + `${character} is not a valid instruction character.`, ); } }); diff --git a/exercises/practice/robot-simulator/babel.config.js b/exercises/practice/robot-simulator/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/robot-simulator/babel.config.js +++ b/exercises/practice/robot-simulator/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/robot-simulator/eslint.config.mjs b/exercises/practice/robot-simulator/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/robot-simulator/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/robot-simulator/jest.config.js b/exercises/practice/robot-simulator/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/robot-simulator/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/robot-simulator/package.json b/exercises/practice/robot-simulator/package.json index 3a5db215a7..dd98a2667a 100644 --- a/exercises/practice/robot-simulator/package.json +++ b/exercises/practice/robot-simulator/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/robot-simulator" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/robot-simulator/robot-simulator.js b/exercises/practice/robot-simulator/robot-simulator.js index 83971be5f1..7c5d6976a4 100644 --- a/exercises/practice/robot-simulator/robot-simulator.js +++ b/exercises/practice/robot-simulator/robot-simulator.js @@ -12,18 +12,18 @@ export class InvalidInputError extends Error { export class Robot { get bearing() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get coordinates() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } place({ x, y, direction }) { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } evaluate(instructions) { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/robot-simulator/robot-simulator.spec.js b/exercises/practice/robot-simulator/robot-simulator.spec.js index b46e629232..a86c31e18f 100644 --- a/exercises/practice/robot-simulator/robot-simulator.spec.js +++ b/exercises/practice/robot-simulator/robot-simulator.spec.js @@ -1,4 +1,5 @@ -import { Robot, InvalidInputError } from './robot-simulator'; +import { describe, expect, test, xtest } from '@jest/globals'; +import { InvalidInputError, Robot } from './robot-simulator'; function turnRight(robot) { robot.evaluate('R'); @@ -19,14 +20,14 @@ describe('Robot', () => { expect(robot.bearing).toEqual('north'); }); - test('facing east', () => { + xtest('facing east', () => { const robot = new Robot(); robot.place({ direction: 'east', x: 0, y: 0 }); expect(robot.bearing).toEqual('east'); }); - test('facing west, at origin', () => { + xtest('facing west, at origin', () => { const robot = new Robot(); robot.place({ direction: 'west', x: 0, y: 0 }); @@ -34,7 +35,7 @@ describe('Robot', () => { expect(robot.coordinates).toEqual([0, 0]); }); - test('at negative position facing south', () => { + xtest('at negative position facing south', () => { const robot = new Robot(); robot.place({ direction: 'south', x: -1, y: -1 }); @@ -47,7 +48,7 @@ describe('Robot', () => { expect(InvalidInputError.prototype).toBeInstanceOf(Error); expect(() => robot.place({ direction: 'crood', x: 0, y: 0 })).toThrow( - InvalidInputError + InvalidInputError, ); }); }); diff --git a/exercises/practice/roman-numerals/.docs/instructions.md b/exercises/practice/roman-numerals/.docs/instructions.md index ce25f205e9..50e2f5bf1c 100644 --- a/exercises/practice/roman-numerals/.docs/instructions.md +++ b/exercises/practice/roman-numerals/.docs/instructions.md @@ -1,43 +1,12 @@ -# Instructions +# Introduction -Write a function to convert from normal numbers to Roman Numerals. +Your task is to convert a number from Arabic numerals to Roman numerals. -The Romans were a clever bunch. They conquered most of Europe and ruled -it for hundreds of years. They invented concrete and straight roads and -even bikinis. One thing they never discovered though was the number -zero. This made writing and dating extensive histories of their exploits -slightly more challenging, but the system of numbers they came up with -is still in use today. For example the BBC uses Roman numerals to date -their programmes. +For this exercise, we are only concerned about traditional Roman numerals, in which the largest number is MMMCMXCIX (or 3,999). -The Romans wrote numbers using letters - I, V, X, L, C, D, M. (notice -these letters have lots of straight lines and are hence easy to hack -into stone tablets). +~~~~exercism/note +There are lots of different ways to convert between Arabic and Roman numerals. +We recommend taking a naive approach first to familiarise yourself with the concept of Roman numerals and then search for more efficient methods. -```text - 1 => I -10 => X - 7 => VII -``` - -There is no need to be able to convert numbers larger than about 3000. -(The Romans themselves didn't tend to go any higher) - -Wikipedia says: Modern Roman numerals ... are written by expressing each -digit separately starting with the left most digit and skipping any -digit with a value of zero. - -To see this in practice, consider the example of 1990. - -In Roman numerals 1990 is MCMXC: - -1000=M -900=CM -90=XC - -2008 is written as MMVIII: - -2000=MM -8=VIII - -See also: http://www.novaroma.org/via_romana/numbers.html +Make sure to check out our Deep Dive video at the end to explore the different approaches you can take! +~~~~ diff --git a/exercises/practice/roman-numerals/.docs/introduction.md b/exercises/practice/roman-numerals/.docs/introduction.md new file mode 100644 index 0000000000..6fd942fef3 --- /dev/null +++ b/exercises/practice/roman-numerals/.docs/introduction.md @@ -0,0 +1,59 @@ +# Description + +Today, most people in the world use Arabic numerals (0–9). +But if you travelled back two thousand years, you'd find that most Europeans were using Roman numerals instead. + +To write a Roman numeral we use the following Latin letters, each of which has a value: + +| M | D | C | L | X | V | I | +| ---- | --- | --- | --- | --- | --- | --- | +| 1000 | 500 | 100 | 50 | 10 | 5 | 1 | + +A Roman numeral is a sequence of these letters, and its value is the sum of the letters' values. +For example, `XVIII` has the value 18 (`10 + 5 + 1 + 1 + 1 = 18`). + +There's one rule that makes things trickier though, and that's that **the same letter cannot be used more than three times in succession**. +That means that we can't express numbers such as 4 with the seemingly natural `IIII`. +Instead, for those numbers, we use a subtraction method between two letters. +So we think of `4` not as `1 + 1 + 1 + 1` but instead as `5 - 1`. +And slightly confusingly to our modern thinking, we write the smaller number first. +This applies only in the following cases: 4 (`IV`), 9 (`IX`), 40 (`XL`), 90 (`XC`), 400 (`CD`) and 900 (`CM`). + +Order matters in Roman numerals! +Letters (and the special compounds above) must be ordered by decreasing value from left to right. + +Here are some examples: + +```text + 105 => CV +---- => -- + 100 => C ++ 5 => V +``` + +```text + 106 => CVI +---- => -- + 100 => C ++ 5 => V ++ 1 => I +``` + +```text + 104 => CIV +---- => --- + 100 => C ++ 4 => IV +``` + +And a final more complex example: + +```text + 1996 => MCMXCVI +----- => ------- + 1000 => M ++ 900 => CM ++ 90 => XC ++ 5 => V ++ 1 => I +``` diff --git a/exercises/practice/roman-numerals/.eslintrc b/exercises/practice/roman-numerals/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/roman-numerals/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/roman-numerals/.gitignore b/exercises/practice/roman-numerals/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/roman-numerals/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/roman-numerals/.meta/config.json b/exercises/practice/roman-numerals/.meta/config.json index 8f1e166068..729f3a21cf 100644 --- a/exercises/practice/roman-numerals/.meta/config.json +++ b/exercises/practice/roman-numerals/.meta/config.json @@ -1,19 +1,34 @@ { - "blurb": "Write a function to convert from normal numbers to Roman Numerals.", - "authors": ["rchavarria"], + "authors": [ + "rchavarria" + ], "contributors": [ "ankorGH", "cmccandless", "matthewmorgan", "ryanplusplus", "serixscorpio", - "SleeplessByte" + "SleeplessByte", + "tejasbubane" ], "files": { - "solution": ["roman-numerals.js"], - "test": ["roman-numerals.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "roman-numerals.js" + ], + "test": [ + "roman-numerals.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Convert modern Arabic numbers into Roman numerals.", "source": "The Roman Numeral Kata", - "source_url": "http://codingdojo.org/cgi-bin/index.pl?KataRomanNumerals" + "source_url": "https://codingdojo.org/kata/RomanNumerals/", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/roman-numerals/.meta/tests.toml b/exercises/practice/roman-numerals/.meta/tests.toml index 521dcc4da5..709011b552 100644 --- a/exercises/practice/roman-numerals/.meta/tests.toml +++ b/exercises/practice/roman-numerals/.meta/tests.toml @@ -1,60 +1,91 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [19828a3a-fbf7-4661-8ddd-cbaeee0e2178] -description = "1 is a single I" +description = "1 is I" [f088f064-2d35-4476-9a41-f576da3f7b03] -description = "2 is two I's" +description = "2 is II" [b374a79c-3bea-43e6-8db8-1286f79c7106] -description = "3 is three I's" +description = "3 is III" [05a0a1d4-a140-4db1-82e8-fcc21fdb49bb] -description = "4, being 5 - 1, is IV" +description = "4 is IV" [57c0f9ad-5024-46ab-975d-de18c430b290] -description = "5 is a single V" +description = "5 is V" [20a2b47f-e57f-4797-a541-0b3825d7f249] -description = "6, being 5 + 1, is VI" +description = "6 is VI" [ff3fb08c-4917-4aab-9f4e-d663491d083d] -description = "9, being 10 - 1, is IX" +description = "9 is IX" + +[6d1d82d5-bf3e-48af-9139-87d7165ed509] +description = "16 is XVI" [2bda64ca-7d28-4c56-b08d-16ce65716cf6] -description = "20 is two X's" +description = "27 is XXVII" [a1f812ef-84da-4e02-b4f0-89c907d0962c] -description = "48 is not 50 - 2 but rather 40 + 8" +description = "48 is XLVIII" [607ead62-23d6-4c11-a396-ef821e2e5f75] -description = "49 is not 40 + 5 + 4 but rather 50 - 10 + 10 - 1" +description = "49 is XLIX" [d5b283d4-455d-4e68-aacf-add6c4b51915] -description = "50 is a single L" +description = "59 is LIX" + +[4465ffd5-34dc-44f3-ada5-56f5007b6dad] +description = "66 is LXVI" [46b46e5b-24da-4180-bfe2-2ef30b39d0d0] -description = "90, being 100 - 10, is XC" +description = "93 is XCIII" [30494be1-9afb-4f84-9d71-db9df18b55e3] -description = "100 is a single C" +description = "141 is CXLI" [267f0207-3c55-459a-b81d-67cec7a46ed9] -description = "60, being 50 + 10, is LX" +description = "163 is CLXIII" + +[902ad132-0b4d-40e3-8597-ba5ed611dd8d] +description = "166 is CLXVI" [cdb06885-4485-4d71-8bfb-c9d0f496b404] -description = "400, being 500 - 100, is CD" +description = "402 is CDII" [6b71841d-13b2-46b4-ba97-dec28133ea80] -description = "500 is a single D" +description = "575 is DLXXV" + +[dacb84b9-ea1c-4a61-acbb-ce6b36674906] +description = "666 is DCLXVI" [432de891-7fd6-4748-a7f6-156082eeca2f] -description = "900, being 1000 - 100, is CM" +description = "911 is CMXI" [e6de6d24-f668-41c0-88d7-889c0254d173] -description = "1000 is a single M" +description = "1024 is MXXIV" + +[efbe1d6a-9f98-4eb5-82bc-72753e3ac328] +description = "1666 is MDCLXVI" [bb550038-d4eb-4be2-a9ce-f21961ac3bc6] -description = "3000 is three M's" +description = "3000 is MMM" + +[3bc4b41c-c2e6-49d9-9142-420691504336] +description = "3001 is MMMI" + +[2f89cad7-73f6-4d1b-857b-0ef531f68b7e] +description = "3888 is MMMDCCCLXXXVIII" + +[4e18e96b-5fbb-43df-a91b-9cb511fe0856] +description = "3999 is MMMCMXCIX" diff --git a/exercises/practice/roman-numerals/babel.config.js b/exercises/practice/roman-numerals/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/roman-numerals/babel.config.js +++ b/exercises/practice/roman-numerals/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/roman-numerals/eslint.config.mjs b/exercises/practice/roman-numerals/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/roman-numerals/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/roman-numerals/jest.config.js b/exercises/practice/roman-numerals/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/roman-numerals/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/roman-numerals/package.json b/exercises/practice/roman-numerals/package.json index d496bdd6b0..6f89567638 100644 --- a/exercises/practice/roman-numerals/package.json +++ b/exercises/practice/roman-numerals/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/roman-numerals" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/roman-numerals/roman-numerals.js b/exercises/practice/roman-numerals/roman-numerals.js index de27903c7e..ec51eb60dc 100644 --- a/exercises/practice/roman-numerals/roman-numerals.js +++ b/exercises/practice/roman-numerals/roman-numerals.js @@ -4,5 +4,5 @@ // export const toRoman = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/roman-numerals/roman-numerals.spec.js b/exercises/practice/roman-numerals/roman-numerals.spec.js index 74c6e684e4..12bf5a873b 100644 --- a/exercises/practice/roman-numerals/roman-numerals.spec.js +++ b/exercises/practice/roman-numerals/roman-numerals.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { toRoman } from './roman-numerals'; describe('toRoman()', () => { @@ -8,16 +9,25 @@ describe('toRoman()', () => { xtest('converts 5', () => expect(toRoman(5)).toEqual('V')); xtest('converts 6', () => expect(toRoman(6)).toEqual('VI')); xtest('converts 9', () => expect(toRoman(9)).toEqual('IX')); + xtest('converts 16', () => expect(toRoman(16)).toEqual('XVI')); xtest('converts 27', () => expect(toRoman(27)).toEqual('XXVII')); xtest('converts 48', () => expect(toRoman(48)).toEqual('XLVIII')); xtest('converts 49', () => expect(toRoman(49)).toEqual('XLIX')); xtest('converts 59', () => expect(toRoman(59)).toEqual('LIX')); + xtest('converts 66', () => expect(toRoman(66)).toEqual('LXVI')); xtest('converts 93', () => expect(toRoman(93)).toEqual('XCIII')); xtest('converts 141', () => expect(toRoman(141)).toEqual('CXLI')); xtest('converts 163', () => expect(toRoman(163)).toEqual('CLXIII')); + xtest('converts 166', () => expect(toRoman(166)).toEqual('CLXVI')); xtest('converts 402', () => expect(toRoman(402)).toEqual('CDII')); xtest('converts 575', () => expect(toRoman(575)).toEqual('DLXXV')); + xtest('converts 666', () => expect(toRoman(666)).toEqual('DCLXVI')); xtest('converts 911', () => expect(toRoman(911)).toEqual('CMXI')); xtest('converts 1024', () => expect(toRoman(1024)).toEqual('MXXIV')); + xtest('converts 1666', () => expect(toRoman(1666)).toEqual('MDCLXVI')); xtest('converts 3000', () => expect(toRoman(3000)).toEqual('MMM')); + xtest('converts 3001', () => expect(toRoman(3001)).toEqual('MMMI')); + xtest('converts 3888', () => + expect(toRoman(3888)).toEqual('MMMDCCCLXXXVIII')); + xtest('converts 3999', () => expect(toRoman(3999)).toEqual('MMMCMXCIX')); }); diff --git a/exercises/practice/rotational-cipher/.docs/instructions.md b/exercises/practice/rotational-cipher/.docs/instructions.md index 8c0717333b..4bf64ca1d3 100644 --- a/exercises/practice/rotational-cipher/.docs/instructions.md +++ b/exercises/practice/rotational-cipher/.docs/instructions.md @@ -2,11 +2,9 @@ Create an implementation of the rotational cipher, also sometimes called the Caesar cipher. -The Caesar cipher is a simple shift cipher that relies on -transposing all the letters in the alphabet using an integer key -between `0` and `26`. Using a key of `0` or `26` will always yield -the same output due to modular arithmetic. The letter is shifted -for as many values as the value of the key. +The Caesar cipher is a simple shift cipher that relies on transposing all the letters in the alphabet using an integer key between `0` and `26`. +Using a key of `0` or `26` will always yield the same output due to modular arithmetic. +The letter is shifted for as many values as the value of the key. The general notation for rotational ciphers is `ROT + `. The most commonly used rotational cipher is `ROT13`. diff --git a/exercises/practice/rotational-cipher/.eslintrc b/exercises/practice/rotational-cipher/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/rotational-cipher/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/rotational-cipher/.gitignore b/exercises/practice/rotational-cipher/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/rotational-cipher/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/rotational-cipher/.meta/config.json b/exercises/practice/rotational-cipher/.meta/config.json index d55af146db..34283f3566 100644 --- a/exercises/practice/rotational-cipher/.meta/config.json +++ b/exercises/practice/rotational-cipher/.meta/config.json @@ -1,12 +1,31 @@ { - "blurb": "Create an implementation of the rotational cipher, also sometimes called the Caesar cipher.", - "authors": ["MattH-be"], - "contributors": ["ankorGH", "SleeplessByte", "tejasbubane", "xarxziux"], + "authors": [ + "MattH-be" + ], + "contributors": [ + "ankorGH", + "SleeplessByte", + "tejasbubane", + "xarxziux" + ], "files": { - "solution": ["rotational-cipher.js"], - "test": ["rotational-cipher.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "rotational-cipher.js" + ], + "test": [ + "rotational-cipher.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Create an implementation of the rotational cipher, also sometimes called the Caesar cipher.", "source": "Wikipedia", - "source_url": "https://en.wikipedia.org/wiki/Caesar_cipher" + "source_url": "https://en.wikipedia.org/wiki/Caesar_cipher", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/rotational-cipher/.meta/proof.ci.js b/exercises/practice/rotational-cipher/.meta/proof.ci.js index a9be173c11..aef62ae6b9 100644 --- a/exercises/practice/rotational-cipher/.meta/proof.ci.js +++ b/exercises/practice/rotational-cipher/.meta/proof.ci.js @@ -7,7 +7,7 @@ export function rotate(text, shift) { return isAlpha ? String.fromCharCode( - ((c.charCodeAt(0) - charShift + shift) % 26) + charShift + ((c.charCodeAt(0) - charShift + shift) % 26) + charShift, ) : c; }) diff --git a/exercises/practice/rotational-cipher/babel.config.js b/exercises/practice/rotational-cipher/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/rotational-cipher/babel.config.js +++ b/exercises/practice/rotational-cipher/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/rotational-cipher/eslint.config.mjs b/exercises/practice/rotational-cipher/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/rotational-cipher/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/rotational-cipher/jest.config.js b/exercises/practice/rotational-cipher/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/rotational-cipher/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/rotational-cipher/package.json b/exercises/practice/rotational-cipher/package.json index 821b02247c..fc1f50fb3f 100644 --- a/exercises/practice/rotational-cipher/package.json +++ b/exercises/practice/rotational-cipher/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/rotational-cipher" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/rotational-cipher/rotational-cipher.js b/exercises/practice/rotational-cipher/rotational-cipher.js index d06eb61daa..3e1f26865b 100644 --- a/exercises/practice/rotational-cipher/rotational-cipher.js +++ b/exercises/practice/rotational-cipher/rotational-cipher.js @@ -4,5 +4,5 @@ // export const rotate = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/rotational-cipher/rotational-cipher.spec.js b/exercises/practice/rotational-cipher/rotational-cipher.spec.js index 3e1e74b9c9..0cf962939d 100644 --- a/exercises/practice/rotational-cipher/rotational-cipher.spec.js +++ b/exercises/practice/rotational-cipher/rotational-cipher.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { rotate } from './rotational-cipher'; describe('Rotational cipher', () => { diff --git a/exercises/practice/run-length-encoding/.docs/instructions.md b/exercises/practice/run-length-encoding/.docs/instructions.md index 95f7a9d69c..fc8ce05694 100644 --- a/exercises/practice/run-length-encoding/.docs/instructions.md +++ b/exercises/practice/run-length-encoding/.docs/instructions.md @@ -2,8 +2,7 @@ Implement run-length encoding and decoding. -Run-length encoding (RLE) is a simple form of data compression, where runs -(consecutive data elements) are replaced by just one data value and count. +Run-length encoding (RLE) is a simple form of data compression, where runs (consecutive data elements) are replaced by just one data value and count. For example we can represent the original 53 characters with only 13. @@ -11,14 +10,11 @@ For example we can represent the original 53 characters with only 13. "WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB" -> "12WB12W3B24WB" ``` -RLE allows the original data to be perfectly reconstructed from -the compressed data, which makes it a lossless data compression. +RLE allows the original data to be perfectly reconstructed from the compressed data, which makes it a lossless data compression. ```text "AABCCCDEEEE" -> "2AB3CD4E" -> "AABCCCDEEEE" ``` -For simplicity, you can assume that the unencoded string will only contain -the letters A through Z (either lower or upper case) and whitespace. This way -data to be encoded will never contain any numbers and numbers inside data to -be decoded always represent the count for the following character. +For simplicity, you can assume that the unencoded string will only contain the letters A through Z (either lower or upper case) and whitespace. +This way data to be encoded will never contain any numbers and numbers inside data to be decoded always represent the count for the following character. diff --git a/exercises/practice/run-length-encoding/.eslintrc b/exercises/practice/run-length-encoding/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/run-length-encoding/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/run-length-encoding/.gitignore b/exercises/practice/run-length-encoding/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/run-length-encoding/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/run-length-encoding/.meta/config.json b/exercises/practice/run-length-encoding/.meta/config.json index ddf5253077..21c97b893e 100644 --- a/exercises/practice/run-length-encoding/.meta/config.json +++ b/exercises/practice/run-length-encoding/.meta/config.json @@ -1,12 +1,30 @@ { - "blurb": "Implement run-length encoding and decoding.", - "authors": ["skabbass1"], - "contributors": ["ankorGH", "serixscorpio", "SleeplessByte"], + "authors": [ + "skabbass1" + ], + "contributors": [ + "ankorGH", + "serixscorpio", + "SleeplessByte" + ], "files": { - "solution": ["run-length-encoding.js"], - "test": ["run-length-encoding.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "run-length-encoding.js" + ], + "test": [ + "run-length-encoding.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Implement run-length encoding and decoding.", "source": "Wikipedia", - "source_url": "https://en.wikipedia.org/wiki/Run-length_encoding" + "source_url": "https://en.wikipedia.org/wiki/Run-length_encoding", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/run-length-encoding/.meta/proof.ci.js b/exercises/practice/run-length-encoding/.meta/proof.ci.js index 133e3d2bb5..0a090ebf67 100644 --- a/exercises/practice/run-length-encoding/.meta/proof.ci.js +++ b/exercises/practice/run-length-encoding/.meta/proof.ci.js @@ -1,13 +1,13 @@ export const encode = (plainText) => { const consecutiveChars = /([\w\s])\1*/g; return plainText.replace(consecutiveChars, (match) => - match.length > 1 ? match.length + match[0] : match[0] + match.length > 1 ? match.length + match[0] : match[0], ); }; export const decode = (encodedText) => { const countAndChar = /(\d+)(\w|\s)/g; return encodedText.replace(countAndChar, (match, repeats, char) => - new Array(Number(repeats) + 1).join(char) + new Array(Number(repeats) + 1).join(char), ); }; diff --git a/exercises/practice/run-length-encoding/babel.config.js b/exercises/practice/run-length-encoding/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/run-length-encoding/babel.config.js +++ b/exercises/practice/run-length-encoding/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/run-length-encoding/eslint.config.mjs b/exercises/practice/run-length-encoding/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/run-length-encoding/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/run-length-encoding/jest.config.js b/exercises/practice/run-length-encoding/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/run-length-encoding/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/run-length-encoding/package.json b/exercises/practice/run-length-encoding/package.json index 93b5b05401..59f1ea99b8 100644 --- a/exercises/practice/run-length-encoding/package.json +++ b/exercises/practice/run-length-encoding/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/run-length-encoding" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/run-length-encoding/run-length-encoding.js b/exercises/practice/run-length-encoding/run-length-encoding.js index 713839cf10..0878f133d3 100644 --- a/exercises/practice/run-length-encoding/run-length-encoding.js +++ b/exercises/practice/run-length-encoding/run-length-encoding.js @@ -4,9 +4,9 @@ // export const encode = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; export const decode = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/run-length-encoding/run-length-encoding.spec.js b/exercises/practice/run-length-encoding/run-length-encoding.spec.js index 6d7fdf692b..a255286c66 100644 --- a/exercises/practice/run-length-encoding/run-length-encoding.spec.js +++ b/exercises/practice/run-length-encoding/run-length-encoding.spec.js @@ -1,4 +1,5 @@ -import { encode, decode } from './run-length-encoding'; +import { describe, expect, test, xtest } from '@jest/globals'; +import { decode, encode } from './run-length-encoding'; describe('run-length encode a string', () => { test('encode empty string', () => { @@ -15,7 +16,7 @@ describe('run-length encode a string', () => { xtest('encode string with single characters mixed with repeated characters', () => { expect( - encode('WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB') + encode('WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB'), ).toEqual('12WB12W3B24WB'); }); @@ -43,7 +44,7 @@ describe('run-length decode a string', () => { xtest('decode string with single characters mixed with repeated characters', () => { expect(decode('12WB12W3B24WB')).toEqual( - 'WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB' + 'WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB', ); }); diff --git a/exercises/practice/saddle-points/.docs/instructions.md b/exercises/practice/saddle-points/.docs/instructions.md index aa11e05713..f69cdab958 100644 --- a/exercises/practice/saddle-points/.docs/instructions.md +++ b/exercises/practice/saddle-points/.docs/instructions.md @@ -1,29 +1,27 @@ # Instructions -Detect saddle points in a matrix. +Your task is to find the potential trees where you could build your tree house. -So say you have a matrix like so: +The data company provides the data as grids that show the heights of the trees. +The rows of the grid represent the east-west direction, and the columns represent the north-south direction. -```text - 1 2 3 - |--------- -1 | 9 8 7 -2 | 5 3 2 <--- saddle point at column 1, row 2, with value 5 -3 | 6 6 7 -``` - -It has a saddle point at column 1, row 2. +An acceptable tree will be the largest in its row, while being the smallest in its column. -It's called a "saddle point" because it is greater than or equal to -every element in its row and less than or equal to every element in -its column. +A grid might not have any good trees at all. +Or it might have one, or even several. -A matrix may have zero or more saddle points. +Here is a grid that has exactly one candidate tree. -Your code should be able to provide the (possibly empty) list of all the -saddle points for any given matrix. +```text + ↓ + 1 2 3 4 + |----------- + 1 | 9 8 7 8 +→ 2 |[5] 3 2 4 + 3 | 6 6 7 1 +``` -The matrix can have a different number of rows and columns (Non square). +- Row 2 has values 5, 3, 2, and 4. The largest value is 5. +- Column 1 has values 9, 5, and 6. The smallest value is 5. -Note that you may find other definitions of matrix saddle points online, -but the tests for this exercise follow the above unambiguous definition. +So the point at `[2, 1]` (row: 2, column: 1) is a great spot for a tree house. diff --git a/exercises/practice/saddle-points/.docs/introduction.md b/exercises/practice/saddle-points/.docs/introduction.md new file mode 100644 index 0000000000..34b2c77e0c --- /dev/null +++ b/exercises/practice/saddle-points/.docs/introduction.md @@ -0,0 +1,11 @@ +# Introduction + +You plan to build a tree house in the woods near your house so that you can watch the sun rise and set. + +You've obtained data from a local survey company that show the height of every tree in each rectangular section of the map. +You need to analyze each grid on the map to find good trees for your tree house. + +A good tree is both: + +- taller than every tree to the east and west, so that you have the best possible view of the sunrises and sunsets. +- shorter than every tree to the north and south, to minimize the amount of tree climbing. diff --git a/exercises/practice/saddle-points/.eslintrc b/exercises/practice/saddle-points/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/saddle-points/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/saddle-points/.gitignore b/exercises/practice/saddle-points/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/saddle-points/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/saddle-points/.meta/config.json b/exercises/practice/saddle-points/.meta/config.json index dfd2721cb8..de01afd353 100644 --- a/exercises/practice/saddle-points/.meta/config.json +++ b/exercises/practice/saddle-points/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Detect saddle points in a matrix.", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "ankorGH", "ovidiu141", @@ -10,10 +11,23 @@ "tejasbubane" ], "files": { - "solution": ["saddle-points.js"], - "test": ["saddle-points.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "saddle-points.js" + ], + "test": [ + "saddle-points.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Detect saddle points in a matrix.", "source": "J Dalbey's Programming Practice problems", - "source_url": "http://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html" + "source_url": "https://users.csc.calpoly.edu/~jdalbey/103/Projects/ProgrammingPractice.html", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/saddle-points/babel.config.js b/exercises/practice/saddle-points/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/saddle-points/babel.config.js +++ b/exercises/practice/saddle-points/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/saddle-points/eslint.config.mjs b/exercises/practice/saddle-points/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/saddle-points/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/saddle-points/jest.config.js b/exercises/practice/saddle-points/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/saddle-points/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/saddle-points/package.json b/exercises/practice/saddle-points/package.json index db673c2a2f..db41801863 100644 --- a/exercises/practice/saddle-points/package.json +++ b/exercises/practice/saddle-points/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/saddle-points" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/saddle-points/saddle-points.js b/exercises/practice/saddle-points/saddle-points.js index e4b6b85341..71a38a388e 100644 --- a/exercises/practice/saddle-points/saddle-points.js +++ b/exercises/practice/saddle-points/saddle-points.js @@ -4,5 +4,5 @@ // export const saddlePoints = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/saddle-points/saddle-points.spec.js b/exercises/practice/saddle-points/saddle-points.spec.js index 546147f890..f3dc21bb21 100644 --- a/exercises/practice/saddle-points/saddle-points.spec.js +++ b/exercises/practice/saddle-points/saddle-points.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { saddlePoints } from './saddle-points'; describe('Saddle Points', () => { @@ -8,7 +9,7 @@ describe('Saddle Points', () => { [9, 8, 7], [5, 3, 2], [6, 6, 7], - ]) + ]), ).toEqual(expected); }); @@ -22,7 +23,7 @@ describe('Saddle Points', () => { [1, 2, 3], [3, 1, 2], [2, 3, 1], - ]) + ]), ).toEqual([]); }); @@ -37,7 +38,7 @@ describe('Saddle Points', () => { [4, 5, 4], [3, 5, 5], [1, 5, 4], - ]) + ]), ).toEqual(expected); }); @@ -52,7 +53,7 @@ describe('Saddle Points', () => { [6, 7, 8], [5, 5, 5], [7, 5, 6], - ]) + ]), ).toEqual(expected); }); @@ -63,7 +64,7 @@ describe('Saddle Points', () => { [8, 7, 9], [6, 7, 6], [3, 2, 5], - ]) + ]), ).toEqual(expected); }); @@ -76,7 +77,7 @@ describe('Saddle Points', () => { saddlePoints([ [3, 1, 3], [3, 2, 4], - ]) + ]), ).toEqual(expected); }); diff --git a/exercises/practice/satellite/.docs/instructions.md b/exercises/practice/satellite/.docs/instructions.md index 4fa066dd54..fbbf14f439 100644 --- a/exercises/practice/satellite/.docs/instructions.md +++ b/exercises/practice/satellite/.docs/instructions.md @@ -1,17 +1,15 @@ -# Description +# Instructions -Imagine you need to transmit a binary tree to a satellite approaching Alpha -Centauri and you have limited bandwidth. Since the tree has no repeating -items it can be uniquely represented by its [pre-order and in-order traversals][wiki]. +Imagine you need to transmit a binary tree to a satellite approaching Alpha Centauri and you have limited bandwidth. +Since the tree has no repeating items it can be uniquely represented by its [pre-order and in-order traversals][wiki]. Write the software for the satellite to rebuild the tree from the traversals. -A pre-order traversal reads the value of the current node before (hence "pre") -reading the left subtree in pre-order. Afterwards the right subtree is read -in pre-order. +A pre-order traversal reads the value of the current node before (hence "pre") reading the left subtree in pre-order. +Afterwards the right subtree is read in pre-order. -An in-order traversal reads the left subtree in-order then the current node and -finally the right subtree in-order. So in order from left to right. +An in-order traversal reads the left subtree in-order then the current node and finally the right subtree in-order. +So in order from left to right. For example the pre-order traversal of this tree is [a, i, x, f, r]. The in-order traversal of this tree is [i, a, f, x, r] diff --git a/exercises/practice/satellite/.eslintrc b/exercises/practice/satellite/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/satellite/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/satellite/.gitignore b/exercises/practice/satellite/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/satellite/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/satellite/.meta/config.json b/exercises/practice/satellite/.meta/config.json index 2fec0f4054..82f08d35e8 100644 --- a/exercises/practice/satellite/.meta/config.json +++ b/exercises/practice/satellite/.meta/config.json @@ -1,11 +1,23 @@ { - "blurb": "Rebuild binary trees from pre-order and in-order traversals.", - "authors": ["lpizzinidev"], - "contributors": [], + "authors": [ + "lpizzinidev" + ], "files": { - "solution": ["satellite.js"], - "test": ["satellite.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "satellite.js" + ], + "test": [ + "satellite.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, - "title": "Satellite" + "blurb": "Rebuild binary trees from pre-order and in-order traversals.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/satellite/babel.config.js b/exercises/practice/satellite/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/satellite/babel.config.js +++ b/exercises/practice/satellite/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/satellite/eslint.config.mjs b/exercises/practice/satellite/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/satellite/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/satellite/jest.config.js b/exercises/practice/satellite/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/satellite/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/satellite/package.json b/exercises/practice/satellite/package.json index de98cecb1c..483e37704a 100644 --- a/exercises/practice/satellite/package.json +++ b/exercises/practice/satellite/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/satellite" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/satellite/satellite.js b/exercises/practice/satellite/satellite.js index 7e0fe2f940..d3bf54978a 100644 --- a/exercises/practice/satellite/satellite.js +++ b/exercises/practice/satellite/satellite.js @@ -4,5 +4,5 @@ // export const treeFromTraversals = (preorder, inorder) => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/satellite/satellite.spec.js b/exercises/practice/satellite/satellite.spec.js index 9828f5f5fb..1c401ffecb 100644 --- a/exercises/practice/satellite/satellite.spec.js +++ b/exercises/practice/satellite/satellite.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { treeFromTraversals } from './satellite'; describe('Satellite', () => { @@ -5,12 +6,12 @@ describe('Satellite', () => { expect(treeFromTraversals([], [])).toEqual({}); }); - test('Tree with one item', () => { + xtest('Tree with one item', () => { const expected = { value: 'a', left: {}, right: {} }; expect(treeFromTraversals(['a'], ['a'])).toEqual(expected); }); - test('Tree with many items', () => { + xtest('Tree with many items', () => { const preorder = ['a', 'i', 'x', 'f', 'r']; const inorder = ['i', 'a', 'f', 'x', 'r']; const expected = { @@ -25,7 +26,7 @@ describe('Satellite', () => { expect(treeFromTraversals(preorder, inorder)).toEqual(expected); }); - test('Reject traversals of different length', () => { + xtest('Reject traversals of different length', () => { const preorder = ['a', 'b']; const inorder = ['b', 'a', 'r']; expect(() => { @@ -33,7 +34,7 @@ describe('Satellite', () => { }).toThrow(new Error('traversals must have the same length')); }); - test('Reject inconsistent traversals of same length', () => { + xtest('Reject inconsistent traversals of same length', () => { const preorder = ['x', 'y', 'z']; const inorder = ['a', 'b', 'c']; expect(() => { @@ -41,7 +42,7 @@ describe('Satellite', () => { }).toThrow(new Error('traversals must have the same elements')); }); - test('Reject traversals with repeated items', () => { + xtest('Reject traversals with repeated items', () => { const preorder = ['a', 'b', 'a']; const inorder = ['b', 'a', 'a']; expect(() => { diff --git a/exercises/practice/say/.docs/instructions.md b/exercises/practice/say/.docs/instructions.md index e7d8bd0e00..3251c519ac 100644 --- a/exercises/practice/say/.docs/instructions.md +++ b/exercises/practice/say/.docs/instructions.md @@ -1,63 +1,12 @@ # Instructions -Given a number from 0 to 999,999,999,999, spell out that number in English. +Given a number, your task is to express it in English words exactly as your friend should say it out loud. +Yaʻqūb expects to use numbers from 0 up to 999,999,999,999. -## Step 1 +Examples: -Handle the basic case of 0 through 99. - -If the input to the program is `22`, then the output should be -`'twenty-two'`. - -Your program should complain loudly if given a number outside the -blessed range. - -Some good test cases for this program are: - -- 0 -- 14 -- 50 -- 98 -- -1 -- 100 - -### Extension - -If you're on a Mac, shell out to Mac OS X's `say` program to talk out -loud. If you're on Linux or Windows, eSpeakNG may be available with the command `espeak`. - -## Step 2 - -Implement breaking a number up into chunks of thousands. - -So `1234567890` should yield a list like 1, 234, 567, and 890, while the -far simpler `1000` should yield just 1 and 0. - -The program must also report any values that are out of range. - -## Step 3 - -Now handle inserting the appropriate scale word between those chunks. - -So `1234567890` should yield `'1 billion 234 million 567 thousand 890'` - -The program must also report any values that are out of range. It's -fine to stop at "trillion". - -## Step 4 - -Put it all together to get nothing but plain English. - -`12345` should give `twelve thousand three hundred forty-five`. - -The program must also report any values that are out of range. - -### Extensions - -Use _and_ (correctly) when spelling out the number in English: - -- 14 becomes "fourteen". -- 100 becomes "one hundred". -- 120 becomes "one hundred and twenty". -- 1002 becomes "one thousand and two". -- 1323 becomes "one thousand three hundred and twenty-three". +- 0 → zero +- 1 → one +- 12 → twelve +- 123 → one hundred twenty-three +- 1,234 → one thousand two hundred thirty-four diff --git a/exercises/practice/say/.docs/introduction.md b/exercises/practice/say/.docs/introduction.md new file mode 100644 index 0000000000..abd22851ef --- /dev/null +++ b/exercises/practice/say/.docs/introduction.md @@ -0,0 +1,6 @@ +# Introduction + +Your friend Yaʻqūb works the counter at the busiest deli in town, slicing, weighing, and wrapping orders for a never-ending line of hungry customers. +To keep things moving, each customer takes a numbered ticket when they arrive. + +When it’s time to call the next person, Yaʻqūb reads their number out loud, always in full English words to make sure everyone hears it clearly. diff --git a/exercises/practice/say/.eslintrc b/exercises/practice/say/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/say/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/say/.gitignore b/exercises/practice/say/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/say/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/say/.meta/config.json b/exercises/practice/say/.meta/config.json index 2680b20316..ecda285894 100644 --- a/exercises/practice/say/.meta/config.json +++ b/exercises/practice/say/.meta/config.json @@ -1,8 +1,10 @@ { - "blurb": "Given a number from 0 to 999,999,999,999, spell out that number in English.", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "ankorGH", + "jagdish-15", "msomji", "rchavarria", "ryanplusplus", @@ -11,10 +13,23 @@ "fearless-spider" ], "files": { - "solution": ["say.js"], - "test": ["say.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "say.js" + ], + "test": [ + "say.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, - "source": "A variation on JavaRanch CattleDrive, exercise 4a", - "source_url": "http://www.javaranch.com/say.jsp" + "blurb": "Given a number from 0 to 999,999,999,999, spell out that number in English.", + "source": "A variation on the JavaRanch CattleDrive, Assignment 4", + "source_url": "https://web.archive.org/web/20240907035912/https://coderanch.com/wiki/718804", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/say/.meta/tests.toml b/exercises/practice/say/.meta/tests.toml index df50fd17bb..a5532e9ed3 100644 --- a/exercises/practice/say/.meta/tests.toml +++ b/exercises/practice/say/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [5d22a120-ba0c-428c-bd25-8682235d83e8] description = "zero" @@ -17,12 +24,24 @@ description = "twenty" [d78601eb-4a84-4bfa-bf0e-665aeb8abe94] description = "twenty-two" +[f010d4ca-12c9-44e9-803a-27789841adb1] +description = "thirty" + +[738ce12d-ee5c-4dfb-ad26-534753a98327] +description = "ninety-nine" + [e417d452-129e-4056-bd5b-6eb1df334dce] description = "one hundred" [d6924f30-80ba-4597-acf6-ea3f16269da8] description = "one hundred twenty-three" +[2f061132-54bc-4fd4-b5df-0a3b778959b9] +description = "two hundred" + +[feed6627-5387-4d38-9692-87c0dbc55c33] +description = "nine hundred ninety-nine" + [3d83da89-a372-46d3-b10d-de0c792432b3] description = "one thousand" diff --git a/exercises/practice/say/babel.config.js b/exercises/practice/say/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/say/babel.config.js +++ b/exercises/practice/say/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/say/eslint.config.mjs b/exercises/practice/say/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/say/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/say/jest.config.js b/exercises/practice/say/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/say/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/say/package.json b/exercises/practice/say/package.json index 2cec4258c9..ba99a6bf52 100644 --- a/exercises/practice/say/package.json +++ b/exercises/practice/say/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/say" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/say/say.js b/exercises/practice/say/say.js index e675c5ec1b..f34f716597 100644 --- a/exercises/practice/say/say.js +++ b/exercises/practice/say/say.js @@ -4,5 +4,5 @@ // export const say = (n) => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/say/say.spec.js b/exercises/practice/say/say.spec.js index 385f160dcc..01504764c1 100644 --- a/exercises/practice/say/say.spec.js +++ b/exercises/practice/say/say.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { say } from './say'; describe('say', () => { @@ -21,6 +22,14 @@ describe('say', () => { expect(say(22)).toBe('twenty-two'); }); + xtest('thirty', () => { + expect(say(30)).toBe('thirty'); + }); + + xtest('ninety-nine', () => { + expect(say(99)).toBe('ninety-nine'); + }); + xtest('one hundred', () => { expect(say(100)).toBe('one hundred'); }); @@ -29,6 +38,14 @@ describe('say', () => { expect(say(123)).toBe('one hundred twenty-three'); }); + xtest('two hundred', () => { + expect(say(200)).toBe('two hundred'); + }); + + xtest('nine hundred ninety-nine', () => { + expect(say(999)).toBe('nine hundred ninety-nine'); + }); + xtest('one thousand', () => { expect(say(1000)).toBe('one thousand'); }); @@ -47,7 +64,7 @@ describe('say', () => { xtest('one million two thousand three hundred forty-five', () => { expect(say(1002345)).toBe( - 'one million two thousand three hundred forty-five' + 'one million two thousand three hundred forty-five', ); }); diff --git a/exercises/practice/scale-generator/.docs/instructions.md b/exercises/practice/scale-generator/.docs/instructions.md index 1d155daa66..ebb7debc76 100644 --- a/exercises/practice/scale-generator/.docs/instructions.md +++ b/exercises/practice/scale-generator/.docs/instructions.md @@ -1,49 +1,68 @@ # Instructions -Given a tonic, or starting note, and a set of intervals, generate -the musical scale starting with the tonic and following the -specified interval pattern. +## Chromatic Scales -Scales in Western music are based on the chromatic (12-note) scale. This -scale can be expressed as the following group of pitches: +Scales in Western music are based on the chromatic (12-note) scale. +This scale can be expressed as the following group of pitches: -A, A#, B, C, C#, D, D#, E, F, F#, G, G# +> A, A♯, B, C, C♯, D, D♯, E, F, F♯, G, G♯ -A given sharp note (indicated by a #) can also be expressed as the flat -of the note above it (indicated by a b) so the chromatic scale can also be -written like this: +A given sharp note (indicated by a ♯) can also be expressed as the flat of the note above it (indicated by a ♭) so the chromatic scale can also be written like this: -A, Bb, B, C, Db, D, Eb, E, F, Gb, G, Ab +> A, B♭, B, C, D♭, D, E♭, E, F, G♭, G, A♭ -The major and minor scale and modes are subsets of this twelve-pitch -collection. They have seven pitches, and are called diatonic scales. -The collection of notes in these scales is written with either sharps or -flats, depending on the tonic. Here is a list of which are which: +The major and minor scale and modes are subsets of this twelve-pitch collection. +They have seven pitches, and are called diatonic scales. +The collection of notes in these scales is written with either sharps or flats, depending on the tonic (starting note). +Here is a table indicating whether the flat expression or sharp expression of the scale would be used for a given tonic: -No Sharps or Flats: -C major, -a minor +| Key Signature | Major | Minor | +| ------------- | --------------------- | -------------------- | +| Natural | C | a | +| Sharp | G, D, A, E, B, F♯ | e, b, f♯, c♯, g♯, d♯ | +| Flat | F, B♭, E♭, A♭, D♭, G♭ | d, g, c, f, b♭, e♭ | -Use Sharps: -G, D, A, E, B, F# major, -e, b, f#, c#, g#, d# minor +Note that by common music theory convention the natural notes "C" and "a" follow the sharps scale when ascending and the flats scale when descending. +For the scope of this exercise the scale is only ascending. -Use Flats: -F, Bb, Eb, Ab, Db, Gb major, -d, g, c, f, bb, eb minor +### Task -The diatonic scales, and all other scales that derive from the -chromatic scale, are built upon intervals. An interval is the space -between two pitches. +Given a tonic, generate the 12 note chromatic scale starting with the tonic. -The simplest interval is between two adjacent notes, and is called a -"half step", or "minor second" (sometimes written as a lower-case "m"). -The interval between two notes that have an interceding note is called -a "whole step" or "major second" (written as an upper-case "M"). The -diatonic scales are built using only these two intervals between -adjacent notes. +- Shift the base scale appropriately so that all 12 notes are returned starting with the given tonic. +- For the given tonic, determine if the scale is to be returned with flats or sharps. +- Return all notes in uppercase letters (except for the `b` for flats) irrespective of the casing of the given tonic. -Non-diatonic scales can contain other intervals. An "augmented second" -interval, written "A", has two interceding notes (e.g., from A to C or Db to E) -or a "whole step" plus a "half step". There are also smaller and larger -intervals, but they will not figure into this exercise. +## Diatonic Scales + +The diatonic scales, and all other scales that derive from the chromatic scale, are built upon intervals. +An interval is the space between two pitches. + +The simplest interval is between two adjacent notes, and is called a "half step", or "minor second" (sometimes written as a lower-case "m"). +The interval between two notes that have an interceding note is called a "whole step" or "major second" (written as an upper-case "M"). +The diatonic scales are built using only these two intervals between adjacent notes. + +Non-diatonic scales can contain other intervals. +An "augmented second" interval, written "A", has two interceding notes (e.g., from A to C or D♭ to E) or a "whole step" plus a "half step". +There are also smaller and larger intervals, but they will not figure into this exercise. + +### Task + +Given a tonic and a set of intervals, generate the musical scale starting with the tonic and following the specified interval pattern. + +This is similar to generating chromatic scales except that instead of returning 12 notes, you will return N+1 notes for N intervals. +The first note is always the given tonic. +Then, for each interval in the pattern, the next note is determined by starting from the previous note and skipping the number of notes indicated by the interval. + +For example, starting with G and using the seven intervals MMmMMMm, there would be the following eight notes: + +| Note | Reason | +| ---- | ------------------------------------------------- | +| G | Tonic | +| A | M indicates a whole step from G, skipping G♯ | +| B | M indicates a whole step from A, skipping A♯ | +| C | m indicates a half step from B, skipping nothing | +| D | M indicates a whole step from C, skipping C♯ | +| E | M indicates a whole step from D, skipping D♯ | +| F♯ | M indicates a whole step from E, skipping F | +| G | m indicates a half step from F♯, skipping nothing | diff --git a/exercises/practice/scale-generator/.eslintrc b/exercises/practice/scale-generator/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/scale-generator/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/scale-generator/.gitignore b/exercises/practice/scale-generator/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/scale-generator/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/scale-generator/.meta/config.json b/exercises/practice/scale-generator/.meta/config.json index 8a19e1341a..c953906f96 100644 --- a/exercises/practice/scale-generator/.meta/config.json +++ b/exercises/practice/scale-generator/.meta/config.json @@ -1,10 +1,26 @@ { - "blurb": "Generate musical scales, given a starting note and a set of intervals. ", - "authors": ["seeksort"], - "contributors": ["SleeplessByte"], + "authors": [ + "seeksort" + ], + "contributors": [ + "SleeplessByte" + ], "files": { - "solution": ["scale-generator.js"], - "test": ["scale-generator.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "scale-generator.js" + ], + "test": [ + "scale-generator.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] + }, + "blurb": "Generate musical scales, given a starting note and a set of intervals.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false } } diff --git a/exercises/practice/scale-generator/.meta/proof.ci.js b/exercises/practice/scale-generator/.meta/proof.ci.js index ec7fe6c052..f22f6b552b 100644 --- a/exercises/practice/scale-generator/.meta/proof.ci.js +++ b/exercises/practice/scale-generator/.meta/proof.ci.js @@ -1,76 +1,49 @@ export class Scale { constructor(tonic) { - this.INTERVAL_STEPS = ['m', 'M', 'A']; - this.SHARPS_SCALE = [ - 'A', - 'A#', - 'B', - 'C', - 'C#', - 'D', - 'D#', - 'E', - 'F', - 'F#', + this.tonic = tonic; + this.notes = { + sharp: ['A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#'], + flat: ['A', 'Bb', 'B', 'C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab'], + }; + this.sharpStart = [ 'G', - 'G#', - ]; - this.FLATS_SCALE = [ + 'D', 'A', - 'Bb', + 'E', 'B', + 'F#', + 'e', + 'b', + 'f#', + 'c#', + 'g#', + 'd#', 'C', - 'Db', - 'D', - 'Eb', - 'E', - 'F', - 'Gb', - 'G', - 'Ab', + 'a', ]; - this.USE_FLATS = [ - 'F', - 'Bb', - 'Eb', - 'Ab', - 'Db', - 'Gb', - 'd', - 'g', - 'c', - 'f', - 'bb', - 'eb', - ]; - - this.tonic = tonic.slice(0, 1).toUpperCase() + tonic.slice(1); - // note use of original tonic argument - this.chromaticScale = this.USE_FLATS.includes(tonic) - ? this.FLATS_SCALE - : this.SHARPS_SCALE; + this.accidental = + this.notes[this.sharpStart.includes(this.tonic) ? 'sharp' : 'flat']; + this.index = this.accidental.indexOf(this.toTitleCase(this.tonic)); + this.steps = { m: 1, M: 2, A: 3 }; } - chromatic() { - return this.reorderChromaticScale(); + toTitleCase(str) { + return str.charAt(0).toUpperCase() + str.substring(1); } - interval(intervals) { - const scale = this.reorderChromaticScale(); - const result = []; - let currentIndex = 0; + generateScale(intervals) { + return [this.accidental[this.index]].concat( + intervals + .split('') + .map((i) => this.accidental[(this.index += this.steps[i]) % 12]), + ); + } - for (const step of intervals) { - result.push(scale[currentIndex]); - currentIndex = currentIndex + (this.INTERVAL_STEPS.indexOf(step) + 1); - } - return result; + chromatic() { + return this.generateScale('mmmmmmmmmmm'); } - reorderChromaticScale() { - const tonicIndex = this.chromaticScale.indexOf(this.tonic); - return this.chromaticScale - .slice(tonicIndex) - .concat(this.chromaticScale.slice(0, tonicIndex)); + interval(intervals) { + return this.generateScale(intervals); } } diff --git a/exercises/practice/scale-generator/babel.config.js b/exercises/practice/scale-generator/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/scale-generator/babel.config.js +++ b/exercises/practice/scale-generator/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/scale-generator/eslint.config.mjs b/exercises/practice/scale-generator/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/scale-generator/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/scale-generator/jest.config.js b/exercises/practice/scale-generator/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/scale-generator/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/scale-generator/package.json b/exercises/practice/scale-generator/package.json index 22a775a864..e294c4e863 100644 --- a/exercises/practice/scale-generator/package.json +++ b/exercises/practice/scale-generator/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/scale-generator" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/scale-generator/scale-generator.js b/exercises/practice/scale-generator/scale-generator.js index 6f702b0165..1132bb42ee 100644 --- a/exercises/practice/scale-generator/scale-generator.js +++ b/exercises/practice/scale-generator/scale-generator.js @@ -5,14 +5,14 @@ export class Scale { constructor(tonic) { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } chromatic() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } interval(intervals) { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/scale-generator/scale-generator.spec.js b/exercises/practice/scale-generator/scale-generator.spec.js index cdf0f0cfe1..899ccd1791 100644 --- a/exercises/practice/scale-generator/scale-generator.spec.js +++ b/exercises/practice/scale-generator/scale-generator.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { Scale } from './scale-generator'; describe('ScaleGenerator', () => { @@ -37,81 +38,117 @@ describe('ScaleGenerator', () => { ]; expect(new Scale('F').chromatic()).toEqual(expected); }); + + xtest('Chromatic scale with sharps from D', () => { + const expected = [ + 'D', + 'D#', + 'E', + 'F', + 'F#', + 'G', + 'G#', + 'A', + 'A#', + 'B', + 'C', + 'C#', + ]; + expect(new Scale('D').chromatic()).toEqual(expected); + }); + + xtest('Chromatic scale with flats from D', () => { + const expected = [ + 'D', + 'Eb', + 'E', + 'F', + 'Gb', + 'G', + 'Ab', + 'A', + 'Bb', + 'B', + 'C', + 'Db', + ]; + expect(new Scale('d').chromatic()).toEqual(expected); + }); }); describe('Scales with specified intervals', () => { xtest('Simple major scale', () => { - const expected = ['C', 'D', 'E', 'F', 'G', 'A', 'B']; + const expected = ['C', 'D', 'E', 'F', 'G', 'A', 'B', 'C']; expect(new Scale('C').interval('MMmMMMm')).toEqual(expected); }); xtest('Major scale with sharps', () => { - const expected = ['G', 'A', 'B', 'C', 'D', 'E', 'F#']; + const expected = ['G', 'A', 'B', 'C', 'D', 'E', 'F#', 'G']; expect(new Scale('G').interval('MMmMMMm')).toEqual(expected); }); xtest('Major scale with flats', () => { - const expected = ['F', 'G', 'A', 'Bb', 'C', 'D', 'E']; + const expected = ['F', 'G', 'A', 'Bb', 'C', 'D', 'E', 'F']; expect(new Scale('F').interval('MMmMMMm')).toEqual(expected); }); xtest('Minor scale with sharps', () => { - const expected = ['F#', 'G#', 'A', 'B', 'C#', 'D', 'E']; + const expected = ['F#', 'G#', 'A', 'B', 'C#', 'D', 'E', 'F#']; expect(new Scale('f#').interval('MmMMmMM')).toEqual(expected); }); xtest('Minor scale with flats', () => { - const expected = ['Bb', 'C', 'Db', 'Eb', 'F', 'Gb', 'Ab']; + const expected = ['Bb', 'C', 'Db', 'Eb', 'F', 'Gb', 'Ab', 'Bb']; expect(new Scale('bb').interval('MmMMmMM')).toEqual(expected); }); xtest('Dorian mode', () => { - const expected = ['D', 'E', 'F', 'G', 'A', 'B', 'C']; + const expected = ['D', 'E', 'F', 'G', 'A', 'B', 'C', 'D']; expect(new Scale('d').interval('MmMMMmM')).toEqual(expected); }); - xtest('Mixolydian mode', () => { - const expected = ['Eb', 'F', 'G', 'Ab', 'Bb', 'C', 'Db']; - expect(new Scale('Eb').interval('MMmMMmM')).toEqual(expected); + xtest('Phrygian mode', () => { + const expected = ['E', 'F', 'G', 'A', 'B', 'C', 'D', 'E']; + expect(new Scale('e').interval('mMMMmMM')).toEqual(expected); }); xtest('Lydian mode', () => { - const expected = ['A', 'B', 'C#', 'D#', 'E', 'F#', 'G#']; + const expected = ['A', 'B', 'C#', 'D#', 'E', 'F#', 'G#', 'A']; expect(new Scale('a').interval('MMMmMMm')).toEqual(expected); }); - xtest('Phrygian mode', () => { - const expected = ['E', 'F', 'G', 'A', 'B', 'C', 'D']; - expect(new Scale('e').interval('mMMMmMM')).toEqual(expected); + xtest('Mixolydian mode', () => { + const expected = ['Eb', 'F', 'G', 'Ab', 'Bb', 'C', 'Db', 'Eb']; + expect(new Scale('Eb').interval('MMmMMmM')).toEqual(expected); }); xtest('Locrian mode', () => { - const expected = ['G', 'Ab', 'Bb', 'C', 'Db', 'Eb', 'F']; + const expected = ['G', 'Ab', 'Bb', 'C', 'Db', 'Eb', 'F', 'G']; expect(new Scale('g').interval('mMMmMMM')).toEqual(expected); }); xtest('Harmonic minor', () => { - const expected = ['D', 'E', 'F', 'G', 'A', 'Bb', 'Db']; + const expected = ['D', 'E', 'F', 'G', 'A', 'Bb', 'Db', 'D']; expect(new Scale('d').interval('MmMMmAm')).toEqual(expected); }); xtest('Octatonic', () => { - const expected = ['C', 'D', 'D#', 'F', 'F#', 'G#', 'A', 'B']; + const expected = ['C', 'D', 'D#', 'F', 'F#', 'G#', 'A', 'B', 'C']; expect(new Scale('C').interval('MmMmMmMm')).toEqual(expected); }); xtest('Hexatonic', () => { - const expected = ['Db', 'Eb', 'F', 'G', 'A', 'B']; + const expected = ['Db', 'Eb', 'F', 'G', 'A', 'B', 'Db']; expect(new Scale('Db').interval('MMMMMM')).toEqual(expected); }); xtest('Pentatonic', () => { - const expected = ['A', 'B', 'C#', 'E', 'F#']; + const expected = ['A', 'B', 'C#', 'E', 'F#', 'A']; expect(new Scale('A').interval('MMAMA')).toEqual(expected); }); xtest('Enigmatic', () => { - const expected = ['G', 'G#', 'B', 'C#', 'D#', 'F', 'F#']; + const expected = ['G', 'G#', 'B', 'C#', 'D#', 'F', 'F#', 'G']; expect(new Scale('G').interval('mAMMMmm')).toEqual(expected); }); }); diff --git a/exercises/practice/scrabble-score/.docs/instructions.md b/exercises/practice/scrabble-score/.docs/instructions.md index 3f986c947b..738f928c5b 100644 --- a/exercises/practice/scrabble-score/.docs/instructions.md +++ b/exercises/practice/scrabble-score/.docs/instructions.md @@ -1,40 +1,25 @@ # Instructions -Given a word, compute the Scrabble score for that word. +Your task is to compute a word's Scrabble score by summing the values of its letters. -## Letter Values +The letters are valued as follows: -You'll need these: +| Letter | Value | +| ---------------------------- | ----- | +| A, E, I, O, U, L, N, R, S, T | 1 | +| D, G | 2 | +| B, C, M, P | 3 | +| F, H, V, W, Y | 4 | +| K | 5 | +| J, X | 8 | +| Q, Z | 10 | -```text -Letter Value -A, E, I, O, U, L, N, R, S, T 1 -D, G 2 -B, C, M, P 3 -F, H, V, W, Y 4 -K 5 -J, X 8 -Q, Z 10 -``` - -## Examples - -"cabbage" should be scored as worth 14 points: +For example, the word "cabbage" is worth 14 points: - 3 points for C -- 1 point for A, twice -- 3 points for B, twice +- 1 point for A +- 3 points for B +- 3 points for B +- 1 point for A - 2 points for G - 1 point for E - -And to total: - -- `3 + 2*1 + 2*3 + 2 + 1` -- = `3 + 2 + 6 + 3` -- = `5 + 9` -- = 14 - -## Extensions - -- You can play a double or a triple letter. -- You can play a double or a triple word. diff --git a/exercises/practice/scrabble-score/.docs/introduction.md b/exercises/practice/scrabble-score/.docs/introduction.md new file mode 100644 index 0000000000..8821f240ba --- /dev/null +++ b/exercises/practice/scrabble-score/.docs/introduction.md @@ -0,0 +1,7 @@ +# Introduction + +[Scrabble][wikipedia] is a word game where players place letter tiles on a board to form words. +Each letter has a value. +A word's score is the sum of its letters' values. + +[wikipedia]: https://en.wikipedia.org/wiki/Scrabble diff --git a/exercises/practice/scrabble-score/.eslintrc b/exercises/practice/scrabble-score/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/scrabble-score/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/scrabble-score/.gitignore b/exercises/practice/scrabble-score/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/scrabble-score/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/scrabble-score/.meta/config.json b/exercises/practice/scrabble-score/.meta/config.json index 70ad518920..0881ba1097 100644 --- a/exercises/practice/scrabble-score/.meta/config.json +++ b/exercises/practice/scrabble-score/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Given a word, compute the Scrabble score for that word.", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "amscotti", "ankorGH", @@ -11,10 +12,23 @@ "SleeplessByte" ], "files": { - "solution": ["scrabble-score.js"], - "test": ["scrabble-score.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "scrabble-score.js" + ], + "test": [ + "scrabble-score.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Given a word, compute the Scrabble score for that word.", "source": "Inspired by the Extreme Startup game", - "source_url": "https://github.com/rchatley/extreme_startup" + "source_url": "https://github.com/rchatley/extreme_startup", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/scrabble-score/.meta/proof.ci.js b/exercises/practice/scrabble-score/.meta/proof.ci.js index e578d5304b..523771d930 100644 --- a/exercises/practice/scrabble-score/.meta/proof.ci.js +++ b/exercises/practice/scrabble-score/.meta/proof.ci.js @@ -32,5 +32,5 @@ const letterScore = (letter) => letterScores[letter] || 0; export const score = (word) => [...word.toLowerCase()].reduce( (sum, currChar) => sum + letterScore(currChar), - 0 + 0, ); diff --git a/exercises/practice/scrabble-score/babel.config.js b/exercises/practice/scrabble-score/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/scrabble-score/babel.config.js +++ b/exercises/practice/scrabble-score/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/scrabble-score/eslint.config.mjs b/exercises/practice/scrabble-score/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/scrabble-score/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/scrabble-score/jest.config.js b/exercises/practice/scrabble-score/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/scrabble-score/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/scrabble-score/package.json b/exercises/practice/scrabble-score/package.json index 741b90efb9..771e50fc80 100644 --- a/exercises/practice/scrabble-score/package.json +++ b/exercises/practice/scrabble-score/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/scrabble-score" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/scrabble-score/scrabble-score.js b/exercises/practice/scrabble-score/scrabble-score.js index c003fa5ab4..d7967d60c7 100644 --- a/exercises/practice/scrabble-score/scrabble-score.js +++ b/exercises/practice/scrabble-score/scrabble-score.js @@ -4,5 +4,5 @@ // export const score = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/scrabble-score/scrabble-score.spec.js b/exercises/practice/scrabble-score/scrabble-score.spec.js index cf14ebaded..e6d2a37724 100644 --- a/exercises/practice/scrabble-score/scrabble-score.spec.js +++ b/exercises/practice/scrabble-score/scrabble-score.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { score } from './scrabble-score'; describe('Scrabble', () => { diff --git a/exercises/practice/secret-handshake/.docs/instructions.md b/exercises/practice/secret-handshake/.docs/instructions.md index 92cef20165..d2120b9bf2 100644 --- a/exercises/practice/secret-handshake/.docs/instructions.md +++ b/exercises/practice/secret-handshake/.docs/instructions.md @@ -1,29 +1,48 @@ # Instructions -> There are 10 types of people in the world: Those who understand -> binary, and those who don't. +Your task is to convert a number between 1 and 31 to a sequence of actions in the secret handshake. -You and your fellow cohort of those in the "know" when it comes to -binary decide to come up with a secret "handshake". - -```text -1 = wink -10 = double blink -100 = close your eyes -1000 = jump +The sequence of actions is chosen by looking at the rightmost five digits of the number once it's been converted to binary. +Start at the right-most digit and move left. +The actions for each number place are: +```plaintext +00001 = wink +00010 = double blink +00100 = close your eyes +01000 = jump 10000 = Reverse the order of the operations in the secret handshake. ``` -Given a decimal number, convert it to the appropriate sequence of events for a secret handshake. +Let's use the number `9` as an example: + +- 9 in binary is `1001`. +- The digit that is farthest to the right is 1, so the first action is `wink`. +- Going left, the next digit is 0, so there is no double-blink. +- Going left again, the next digit is 0, so you leave your eyes open. +- Going left again, the next digit is 1, so you jump. + +That was the last digit, so the final code is: + +```plaintext +wink, jump +``` -Here's a couple of examples: +Given the number 26, which is `11010` in binary, we get the following actions: + +- double blink +- jump +- reverse actions + +The secret handshake for 26 is therefore: + +```plaintext +jump, double blink +``` -Given the input 3, the function would return the array -["wink", "double blink"] because 3 is 11 in binary. +~~~~exercism/note +If you aren't sure what binary is or how it works, check out [this binary tutorial][intro-to-binary]. -Given the input 19, the function would return the array -["double blink", "wink"] because 19 is 10011 in binary. -Notice that the addition of 16 (10000 in binary) -has caused the array to be reversed. +[intro-to-binary]: https://medium.com/basecs/bits-bytes-building-with-binary-13cb4289aafa +~~~~ diff --git a/exercises/practice/secret-handshake/.docs/introduction.md b/exercises/practice/secret-handshake/.docs/introduction.md new file mode 100644 index 0000000000..176b92e8cf --- /dev/null +++ b/exercises/practice/secret-handshake/.docs/introduction.md @@ -0,0 +1,7 @@ +# Introduction + +You are starting a secret coding club with some friends and friends-of-friends. +Not everyone knows each other, so you and your friends have decided to create a secret handshake that you can use to recognize that someone is a member. +You don't want anyone who isn't in the know to be able to crack the code. + +You've designed the code so that one person says a number between 1 and 31, and the other person turns it into a series of actions. diff --git a/exercises/practice/secret-handshake/.eslintrc b/exercises/practice/secret-handshake/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/secret-handshake/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/secret-handshake/.gitignore b/exercises/practice/secret-handshake/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/secret-handshake/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/secret-handshake/.meta/config.json b/exercises/practice/secret-handshake/.meta/config.json index b2542f76cf..a685bdd8a7 100644 --- a/exercises/practice/secret-handshake/.meta/config.json +++ b/exercises/practice/secret-handshake/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Given a decimal number, convert it to the appropriate sequence of events for a secret handshake.", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "ankorGH", "ovidiu141", @@ -11,10 +12,23 @@ "xarxziux" ], "files": { - "solution": ["secret-handshake.js"], - "test": ["secret-handshake.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "secret-handshake.js" + ], + "test": [ + "secret-handshake.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Given a decimal number, convert it to the appropriate sequence of events for a secret handshake.", "source": "Bert, in Mary Poppins", - "source_url": "http://www.imdb.com/title/tt0058331/quotes/qt0437047" + "source_url": "https://www.imdb.com/title/tt0058331/quotes/?item=qt0437047", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/secret-handshake/.meta/proof.ci.js b/exercises/practice/secret-handshake/.meta/proof.ci.js index 5065ebdde5..645882db2c 100644 --- a/exercises/practice/secret-handshake/.meta/proof.ci.js +++ b/exercises/practice/secret-handshake/.meta/proof.ci.js @@ -6,7 +6,7 @@ export const commands = (handshake) => { } const shakeWith = handshakeCommands.filter( - (_, i) => handshake & Math.pow(2, i) + (_, i) => handshake & Math.pow(2, i), ); if (handshake & Math.pow(2, 4)) shakeWith.reverse(); diff --git a/exercises/practice/secret-handshake/babel.config.js b/exercises/practice/secret-handshake/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/secret-handshake/babel.config.js +++ b/exercises/practice/secret-handshake/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/secret-handshake/eslint.config.mjs b/exercises/practice/secret-handshake/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/secret-handshake/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/secret-handshake/jest.config.js b/exercises/practice/secret-handshake/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/secret-handshake/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/secret-handshake/package.json b/exercises/practice/secret-handshake/package.json index 2169368a43..e69e602ebb 100644 --- a/exercises/practice/secret-handshake/package.json +++ b/exercises/practice/secret-handshake/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/secret-handshake" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/secret-handshake/secret-handshake.js b/exercises/practice/secret-handshake/secret-handshake.js index c6af522587..42513ec5f7 100644 --- a/exercises/practice/secret-handshake/secret-handshake.js +++ b/exercises/practice/secret-handshake/secret-handshake.js @@ -4,5 +4,5 @@ // export const commands = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/secret-handshake/secret-handshake.spec.js b/exercises/practice/secret-handshake/secret-handshake.spec.js index 0b461adacd..0b277b93df 100644 --- a/exercises/practice/secret-handshake/secret-handshake.spec.js +++ b/exercises/practice/secret-handshake/secret-handshake.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { commands } from './secret-handshake'; describe('Secret Handshake', () => { diff --git a/exercises/practice/series/.docs/instructions.md b/exercises/practice/series/.docs/instructions.md index de1263320e..fd97a6706a 100644 --- a/exercises/practice/series/.docs/instructions.md +++ b/exercises/practice/series/.docs/instructions.md @@ -1,7 +1,6 @@ # Instructions -Given a string of digits, output all the contiguous substrings of length `n` in -that string in the order that they appear. +Given a string of digits, output all the contiguous substrings of length `n` in that string in the order that they appear. For example, the string "49142" has the following 3-digit series: @@ -14,8 +13,7 @@ And the following 4-digit series: - "4914" - "9142" -And if you ask for a 6-digit series from a 5-digit string, you deserve -whatever you get. +And if you ask for a 6-digit series from a 5-digit string, you deserve whatever you get. -Note that these series are only required to occupy _adjacent positions_ -in the input; the digits need not be _numerically consecutive_. +Note that these series are only required to occupy _adjacent positions_ in the input; +the digits need not be _numerically consecutive_. diff --git a/exercises/practice/series/.eslintrc b/exercises/practice/series/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/series/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/series/.gitignore b/exercises/practice/series/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/series/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/series/.meta/config.json b/exercises/practice/series/.meta/config.json index 52ae90b3a2..fb399340ec 100644 --- a/exercises/practice/series/.meta/config.json +++ b/exercises/practice/series/.meta/config.json @@ -1,8 +1,10 @@ { - "blurb": "Given a string of digits, output all the contiguous substrings of length `n` in that string.", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "ankorGH", + "jagdish-15", "rchavarria", "ryanplusplus", "serixscorpio", @@ -11,10 +13,23 @@ "xarxziux" ], "files": { - "solution": ["series.js"], - "test": ["series.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "series.js" + ], + "test": [ + "series.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Given a string of digits, output all the contiguous substrings of length `n` in that string.", "source": "A subset of the Problem 8 at Project Euler", - "source_url": "http://projecteuler.net/problem=8" + "source_url": "https://projecteuler.net/problem=8", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/series/.meta/tests.toml b/exercises/practice/series/.meta/tests.toml index 52ff7ed54c..9696f51fca 100644 --- a/exercises/practice/series/.meta/tests.toml +++ b/exercises/practice/series/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [7ae7a46a-d992-4c2a-9c15-a112d125ebad] description = "slices of one from one" @@ -23,6 +30,9 @@ description = "slices of a long series" [6d235d85-46cf-4fae-9955-14b6efef27cd] description = "slice length is too large" +[d7957455-346d-4e47-8e4b-87ed1564c6d7] +description = "slice length is way too large" + [d34004ad-8765-4c09-8ba1-ada8ce776806] description = "slice length cannot be zero" diff --git a/exercises/practice/series/babel.config.js b/exercises/practice/series/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/series/babel.config.js +++ b/exercises/practice/series/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/series/eslint.config.mjs b/exercises/practice/series/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/series/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/series/jest.config.js b/exercises/practice/series/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/series/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/series/package.json b/exercises/practice/series/package.json index 5df3574c1c..b7ef4ef512 100644 --- a/exercises/practice/series/package.json +++ b/exercises/practice/series/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/series" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/series/series.js b/exercises/practice/series/series.js index aec5365a19..48fb472ea7 100644 --- a/exercises/practice/series/series.js +++ b/exercises/practice/series/series.js @@ -5,10 +5,10 @@ export class Series { constructor(series) { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } slices(sliceLength) { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/series/series.spec.js b/exercises/practice/series/series.spec.js index c80900cf58..3480e1f9fa 100644 --- a/exercises/practice/series/series.spec.js +++ b/exercises/practice/series/series.spec.js @@ -1,7 +1,8 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { Series } from './series'; describe('Series', () => { - xtest('slices of one from one', () => { + test('slices of one from one', () => { expect(new Series('1').slices(1)).toEqual([[1]]); }); @@ -49,6 +50,12 @@ describe('Series', () => { }).toThrow(new Error('slice length cannot be greater than series length')); }); + xtest('slice length is way too large', () => { + expect(() => { + new Series('12345').slices(42); + }).toThrow(new Error('slice length cannot be greater than series length')); + }); + xtest('slice length cannot be zero', () => { expect(() => { new Series('12345').slices(0); diff --git a/exercises/practice/sieve/.docs/instructions.md b/exercises/practice/sieve/.docs/instructions.md index 7228737a24..71292e1782 100644 --- a/exercises/practice/sieve/.docs/instructions.md +++ b/exercises/practice/sieve/.docs/instructions.md @@ -1,30 +1,101 @@ # Instructions -Use the Sieve of Eratosthenes to find all the primes from 2 up to a given -number. +Your task is to create a program that implements the Sieve of Eratosthenes algorithm to find all prime numbers less than or equal to a given number. -The Sieve of Eratosthenes is a simple, ancient algorithm for finding all -prime numbers up to any given limit. It does so by iteratively marking as -composite (i.e. not prime) the multiples of each prime, starting with the -multiples of 2. It does not use any division or remainder operation. +A prime number is a number larger than 1 that is only divisible by 1 and itself. +For example, 2, 3, 5, 7, 11, and 13 are prime numbers. +By contrast, 6 is _not_ a prime number as it not only divisible by 1 and itself, but also by 2 and 3. -Create your range, starting at two and continuing up to and including the given limit. (i.e. [2, limit]) +To use the Sieve of Eratosthenes, first, write out all the numbers from 2 up to and including your given number. +Then, follow these steps: -The algorithm consists of repeating the following over and over: +1. Find the next unmarked number (skipping over marked numbers). + This is a prime number. +2. Mark all the multiples of that prime number as **not** prime. -- take the next available unmarked number in your list (it is prime) -- mark all the multiples of that number (they are not prime) +Repeat the steps until you've gone through every number. +At the end, all the unmarked numbers are prime. -Repeat until you have processed each number in your range. +~~~~exercism/note +The Sieve of Eratosthenes marks off multiples of each prime using addition (repeatedly adding the prime) or multiplication (directly computing its multiples), rather than checking each number for divisibility. -When the algorithm terminates, all the numbers in the list that have not -been marked are prime. +The tests don't check that you've implemented the algorithm, only that you've come up with the correct primes. +~~~~ -The wikipedia article has a useful graphic that explains the algorithm: -https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes +## Example -Notice that this is a very specific algorithm, and the tests don't check -that you've implemented the algorithm, only that you've come up with the -correct list of primes. A good first test is to check that you do not use -division or remainder operations (div, /, mod or % depending on the -language). +Let's say you're finding the primes less than or equal to 10. + +- Write out 2, 3, 4, 5, 6, 7, 8, 9, 10, leaving them all unmarked. + + ```text + 2 3 4 5 6 7 8 9 10 + ``` + +- 2 is unmarked and is therefore a prime. + Mark 4, 6, 8 and 10 as "not prime". + + ```text + 2 3 [4] 5 [6] 7 [8] 9 [10] + ↑ + ``` + +- 3 is unmarked and is therefore a prime. + Mark 6 and 9 as not prime _(marking 6 is optional - as it's already been marked)_. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + +- 4 is marked as "not prime", so we skip over it. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + +- 5 is unmarked and is therefore a prime. + Mark 10 as not prime _(optional - as it's already been marked)_. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + +- 6 is marked as "not prime", so we skip over it. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + +- 7 is unmarked and is therefore a prime. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + +- 8 is marked as "not prime", so we skip over it. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + +- 9 is marked as "not prime", so we skip over it. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + +- 10 is marked as "not prime", so we stop as there are no more numbers to check. + + ```text + 2 3 [4] 5 [6] 7 [8] [9] [10] + ↑ + ``` + +You've examined all the numbers and found that 2, 3, 5, and 7 are still unmarked, meaning they're the primes less than or equal to 10. diff --git a/exercises/practice/sieve/.docs/introduction.md b/exercises/practice/sieve/.docs/introduction.md new file mode 100644 index 0000000000..f6c1cf79a9 --- /dev/null +++ b/exercises/practice/sieve/.docs/introduction.md @@ -0,0 +1,7 @@ +# Introduction + +You bought a big box of random computer parts at a garage sale. +You've started putting the parts together to build custom computers. + +You want to test the performance of different combinations of parts, and decide to create your own benchmarking program to see how your computers compare. +You choose the famous "Sieve of Eratosthenes" algorithm, an ancient algorithm, but one that should push your computers to the limits. diff --git a/exercises/practice/sieve/.eslintrc b/exercises/practice/sieve/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/sieve/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/sieve/.gitignore b/exercises/practice/sieve/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/sieve/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/sieve/.meta/config.json b/exercises/practice/sieve/.meta/config.json index 6c88ab2d23..1ad2320db2 100644 --- a/exercises/practice/sieve/.meta/config.json +++ b/exercises/practice/sieve/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Use the Sieve of Eratosthenes to find all the primes from 2 up to a given number.", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "ankorGH", "ntshcalleia", @@ -11,10 +12,23 @@ "smb26" ], "files": { - "solution": ["sieve.js"], - "test": ["sieve.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "sieve.js" + ], + "test": [ + "sieve.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Use the Sieve of Eratosthenes to find all the primes from 2 up to a given number.", "source": "Sieve of Eratosthenes at Wikipedia", - "source_url": "http://en.wikipedia.org/wiki/Sieve_of_Eratosthenes" + "source_url": "https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": true, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/sieve/babel.config.js b/exercises/practice/sieve/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/sieve/babel.config.js +++ b/exercises/practice/sieve/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/sieve/eslint.config.mjs b/exercises/practice/sieve/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/sieve/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/sieve/jest.config.js b/exercises/practice/sieve/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/sieve/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/sieve/package.json b/exercises/practice/sieve/package.json index cb8c030038..4602409f06 100644 --- a/exercises/practice/sieve/package.json +++ b/exercises/practice/sieve/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/sieve" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/sieve/sieve.js b/exercises/practice/sieve/sieve.js index 3223eaa0e4..29256dbb36 100644 --- a/exercises/practice/sieve/sieve.js +++ b/exercises/practice/sieve/sieve.js @@ -4,5 +4,5 @@ // export const primes = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/sieve/sieve.spec.js b/exercises/practice/sieve/sieve.spec.js index 1ea2093edf..2a8f382a3f 100644 --- a/exercises/practice/sieve/sieve.spec.js +++ b/exercises/practice/sieve/sieve.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { primes } from './sieve'; describe('Sieve', () => { diff --git a/exercises/practice/simple-cipher/.docs/instructions.md b/exercises/practice/simple-cipher/.docs/instructions.md index 22a7e4d4bd..afd0b57da9 100644 --- a/exercises/practice/simple-cipher/.docs/instructions.md +++ b/exercises/practice/simple-cipher/.docs/instructions.md @@ -1,79 +1,40 @@ # Instructions -Implement a simple shift cipher like Caesar and a more secure substitution cipher. +Create an implementation of the [Vigenère cipher][wiki]. +The Vigenère cipher is a simple substitution cipher. -## Step 1 +## Cipher terminology -"If he had anything confidential to say, he wrote it in cipher, that is, -by so changing the order of the letters of the alphabet, that not a word -could be made out. If anyone wishes to decipher these, and get at their -meaning, he must substitute the fourth letter of the alphabet, namely D, -for A, and so with the others." -—Suetonius, Life of Julius Caesar +A cipher is an algorithm used to encrypt, or encode, a string. +The unencrypted string is called the _plaintext_ and the encrypted string is called the _ciphertext_. +Converting plaintext to ciphertext is called _encoding_ while the reverse is called _decoding_. -Ciphers are very straight-forward algorithms that allow us to render -text less readable while still allowing easy deciphering. They are -vulnerable to many forms of cryptanalysis, but we are lucky that -generally our little sisters are not cryptanalysts. +In a _substitution cipher_, each plaintext letter is replaced with a ciphertext letter which is computed with the help of a _key_. +(Note, it is possible for replacement letter to be the same as the original letter.) -The Caesar Cipher was used for some messages from Julius Caesar that -were sent afield. Now Caesar knew that the cipher wasn't very good, but -he had one ally in that respect: almost nobody could read well. So even -being a couple letters off was sufficient so that people couldn't -recognize the few words that they did know. +## Encoding details -Your task is to create a simple shift cipher like the Caesar Cipher. -This image is a great example of the Caesar Cipher: +In this cipher, the key is a series of lowercase letters, such as `"abcd"`. +Each letter of the plaintext is _shifted_ or _rotated_ by a distance based on a corresponding letter in the key. +An `"a"` in the key means a shift of 0 (that is, no shift). +A `"b"` in the key means a shift of 1. +A `"c"` in the key means a shift of 2, and so on. -![Caesar Cipher][1] +The first letter of the plaintext uses the first letter of the key, the second letter of the plaintext uses the second letter of the key and so on. +If you run out of letters in the key before you run out of letters in the plaintext, start over from the start of the key again. -For example: +If the key only contains one letter, such as `"dddddd"`, then all letters of the plaintext are shifted by the same amount (three in this example), which would make this the same as a rotational cipher or shift cipher (sometimes called a Caesar cipher). +For example, the plaintext `"iamapandabear"` would become `"ldpdsdqgdehdu"`. -Giving "iamapandabear" as input to the encode function returns the cipher "ldpdsdqgdehdu". Obscure enough to keep our message secret in transit. +If the key only contains the letter `"a"` (one or more times), the shift distance is zero and the ciphertext is the same as the plaintext. -When "ldpdsdqgdehdu" is put into the decode function it would return -the original "iamapandabear" letting your friend read your original -message. +Usually the key is more complicated than that, though! +If the key is `"abcd"` then letters of the plaintext would be shifted by a distance of 0, 1, 2, and 3. +If the plaintext is `"hello"`, we need 5 shifts so the key would wrap around, giving shift distances of 0, 1, 2, 3, and 0. +Applying those shifts to the letters of `"hello"` we get `"hfnoo"`. -## Step 2 +## Random keys -Shift ciphers are no fun though when your kid sister figures it out. Try -amending the code to allow us to specify a key and use that for the -shift distance. This is called a substitution cipher. +If no key is provided, generate a key which consists of at least 100 random lowercase letters from the Latin alphabet. -Here's an example: - -Given the key "aaaaaaaaaaaaaaaaaa", encoding the string "iamapandabear" -would return the original "iamapandabear". - -Given the key "ddddddddddddddddd", encoding our string "iamapandabear" -would return the obscured "ldpdsdqgdehdu" - -In the example above, we've set a = 0 for the key value. So when the -plaintext is added to the key, we end up with the same message coming -out. So "aaaa" is not an ideal key. But if we set the key to "dddd", we -would get the same thing as the Caesar Cipher. - -## Step 3 - -The weakest link in any cipher is the human being. Let's make your -substitution cipher a little more fault tolerant by providing a source -of randomness and ensuring that the key contains only lowercase letters. - -If someone doesn't submit a key at all, generate a truly random key of -at least 100 lowercase characters in length. - -## Extensions - -Shift ciphers work by making the text slightly odd, but are vulnerable -to frequency analysis. Substitution ciphers help that, but are still -very vulnerable when the key is short or if spaces are preserved. Later -on you'll see one solution to this problem in the exercise -"crypto-square". - -If you want to go farther in this field, the questions begin to be about -how we can exchange keys in a secure way. Take a look at [Diffie-Hellman -on Wikipedia][dh] for one of the first implementations of this scheme. - -[1]: https://upload.wikimedia.org/wikipedia/commons/thumb/4/4a/Caesar_cipher_left_shift_of_3.svg/320px-Caesar_cipher_left_shift_of_3.svg.png -[dh]: http://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange +[wiki]: https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher diff --git a/exercises/practice/simple-cipher/.eslintrc b/exercises/practice/simple-cipher/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/simple-cipher/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/simple-cipher/.gitignore b/exercises/practice/simple-cipher/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/simple-cipher/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/simple-cipher/.meta/config.json b/exercises/practice/simple-cipher/.meta/config.json index cf6cf3a7df..4746dcf38c 100644 --- a/exercises/practice/simple-cipher/.meta/config.json +++ b/exercises/practice/simple-cipher/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Implement a simple shift cipher like Caesar and a more secure substitution cipher", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "ankorGH", "daveyarwood", @@ -18,10 +19,23 @@ "xarxziux" ], "files": { - "solution": ["simple-cipher.js"], - "test": ["simple-cipher.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "simple-cipher.js" + ], + "test": [ + "simple-cipher.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Implement the Vigenère cipher, a simple substitution cipher.", "source": "Substitution Cipher at Wikipedia", - "source_url": "http://en.wikipedia.org/wiki/Substitution_cipher" + "source_url": "https://en.wikipedia.org/wiki/Substitution_cipher", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/simple-cipher/babel.config.js b/exercises/practice/simple-cipher/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/simple-cipher/babel.config.js +++ b/exercises/practice/simple-cipher/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/simple-cipher/eslint.config.mjs b/exercises/practice/simple-cipher/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/simple-cipher/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/simple-cipher/jest.config.js b/exercises/practice/simple-cipher/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/simple-cipher/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/simple-cipher/package.json b/exercises/practice/simple-cipher/package.json index 815c21b46a..64ae3c951a 100644 --- a/exercises/practice/simple-cipher/package.json +++ b/exercises/practice/simple-cipher/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/simple-cipher" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/simple-cipher/simple-cipher.js b/exercises/practice/simple-cipher/simple-cipher.js index ec77b1bcb0..f9412acae2 100644 --- a/exercises/practice/simple-cipher/simple-cipher.js +++ b/exercises/practice/simple-cipher/simple-cipher.js @@ -5,18 +5,18 @@ export class Cipher { constructor() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } encode() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } decode() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get key() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/simple-cipher/simple-cipher.spec.js b/exercises/practice/simple-cipher/simple-cipher.spec.js index ed1446aa46..b9b98b0860 100644 --- a/exercises/practice/simple-cipher/simple-cipher.spec.js +++ b/exercises/practice/simple-cipher/simple-cipher.spec.js @@ -1,4 +1,4 @@ -/* eslint-disable no-new */ +import { describe, expect, test, xtest } from '@jest/globals'; import { Cipher } from './simple-cipher'; describe('Random key cipher', () => { @@ -8,11 +8,11 @@ describe('Random key cipher', () => { // Here we take advantage of the fact that plaintext of "aaa..." // outputs the key. This is a critical problem with shift ciphers, some // characters will always output the key verbatim. - expect(cipher.encode('aaaaaaaaaa')).toEqual(cipher.key.substr(0, 10)); + expect(cipher.encode('aaaaaaaaaa')).toEqual(cipher.key.substring(0, 10)); }); xtest('can decode', () => { - expect(cipher.decode(cipher.key.substr(0, 10))).toEqual('aaaaaaaaaa'); + expect(cipher.decode(cipher.key.substring(0, 10))).toEqual('aaaaaaaaaa'); }); xtest('is reversible', () => { @@ -47,7 +47,7 @@ describe('Substitution cipher', () => { xtest('can double shift encode', () => { expect(new Cipher('iamapandabear').encode('iamapandabear')).toEqual( - 'qayaeaagaciai' + 'qayaeaagaciai', ); }); diff --git a/exercises/practice/simple-linked-list/.docs/instructions.md b/exercises/practice/simple-linked-list/.docs/instructions.md index 1c9d0b3de9..04640b1fb0 100644 --- a/exercises/practice/simple-linked-list/.docs/instructions.md +++ b/exercises/practice/simple-linked-list/.docs/instructions.md @@ -1,22 +1,19 @@ # Instructions -Write a simple linked list implementation that uses Elements and a List. +Write a prototype of the music player application. -The linked list is a fundamental data structure in computer science, -often used in the implementation of other data structures. They're -pervasive in functional programming languages, such as Clojure, Erlang, -or Haskell, but far less common in imperative languages such as Ruby or -Python. +For the prototype, each song will simply be represented by a number. +Given a range of numbers (the song IDs), create a singly linked list. -The simplest kind of linked list is a singly linked list. Each element in the -list contains data and a "next" field pointing to the next element in the list -of elements. +Given a singly linked list, you should be able to reverse the list to play the songs in the opposite order. -This variant of linked lists is often used to represent sequences or -push-down stacks (also called a LIFO stack; Last In, First Out). +~~~~exercism/note +The linked list is a fundamental data structure in computer science, often used in the implementation of other data structures. -As a first take, lets create a singly linked list to contain the range (1..10), -and provide functions to reverse a linked list and convert to and from arrays. +The simplest kind of linked list is a **singly** linked list. +That means that each element (or "node") contains data, along with something that points to the next node in the list. -When implementing this in a language with built-in linked lists, -implement your own abstract data type. +If you want to dig deeper into linked lists, check out [this article][intro-linked-list] that explains it using nice drawings. + +[intro-linked-list]: https://medium.com/basecs/whats-a-linked-list-anyway-part-1-d8b7e6508b9d +~~~~ diff --git a/exercises/practice/simple-linked-list/.docs/introduction.md b/exercises/practice/simple-linked-list/.docs/introduction.md new file mode 100644 index 0000000000..0e1df72f9b --- /dev/null +++ b/exercises/practice/simple-linked-list/.docs/introduction.md @@ -0,0 +1,5 @@ +# Introduction + +You work for a music streaming company. + +You've been tasked with creating a playlist feature for your music player application. diff --git a/exercises/practice/simple-linked-list/.eslintrc b/exercises/practice/simple-linked-list/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/simple-linked-list/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/simple-linked-list/.gitignore b/exercises/practice/simple-linked-list/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/simple-linked-list/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/simple-linked-list/.meta/config.json b/exercises/practice/simple-linked-list/.meta/config.json index 2e3edd36a1..237482bb88 100644 --- a/exercises/practice/simple-linked-list/.meta/config.json +++ b/exercises/practice/simple-linked-list/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Write a simple linked list implementation that uses Elements and a List", - "authors": ["apapirovski"], + "authors": [ + "apapirovski" + ], "contributors": [ "maruthimohan", "matthewmorgan", @@ -8,10 +9,23 @@ "SleeplessByte" ], "files": { - "solution": ["simple-linked-list.js"], - "test": ["simple-linked-list.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "simple-linked-list.js" + ], + "test": [ + "simple-linked-list.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Write a simple linked list implementation that uses Elements and a List.", "source": "Inspired by 'Data Structures and Algorithms with Object-Oriented Design Patterns in Ruby', singly linked-lists.", - "source_url": "https://web.archive.org/web/20160731005714/http://brpreiss.com/books/opus8/html/page96.html" + "source_url": "https://web.archive.org/web/20160731005714/http://brpreiss.com/books/opus8/html/page96.html", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/simple-linked-list/babel.config.js b/exercises/practice/simple-linked-list/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/simple-linked-list/babel.config.js +++ b/exercises/practice/simple-linked-list/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/simple-linked-list/eslint.config.mjs b/exercises/practice/simple-linked-list/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/simple-linked-list/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/simple-linked-list/jest.config.js b/exercises/practice/simple-linked-list/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/simple-linked-list/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/simple-linked-list/package.json b/exercises/practice/simple-linked-list/package.json index c82d1c957b..cf017227e1 100644 --- a/exercises/practice/simple-linked-list/package.json +++ b/exercises/practice/simple-linked-list/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/simple-linked-list" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/simple-linked-list/simple-linked-list.js b/exercises/practice/simple-linked-list/simple-linked-list.js index e51d534bc9..1352c548d9 100644 --- a/exercises/practice/simple-linked-list/simple-linked-list.js +++ b/exercises/practice/simple-linked-list/simple-linked-list.js @@ -5,40 +5,40 @@ export class Element { constructor() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get value() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get next() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } export class List { constructor() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } add(nextValue) { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get length() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get head() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } toArray() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } reverse() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/simple-linked-list/simple-linked-list.spec.js b/exercises/practice/simple-linked-list/simple-linked-list.spec.js index c6c00cc52a..99d8c0300b 100644 --- a/exercises/practice/simple-linked-list/simple-linked-list.spec.js +++ b/exercises/practice/simple-linked-list/simple-linked-list.spec.js @@ -1,4 +1,5 @@ -import { List, Element } from './simple-linked-list'; +import { beforeEach, describe, expect, test, xtest } from '@jest/globals'; +import { Element, List } from './simple-linked-list'; describe('Element class', () => { test('has constructor', () => { @@ -109,10 +110,6 @@ describe('Lists with multiple elements', () => { const twoList = new List([1, 2]); expect(twoList.head.value).toEqual(2); }); - xtest('can convert to an array', () => { - const oneList = new List([1]); - expect(oneList.toArray()).toEqual([1]); - }); xtest('can convert longer list to an array', () => { expect(list.toArray()).toEqual([10, 9, 8, 7, 6, 5, 4, 3, 2, 1]); }); diff --git a/exercises/practice/space-age/.docs/instructions.md b/exercises/practice/space-age/.docs/instructions.md index e60be80607..f23b5e2c1f 100644 --- a/exercises/practice/space-age/.docs/instructions.md +++ b/exercises/practice/space-age/.docs/instructions.md @@ -1,18 +1,28 @@ # Instructions -Given an age in seconds, calculate how old someone would be on: +Given an age in seconds, calculate how old someone would be on a planet in our Solar System. -- Mercury: orbital period 0.2408467 Earth years -- Venus: orbital period 0.61519726 Earth years -- Earth: orbital period 1.0 Earth years, 365.25 Earth days, or 31557600 seconds -- Mars: orbital period 1.8808158 Earth years -- Jupiter: orbital period 11.862615 Earth years -- Saturn: orbital period 29.447498 Earth years -- Uranus: orbital period 84.016846 Earth years -- Neptune: orbital period 164.79132 Earth years +One Earth year equals 365.25 Earth days, or 31,557,600 seconds. +If you were told someone was 1,000,000,000 seconds old, their age would be 31.69 Earth-years. -So if you were told someone were 1,000,000,000 seconds old, you should -be able to say that they're 31.69 Earth-years old. +For the other planets, you have to account for their orbital period in Earth Years: -If you're wondering why Pluto didn't make the cut, go watch [this -youtube video](http://www.youtube.com/watch?v=Z_2gbGXzFbs). +| Planet | Orbital period in Earth Years | +| ------- | ----------------------------- | +| Mercury | 0.2408467 | +| Venus | 0.61519726 | +| Earth | 1.0 | +| Mars | 1.8808158 | +| Jupiter | 11.862615 | +| Saturn | 29.447498 | +| Uranus | 84.016846 | +| Neptune | 164.79132 | + +~~~~exercism/note +The actual length of one complete orbit of the Earth around the sun is closer to 365.256 days (1 sidereal year). +The Gregorian calendar has, on average, 365.2425 days. +While not entirely accurate, 365.25 is the value used in this exercise. +See [Year on Wikipedia][year] for more ways to measure a year. + +[year]: https://en.wikipedia.org/wiki/Year#Summary +~~~~ diff --git a/exercises/practice/space-age/.docs/introduction.md b/exercises/practice/space-age/.docs/introduction.md new file mode 100644 index 0000000000..014d78857c --- /dev/null +++ b/exercises/practice/space-age/.docs/introduction.md @@ -0,0 +1,20 @@ +# Introduction + +The year is 2525 and you've just embarked on a journey to visit all planets in the Solar System (Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus and Neptune). +The first stop is Mercury, where customs require you to fill out a form (bureaucracy is apparently _not_ Earth-specific). +As you hand over the form to the customs officer, they scrutinize it and frown. +"Do you _really_ expect me to believe you're just 50 years old? +You must be closer to 200 years old!" + +Amused, you wait for the customs officer to start laughing, but they appear to be dead serious. +You realize that you've entered your age in _Earth years_, but the officer expected it in _Mercury years_! +As Mercury's orbital period around the sun is significantly shorter than Earth, you're actually a lot older in Mercury years. +After some quick calculations, you're able to provide your age in Mercury Years. +The customs officer smiles, satisfied, and waves you through. +You make a mental note to pre-calculate your planet-specific age _before_ future customs checks, to avoid such mix-ups. + +~~~~exercism/note +If you're wondering why Pluto didn't make the cut, go watch [this YouTube video][pluto-video]. + +[pluto-video]: https://www.youtube.com/watch?v=Z_2gbGXzFbs +~~~~ diff --git a/exercises/practice/space-age/.eslintrc b/exercises/practice/space-age/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/space-age/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/space-age/.gitignore b/exercises/practice/space-age/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/space-age/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/space-age/.meta/config.json b/exercises/practice/space-age/.meta/config.json index ea2e4f4580..d7d1b2ae57 100644 --- a/exercises/practice/space-age/.meta/config.json +++ b/exercises/practice/space-age/.meta/config.json @@ -1,10 +1,12 @@ { - "blurb": "Given an age in seconds, calculate how old someone is in terms of a given planet's solar years.", - "authors": ["rchavarria"], + "authors": [ + "rchavarria" + ], "contributors": [ "ankorGH", "cmccandless", "draalger", + "jagdish-15", "JesseSingleton", "jscheffner", "kytrinyx", @@ -16,10 +18,23 @@ "xarxziux" ], "files": { - "solution": ["space-age.js"], - "test": ["space-age.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "space-age.js" + ], + "test": [ + "space-age.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Given an age in seconds, calculate how old someone is in terms of a given planet's solar years.", "source": "Partially inspired by Chapter 1 in Chris Pine's online Learn to Program tutorial.", - "source_url": "http://pine.fm/LearnToProgram/?Chapter=01" + "source_url": "https://pine.fm/LearnToProgram/?Chapter=01", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/space-age/.meta/proof.ci.js b/exercises/practice/space-age/.meta/proof.ci.js index 46183fdd6e..c3ca024049 100644 --- a/exercises/practice/space-age/.meta/proof.ci.js +++ b/exercises/practice/space-age/.meta/proof.ci.js @@ -10,6 +10,10 @@ const EARTH_TO_OTHER_PLANETS = { }; export const age = (planet, seconds) => { + if (!EARTH_TO_OTHER_PLANETS[planet]) { + throw new Error('not a planet'); + } + const earthYears = seconds / 31557600; const years = earthYears / EARTH_TO_OTHER_PLANETS[planet]; diff --git a/exercises/practice/space-age/.meta/tests.toml b/exercises/practice/space-age/.meta/tests.toml index b4a221dc9c..7957bb7799 100644 --- a/exercises/practice/space-age/.meta/tests.toml +++ b/exercises/practice/space-age/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [84f609af-5a91-4d68-90a3-9e32d8a5cd34] description = "age on Earth" @@ -25,3 +32,6 @@ description = "age on Uranus" [80096d30-a0d4-4449-903e-a381178355d8] description = "age on Neptune" + +[57b96e2a-1178-40b7-b34d-f3c9c34e4bf4] +description = "invalid planet causes error" diff --git a/exercises/practice/space-age/babel.config.js b/exercises/practice/space-age/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/space-age/babel.config.js +++ b/exercises/practice/space-age/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/space-age/eslint.config.mjs b/exercises/practice/space-age/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/space-age/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/space-age/jest.config.js b/exercises/practice/space-age/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/space-age/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/space-age/package.json b/exercises/practice/space-age/package.json index 039ea1fb53..095bafb8d0 100644 --- a/exercises/practice/space-age/package.json +++ b/exercises/practice/space-age/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/space-age" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/space-age/space-age.js b/exercises/practice/space-age/space-age.js index 4b53e05f4c..aa827ed56f 100644 --- a/exercises/practice/space-age/space-age.js +++ b/exercises/practice/space-age/space-age.js @@ -4,5 +4,5 @@ // export const age = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/space-age/space-age.spec.js b/exercises/practice/space-age/space-age.spec.js index 374939c3b1..84c4e4449f 100644 --- a/exercises/practice/space-age/space-age.spec.js +++ b/exercises/practice/space-age/space-age.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { age } from './space-age'; describe('Space Age', () => { @@ -32,4 +33,8 @@ describe('Space Age', () => { xtest('age on Neptune', () => { expect(age('neptune', 1821023456)).toEqual(0.35); }); + + xtest('invalid planet causes error', () => { + expect(() => age('Sun', 680804807)).toThrow(new Error('not a planet')); + }); }); diff --git a/exercises/practice/spiral-matrix/.docs/instructions.md b/exercises/practice/spiral-matrix/.docs/instructions.md index 0e7674ff11..01e8a77f80 100644 --- a/exercises/practice/spiral-matrix/.docs/instructions.md +++ b/exercises/practice/spiral-matrix/.docs/instructions.md @@ -1,10 +1,8 @@ # Instructions -Given the size, return a square matrix of numbers in spiral order. +Your task is to return a square matrix of a given size. -The matrix should be filled with natural numbers, starting from 1 -in the top-left corner, increasing in an inward, clockwise spiral order, -like these examples: +The matrix should be filled with natural numbers, starting from 1 in the top-left corner, increasing in an inward, clockwise spiral order, like these examples: ## Examples diff --git a/exercises/practice/spiral-matrix/.docs/introduction.md b/exercises/practice/spiral-matrix/.docs/introduction.md new file mode 100644 index 0000000000..25c7eb595a --- /dev/null +++ b/exercises/practice/spiral-matrix/.docs/introduction.md @@ -0,0 +1,11 @@ +# Introduction + +In a small village near an ancient forest, there was a legend of a hidden treasure buried deep within the woods. +Despite numerous attempts, no one had ever succeeded in finding it. +This was about to change, however, thanks to a young explorer named Elara. +She had discovered an old document containing instructions on how to locate the treasure. +Using these instructions, Elara was able to draw a map that revealed the path to the treasure. + +To her surprise, the path followed a peculiar clockwise spiral. +It was no wonder no one had been able to find the treasure before! +With the map in hand, Elara embarks on her journey to uncover the hidden treasure. diff --git a/exercises/practice/spiral-matrix/.eslintrc b/exercises/practice/spiral-matrix/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/spiral-matrix/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/spiral-matrix/.gitignore b/exercises/practice/spiral-matrix/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/spiral-matrix/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/spiral-matrix/.meta/config.json b/exercises/practice/spiral-matrix/.meta/config.json index bc25f2ccff..fe6f76a1f6 100644 --- a/exercises/practice/spiral-matrix/.meta/config.json +++ b/exercises/practice/spiral-matrix/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": " Given the size, return a square matrix of numbers in spiral order.", - "authors": ["MattH-be"], + "authors": [ + "MattH-be" + ], "contributors": [ "ankorGH", "rchavarria", @@ -10,10 +11,23 @@ "ProProgrammer2504" ], "files": { - "solution": ["spiral-matrix.js"], - "test": ["spiral-matrix.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "spiral-matrix.js" + ], + "test": [ + "spiral-matrix.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Given the size, return a square matrix of numbers in spiral order.", "source": "Reddit r/dailyprogrammer challenge #320 [Easy] Spiral Ascension.", - "source_url": "https://www.reddit.com/r/dailyprogrammer/comments/6i60lr/20170619_challenge_320_easy_spiral_ascension/" + "source_url": "https://web.archive.org/web/20230607064729/https://old.reddit.com/r/dailyprogrammer/comments/6i60lr/20170619_challenge_320_easy_spiral_ascension/", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/spiral-matrix/babel.config.js b/exercises/practice/spiral-matrix/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/spiral-matrix/babel.config.js +++ b/exercises/practice/spiral-matrix/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/spiral-matrix/eslint.config.mjs b/exercises/practice/spiral-matrix/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/spiral-matrix/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/spiral-matrix/jest.config.js b/exercises/practice/spiral-matrix/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/spiral-matrix/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/spiral-matrix/package.json b/exercises/practice/spiral-matrix/package.json index b0ba99ba34..0d4b37c868 100644 --- a/exercises/practice/spiral-matrix/package.json +++ b/exercises/practice/spiral-matrix/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/spiral-matrix" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/spiral-matrix/spiral-matrix.js b/exercises/practice/spiral-matrix/spiral-matrix.js index 97a1528e3f..a7bc2e59b7 100644 --- a/exercises/practice/spiral-matrix/spiral-matrix.js +++ b/exercises/practice/spiral-matrix/spiral-matrix.js @@ -4,5 +4,5 @@ // export const spiralMatrix = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/spiral-matrix/spiral-matrix.spec.js b/exercises/practice/spiral-matrix/spiral-matrix.spec.js index f059169d39..8486f70b68 100644 --- a/exercises/practice/spiral-matrix/spiral-matrix.spec.js +++ b/exercises/practice/spiral-matrix/spiral-matrix.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { spiralMatrix } from './spiral-matrix'; describe('Spiral Matrix', () => { diff --git a/exercises/practice/square-root/.docs/instructions.md b/exercises/practice/square-root/.docs/instructions.md index 19b61863e7..d258b86876 100644 --- a/exercises/practice/square-root/.docs/instructions.md +++ b/exercises/practice/square-root/.docs/instructions.md @@ -1,9 +1,18 @@ # Instructions -Given a natural radicand, return its square root. +Your task is to calculate the square root of a given number. -Note that the term "radicand" refers to the number for which the root is to be determined. That is, it is the number under the root symbol. +- Try to avoid using the pre-existing math libraries of your language. +- As input you'll be given a positive whole number, i.e. 1, 2, 3, 4… +- You are only required to handle cases where the result is a positive whole number. -Check out the Wikipedia pages on [square root](https://en.wikipedia.org/wiki/Square_root) and [methods of computing square roots](https://en.wikipedia.org/wiki/Methods_of_computing_square_roots). +Some potential approaches: -Recall also that natural numbers are positive real whole numbers (i.e. 1, 2, 3 and up). +- Linear or binary search for a number that gives the input number when squared. +- Successive approximation using Newton's or Heron's method. +- Calculating one digit at a time or one bit at a time. + +You can check out the Wikipedia pages on [integer square root][integer-square-root] and [methods of computing square roots][computing-square-roots] to help with choosing a method of calculation. + +[integer-square-root]: https://en.wikipedia.org/wiki/Integer_square_root +[computing-square-roots]: https://en.wikipedia.org/wiki/Methods_of_computing_square_roots diff --git a/exercises/practice/square-root/.docs/introduction.md b/exercises/practice/square-root/.docs/introduction.md new file mode 100644 index 0000000000..1d692934f2 --- /dev/null +++ b/exercises/practice/square-root/.docs/introduction.md @@ -0,0 +1,10 @@ +# Introduction + +We are launching a deep space exploration rocket and we need a way to make sure the navigation system stays on target. + +As the first step in our calculation, we take a target number and find its square root (that is, the number that when multiplied by itself equals the target number). + +The journey will be very long. +To make the batteries last as long as possible, we had to make our rocket's onboard computer very power efficient. +Unfortunately that means that we can't rely on fancy math libraries and functions, as they use more power. +Instead we want to implement our own square root calculation. diff --git a/exercises/practice/square-root/.eslintrc b/exercises/practice/square-root/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/square-root/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/square-root/.gitignore b/exercises/practice/square-root/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/square-root/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/square-root/.meta/config.json b/exercises/practice/square-root/.meta/config.json index 36fe9b425d..1dce776044 100644 --- a/exercises/practice/square-root/.meta/config.json +++ b/exercises/practice/square-root/.meta/config.json @@ -1,12 +1,29 @@ { - "blurb": "Given a natural radicand, return its square root.", - "authors": ["evelynstender"], - "contributors": ["SleeplessByte", "zimmah"], + "authors": [ + "evelynstender" + ], + "contributors": [ + "SleeplessByte", + "zimmah" + ], "files": { - "solution": ["square-root.js"], - "test": ["square-root.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "square-root.js" + ], + "test": [ + "square-root.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Given a natural radicand, return its square root.", "source": "wolf99", - "source_url": "https://github.com/exercism/problem-specifications/pull/1582" + "source_url": "https://github.com/exercism/problem-specifications/pull/1582", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/square-root/babel.config.js b/exercises/practice/square-root/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/square-root/babel.config.js +++ b/exercises/practice/square-root/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/square-root/eslint.config.mjs b/exercises/practice/square-root/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/square-root/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/square-root/jest.config.js b/exercises/practice/square-root/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/square-root/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/square-root/package.json b/exercises/practice/square-root/package.json index 65a75058f8..7dda8cac46 100644 --- a/exercises/practice/square-root/package.json +++ b/exercises/practice/square-root/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/square-root" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/square-root/square-root.js b/exercises/practice/square-root/square-root.js index 922a59298b..c8345c21f7 100644 --- a/exercises/practice/square-root/square-root.js +++ b/exercises/practice/square-root/square-root.js @@ -4,5 +4,5 @@ // export const squareRoot = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/square-root/square-root.spec.js b/exercises/practice/square-root/square-root.spec.js index 1d2263b0fb..fc59e70947 100644 --- a/exercises/practice/square-root/square-root.spec.js +++ b/exercises/practice/square-root/square-root.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { squareRoot } from './square-root'; describe('Square root', () => { diff --git a/exercises/practice/state-of-tic-tac-toe/.docs/instructions.md b/exercises/practice/state-of-tic-tac-toe/.docs/instructions.md new file mode 100644 index 0000000000..1a03ebb6cb --- /dev/null +++ b/exercises/practice/state-of-tic-tac-toe/.docs/instructions.md @@ -0,0 +1,101 @@ +# Instructions + +In this exercise, you're going to implement a program that determines the state of a [tic-tac-toe][] game. +(_You may also know the game as "noughts and crosses" or "Xs and Os"._) + +The game is played on a 3×3 grid. +Players take turns to place `X`s and `O`s on the grid. +The game ends when one player has won by placing three of marks in a row, column, or along a diagonal of the grid, or when the entire grid is filled up. + +In this exercise, we will assume that `X` starts. + +It's your job to determine which state a given game is in. + +There are 3 potential game states: + +- The game is **ongoing**. +- The game ended in a **draw**. +- The game ended in a **win**. + +If the given board is invalid, throw an appropriate error. + +If a board meets the following conditions, it is invalid: + +- The given board cannot be reached when turns are taken in the correct order (remember that `X` starts). +- The game was played after it already ended. + +## Examples + +### Ongoing game + +```text + | | + X | | +___|___|___ + | | + | X | O +___|___|___ + | | + O | X | + | | +``` + +### Draw + +```text + | | + X | O | X +___|___|___ + | | + X | X | O +___|___|___ + | | + O | X | O + | | +``` + +### Win + +```text + | | + X | X | X +___|___|___ + | | + | O | O +___|___|___ + | | + | | + | | +``` + +### Invalid + +#### Wrong turn order + +```text + | | + O | O | X +___|___|___ + | | + | | +___|___|___ + | | + | | + | | +``` + +#### Continued playing after win + +```text + | | + X | X | X +___|___|___ + | | + O | O | O +___|___|___ + | | + | | + | | +``` + +[tic-tac-toe]: https://en.wikipedia.org/wiki/Tic-tac-toe diff --git a/exercises/practice/state-of-tic-tac-toe/.gitignore b/exercises/practice/state-of-tic-tac-toe/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/state-of-tic-tac-toe/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/state-of-tic-tac-toe/.meta/config.json b/exercises/practice/state-of-tic-tac-toe/.meta/config.json new file mode 100644 index 0000000000..525b904770 --- /dev/null +++ b/exercises/practice/state-of-tic-tac-toe/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "Cool-Katt" + ], + "files": { + "solution": [ + "state-of-tic-tac-toe.js" + ], + "test": [ + "state-of-tic-tac-toe.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] + }, + "blurb": "Determine the game state of a match of Tic-Tac-Toe.", + "source": "Created by Sascha Mann for the Julia track of the Exercism Research Experiment.", + "source_url": "https://github.com/exercism/research_experiment_1/tree/julia-dev/exercises/julia-1-a" +} diff --git a/exercises/practice/state-of-tic-tac-toe/.meta/proof.ci.js b/exercises/practice/state-of-tic-tac-toe/.meta/proof.ci.js new file mode 100644 index 0000000000..ac6750df6d --- /dev/null +++ b/exercises/practice/state-of-tic-tac-toe/.meta/proof.ci.js @@ -0,0 +1,41 @@ +export const gamestate = (board) => { + let gridSize = board.length; + let numberOfX = board.join('').match(/X/g)?.length ?? 0; + let numberOfO = board.join('').match(/O/g)?.length ?? 0; + let scorringArray = Array(gridSize * 2 + 2).fill(null); + let boardAsNumbers = board + .map((row) => + row + .split('') + .map((element) => + element.replaceAll('X', 1).replaceAll('O', -1).replaceAll(/\s/g, 0), + ), + ) + .flat(); + + boardAsNumbers.forEach((element, index) => { + let [row, col] = [Math.floor(index / gridSize), index % gridSize]; + scorringArray[row] += Number(element); + scorringArray[gridSize + col] += Number(element); + row === col && (scorringArray[2 * gridSize] += Number(element)); + gridSize - 1 - col === row && + (scorringArray[2 * gridSize + 1] += Number(element)); + }); + + switch (true) { + case numberOfX - numberOfO > 1: + throw new Error('Wrong turn order: X went twice'); + case numberOfX - numberOfO < 0: + throw new Error('Wrong turn order: O started'); + case scorringArray.includes(gridSize) && scorringArray.includes(-gridSize): + throw new Error( + 'Impossible board: game should have ended after the game was won', + ); + case scorringArray.includes(gridSize) || scorringArray.includes(-gridSize): + return 'win'; + case boardAsNumbers.includes('0'): + return 'ongoing'; + default: + return 'draw'; + } +}; diff --git a/exercises/practice/state-of-tic-tac-toe/.meta/tests.toml b/exercises/practice/state-of-tic-tac-toe/.meta/tests.toml new file mode 100644 index 0000000000..8fc25e2118 --- /dev/null +++ b/exercises/practice/state-of-tic-tac-toe/.meta/tests.toml @@ -0,0 +1,101 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[fe8e9fa9-37af-4d7e-aa24-2f4b8517161a] +description = "Won games -> Finished game where X won via left column victory" + +[96c30df5-ae23-4cf6-bf09-5ef056dddea1] +description = "Won games -> Finished game where X won via middle column victory" + +[0d7a4b0a-2afd-4a75-8389-5fb88ab05eda] +description = "Won games -> Finished game where X won via right column victory" + +[bd1007c0-ec5d-4c60-bb9f-1a4f22177d51] +description = "Won games -> Finished game where O won via left column victory" + +[c032f800-5735-4354-b1b9-46f14d4ee955] +description = "Won games -> Finished game where O won via middle column victory" + +[662c8902-c94a-4c4c-9d9c-e8ca513db2b4] +description = "Won games -> Finished game where O won via right column victory" + +[2d62121f-7e3a-44a0-9032-0d73e3494941] +description = "Won games -> Finished game where X won via top row victory" + +[108a5e82-cc61-409f-aece-d7a18c1beceb] +description = "Won games -> Finished game where X won via middle row victory" +include = false + +[346527db-4db9-4a96-b262-d7023dc022b0] +description = "Won games -> Finished game where X won via middle row victory" +reimplements = "108a5e82-cc61-409f-aece-d7a18c1beceb" + +[a013c583-75f8-4ab2-8d68-57688ff04574] +description = "Won games -> Finished game where X won via bottom row victory" + +[2c08e7d7-7d00-487f-9442-e7398c8f1727] +description = "Won games -> Finished game where O won via top row victory" + +[bb1d6c62-3e3f-4d1a-9766-f8803c8ed70f] +description = "Won games -> Finished game where O won via middle row victory" + +[6ef641e9-12ec-44f5-a21c-660ea93907af] +description = "Won games -> Finished game where O won via bottom row victory" + +[ab145b7b-26a7-426c-ab71-bf418cd07f81] +description = "Won games -> Finished game where X won via falling diagonal victory" + +[7450caab-08f5-4f03-a74b-99b98c4b7a4b] +description = "Won games -> Finished game where X won via rising diagonal victory" + +[c2a652ee-2f93-48aa-a710-a70cd2edce61] +description = "Won games -> Finished game where O won via falling diagonal victory" + +[5b20ceea-494d-4f0c-a986-b99efc163bcf] +description = "Won games -> Finished game where O won via rising diagonal victory" + +[035a49b9-dc35-47d3-9d7c-de197161b9d4] +description = "Won games -> Finished game where X won via a row and a column victory" + +[e5dfdeb0-d2bf-4b5a-b307-e673f69d4a53] +description = "Won games -> Finished game where X won via two diagonal victories" + +[b42ed767-194c-4364-b36e-efbfb3de8788] +description = "Drawn games -> Draw" + +[227a76b2-0fef-4e16-a4bd-8f9d7e4c3b13] +description = "Drawn games -> Another draw" + +[4d93f15c-0c40-43d6-b966-418b040012a9] +description = "Ongoing games -> Ongoing game: one move in" + +[c407ae32-4c44-4989-b124-2890cf531f19] +description = "Ongoing games -> Ongoing game: two moves in" + +[199b7a8d-e2b6-4526-a85e-78b416e7a8a9] +description = "Ongoing games -> Ongoing game: five moves in" + +[1670145b-1e3d-4269-a7eb-53cd327b302e] +description = "Invalid boards -> Invalid board: X went twice" + +[47c048e8-b404-4bcf-9e51-8acbb3253f3b] +description = "Invalid boards -> Invalid board: O started" + +[b1dc8b13-46c4-47db-a96d-aa90eedc4e8d] +description = "Invalid boards -> Invalid board" +include = false + +[6c1920f2-ab5c-4648-a0c9-997414dda5eb] +description = "Invalid boards -> Invalid board: X won and O kept playing" +reimplements = "b1dc8b13-46c4-47db-a96d-aa90eedc4e8d" + +[4801cda2-f5b7-4c36-8317-3cdd167ac22c] +description = "Invalid boards -> Invalid board: players kept playing after a win" diff --git a/exercises/practice/state-of-tic-tac-toe/.npmrc b/exercises/practice/state-of-tic-tac-toe/.npmrc new file mode 100644 index 0000000000..d26df800bb --- /dev/null +++ b/exercises/practice/state-of-tic-tac-toe/.npmrc @@ -0,0 +1 @@ +audit=false diff --git a/exercises/practice/state-of-tic-tac-toe/LICENSE b/exercises/practice/state-of-tic-tac-toe/LICENSE new file mode 100644 index 0000000000..90e73be03b --- /dev/null +++ b/exercises/practice/state-of-tic-tac-toe/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Exercism + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/exercises/practice/state-of-tic-tac-toe/babel.config.js b/exercises/practice/state-of-tic-tac-toe/babel.config.js new file mode 100644 index 0000000000..a638497df1 --- /dev/null +++ b/exercises/practice/state-of-tic-tac-toe/babel.config.js @@ -0,0 +1,4 @@ +module.exports = { + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], +}; diff --git a/exercises/practice/state-of-tic-tac-toe/eslint.config.mjs b/exercises/practice/state-of-tic-tac-toe/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/state-of-tic-tac-toe/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/state-of-tic-tac-toe/jest.config.js b/exercises/practice/state-of-tic-tac-toe/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/state-of-tic-tac-toe/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/state-of-tic-tac-toe/package.json b/exercises/practice/state-of-tic-tac-toe/package.json new file mode 100644 index 0000000000..2f016e94b4 --- /dev/null +++ b/exercises/practice/state-of-tic-tac-toe/package.json @@ -0,0 +1,39 @@ +{ + "name": "@exercism/javascript-state-of-tic-tac-toe", + "description": "Exercism practice exercise on state-of-tic-tac-toe", + "author": "Katrina Owen", + "contributors": [ + "Derk-Jan Karrenbeld (https://derk-jan.com)", + "Tejas Bubane (https://tejasbubane.github.io/)", + "Cool-Katt (https://github.com/Cool-Katt)" + ], + "private": true, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/exercism/javascript", + "directory": "exercises/practice/state-of-tic-tac-toe" + }, + "devDependencies": { + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" + }, + "dependencies": {}, + "scripts": { + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" +} diff --git a/exercises/practice/state-of-tic-tac-toe/state-of-tic-tac-toe.js b/exercises/practice/state-of-tic-tac-toe/state-of-tic-tac-toe.js new file mode 100644 index 0000000000..12f035fd7e --- /dev/null +++ b/exercises/practice/state-of-tic-tac-toe/state-of-tic-tac-toe.js @@ -0,0 +1,8 @@ +// +// This is only a SKELETON file for the 'State of Tic Tac Toe' exercise. It's been provided as a +// convenience to get you started writing code faster. +// + +export const gamestate = (board) => { + throw new Error('Remove this line and implement the function'); +}; diff --git a/exercises/practice/state-of-tic-tac-toe/state-of-tic-tac-toe.spec.js b/exercises/practice/state-of-tic-tac-toe/state-of-tic-tac-toe.spec.js new file mode 100644 index 0000000000..d6cdda3750 --- /dev/null +++ b/exercises/practice/state-of-tic-tac-toe/state-of-tic-tac-toe.spec.js @@ -0,0 +1,203 @@ +import { describe, expect, test, xtest } from '@jest/globals'; +import { gamestate } from './state-of-tic-tac-toe'; + +describe('Won games', () => { + test('Finished game where X won via left column victory', () => { + const board = ['XOO', 'X ', 'X ']; + const expected = 'win'; + const actual = gamestate(board); + expect(actual).toEqual(expected); + }); + + xtest('Finished game where X won via middle column victory', () => { + const board = ['OXO', ' X ', ' X ']; + const expected = 'win'; + const actual = gamestate(board); + expect(actual).toEqual(expected); + }); + + xtest('Finished game where X won via right column victory', () => { + const board = ['OOX', ' X', ' X']; + const expected = 'win'; + const actual = gamestate(board); + expect(actual).toEqual(expected); + }); + + xtest('Finished game where O won via left column victory', () => { + const board = ['OXX', 'OX ', 'O ']; + const expected = 'win'; + const actual = gamestate(board); + expect(actual).toEqual(expected); + }); + + xtest('Finished game where O won via middle column victory', () => { + const board = ['XOX', ' OX', ' O ']; + const expected = 'win'; + const actual = gamestate(board); + expect(actual).toEqual(expected); + }); + + xtest('Finished game where O won via right column victory', () => { + const board = ['XXO', ' XO', ' O']; + const expected = 'win'; + const actual = gamestate(board); + expect(actual).toEqual(expected); + }); + + xtest('Finished game where X won via top row victory', () => { + const board = ['XXX', 'XOO', 'O ']; + const expected = 'win'; + const actual = gamestate(board); + expect(actual).toEqual(expected); + }); + + xtest('Finished game where X won via middle row victory', () => { + const board = ['O ', 'XXX', ' O ']; + const expected = 'win'; + const actual = gamestate(board); + expect(actual).toEqual(expected); + }); + + xtest('Finished game where X won via bottom row victory', () => { + const board = [' OO', 'O X', 'XXX']; + const expected = 'win'; + const actual = gamestate(board); + expect(actual).toEqual(expected); + }); + + xtest('Finished game where O won via top row victory', () => { + const board = ['OOO', 'XXO', 'XX ']; + const expected = 'win'; + const actual = gamestate(board); + expect(actual).toEqual(expected); + }); + + xtest('Finished game where O won via middle row victory', () => { + const board = ['XX ', 'OOO', 'X ']; + const expected = 'win'; + const actual = gamestate(board); + expect(actual).toEqual(expected); + }); + + xtest('Finished game where O won via bottom row victory', () => { + const board = ['XOX', ' XX', 'OOO']; + const expected = 'win'; + const actual = gamestate(board); + expect(actual).toEqual(expected); + }); + + xtest('Finished game where X won via falling diagonal victory', () => { + const board = ['XOO', ' X ', ' X']; + const expected = 'win'; + const actual = gamestate(board); + expect(actual).toEqual(expected); + }); + + xtest('Finished game where X won via rising diagonal victory', () => { + const board = ['O X', 'OX ', 'X ']; + const expected = 'win'; + const actual = gamestate(board); + expect(actual).toEqual(expected); + }); + + xtest('Finished game where O won via falling diagonal victory', () => { + const board = ['OXX', 'OOX', 'X O']; + const expected = 'win'; + const actual = gamestate(board); + expect(actual).toEqual(expected); + }); + + xtest('Finished game where O won via rising diagonal victory', () => { + const board = [' O', ' OX', 'OXX']; + const expected = 'win'; + const actual = gamestate(board); + expect(actual).toEqual(expected); + }); + + xtest('Finished game where X won via a row and a column victory', () => { + const board = ['XXX', 'XOO', 'XOO']; + const expected = 'win'; + const actual = gamestate(board); + expect(actual).toEqual(expected); + }); + + xtest('Finished game where X won via two diagonal victories', () => { + const board = ['XOX', 'OXO', 'XOX']; + const expected = 'win'; + const actual = gamestate(board); + expect(actual).toEqual(expected); + }); +}); + +describe('Draw games', () => { + xtest('Draw', () => { + const board = ['XOX', 'XXO', 'OXO']; + const expected = 'draw'; + const actual = gamestate(board); + expect(actual).toEqual(expected); + }); + + xtest('Another draw', () => { + const board = ['XXO', 'OXX', 'XOO']; + const expected = 'draw'; + const actual = gamestate(board); + expect(actual).toEqual(expected); + }); +}); + +describe('Ongoing games', () => { + xtest('Ongoing game: one move in', () => { + const board = [' ', 'X ', ' ']; + const expected = 'ongoing'; + const actual = gamestate(board); + expect(actual).toEqual(expected); + }); + + xtest('Ongoing game: two moves in', () => { + const board = ['O ', ' X ', ' ']; + const expected = 'ongoing'; + const actual = gamestate(board); + expect(actual).toEqual(expected); + }); + + xtest('Ongoing game: five moves in', () => { + const board = ['X ', ' XO', 'OX ']; + const expected = 'ongoing'; + const actual = gamestate(board); + expect(actual).toEqual(expected); + }); +}); + +describe('Invalid boards', () => { + xtest('Invalid board: X went twice', () => { + const board = ['XX ', ' ', ' ']; + const expected = new Error('Wrong turn order: X went twice'); + const actual = () => gamestate(board); + expect(actual).toThrow(expected); + }); + + xtest('Invalid board: O started', () => { + const board = ['OOX', ' ', ' ']; + const expected = new Error('Wrong turn order: O started'); + const actual = () => gamestate(board); + expect(actual).toThrow(expected); + }); + + xtest('Invalid board: X won and O kept playing', () => { + const board = ['XXX', 'OOO', ' ']; + const expected = new Error( + 'Impossible board: game should have ended after the game was won', + ); + const actual = () => gamestate(board); + expect(actual).toThrow(expected); + }); + + xtest('Invalid board: players kept playing after a win', () => { + const board = ['XXX', 'OOO', 'XOX']; + const expected = new Error( + 'Impossible board: game should have ended after the game was won', + ); + const actual = () => gamestate(board); + expect(actual).toThrow(expected); + }); +}); diff --git a/exercises/practice/strain/.docs/instructions.md b/exercises/practice/strain/.docs/instructions.md index 320be1a502..3469ae6579 100644 --- a/exercises/practice/strain/.docs/instructions.md +++ b/exercises/practice/strain/.docs/instructions.md @@ -1,9 +1,7 @@ # Instructions -Implement the `keep` and `discard` operation on collections. Given a collection -and a predicate on the collection's elements, `keep` returns a new collection -containing those elements where the predicate is true, while `discard` returns -a new collection containing those elements where the predicate is false. +Implement the `keep` and `discard` operation on collections. +Given a collection and a predicate on the collection's elements, `keep` returns a new collection containing those elements where the predicate is true, while `discard` returns a new collection containing those elements where the predicate is false. For example, given the collection of numbers: @@ -23,12 +21,9 @@ While your discard operation should produce: Note that the union of keep and discard is all the elements. -The functions may be called `keep` and `discard`, or they may need different -names in order to not clash with existing functions or concepts in your -language. +The functions may be called `keep` and `discard`, or they may need different names in order to not clash with existing functions or concepts in your language. ## Restrictions -Keep your hands off that filter/reject/whatchamacallit functionality -provided by your standard library! Solve this one yourself using other -basic tools instead. +Keep your hands off that filter/reject/whatchamacallit functionality provided by your standard library! +Solve this one yourself using other basic tools instead. diff --git a/exercises/practice/strain/.eslintrc b/exercises/practice/strain/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/strain/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/strain/.gitignore b/exercises/practice/strain/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/strain/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/strain/.meta/config.json b/exercises/practice/strain/.meta/config.json index 1f3c2d1a64..fe47034587 100644 --- a/exercises/practice/strain/.meta/config.json +++ b/exercises/practice/strain/.meta/config.json @@ -1,8 +1,10 @@ { - "blurb": "Implement the `keep` and `discard` operation on collections. Given a collection and a predicate on the collection's elements, `keep` returns a new collection containing those elements where the predicate is true, while `discard` returns a new collection containing those elements where the predicate is false.", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "ankorGH", + "jagdish-15", "rchavarria", "ryanplusplus", "SleeplessByte", @@ -10,10 +12,23 @@ "xarxziux" ], "files": { - "solution": ["strain.js"], - "test": ["strain.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "strain.js" + ], + "test": [ + "strain.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Implement the `keep` and `discard` operation on collections.", "source": "Conversation with James Edward Gray II", - "source_url": "https://twitter.com/jeg2" + "source_url": "http://graysoftinc.com/", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/strain/.meta/tests.toml b/exercises/practice/strain/.meta/tests.toml new file mode 100644 index 0000000000..3a617b4a92 --- /dev/null +++ b/exercises/practice/strain/.meta/tests.toml @@ -0,0 +1,52 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[26af8c32-ba6a-4eb3-aa0a-ebd8f136e003] +description = "keep on empty list returns empty list" + +[f535cb4d-e99b-472a-bd52-9fa0ffccf454] +description = "keeps everything" + +[950b8e8e-f628-42a8-85e2-9b30f09cde38] +description = "keeps nothing" + +[92694259-6e76-470c-af87-156bdf75018a] +description = "keeps first and last" + +[938f7867-bfc7-449e-a21b-7b00cbb56994] +description = "keeps neither first nor last" + +[8908e351-4437-4d2b-a0f7-770811e48816] +description = "keeps strings" + +[2728036b-102a-4f1e-a3ef-eac6160d876a] +description = "keeps lists" + +[ef16beb9-8d84-451a-996a-14e80607fce6] +description = "discard on empty list returns empty list" + +[2f42f9bc-8e06-4afe-a222-051b5d8cd12a] +description = "discards everything" + +[ca990fdd-08c2-4f95-aa50-e0f5e1d6802b] +description = "discards nothing" + +[71595dae-d283-48ca-a52b-45fa96819d2f] +description = "discards first and last" + +[ae141f79-f86d-4567-b407-919eaca0f3dd] +description = "discards neither first nor last" + +[daf25b36-a59f-4f29-bcfe-302eb4e43609] +description = "discards strings" + +[a38d03f9-95ad-4459-80d1-48e937e4acaf] +description = "discards lists" diff --git a/exercises/practice/strain/babel.config.js b/exercises/practice/strain/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/strain/babel.config.js +++ b/exercises/practice/strain/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/strain/eslint.config.mjs b/exercises/practice/strain/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/strain/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/strain/jest.config.js b/exercises/practice/strain/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/strain/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/strain/package.json b/exercises/practice/strain/package.json index b6b409b71a..6a9e1ff742 100644 --- a/exercises/practice/strain/package.json +++ b/exercises/practice/strain/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/strain" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/strain/strain.js b/exercises/practice/strain/strain.js index 427b6e7a5a..873d6628b1 100644 --- a/exercises/practice/strain/strain.js +++ b/exercises/practice/strain/strain.js @@ -4,9 +4,9 @@ // export const keep = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; export const discard = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/strain/strain.spec.js b/exercises/practice/strain/strain.spec.js index 621db90344..919bd7d169 100644 --- a/exercises/practice/strain/strain.spec.js +++ b/exercises/practice/strain/strain.spec.js @@ -1,14 +1,19 @@ -import { keep, discard } from './strain'; +import { describe, expect, test, xtest } from '@jest/globals'; +import { discard, keep } from './strain'; describe('strain', () => { test('keeps on empty array returns empty array', () => { expect(keep([], (e) => e < 10)).toEqual([]); }); - xtest('keeps everything ', () => { + xtest('keeps everything', () => { expect(keep([1, 2, 3], (e) => e < 10)).toEqual([1, 2, 3]); }); + xtest('keeps nothing', () => { + expect(keep([1, 2, 3], (e) => e > 10)).toEqual([]); + }); + xtest('keeps first and last', () => { expect(keep([1, 2, 3], (e) => e % 2 === 1)).toEqual([1, 3]); }); @@ -23,7 +28,7 @@ describe('strain', () => { expect(result).toEqual('zebra zombies zelot'.split(' ')); }); - xtest('keeps arrays', () => { + xtest('keeps lists', () => { const rows = [ [1, 2, 3], [5, 5, 5], @@ -42,10 +47,14 @@ describe('strain', () => { ]); }); - xtest('empty discard', () => { + xtest('discards everything', () => { expect(discard([], (e) => e < 10)).toEqual([]); }); + xtest('discards everything', () => { + expect(discard([1, 3, 5], (e) => e < 10)).toEqual([]); + }); + xtest('discards nothing', () => { expect(discard([1, 2, 3], (e) => e > 10)).toEqual([1, 2, 3]); }); @@ -65,7 +74,7 @@ describe('strain', () => { expect(result).toEqual('apple banana cherimoya'.split(' ')); }); - xtest('discards arrays', () => { + xtest('discards lists', () => { const rows = [ [1, 2, 3], [5, 5, 5], diff --git a/exercises/practice/sublist/.docs/instructions.md b/exercises/practice/sublist/.docs/instructions.md index a0c9a1a9b1..8228edc6ce 100644 --- a/exercises/practice/sublist/.docs/instructions.md +++ b/exercises/practice/sublist/.docs/instructions.md @@ -1,18 +1,25 @@ # Instructions -Given two lists determine if the first list is contained within the second -list, if the second list is contained within the first list, if both lists are -contained within each other or if none of these are true. +Given any two lists `A` and `B`, determine if: -Specifically, a list A is a sublist of list B if by dropping 0 or more elements -from the front of B and 0 or more elements from the back of B you get a list -that's completely equal to A. +- List `A` is equal to list `B`; or +- List `A` contains list `B` (`A` is a superlist of `B`); or +- List `A` is contained by list `B` (`A` is a sublist of `B`); or +- None of the above is true, thus lists `A` and `B` are unequal + +Specifically, list `A` is equal to list `B` if both lists have the same values in the same order. +List `A` is a superlist of `B` if `A` contains a contiguous sub-sequence of values equal to `B`. +List `A` is a sublist of `B` if `B` contains a contiguous sub-sequence of values equal to `A`. Examples: -- A = [1, 2, 3], B = [1, 2, 3, 4, 5], A is a sublist of B -- A = [3, 4, 5], B = [1, 2, 3, 4, 5], A is a sublist of B -- A = [3, 4], B = [1, 2, 3, 4, 5], A is a sublist of B -- A = [1, 2, 3], B = [1, 2, 3], A is equal to B -- A = [1, 2, 3, 4, 5], B = [2, 3, 4], A is a superlist of B -- A = [1, 2, 4], B = [1, 2, 3, 4, 5], A is not a superlist of, sublist of or equal to B +- If `A = []` and `B = []` (both lists are empty), then `A` and `B` are equal +- If `A = [1, 2, 3]` and `B = []`, then `A` is a superlist of `B` +- If `A = []` and `B = [1, 2, 3]`, then `A` is a sublist of `B` +- If `A = [1, 2, 3]` and `B = [1, 2, 3, 4, 5]`, then `A` is a sublist of `B` +- If `A = [3, 4, 5]` and `B = [1, 2, 3, 4, 5]`, then `A` is a sublist of `B` +- If `A = [3, 4]` and `B = [1, 2, 3, 4, 5]`, then `A` is a sublist of `B` +- If `A = [1, 2, 3]` and `B = [1, 2, 3]`, then `A` and `B` are equal +- If `A = [1, 2, 3, 4, 5]` and `B = [2, 3, 4]`, then `A` is a superlist of `B` +- If `A = [1, 2, 4]` and `B = [1, 2, 3, 4, 5]`, then `A` and `B` are unequal +- If `A = [1, 2, 3]` and `B = [1, 3, 2]`, then `A` and `B` are unequal diff --git a/exercises/practice/sublist/.eslintrc b/exercises/practice/sublist/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/sublist/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/sublist/.gitignore b/exercises/practice/sublist/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/sublist/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/sublist/.meta/config.json b/exercises/practice/sublist/.meta/config.json index da9cd43814..502e8e0c84 100644 --- a/exercises/practice/sublist/.meta/config.json +++ b/exercises/practice/sublist/.meta/config.json @@ -1,16 +1,32 @@ { - "blurb": "Write a function to determine if a list is a sublist of another list.", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "ankorGH", + "atk", + "jagdish-15", "rchavarria", "SleeplessByte", "tejasbubane", "xarxziux" ], "files": { - "solution": ["sublist.js"], - "test": ["sublist.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "sublist.js" + ], + "test": [ + "sublist.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] + }, + "blurb": "Write a function to determine if a list is a sublist of another list.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false } } diff --git a/exercises/practice/sublist/.meta/proof.ci.js b/exercises/practice/sublist/.meta/proof.ci.js index b6ceb8fe8d..51602fc813 100644 --- a/exercises/practice/sublist/.meta/proof.ci.js +++ b/exercises/practice/sublist/.meta/proof.ci.js @@ -1,21 +1,19 @@ export class List { - constructor(list = []) { - this.list = list; + items = []; + + constructor(...items) { + this.items = items; } compare(other) { - return { - '-1': isSublist(other.list, this.list) ? 'SUBLIST' : 'UNEQUAL', - 0: isSublist(other.list, this.list) ? 'EQUAL' : 'UNEQUAL', - 1: isSublist(this.list, other.list) ? 'SUPERLIST' : 'UNEQUAL', - }[lengthDiff(this, other)]; + const sublist = + this.items.length === 0 || + `,${other.items.join(',')},`.includes(`,${this.items.join(',')},`); + const superlist = + other.items.length === 0 || + `,${this.items.join(',')},`.includes(`,${other.items.join(',')},`); + return ['UNEQUAL', 'SUPERLIST', 'SUBLIST', 'EQUAL'][ + Number(superlist) + (Number(sublist) << 1) + ]; } } - -function lengthDiff(one, two) { - return String(Math.sign(one.list.length - two.list.length)); -} - -function isSublist(one, two) { - return one.join().match(two.join()); -} diff --git a/exercises/practice/sublist/.meta/tests.toml b/exercises/practice/sublist/.meta/tests.toml index 74bd60cce7..de5020a9dd 100644 --- a/exercises/practice/sublist/.meta/tests.toml +++ b/exercises/practice/sublist/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [97319c93-ebc5-47ab-a022-02a1980e1d29] description = "empty lists" @@ -47,6 +54,9 @@ description = "first list missing element from second list" [83ffe6d8-a445-4a3c-8795-1e51a95e65c3] description = "second list missing element from first list" +[7bc76cb8-5003-49ca-bc47-cdfbe6c2bb89] +description = "first list missing additional digits from second list" + [0d7ee7c1-0347-45c8-9ef5-b88db152b30b] description = "order matters to a list" diff --git a/exercises/practice/sublist/babel.config.js b/exercises/practice/sublist/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/sublist/babel.config.js +++ b/exercises/practice/sublist/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/sublist/eslint.config.mjs b/exercises/practice/sublist/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/sublist/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/sublist/jest.config.js b/exercises/practice/sublist/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/sublist/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/sublist/package.json b/exercises/practice/sublist/package.json index e37bc72c5a..160d07ed6d 100644 --- a/exercises/practice/sublist/package.json +++ b/exercises/practice/sublist/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/sublist" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/sublist/sublist.js b/exercises/practice/sublist/sublist.js index 6375e7178c..8014e5093b 100644 --- a/exercises/practice/sublist/sublist.js +++ b/exercises/practice/sublist/sublist.js @@ -5,10 +5,10 @@ export class List { constructor() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } compare() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/sublist/sublist.spec.js b/exercises/practice/sublist/sublist.spec.js index 55b91dc02e..e290fc1de9 100644 --- a/exercises/practice/sublist/sublist.spec.js +++ b/exercises/practice/sublist/sublist.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { List } from './sublist'; describe('sublist', () => { @@ -106,6 +107,13 @@ describe('sublist', () => { expect(listOne.compare(listTwo)).toEqual('UNEQUAL'); }); + xtest('first list missing additional digits from second list', () => { + const listOne = new List([1, 2]); + const listTwo = new List([1, 22]); + + expect(listOne.compare(listTwo)).toEqual('UNEQUAL'); + }); + xtest('order matters to a list', () => { const listOne = new List([1, 2, 3]); const listTwo = new List([3, 2, 1]); diff --git a/exercises/practice/sum-of-multiples/.docs/instructions.md b/exercises/practice/sum-of-multiples/.docs/instructions.md index bb512396aa..d69f890e9d 100644 --- a/exercises/practice/sum-of-multiples/.docs/instructions.md +++ b/exercises/practice/sum-of-multiples/.docs/instructions.md @@ -1,9 +1,27 @@ # Instructions -Given a number, find the sum of all the unique multiples of particular numbers up to -but not including that number. +Your task is to write the code that calculates the energy points that get awarded to players when they complete a level. -If we list all the natural numbers below 20 that are multiples of 3 or 5, -we get 3, 5, 6, 9, 10, 12, 15, and 18. +The points awarded depend on two things: -The sum of these multiples is 78. +- The level (a number) that the player completed. +- The base value of each magical item collected by the player during that level. + +The energy points are awarded according to the following rules: + +1. For each magical item, take the base value and find all the multiples of that value that are less than the level number. +2. Combine the sets of numbers. +3. Remove any duplicates. +4. Calculate the sum of all the numbers that are left. + +Let's look at an example: + +**The player completed level 20 and found two magical items with base values of 3 and 5.** + +To calculate the energy points earned by the player, we need to find all the unique multiples of these base values that are less than level 20. + +- Multiples of 3 less than 20: `{3, 6, 9, 12, 15, 18}` +- Multiples of 5 less than 20: `{5, 10, 15}` +- Combine the sets and remove duplicates: `{3, 5, 6, 9, 10, 12, 15, 18}` +- Sum the unique multiples: `3 + 5 + 6 + 9 + 10 + 12 + 15 + 18 = 78` +- Therefore, the player earns **78** energy points for completing level 20 and finding the two magical items with base values of 3 and 5. diff --git a/exercises/practice/sum-of-multiples/.docs/introduction.md b/exercises/practice/sum-of-multiples/.docs/introduction.md new file mode 100644 index 0000000000..69cabeed5a --- /dev/null +++ b/exercises/practice/sum-of-multiples/.docs/introduction.md @@ -0,0 +1,6 @@ +# Introduction + +You work for a company that makes an online, fantasy-survival game. + +When a player finishes a level, they are awarded energy points. +The amount of energy awarded depends on which magical items the player found while exploring that level. diff --git a/exercises/practice/sum-of-multiples/.eslintrc b/exercises/practice/sum-of-multiples/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/sum-of-multiples/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/sum-of-multiples/.gitignore b/exercises/practice/sum-of-multiples/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/sum-of-multiples/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/sum-of-multiples/.meta/config.json b/exercises/practice/sum-of-multiples/.meta/config.json index f80de782ab..1ff3d760ab 100644 --- a/exercises/practice/sum-of-multiples/.meta/config.json +++ b/exercises/practice/sum-of-multiples/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Given a number, find the sum of all the multiples of particular numbers up to but not including that number.", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "ankorGH", "hayashi-ay", @@ -12,10 +13,23 @@ "xarxziux" ], "files": { - "solution": ["sum-of-multiples.js"], - "test": ["sum-of-multiples.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "sum-of-multiples.js" + ], + "test": [ + "sum-of-multiples.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Given a number, find the sum of all the multiples of particular numbers up to but not including that number.", "source": "A variation on Problem 1 at Project Euler", - "source_url": "http://projecteuler.net/problem=1" + "source_url": "https://projecteuler.net/problem=1", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/sum-of-multiples/babel.config.js b/exercises/practice/sum-of-multiples/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/sum-of-multiples/babel.config.js +++ b/exercises/practice/sum-of-multiples/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/sum-of-multiples/eslint.config.mjs b/exercises/practice/sum-of-multiples/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/sum-of-multiples/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/sum-of-multiples/jest.config.js b/exercises/practice/sum-of-multiples/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/sum-of-multiples/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/sum-of-multiples/package.json b/exercises/practice/sum-of-multiples/package.json index a5b2063724..6f34a63a08 100644 --- a/exercises/practice/sum-of-multiples/package.json +++ b/exercises/practice/sum-of-multiples/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/sum-of-multiples" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/sum-of-multiples/sum-of-multiples.js b/exercises/practice/sum-of-multiples/sum-of-multiples.js index 601ee9f600..6dbadfc361 100644 --- a/exercises/practice/sum-of-multiples/sum-of-multiples.js +++ b/exercises/practice/sum-of-multiples/sum-of-multiples.js @@ -4,5 +4,5 @@ // export const sum = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/sum-of-multiples/sum-of-multiples.spec.js b/exercises/practice/sum-of-multiples/sum-of-multiples.spec.js index bd38507eee..d824231a7a 100644 --- a/exercises/practice/sum-of-multiples/sum-of-multiples.spec.js +++ b/exercises/practice/sum-of-multiples/sum-of-multiples.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { sum } from './sum-of-multiples'; describe('Sum Of Multiples', () => { diff --git a/exercises/practice/tournament/.docs/instructions.md b/exercises/practice/tournament/.docs/instructions.md index 26e93ec775..e5ca237385 100644 --- a/exercises/practice/tournament/.docs/instructions.md +++ b/exercises/practice/tournament/.docs/instructions.md @@ -20,9 +20,12 @@ What do those abbreviations mean? - L: Matches Lost - P: Points -A win earns a team 3 points. A draw earns 1. A loss earns 0. +A win earns a team 3 points. +A draw earns 1. +A loss earns 0. -The outcome should be ordered by points, descending. In case of a tie, teams are ordered alphabetically. +The outcome is ordered by points, descending. +In case of a tie, teams are ordered alphabetically. ## Input @@ -37,7 +40,8 @@ Blithering Badgers;Devastating Donkeys;loss Allegoric Alaskans;Courageous Californians;win ``` -The result of the match refers to the first team listed. So this line: +The result of the match refers to the first team listed. +So this line: ```text Allegoric Alaskans;Blithering Badgers;win diff --git a/exercises/practice/tournament/.eslintrc b/exercises/practice/tournament/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/tournament/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/tournament/.gitignore b/exercises/practice/tournament/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/tournament/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/tournament/.meta/config.json b/exercises/practice/tournament/.meta/config.json index 56bdc49187..1b0ac6190c 100644 --- a/exercises/practice/tournament/.meta/config.json +++ b/exercises/practice/tournament/.meta/config.json @@ -1,10 +1,23 @@ { - "blurb": "Tally the results of a small football competition.", - "authors": ["lpizzinidev"], - "contributors": [], + "authors": [ + "lpizzinidev" + ], "files": { - "solution": ["tournament.js"], - "test": ["tournament.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "tournament.js" + ], + "test": [ + "tournament.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] + }, + "blurb": "Tally the results of a small football competition.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false } } diff --git a/exercises/practice/tournament/babel.config.js b/exercises/practice/tournament/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/tournament/babel.config.js +++ b/exercises/practice/tournament/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/tournament/eslint.config.mjs b/exercises/practice/tournament/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/tournament/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/tournament/jest.config.js b/exercises/practice/tournament/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/tournament/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/tournament/package.json b/exercises/practice/tournament/package.json index 2eda02e68b..6aeee15837 100644 --- a/exercises/practice/tournament/package.json +++ b/exercises/practice/tournament/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/tournament" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/tournament/tournament.js b/exercises/practice/tournament/tournament.js index e559cf1bb0..2e29cf6518 100644 --- a/exercises/practice/tournament/tournament.js +++ b/exercises/practice/tournament/tournament.js @@ -4,5 +4,5 @@ // export const tournamentTally = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/tournament/tournament.spec.js b/exercises/practice/tournament/tournament.spec.js index ac5a12edd8..9cb53d5f64 100644 --- a/exercises/practice/tournament/tournament.spec.js +++ b/exercises/practice/tournament/tournament.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { tournamentTally } from './tournament'; describe('Tournament', () => { @@ -6,7 +7,7 @@ describe('Tournament', () => { const expected = 'Team | MP | W | D | L | P'; expect(tally).toEqual(expected); }); - test('a win is three points, a loss is zero points', () => { + xtest('a win is three points, a loss is zero points', () => { const tally = tournamentTally('Allegoric Alaskans;Blithering Badgers;win'); const expected = 'Team | MP | W | D | L | P\n' + @@ -14,7 +15,7 @@ describe('Tournament', () => { 'Blithering Badgers | 1 | 0 | 0 | 1 | 0'; expect(tally).toEqual(expected); }); - test('a win can also be expressed as a loss', () => { + xtest('a win can also be expressed as a loss', () => { const tally = tournamentTally('Blithering Badgers;Allegoric Alaskans;loss'); const expected = 'Team | MP | W | D | L | P\n' + @@ -22,7 +23,7 @@ describe('Tournament', () => { 'Blithering Badgers | 1 | 0 | 0 | 1 | 0'; expect(tally).toEqual(expected); }); - test('a different team can win', () => { + xtest('a different team can win', () => { const tally = tournamentTally('Blithering Badgers;Allegoric Alaskans;win'); const expected = 'Team | MP | W | D | L | P\n' + @@ -30,7 +31,7 @@ describe('Tournament', () => { 'Allegoric Alaskans | 1 | 0 | 0 | 1 | 0'; expect(tally).toEqual(expected); }); - test('a draw is one point each', () => { + xtest('a draw is one point each', () => { const tally = tournamentTally('Allegoric Alaskans;Blithering Badgers;draw'); const expected = 'Team | MP | W | D | L | P\n' + @@ -38,7 +39,7 @@ describe('Tournament', () => { 'Blithering Badgers | 1 | 0 | 1 | 0 | 1'; expect(tally).toEqual(expected); }); - test('there can be more than one match', () => { + xtest('there can be more than one match', () => { const input = 'Allegoric Alaskans;Blithering Badgers;win\n' + 'Allegoric Alaskans;Blithering Badgers;win'; @@ -49,7 +50,7 @@ describe('Tournament', () => { 'Blithering Badgers | 2 | 0 | 0 | 2 | 0'; expect(tally).toEqual(expected); }); - test('there can be more than one winner', () => { + xtest('there can be more than one winner', () => { const input = 'Allegoric Alaskans;Blithering Badgers;loss\n' + 'Allegoric Alaskans;Blithering Badgers;win'; @@ -60,7 +61,7 @@ describe('Tournament', () => { 'Blithering Badgers | 2 | 1 | 0 | 1 | 3'; expect(tally).toEqual(expected); }); - test('there can be more than two teams', () => { + xtest('there can be more than two teams', () => { const input = 'Allegoric Alaskans;Blithering Badgers;win\n' + 'Blithering Badgers;Courageous Californians;win\n' + @@ -73,7 +74,7 @@ describe('Tournament', () => { 'Courageous Californians | 2 | 0 | 0 | 2 | 0'; expect(tally).toEqual(expected); }); - test('typical input', () => { + xtest('typical input', () => { const input = 'Allegoric Alaskans;Blithering Badgers;win\n' + 'Devastating Donkeys;Courageous Californians;draw\n' + @@ -90,7 +91,7 @@ describe('Tournament', () => { 'Courageous Californians | 3 | 0 | 1 | 2 | 1'; expect(tally).toEqual(expected); }); - test('incomplete competition (not all pairs have played)', () => { + xtest('incomplete competition (not all pairs have played)', () => { const input = 'Allegoric Alaskans;Blithering Badgers;loss\n' + 'Devastating Donkeys;Allegoric Alaskans;loss\n' + @@ -105,7 +106,7 @@ describe('Tournament', () => { 'Devastating Donkeys | 1 | 0 | 0 | 1 | 0'; expect(tally).toEqual(expected); }); - test('ties broken alphabetically', () => { + xtest('ties broken alphabetically', () => { const input = 'Courageous Californians;Devastating Donkeys;win\n' + 'Allegoric Alaskans;Blithering Badgers;win\n' + @@ -122,7 +123,7 @@ describe('Tournament', () => { 'Devastating Donkeys | 3 | 0 | 1 | 2 | 1'; expect(tally).toEqual(expected); }); - test('ensure points sorted numerically', () => { + xtest('ensure points sorted numerically', () => { const input = 'Devastating Donkeys;Blithering Badgers;win\n' + 'Devastating Donkeys;Blithering Badgers;win\n' + diff --git a/exercises/practice/transpose/.docs/instructions.md b/exercises/practice/transpose/.docs/instructions.md index c0e1d14a54..6033af745f 100644 --- a/exercises/practice/transpose/.docs/instructions.md +++ b/exercises/practice/transpose/.docs/instructions.md @@ -17,7 +17,8 @@ BE CF ``` -Rows become columns and columns become rows. See . +Rows become columns and columns become rows. +See [transpose][]. If the input has rows of different lengths, this is to be solved as follows: @@ -55,5 +56,6 @@ BE ``` In general, all characters from the input should also be present in the transposed output. -That means that if a column in the input text contains only spaces on its bottom-most row(s), -the corresponding output row should contain the spaces in its right-most column(s). +That means that if a column in the input text contains only spaces on its bottom-most row(s), the corresponding output row should contain the spaces in its right-most column(s). + +[transpose]: https://en.wikipedia.org/wiki/Transpose diff --git a/exercises/practice/transpose/.eslintrc b/exercises/practice/transpose/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/transpose/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/transpose/.gitignore b/exercises/practice/transpose/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/transpose/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/transpose/.meta/config.json b/exercises/practice/transpose/.meta/config.json index 619a66d1e6..294e4457f7 100644 --- a/exercises/practice/transpose/.meta/config.json +++ b/exercises/practice/transpose/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Take input text and output it transposed.", - "authors": ["RobinCsl"], + "authors": [ + "RobinCsl" + ], "contributors": [ "ankorGH", "matthewmorgan", @@ -10,10 +11,23 @@ "xarxziux" ], "files": { - "solution": ["transpose.js"], - "test": ["transpose.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "transpose.js" + ], + "test": [ + "transpose.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Take input text and output it transposed.", "source": "Reddit r/dailyprogrammer challenge #270 [Easy].", - "source_url": "https://www.reddit.com/r/dailyprogrammer/comments/4msu2x/challenge_270_easy_transpose_the_input_text" + "source_url": "https://web.archive.org/web/20230630051421/https://old.reddit.com/r/dailyprogrammer/comments/4msu2x/challenge_270_easy_transpose_the_input_text/", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/transpose/.meta/proof.ci.js b/exercises/practice/transpose/.meta/proof.ci.js index 00b84f7ed3..c8bd606ea6 100644 --- a/exercises/practice/transpose/.meta/proof.ci.js +++ b/exercises/practice/transpose/.meta/proof.ci.js @@ -10,6 +10,6 @@ export function transpose(input) { return [...Array(maxCol).keys()].map((col) => trimTrailingUndefined(input.map((_v, row) => input[row][col])) .map((charOrUndefined) => charOrUndefined || ' ') - .join('') + .join(''), ); } diff --git a/exercises/practice/transpose/.meta/tests.toml b/exercises/practice/transpose/.meta/tests.toml index 7faa8aea3e..23ac5eecd7 100644 --- a/exercises/practice/transpose/.meta/tests.toml +++ b/exercises/practice/transpose/.meta/tests.toml @@ -34,3 +34,6 @@ description = "rectangle" [b80badc9-057e-4543-bd07-ce1296a1ea2c] description = "triangle" + +[76acfd50-5596-4d05-89f1-5116328a7dd9] +description = "jagged triangle" diff --git a/exercises/practice/transpose/babel.config.js b/exercises/practice/transpose/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/transpose/babel.config.js +++ b/exercises/practice/transpose/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/transpose/eslint.config.mjs b/exercises/practice/transpose/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/transpose/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/transpose/jest.config.js b/exercises/practice/transpose/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/transpose/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/transpose/package.json b/exercises/practice/transpose/package.json index 9cc1e90981..c597d5d367 100644 --- a/exercises/practice/transpose/package.json +++ b/exercises/practice/transpose/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/transpose" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/transpose/transpose.js b/exercises/practice/transpose/transpose.js index f29c78ece4..2ab7d9db71 100644 --- a/exercises/practice/transpose/transpose.js +++ b/exercises/practice/transpose/transpose.js @@ -4,5 +4,5 @@ // export const transpose = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/transpose/transpose.spec.js b/exercises/practice/transpose/transpose.spec.js index 8613c04b5a..ff7e8779ee 100644 --- a/exercises/practice/transpose/transpose.spec.js +++ b/exercises/practice/transpose/transpose.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { transpose } from './transpose'; describe('Transpose', () => { @@ -88,6 +89,35 @@ describe('Transpose', () => { expect(transpose(input)).toEqual(expected); }); + xtest('mixed line length', () => { + const input = [ + 'The longest line.', + 'A long line.', + 'A longer line.', + 'A line.', + ]; + const expected = [ + 'TAAA', + 'h ', + 'elll', + ' ooi', + 'lnnn', + 'ogge', + 'n e.', + 'glr', + 'ei ', + 'snl', + 'tei', + ' .n', + 'l e', + 'i .', + 'n', + 'e', + '.', + ]; + expect(transpose(input)).toEqual(expected); + }); + xtest('square', () => { const input = ['HEART', 'EMBER', 'ABUSE', 'RESIN', 'TREND']; const expected = ['HEART', 'EMBER', 'ABUSE', 'RESIN', 'TREND']; @@ -122,6 +152,19 @@ describe('Transpose', () => { expect(transpose(input)).toEqual(expected); }); + xtest('jagged triangle', () => { + const input = ['11', '2', '3333', '444', '555555', '66666']; + const expected = [ + '123456', + '1 3456', + ' 3456', + ' 3 56', + ' 56', + ' 5', + ]; + expect(transpose(input)).toEqual(expected); + }); + xtest('many lines', () => { const input = [ 'Chor. Two households, both alike in dignity,', diff --git a/exercises/practice/triangle/.docs/instructions.md b/exercises/practice/triangle/.docs/instructions.md index 0a9c68e3b0..755cb8d19d 100644 --- a/exercises/practice/triangle/.docs/instructions.md +++ b/exercises/practice/triangle/.docs/instructions.md @@ -4,20 +4,31 @@ Determine if a triangle is equilateral, isosceles, or scalene. An _equilateral_ triangle has all three sides the same length. -An _isosceles_ triangle has at least two sides the same length. (It is sometimes -specified as having exactly two sides the same length, but for the purposes of -this exercise we'll say at least two.) +An _isosceles_ triangle has at least two sides the same length. +(It is sometimes specified as having exactly two sides the same length, but for the purposes of this exercise we'll say at least two.) A _scalene_ triangle has all sides of different lengths. ## Note -For a shape to be a triangle at all, all sides have to be of length > 0, and -the sum of the lengths of any two sides must be greater than or equal to the -length of the third side. See [Triangle Inequality](https://en.wikipedia.org/wiki/Triangle_inequality). +For a shape to be a triangle at all, all sides have to be of length > 0, and the sum of the lengths of any two sides must be greater than or equal to the length of the third side. -## Dig Deeper +~~~~exercism/note +We opted to not include tests for degenerate triangles (triangles that violate these rules) to keep things simpler. +You may handle those situations if you wish to do so, or safely ignore them. +~~~~ -The case where the sum of the lengths of two sides _equals_ that of the -third is known as a _degenerate_ triangle - it has zero area and looks like -a single line. Feel free to add your own code/tests to check for degenerate triangles. +In equations: + +Let `a`, `b`, and `c` be sides of the triangle. +Then all three of the following expressions must be true: + +```text +a + b ≥ c +b + c ≥ a +a + c ≥ b +``` + +See [Triangle Inequality][triangle-inequality] + +[triangle-inequality]: https://en.wikipedia.org/wiki/Triangle_inequality diff --git a/exercises/practice/triangle/.eslintrc b/exercises/practice/triangle/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/triangle/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/triangle/.gitignore b/exercises/practice/triangle/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/triangle/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/triangle/.meta/config.json b/exercises/practice/triangle/.meta/config.json index 49b6326365..c344aa6eb8 100644 --- a/exercises/practice/triangle/.meta/config.json +++ b/exercises/practice/triangle/.meta/config.json @@ -1,8 +1,10 @@ { - "blurb": "Determine if a triangle is equilateral, isosceles, or scalene.", - "authors": ["rchavarria"], + "authors": [ + "rchavarria" + ], "contributors": [ "ankorGH", + "jagdish-15", "matthewmorgan", "msomji", "ovidiu141", @@ -12,10 +14,23 @@ "WebCu" ], "files": { - "solution": ["triangle.js"], - "test": ["triangle.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "triangle.js" + ], + "test": [ + "triangle.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Determine if a triangle is equilateral, isosceles, or scalene.", "source": "The Ruby Koans triangle project, parts 1 & 2", - "source_url": "http://rubykoans.com" + "source_url": "https://web.archive.org/web/20220831105330/http://rubykoans.com", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/triangle/.meta/tests.toml b/exercises/practice/triangle/.meta/tests.toml index 59107059cc..7db091648d 100644 --- a/exercises/practice/triangle/.meta/tests.toml +++ b/exercises/practice/triangle/.meta/tests.toml @@ -1,60 +1,73 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [8b2c43ac-7257-43f9-b552-7631a91988af] -description = "all sides are equal" +description = "equilateral triangle -> all sides are equal" [33eb6f87-0498-4ccf-9573-7f8c3ce92b7b] -description = "any side is unequal" +description = "equilateral triangle -> any side is unequal" [c6585b7d-a8c0-4ad8-8a34-e21d36f7ad87] -description = "no sides are equal" +description = "equilateral triangle -> no sides are equal" [16e8ceb0-eadb-46d1-b892-c50327479251] -description = "all zero sides is not a triangle" +description = "equilateral triangle -> all zero sides is not a triangle" [3022f537-b8e5-4cc1-8f12-fd775827a00c] -description = "sides may be floats" +description = "equilateral triangle -> sides may be floats" [cbc612dc-d75a-4c1c-87fc-e2d5edd70b71] -description = "last two sides are equal" +description = "isosceles triangle -> last two sides are equal" [e388ce93-f25e-4daf-b977-4b7ede992217] -description = "first two sides are equal" +description = "isosceles triangle -> first two sides are equal" [d2080b79-4523-4c3f-9d42-2da6e81ab30f] -description = "first and last sides are equal" +description = "isosceles triangle -> first and last sides are equal" [8d71e185-2bd7-4841-b7e1-71689a5491d8] -description = "equilateral triangles are also isosceles" +description = "isosceles triangle -> equilateral triangles are also isosceles" [840ed5f8-366f-43c5-ac69-8f05e6f10bbb] -description = "no sides are equal" +description = "isosceles triangle -> no sides are equal" [2eba0cfb-6c65-4c40-8146-30b608905eae] -description = "first triangle inequality violation" +description = "isosceles triangle -> first triangle inequality violation" [278469cb-ac6b-41f0-81d4-66d9b828f8ac] -description = "second triangle inequality violation" +description = "isosceles triangle -> second triangle inequality violation" [90efb0c7-72bb-4514-b320-3a3892e278ff] -description = "third triangle inequality violation" +description = "isosceles triangle -> third triangle inequality violation" [adb4ee20-532f-43dc-8d31-e9271b7ef2bc] -description = "sides may be floats" +description = "isosceles triangle -> sides may be floats" [e8b5f09c-ec2e-47c1-abec-f35095733afb] -description = "no sides are equal" +description = "scalene triangle -> no sides are equal" [2510001f-b44d-4d18-9872-2303e7977dc1] -description = "all sides are equal" +description = "scalene triangle -> all sides are equal" [c6e15a92-90d9-4fb3-90a2-eef64f8d3e1e] -description = "two sides are equal" +description = "scalene triangle -> first and second sides are equal" + +[3da23a91-a166-419a-9abf-baf4868fd985] +description = "scalene triangle -> first and third sides are equal" + +[b6a75d98-1fef-4c42-8e9a-9db854ba0a4d] +description = "scalene triangle -> second and third sides are equal" [70ad5154-0033-48b7-af2c-b8d739cd9fdc] -description = "may not violate triangle inequality" +description = "scalene triangle -> may not violate triangle inequality" [26d9d59d-f8f1-40d3-ad58-ae4d54123d7d] -description = "sides may be floats" +description = "scalene triangle -> sides may be floats" diff --git a/exercises/practice/triangle/babel.config.js b/exercises/practice/triangle/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/triangle/babel.config.js +++ b/exercises/practice/triangle/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/triangle/eslint.config.mjs b/exercises/practice/triangle/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/triangle/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/triangle/jest.config.js b/exercises/practice/triangle/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/triangle/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/triangle/package.json b/exercises/practice/triangle/package.json index 9dd964c563..6f24674754 100644 --- a/exercises/practice/triangle/package.json +++ b/exercises/practice/triangle/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/triangle" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/triangle/triangle.js b/exercises/practice/triangle/triangle.js index d2897b64e2..cef60307d3 100644 --- a/exercises/practice/triangle/triangle.js +++ b/exercises/practice/triangle/triangle.js @@ -5,18 +5,18 @@ export class Triangle { constructor(...sides) { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get isEquilateral() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get isIsosceles() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } get isScalene() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/triangle/triangle.spec.js b/exercises/practice/triangle/triangle.spec.js index 5467470d0a..b8b6cb6110 100644 --- a/exercises/practice/triangle/triangle.spec.js +++ b/exercises/practice/triangle/triangle.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { Triangle } from './triangle'; describe('Triangle', () => { @@ -86,11 +87,21 @@ describe('Triangle', () => { expect(triangle.isScalene).toBe(false); }); - xtest('two sides are equal', () => { + xtest('first and second sides are equal', () => { const triangle = new Triangle(4, 4, 3); expect(triangle.isScalene).toBe(false); }); + xtest('first and third sides are equal', () => { + const triangle = new Triangle(3, 4, 3); + expect(triangle.isScalene).toBe(false); + }); + + xtest('second and third sides are equal', () => { + const triangle = new Triangle(4, 3, 3); + expect(triangle.isScalene).toBe(false); + }); + xtest('may not violate triangle inequality', () => { const triangle = new Triangle(7, 3, 2); expect(triangle.isScalene).toBe(false); diff --git a/exercises/practice/trinary/.eslintrc b/exercises/practice/trinary/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/trinary/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/trinary/.gitignore b/exercises/practice/trinary/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/trinary/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/trinary/.meta/config.json b/exercises/practice/trinary/.meta/config.json index 39e1189f65..b143300bbd 100644 --- a/exercises/practice/trinary/.meta/config.json +++ b/exercises/practice/trinary/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Convert a trinary number, represented as a string (e.g. '102012'), to its decimal equivalent using first principles.", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "ankorGH", "msomji", @@ -11,10 +12,23 @@ "xarxziux" ], "files": { - "solution": ["trinary.js"], - "test": ["trinary.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "trinary.js" + ], + "test": [ + "trinary.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Convert a trinary number, represented as a string (e.g. '102012'), to its decimal equivalent using first principles.", "source": "All of Computer Science", - "source_url": "http://www.wolframalpha.com/input/?i=binary&a=*C.binary-_*MathWorld-" + "source_url": "http://www.wolframalpha.com/input/?i=binary&a=*C.binary-_*MathWorld-", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/trinary/.meta/proof.ci.js b/exercises/practice/trinary/.meta/proof.ci.js index cdde2fbc40..3147b2908f 100644 --- a/exercises/practice/trinary/.meta/proof.ci.js +++ b/exercises/practice/trinary/.meta/proof.ci.js @@ -12,7 +12,7 @@ export class Trinary { return this.digits.reduce( (decimal, digit, index) => decimal + digit * BASE ** index, - 0 + 0, ); } diff --git a/exercises/practice/trinary/babel.config.js b/exercises/practice/trinary/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/trinary/babel.config.js +++ b/exercises/practice/trinary/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/trinary/eslint.config.mjs b/exercises/practice/trinary/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/trinary/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/trinary/jest.config.js b/exercises/practice/trinary/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/trinary/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/trinary/package.json b/exercises/practice/trinary/package.json index 5d54400aef..5bb6971abf 100644 --- a/exercises/practice/trinary/package.json +++ b/exercises/practice/trinary/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/trinary" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/trinary/trinary.js b/exercises/practice/trinary/trinary.js index 0cf1e65a29..6c65184f59 100644 --- a/exercises/practice/trinary/trinary.js +++ b/exercises/practice/trinary/trinary.js @@ -5,10 +5,10 @@ export class Trinary { constructor() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } toDecimal() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/trinary/trinary.spec.js b/exercises/practice/trinary/trinary.spec.js index f7c564386d..f692226379 100644 --- a/exercises/practice/trinary/trinary.spec.js +++ b/exercises/practice/trinary/trinary.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { Trinary } from './trinary'; describe('Trinary', () => { diff --git a/exercises/practice/twelve-days/.docs/instructions.md b/exercises/practice/twelve-days/.docs/instructions.md index c54cd95fcf..83bb6e1926 100644 --- a/exercises/practice/twelve-days/.docs/instructions.md +++ b/exercises/practice/twelve-days/.docs/instructions.md @@ -1,6 +1,13 @@ # Instructions -Output the lyrics to 'The Twelve Days of Christmas'. +Your task in this exercise is to write code that returns the lyrics of the song: "The Twelve Days of Christmas." + +"The Twelve Days of Christmas" is a common English Christmas carol. +Each subsequent verse of the song builds on the previous verse. + +The lyrics your code returns should _exactly_ match the full song text shown below. + +## Lyrics ```text On the first day of Christmas my true love gave to me: a Partridge in a Pear Tree. diff --git a/exercises/practice/twelve-days/.eslintrc b/exercises/practice/twelve-days/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/twelve-days/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/twelve-days/.gitignore b/exercises/practice/twelve-days/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/twelve-days/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/twelve-days/.meta/config.json b/exercises/practice/twelve-days/.meta/config.json index 1a75e4a40e..a9328033a9 100644 --- a/exercises/practice/twelve-days/.meta/config.json +++ b/exercises/practice/twelve-days/.meta/config.json @@ -1,6 +1,7 @@ { - "blurb": "Output the lyrics to 'The Twelve Days of Christmas'", - "authors": ["AakashMallik"], + "authors": [ + "AakashMallik" + ], "contributors": [ "ankorGH", "cmccandless", @@ -9,10 +10,23 @@ "tejasbubane" ], "files": { - "solution": ["twelve-days.js"], - "test": ["twelve-days.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "twelve-days.js" + ], + "test": [ + "twelve-days.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Output the lyrics to 'The Twelve Days of Christmas'.", "source": "Wikipedia", - "source_url": "http://en.wikipedia.org/wiki/The_Twelve_Days_of_Christmas_(song)" + "source_url": "https://en.wikipedia.org/wiki/The_Twelve_Days_of_Christmas_(song)", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/twelve-days/babel.config.js b/exercises/practice/twelve-days/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/twelve-days/babel.config.js +++ b/exercises/practice/twelve-days/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/twelve-days/eslint.config.mjs b/exercises/practice/twelve-days/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/twelve-days/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/twelve-days/jest.config.js b/exercises/practice/twelve-days/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/twelve-days/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/twelve-days/package.json b/exercises/practice/twelve-days/package.json index c0479682ce..a4371c33f7 100644 --- a/exercises/practice/twelve-days/package.json +++ b/exercises/practice/twelve-days/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/twelve-days" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/twelve-days/twelve-days.js b/exercises/practice/twelve-days/twelve-days.js index b3ac658733..0b4fb3511d 100644 --- a/exercises/practice/twelve-days/twelve-days.js +++ b/exercises/practice/twelve-days/twelve-days.js @@ -4,5 +4,5 @@ // export const recite = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/twelve-days/twelve-days.spec.js b/exercises/practice/twelve-days/twelve-days.spec.js index ec4fc28079..0776467fbf 100644 --- a/exercises/practice/twelve-days/twelve-days.spec.js +++ b/exercises/practice/twelve-days/twelve-days.spec.js @@ -1,79 +1,80 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { recite } from './twelve-days'; describe('TwelveDays', () => { - test('test verse first day a partridge in a pear tree', () => { + test('verse first day a partridge in a pear tree', () => { const expectedVerseOne = 'On the first day of Christmas my true love gave to me: a Partridge in a Pear Tree.\n'; expect(recite(1)).toEqual(expectedVerseOne); }); - xtest('test verse second day two turtle doves', () => { + xtest('verse second day two turtle doves', () => { const expectedVerseTwo = 'On the second day of Christmas my true love gave to me: two Turtle Doves, and a Partridge in a Pear Tree.\n'; expect(recite(2)).toEqual(expectedVerseTwo); }); - xtest('test verse third day three french hens', () => { + xtest('verse third day three french hens', () => { const expectedVerseThree = 'On the third day of Christmas my true love gave to me: three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n'; expect(recite(3)).toEqual(expectedVerseThree); }); - xtest('test verse fourth day four calling birds', () => { + xtest('verse fourth day four calling birds', () => { const expectedVerseFour = 'On the fourth day of Christmas my true love gave to me: four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n'; expect(recite(4)).toEqual(expectedVerseFour); }); - xtest('test verse fifth day five gold rings', () => { + xtest('verse fifth day five gold rings', () => { const expectedVerseFive = 'On the fifth day of Christmas my true love gave to me: five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n'; expect(recite(5)).toEqual(expectedVerseFive); }); - xtest('test verse sixth day six geese-a-laying', () => { + xtest('verse sixth day six geese-a-laying', () => { const expectedVerseSix = 'On the sixth day of Christmas my true love gave to me: six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n'; expect(recite(6)).toEqual(expectedVerseSix); }); - xtest('test verse seventh day seven swans-a-swimming', () => { + xtest('verse seventh day seven swans-a-swimming', () => { const expectedVerseSeven = 'On the seventh day of Christmas my true love gave to me: seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n'; expect(recite(7)).toEqual(expectedVerseSeven); }); - xtest('test verse eighth day eight maids-a-milking', () => { + xtest('verse eighth day eight maids-a-milking', () => { const expectedVerseEight = 'On the eighth day of Christmas my true love gave to me: eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n'; expect(recite(8)).toEqual(expectedVerseEight); }); - xtest('test verse ninth day nine ladies dancing', () => { + xtest('verse ninth day nine ladies dancing', () => { const expectedVerseNine = 'On the ninth day of Christmas my true love gave to me: nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n'; expect(recite(9)).toEqual(expectedVerseNine); }); - xtest('test verse tenth day ten lords-a-leaping', () => { + xtest('verse tenth day ten lords-a-leaping', () => { const expectedVerseTen = 'On the tenth day of Christmas my true love gave to me: ten Lords-a-Leaping, nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n'; expect(recite(10)).toEqual(expectedVerseTen); }); - xtest('test verse eleventh day eleven pipers piping', () => { + xtest('verse eleventh day eleven pipers piping', () => { const expectedVerseEleven = 'On the eleventh day of Christmas my true love gave to me: eleven Pipers Piping, ten Lords-a-Leaping, nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n'; expect(recite(11)).toEqual(expectedVerseEleven); }); - xtest('test verse twelfth day twelve drummers drumming', () => { + xtest('verse twelfth day twelve drummers drumming', () => { const expectedVerseTwelve = 'On the twelfth day of Christmas my true love gave to me: twelve Drummers Drumming, eleven Pipers Piping, ten Lords-a-Leaping, nine Ladies Dancing, eight Maids-a-Milking, seven Swans-a-Swimming, six Geese-a-Laying, five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n'; expect(recite(12)).toEqual(expectedVerseTwelve); }); - xtest('test lyrics recites first three verses of the song', () => { + xtest('lyrics recites first three verses of the song', () => { const expectedVerseOneToThree = 'On the first day of Christmas my true love gave to me: a Partridge in a Pear Tree.\n\n' + 'On the second day of Christmas my true love gave to me: two Turtle Doves, and a Partridge in a Pear Tree.\n\n' + @@ -81,7 +82,7 @@ describe('TwelveDays', () => { expect(recite(1, 3)).toEqual(expectedVerseOneToThree); }); - xtest('test lyrics recites three verses from the middle of the song', () => { + xtest('lyrics recites three verses from the middle of the song', () => { const expectedVerseFourToSix = 'On the fourth day of Christmas my true love gave to me: four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n\n' + 'On the fifth day of Christmas my true love gave to me: five Gold Rings, four Calling Birds, three French Hens, two Turtle Doves, and a Partridge in a Pear Tree.\n\n' + @@ -89,7 +90,7 @@ describe('TwelveDays', () => { expect(recite(4, 6)).toEqual(expectedVerseFourToSix); }); - xtest('test lyrics recites the whole song', () => { + xtest('lyrics recites the whole song', () => { const expectedSong = 'On the first day of Christmas my true love gave to me: a Partridge in a Pear Tree.\n\n' + 'On the second day of Christmas my true love gave to me: two Turtle Doves, and a Partridge in a Pear Tree.\n\n' + diff --git a/exercises/practice/two-bucket/.docs/instructions.append.md b/exercises/practice/two-bucket/.docs/instructions.append.md new file mode 100644 index 0000000000..8207b3eb8e --- /dev/null +++ b/exercises/practice/two-bucket/.docs/instructions.append.md @@ -0,0 +1,20 @@ +# Instructions.append + +## Output format + +The `solve()` method is expected to return an object with these properties: + +- `moves` - the number of bucket actions required to reach the goal + (includes filling the start bucket), +- `goalBucket` - the name of the bucket that reached the goal amount, +- `otherBucket` - the amount contained in the other bucket. + +Example: + +```json +{ + "moves": 5, + "goalBucket": "one", + "otherBucket": 2 +} +``` diff --git a/exercises/practice/two-bucket/.docs/instructions.md b/exercises/practice/two-bucket/.docs/instructions.md index 619dd5cadd..30d779aa92 100644 --- a/exercises/practice/two-bucket/.docs/instructions.md +++ b/exercises/practice/two-bucket/.docs/instructions.md @@ -1,10 +1,17 @@ # Instructions -Given two buckets of different size, demonstrate how to measure an exact number of liters by strategically transferring liters of fluid between the buckets. +Given two buckets of different size and which bucket to fill first, determine how many actions are required to measure an exact number of liters by strategically transferring fluid between the buckets. -Since this mathematical problem is fairly subject to interpretation / individual approach, the tests have been written specifically to expect one overarching solution. +There are some rules that your solution must follow: -To help, the tests provide you with which bucket to fill first. That means, when starting with the larger bucket full, you are NOT allowed at any point to have the smaller bucket full and the larger bucket empty (aka, the opposite starting point); that would defeat the purpose of comparing both approaches! +- You can only do one action at a time. +- There are only 3 possible actions: + 1. Pouring one bucket into the other bucket until either: + a) the first bucket is empty + b) the second bucket is full + 2. Emptying a bucket and doing nothing to the other. + 3. Filling a bucket and doing nothing to the other. +- After an action, you may not arrive at a state where the initial starting bucket is empty and the other bucket is full. Your program will take as input: @@ -15,19 +22,25 @@ Your program will take as input: Your program should determine: -- the total number of "moves" it should take to reach the desired number of liters, including the first fill -- which bucket should end up with the desired number of liters (let's say this is bucket A) - either bucket one or bucket two -- how many liters are left in the other bucket (bucket B) +- the total number of actions it should take to reach the desired number of liters, including the first fill of the starting bucket +- which bucket should end up with the desired number of liters - either bucket one or bucket two +- how many liters are left in the other bucket -Note: any time a change is made to either or both buckets counts as one (1) move. +Note: any time a change is made to either or both buckets counts as one (1) action. Example: -Bucket one can hold up to 7 liters, and bucket two can hold up to 11 liters. Let's say bucket one, at a given step, is holding 7 liters, and bucket two is holding 8 liters (7,8). If you empty bucket one and make no change to bucket two, leaving you with 0 liters and 8 liters respectively (0,8), that counts as one "move". Instead, if you had poured from bucket one into bucket two until bucket two was full, leaving you with 4 liters in bucket one and 11 liters in bucket two (4,11), that would count as only one "move" as well. +Bucket one can hold up to 7 liters, and bucket two can hold up to 11 liters. +Let's say at a given step, bucket one is holding 7 liters and bucket two is holding 8 liters (7,8). +If you empty bucket one and make no change to bucket two, leaving you with 0 liters and 8 liters respectively (0,8), that counts as one action. +Instead, if you had poured from bucket one into bucket two until bucket two was full, resulting in 4 liters in bucket one and 11 liters in bucket two (4,11), that would also only count as one action. -To conclude, the only valid moves are: +Another Example: +Bucket one can hold 3 liters, and bucket two can hold up to 5 liters. +You are told you must start with bucket one. +So your first action is to fill bucket one. +You choose to empty bucket one for your second action. +For your third action, you may not fill bucket two, because this violates the third rule -- you may not end up in a state after any action where the starting bucket is empty and the other bucket is full. -- pouring from either bucket to another -- emptying either bucket and doing nothing to the other -- filling either bucket and doing nothing to the other +Written with <3 at [Fullstack Academy][fullstack] by Lindsay Levine. -Written with <3 at [Fullstack Academy](http://www.fullstackacademy.com/) by Lindsay Levine. +[fullstack]: https://www.fullstackacademy.com/ diff --git a/exercises/practice/two-bucket/.eslintrc b/exercises/practice/two-bucket/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/two-bucket/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/two-bucket/.gitignore b/exercises/practice/two-bucket/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/two-bucket/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/two-bucket/.meta/config.json b/exercises/practice/two-bucket/.meta/config.json index 6bc1bf2e85..df8d3a9cf5 100644 --- a/exercises/practice/two-bucket/.meta/config.json +++ b/exercises/practice/two-bucket/.meta/config.json @@ -1,9 +1,11 @@ { - "blurb": "Given two buckets of different size, demonstrate how to measure an exact number of liters.", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "ankorGH", "ganderzz", + "jagdish-15", "rchavarria", "ryanplusplus", "slaymance", @@ -11,10 +13,23 @@ "tejasbubane" ], "files": { - "solution": ["two-bucket.js"], - "test": ["two-bucket.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "two-bucket.js" + ], + "test": [ + "two-bucket.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Given two buckets of different size, demonstrate how to measure an exact number of liters.", "source": "Water Pouring Problem", - "source_url": "http://demonstrations.wolfram.com/WaterPouringProblem/" + "source_url": "https://demonstrations.wolfram.com/WaterPouringProblem/", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/two-bucket/.meta/proof.ci.js b/exercises/practice/two-bucket/.meta/proof.ci.js index f5cd5bf4ef..9330e75785 100644 --- a/exercises/practice/two-bucket/.meta/proof.ci.js +++ b/exercises/practice/two-bucket/.meta/proof.ci.js @@ -1,141 +1,110 @@ export class TwoBucket { - constructor(x, y, z, starter) { - this.starter = starter; - this.x = x; - this.y = y; - this.z = z; - } + constructor(size1, size2, goal, start) { + this.goal = goal; + this.buckets = [new Bucket('one', size1), new Bucket('two', size2)]; - reachedGoal(measurements) { - const j = measurements[0]; - const k = measurements[1]; + if (start === 'two') { + this.buckets.reverse(); + } - if (j === this.z || k === this.z) { - if (j === this.z) { - this.goalBucket = 'one'; - this.otherBucket = k; - } else { - this.goalBucket = 'two'; - this.otherBucket = j; - } + this.validate(); + } + + get first() { + return this.buckets[0]; + } + get second() { + return this.buckets[1]; + } - return true; + validate() { + if (this.goal > Math.max(this.first.size, this.second.size)) { + throw new Error('Goal is bigger than the largest bucket.'); } - return false; + if (this.goal % gcd(this.first.size, this.second.size) !== 0) { + throw new Error( + 'Goal must be a multiple of the GCD of the sizes of the two buckets.', + ); + } } - bigFirst(measurements, moveCount, prBool) { - let measure = measurements; - let mvCount = moveCount; - let j = measure[0]; - let k = measure[1]; - let bool = prBool; - - while (!this.reachedGoal(measure)) { - if (k > this.x && j === 0 && mvCount === 0) { - j = this.x; - k = this.y - j; - } else if (j === this.x) { - j = 0; - } else if (k > this.x && (j !== 0 || k > this.x) && bool) { - k -= this.x - j; - j = this.x; - } else if (k > this.x || j === 0) { - j = k; - k -= j; - } else if (k === 0) { - k = this.y; - } - measure = [j, k]; - mvCount += 1; - bool = !bool; + solve() { + this.first.empty(); + this.second.empty(); + let moves = 0; + + // fill the start bucket with the first move + this.first.fill(); + moves += 1; + + // optimization: if the other bucket is the right size, + // fill it immediately with the second move + if (this.second.size === this.goal) { + this.second.fill(); + moves += 1; } - return mvCount; - } + while (true) { + if (this.first.amount === this.goal) { + return { + moves: moves, + goalBucket: this.first.name, + otherBucket: this.second.amount, + }; + } - smallFirst(measurements, moveCount, prBool) { - let measure = measurements; - let mvCount = moveCount; - let j = measure[0]; - let k = measure[1]; - let bool = prBool; - - while (!this.reachedGoal(measure)) { - if (j === this.x && mvCount === 0) { - j = 0; - k = this.x; - } else if (j === 0) { - j = this.x; - } else if (j === this.x && k < this.y) { - const tempK = k; - if (k + j > this.y) { - k = this.y; - } else { - k = tempK + j; - } - - if (tempK + j > this.y) { - j -= this.y - tempK; - } else { - j = 0; - } - } else if (k === this.y) { - k = 0; - } else if (k === 0 && j < this.x) { - k = j; - j = 0; + if (this.second.amount === this.goal) { + return { + moves: moves, + goalBucket: this.second.name, + otherBucket: this.first.amount, + }; } - measure = [j, k]; - mvCount += 1; - bool = !bool; - } - return mvCount; - } + if (this.first.isEmpty) { + this.first.fill(); + } else if (this.second.isFull) { + this.second.empty(); + } else { + this.first.pourInto(this.second); + } - gcd(a, b) { - // greatest common divisor - if (!b) { - return a; + moves += 1; } - return this.gcd(b, a % b); } +} - moves() { - // j will be running val of bucket one, k = running val of bucket two - let j = 0; - let k = 0; - - // if the goal is not a multiple of the gcd of bucket one and bucket two, - // or the goal is bigger than both buckets, - // the solution will be impossible. - if ( - this.z % this.gcd(this.x, this.y) !== 0 || - (this.z > this.x && this.z > this.y) - ) { - throw new Error('Cannot reach the goal.'); - } - - if (this.starter === 'one') { - j = this.x; - } else { - k = this.y; - } +class Bucket { + constructor(name, size) { + this.name = name; + this.size = size; + this.amount = 0; + } - const measurements = [j, k]; - let moveCount = 0; - // pour / receive boolean - need to pour or receive every other turn - const prBool = true; + // accessors + get available() { + return this.size - this.amount; + } + get isFull() { + return this.amount === this.size; + } + get isEmpty() { + return this.amount === 0; + } - if (this.starter === 'one') { - moveCount = this.smallFirst(measurements, moveCount, prBool); - } else { - moveCount = this.bigFirst(measurements, moveCount, prBool); - } + fill() { + this.amount = this.size; + } + empty() { + this.amount = 0; + } - // accounts for first move made before loop (and moveCount starts at zero before loop) - return moveCount + 1; + pourInto(other) { + const quantity = Math.min(this.amount, other.available); + this.amount -= quantity; + other.amount += quantity; } } + +const gcd = (a, b) => (b === 0 ? a : gcd(b, a % b)); diff --git a/exercises/practice/two-bucket/.meta/tests.toml b/exercises/practice/two-bucket/.meta/tests.toml index d6ff02f53e..a3fe533ece 100644 --- a/exercises/practice/two-bucket/.meta/tests.toml +++ b/exercises/practice/two-bucket/.meta/tests.toml @@ -27,6 +27,12 @@ description = "Measure one step using bucket one of size 1 and bucket two of siz [eb329c63-5540-4735-b30b-97f7f4df0f84] description = "Measure using bucket one of size 2 and bucket two of size 3 - start with bucket one and end with bucket two" +[58d70152-bf2b-46bb-ad54-be58ebe94c03] +description = "Measure using bucket one much bigger than bucket two" + +[9dbe6499-caa5-4a58-b5ce-c988d71b8981] +description = "Measure using bucket one much smaller than bucket two" + [449be72d-b10a-4f4b-a959-ca741e333b72] description = "Not possible to reach the goal" diff --git a/exercises/practice/two-bucket/babel.config.js b/exercises/practice/two-bucket/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/two-bucket/babel.config.js +++ b/exercises/practice/two-bucket/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/two-bucket/eslint.config.mjs b/exercises/practice/two-bucket/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/two-bucket/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/two-bucket/jest.config.js b/exercises/practice/two-bucket/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/two-bucket/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/two-bucket/package.json b/exercises/practice/two-bucket/package.json index 16bcd82919..b8f71f20d1 100644 --- a/exercises/practice/two-bucket/package.json +++ b/exercises/practice/two-bucket/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/two-bucket" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/two-bucket/two-bucket.js b/exercises/practice/two-bucket/two-bucket.js index c313992f16..4c7043e855 100644 --- a/exercises/practice/two-bucket/two-bucket.js +++ b/exercises/practice/two-bucket/two-bucket.js @@ -5,18 +5,10 @@ export class TwoBucket { constructor() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } - moves() { - throw new Error('Remove this statement and implement this function'); - } - - get goalBucket() { - throw new Error('Remove this statement and implement this function'); - } - - get otherBucket() { - throw new Error('Remove this statement and implement this function'); + solve() { + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/two-bucket/two-bucket.spec.js b/exercises/practice/two-bucket/two-bucket.spec.js index 7765c6facf..dd05397480 100644 --- a/exercises/practice/two-bucket/two-bucket.spec.js +++ b/exercises/practice/two-bucket/two-bucket.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { TwoBucket } from './two-bucket'; describe('TwoBucket', () => { @@ -10,20 +11,22 @@ describe('TwoBucket', () => { // indicates which bucket to fill first const starterBuck = 'one'; const twoBucket = new TwoBucket(buckOne, buckTwo, goal, starterBuck); + const result = twoBucket.solve(); // includes the first fill - expect(twoBucket.moves()).toEqual(4); + expect(result.moves).toEqual(4); // which bucket should end up with the desired # of liters - expect(twoBucket.goalBucket).toEqual('one'); + expect(result.goalBucket).toEqual('one'); // leftover value in the "other" bucket once the goal has been reached - expect(twoBucket.otherBucket).toEqual(5); + expect(result.otherBucket).toEqual(5); }); xtest('start with bucket two', () => { const starterBuck = 'two'; const twoBucket = new TwoBucket(buckOne, buckTwo, goal, starterBuck); - expect(twoBucket.moves()).toEqual(8); - expect(twoBucket.goalBucket).toEqual('two'); - expect(twoBucket.otherBucket).toEqual(3); + const result = twoBucket.solve(); + expect(result.moves).toEqual(8); + expect(result.goalBucket).toEqual('two'); + expect(result.otherBucket).toEqual(3); }); }); @@ -35,70 +38,71 @@ describe('TwoBucket', () => { xtest('start with bucket one', () => { const starterBuck = 'one'; const twoBucket = new TwoBucket(buckOne, buckTwo, goal, starterBuck); - expect(twoBucket.moves()).toEqual(14); - expect(twoBucket.goalBucket).toEqual('one'); - expect(twoBucket.otherBucket).toEqual(11); + const result = twoBucket.solve(); + expect(result.moves).toEqual(14); + expect(result.goalBucket).toEqual('one'); + expect(result.otherBucket).toEqual(11); }); xtest('start with bucket two', () => { const starterBuck = 'two'; const twoBucket = new TwoBucket(buckOne, buckTwo, goal, starterBuck); - expect(twoBucket.moves()).toEqual(18); - expect(twoBucket.goalBucket).toEqual('two'); - expect(twoBucket.otherBucket).toEqual(7); + const result = twoBucket.solve(); + expect(result.moves).toEqual(18); + expect(result.goalBucket).toEqual('two'); + expect(result.otherBucket).toEqual(7); }); }); describe('Measure one step using bucket one of size 1 and bucket two of size 3', () => { xtest('start with bucket two', () => { const twoBucket = new TwoBucket(1, 3, 3, 'two'); - expect(twoBucket.moves()).toEqual(1); - expect(twoBucket.goalBucket).toEqual('two'); - expect(twoBucket.otherBucket).toEqual(0); + const result = twoBucket.solve(); + expect(result.moves).toEqual(1); + expect(result.goalBucket).toEqual('two'); + expect(result.otherBucket).toEqual(0); }); }); describe('Measure using bucket one of size 2 and bucket two of size 3', () => { xtest('start with bucket one and end with bucket two', () => { const twoBucket = new TwoBucket(2, 3, 3, 'one'); - expect(twoBucket.moves()).toEqual(4); - expect(twoBucket.goalBucket).toEqual('two'); - expect(twoBucket.otherBucket).toEqual(1); + const result = twoBucket.solve(); + expect(result.moves).toEqual(2); + expect(result.goalBucket).toEqual('two'); + expect(result.otherBucket).toEqual(2); }); }); - describe('Reachability', () => { - const buckOne = 6; - const buckTwo = 15; + xtest('Measure using bucket one much bigger than bucket two', () => { + const twoBucket = new TwoBucket(5, 1, 2, 'one'); + const result = twoBucket.solve(); + expect(result.moves).toEqual(6); + expect(result.goalBucket).toEqual('one'); + expect(result.otherBucket).toEqual(1); + }); - xtest('Not possible to reach the goal, start with bucket one', () => { - const starterBuck = 'one'; - const goal = 5; - const twoBucket = new TwoBucket(buckOne, buckTwo, goal, starterBuck); - expect(() => twoBucket.moves()).toThrow(); - }); + xtest('Measure using bucket one much smaller than bucket two', () => { + const twoBucket = new TwoBucket(3, 15, 9, 'one'); + const result = twoBucket.solve(); + expect(result.moves).toEqual(6); + expect(result.goalBucket).toEqual('two'); + expect(result.otherBucket).toEqual(0); + }); - xtest('Not possible to reach the goal, start with bucket two', () => { - const starterBuck = 'two'; - const goal = 5; - const twoBucket = new TwoBucket(buckOne, buckTwo, goal, starterBuck); - expect(() => twoBucket.moves()).toThrow(); - }); + xtest('Not possible to reach the goal', () => { + expect(() => new TwoBucket(6, 15, 5, 'one')).toThrow(); + }); - xtest('With the same buckets but a different goal, then it is possible', () => { - const starterBuck = 'one'; - const goal = 9; - const twoBucket = new TwoBucket(buckOne, buckTwo, goal, starterBuck); - expect(twoBucket.moves()).toEqual(10); - expect(twoBucket.goalBucket).toEqual('two'); - expect(twoBucket.otherBucket).toEqual(0); - }); + xtest('With the same buckets but a different goal, then it is possible', () => { + const twoBucket = new TwoBucket(6, 15, 9, 'one'); + const result = twoBucket.solve(); + expect(result.moves).toEqual(10); + expect(result.goalBucket).toEqual('two'); + expect(result.otherBucket).toEqual(0); }); - describe('Goal larger than both buckets', () => { - xtest('Is impossible', () => { - const twoBucket = new TwoBucket(5, 7, 8, 'one'); - expect(() => twoBucket.moves()).toThrow(); - }); + xtest('Goal larger than both buckets is impossible', () => { + expect(() => new TwoBucket(5, 7, 8, 'one')).toThrow(); }); }); diff --git a/exercises/practice/two-fer/.docs/instructions.md b/exercises/practice/two-fer/.docs/instructions.md index 11f14f2c42..adc5348798 100644 --- a/exercises/practice/two-fer/.docs/instructions.md +++ b/exercises/practice/two-fer/.docs/instructions.md @@ -1,16 +1,14 @@ # Instructions -`Two-fer` or `2-fer` is short for two for one. One for you and one for me. +Your task is to determine what you will say as you give away the extra cookie. -Given a name, return a string with the message: +If you know the person's name (e.g. if they're named Do-yun), then you will say: ```text -One for name, one for me. +One for Do-yun, one for me. ``` -Where "name" is the given name. - -However, if the name is missing, return the string: +If you don't know the person's name, you will say _you_ instead. ```text One for you, one for me. @@ -18,9 +16,9 @@ One for you, one for me. Here are some examples: -| Name | String to return | +| Name | Dialogue | | :----- | :-------------------------- | | Alice | One for Alice, one for me. | -| Bob | One for Bob, one for me. | +| Bohdan | One for Bohdan, one for me. | | | One for you, one for me. | | Zaphod | One for Zaphod, one for me. | diff --git a/exercises/practice/two-fer/.docs/introduction.md b/exercises/practice/two-fer/.docs/introduction.md new file mode 100644 index 0000000000..5947a2230b --- /dev/null +++ b/exercises/practice/two-fer/.docs/introduction.md @@ -0,0 +1,8 @@ +# Introduction + +In some English accents, when you say "two for" quickly, it sounds like "two fer". +Two-for-one is a way of saying that if you buy one, you also get one for free. +So the phrase "two-fer" often implies a two-for-one offer. + +Imagine a bakery that has a holiday offer where you can buy two cookies for the price of one ("two-fer one!"). +You take the offer and (very generously) decide to give the extra cookie to someone else in the queue. diff --git a/exercises/practice/two-fer/.eslintrc b/exercises/practice/two-fer/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/two-fer/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/two-fer/.gitignore b/exercises/practice/two-fer/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/two-fer/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/two-fer/.meta/config.json b/exercises/practice/two-fer/.meta/config.json index 4af566316b..d9490b6b47 100644 --- a/exercises/practice/two-fer/.meta/config.json +++ b/exercises/practice/two-fer/.meta/config.json @@ -1,11 +1,29 @@ { - "blurb": "Create a sentence of the form \"One for X, one for me.\"", - "authors": ["laurmurclar"], - "contributors": ["mluisamc", "serixscorpio", "SleeplessByte"], + "authors": [ + "laurmurclar" + ], + "contributors": [ + "mluisamc", + "serixscorpio", + "SleeplessByte" + ], "files": { - "solution": ["two-fer.js"], - "test": ["two-fer.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "two-fer.js" + ], + "test": [ + "two-fer.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, - "source_url": "https://github.com/exercism/problem-specifications/issues/757" + "blurb": "Create a sentence of the form \"One for X, one for me.\".", + "source_url": "https://github.com/exercism/problem-specifications/issues/757", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/two-fer/babel.config.js b/exercises/practice/two-fer/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/two-fer/babel.config.js +++ b/exercises/practice/two-fer/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/two-fer/eslint.config.mjs b/exercises/practice/two-fer/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/two-fer/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/two-fer/jest.config.js b/exercises/practice/two-fer/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/two-fer/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/two-fer/package.json b/exercises/practice/two-fer/package.json index de536235ac..4881d04d3e 100644 --- a/exercises/practice/two-fer/package.json +++ b/exercises/practice/two-fer/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/two-fer" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/two-fer/two-fer.js b/exercises/practice/two-fer/two-fer.js index da86f41da3..3276c9b234 100644 --- a/exercises/practice/two-fer/two-fer.js +++ b/exercises/practice/two-fer/two-fer.js @@ -4,5 +4,5 @@ // export const twoFer = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/two-fer/two-fer.spec.js b/exercises/practice/two-fer/two-fer.spec.js index 1c39a36556..e84c57fed0 100644 --- a/exercises/practice/two-fer/two-fer.spec.js +++ b/exercises/practice/two-fer/two-fer.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { twoFer } from './two-fer'; describe('twoFer()', () => { diff --git a/exercises/practice/variable-length-quantity/.docs/instructions.md b/exercises/practice/variable-length-quantity/.docs/instructions.md index eadce28d0e..5012548268 100644 --- a/exercises/practice/variable-length-quantity/.docs/instructions.md +++ b/exercises/practice/variable-length-quantity/.docs/instructions.md @@ -2,10 +2,10 @@ Implement variable length quantity encoding and decoding. -The goal of this exercise is to implement [VLQ](https://en.wikipedia.org/wiki/Variable-length_quantity) encoding/decoding. +The goal of this exercise is to implement [VLQ][vlq] encoding/decoding. In short, the goal of this encoding is to encode integer values in a way that would save bytes. -Only the first 7 bits of each byte is significant (right-justified; sort of like an ASCII byte). +Only the first 7 bits of each byte are significant (right-justified; sort of like an ASCII byte). So, if you have a 32-bit value, you have to unpack it into a series of 7-bit bytes. Of course, you will have a variable number of bytes depending upon your integer. To indicate which is the last byte of the series, you leave bit #7 clear. @@ -30,3 +30,5 @@ Here are examples of integers as 32-bit values, and the variable length quantiti 08000000 C0 80 80 00 0FFFFFFF FF FF FF 7F ``` + +[vlq]: https://en.wikipedia.org/wiki/Variable-length_quantity diff --git a/exercises/practice/variable-length-quantity/.eslintrc b/exercises/practice/variable-length-quantity/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/variable-length-quantity/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/variable-length-quantity/.gitignore b/exercises/practice/variable-length-quantity/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/variable-length-quantity/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/variable-length-quantity/.meta/config.json b/exercises/practice/variable-length-quantity/.meta/config.json index 3aaa0845de..23e4024824 100644 --- a/exercises/practice/variable-length-quantity/.meta/config.json +++ b/exercises/practice/variable-length-quantity/.meta/config.json @@ -1,12 +1,32 @@ { - "blurb": "Implement variable length quantity encoding and decoding.", - "authors": ["matthewmorgan"], - "contributors": ["ankorGH", "hayashi-ay", "SleeplessByte", "smb26"], + "authors": [ + "matthewmorgan" + ], + "contributors": [ + "ankorGH", + "hayashi-ay", + "jagdish-15", + "SleeplessByte", + "smb26" + ], "files": { - "solution": ["variable-length-quantity.js"], - "test": ["variable-length-quantity.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "variable-length-quantity.js" + ], + "test": [ + "variable-length-quantity.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Implement variable length quantity encoding and decoding.", "source": "A poor Splice developer having to implement MIDI encoding/decoding.", - "source_url": "https://splice.com" + "source_url": "https://splice.com", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/variable-length-quantity/.meta/tests.toml b/exercises/practice/variable-length-quantity/.meta/tests.toml index 923fa0c1aa..53be789a38 100644 --- a/exercises/practice/variable-length-quantity/.meta/tests.toml +++ b/exercises/practice/variable-length-quantity/.meta/tests.toml @@ -1,81 +1,103 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [35c9db2e-f781-4c52-b73b-8e76427defd0] -description = "zero" +description = "Encode a series of integers, producing a series of bytes. -> zero" [be44d299-a151-4604-a10e-d4b867f41540] -description = "arbitrary single byte" +description = "Encode a series of integers, producing a series of bytes. -> arbitrary single byte" + +[890bc344-cb80-45af-b316-6806a6971e81] +description = "Encode a series of integers, producing a series of bytes. -> asymmetric single byte" [ea399615-d274-4af6-bbef-a1c23c9e1346] -description = "largest single byte" +description = "Encode a series of integers, producing a series of bytes. -> largest single byte" [77b07086-bd3f-4882-8476-8dcafee79b1c] -description = "smallest double byte" +description = "Encode a series of integers, producing a series of bytes. -> smallest double byte" [63955a49-2690-4e22-a556-0040648d6b2d] -description = "arbitrary double byte" +description = "Encode a series of integers, producing a series of bytes. -> arbitrary double byte" + +[4977d113-251b-4d10-a3ad-2f5a7756bb58] +description = "Encode a series of integers, producing a series of bytes. -> asymmetric double byte" [29da7031-0067-43d3-83a7-4f14b29ed97a] -description = "largest double byte" +description = "Encode a series of integers, producing a series of bytes. -> largest double byte" [3345d2e3-79a9-4999-869e-d4856e3a8e01] -description = "smallest triple byte" +description = "Encode a series of integers, producing a series of bytes. -> smallest triple byte" [5df0bc2d-2a57-4300-a653-a75ee4bd0bee] -description = "arbitrary triple byte" +description = "Encode a series of integers, producing a series of bytes. -> arbitrary triple byte" + +[6731045f-1e00-4192-b5ae-98b22e17e9f7] +description = "Encode a series of integers, producing a series of bytes. -> asymmetric triple byte" [f51d8539-312d-4db1-945c-250222c6aa22] -description = "largest triple byte" +description = "Encode a series of integers, producing a series of bytes. -> largest triple byte" [da78228b-544f-47b7-8bfe-d16b35bbe570] -description = "smallest quadruple byte" +description = "Encode a series of integers, producing a series of bytes. -> smallest quadruple byte" [11ed3469-a933-46f1-996f-2231e05d7bb6] -description = "arbitrary quadruple byte" +description = "Encode a series of integers, producing a series of bytes. -> arbitrary quadruple byte" + +[b45ef770-cbba-48c2-bd3c-c6362679516e] +description = "Encode a series of integers, producing a series of bytes. -> asymmetric quadruple byte" [d5f3f3c3-e0f1-4e7f-aad0-18a44f223d1c] -description = "largest quadruple byte" +description = "Encode a series of integers, producing a series of bytes. -> largest quadruple byte" [91a18b33-24e7-4bfb-bbca-eca78ff4fc47] -description = "smallest quintuple byte" +description = "Encode a series of integers, producing a series of bytes. -> smallest quintuple byte" [5f34ff12-2952-4669-95fe-2d11b693d331] -description = "arbitrary quintuple byte" +description = "Encode a series of integers, producing a series of bytes. -> arbitrary quintuple byte" + +[9be46731-7cd5-415c-b960-48061cbc1154] +description = "Encode a series of integers, producing a series of bytes. -> asymmetric quintuple byte" [7489694b-88c3-4078-9864-6fe802411009] -description = "maximum 32-bit integer input" +description = "Encode a series of integers, producing a series of bytes. -> maximum 32-bit integer input" [f9b91821-cada-4a73-9421-3c81d6ff3661] -description = "two single-byte values" +description = "Encode a series of integers, producing a series of bytes. -> two single-byte values" [68694449-25d2-4974-ba75-fa7bb36db212] -description = "two multi-byte values" +description = "Encode a series of integers, producing a series of bytes. -> two multi-byte values" [51a06b5c-de1b-4487-9a50-9db1b8930d85] -description = "many multi-byte values" +description = "Encode a series of integers, producing a series of bytes. -> many multi-byte values" [baa73993-4514-4915-bac0-f7f585e0e59a] -description = "one byte" +description = "Decode a series of bytes, producing a series of integers. -> one byte" [72e94369-29f9-46f2-8c95-6c5b7a595aee] -description = "two bytes" +description = "Decode a series of bytes, producing a series of integers. -> two bytes" [df5a44c4-56f7-464e-a997-1db5f63ce691] -description = "three bytes" +description = "Decode a series of bytes, producing a series of integers. -> three bytes" [1bb58684-f2dc-450a-8406-1f3452aa1947] -description = "four bytes" +description = "Decode a series of bytes, producing a series of integers. -> four bytes" [cecd5233-49f1-4dd1-a41a-9840a40f09cd] -description = "maximum 32-bit integer" +description = "Decode a series of bytes, producing a series of integers. -> maximum 32-bit integer" [e7d74ba3-8b8e-4bcb-858d-d08302e15695] -description = "incomplete sequence causes error" +description = "Decode a series of bytes, producing a series of integers. -> incomplete sequence causes error" [aa378291-9043-4724-bc53-aca1b4a3fcb6] -description = "incomplete sequence causes error, even if value is zero" +description = "Decode a series of bytes, producing a series of integers. -> incomplete sequence causes error, even if value is zero" [a91e6f5a-c64a-48e3-8a75-ce1a81e0ebee] -description = "multiple values" +description = "Decode a series of bytes, producing a series of integers. -> multiple values" diff --git a/exercises/practice/variable-length-quantity/babel.config.js b/exercises/practice/variable-length-quantity/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/variable-length-quantity/babel.config.js +++ b/exercises/practice/variable-length-quantity/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/variable-length-quantity/eslint.config.mjs b/exercises/practice/variable-length-quantity/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/variable-length-quantity/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/variable-length-quantity/jest.config.js b/exercises/practice/variable-length-quantity/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/variable-length-quantity/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/variable-length-quantity/package.json b/exercises/practice/variable-length-quantity/package.json index f707e26b50..3e130ae764 100644 --- a/exercises/practice/variable-length-quantity/package.json +++ b/exercises/practice/variable-length-quantity/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/variable-length-quantity" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/variable-length-quantity/variable-length-quantity.js b/exercises/practice/variable-length-quantity/variable-length-quantity.js index 62862ffa79..eb750ba92b 100644 --- a/exercises/practice/variable-length-quantity/variable-length-quantity.js +++ b/exercises/practice/variable-length-quantity/variable-length-quantity.js @@ -4,9 +4,9 @@ // export const encode = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; export const decode = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/variable-length-quantity/variable-length-quantity.spec.js b/exercises/practice/variable-length-quantity/variable-length-quantity.spec.js index d6971bff4a..eebf839bd3 100644 --- a/exercises/practice/variable-length-quantity/variable-length-quantity.spec.js +++ b/exercises/practice/variable-length-quantity/variable-length-quantity.spec.js @@ -1,4 +1,5 @@ -import { encode, decode } from './variable-length-quantity'; +import { describe, expect, test, xtest } from '@jest/globals'; +import { decode, encode } from './variable-length-quantity'; describe('VariableLengthQuantity', () => { describe('Encode a series of integers, producing a series of bytes.', () => { @@ -10,6 +11,10 @@ describe('VariableLengthQuantity', () => { expect(encode([0x40])).toEqual([0x40]); }); + xtest('asymmetric single byte', () => { + expect(encode([0x53])).toEqual([0x53]); + }); + xtest('largest single byte', () => { expect(encode([0x7f])).toEqual([0x7f]); }); @@ -22,6 +27,10 @@ describe('VariableLengthQuantity', () => { expect(encode([0x2000])).toEqual([0xc0, 0]); }); + xtest('asymmetric double byte', () => { + expect(encode([0xad])).toEqual([0x81, 0x2d]); + }); + xtest('largest double byte', () => { expect(encode([0x3fff])).toEqual([0xff, 0x7f]); }); @@ -34,6 +43,10 @@ describe('VariableLengthQuantity', () => { expect(encode([0x100000])).toEqual([0xc0, 0x80, 0]); }); + xtest('asymmetric triple byte', () => { + expect(encode([0x1d59c])).toEqual([0x87, 0xab, 0x1c]); + }); + xtest('largest triple byte', () => { expect(encode([0x1fffff])).toEqual([0xff, 0xff, 0x7f]); }); @@ -46,6 +59,10 @@ describe('VariableLengthQuantity', () => { expect(encode([0x8000000])).toEqual([0xc0, 0x80, 0x80, 0]); }); + xtest('asymmetric quadruple byte', () => { + expect(encode([0x357704])).toEqual([0x81, 0xd5, 0xee, 0x04]); + }); + xtest('largest quadruple byte', () => { expect(encode([0xfffffff])).toEqual([0xff, 0xff, 0xff, 0x7f]); }); @@ -58,6 +75,10 @@ describe('VariableLengthQuantity', () => { expect(encode([0xff000000])).toEqual([0x8f, 0xf8, 0x80, 0x80, 0]); }); + xtest('asymmetric quintuple byte', () => { + expect(encode([0x86656105])).toEqual([0x88, 0xb3, 0x95, 0xc2, 0x05]); + }); + xtest('maximum 32-bit integer input', () => { expect(encode([0xffffffff])).toEqual([0x8f, 0xff, 0xff, 0xff, 0x7f]); }); diff --git a/exercises/practice/word-count/.docs/instructions.md b/exercises/practice/word-count/.docs/instructions.md index d3548e5d88..064393c8a0 100644 --- a/exercises/practice/word-count/.docs/instructions.md +++ b/exercises/practice/word-count/.docs/instructions.md @@ -1,31 +1,47 @@ # Instructions -Given a phrase, count the occurrences of each _word_ in that phrase. +Your task is to count how many times each word occurs in a subtitle of a drama. -For the purposes of this exercise you can expect that a _word_ will always be one of: +The subtitles from these dramas use only ASCII characters. -1. A _number_ composed of one or more ASCII digits (ie "0" or "1234") OR -2. A _simple word_ composed of one or more ASCII letters (ie "a" or "they") OR -3. A _contraction_ of two _simple words_ joined by a single apostrophe (ie "it's" or "they're") +The characters often speak in casual English, using contractions like _they're_ or _it's_. +Though these contractions come from two words (e.g. _we are_), the contraction (_we're_) is considered a single word. -When counting words you can assume the following rules: +Words can be separated by any form of punctuation (e.g. ":", "!", or "?") or whitespace (e.g. "\t", "\n", or " "). +The only punctuation that does not separate words is the apostrophe in contractions. -1. The count is _case insensitive_ (ie "You", "you", and "YOU" are 3 uses of the same word) -2. The count is _unordered_; the tests will ignore how words and counts are ordered -3. Other than the apostrophe in a _contraction_ all forms of _punctuation_ are ignored -4. The words can be separated by _any_ form of whitespace (ie "\t", "\n", " ") +Numbers are considered words. +If the subtitles say _It costs 100 dollars._ then _100_ will be its own word. -For example, for the phrase `"That's the password: 'PASSWORD 123'!", cried the Special Agent.\nSo I fled.` the count would be: +Words are case insensitive. +For example, the word _you_ occurs three times in the following sentence: + +> You come back, you hear me? DO YOU HEAR ME? + +The ordering of the word counts in the results doesn't matter. + +Here's an example that incorporates several of the elements discussed above: + +- simple words +- contractions +- numbers +- case insensitive words +- punctuation (including apostrophes) to separate words +- different forms of whitespace to separate words + +`"That's the password: 'PASSWORD 123'!", cried the Special Agent.\nSo I fled.` + +The mapping for this subtitle would be: ```text -that's: 1 -the: 2 -password: 2 123: 1 -cried: 1 -special: 1 agent: 1 -so: 1 -i: 1 +cried: 1 fled: 1 +i: 1 +password: 2 +so: 1 +special: 1 +that's: 1 +the: 2 ``` diff --git a/exercises/practice/word-count/.docs/introduction.md b/exercises/practice/word-count/.docs/introduction.md new file mode 100644 index 0000000000..1654508e79 --- /dev/null +++ b/exercises/practice/word-count/.docs/introduction.md @@ -0,0 +1,8 @@ +# Introduction + +You teach English as a foreign language to high school students. + +You've decided to base your entire curriculum on TV shows. +You need to analyze which words are used, and how often they're repeated. + +This will let you choose the simplest shows to start with, and to gradually increase the difficulty as time passes. diff --git a/exercises/practice/word-count/.eslintrc b/exercises/practice/word-count/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/word-count/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/word-count/.gitignore b/exercises/practice/word-count/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/word-count/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/word-count/.meta/config.json b/exercises/practice/word-count/.meta/config.json index f68cc5e588..5455789de7 100644 --- a/exercises/practice/word-count/.meta/config.json +++ b/exercises/practice/word-count/.meta/config.json @@ -1,9 +1,11 @@ { - "blurb": "Given a phrase, count the occurrences of each word in that phrase.", - "authors": ["rchavarria"], + "authors": [ + "rchavarria" + ], "contributors": [ "ankorGH", "draalger", + "jagdish-15", "kytrinyx", "matthewmorgan", "ovidiu141", @@ -14,9 +16,22 @@ "ZacharyRSmith" ], "files": { - "solution": ["word-count.js"], - "test": ["word-count.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "word-count.js" + ], + "test": [ + "word-count.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, - "source": "This is a classic toy problem, but we were reminded of it by seeing it in the Go Tour." + "blurb": "Given a phrase, count the occurrences of each word in that phrase.", + "source": "This is a classic toy problem, but we were reminded of it by seeing it in the Go Tour.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/word-count/.meta/tests.toml b/exercises/practice/word-count/.meta/tests.toml index b00c20ae03..1be425b33c 100644 --- a/exercises/practice/word-count/.meta/tests.toml +++ b/exercises/practice/word-count/.meta/tests.toml @@ -1,6 +1,13 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [61559d5f-2cad-48fb-af53-d3973a9ee9ef] description = "count one word" @@ -28,6 +35,11 @@ description = "normalize case" [4185a902-bdb0-4074-864c-f416e42a0f19] description = "with apostrophes" +include = false + +[4ff6c7d7-fcfc-43ef-b8e7-34ff1837a2d3] +description = "with apostrophes" +reimplements = "4185a902-bdb0-4074-864c-f416e42a0f19" [be72af2b-8afe-4337-b151-b297202e4a7b] description = "with quotations" @@ -40,3 +52,6 @@ description = "multiple spaces not detected as a word" [50176e8a-fe8e-4f4c-b6b6-aa9cf8f20360] description = "alternating word separators not detected as a word" + +[6d00f1db-901c-4bec-9829-d20eb3044557] +description = "quotation for word with apostrophe" diff --git a/exercises/practice/word-count/babel.config.js b/exercises/practice/word-count/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/word-count/babel.config.js +++ b/exercises/practice/word-count/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/word-count/eslint.config.mjs b/exercises/practice/word-count/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/word-count/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/word-count/jest.config.js b/exercises/practice/word-count/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/word-count/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/word-count/package.json b/exercises/practice/word-count/package.json index ed0c6f18ab..98eab15608 100644 --- a/exercises/practice/word-count/package.json +++ b/exercises/practice/word-count/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/word-count" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/word-count/word-count.js b/exercises/practice/word-count/word-count.js index 697cc32351..05feadfc63 100644 --- a/exercises/practice/word-count/word-count.js +++ b/exercises/practice/word-count/word-count.js @@ -4,5 +4,5 @@ // export const countWords = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/word-count/word-count.spec.js b/exercises/practice/word-count/word-count.spec.js index 091683ba7d..b83e5043bd 100644 --- a/exercises/practice/word-count/word-count.spec.js +++ b/exercises/practice/word-count/word-count.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { countWords } from './word-count'; describe('countWords', () => { @@ -20,7 +21,7 @@ describe('countWords', () => { blue: 1, }; expect(countWords('one fish two fish red fish blue fish')).toEqual( - expectedCounts + expectedCounts, ); }); @@ -51,7 +52,7 @@ describe('countWords', () => { javascript: 1, }; expect(countWords('car: carpet as java: javascript!!&@$%^&')).toEqual( - expectedCounts + expectedCounts, ); }); @@ -79,10 +80,13 @@ describe('countWords', () => { laugh: 1, then: 1, cry: 1, + "you're": 1, + getting: 1, + it: 1, }; - expect(countWords("First: don't laugh. Then: don't cry.")).toEqual( - expectedCounts - ); + expect( + countWords("'First: don't laugh. Then: don't cry. You're getting it.'"), + ).toEqual(expectedCounts); }); xtest('with quotations', () => { @@ -95,7 +99,7 @@ describe('countWords', () => { and: 1, }; expect(countWords("Joe can't tell between 'large' and large.")).toEqual( - expectedCounts + expectedCounts, ); }); @@ -111,7 +115,7 @@ describe('countWords', () => { a: 1, }; expect(countWords("Joe can't tell between app, apple and a.")).toEqual( - expectedCounts + expectedCounts, ); }); @@ -131,4 +135,12 @@ describe('countWords', () => { }; expect(countWords(",\n,one,\n ,two \n 'three'")).toEqual(expectedCounts); }); + + xtest('quotation for word with apostrophe', () => { + const expectedCounts = { + can: 1, + "can't": 2, + }; + expect(countWords("can, can't, 'can't'")).toEqual(expectedCounts); + }); }); diff --git a/exercises/practice/word-search/.docs/instructions.md b/exercises/practice/word-search/.docs/instructions.md index 345fa592ef..e2d08aa9ee 100644 --- a/exercises/practice/word-search/.docs/instructions.md +++ b/exercises/practice/word-search/.docs/instructions.md @@ -1,7 +1,6 @@ # Instructions -In word search puzzles you get a square of letters and have to find specific -words in them. +In word search puzzles you get a square of letters and have to find specific words in them. For example: @@ -20,8 +19,6 @@ clojurermt There are several programming languages hidden in the above square. -Words can be hidden in all kinds of directions: left-to-right, right-to-left, -vertical and diagonal. +Words can be hidden in all kinds of directions: left-to-right, right-to-left, vertical and diagonal. -Given a puzzle and a list of words return the location of the first and last -letter of each word. +Given a puzzle and a list of words return the location of the first and last letter of each word. diff --git a/exercises/practice/word-search/.eslintrc b/exercises/practice/word-search/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/word-search/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/word-search/.gitignore b/exercises/practice/word-search/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/word-search/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/word-search/.meta/config.json b/exercises/practice/word-search/.meta/config.json index 576d412773..d915c69783 100644 --- a/exercises/practice/word-search/.meta/config.json +++ b/exercises/practice/word-search/.meta/config.json @@ -1,16 +1,31 @@ { - "blurb": "Create a program to solve a word search puzzle.", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "hyuko21", "ivanvotti", + "jagdish-15", "msomji", "rchavarria", "SleeplessByte" ], "files": { - "solution": ["word-search.js"], - "test": ["word-search.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "word-search.js" + ], + "test": [ + "word-search.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] + }, + "blurb": "Create a program to solve a word search puzzle.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false } } diff --git a/exercises/practice/word-search/.meta/proof.ci.js b/exercises/practice/word-search/.meta/proof.ci.js index 727f68fa98..63fb510148 100644 --- a/exercises/practice/word-search/.meta/proof.ci.js +++ b/exercises/practice/word-search/.meta/proof.ci.js @@ -26,13 +26,11 @@ function searchHorizontally({ word, grid }) { } rowIndex += 1; } - return false; + return undefined; } function flipCoordinates(coords) { - if (!coords) { - return undefined; - } + if (!coords) return undefined; return { start: coords.start.reverse(), end: coords.end.reverse(), @@ -41,110 +39,85 @@ function flipCoordinates(coords) { function flipGrid(grid) { return [...grid[0]] - .map((col, c) => grid.map((row, r) => grid[r][c])) + .map((_, c) => grid.map((row) => row[c])) .map((row) => row.join('')); } -function diagonalFind(r, c, word, grid, rIncrement, outOfRange, buildCoords) { +function diagonalFind(r, c, word, grid, rIncrement, cIncrement) { let currentRow = r; let currentColumn = c; let foundLetters = ''; const startR = r + 1; const startC = c + 1; - let result; - word.split('').forEach((letter) => { + + for (const letter of word) { + // Bounds check if ( - !outOfRange( - currentRow, - currentColumn, - word.length, - grid[currentRow].length, - foundLetters.length - ) + currentRow < 0 || + currentRow >= grid.length || + currentColumn < 0 || + currentColumn >= grid[currentRow].length ) { - const currLetterInGrid = grid[currentRow].charAt(currentColumn); - currentColumn += 1; - if (currLetterInGrid === letter) { - foundLetters += currLetterInGrid; - if (foundLetters === word) { - result = buildCoords(startR, startC, currentRow, currentColumn); - } - currentRow += rIncrement; - } + return undefined; } - }); - return result; -} - -function findAWordDiagonallyTopDown(r, c, word, grid) { - function outOfRange(row, column, words, columns, letters) { - return ( - row > columns - words + letters || column > columns - words + letters - ); - } - - function buildCoords(startR, startC, row, column) { - return { - start: [startR, startC], - end: [row + 1, column], - }; - } - - return diagonalFind(r, c, word, grid, 1, outOfRange, buildCoords); -} -function findAWordDiagonallyBottomUp(r, c, word, grid) { - function outOfRange(row, column, words, columns, letters) { - return row < words - letters - 1 || column > columns - words + letters; - } + const currLetterInGrid = grid[currentRow].charAt(currentColumn); + if (currLetterInGrid === letter) { + foundLetters += currLetterInGrid; + if (foundLetters === word) { + return { + start: [startR, startC], + end: [currentRow + 1, currentColumn + 1], + }; + } + } else { + return undefined; + } - function buildCoords(startR, startC, row, column) { - return { - start: [startR, startC], - end: [row + 1, column], - }; + currentRow += rIncrement; + currentColumn += cIncrement; } - return diagonalFind(r, c, word, grid, -1, outOfRange, buildCoords); -} - -function formatCoordinates(coords, isReversed) { - return { - true: { - start: coords.end, - end: coords.start, - }, - false: coords, - }[isReversed]; + return undefined; } -function searchDiagonally({ word, grid, isReversed = false, fromTop = true }) { +function searchDiagonally({ word, grid, fromTop = true, reversed = false }) { const rIncrement = fromTop ? 1 : -1; const startRow = fromTop ? 0 : grid.length - 1; - const endRow = fromTop ? (r) => r < grid.length : (r) => r > 0; - const findDirection = fromTop - ? findAWordDiagonallyTopDown - : findAWordDiagonallyBottomUp; + const endRow = fromTop ? (r) => r < grid.length : (r) => r >= 0; for (let r = startRow; endRow(r); r += rIncrement) { for (let c = 0; c < grid[r].length; c += 1) { - const possibleCoords = findDirection(r, c, word, grid); - if (possibleCoords) { - return formatCoordinates(possibleCoords, isReversed); + const dirs = [ + [1, 1], // top-left to bottom-right + [1, -1], // top-right to bottom-left + [-1, 1], // bottom-left to top-right + [-1, -1], // bottom-right to top-left + ]; + + for (const [dr, dc] of dirs) { + const possible = diagonalFind(r, c, word, grid, dr, dc); + if (possible) { + if (reversed) { + return { start: possible.end, end: possible.start }; + } + return possible; + } } } } - if (!isReversed) { - // now find the reversed version + // Try reversed word + if (!reversed) { const reversedWord = [...word].reverse().join(''); return searchDiagonally({ word: reversedWord, grid, - isReversed: true, fromTop, + reversed: true, }); } + return undefined; } @@ -152,7 +125,7 @@ function findWordInAnyDirection(word, grid) { return ( searchHorizontally({ word, grid }) || flipCoordinates(searchHorizontally({ word, grid: flipGrid(grid) })) || - searchDiagonally({ word, grid }) || + searchDiagonally({ word, grid, fromTop: true }) || searchDiagonally({ word, grid, fromTop: false }) ); } diff --git a/exercises/practice/word-search/.meta/tests.toml b/exercises/practice/word-search/.meta/tests.toml index 68c3b60631..3f98113d7e 100644 --- a/exercises/practice/word-search/.meta/tests.toml +++ b/exercises/practice/word-search/.meta/tests.toml @@ -68,3 +68,15 @@ description = "Should locate words written top right to bottom left" [695531db-69eb-463f-8bad-8de3bf5ef198] description = "Should fail to locate a word that is not in the puzzle" + +[fda5b937-6774-4a52-8f89-f64ed833b175] +description = "Should fail to locate words that are not on horizontal, vertical, or diagonal lines" + +[5b6198eb-2847-4e2f-8efe-65045df16bd3] +description = "Should not concatenate different lines to find a horizontal word" + +[eba44139-a34f-4a92-98e1-bd5f259e5769] +description = "Should not wrap around horizontally to find a word" + +[cd1f0fa8-76af-4167-b105-935f78364dac] +description = "Should not wrap around vertically to find a word" diff --git a/exercises/practice/word-search/babel.config.js b/exercises/practice/word-search/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/word-search/babel.config.js +++ b/exercises/practice/word-search/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/word-search/eslint.config.mjs b/exercises/practice/word-search/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/word-search/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/word-search/jest.config.js b/exercises/practice/word-search/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/word-search/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/word-search/package.json b/exercises/practice/word-search/package.json index 63641fad89..334eb5115c 100644 --- a/exercises/practice/word-search/package.json +++ b/exercises/practice/word-search/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/word-search" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/word-search/word-search.js b/exercises/practice/word-search/word-search.js index 857c5222b9..a72fdcbedf 100644 --- a/exercises/practice/word-search/word-search.js +++ b/exercises/practice/word-search/word-search.js @@ -5,11 +5,11 @@ class WordSearch { constructor() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } find() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/word-search/word-search.spec.js b/exercises/practice/word-search/word-search.spec.js index 26c0f23817..aaa5f77795 100644 --- a/exercises/practice/word-search/word-search.spec.js +++ b/exercises/practice/word-search/word-search.spec.js @@ -1,6 +1,7 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import WordSearch from './word-search'; -describe('single line grids', () => { +describe('Word Search', () => { test('Should accept an initial game grid', () => { const grid = ['jefblpepre']; const wordSearch = new WordSearch(grid); @@ -8,14 +9,14 @@ describe('single line grids', () => { expect(wordSearch instanceof WordSearch).toEqual(true); }); - xtest('can accept a target search word', () => { + xtest('Can accept a target search word', () => { const grid = ['jefblpepre']; const wordSearch = new WordSearch(grid); expect(wordSearch.find(['glasnost'])).toEqual({ glasnost: undefined }); }); - xtest('should locate a word written left to right', () => { + xtest('Should locate a word written left to right', () => { const grid = ['clojurermt']; const expectedResults = { clojure: { @@ -28,7 +29,7 @@ describe('single line grids', () => { expect(wordSearch.find(['clojure'])).toEqual(expectedResults); }); - xtest('can locate a left to right word in a different position', () => { + xtest('Can locate a left to right word in a different position', () => { const grid = ['mtclojurer']; const expectedResults = { clojure: { @@ -36,12 +37,13 @@ describe('single line grids', () => { end: [1, 9], }, }; + const wordSearch = new WordSearch(grid); expect(wordSearch.find(['clojure'])).toEqual(expectedResults); }); - xtest('can locate a different left to right word', () => { + xtest('Can locate a different left to right word', () => { const grid = ['coffeelplx']; const expectedResults = { coffee: { @@ -49,11 +51,13 @@ describe('single line grids', () => { end: [1, 6], }, }; + const wordSearch = new WordSearch(grid); expect(wordSearch.find(['coffee'])).toEqual(expectedResults); }); - xtest('can locate that different left to right word in a different position', () => { + + xtest('Can locate that different left to right word in a different position', () => { const grid = ['xcoffeezlp']; const expectedResults = { coffee: { @@ -61,14 +65,13 @@ describe('single line grids', () => { end: [1, 7], }, }; + const wordSearch = new WordSearch(grid); expect(wordSearch.find(['coffee'])).toEqual(expectedResults); }); -}); -describe('multi line grids', () => { - xtest('can locate a left to right word in a two line grid', () => { + xtest('Can locate a left to right word in a two line grid', () => { const grid = ['jefblpepre', 'clojurermt']; const expectedResults = { @@ -82,7 +85,8 @@ describe('multi line grids', () => { expect(wordSearch.find(['clojure'])).toEqual(expectedResults); }); - xtest('can locate a left to right word in a different position in a two line grid', () => { + + xtest('Can locate a left to right word in a different position in a two line grid', () => { const grid = ['jefblpepre', 'tclojurerm']; const expectedResults = { clojure: { @@ -90,11 +94,13 @@ describe('multi line grids', () => { end: [2, 8], }, }; + const wordSearch = new WordSearch(grid); expect(wordSearch.find(['clojure'])).toEqual(expectedResults); }); - xtest('can locate a left to right word in a three line grid', () => { + + xtest('Can locate a left to right word in a three line grid', () => { const grid = ['camdcimgtc', 'jefblpepre', 'clojurermt']; const expectedResults = { clojure: { @@ -102,12 +108,13 @@ describe('multi line grids', () => { end: [3, 7], }, }; + const wordSearch = new WordSearch(grid); expect(wordSearch.find(['clojure'])).toEqual(expectedResults); }); - xtest('can locate a left to right word in a ten line grid', () => { + xtest('Can locate a left to right word in a ten line grid', () => { const grid = [ 'jefblpepre', 'camdcimgtc', @@ -127,12 +134,13 @@ describe('multi line grids', () => { end: [10, 7], }, }; + const wordSearch = new WordSearch(grid); expect(wordSearch.find(['clojure'])).toEqual(expectedResults); }); - xtest('can locate a left to right word in a different position in a ten line grid', () => { + xtest('Can locate a left to right word in a different position in a ten line grid', () => { const grid = [ 'jefblpepre', 'camdcimgtc', @@ -152,11 +160,13 @@ describe('multi line grids', () => { end: [9, 7], }, }; + const wordSearch = new WordSearch(grid); expect(wordSearch.find(['clojure'])).toEqual(expectedResults); }); - xtest('can locate a different left to right word in a ten line grid', () => { + + xtest('Can locate a different left to right word in a ten line grid', () => { const grid = [ 'jefblpepre', 'camdcimgtc', @@ -169,20 +179,20 @@ describe('multi line grids', () => { 'clojurermt', 'jalaycalmp', ]; + const expectedResults = { scree: { start: [7, 1], end: [7, 5], }, }; + const wordSearch = new WordSearch(grid); expect(wordSearch.find(['scree'])).toEqual(expectedResults); }); -}); -describe('can find multiple words', () => { - xtest('can find two words written left to right', () => { + xtest('Can find two words written left to right', () => { const grid = [ 'aefblpepre', 'camdcimgtc', @@ -196,6 +206,7 @@ describe('can find multiple words', () => { 'clojurermt', 'xjavamtzlp', ]; + const expectedResults = { clojure: { start: [10, 1], @@ -206,14 +217,13 @@ describe('can find multiple words', () => { end: [11, 5], }, }; + const wordSearch = new WordSearch(grid); expect(wordSearch.find(['java', 'clojure'])).toEqual(expectedResults); }); -}); -describe('different directions', () => { - xtest('should locate a single word written right to left', () => { + xtest('Should locate a single word written right to left', () => { const grid = ['rixilelhrs']; const expectedResults = { elixir: { @@ -221,11 +231,13 @@ describe('different directions', () => { end: [1, 1], }, }; + const wordSearch = new WordSearch(grid); expect(wordSearch.find(['elixir'])).toEqual(expectedResults); }); - xtest('should locate multiple words written in different horizontal directions', () => { + + xtest('Should locate multiple words written in different horizontal directions', () => { const grid = [ 'jefblpepre', 'camdcimgtc', @@ -238,6 +250,7 @@ describe('different directions', () => { 'jalaycalmp', 'clojurermt', ]; + const expectedResults = { clojure: { start: [10, 1], @@ -248,14 +261,13 @@ describe('different directions', () => { end: [5, 1], }, }; + const wordSearch = new WordSearch(grid); expect(wordSearch.find(['elixir', 'clojure'])).toEqual(expectedResults); }); -}); -describe('vertical directions', () => { - xtest('should locate words written top to bottom', () => { + xtest('Should locate words written top to bottom', () => { const grid = [ 'jefblpepre', 'camdcimgtc', @@ -268,6 +280,7 @@ describe('vertical directions', () => { 'jalaycalmp', 'clojurermt', ]; + const expectedResults = { clojure: { start: [10, 1], @@ -282,13 +295,55 @@ describe('vertical directions', () => { end: [10, 10], }, }; + const wordSearch = new WordSearch(grid); expect(wordSearch.find(['elixir', 'clojure', 'ecmascript'])).toEqual( - expectedResults + expectedResults, ); }); - xtest('should locate words written bottom to top', () => { + + xtest('Should locate words written bottom to top', () => { + const grid = [ + 'jefblpepre', + 'camdcimgtc', + 'oivokprjsm', + 'pbwasqroua', + 'rixilelhrs', + 'wolcqlirpc', + 'screeaumgr', + 'alxhpburyi', + 'jalaycalmp', + 'clojurermt', + ]; + + const expectedResults = { + clojure: { + start: [10, 1], + end: [10, 7], + }, + elixir: { + start: [5, 6], + end: [5, 1], + }, + ecmascript: { + start: [1, 10], + end: [10, 10], + }, + rust: { + start: [5, 9], + end: [2, 9], + }, + }; + + const wordSearch = new WordSearch(grid); + + expect( + wordSearch.find(['elixir', 'clojure', 'ecmascript', 'rust']), + ).toEqual(expectedResults); + }); + + xtest('Should locate words written top left to bottom right', () => { const grid = [ 'jefblpepre', 'camdcimgtc', @@ -301,6 +356,7 @@ describe('vertical directions', () => { 'jalaycalmp', 'clojurermt', ]; + const expectedResults = { clojure: { start: [10, 1], @@ -318,14 +374,20 @@ describe('vertical directions', () => { start: [5, 9], end: [2, 9], }, + java: { + start: [1, 1], + end: [4, 4], + }, }; + const wordSearch = new WordSearch(grid); expect( - wordSearch.find(['elixir', 'clojure', 'ecmascript', 'rust']) + wordSearch.find(['clojure', 'elixir', 'ecmascript', 'rust', 'java']), ).toEqual(expectedResults); }); - xtest('should locate words written top left to bottom right', () => { + + xtest('Should locate words written bottom right to top left', () => { const grid = [ 'jefblpepre', 'camdcimgtc', @@ -338,6 +400,7 @@ describe('vertical directions', () => { 'jalaycalmp', 'clojurermt', ]; + const expectedResults = { clojure: { start: [10, 1], @@ -359,14 +422,27 @@ describe('vertical directions', () => { start: [1, 1], end: [4, 4], }, + lua: { + start: [9, 8], + end: [7, 6], + }, }; + const wordSearch = new WordSearch(grid); expect( - wordSearch.find(['clojure', 'elixir', 'ecmascript', 'rust', 'java']) + wordSearch.find([ + 'clojure', + 'elixir', + 'ecmascript', + 'rust', + 'java', + 'lua', + ]), ).toEqual(expectedResults); }); - xtest('should locate words written bottom right to top left', () => { + + xtest('Should locate words written bottom left to top right', () => { const grid = [ 'jefblpepre', 'camdcimgtc', @@ -405,7 +481,12 @@ describe('vertical directions', () => { start: [9, 8], end: [7, 6], }, + lisp: { + start: [6, 3], + end: [3, 6], + }, }; + const wordSearch = new WordSearch(grid); expect( @@ -416,10 +497,12 @@ describe('vertical directions', () => { 'rust', 'java', 'lua', - ]) + 'lisp', + ]), ).toEqual(expectedResults); }); - xtest('should locate words written bottom left to top right', () => { + + xtest('Should locate words written top right to bottom left', () => { const grid = [ 'jefblpepre', 'camdcimgtc', @@ -432,6 +515,7 @@ describe('vertical directions', () => { 'jalaycalmp', 'clojurermt', ]; + const expectedResults = { clojure: { start: [10, 1], @@ -461,6 +545,10 @@ describe('vertical directions', () => { start: [6, 3], end: [3, 6], }, + ruby: { + start: [6, 8], + end: [9, 5], + }, }; const wordSearch = new WordSearch(grid); @@ -474,10 +562,12 @@ describe('vertical directions', () => { 'java', 'lua', 'lisp', - ]) + 'ruby', + ]), ).toEqual(expectedResults); }); - xtest('should locate words written top right to bottom left', () => { + + xtest('Should fail to locate a word that is not in the puzzle', () => { const grid = [ 'jefblpepre', 'camdcimgtc', @@ -524,7 +614,9 @@ describe('vertical directions', () => { start: [6, 8], end: [9, 5], }, + haskell: undefined, }; + const wordSearch = new WordSearch(grid); expect( @@ -537,31 +629,61 @@ describe('vertical directions', () => { 'lua', 'lisp', 'ruby', - ]) + 'haskell', + ]), ).toEqual(expectedResults); }); - describe("word doesn't exist", () => { - xtest('should fail to locate a word that is not in the puzzle', () => { - const grid = [ - 'jefblpepre', - 'camdcimgtc', - 'oivokprjsm', - 'pbwasqroua', - 'rixilelhrs', - 'wolcqlirpc', - 'screeaumgr', - 'alxhpburyi', - 'jalaycalmp', - 'clojurermt', - ]; - - const expectedResults = { - fail: undefined, - }; - const wordSearch = new WordSearch(grid); - - expect(wordSearch.find(['fail'])).toEqual(expectedResults); - }); + xtest('Should fail to locate words that are not on horizontal, vertical, or diagonal lines', () => { + const grid = ['abc', 'def']; + + const expectedResults = { + aef: undefined, + ced: undefined, + abf: undefined, + cbd: undefined, + }; + + const wordSearch = new WordSearch(grid); + + expect(wordSearch.find(['aef', 'ced', 'abf', 'cbd'])).toEqual( + expectedResults, + ); + }); + + xtest('Should not concatenate different lines to find a horizontal word', () => { + const grid = ['abceli', 'xirdfg']; + + const expectedResults = { + elixir: undefined, + }; + + const wordSearch = new WordSearch(grid); + + expect(wordSearch.find(['elixir'])).toEqual(expectedResults); + }); + + xtest('Should not wrap around horizontally to find a word', () => { + const grid = ['silabcdefp']; + + const expectedResults = { + lisp: undefined, + }; + + const wordSearch = new WordSearch(grid); + + expect(wordSearch.find(['lisp'])).toEqual(expectedResults); + }); + + xtest('Should not wrap around vertically to find a word', () => { + const grid = ['s', 'u', 'r', 'a', 'b', 'c', 't']; + + const expectedResults = { + rust: undefined, + }; + + const wordSearch = new WordSearch(grid); + + expect(wordSearch.find(['rust'])).toEqual(expectedResults); }); }); diff --git a/exercises/practice/wordy/.docs/instructions.md b/exercises/practice/wordy/.docs/instructions.md index 0c72618644..aafb9ee54b 100644 --- a/exercises/practice/wordy/.docs/instructions.md +++ b/exercises/practice/wordy/.docs/instructions.md @@ -40,8 +40,7 @@ Now, perform the other three operations. Handle a set of operations, in sequence. -Since these are verbal word problems, evaluate the expression from -left-to-right, _ignoring the typical order of operations._ +Since these are verbal word problems, evaluate the expression from left-to-right, _ignoring the typical order of operations._ > What is 5 plus 13 plus 6? @@ -58,11 +57,3 @@ The parser should reject: - Unsupported operations ("What is 52 cubed?") - Non-math questions ("Who is the President of the United States") - Word problems with invalid syntax ("What is 1 plus plus 2?") - -## Bonus — Exponentials - -If you'd like, handle exponentials. - -> What is 2 raised to the 5th power? - -32 diff --git a/exercises/practice/wordy/.eslintrc b/exercises/practice/wordy/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/wordy/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/wordy/.gitignore b/exercises/practice/wordy/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/wordy/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/wordy/.meta/config.json b/exercises/practice/wordy/.meta/config.json index cfb40eb280..b3381e5f81 100644 --- a/exercises/practice/wordy/.meta/config.json +++ b/exercises/practice/wordy/.meta/config.json @@ -1,8 +1,10 @@ { - "blurb": "Parse and evaluate simple math word problems returning the answer as an integer.", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "hyuko21", + "jagdish-15", "msomji", "ovidiu141", "rchavarria", @@ -11,10 +13,23 @@ "SleeplessByte" ], "files": { - "solution": ["wordy.js"], - "test": ["wordy.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "wordy.js" + ], + "test": [ + "wordy.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Parse and evaluate simple math word problems returning the answer as an integer.", "source": "Inspired by one of the generated questions in the Extreme Startup game.", - "source_url": "https://github.com/rchatley/extreme_startup" + "source_url": "https://github.com/rchatley/extreme_startup", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/wordy/.meta/tests.toml b/exercises/practice/wordy/.meta/tests.toml index 912d576009..a0a83ed0b9 100644 --- a/exercises/practice/wordy/.meta/tests.toml +++ b/exercises/practice/wordy/.meta/tests.toml @@ -1,13 +1,32 @@ -# This is an auto-generated file. Regular comments will be removed when this -# file is regenerated. Regenerating will not touch any manually added keys, -# so comments can be added in a "comment" key. +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. [88bf4b28-0de3-4883-93c7-db1b14aa806e] description = "just a number" +[18983214-1dfc-4ebd-ac77-c110dde699ce] +description = "just a zero" + +[607c08ee-2241-4288-916d-dae5455c87e6] +description = "just a negative number" + [bb8c655c-cf42-4dfc-90e0-152fcfd8d4e0] description = "addition" +[bb9f2082-171c-46ad-ad4e-c3f72087c1b5] +description = "addition with a left hand zero" + +[6fa05f17-405a-4742-80ae-5d1a8edb0d5d] +description = "addition with a right hand zero" + [79e49e06-c5ae-40aa-a352-7a3a01f70015] description = "more addition" diff --git a/exercises/practice/wordy/babel.config.js b/exercises/practice/wordy/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/wordy/babel.config.js +++ b/exercises/practice/wordy/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/wordy/eslint.config.mjs b/exercises/practice/wordy/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/wordy/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/wordy/jest.config.js b/exercises/practice/wordy/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/wordy/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/wordy/package.json b/exercises/practice/wordy/package.json index ed723b879f..b564859909 100644 --- a/exercises/practice/wordy/package.json +++ b/exercises/practice/wordy/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/wordy" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/wordy/wordy.js b/exercises/practice/wordy/wordy.js index 9ee42c7703..f7c398a84a 100644 --- a/exercises/practice/wordy/wordy.js +++ b/exercises/practice/wordy/wordy.js @@ -4,5 +4,5 @@ // export const answer = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/wordy/wordy.spec.js b/exercises/practice/wordy/wordy.spec.js index d6653af692..1be21e3dca 100644 --- a/exercises/practice/wordy/wordy.spec.js +++ b/exercises/practice/wordy/wordy.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { answer } from './wordy'; describe('Wordy', () => { @@ -5,10 +6,26 @@ describe('Wordy', () => { expect(answer('What is 5?')).toEqual(5); }); + xtest('just a zero', () => { + expect(answer('What is 0?')).toEqual(0); + }); + + xtest('just a negative number', () => { + expect(answer('What is -123?')).toEqual(-123); + }); + xtest('addition', () => { expect(answer('What is 1 plus 1?')).toEqual(2); }); + xtest('addition with a left hand zero', () => { + expect(answer('What is 0 plus 2?')).toEqual(2); + }); + + xtest('addition with a right hand zero', () => { + expect(answer('What is 3 plus 0?')).toEqual(3); + }); + xtest('more addition', () => { expect(answer('What is 53 plus 2?')).toEqual(55); }); @@ -63,13 +80,13 @@ describe('Wordy', () => { xtest('unknown operation', () => { expect(() => answer('What is 52 cubed?')).toThrow( - new Error('Unknown operation') + new Error('Unknown operation'), ); }); xtest('Non math question', () => { expect(() => answer('Who is the President of the United States?')).toThrow( - new Error('Unknown operation') + new Error('Unknown operation'), ); }); @@ -83,25 +100,25 @@ describe('Wordy', () => { xtest('reject two operations in a row', () => { expect(() => answer('What is 1 plus plus 2?')).toThrow( - new Error('Syntax error') + new Error('Syntax error'), ); }); xtest('reject two numbers in a row', () => { expect(() => answer('What is 1 plus 2 1?')).toThrow( - new Error('Syntax error') + new Error('Syntax error'), ); }); xtest('reject postfix notation', () => { expect(() => answer('What is 1 2 plus?')).toThrow( - new Error('Syntax error') + new Error('Syntax error'), ); }); xtest('reject prefix notation', () => { expect(() => answer('What is plus 1 2?')).toThrow( - new Error('Syntax error') + new Error('Syntax error'), ); }); }); diff --git a/exercises/practice/yacht/.docs/instructions.md b/exercises/practice/yacht/.docs/instructions.md index 584ef8df93..519b7a68b8 100644 --- a/exercises/practice/yacht/.docs/instructions.md +++ b/exercises/practice/yacht/.docs/instructions.md @@ -1,12 +1,12 @@ # Instructions -# Score a single throw of dice in _Yacht_ +Given five dice and a category, calculate the score of the dice for that category. -The dice game [Yacht]() is from -the same family as Poker Dice, Generala and particularly Yahtzee, of which it -is a precursor. In the game, five dice are rolled and the result can be entered -in any of twelve categories. The score of a throw of the dice depends on -category chosen. +~~~~exercism/note +You'll always be presented with five dice. +Each dice's value will be between one and six inclusively. +The dice may be unordered. +~~~~ ## Scores in Yacht @@ -25,14 +25,6 @@ category chosen. | Choice | Sum of the dice | Any combination | 2 3 3 4 6 scores 18 | | Yacht | 50 points | All five dice showing the same face | 4 4 4 4 4 scores 50 | -If the dice do not satisfy the requirements of a category, the score is zero. -If, for example, _Four Of A Kind_ is entered in the _Yacht_ category, zero -points are scored. A _Yacht_ scores zero if entered in the _Full House_ category. - -## Task - -Given a list of values for five dice and a category, your solution should return -the score of the dice for that category. If the dice do not satisfy the requirements -of the category your solution should return 0. You can assume that five values -will always be presented, and the value of each will be between one and six -inclusively. You should not assume that the dice are ordered. +If the dice do **not** satisfy the requirements of a category, the score is zero. +If, for example, _Four Of A Kind_ is entered in the _Yacht_ category, zero points are scored. +A _Yacht_ scores zero if entered in the _Full House_ category. diff --git a/exercises/practice/yacht/.docs/introduction.md b/exercises/practice/yacht/.docs/introduction.md new file mode 100644 index 0000000000..5b541f5625 --- /dev/null +++ b/exercises/practice/yacht/.docs/introduction.md @@ -0,0 +1,11 @@ +# Introduction + +Each year, something new is "all the rage" in your high school. +This year it is a dice game: [Yacht][yacht]. + +The game of Yacht is from the same family as Poker Dice, Generala and particularly Yahtzee, of which it is a precursor. +The game consists of twelve rounds. +In each, five dice are rolled and the player chooses one of twelve categories. +The chosen category is then used to score the throw of the dice. + +[yacht]: https://en.wikipedia.org/wiki/Yacht_(dice_game) diff --git a/exercises/practice/yacht/.eslintrc b/exercises/practice/yacht/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/yacht/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/yacht/.gitignore b/exercises/practice/yacht/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/yacht/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/yacht/.meta/config.json b/exercises/practice/yacht/.meta/config.json index b8e4026168..021580ef1e 100644 --- a/exercises/practice/yacht/.meta/config.json +++ b/exercises/practice/yacht/.meta/config.json @@ -1,12 +1,28 @@ { - "blurb": "Score a single throw of dice in the game Yacht", - "authors": ["ovidiu141"], - "contributors": ["SleeplessByte"], + "authors": [ + "ovidiu141" + ], + "contributors": [ + "SleeplessByte" + ], "files": { - "solution": ["yacht.js"], - "test": ["yacht.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "yacht.js" + ], + "test": [ + "yacht.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, - "source": "James Kilfiger, using wikipedia", - "source_url": "https://en.wikipedia.org/wiki/Yacht_(dice_game)" + "blurb": "Score a single throw of dice in the game Yacht.", + "source": "James Kilfiger, using Wikipedia", + "source_url": "https://en.wikipedia.org/wiki/Yacht_(dice_game)", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/yacht/.meta/proof.ci.js b/exercises/practice/yacht/.meta/proof.ci.js index 9ba5a965ea..fea2bd080a 100644 --- a/exercises/practice/yacht/.meta/proof.ci.js +++ b/exercises/practice/yacht/.meta/proof.ci.js @@ -35,7 +35,7 @@ const getScoreForTheLittleStraightCategory = (dices) => { counterArray, 0, counterArray.length - 1, - 1 + 1, ); return isLittleStraight ? 30 : 0; }; @@ -46,7 +46,7 @@ const getScoreForTheBigStraightCategory = (dices) => { counterArray, 1, counterArray.length, - 1 + 1, ); return isBigStraight ? 30 : 0; }; diff --git a/exercises/practice/yacht/.meta/tests.toml b/exercises/practice/yacht/.meta/tests.toml index 5aa349a8e6..e6003ee54c 100644 --- a/exercises/practice/yacht/.meta/tests.toml +++ b/exercises/practice/yacht/.meta/tests.toml @@ -29,6 +29,9 @@ description = "Yacht counted as threes" [464fc809-96ed-46e4-acb8-d44e302e9726] description = "Yacht of 3s counted as fives" +[d054227f-3a71-4565-a684-5c7e621ec1e9] +description = "Fives" + [e8a036e0-9d21-443a-8b5f-e15a9e19a761] description = "Sixes" diff --git a/exercises/practice/yacht/babel.config.js b/exercises/practice/yacht/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/yacht/babel.config.js +++ b/exercises/practice/yacht/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/yacht/eslint.config.mjs b/exercises/practice/yacht/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/yacht/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/yacht/jest.config.js b/exercises/practice/yacht/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/yacht/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/yacht/package.json b/exercises/practice/yacht/package.json index d6865215ae..dc04676d05 100644 --- a/exercises/practice/yacht/package.json +++ b/exercises/practice/yacht/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/yacht" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/yacht/yacht.js b/exercises/practice/yacht/yacht.js index 23edbb5fad..1735984214 100644 --- a/exercises/practice/yacht/yacht.js +++ b/exercises/practice/yacht/yacht.js @@ -4,5 +4,5 @@ // export const score = () => { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); }; diff --git a/exercises/practice/yacht/yacht.spec.js b/exercises/practice/yacht/yacht.spec.js index 6c6d2417e9..1800c90d28 100644 --- a/exercises/practice/yacht/yacht.spec.js +++ b/exercises/practice/yacht/yacht.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { score } from './yacht'; describe('Yacht', () => { @@ -37,6 +38,10 @@ describe('Yacht', () => { expect(score([3, 3, 3, 3, 3], 'fives')).toEqual(0); }); + xtest('Fives', () => { + expect(score([1, 5, 3, 5, 3], 'fives')).toEqual(10); + }); + xtest('Sixes', () => { expect(score([2, 3, 4, 5, 6], 'sixes')).toEqual(6); }); diff --git a/exercises/practice/zebra-puzzle/.docs/instructions.md b/exercises/practice/zebra-puzzle/.docs/instructions.md index 806e264834..aedce9b25e 100644 --- a/exercises/practice/zebra-puzzle/.docs/instructions.md +++ b/exercises/practice/zebra-puzzle/.docs/instructions.md @@ -1,26 +1,32 @@ -# Description +# Instructions -Solve the zebra puzzle. +Your task is to solve the Zebra Puzzle to find the answer to these two questions: + +- Which of the residents drinks water? +- Who owns the zebra? + +## Puzzle + +The following 15 statements are all known to be true: 1. There are five houses. 2. The Englishman lives in the red house. 3. The Spaniard owns the dog. -4. Coffee is drunk in the green house. +4. The person in the green house drinks coffee. 5. The Ukrainian drinks tea. 6. The green house is immediately to the right of the ivory house. -7. The Old Gold smoker owns snails. -8. Kools are smoked in the yellow house. -9. Milk is drunk in the middle house. +7. The snail owner likes to go dancing. +8. The person in the yellow house is a painter. +9. The person in the middle house drinks milk. 10. The Norwegian lives in the first house. -11. The man who smokes Chesterfields lives in the house next to the man with the fox. -12. Kools are smoked in the house next to the house where the horse is kept. -13. The Lucky Strike smoker drinks orange juice. -14. The Japanese smokes Parliaments. +11. The person who enjoys reading lives in the house next to the person with the fox. +12. The painter's house is next to the house with the horse. +13. The person who plays football drinks orange juice. +14. The Japanese person plays chess. 15. The Norwegian lives next to the blue house. -Each of the five houses is painted a different color, and their -inhabitants are of different national extractions, own different pets, -drink different beverages and smoke different brands of cigarettes. +Additionally, each of the five houses is painted a different color, and their inhabitants are of different national extractions, own different pets, drink different beverages and engage in different hobbies. -Which of the residents drinks water? -Who owns the zebra? +~~~~exercism/note +There are 24 billion (5!⁵ = 24,883,200,000) possible solutions, so try ruling out as many solutions as possible. +~~~~ diff --git a/exercises/practice/zebra-puzzle/.docs/introduction.md b/exercises/practice/zebra-puzzle/.docs/introduction.md new file mode 100644 index 0000000000..bbcaa6fd20 --- /dev/null +++ b/exercises/practice/zebra-puzzle/.docs/introduction.md @@ -0,0 +1,15 @@ +# Introduction + +The Zebra Puzzle is a famous logic puzzle in which there are five houses, each painted a different color. +The houses have different inhabitants, who have different nationalities, own different pets, drink different beverages and enjoy different hobbies. + +To help you solve the puzzle, you're given 15 statements describing the solution. +However, only by combining the information in _all_ statements will you be able to find the solution to the puzzle. + +~~~~exercism/note +The Zebra Puzzle is a [Constraint satisfaction problem (CSP)][constraint-satisfaction-problem]. +In such a problem, you have a set of possible values and a set of constraints that limit which values are valid. +Another well-known CSP is Sudoku. + +[constraint-satisfaction-problem]: https://en.wikipedia.org/wiki/Constraint_satisfaction_problem +~~~~ diff --git a/exercises/practice/zebra-puzzle/.eslintrc b/exercises/practice/zebra-puzzle/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/zebra-puzzle/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/zebra-puzzle/.gitignore b/exercises/practice/zebra-puzzle/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/zebra-puzzle/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/zebra-puzzle/.meta/config.json b/exercises/practice/zebra-puzzle/.meta/config.json index 4793d3ec70..ff8a64921f 100644 --- a/exercises/practice/zebra-puzzle/.meta/config.json +++ b/exercises/practice/zebra-puzzle/.meta/config.json @@ -1,12 +1,25 @@ { - "blurb": "Solve the zebra puzzle.", - "authors": ["lpizzinidev"], - "contributors": [], + "authors": [ + "lpizzinidev" + ], "files": { - "solution": ["zebra-puzzle.js"], - "test": ["zebra-puzzle.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "zebra-puzzle.js" + ], + "test": [ + "zebra-puzzle.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] }, + "blurb": "Solve the zebra puzzle.", "source": "Wikipedia", - "source_url": "https://en.wikipedia.org/wiki/Zebra_Puzzle" + "source_url": "https://en.wikipedia.org/wiki/Zebra_Puzzle", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false + } } diff --git a/exercises/practice/zebra-puzzle/.meta/proof.ci.js b/exercises/practice/zebra-puzzle/.meta/proof.ci.js index 415c9f0189..8916a1964f 100644 --- a/exercises/practice/zebra-puzzle/.meta/proof.ci.js +++ b/exercises/practice/zebra-puzzle/.meta/proof.ci.js @@ -56,7 +56,7 @@ export class ZebraPuzzle { for (let i = 0; i < arr.length; i++) { const rest = this.permutateValues( - arr.slice(0, i).concat(arr.slice(i + 1)) + arr.slice(0, i).concat(arr.slice(i + 1)), ); if (!rest.length) { diff --git a/exercises/practice/zebra-puzzle/babel.config.js b/exercises/practice/zebra-puzzle/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/zebra-puzzle/babel.config.js +++ b/exercises/practice/zebra-puzzle/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/zebra-puzzle/eslint.config.mjs b/exercises/practice/zebra-puzzle/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/zebra-puzzle/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/zebra-puzzle/jest.config.js b/exercises/practice/zebra-puzzle/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/zebra-puzzle/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/zebra-puzzle/package.json b/exercises/practice/zebra-puzzle/package.json index 46f75e3a79..de721ef8aa 100644 --- a/exercises/practice/zebra-puzzle/package.json +++ b/exercises/practice/zebra-puzzle/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/zebra-puzzle" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/zebra-puzzle/zebra-puzzle.js b/exercises/practice/zebra-puzzle/zebra-puzzle.js index 63fbd056c1..dd9a645573 100644 --- a/exercises/practice/zebra-puzzle/zebra-puzzle.js +++ b/exercises/practice/zebra-puzzle/zebra-puzzle.js @@ -5,16 +5,14 @@ export class ZebraPuzzle { constructor() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } waterDrinker() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } zebraOwner() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } - -module.exports = ZebraPuzzle; diff --git a/exercises/practice/zebra-puzzle/zebra-puzzle.spec.js b/exercises/practice/zebra-puzzle/zebra-puzzle.spec.js index 336ef71b4a..e61823d6ac 100644 --- a/exercises/practice/zebra-puzzle/zebra-puzzle.spec.js +++ b/exercises/practice/zebra-puzzle/zebra-puzzle.spec.js @@ -1,3 +1,4 @@ +import { describe, expect, test, xtest } from '@jest/globals'; import { ZebraPuzzle } from './zebra-puzzle'; describe('Zebra puzzle', () => { @@ -5,7 +6,7 @@ describe('Zebra puzzle', () => { const puzzle = new ZebraPuzzle(); expect(puzzle.waterDrinker()).toEqual('Norwegian'); }); - test('resident who owns zebra', () => { + xtest('resident who owns zebra', () => { const puzzle = new ZebraPuzzle(); expect(puzzle.zebraOwner()).toEqual('Japanese'); }); diff --git a/exercises/practice/zipper/.docs/instructions.md b/exercises/practice/zipper/.docs/instructions.md index 10fd77b644..5445db0035 100644 --- a/exercises/practice/zipper/.docs/instructions.md +++ b/exercises/practice/zipper/.docs/instructions.md @@ -2,13 +2,10 @@ Creating a zipper for a binary tree. -[Zippers](https://en.wikipedia.org/wiki/Zipper_%28data_structure%29) are -a purely functional way of navigating within a data structure and -manipulating it. They essentially contain a data structure and a -pointer into that data structure (called the focus). +[Zippers][zipper] are a purely functional way of navigating within a data structure and manipulating it. +They essentially contain a data structure and a pointer into that data structure (called the focus). -For example given a rose tree (where each node contains a value and a -list of child nodes) a zipper might support these operations: +For example given a rose tree (where each node contains a value and a list of child nodes) a zipper might support these operations: - `from_tree` (get a zipper out of a rose tree, the focus is on the root node) - `to_tree` (get the rose tree out of the zipper) @@ -26,3 +23,5 @@ list of child nodes) a zipper might support these operations: - `delete` (removes the focus node and all subtrees, focus moves to the `next` node if possible otherwise to the `prev` node if possible, otherwise to the parent node, returns a new zipper) + +[zipper]: https://en.wikipedia.org/wiki/Zipper_%28data_structure%29 diff --git a/exercises/practice/zipper/.eslintrc b/exercises/practice/zipper/.eslintrc deleted file mode 100644 index 1d4446029c..0000000000 --- a/exercises/practice/zipper/.eslintrc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "root": true, - "extends": "@exercism/eslint-config-javascript", - "env": { - "jest": true - }, - "overrides": [ - { - "files": [".meta/proof.ci.js", ".meta/exemplar.js", "*.spec.js"], - "excludedFiles": ["custom.spec.js"], - "extends": "@exercism/eslint-config-javascript/maintainers" - } - ] -} diff --git a/exercises/practice/zipper/.gitignore b/exercises/practice/zipper/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/zipper/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/zipper/.meta/config.json b/exercises/practice/zipper/.meta/config.json index 354c1593d9..0b367426c2 100644 --- a/exercises/practice/zipper/.meta/config.json +++ b/exercises/practice/zipper/.meta/config.json @@ -1,17 +1,32 @@ { - "blurb": "Creating a zipper for a binary tree.", - "authors": ["matthewmorgan"], + "authors": [ + "matthewmorgan" + ], "contributors": [ "felbit", "ganderzz", "hyuko21", + "jagdish-15", "joshgoebel", "SleeplessByte", "tejasbubane" ], "files": { - "solution": ["zipper.js"], - "test": ["zipper.spec.js"], - "example": [".meta/proof.ci.js"] + "solution": [ + "zipper.js" + ], + "test": [ + "zipper.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] + }, + "blurb": "Creating a zipper for a binary tree.", + "custom": { + "version.tests.compatibility": "jest-27", + "flag.tests.task-per-describe": false, + "flag.tests.may-run-long": false, + "flag.tests.includes-optional": false } } diff --git a/exercises/practice/zipper/.meta/proof.ci.js b/exercises/practice/zipper/.meta/proof.ci.js index 9aff85b7fd..e020141c5e 100644 --- a/exercises/practice/zipper/.meta/proof.ci.js +++ b/exercises/practice/zipper/.meta/proof.ci.js @@ -43,7 +43,7 @@ export class Zipper { return new Zipper( this.tree.left, - [['left', this.tree.value, this.tree.right]].concat(this.trail) + [['left', this.tree.value, this.tree.right]].concat(this.trail), ); } @@ -52,7 +52,7 @@ export class Zipper { return new Zipper( this.tree.right, - [['right', this.tree.value, this.tree.left]].concat(this.trail) + [['right', this.tree.value, this.tree.left]].concat(this.trail), ); } @@ -66,21 +66,21 @@ export class Zipper { setValue(value) { return new Zipper( { value, left: this.tree.left, right: this.tree.right }, - this.trail + this.trail, ); } setLeft(left) { return new Zipper( { value: this.tree.value, left, right: this.tree.right }, - this.trail + this.trail, ); } setRight(right) { return new Zipper( { value: this.tree.value, left: this.tree.left, right }, - this.trail + this.trail, ); } } diff --git a/exercises/practice/zipper/.meta/tests.toml b/exercises/practice/zipper/.meta/tests.toml index 80e629ad6e..e93932b173 100644 --- a/exercises/practice/zipper/.meta/tests.toml +++ b/exercises/practice/zipper/.meta/tests.toml @@ -27,6 +27,9 @@ description = "traversing up from top" [b8505f6a-aed4-4c2e-824f-a0ed8570d74b] description = "left, right, and up" +[b9aa8d54-07b7-4bfd-ab6b-7ff7f35930b6] +description = "test ability to descend multiple levels and return" + [47df1a27-b709-496e-b381-63a03b82ea5f] description = "set_value" diff --git a/exercises/practice/zipper/babel.config.js b/exercises/practice/zipper/babel.config.js index b5285b9ec0..a638497df1 100644 --- a/exercises/practice/zipper/babel.config.js +++ b/exercises/practice/zipper/babel.config.js @@ -1,15 +1,4 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - targets: { - node: 'current', - }, - useBuiltIns: 'entry', - corejs: '3.20', - }, - ], - ], - plugins: ['@babel/plugin-syntax-bigint'], + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], }; diff --git a/exercises/practice/zipper/eslint.config.mjs b/exercises/practice/zipper/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/zipper/eslint.config.mjs @@ -0,0 +1,45 @@ +// @ts-check + +import config from '@exercism/eslint-config-javascript'; +import maintainersConfig from '@exercism/eslint-config-javascript/maintainers.mjs'; + +import globals from 'globals'; + +export default [ + ...config, + ...maintainersConfig, + { + files: maintainersConfig[1].files, + rules: { + 'jest/expect-expect': ['warn', { assertFunctionNames: ['expect*'] }], + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + // <> + { + ignores: [ + // # Protected or generated + '/.appends/**/*', + '/.github/**/*', + '/.vscode/**/*', + + // # Binaries + '/bin/*', + + // # Configuration + '/config', + '/babel.config.js', + + // # Typings + '/exercises/**/global.d.ts', + '/exercises/**/env.d.ts', + ], + }, +]; diff --git a/exercises/practice/zipper/jest.config.js b/exercises/practice/zipper/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/zipper/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/exercises/practice/zipper/package.json b/exercises/practice/zipper/package.json index 42402392d4..a732e8a7f9 100644 --- a/exercises/practice/zipper/package.json +++ b/exercises/practice/zipper/package.json @@ -10,22 +10,25 @@ "directory": "exercises/practice/zipper" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "core-js": "^3.20.3", - "eslint": "^7.32.0", - "jest": "^26.6.3" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^22.15.29", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.2.0", + "jest": "^29.7.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/exercises/practice/zipper/zipper.js b/exercises/practice/zipper/zipper.js index bbd19d1dd4..116087e052 100644 --- a/exercises/practice/zipper/zipper.js +++ b/exercises/practice/zipper/zipper.js @@ -5,42 +5,42 @@ export class Zipper { constructor() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } static fromTree() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } toTree() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } value() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } left() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } right() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } up() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } setValue() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } setLeft() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } setRight() { - throw new Error('Remove this statement and implement this function'); + throw new Error('Remove this line and implement the function'); } } diff --git a/exercises/practice/zipper/zipper.spec.js b/exercises/practice/zipper/zipper.spec.js index 235fc80c5a..df71114441 100644 --- a/exercises/practice/zipper/zipper.spec.js +++ b/exercises/practice/zipper/zipper.spec.js @@ -1,3 +1,4 @@ +import { beforeEach, describe, expect, test, xtest } from '@jest/globals'; import { Zipper } from './zipper'; function bt(value, left, right) { @@ -49,6 +50,10 @@ describe('Zipper', () => { expect(zipper.left().up().right().up().left().right().value()).toEqual(3); }); + xtest('ability to descend multiple levels and return', () => { + expect(zipper.left().right().up().up().value()).toEqual(1); + }); + xtest('setValue', () => { expect(zipper.left().setValue(5).toTree()).toEqual(t2); }); diff --git a/exercises/shared/.docs/help.md b/exercises/shared/.docs/help.md index 2f94f7c4aa..b2bb9f4122 100644 --- a/exercises/shared/.docs/help.md +++ b/exercises/shared/.docs/help.md @@ -2,7 +2,6 @@ To get help if you're having trouble, you can use one of the following resources: -- [Gitter](https://gitter.im/exercism/support) is Exercism's Gitter room; go here to get support and ask questions if you face any issues with downloading or submitting your exercises. - [/r/javascript](https://www.reddit.com/r/javascript) is the Javascript subreddit. - [StackOverflow](https://stackoverflow.com/questions/tagged/javascript+exercism) can be used to search for your problem and see if it has been answered already. You can also ask and answer questions. - [Github issue tracker](https://github.com/exercism/javascript/issues) is where we track our development and maintainance of Javascript exercises in exercism. But if none of the above links help you, feel free to post an issue here. diff --git a/exercises/shared/.docs/representations.md b/exercises/shared/.docs/representations.md new file mode 100644 index 0000000000..4c495a1e41 --- /dev/null +++ b/exercises/shared/.docs/representations.md @@ -0,0 +1,22 @@ +## JavaScript representations + +The [JavaScript representer][github-javascript-representer] applies the following normalizations: + +- [All comments are removed][docs-representer-normalizations-comments] +- [All whitespace is normalized][docs-representer-normalizations-whitespace] +- [Identifiers are normalized to a placeholder value][docs-representer-normalizations-identifiers] + +[github-javascript-representer]: https://github.com/exercism/javascript-representer +[docs-representer-normalizations-comments]: https://exercism.org/docs/tracks/javascript/representer-normalizations#h-remove-comments +[docs-representer-normalizations-whitespace]: https://exercism.org/docs/tracks/javascript/representer-normalizations#h-normalize-whitespace +[docs-representer-normalizations-identifiers]: https://exercism.org/docs/tracks/javascript/representer-normalizations#h-normalize-identifiers + +### Before you submit + +Please check the following things: + +1. You don't duplicate analyzer feedback +2. You check the "examples" tab in the submit dialog and see if the feedback makes sense for _all_ tabs. +3. You check that you have not referred to whitespace or comments +4. You check that you don't refer to function names, or variable names as they appear in the solution, but rather use the mapping provided (or leave names out). + Only _exported_ names (required by the tests) you can safely refer to because these are always the same for everyone. diff --git a/exercises/shared/.docs/tests.md b/exercises/shared/.docs/tests.md index c7f3fdb47a..45f813eb55 100644 --- a/exercises/shared/.docs/tests.md +++ b/exercises/shared/.docs/tests.md @@ -9,11 +9,13 @@ Go through the setup [instructions for JavaScript][docs-exercism-javascript] to Install assignment dependencies: ```shell -# Using npm -npm install +corepack pnpm install +``` + +If `corepack` complains about not being enabled, you can do so by running: -# Alternatively using yarn -yarn +```bash +corepack enable pnpm ``` ## Making the test suite pass @@ -22,11 +24,7 @@ All exercises come with a test suite to help you validate your solution before s You can execute these tests by opening a command prompt in the exercise's directory, and then running: ```bash -# Using npm -npm test - -# Alternatively using yarn -yarn test +corepack pnpm test ``` In some test suites all tests but the first have been skipped. diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,22 @@ +module.exports = { + verbose: true, + projects: [''], + testMatch: [ + '**/__tests__/**/*.[jt]s?(x)', + '**/test/**/*.[jt]s?(x)', + '**/?(*.)+(spec|test).[jt]s?(x)', + ], + testPathIgnorePatterns: [ + '/(?:production_)?node_modules/', + '.d.ts$', + '/test/fixtures', + '/test/helpers', + '__mocks__', + ], + transform: { + '^.+\\.[jt]sx?$': 'babel-jest', + }, + moduleNameMapper: { + '^(\\.\\/.+)\\.js$': '$1', + }, +}; diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 982066d846..0000000000 --- a/package-lock.json +++ /dev/null @@ -1,11452 +0,0 @@ -{ - "name": "@exercism/javascript", - "requires": true, - "lockfileVersion": 1, - "dependencies": { - "@babel/cli": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.16.8.tgz", - "integrity": "sha512-FTKBbxyk5TclXOGmwYyqelqP5IF6hMxaeJskd85jbR5jBfYlwqgwAbJwnixi1ZBbTqKfFuAA95mdmUFeSRwyJA==", - "dev": true, - "requires": { - "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3", - "chokidar": "^3.4.0", - "commander": "^4.0.1", - "convert-source-map": "^1.1.0", - "fs-readdir-recursive": "^1.1.0", - "glob": "^7.0.0", - "make-dir": "^2.1.0", - "slash": "^2.0.0", - "source-map": "^0.5.0" - } - }, - "@babel/code-frame": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", - "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.0.0" - } - }, - "@babel/compat-data": { - "version": "7.16.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.4.tgz", - "integrity": "sha512-1o/jo7D+kC9ZjHX5v+EHrdjl3PhxMrLSOTGsOdHJ+KL8HCaEK6ehrVL2RS6oHDZp+L7xLirLrPmQtEng769J/Q==", - "dev": true - }, - "@babel/core": { - "version": "7.16.12", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.12.tgz", - "integrity": "sha512-dK5PtG1uiN2ikk++5OzSYsitZKny4wOCD0nrO4TqnW4BVBTQ2NGS3NgilvT/TEyxTST7LNyWV/T4tXDoD3fOgg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.16.8", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helpers": "^7.16.7", - "@babel/parser": "^7.16.12", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.10", - "@babel/types": "^7.16.8", - "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" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.16.7" - } - }, - "@babel/generator": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz", - "integrity": "sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.8", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", - "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", - "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", - "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true - }, - "@babel/highlight": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", - "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.16.12", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.12.tgz", - "integrity": "sha512-VfaV15po8RiZssrkPweyvbGVSe4x2y+aciFCgn0n0/SJMR22cwofRV1mtnJQYcSB1wUTaA/X1LnA3es66MCO5A==", - "dev": true - }, - "@babel/template": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/traverse": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.10.tgz", - "integrity": "sha512-yzuaYXoRJBGMlBhsMJoUW7G1UmSb/eXr/JHYM/MsOJgavJibLwASijW7oXBdw3NQ6T0bW7Ty5P/VarOs9cHmqw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.16.8", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.16.10", - "@babel/types": "^7.16.8", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", - "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "@babel/eslint-parser": { - "version": "7.15.4", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.15.4.tgz", - "integrity": "sha512-hPMIAmGNbmQzXJIo2P43Zj9UhRmGev5f9nqdBFOWNGDGh6XKmjby79woBvg6y0Jur6yRfQBneDbUQ8ZVc1krFw==", - "dev": true, - "requires": { - "eslint-scope": "^5.1.1", - "eslint-visitor-keys": "^2.1.0", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "@babel/eslint-plugin": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/eslint-plugin/-/eslint-plugin-7.14.5.tgz", - "integrity": "sha512-nzt/YMnOOIRikvSn2hk9+W2omgJBy6U8TN0R+WTTmqapA+HnZTuviZaketdTE9W7/k/+E/DfZlt1ey1NSE39pg==", - "dev": true, - "requires": { - "eslint-rule-composer": "^0.3.0" - } - }, - "@babel/generator": { - "version": "7.8.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.8.tgz", - "integrity": "sha512-HKyUVu69cZoclptr8t8U5b6sx6zoWjh8jiUhnuj3MpZuKT2dJ8zPTuiy31luq32swhI0SpwItCIlU8XW7BZeJg==", - "dev": true, - "requires": { - "@babel/types": "^7.8.7", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", - "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true - }, - "@babel/types": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", - "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" - } - } - } - }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.16.7.tgz", - "integrity": "sha512-C6FdbRaxYjwVu/geKW4ZeQ0Q31AftgRcdSnZ5/jsH6BzCJbtvXvhpfkbkThYSuutZA7nCXpPR6AD9zd1dprMkA==", - "dev": true, - "requires": { - "@babel/helper-explode-assignable-expression": "^7.16.7", - "@babel/types": "^7.16.7" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true - }, - "@babel/types": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", - "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" - } - } - } - }, - "@babel/helper-compilation-targets": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz", - "integrity": "sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.16.4", - "@babel/helper-validator-option": "^7.16.7", - "browserslist": "^4.17.5", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "@babel/helper-create-class-features-plugin": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.16.10.tgz", - "integrity": "sha512-wDeej0pu3WN/ffTxMNCPW5UCiOav8IcLRxSIyp/9+IF2xJUM9h/OYjg0IJLHaL6F8oU8kqMz9nc1vryXhMsgXg==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-member-expression-to-functions": "^7.16.7", - "@babel/helper-optimise-call-expression": "^7.16.7", - "@babel/helper-replace-supers": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.16.7" - } - }, - "@babel/helper-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", - "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", - "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", - "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true - }, - "@babel/highlight": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", - "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.10.tgz", - "integrity": "sha512-Sm/S9Or6nN8uiFsQU1yodyDW3MWXQhFeqzMPM+t8MJjM+pLsnFVxFZzkpXKvUXh+Gz9cbMoYYs484+Jw/NTEFQ==", - "dev": true - }, - "@babel/template": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/types": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", - "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - } - } - }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.16.7.tgz", - "integrity": "sha512-fk5A6ymfp+O5+p2yCkXAu5Kyj6v0xh0RBeNcAkYUMDvvAAoxvSKXn+Jb37t/yWFiQVDFK1ELpUTD8/aLhCPu+g==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "regexpu-core": "^4.7.1" - } - }, - "@babel/helper-define-polyfill-provider": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz", - "integrity": "sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA==", - "dev": true, - "requires": { - "@babel/helper-compilation-targets": "^7.13.0", - "@babel/helper-module-imports": "^7.12.13", - "@babel/helper-plugin-utils": "^7.13.0", - "@babel/traverse": "^7.13.0", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.16.7" - } - }, - "@babel/generator": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz", - "integrity": "sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.8", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", - "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", - "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - }, - "@babel/helper-split-export-declaration": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", - "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true - }, - "@babel/highlight": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", - "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.10.tgz", - "integrity": "sha512-Sm/S9Or6nN8uiFsQU1yodyDW3MWXQhFeqzMPM+t8MJjM+pLsnFVxFZzkpXKvUXh+Gz9cbMoYYs484+Jw/NTEFQ==", - "dev": true - }, - "@babel/template": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/traverse": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.10.tgz", - "integrity": "sha512-yzuaYXoRJBGMlBhsMJoUW7G1UmSb/eXr/JHYM/MsOJgavJibLwASijW7oXBdw3NQ6T0bW7Ty5P/VarOs9cHmqw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.16.8", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.16.10", - "@babel/types": "^7.16.8", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", - "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "@babel/helper-environment-visitor": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", - "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true - }, - "@babel/types": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.7.tgz", - "integrity": "sha512-E8HuV7FO9qLpx6OtoGfUQ2cjIYnbFwvZWYBS+87EwtdMvmUPJSwykpovFB+8insbpF0uJcpr8KMUi64XZntZcg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" - } - } - } - }, - "@babel/helper-explode-assignable-expression": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.16.7.tgz", - "integrity": "sha512-KyUenhWMC8VrxzkGP0Jizjo4/Zx+1nNZhgocs+gLzyZyB8SHidhoq9KK/8Ato4anhwsivfkBLftky7gvzbZMtQ==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true - }, - "@babel/types": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", - "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" - } - } - } - }, - "@babel/helper-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", - "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", - "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true - }, - "@babel/types": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.7.tgz", - "integrity": "sha512-E8HuV7FO9qLpx6OtoGfUQ2cjIYnbFwvZWYBS+87EwtdMvmUPJSwykpovFB+8insbpF0uJcpr8KMUi64XZntZcg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" - } - } - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.7.tgz", - "integrity": "sha512-VtJ/65tYiU/6AbMTDwyoXGPKHgTsfRarivm+YbB5uAzKUyuPjgZSgAFeG87FCigc7KNHu2Pegh1XIT3lXjvz3Q==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true - }, - "@babel/types": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", - "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" - } - } - } - }, - "@babel/helper-module-imports": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", - "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true - }, - "@babel/types": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", - "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" - } - } - } - }, - "@babel/helper-module-transforms": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz", - "integrity": "sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-simple-access": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/helper-validator-identifier": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.16.7" - } - }, - "@babel/generator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.7.tgz", - "integrity": "sha512-/ST3Sg8MLGY5HVYmrjOgL60ENux/HfO/CsUh7y4MalThufhE/Ff/6EibFDHi4jiDCaWfJKoqbE6oTh21c5hrRg==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-environment-visitor": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", - "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", - "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", - "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", - "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-module-imports": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", - "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-simple-access": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz", - "integrity": "sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", - "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true - }, - "@babel/highlight": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.7.tgz", - "integrity": "sha512-aKpPMfLvGO3Q97V0qhw/V2SWNWlwfJknuwAunU7wZLSfrM4xTBvg7E5opUVi1kJTBKihE38CPg4nBiqX83PWYw==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.7.tgz", - "integrity": "sha512-sR4eaSrnM7BV7QPzGfEX5paG/6wrZM3I0HDzfIAK06ESvo9oy3xBuVBxE3MbQaKNhvg8g/ixjMWo2CGpzpHsDA==", - "dev": true - }, - "@babel/template": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/traverse": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.7.tgz", - "integrity": "sha512-8KWJPIb8c2VvY8AJrydh6+fVRo2ODx1wYBU2398xJVq0JomuLBZmVQzLPBblJgHIGYG4znCpUZUZ0Pt2vdmVYQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.16.7", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/types": "^7.16.7", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.7.tgz", - "integrity": "sha512-E8HuV7FO9qLpx6OtoGfUQ2cjIYnbFwvZWYBS+87EwtdMvmUPJSwykpovFB+8insbpF0uJcpr8KMUi64XZntZcg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz", - "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true - }, - "@babel/types": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", - "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" - } - } - } - }, - "@babel/helper-plugin-utils": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", - "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==", - "dev": true - }, - "@babel/helper-remap-async-to-generator": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz", - "integrity": "sha512-fm0gH7Flb8H51LqJHy3HJ3wnE1+qtYR2A99K06ahwrawLdOFsCEWjZOrYricXJHoPSudNKxrMBUPEIPxiIIvBw==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-wrap-function": "^7.16.8", - "@babel/types": "^7.16.8" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true - }, - "@babel/types": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", - "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" - } - } - } - }, - "@babel/helper-replace-supers": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz", - "integrity": "sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-member-expression-to-functions": "^7.16.7", - "@babel/helper-optimise-call-expression": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.16.7" - } - }, - "@babel/generator": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz", - "integrity": "sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.8", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", - "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", - "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", - "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true - }, - "@babel/highlight": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", - "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.10.tgz", - "integrity": "sha512-Sm/S9Or6nN8uiFsQU1yodyDW3MWXQhFeqzMPM+t8MJjM+pLsnFVxFZzkpXKvUXh+Gz9cbMoYYs484+Jw/NTEFQ==", - "dev": true - }, - "@babel/template": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/traverse": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.10.tgz", - "integrity": "sha512-yzuaYXoRJBGMlBhsMJoUW7G1UmSb/eXr/JHYM/MsOJgavJibLwASijW7oXBdw3NQ6T0bW7Ty5P/VarOs9cHmqw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.16.8", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.16.10", - "@babel/types": "^7.16.8", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", - "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "@babel/helper-simple-access": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz", - "integrity": "sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true - }, - "@babel/types": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", - "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" - } - } - } - }, - "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz", - "integrity": "sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.0" - }, - "dependencies": { - "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true - }, - "@babel/types": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", - "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" - } - } - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", - "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", - "dev": true, - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.14.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", - "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==", - "dev": true - }, - "@babel/helper-validator-option": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", - "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", - "dev": true - }, - "@babel/helper-wrap-function": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.16.8.tgz", - "integrity": "sha512-8RpyRVIAW1RcDDGTA+GpPAwV22wXCfKOoM9bet6TLkGIFTkRQSkH1nMQ5Yet4MpoXe1ZwHPVtNasc2w0uZMqnw==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.8", - "@babel/types": "^7.16.8" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.16.7" - } - }, - "@babel/generator": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz", - "integrity": "sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.8", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", - "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", - "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", - "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true - }, - "@babel/highlight": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", - "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.10.tgz", - "integrity": "sha512-Sm/S9Or6nN8uiFsQU1yodyDW3MWXQhFeqzMPM+t8MJjM+pLsnFVxFZzkpXKvUXh+Gz9cbMoYYs484+Jw/NTEFQ==", - "dev": true - }, - "@babel/template": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/traverse": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.10.tgz", - "integrity": "sha512-yzuaYXoRJBGMlBhsMJoUW7G1UmSb/eXr/JHYM/MsOJgavJibLwASijW7oXBdw3NQ6T0bW7Ty5P/VarOs9cHmqw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.16.8", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.16.10", - "@babel/types": "^7.16.8", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", - "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "@babel/helpers": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.7.tgz", - "integrity": "sha512-9ZDoqtfY7AuEOt3cxchfii6C7GDyyMBffktR5B2jvWv8u2+efwvpnVKXMWzNehqy68tKgAfSwfdw/lWpthS2bw==", - "dev": true, - "requires": { - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.16.7" - } - }, - "@babel/generator": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz", - "integrity": "sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.8", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", - "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", - "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", - "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true - }, - "@babel/highlight": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", - "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.16.12", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.12.tgz", - "integrity": "sha512-VfaV15po8RiZssrkPweyvbGVSe4x2y+aciFCgn0n0/SJMR22cwofRV1mtnJQYcSB1wUTaA/X1LnA3es66MCO5A==", - "dev": true - }, - "@babel/template": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/traverse": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.10.tgz", - "integrity": "sha512-yzuaYXoRJBGMlBhsMJoUW7G1UmSb/eXr/JHYM/MsOJgavJibLwASijW7oXBdw3NQ6T0bW7Ty5P/VarOs9cHmqw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.16.8", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.16.10", - "@babel/types": "^7.16.8", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", - "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "@babel/highlight": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", - "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - } - } - }, - "@babel/node": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/node/-/node-7.16.8.tgz", - "integrity": "sha512-V2dopEtPUL4LD+e8UtMIZB6BbsmMsS/7E1ZAvWNINzBfi7Cf3X9MLCpzHVZT4HeeF1lQl72IRtqqVt2RUImwyA==", - "dev": true, - "requires": { - "@babel/register": "^7.16.8", - "commander": "^4.0.1", - "core-js": "^3.20.2", - "node-environment-flags": "^1.0.5", - "regenerator-runtime": "^0.13.4", - "v8flags": "^3.1.1" - } - }, - "@babel/parser": { - "version": "7.8.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.8.tgz", - "integrity": "sha512-mO5GWzBPsPf6865iIbzNE0AvkKF3NE+2S3eRUpE+FE07BOAkXh6G+GW/Pj01hhXjve1WScbaIO4UlY1JKeqCcA==", - "dev": true - }, - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.16.7.tgz", - "integrity": "sha512-anv/DObl7waiGEnC24O9zqL0pSuI9hljihqiDuFHC8d7/bjr/4RLGPWuc8rYOff/QPzbEPSkzG8wGG9aDuhHRg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.16.7.tgz", - "integrity": "sha512-di8vUHRdf+4aJ7ltXhaDbPoszdkh59AQtJM5soLsuHpQJdFQZOA4uGj0V2u/CZ8bJ/u8ULDL5yq6FO/bCXnKHw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", - "@babel/plugin-proposal-optional-chaining": "^7.16.7" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.16.8.tgz", - "integrity": "sha512-71YHIvMuiuqWJQkebWJtdhQTfd4Q4mF76q2IX37uZPkG9+olBxsX+rH1vkhFto4UeJZ9dPY2s+mDvhDm1u2BGQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-remap-async-to-generator": "^7.16.8", - "@babel/plugin-syntax-async-generators": "^7.8.4" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-proposal-class-properties": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz", - "integrity": "sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-proposal-class-static-block": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.16.7.tgz", - "integrity": "sha512-dgqJJrcZoG/4CkMopzhPJjGxsIe9A8RlkQLnL/Vhhx8AA9ZuaRwGSlscSh42hazc7WSrya/IK7mTeoF0DP9tEw==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-proposal-dynamic-import": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz", - "integrity": "sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-proposal-export-namespace-from": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.7.tgz", - "integrity": "sha512-ZxdtqDXLRGBL64ocZcs7ovt71L3jhC1RGSyR996svrCi3PYqHNkb3SwPJCs8RIzD86s+WPpt2S73+EHCGO+NUA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-proposal-json-strings": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.16.7.tgz", - "integrity": "sha512-lNZ3EEggsGY78JavgbHsK9u5P3pQaW7k4axlgFLYkMd7UBsiNahCITShLjNQschPyjtO6dADrL24757IdhBrsQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-json-strings": "^7.8.3" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.7.tgz", - "integrity": "sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz", - "integrity": "sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-proposal-numeric-separator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz", - "integrity": "sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.16.7.tgz", - "integrity": "sha512-3O0Y4+dw94HA86qSg9IHfyPktgR7q3gpNVAeiKQd+8jBKFaU5NQS1Yatgo4wY+UFNuLjvxcSmzcsHqrhgTyBUA==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.16.4", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.16.7" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.16.7.tgz", - "integrity": "sha512-eMOH/L4OvWSZAE1VkHbr1vckLG1WUcHGJSLqqQwl2GaUqG6QjddvrOaTUMNYiv77H5IKPMZ9U9P7EaHwvAShfA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-proposal-optional-chaining": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.7.tgz", - "integrity": "sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-proposal-private-methods": { - "version": "7.16.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.11.tgz", - "integrity": "sha512-F/2uAkPlXDr8+BHpZvo19w3hLFKge+k75XUprE6jaqKxjGkSYcK+4c+bup5PdW/7W/Rpjwql7FTVEDW+fRAQsw==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.16.10", - "@babel/helper-plugin-utils": "^7.16.7" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-proposal-private-property-in-object": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.7.tgz", - "integrity": "sha512-rMQkjcOFbm+ufe3bTZLyOfsOUOxyvLXZJCTARhJr+8UMSoZmqTe1K1BgkFcrW37rAchWg57yI69ORxiWvUINuQ==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-create-class-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.16.7.tgz", - "integrity": "sha512-QRK0YI/40VLhNVGIjRNAAQkEHws0cswSdFFjpFyt943YmJIU1da9uW63Iu6NFV6CxTZW5eTDCrwZUstBWgp/Rg==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", - "dev": true - } - } - }, - "@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", - "dev": true - } - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", - "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", - "dev": true - } - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", - "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", - "dev": true - } - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.13.tgz", - "integrity": "sha512-A81F9pDwyS7yM//KwbCSDqy3Uj4NMIurtplxphWxoYtNPov7cJsDkAFNNyVlIZ3jwGycVsurZ+LtOA8gZ376iQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", - "dev": true - } - } - }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.7.tgz", - "integrity": "sha512-9ffkFFMbvzTvv+7dTp/66xvZAWASuPD5Tl9LK3Z9vhOmANo6j94rik+5YMBt4CwHVMWLWpMsriIc2zsa3WW3xQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.16.8.tgz", - "integrity": "sha512-MtmUmTJQHCnyJVrScNzNlofQJ3dLFuobYn3mwOTKHnSCMtbNsqvF71GQmJfFjdrXSsAA7iysFmYWw4bXZ20hOg==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-remap-async-to-generator": "^7.16.8" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.16.7.tgz", - "integrity": "sha512-JUuzlzmF40Z9cXyytcbZEZKckgrQzChbQJw/5PuEHYeqzCsvebDx0K0jWnIIVcmmDOAVctCgnYs0pMcrYj2zJg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-transform-block-scoping": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.16.7.tgz", - "integrity": "sha512-ObZev2nxVAYA4bhyusELdo9hb3H+A56bxH3FZMbEImZFiEDYVHXQSJ1hQKFlDnlt8G9bBrCZ5ZpURZUrV4G5qQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-transform-classes": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.16.7.tgz", - "integrity": "sha512-WY7og38SFAGYRe64BrjKf8OrE6ulEHtr5jEYaZMwox9KebgqPi67Zqz8K53EKk1fFEJgm96r32rkKZ3qA2nCWQ==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-optimise-call-expression": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-replace-supers": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "globals": "^11.1.0" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.16.7" - } - }, - "@babel/helper-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", - "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", - "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - }, - "@babel/helper-split-export-declaration": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", - "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true - }, - "@babel/highlight": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", - "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.10.tgz", - "integrity": "sha512-Sm/S9Or6nN8uiFsQU1yodyDW3MWXQhFeqzMPM+t8MJjM+pLsnFVxFZzkpXKvUXh+Gz9cbMoYYs484+Jw/NTEFQ==", - "dev": true - }, - "@babel/template": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/types": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", - "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - } - } - }, - "@babel/plugin-transform-computed-properties": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.16.7.tgz", - "integrity": "sha512-gN72G9bcmenVILj//sv1zLNaPyYcOzUho2lIJBMh/iakJ9ygCo/hEF9cpGb61SCMEDxbbyBoVQxrt+bWKu5KGw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-transform-destructuring": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.16.7.tgz", - "integrity": "sha512-VqAwhTHBnu5xBVDCvrvqJbtLUa++qZaWC0Fgr2mqokBlulZARGyIvZDoqbPlPaKImQ9dKAcCzbv+ul//uqu70A==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-transform-dotall-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.16.7.tgz", - "integrity": "sha512-Lyttaao2SjZF6Pf4vk1dVKv8YypMpomAbygW+mU5cYP3S5cWTfCJjG8xV6CFdzGFlfWK81IjL9viiTvpb6G7gQ==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-transform-duplicate-keys": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.16.7.tgz", - "integrity": "sha512-03DvpbRfvWIXyK0/6QiR1KMTWeT6OcQ7tbhjrXyFS02kjuX/mu5Bvnh5SDSWHxyawit2g5aWhKwI86EE7GUnTw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.16.7.tgz", - "integrity": "sha512-8UYLSlyLgRixQvlYH3J2ekXFHDFLQutdy7FfFAMm3CPZ6q9wHCwnUyiXpQCe3gVVnQlHc5nsuiEVziteRNTXEA==", - "dev": true, - "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-transform-for-of": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.16.7.tgz", - "integrity": "sha512-/QZm9W92Ptpw7sjI9Nx1mbcsWz33+l8kuMIQnDwgQBG5s3fAfQvkRjQ7NqXhtNcKOnPkdICmUHyCaWW06HCsqg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-transform-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.16.7.tgz", - "integrity": "sha512-SU/C68YVwTRxqWj5kgsbKINakGag0KTgq9f2iZEXdStoAbOzLHEBRYzImmA6yFo8YZhJVflvXmIHUO7GWHmxxA==", - "dev": true, - "requires": { - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.16.7" - } - }, - "@babel/helper-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", - "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", - "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - }, - "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true - }, - "@babel/highlight": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", - "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.10.tgz", - "integrity": "sha512-Sm/S9Or6nN8uiFsQU1yodyDW3MWXQhFeqzMPM+t8MJjM+pLsnFVxFZzkpXKvUXh+Gz9cbMoYYs484+Jw/NTEFQ==", - "dev": true - }, - "@babel/template": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/types": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", - "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - } - } - }, - "@babel/plugin-transform-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.16.7.tgz", - "integrity": "sha512-6tH8RTpTWI0s2sV6uq3e/C9wPo4PTqqZps4uF0kzQ9/xPLFQtipynvmT1g/dOfEJ+0EQsHhkQ/zyRId8J2b8zQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.16.7.tgz", - "integrity": "sha512-mBruRMbktKQwbxaJof32LT9KLy2f3gH+27a5XSuXo6h7R3vqltl0PgZ80C8ZMKw98Bf8bqt6BEVi3svOh2PzMw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-transform-modules-amd": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.16.7.tgz", - "integrity": "sha512-KaaEtgBL7FKYwjJ/teH63oAmE3lP34N3kshz8mm4VMAw7U3PxjVwwUmxEFksbgsNUaO3wId9R2AVQYSEGRa2+g==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.8.tgz", - "integrity": "sha512-oflKPvsLT2+uKQopesJt3ApiaIS2HW+hzHFcwRNtyDGieAeC/dIHZX8buJQ2J2X1rxGPy4eRcUijm3qcSPjYcA==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-simple-access": "^7.16.7", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.16.7.tgz", - "integrity": "sha512-DuK5E3k+QQmnOqBR9UkusByy5WZWGRxfzV529s9nPra1GE7olmxfqO2FHobEOYSPIjPBTr4p66YDcjQnt8cBmw==", - "dev": true, - "requires": { - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-validator-identifier": "^7.16.7", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - }, - "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true - } - } - }, - "@babel/plugin-transform-modules-umd": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.16.7.tgz", - "integrity": "sha512-EMh7uolsC8O4xhudF2F6wedbSHm1HHZ0C6aJ7K67zcDNidMzVcxWdGr+htW9n21klm+bOn+Rx4CBsAntZd3rEQ==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.16.8.tgz", - "integrity": "sha512-j3Jw+n5PvpmhRR+mrgIh04puSANCk/T/UA3m3P1MjJkhlK906+ApHhDIqBQDdOgL/r1UYpz4GNclTXxyZrYGSw==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.16.7" - } - }, - "@babel/plugin-transform-new-target": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.16.7.tgz", - "integrity": "sha512-xiLDzWNMfKoGOpc6t3U+etCE2yRnn3SM09BXqWPIZOBpL2gvVrBWUKnsJx0K/ADi5F5YC5f8APFfWrz25TdlGg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-transform-object-super": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.16.7.tgz", - "integrity": "sha512-14J1feiQVWaGvRxj2WjyMuXS2jsBkgB3MdSN5HuC2G5nRspa5RK9COcs82Pwy5BuGcjb+fYaUj94mYcOj7rCvw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-replace-supers": "^7.16.7" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-transform-parameters": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.16.7.tgz", - "integrity": "sha512-AT3MufQ7zZEhU2hwOA11axBnExW0Lszu4RL/tAlUJBuNoRak+wehQW8h6KcXOcgjY42fHtDxswuMhMjFEuv/aw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-transform-property-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.16.7.tgz", - "integrity": "sha512-z4FGr9NMGdoIl1RqavCqGG+ZuYjfZ/hkCIeuH6Do7tXmSm0ls11nYVSJqFEUOSJbDab5wC6lRE/w6YjVcr6Hqw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-transform-regenerator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.16.7.tgz", - "integrity": "sha512-mF7jOgGYCkSJagJ6XCujSQg+6xC1M77/03K2oBmVJWoFGNUtnVJO4WHKJk3dnPC8HCcj4xBQP1Egm8DWh3Pb3Q==", - "dev": true, - "requires": { - "regenerator-transform": "^0.14.2" - } - }, - "@babel/plugin-transform-reserved-words": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.16.7.tgz", - "integrity": "sha512-KQzzDnZ9hWQBjwi5lpY5v9shmm6IVG0U9pB18zvMu2i4H90xpT4gmqwPYsn8rObiadYe2M0gmgsiOIF5A/2rtg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz", - "integrity": "sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-transform-spread": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.16.7.tgz", - "integrity": "sha512-+pjJpgAngb53L0iaA5gU/1MLXJIfXcYepLgXB3esVRf4fqmj8f2cxM3/FKaHsZms08hFQJkFccEWuIpm429TXg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.16.7.tgz", - "integrity": "sha512-NJa0Bd/87QV5NZZzTuZG5BPJjLYadeSZ9fO6oOUoL4iQx+9EEuw/eEM92SrsT19Yc2jgB1u1hsjqDtH02c3Drw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-transform-template-literals": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.16.7.tgz", - "integrity": "sha512-VwbkDDUeenlIjmfNeDX/V0aWrQH2QiVyJtwymVQSzItFDTpxfyJh3EVaQiS0rIN/CqbLGr0VcGmuwyTdZtdIsA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.16.7.tgz", - "integrity": "sha512-p2rOixCKRJzpg9JB4gjnG4gjWkWa89ZoYUnl9snJ1cWIcTH/hvxZqfO+WjG6T8DRBpctEol5jw1O5rA8gkCokQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-transform-unicode-escapes": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.7.tgz", - "integrity": "sha512-TAV5IGahIz3yZ9/Hfv35TV2xEm+kaBDaZQCn2S/hG9/CZ0DktxJv9eKfPc7yYCvOYR4JGx1h8C+jcSOvgaaI/Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.16.7.tgz", - "integrity": "sha512-oC5tYYKw56HO75KZVLQ+R/Nl3Hro9kf8iG0hXoaHP7tjAyCpvqBiSNe6vGrZni1Z6MggmUOC6A7VP7AVmw225Q==", - "dev": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - } - } - }, - "@babel/preset-env": { - "version": "7.16.11", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.16.11.tgz", - "integrity": "sha512-qcmWG8R7ZW6WBRPZK//y+E3Cli151B20W1Rv7ln27vuPaXU/8TKms6jFdiJtF7UDTxcrb7mZd88tAeK9LjdT8g==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.16.8", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-validator-option": "^7.16.7", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.16.7", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.16.7", - "@babel/plugin-proposal-async-generator-functions": "^7.16.8", - "@babel/plugin-proposal-class-properties": "^7.16.7", - "@babel/plugin-proposal-class-static-block": "^7.16.7", - "@babel/plugin-proposal-dynamic-import": "^7.16.7", - "@babel/plugin-proposal-export-namespace-from": "^7.16.7", - "@babel/plugin-proposal-json-strings": "^7.16.7", - "@babel/plugin-proposal-logical-assignment-operators": "^7.16.7", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.7", - "@babel/plugin-proposal-numeric-separator": "^7.16.7", - "@babel/plugin-proposal-object-rest-spread": "^7.16.7", - "@babel/plugin-proposal-optional-catch-binding": "^7.16.7", - "@babel/plugin-proposal-optional-chaining": "^7.16.7", - "@babel/plugin-proposal-private-methods": "^7.16.11", - "@babel/plugin-proposal-private-property-in-object": "^7.16.7", - "@babel/plugin-proposal-unicode-property-regex": "^7.16.7", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.16.7", - "@babel/plugin-transform-async-to-generator": "^7.16.8", - "@babel/plugin-transform-block-scoped-functions": "^7.16.7", - "@babel/plugin-transform-block-scoping": "^7.16.7", - "@babel/plugin-transform-classes": "^7.16.7", - "@babel/plugin-transform-computed-properties": "^7.16.7", - "@babel/plugin-transform-destructuring": "^7.16.7", - "@babel/plugin-transform-dotall-regex": "^7.16.7", - "@babel/plugin-transform-duplicate-keys": "^7.16.7", - "@babel/plugin-transform-exponentiation-operator": "^7.16.7", - "@babel/plugin-transform-for-of": "^7.16.7", - "@babel/plugin-transform-function-name": "^7.16.7", - "@babel/plugin-transform-literals": "^7.16.7", - "@babel/plugin-transform-member-expression-literals": "^7.16.7", - "@babel/plugin-transform-modules-amd": "^7.16.7", - "@babel/plugin-transform-modules-commonjs": "^7.16.8", - "@babel/plugin-transform-modules-systemjs": "^7.16.7", - "@babel/plugin-transform-modules-umd": "^7.16.7", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.16.8", - "@babel/plugin-transform-new-target": "^7.16.7", - "@babel/plugin-transform-object-super": "^7.16.7", - "@babel/plugin-transform-parameters": "^7.16.7", - "@babel/plugin-transform-property-literals": "^7.16.7", - "@babel/plugin-transform-regenerator": "^7.16.7", - "@babel/plugin-transform-reserved-words": "^7.16.7", - "@babel/plugin-transform-shorthand-properties": "^7.16.7", - "@babel/plugin-transform-spread": "^7.16.7", - "@babel/plugin-transform-sticky-regex": "^7.16.7", - "@babel/plugin-transform-template-literals": "^7.16.7", - "@babel/plugin-transform-typeof-symbol": "^7.16.7", - "@babel/plugin-transform-unicode-escapes": "^7.16.7", - "@babel/plugin-transform-unicode-regex": "^7.16.7", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.16.8", - "babel-plugin-polyfill-corejs2": "^0.3.0", - "babel-plugin-polyfill-corejs3": "^0.5.0", - "babel-plugin-polyfill-regenerator": "^0.3.0", - "core-js-compat": "^3.20.2", - "semver": "^6.3.0" - }, - "dependencies": { - "@babel/compat-data": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.8.tgz", - "integrity": "sha512-m7OkX0IdKLKPpBlJtF561YJal5y/jyI5fNfWbPxh2D/nbzzGI4qRyrD8xO2jB24u7l+5I2a43scCG2IrfjC50Q==", - "dev": true - }, - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - }, - "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/types": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", - "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "@babel/preset-modules": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", - "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - } - }, - "@babel/register": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.16.8.tgz", - "integrity": "sha512-aoUj2ocH92k7qyyA59y07sUaCVxxS7VjNul/jR0mpAyYvpo6n5HELZmyUGtrgFm7/1b0UutT7I1w/4bAkXxCHA==", - "dev": true, - "requires": { - "clone-deep": "^4.0.1", - "find-cache-dir": "^2.0.0", - "make-dir": "^2.1.0", - "pirates": "^4.0.0", - "source-map-support": "^0.5.16" - } - }, - "@babel/runtime": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.7.tgz", - "integrity": "sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "@babel/template": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", - "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.6", - "@babel/types": "^7.8.6" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - } - } - } - } - }, - "@babel/traverse": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.6.tgz", - "integrity": "sha512-2B8l0db/DPi8iinITKuo7cbPznLCEk0kCxDoB9/N6gGNg/gxOXiR/IcymAFPiBwk5w6TtQ27w4wpElgp9btR9A==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.6", - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.6", - "@babel/types": "^7.8.6", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.8.3" - } - }, - "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - } - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "@babel/types": { - "version": "7.8.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.7.tgz", - "integrity": "sha512-k2TreEHxFA4CjGkL+GYjRyx35W0Mr7DP5+9q6WMkyKXB+904bYmG40syjMFV0oLlhhFCwWl0vA0DyzTDkwAiJw==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - }, - "@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "@cnakazawa/watch": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", - "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==", - "dev": true, - "requires": { - "exec-sh": "^0.3.2", - "minimist": "^1.2.0" - } - }, - "@eslint/eslintrc": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", - "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "globals": { - "version": "13.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.11.0.tgz", - "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - } - } - }, - "@exercism/eslint-config-javascript": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@exercism/eslint-config-javascript/-/eslint-config-javascript-0.5.0.tgz", - "integrity": "sha512-uTMIRvhFvjHQVwmTyHgSjFgmltJixFeoCPO7Iw6boK6sca0QBjvpsk8Drl7Z8wTXga5b8VXgplvOOPTsUlV4gw==", - "dev": true, - "requires": { - "@babel/eslint-parser": "^7.15.4", - "@babel/eslint-plugin": "^7.14.5", - "eslint-config-prettier": "^8.3.0", - "eslint-plugin-import": "^2.24.2" - } - }, - "@humanwhocodes/config-array": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", - "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.0", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - }, - "dependencies": { - "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "@humanwhocodes/object-schema": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz", - "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==", - "dev": true - }, - "@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - } - } - }, - "@istanbuljs/schema": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", - "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", - "dev": true - }, - "@jest/console": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-26.6.2.tgz", - "integrity": "sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^26.6.2", - "jest-util": "^26.6.2", - "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/core": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-26.6.3.tgz", - "integrity": "sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw==", - "dev": true, - "requires": { - "@jest/console": "^26.6.2", - "@jest/reporters": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-changed-files": "^26.6.2", - "jest-config": "^26.6.3", - "jest-haste-map": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-resolve-dependencies": "^26.6.3", - "jest-runner": "^26.6.3", - "jest-runtime": "^26.6.3", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "jest-watcher": "^26.6.2", - "micromatch": "^4.0.2", - "p-each-series": "^2.1.0", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - } - } - }, - "@jest/environment": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.6.2.tgz", - "integrity": "sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2" - } - }, - "@jest/fake-timers": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.6.2.tgz", - "integrity": "sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "@sinonjs/fake-timers": "^6.0.1", - "@types/node": "*", - "jest-message-util": "^26.6.2", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2" - } - }, - "@jest/globals": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.6.2.tgz", - "integrity": "sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA==", - "dev": true, - "requires": { - "@jest/environment": "^26.6.2", - "@jest/types": "^26.6.2", - "expect": "^26.6.2" - } - }, - "@jest/reporters": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-26.6.2.tgz", - "integrity": "sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw==", - "dev": true, - "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.2.4", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^4.0.3", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "jest-haste-map": "^26.6.2", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "node-notifier": "^8.0.0", - "slash": "^3.0.0", - "source-map": "^0.6.0", - "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^7.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/source-map": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.6.2.tgz", - "integrity": "sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA==", - "dev": true, - "requires": { - "callsites": "^3.0.0", - "graceful-fs": "^4.2.4", - "source-map": "^0.6.0" - }, - "dependencies": { - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "@jest/test-result": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.6.2.tgz", - "integrity": "sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ==", - "dev": true, - "requires": { - "@jest/console": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - } - }, - "@jest/test-sequencer": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz", - "integrity": "sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw==", - "dev": true, - "requires": { - "@jest/test-result": "^26.6.2", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.6.2", - "jest-runner": "^26.6.3", - "jest-runtime": "^26.6.3" - }, - "dependencies": { - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - } - } - }, - "@jest/transform": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.6.2.tgz", - "integrity": "sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA==", - "dev": true, - "requires": { - "@babel/core": "^7.1.0", - "@jest/types": "^26.6.2", - "babel-plugin-istanbul": "^6.0.0", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-util": "^26.6.2", - "micromatch": "^4.0.2", - "pirates": "^4.0.1", - "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - } - } - }, - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@nicolo-ribaudo/chokidar-2": { - "version": "2.1.8-no-fsevents.3", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz", - "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==", - "dev": true, - "optional": true - }, - "@sinonjs/commons": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", - "integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", - "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, - "@types/babel__core": { - "version": "7.1.14", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.14.tgz", - "integrity": "sha512-zGZJzzBUVDo/eV6KgbE0f0ZI7dInEYvo12Rb70uNQDshC3SkRMb67ja0GgRHZgAX3Za6rhaWlvbDO8rrGyAb1g==", - "dev": true, - "requires": { - "@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.2", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.2.tgz", - "integrity": "sha512-MdSJnBjl+bdwkLskZ3NGFp9YcXGx5ggLpQQPqtgakVhsWK0hTtNYhjpZLlWQTviGTvF8at+Bvli3jV7faPdgeQ==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/babel__template": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.0.tgz", - "integrity": "sha512-NTPErx4/FiPCGScH7foPyr+/1Dkzkni+rHiYHHoTjvwou7AQzJkNeD60A9CXRy+ZEN2B1bggmkTMCDb+Mv5k+A==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.11.0.tgz", - "integrity": "sha512-kSjgDMZONiIfSH1Nxcr5JIRMwUetDki63FSQfpTCz8ogF3Ulqm8+mr5f78dUYs6vMiB6gBusQqfQmBvHZj/lwg==", - "dev": true, - "requires": { - "@babel/types": "^7.3.0" - } - }, - "@types/graceful-fs": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.4.tgz", - "integrity": "sha512-mWA/4zFQhfvOA8zWkXobwJvBD7vzcxgrOQ0J5CH1votGqdq9m7+FwtGaqyCZqC3NyyBkc9z4m+iry4LlqcMWJg==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", - "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", - "dev": true - }, - "@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "@types/jest": { - "version": "26.0.24", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.24.tgz", - "integrity": "sha512-E/X5Vib8BWqZNRlDxj9vYXhsDwPYbPINqKF9BsnSoon4RQ0D9moEuLD8txgyypFLH7J4+Lho9Nr/c8H0Fi+17w==", - "dev": true, - "requires": { - "jest-diff": "^26.0.0", - "pretty-format": "^26.0.0" - } - }, - "@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", - "dev": true - }, - "@types/node": { - "version": "16.11.21", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.21.tgz", - "integrity": "sha512-Pf8M1XD9i1ksZEcCP8vuSNwooJ/bZapNmIzpmsMaL+jMI+8mEYU3PKvs+xDNuQcJWF/x24WzY4qxLtB0zNow9A==", - "dev": true - }, - "@types/normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", - "dev": true - }, - "@types/prettier": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.1.5.tgz", - "integrity": "sha512-UEyp8LwZ4Dg30kVU2Q3amHHyTn1jEdhCIE59ANed76GaT1Vp76DD3ZWSAxgCrw6wJ0TqeoBpqmfUHiUDPs//HQ==", - "dev": true - }, - "@types/stack-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz", - "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==", - "dev": true - }, - "@types/yargs": { - "version": "15.0.12", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.12.tgz", - "integrity": "sha512-f+fD/fQAo3BCbCDlrUpznF1A5Zp9rB0noS5vnoormHSIPFKL0Z2DcUJ3Gxp5ytH4uLRNxy7AwYUC9exZzqGMAw==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", - "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==", - "dev": true - }, - "abab": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", - "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", - "dev": true - }, - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - }, - "acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dev": true, - "requires": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true - }, - "acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "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-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "ansi-escapes": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", - "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", - "dev": true, - "requires": { - "type-fest": "^0.11.0" - }, - "dependencies": { - "type-fest": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", - "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", - "dev": true - } - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true - }, - "array-includes": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.3.tgz", - "integrity": "sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.2", - "get-intrinsic": "^1.1.1", - "is-string": "^1.0.5" - } - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "array.prototype.flat": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.4.tgz", - "integrity": "sha512-4470Xi3GAPAjZqFcljX2xzckv1qeKPizoNkiS0+O4IoPR2ZNpcjE0pkhdihlDouK+x6QOast26B4Q/O9DJnwSg==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.1" - } - }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true - }, - "astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true - }, - "aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", - "dev": true - }, - "babel-jest": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.4.6.tgz", - "integrity": "sha512-qZL0JT0HS1L+lOuH+xC2DVASR3nunZi/ozGhpgauJHgmI7f8rudxf6hUjEHympdQ/J64CdKmPkgfJ+A3U6QCrg==", - "dev": true, - "requires": { - "@jest/transform": "^27.4.6", - "@jest/types": "^27.4.2", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^27.4.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "slash": "^3.0.0" - }, - "dependencies": { - "@babel/parser": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.7.tgz", - "integrity": "sha512-sR4eaSrnM7BV7QPzGfEX5paG/6wrZM3I0HDzfIAK06ESvo9oy3xBuVBxE3MbQaKNhvg8g/ixjMWo2CGpzpHsDA==", - "dev": true - }, - "@jest/transform": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.4.6.tgz", - "integrity": "sha512-9MsufmJC8t5JTpWEQJ0OcOOAXaH5ioaIX6uHVBLBMoCZPfKKQF+EqP8kACAvCZ0Y1h2Zr3uOccg8re+Dr5jxyw==", - "dev": true, - "requires": { - "@babel/core": "^7.1.0", - "@jest/types": "^27.4.2", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.4.6", - "jest-regex-util": "^27.4.0", - "jest-util": "^27.4.2", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" - } - }, - "@jest/types": { - "version": "27.4.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.4.2.tgz", - "integrity": "sha512-j35yw0PMTPpZsUoOBiuHzr1zTYoad1cVIE0ajEjcrJONxxrko/IRGKkXx3os0Nsi4Hu3+5VmDbVfq5WhG/pWAg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "ci-info": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.0.tgz", - "integrity": "sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw==", - "dev": true - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "istanbul-lib-coverage": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz", - "integrity": "sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q==", - "dev": true, - "requires": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - } - }, - "jest-haste-map": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.4.6.tgz", - "integrity": "sha512-0tNpgxg7BKurZeFkIOvGCkbmOHbLFf4LUQOxrQSMjvrQaQe3l6E8x6jYC1NuWkGo5WDdbr8FEzUxV2+LWNawKQ==", - "dev": true, - "requires": { - "@jest/types": "^27.4.2", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.4", - "jest-regex-util": "^27.4.0", - "jest-serializer": "^27.4.0", - "jest-util": "^27.4.2", - "jest-worker": "^27.4.6", - "micromatch": "^4.0.4", - "walker": "^1.0.7" - } - }, - "jest-regex-util": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.4.0.tgz", - "integrity": "sha512-WeCpMpNnqJYMQoOjm1nTtsgbR4XHAk1u00qDoNBQoykM280+/TmgA5Qh5giC1ecy6a5d4hbSsHzpBtu5yvlbEg==", - "dev": true - }, - "jest-serializer": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.4.0.tgz", - "integrity": "sha512-RDhpcn5f1JYTX2pvJAGDcnsNTnsV9bjYPU8xcV+xPwOXnUPOQwf4ZEuiU6G9H1UztH+OapMgu/ckEVwO87PwnQ==", - "dev": true, - "requires": { - "@types/node": "*", - "graceful-fs": "^4.2.4" - } - }, - "jest-util": { - "version": "27.4.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.4.2.tgz", - "integrity": "sha512-YuxxpXU6nlMan9qyLuxHaMMOzXAl5aGZWCSzben5DhLHemYQxCc4YK+4L3ZrCutT8GPQ+ui9k5D8rUJoDioMnA==", - "dev": true, - "requires": { - "@jest/types": "^27.4.2", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.4", - "picomatch": "^2.2.3" - }, - "dependencies": { - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - } - } - }, - "jest-worker": { - "version": "27.4.6", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.4.6.tgz", - "integrity": "sha512-gHWJF/6Xi5CTG5QCvROr6GcmpIqNYpDJyc8A1h/DyXqH1tD6SnRCM0d3U5msV31D2LB/U+E0M+W4oyvKV44oNw==", - "dev": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - } - }, - "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - }, - "dependencies": { - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - } - } - }, - "pirates": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.4.tgz", - "integrity": "sha512-ZIrVPH+A52Dw84R0L3/VS9Op04PuQ2SEoJL6bkshmiTic/HldyW9Tf7oH5mhJZBK7NmDx27vSMrYEXPXclpDKw==", - "dev": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - } - } - }, - "babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "dev": true, - "requires": { - "object.assign": "^4.1.0" - } - }, - "babel-plugin-istanbul": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", - "integrity": "sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^4.0.0", - "test-exclude": "^6.0.0" - } - }, - "babel-plugin-jest-hoist": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.4.0.tgz", - "integrity": "sha512-Jcu7qS4OX5kTWBc45Hz7BMmgXuJqRnhatqpUhnzGC3OBYpOmf2tv6jFNwZpwM7wU7MUuv2r9IPS/ZlYOuburVw==", - "dev": true, - "requires": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", - "@types/babel__traverse": "^7.0.6" - } - }, - "babel-plugin-polyfill-corejs2": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz", - "integrity": "sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.13.11", - "@babel/helper-define-polyfill-provider": "^0.3.1", - "semver": "^6.1.1" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "babel-plugin-polyfill-corejs3": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.1.tgz", - "integrity": "sha512-TihqEe4sQcb/QcPJvxe94/9RZuLQuF1+To4WqQcRvc+3J3gLCPIPgDKzGLG6zmQLfH3nn25heRuDNkS2KR4I8A==", - "dev": true, - "requires": { - "@babel/helper-define-polyfill-provider": "^0.3.1", - "core-js-compat": "^3.20.0" - } - }, - "babel-plugin-polyfill-regenerator": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz", - "integrity": "sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==", - "dev": true, - "requires": { - "@babel/helper-define-polyfill-provider": "^0.3.1" - } - }, - "babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "requires": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - } - }, - "babel-preset-jest": { - "version": "27.4.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.4.0.tgz", - "integrity": "sha512-NK4jGYpnBvNxcGo7/ZpZJr51jCGT+3bwwpVIDY2oNfTxJJldRtB4VAcYdgp1loDE50ODuTu+yBjpMAswv5tlpg==", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^27.4.0", - "babel-preset-current-node-syntax": "^1.0.0" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "requires": { - "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" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "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" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, - "browserslist": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz", - "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001286", - "electron-to-chromium": "^1.4.17", - "escalade": "^3.1.1", - "node-releases": "^2.0.1", - "picocolors": "^1.0.0" - } - }, - "bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "requires": { - "node-int64": "^0.4.0" - } - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "requires": { - "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": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30001295", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001295.tgz", - "integrity": "sha512-lSP16vcyC0FEy0R4ECc9duSPoKoZy+YkpGkue9G4D81OfPnliopaZrU10+qtPdT8PbGXad/PNx43TIQrOmJZSQ==", - "dev": true - }, - "capture-exit": { - "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==", - "dev": true, - "requires": { - "rsvp": "^4.8.4" - } - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true - }, - "chokidar": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", - "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", - "dev": true, - "optional": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "dependencies": { - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "optional": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "optional": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "optional": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "optional": true - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "optional": true, - "requires": { - "is-number": "^7.0.0" - } - } - } - }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "cjs-module-lexer": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz", - "integrity": "sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw==", - "dev": true - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - }, - "dependencies": { - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - } - } - }, - "clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, - "collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true - }, - "core-js": { - "version": "3.20.3", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.20.3.tgz", - "integrity": "sha512-vVl8j8ph6tRS3B8qir40H7yw7voy17xL0piAjlbBUsH7WIfzoedL/ZOr1OV9FyZQLWXsayOJyV4tnRyXR85/ag==", - "dev": true - }, - "core-js-compat": { - "version": "3.20.3", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.20.3.tgz", - "integrity": "sha512-c8M5h0IkNZ+I92QhIpuSijOxGAcj3lgpsWdkCqmUTZNwidujF4r3pi6x1DCN+Vcs5qTS2XWWMfWSuCqyupX8gw==", - "dev": true, - "requires": { - "browserslist": "^4.19.1", - "semver": "7.0.0" - }, - "dependencies": { - "semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true - } - } - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", - "dev": true - }, - "cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "requires": { - "cssom": "~0.3.6" - }, - "dependencies": { - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - } - } - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", - "dev": true, - "requires": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, - "decimal.js": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz", - "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==", - "dev": true - }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, - "detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true - }, - "diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true - }, - "diff-sequences": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", - "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", - "dev": true - }, - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", - "dev": true, - "requires": { - "webidl-conversions": "^5.0.0" - }, - "dependencies": { - "webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "dev": true - } - } - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "dev": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "electron-to-chromium": { - "version": "1.4.31", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.31.tgz", - "integrity": "sha512-t3XVQtk+Frkv6aTD4RRk0OqosU+VLe1dQFW83MDer78ZD6a52frgXuYOIsLYTQiH2Lm+JB2OKYcn7zrX+YGAiQ==", - "dev": true - }, - "emittery": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz", - "integrity": "sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "end-of-stream": { - "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==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1" - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.18.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.3.tgz", - "integrity": "sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.2", - "is-callable": "^1.2.3", - "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.3", - "is-string": "^1.0.6", - "object-inspect": "^1.10.3", - "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": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "escodegen": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", - "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", - "dev": true, - "requires": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "requires": { - "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" - } - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - } - } - }, - "eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", - "dev": true, - "requires": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/highlight": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz", - "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.5", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - } - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "globals": { - "version": "13.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.11.0.tgz", - "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "eslint-config-prettier": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz", - "integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==", - "dev": true - }, - "eslint-import-resolver-node": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", - "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", - "dev": true, - "requires": { - "debug": "^3.2.7", - "resolve": "^1.20.0" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", - "dev": true, - "requires": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - } - } - } - }, - "eslint-module-utils": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.2.tgz", - "integrity": "sha512-QG8pcgThYOuqxupd06oYTZoNOGaUdTY1PqK+oS6ElF6vs4pBdk/aYxFVQQXzcrAqp9m7cl7lb2ubazX+g16k2Q==", - "dev": true, - "requires": { - "debug": "^3.2.7", - "pkg-dir": "^2.0.0" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", - "dev": true, - "requires": { - "find-up": "^2.1.0" - } - } - } - }, - "eslint-plugin-import": { - "version": "2.24.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.24.2.tgz", - "integrity": "sha512-hNVtyhiEtZmpsabL4neEj+6M5DCLgpYyG9nzJY8lZQeQXEn5UPW1DpUdsMHMXsq98dbNm7nt1w9ZMSVpfJdi8Q==", - "dev": true, - "requires": { - "array-includes": "^3.1.3", - "array.prototype.flat": "^1.2.4", - "debug": "^2.6.9", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.6", - "eslint-module-utils": "^2.6.2", - "find-up": "^2.0.0", - "has": "^1.0.3", - "is-core-module": "^2.6.0", - "minimatch": "^3.0.4", - "object.values": "^1.1.4", - "pkg-up": "^2.0.0", - "read-pkg-up": "^3.0.0", - "resolve": "^1.20.0", - "tsconfig-paths": "^3.11.0" - }, - "dependencies": { - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "is-core-module": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.6.0.tgz", - "integrity": "sha512-wShG8vs60jKfPWpF2KZRaAtvt3a20OAn7+IJ6hLPECpSABLcKtFKTTI4ZtH5QcBruBHlq+WsdHWyz0BCZW7svQ==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", - "dev": true, - "requires": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - } - } - } - }, - "eslint-rule-composer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz", - "integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==", - "dev": true - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - }, - "espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", - "dev": true, - "requires": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - } - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "exec-sh": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.4.tgz", - "integrity": "sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A==", - "dev": true - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "requires": { - "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": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "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" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "expect": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/expect/-/expect-26.6.2.tgz", - "integrity": "sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "ansi-styles": "^4.0.0", - "jest-get-type": "^26.3.0", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-regex-util": "^26.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "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" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true - }, - "fast-deep-equal": { - "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==", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", - "dev": true, - "requires": { - "bser": "2.1.1" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.2.tgz", - "integrity": "sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==", - "dev": true - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "requires": { - "map-cache": "^0.2.2" - } - }, - "fs-readdir-recursive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", - "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - } - }, - "get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - } - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", - "dev": true - }, - "growly": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", - "dev": true, - "optional": true - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true - }, - "har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "dev": true, - "requires": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", - "dev": true - }, - "has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "homedir-polyfill": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", - "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", - "dev": true, - "requires": { - "parse-passwd": "^1.0.0" - } - }, - "hosted-git-info": { - "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==", - "dev": true - }, - "html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", - "dev": true, - "requires": { - "whatwg-encoding": "^1.0.5" - } - }, - "html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - } - } - }, - "import-local": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", - "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - } - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - } - }, - "interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "dev": true - }, - "ip-regex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", - "dev": true - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-bigint": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.2.tgz", - "integrity": "sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA==", - "dev": true - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "optional": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-boolean-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.1.tgz", - "integrity": "sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng==", - "dev": true, - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-callable": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", - "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", - "dev": true - }, - "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "requires": { - "ci-info": "^2.0.0" - } - }, - "is-core-module": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", - "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-date-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.4.tgz", - "integrity": "sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==", - "dev": true - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "is-docker": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz", - "integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==", - "dev": true, - "optional": true - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", - "dev": true - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-number-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.5.tgz", - "integrity": "sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==", - "dev": true - }, - "is-plain-object": { - "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==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "is-potential-custom-element-name": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz", - "integrity": "sha1-DFLlS8yjkbssSUsh6GJtczbG45c=", - "dev": true - }, - "is-regex": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.3.tgz", - "integrity": "sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-symbols": "^1.0.2" - } - }, - "is-shared-array-buffer": { - "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==", - "dev": true - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true - }, - "is-string": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.6.tgz", - "integrity": "sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w==", - "dev": true - }, - "is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true - }, - "is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "optional": true, - "requires": { - "is-docker": "^2.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true - }, - "istanbul-lib-coverage": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", - "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dev": true, - "requires": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "istanbul-lib-source-maps": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", - "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "dependencies": { - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "istanbul-reports": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", - "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } - }, - "jest": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest/-/jest-26.6.3.tgz", - "integrity": "sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q==", - "dev": true, - "requires": { - "@jest/core": "^26.6.3", - "import-local": "^3.0.2", - "jest-cli": "^26.6.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "jest-cli": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz", - "integrity": "sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg==", - "dev": true, - "requires": { - "@jest/core": "^26.6.3", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "import-local": "^3.0.2", - "is-ci": "^2.0.0", - "jest-config": "^26.6.3", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "prompts": "^2.0.1", - "yargs": "^15.4.1" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-changed-files": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.6.2.tgz", - "integrity": "sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "execa": "^4.0.0", - "throat": "^5.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - } - }, - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "jest-config": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz", - "integrity": "sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==", - "dev": true, - "requires": { - "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^26.6.3", - "@jest/types": "^26.6.2", - "babel-jest": "^26.6.3", - "chalk": "^4.0.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "jest-environment-jsdom": "^26.6.2", - "jest-environment-node": "^26.6.2", - "jest-get-type": "^26.3.0", - "jest-jasmine2": "^26.6.3", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "babel-jest": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz", - "integrity": "sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA==", - "dev": true, - "requires": { - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/babel__core": "^7.1.7", - "babel-plugin-istanbul": "^6.0.0", - "babel-preset-jest": "^26.6.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "slash": "^3.0.0" - } - }, - "babel-plugin-jest-hoist": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz", - "integrity": "sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw==", - "dev": true, - "requires": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", - "@types/babel__traverse": "^7.0.6" - } - }, - "babel-preset-jest": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz", - "integrity": "sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ==", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^26.6.2", - "babel-preset-current-node-syntax": "^1.0.0" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - } - } - }, - "jest-diff": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", - "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-docblock": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz", - "integrity": "sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==", - "dev": true, - "requires": { - "detect-newline": "^3.0.0" - } - }, - "jest-each": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.6.2.tgz", - "integrity": "sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", - "jest-util": "^26.6.2", - "pretty-format": "^26.6.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-environment-jsdom": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz", - "integrity": "sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q==", - "dev": true, - "requires": { - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2", - "jsdom": "^16.4.0" - } - }, - "jest-environment-node": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.6.2.tgz", - "integrity": "sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag==", - "dev": true, - "requires": { - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2" - } - }, - "jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "dev": true - }, - "jest-haste-map": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz", - "integrity": "sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.1.2", - "graceful-fs": "^4.2.4", - "jest-regex-util": "^26.0.0", - "jest-serializer": "^26.6.2", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "micromatch": "^4.0.2", - "sane": "^4.0.3", - "walker": "^1.0.7" - }, - "dependencies": { - "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - } - } - }, - "jest-jasmine2": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz", - "integrity": "sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg==", - "dev": true, - "requires": { - "@babel/traverse": "^7.1.0", - "@jest/environment": "^26.6.2", - "@jest/source-map": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^26.6.2", - "is-generator-fn": "^2.0.0", - "jest-each": "^26.6.2", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-runtime": "^26.6.3", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "pretty-format": "^26.6.2", - "throat": "^5.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-leak-detector": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz", - "integrity": "sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg==", - "dev": true, - "requires": { - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - } - }, - "jest-matcher-utils": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz", - "integrity": "sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-message-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz", - "integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@jest/types": "^26.6.2", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2", - "slash": "^3.0.0", - "stack-utils": "^2.0.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - } - } - }, - "jest-mock": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.6.2.tgz", - "integrity": "sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "@types/node": "*" - } - }, - "jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true - }, - "jest-regex-util": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", - "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==", - "dev": true - }, - "jest-resolve": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz", - "integrity": "sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^26.6.2", - "read-pkg-up": "^7.0.1", - "resolve": "^1.18.1", - "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "parse-json": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", - "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "requires": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "dependencies": { - "type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true - } - } - }, - "read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, - "requires": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - } - }, - "resolve": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", - "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", - "dev": true, - "requires": { - "is-core-module": "^2.1.0", - "path-parse": "^1.0.6" - } - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-resolve-dependencies": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz", - "integrity": "sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-snapshot": "^26.6.2" - } - }, - "jest-runner": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.6.3.tgz", - "integrity": "sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ==", - "dev": true, - "requires": { - "@jest/console": "^26.6.2", - "@jest/environment": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.7.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-config": "^26.6.3", - "jest-docblock": "^26.0.0", - "jest-haste-map": "^26.6.2", - "jest-leak-detector": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-resolve": "^26.6.2", - "jest-runtime": "^26.6.3", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "source-map-support": "^0.5.6", - "throat": "^5.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-runtime": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.6.3.tgz", - "integrity": "sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw==", - "dev": true, - "requires": { - "@jest/console": "^26.6.2", - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/globals": "^26.6.2", - "@jest/source-map": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0", - "cjs-module-lexer": "^0.6.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.4", - "jest-config": "^26.6.3", - "jest-haste-map": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-mock": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "slash": "^3.0.0", - "strip-bom": "^4.0.0", - "yargs": "^15.4.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-serializer": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.6.2.tgz", - "integrity": "sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==", - "dev": true, - "requires": { - "@types/node": "*", - "graceful-fs": "^4.2.4" - }, - "dependencies": { - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - } - } - }, - "jest-snapshot": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz", - "integrity": "sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0", - "@jest/types": "^26.6.2", - "@types/babel__traverse": "^7.0.4", - "@types/prettier": "^2.0.0", - "chalk": "^4.0.0", - "expect": "^26.6.2", - "graceful-fs": "^4.2.4", - "jest-diff": "^26.6.2", - "jest-get-type": "^26.3.0", - "jest-haste-map": "^26.6.2", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-resolve": "^26.6.2", - "natural-compare": "^1.4.0", - "pretty-format": "^26.6.2", - "semver": "^7.3.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", - "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^2.0.0", - "micromatch": "^4.0.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - } - } - }, - "jest-validate": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz", - "integrity": "sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "camelcase": "^6.0.0", - "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", - "leven": "^3.1.0", - "pretty-format": "^26.6.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-watcher": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.6.2.tgz", - "integrity": "sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ==", - "dev": true, - "requires": { - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "jest-util": "^26.6.2", - "string-length": "^4.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", - "dev": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true - }, - "jsdom": { - "version": "16.4.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.4.0.tgz", - "integrity": "sha512-lYMm3wYdgPhrl7pDcRmvzPhhrGVBeVhPIqeHjzeiHN3DFmD1RBpbExbi8vU7BJdH8VAZYovR8DMt0PNNDM7k8w==", - "dev": true, - "requires": { - "abab": "^2.0.3", - "acorn": "^7.1.1", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.2.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.0", - "domexception": "^2.0.1", - "escodegen": "^1.14.1", - "html-encoding-sniffer": "^2.0.1", - "is-potential-custom-element-name": "^1.0.0", - "nwsapi": "^2.2.0", - "parse5": "5.1.1", - "request": "^2.88.2", - "request-promise-native": "^1.0.8", - "saxes": "^5.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^3.0.1", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0", - "ws": "^7.2.3", - "xml-name-validator": "^3.0.0" - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "json-parse-better-errors": { - "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==", - "dev": true - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true - }, - "json-schema-traverse": { - "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==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, - "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true - }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", - "dev": true - }, - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", - "dev": true - }, - "lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", - "dev": true - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", - "dev": true - }, - "lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", - "dev": true - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - } - }, - "makeerror": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", - "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", - "dev": true, - "requires": { - "tmpl": "1.0.x" - } - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "requires": { - "object-visit": "^1.0.0" - } - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "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": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", - "dev": true - }, - "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", - "dev": true, - "requires": { - "mime-db": "1.44.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "requires": { - "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": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "node-environment-flags": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", - "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", - "dev": true, - "requires": { - "object.getownpropertydescriptors": "^2.0.3", - "semver": "^5.7.0" - } - }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", - "dev": true - }, - "node-modules-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", - "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", - "dev": true - }, - "node-notifier": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.1.tgz", - "integrity": "sha512-BvEXF+UmsnAfYfoapKM9nGxnP+Wn7P91YfXmrKnfcYCx6VBeoN5Ez5Ogck6I8Bi5k4RlpqRYaw75pAwzX9OphA==", - "dev": true, - "optional": true, - "requires": { - "growly": "^1.3.0", - "is-wsl": "^2.2.0", - "semver": "^7.3.2", - "shellwords": "^0.1.1", - "uuid": "^8.3.0", - "which": "^2.0.2" - }, - "dependencies": { - "semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", - "dev": true, - "optional": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "optional": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "node-releases": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", - "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", - "dev": true - }, - "normalize-package-data": { - "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==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "nwsapi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", - "dev": true - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "object-inspect": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.3.tgz", - "integrity": "sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "requires": { - "isobject": "^3.0.0" - } - }, - "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - } - }, - "object.getownpropertydescriptors": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.3.tgz", - "integrity": "sha512-VdDoCwvJI4QdC6ndjpqFmoL3/+HxffFBbcJzKi5hwLLqqx3mdbedRpfZDdK0SrOSauj8X4GzBvnDZl4vTN7dOw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - }, - "dependencies": { - "es-abstract": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", - "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", - "dev": true, - "requires": { - "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" - } - }, - "is-callable": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", - "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", - "dev": true - }, - "is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "object-inspect": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", - "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", - "dev": true - } - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "object.values": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.4.tgz", - "integrity": "sha512-TnGo7j4XSnKQoK3MfvkzqKCi0nVe/D9I9IjwTNYdb/fxYHpjrluHVOgw0AF6jrRFGMPHdfuidR09tIDiIvnaSg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.18.2" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "p-each-series": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", - "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", - "dev": true - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "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==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dev": true, - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", - "dev": true - }, - "parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", - "dev": true - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "requires": { - "pify": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } - } - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "dev": true - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - }, - "pirates": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", - "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", - "dev": true, - "requires": { - "node-modules-regexp": "^1.0.0" - } - }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "requires": { - "find-up": "^3.0.0" - } - }, - "pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-2.0.0.tgz", - "integrity": "sha1-yBmscoBZpGHKscOImivjxJoATX8=", - "dev": true, - "requires": { - "find-up": "^2.1.0" - }, - "dependencies": { - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - } - } - }, - "posix-character-classes": { - "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=", - "dev": true - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "prettier": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", - "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", - "dev": true - }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } - } - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "prompts": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.0.tgz", - "integrity": "sha512-awZAKrk3vN6CroQukBL+R9051a4R3zCZBlJm/HBfrSZ8iTpYix3VX1vU4mveiLpiwmOJT4wokTF9m6HUk4KqWQ==", - "dev": true, - "requires": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - } - }, - "psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "dev": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true - }, - "react-is": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", - "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==", - "dev": true - }, - "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "dev": true, - "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - } - }, - "read-pkg-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", - "dev": true, - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" - }, - "dependencies": { - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - } - } - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "optional": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", - "dev": true, - "requires": { - "resolve": "^1.1.6" - } - }, - "regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true - }, - "regenerate-unicode-properties": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-9.0.0.tgz", - "integrity": "sha512-3E12UeNSPfjrgwjkR81m5J7Aw/T55Tu7nUyZVQYCKEOs+2dkxEY+DpPtZzO4YruuiPb7NkYLVcyJC4+zCbk5pA==", - "dev": true, - "requires": { - "regenerate": "^1.4.2" - } - }, - "regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", - "dev": true - }, - "regenerator-transform": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", - "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", - "dev": true, - "requires": { - "@babel/runtime": "^7.8.4" - } - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, - "regexpu-core": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.8.0.tgz", - "integrity": "sha512-1F6bYsoYiz6is+oz70NWur2Vlh9KWtswuRuzJOfeYUrfPX2o8n74AnUVaOGDbUqVGO9fNHu48/pjJO4sNVwsOg==", - "dev": true, - "requires": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^9.0.0", - "regjsgen": "^0.5.2", - "regjsparser": "^0.7.0", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.0.0" - } - }, - "regjsgen": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", - "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==", - "dev": true - }, - "regjsparser": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.7.0.tgz", - "integrity": "sha512-A4pcaORqmNMDVwUjWoTzuhwMGpP+NykpfqAsEgI1FSH/EzC7lrN5TMd+kN8YCovX+jMpu8eaqXgXPCa0g8FQNQ==", - "dev": true, - "requires": { - "jsesc": "~0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true - } - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "dev": true, - "requires": { - "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" - }, - "dependencies": { - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true - } - } - }, - "request-promise-core": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", - "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", - "dev": true, - "requires": { - "lodash": "^4.17.19" - } - }, - "request-promise-native": { - "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==", - "dev": true, - "requires": { - "request-promise-core": "1.1.4", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - }, - "dependencies": { - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - } - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true - }, - "require-main-filename": { - "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==", - "dev": true - }, - "resolve": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.14.2.tgz", - "integrity": "sha512-EjlOBLBO1kxsUxsKjLt7TAECyKW6fOh1VRkykQkKGzcBbjjPIxBqGh0jf7GJ3k/f5mxMqW3htMD3WdTUVtW8HQ==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - } - }, - "resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "rsvp": { - "version": "4.8.5", - "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", - "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", - "dev": true - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "requires": { - "ret": "~0.1.10" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "sane": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", - "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", - "dev": true, - "requires": { - "@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" - } - }, - "saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "dev": true, - "requires": { - "xmlchars": "^2.2.0" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "requires": { - "kind-of": "^6.0.2" - } - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "shelljs": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", - "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", - "dev": true, - "requires": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - } - }, - "shellwords": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", - "dev": true, - "optional": true - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true - }, - "sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true - }, - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } - } - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "requires": { - "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" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "source-map-resolve": { - "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==", - "dev": true, - "requires": { - "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": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true - }, - "spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz", - "integrity": "sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw==", - "dev": true - }, - "split-string": { - "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==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.0" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "dev": true, - "requires": { - "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": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw==", - "dev": true, - "requires": { - "escape-string-regexp": "^2.0.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true - } - } - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", - "dev": true - }, - "string-length": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.1.tgz", - "integrity": "sha512-PKyXUd0LK0ePjSOnWn34V2uD6acUWev9uy0Ft05k0E8xRW+SKcA0F7eMr7h5xlzfn+4O3N+55rduYyet3Jk+jw==", - "dev": true, - "requires": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - } - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "string.prototype.trimend": { - "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==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "supports-hyperlinks": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.1.0.tgz", - "integrity": "sha512-zoE5/e+dnEijk6ASB6/qrK+oYdm2do1hjoLWrqUC/8WEIW1gbxFcKuBof7sW8ArN6e+AYvsE8HBGiVRWL/F5CA==", - "dev": true, - "requires": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "symbol-tree": { - "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==", - "dev": true - }, - "table": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz", - "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==", - "dev": true, - "requires": { - "ajv": "^8.0.1", - "lodash.clonedeep": "^4.5.0", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ajv": { - "version": "8.6.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.2.tgz", - "integrity": "sha512-9807RlWAgT564wT+DjeyU5OFMPjmzxVobvDFmNAhY+5zD6A2ly3jDp6sgnfyDtlIQ+7H97oc/DGCzzfu9rjw9w==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - } - } - }, - "terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - } - }, - "test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "throat": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", - "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", - "dev": true - }, - "tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - }, - "tough-cookie": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", - "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", - "dev": true, - "requires": { - "ip-regex": "^2.1.0", - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, - "tr46": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz", - "integrity": "sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==", - "dev": true, - "requires": { - "punycode": "^2.1.1" - } - }, - "tsconfig-paths": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.11.0.tgz", - "integrity": "sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA==", - "dev": true, - "requires": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", - "minimist": "^1.2.0", - "strip-bom": "^3.0.0" - }, - "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - } - } - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "requires": { - "is-typedarray": "^1.0.0" - } - }, - "unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", - "which-boxed-primitive": "^1.0.2" - } - }, - "unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "dev": true - }, - "unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, - "requires": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - } - }, - "unicode-match-property-value-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", - "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", - "dev": true - }, - "unicode-property-aliases-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz", - "integrity": "sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==", - "dev": true - }, - "union-value": { - "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==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - } - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true - } - } - }, - "uri-js": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", - "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true - }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "optional": true - }, - "v8-compile-cache": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", - "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", - "dev": true - }, - "v8-to-istanbul": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.0.0.tgz", - "integrity": "sha512-fLL2rFuQpMtm9r8hrAV2apXX/WqHJ6+IC4/eQVdMDGBUgH/YMV4Gv3duk3kjmyg6uiQWBAA9nJwue4iJUOkHeA==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" - }, - "dependencies": { - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - } - } - }, - "v8flags": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", - "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", - "dev": true, - "requires": { - "homedir-polyfill": "^1.0.1" - } - }, - "validate-npm-package-license": { - "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==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "requires": { - "browser-process-hrtime": "^1.0.0" - } - }, - "w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", - "dev": true, - "requires": { - "xml-name-validator": "^3.0.0" - } - }, - "walker": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", - "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", - "dev": true, - "requires": { - "makeerror": "1.0.x" - } - }, - "webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "dev": true - }, - "whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "requires": { - "iconv-lite": "0.4.24" - } - }, - "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, - "whatwg-url": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.4.0.tgz", - "integrity": "sha512-vwTUFf6V4zhcPkWp/4CQPr1TW9Ml6SF4lVyaIMBdJw5i6qUUJ1QWM4Z6YYVkfka0OUIzVo/0aNtGVGk256IKWw==", - "dev": true, - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^2.0.2", - "webidl-conversions": "^6.1.0" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "requires": { - "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": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "word-wrap": { - "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==", - "dev": true - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "ws": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", - "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", - "dev": true - }, - "xml-name-validator": { - "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==", - "dev": true - }, - "xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true - }, - "y18n": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", - "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.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": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "dependencies": { - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - } - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } -} diff --git a/package.json b/package.json index d0f0259e99..bddbaa23ce 100644 --- a/package.json +++ b/package.json @@ -13,27 +13,27 @@ "url": "https://github.com/exercism/javascript" }, "devDependencies": { - "@babel/cli": "^7.16.8", - "@babel/core": "^7.16.12", - "@babel/node": "^7.16.8", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/preset-env": "^7.16.11", - "@exercism/eslint-config-javascript": "^0.5.0", - "@types/jest": "^26.0.24", - "@types/node": "^16.11.21", - "babel-jest": "^27.4.6", - "chalk": "^4.1.2", - "core-js": "^3.20.3", - "diff": "^5.0.0", - "eslint": "^7.32.0", - "jest": "^26.6.3", - "prettier": "^2.5.1", - "shelljs": "^0.8.5" + "@exercism/babel-preset-javascript": "^0.5.1", + "@exercism/eslint-config-javascript": "^0.8.1", + "@jest/globals": "^29.7.0", + "@types/node": "^24.3.0", + "@types/shelljs": "^0.8.17", + "babel-jest": "^29.7.0", + "core-js": "~3.42.0", + "diff": "^8.0.2", + "eslint": "^9.28.0", + "expect": "^29.7.0", + "globals": "^16.3.0", + "jest": "^29.7.0", + "prettier": "^3.6.2", + "shelljs": "^0.10.0" }, "dependencies": {}, "scripts": { - "test": "jest --no-cache ./*", - "watch": "jest --no-cache --watch ./*", - "lint": "eslint ." - } + "lint": "corepack pnpm eslint .", + "test": "corepack pnpm jest", + "watch": "corepack pnpm jest --watch", + "format": "corepack pnpm prettier -w ." + }, + "packageManager": "pnpm@9.15.2" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000000..62c71f38fb --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,5588 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@exercism/babel-preset-javascript': + specifier: ^0.5.1 + version: 0.5.1 + '@exercism/eslint-config-javascript': + specifier: ^0.8.1 + version: 0.8.1(@babel/core@7.25.8)(@exercism/babel-preset-javascript@0.5.1)(eslint@9.28.0)(jest@29.7.0(@types/node@24.3.0))(typescript@5.6.3) + '@jest/globals': + specifier: ^29.7.0 + version: 29.7.0 + '@types/node': + specifier: ^24.3.0 + version: 24.3.0 + '@types/shelljs': + specifier: ^0.8.17 + version: 0.8.17 + babel-jest: + specifier: ^29.7.0 + version: 29.7.0(@babel/core@7.25.8) + core-js: + specifier: ~3.42.0 + version: 3.42.0 + diff: + specifier: ^8.0.2 + version: 8.0.2 + eslint: + specifier: ^9.28.0 + version: 9.28.0 + expect: + specifier: ^29.7.0 + version: 29.7.0 + globals: + specifier: ^16.3.0 + version: 16.3.0 + jest: + specifier: ^29.7.0 + version: 29.7.0(@types/node@24.3.0) + prettier: + specifier: ^3.6.2 + version: 3.6.2 + shelljs: + specifier: ^0.10.0 + version: 0.10.0 + +packages: + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@babel/code-frame@7.25.7': + resolution: {integrity: sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.25.8': + resolution: {integrity: sha512-ZsysZyXY4Tlx+Q53XdnOFmqwfB9QDTHYxaZYajWRoBLuLEAwI2UIbtxOjWh/cFaa9IKUlcB+DDuoskLuKu56JA==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.25.8': + resolution: {integrity: sha512-Oixnb+DzmRT30qu9d3tJSQkxuygWm32DFykT4bRoORPa9hZ/L4KhVB/XiRm6KG+roIEM7DBQlmg27kw2HZkdZg==} + engines: {node: '>=6.9.0'} + + '@babel/eslint-parser@7.25.9': + resolution: {integrity: sha512-5UXfgpK0j0Xr/xIdgdLEhOFxaDZ0bRPWJJchRpqOSur/3rZoPbqqki5mm0p4NE2cs28krBEiSM2MB7//afRSQQ==} + engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0} + peerDependencies: + '@babel/core': ^7.11.0 + eslint: ^7.5.0 || ^8.0.0 || ^9.0.0 + + '@babel/eslint-plugin@7.25.9': + resolution: {integrity: sha512-MWg1lz+JiP9l1fXkE0qCUVo+1XwgNRPs6GTc88hmw6qN3AdgmfTSkyHt0e1xOTsKdXW5xlh2Lsk3wrFZbW5rzQ==} + engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0} + peerDependencies: + '@babel/eslint-parser': ^7.11.0 + eslint: ^7.5.0 || ^8.0.0 || ^9.0.0 + + '@babel/generator@7.25.7': + resolution: {integrity: sha512-5Dqpl5fyV9pIAD62yK9P7fcA768uVPUyrQmqpqstHWgMma4feF1x/oFysBCVZLY5wJ2GkMUCdsNDnGZrPoR6rA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.25.7': + resolution: {integrity: sha512-4xwU8StnqnlIhhioZf1tqnVWeQ9pvH/ujS8hRfw/WOza+/a+1qv69BWNy+oY231maTCWgKWhfBU7kDpsds6zAA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-builder-binary-assignment-operator-visitor@7.25.7': + resolution: {integrity: sha512-12xfNeKNH7jubQNm7PAkzlLwEmCs1tfuX3UjIw6vP6QXi+leKh6+LyC/+Ed4EIQermwd58wsyh070yjDHFlNGg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.25.7': + resolution: {integrity: sha512-DniTEax0sv6isaw6qSQSfV4gVRNtw2rte8HHM45t9ZR0xILaufBRNkpMifCRiAPyvL4ACD6v0gfCwCmtOQaV4A==} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.25.7': + resolution: {integrity: sha512-bD4WQhbkx80mAyj/WCm4ZHcF4rDxkoLFO6ph8/5/mQ3z4vAzltQXAmbc7GvVJx5H+lk5Mi5EmbTeox5nMGCsbw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-create-regexp-features-plugin@7.25.7': + resolution: {integrity: sha512-byHhumTj/X47wJ6C6eLpK7wW/WBEcnUeb7D0FNc/jFQnQVw7DOso3Zz5u9x/zLrFVkHa89ZGDbkAa1D54NdrCQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-define-polyfill-provider@0.6.2': + resolution: {integrity: sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + '@babel/helper-member-expression-to-functions@7.25.7': + resolution: {integrity: sha512-O31Ssjd5K6lPbTX9AAYpSKrZmLeagt9uwschJd+Ixo6QiRyfpvgtVQp8qrDR9UNFjZ8+DO34ZkdrN+BnPXemeA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.25.7': + resolution: {integrity: sha512-o0xCgpNmRohmnoWKQ0Ij8IdddjyBFE4T2kagL/x6M3+4zUgc+4qTOUBoNe4XxDskt1HPKO007ZPiMgLDq2s7Kw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.25.7': + resolution: {integrity: sha512-k/6f8dKG3yDz/qCwSM+RKovjMix563SLxQFo0UhRNo239SP6n9u5/eLtKD6EAjwta2JHJ49CsD8pms2HdNiMMQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.25.7': + resolution: {integrity: sha512-VAwcwuYhv/AT+Vfr28c9y6SHzTan1ryqrydSTFGjU0uDJHw3uZ+PduI8plCLkRsDnqK2DMEDmwrOQRsK/Ykjng==} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.25.7': + resolution: {integrity: sha512-eaPZai0PiqCi09pPs3pAFfl/zYgGaE6IdXtYvmf0qlcDTd3WCtO7JWCcRd64e0EQrcYgiHibEZnOGsSY4QSgaw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-remap-async-to-generator@7.25.7': + resolution: {integrity: sha512-kRGE89hLnPfcz6fTrlNU+uhgcwv0mBE4Gv3P9Ke9kLVJYpi4AMVVEElXvB5CabrPZW4nCM8P8UyyjrzCM0O2sw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-replace-supers@7.25.7': + resolution: {integrity: sha512-iy8JhqlUW9PtZkd4pHM96v6BdJ66Ba9yWSE4z0W4TvSZwLBPkyDsiIU3ENe4SmrzRBs76F7rQXTy1lYC49n6Lw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-simple-access@7.25.7': + resolution: {integrity: sha512-FPGAkJmyoChQeM+ruBGIDyrT2tKfZJO8NcxdC+CWNJi7N8/rZpSxK7yvBJ5O/nF1gfu5KzN7VKG3YVSLFfRSxQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-skip-transparent-expression-wrappers@7.25.7': + resolution: {integrity: sha512-pPbNbchZBkPMD50K0p3JGcFMNLVUCuU/ABybm/PGNj4JiHrpmNyqqCphBk4i19xXtNV0JhldQJJtbSW5aUvbyA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.25.7': + resolution: {integrity: sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.25.7': + resolution: {integrity: sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.25.7': + resolution: {integrity: sha512-ytbPLsm+GjArDYXJ8Ydr1c/KJuutjF2besPNbIZnZ6MKUxi/uTA22t2ymmA4WFjZFpjiAMO0xuuJPqK2nvDVfQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-wrap-function@7.25.7': + resolution: {integrity: sha512-MA0roW3JF2bD1ptAaJnvcabsVlNQShUaThyJbCDD4bCp8NEgiFvpoqRI2YS22hHlc2thjO/fTg2ShLMC3jygAg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.25.7': + resolution: {integrity: sha512-Sv6pASx7Esm38KQpF/U/OXLwPPrdGHNKoeblRxgZRLXnAtnkEe4ptJPDtAZM7fBLadbc1Q07kQpSiGQ0Jg6tRA==} + engines: {node: '>=6.9.0'} + + '@babel/highlight@7.25.7': + resolution: {integrity: sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==} + engines: {node: '>=6.9.0'} + + '@babel/node@7.25.7': + resolution: {integrity: sha512-SLrRogiTuneH3mZeZQtWBECyVRtznezYdnH4UjatZjHrk/QP+GH9bqsToCWp23pPeA20NO1/p8kECzWt5TTpUA==} + engines: {node: '>=6.9.0'} + hasBin: true + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/parser@7.25.8': + resolution: {integrity: sha512-HcttkxzdPucv3nNFmfOOMfFf64KgdJVqm1KaCm25dPGMLElo9nsLvXeJECQg8UzPuBGLyTSA0ZzqCtDSzKTEoQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.7': + resolution: {integrity: sha512-UV9Lg53zyebzD1DwQoT9mzkEKa922LNUp5YkTJ6Uta0RbyXaQNUgcvSt7qIu1PpPzVb6rd10OVNTzkyBGeVmxQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.7': + resolution: {integrity: sha512-GDDWeVLNxRIkQTnJn2pDOM1pkCgYdSqPeT1a9vh9yIqu2uzzgw1zcqEb+IJOhy+dTBMlNdThrDIksr2o09qrrQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.7': + resolution: {integrity: sha512-wxyWg2RYaSUYgmd9MR0FyRGyeOMQE/Uzr1wzd/g5cf5bwi9A4v6HFdDm7y1MgDtod/fLOSTZY6jDgV0xU9d5bA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.7': + resolution: {integrity: sha512-Xwg6tZpLxc4iQjorYsyGMyfJE7nP5MV8t/Ka58BgiA7Jw0fRqQNcANlLfdJ/yvBt9z9LD2We+BEkT7vLqZRWng==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.13.0 + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.7': + resolution: {integrity: sha512-UVATLMidXrnH+GMUIuxq55nejlj02HP7F5ETyBONzP6G87fPBogG4CH6kxrSrdIuAjdwNO9VzyaYsrZPscWUrw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2': + resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-async-generators@7.8.4': + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-bigint@7.8.3': + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-properties@7.12.13': + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-static-block@7.14.5': + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-assertions@7.25.7': + resolution: {integrity: sha512-ZvZQRmME0zfJnDQnVBKYzHxXT7lYBB3Revz1GuS7oLXWMgqUPX4G+DDbT30ICClht9WKV34QVrZhSw6WdklwZQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.25.7': + resolution: {integrity: sha512-AqVo+dguCgmpi/3mYBdu9lkngOBlQ2w2vnNpa6gfiCxQZLzV4ZbhsXitJ2Yblkoe1VQwtHSaNmIaGll/26YWRw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-meta@7.10.4': + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-json-strings@7.8.3': + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.25.7': + resolution: {integrity: sha512-ruZOnKO+ajVL/MVx+PwNBPOkrnXTXoWMtte1MBpegfCArhqOe3Bj52avVj1huLLxNKYKXYaSxZ2F+woK1ekXfw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4': + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-numeric-separator@7.10.4': + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-object-rest-spread@7.8.3': + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3': + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-chaining@7.8.3': + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-private-property-in-object@7.14.5': + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-top-level-await@7.14.5': + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.25.7': + resolution: {integrity: sha512-rR+5FDjpCHqqZN2bzZm18bVYGaejGq5ZkpVCJLXor/+zlSrSoc4KWcHI0URVWjl/68Dyr1uwZUz/1njycEAv9g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6': + resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-arrow-functions@7.25.7': + resolution: {integrity: sha512-EJN2mKxDwfOUCPxMO6MUI58RN3ganiRAG/MS/S3HfB6QFNjroAMelQo/gybyYq97WerCBAZoyrAoW8Tzdq2jWg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-generator-functions@7.25.8': + resolution: {integrity: sha512-9ypqkozyzpG+HxlH4o4gdctalFGIjjdufzo7I2XPda0iBnZ6a+FO0rIEQcdSPXp02CkvGsII1exJhmROPQd5oA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-to-generator@7.25.7': + resolution: {integrity: sha512-ZUCjAavsh5CESCmi/xCpX1qcCaAglzs/7tmuvoFnJgA1dM7gQplsguljoTg+Ru8WENpX89cQyAtWoaE0I3X3Pg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoped-functions@7.25.7': + resolution: {integrity: sha512-xHttvIM9fvqW+0a3tZlYcZYSBpSWzGBFIt/sYG3tcdSzBB8ZeVgz2gBP7Df+sM0N1850jrviYSSeUuc+135dmQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoping@7.25.7': + resolution: {integrity: sha512-ZEPJSkVZaeTFG/m2PARwLZQ+OG0vFIhPlKHK/JdIMy8DbRJ/htz6LRrTFtdzxi9EHmcwbNPAKDnadpNSIW+Aow==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-properties@7.25.7': + resolution: {integrity: sha512-mhyfEW4gufjIqYFo9krXHJ3ElbFLIze5IDp+wQTxoPd+mwFb1NxatNAwmv8Q8Iuxv7Zc+q8EkiMQwc9IhyGf4g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-static-block@7.25.8': + resolution: {integrity: sha512-e82gl3TCorath6YLf9xUwFehVvjvfqFhdOo4+0iVIVju+6XOi5XHkqB3P2AXnSwoeTX0HBoXq5gJFtvotJzFnQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.12.0 + + '@babel/plugin-transform-classes@7.25.7': + resolution: {integrity: sha512-9j9rnl+YCQY0IGoeipXvnk3niWicIB6kCsWRGLwX241qSXpbA4MKxtp/EdvFxsc4zI5vqfLxzOd0twIJ7I99zg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-computed-properties@7.25.7': + resolution: {integrity: sha512-QIv+imtM+EtNxg/XBKL3hiWjgdLjMOmZ+XzQwSgmBfKbfxUjBzGgVPklUuE55eq5/uVoh8gg3dqlrwR/jw3ZeA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-destructuring@7.25.7': + resolution: {integrity: sha512-xKcfLTlJYUczdaM1+epcdh1UGewJqr9zATgrNHcLBcV2QmfvPPEixo/sK/syql9cEmbr7ulu5HMFG5vbbt/sEA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-dotall-regex@7.25.7': + resolution: {integrity: sha512-kXzXMMRzAtJdDEgQBLF4oaiT6ZCU3oWHgpARnTKDAqPkDJ+bs3NrZb310YYevR5QlRo3Kn7dzzIdHbZm1VzJdQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-keys@7.25.7': + resolution: {integrity: sha512-by+v2CjoL3aMnWDOyCIg+yxU9KXSRa9tN6MbqggH5xvymmr9p4AMjYkNlQy4brMceBnUyHZ9G8RnpvT8wP7Cfg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.7': + resolution: {integrity: sha512-HvS6JF66xSS5rNKXLqkk7L9c/jZ/cdIVIcoPVrnl8IsVpLggTjXs8OWekbLHs/VtYDDh5WXnQyeE3PPUGm22MA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-dynamic-import@7.25.8': + resolution: {integrity: sha512-gznWY+mr4ZQL/EWPcbBQUP3BXS5FwZp8RUOw06BaRn8tQLzN4XLIxXejpHN9Qo8x8jjBmAAKp6FoS51AgkSA/A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-exponentiation-operator@7.25.7': + resolution: {integrity: sha512-yjqtpstPfZ0h/y40fAXRv2snciYr0OAoMXY/0ClC7tm4C/nG5NJKmIItlaYlLbIVAWNfrYuy9dq1bE0SbX0PEg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-export-namespace-from@7.25.8': + resolution: {integrity: sha512-sPtYrduWINTQTW7FtOy99VCTWp4H23UX7vYcut7S4CIMEXU+54zKX9uCoGkLsWXteyaMXzVHgzWbLfQ1w4GZgw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-for-of@7.25.7': + resolution: {integrity: sha512-n/TaiBGJxYFWvpJDfsxSj9lEEE44BFM1EPGz4KEiTipTgkoFVVcCmzAL3qA7fdQU96dpo4gGf5HBx/KnDvqiHw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-function-name@7.25.7': + resolution: {integrity: sha512-5MCTNcjCMxQ63Tdu9rxyN6cAWurqfrDZ76qvVPrGYdBxIj+EawuuxTu/+dgJlhK5eRz3v1gLwp6XwS8XaX2NiQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-json-strings@7.25.8': + resolution: {integrity: sha512-4OMNv7eHTmJ2YXs3tvxAfa/I43di+VcF+M4Wt66c88EAED1RoGaf1D64cL5FkRpNL+Vx9Hds84lksWvd/wMIdA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-literals@7.25.7': + resolution: {integrity: sha512-fwzkLrSu2fESR/cm4t6vqd7ebNIopz2QHGtjoU+dswQo/P6lwAG04Q98lliE3jkz/XqnbGFLnUcE0q0CVUf92w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-logical-assignment-operators@7.25.8': + resolution: {integrity: sha512-f5W0AhSbbI+yY6VakT04jmxdxz+WsID0neG7+kQZbCOjuyJNdL5Nn4WIBm4hRpKnUcO9lP0eipUhFN12JpoH8g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-member-expression-literals@7.25.7': + resolution: {integrity: sha512-Std3kXwpXfRV0QtQy5JJcRpkqP8/wG4XL7hSKZmGlxPlDqmpXtEPRmhF7ztnlTCtUN3eXRUJp+sBEZjaIBVYaw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-amd@7.25.7': + resolution: {integrity: sha512-CgselSGCGzjQvKzghCvDTxKHP3iooenLpJDO842ehn5D2G5fJB222ptnDwQho0WjEvg7zyoxb9P+wiYxiJX5yA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-commonjs@7.25.7': + resolution: {integrity: sha512-L9Gcahi0kKFYXvweO6n0wc3ZG1ChpSFdgG+eV1WYZ3/dGbJK7vvk91FgGgak8YwRgrCuihF8tE/Xg07EkL5COg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-systemjs@7.25.7': + resolution: {integrity: sha512-t9jZIvBmOXJsiuyOwhrIGs8dVcD6jDyg2icw1VL4A/g+FnWyJKwUfSSU2nwJuMV2Zqui856El9u+ElB+j9fV1g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-umd@7.25.7': + resolution: {integrity: sha512-p88Jg6QqsaPh+EB7I9GJrIqi1Zt4ZBHUQtjw3z1bzEXcLh6GfPqzZJ6G+G1HBGKUNukT58MnKG7EN7zXQBCODw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-named-capturing-groups-regex@7.25.7': + resolution: {integrity: sha512-BtAT9LzCISKG3Dsdw5uso4oV1+v2NlVXIIomKJgQybotJY3OwCwJmkongjHgwGKoZXd0qG5UZ12JUlDQ07W6Ow==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-new-target@7.25.7': + resolution: {integrity: sha512-CfCS2jDsbcZaVYxRFo2qtavW8SpdzmBXC2LOI4oO0rP+JSRDxxF3inF4GcPsLgfb5FjkhXG5/yR/lxuRs2pySA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-nullish-coalescing-operator@7.25.8': + resolution: {integrity: sha512-Z7WJJWdQc8yCWgAmjI3hyC+5PXIubH9yRKzkl9ZEG647O9szl9zvmKLzpbItlijBnVhTUf1cpyWBsZ3+2wjWPQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-numeric-separator@7.25.8': + resolution: {integrity: sha512-rm9a5iEFPS4iMIy+/A/PiS0QN0UyjPIeVvbU5EMZFKJZHt8vQnasbpo3T3EFcxzCeYO0BHfc4RqooCZc51J86Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-rest-spread@7.25.8': + resolution: {integrity: sha512-LkUu0O2hnUKHKE7/zYOIjByMa4VRaV2CD/cdGz0AxU9we+VA3kDDggKEzI0Oz1IroG+6gUP6UmWEHBMWZU316g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-super@7.25.7': + resolution: {integrity: sha512-pWT6UXCEW3u1t2tcAGtE15ornCBvopHj9Bps9D2DsH15APgNVOTwwczGckX+WkAvBmuoYKRCFa4DK+jM8vh5AA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-catch-binding@7.25.8': + resolution: {integrity: sha512-EbQYweoMAHOn7iJ9GgZo14ghhb9tTjgOc88xFgYngifx7Z9u580cENCV159M4xDh3q/irbhSjZVpuhpC2gKBbg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-chaining@7.25.8': + resolution: {integrity: sha512-q05Bk7gXOxpTHoQ8RSzGSh/LHVB9JEIkKnk3myAWwZHnYiTGYtbdrYkIsS8Xyh4ltKf7GNUSgzs/6P2bJtBAQg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-parameters@7.25.7': + resolution: {integrity: sha512-FYiTvku63me9+1Nz7TOx4YMtW3tWXzfANZtrzHhUZrz4d47EEtMQhzFoZWESfXuAMMT5mwzD4+y1N8ONAX6lMQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-methods@7.25.7': + resolution: {integrity: sha512-KY0hh2FluNxMLwOCHbxVOKfdB5sjWG4M183885FmaqWWiGMhRZq4DQRKH6mHdEucbJnyDyYiZNwNG424RymJjA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-property-in-object@7.25.8': + resolution: {integrity: sha512-8Uh966svuB4V8RHHg0QJOB32QK287NBksJOByoKmHMp1TAobNniNalIkI2i5IPj5+S9NYCG4VIjbEuiSN8r+ow==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-property-literals@7.25.7': + resolution: {integrity: sha512-lQEeetGKfFi0wHbt8ClQrUSUMfEeI3MMm74Z73T9/kuz990yYVtfofjf3NuA42Jy3auFOpbjDyCSiIkTs1VIYw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regenerator@7.25.7': + resolution: {integrity: sha512-mgDoQCRjrY3XK95UuV60tZlFCQGXEtMg8H+IsW72ldw1ih1jZhzYXbJvghmAEpg5UVhhnCeia1CkGttUvCkiMQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-reserved-words@7.25.7': + resolution: {integrity: sha512-3OfyfRRqiGeOvIWSagcwUTVk2hXBsr/ww7bLn6TRTuXnexA+Udov2icFOxFX9abaj4l96ooYkcNN1qi2Zvqwng==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-shorthand-properties@7.25.7': + resolution: {integrity: sha512-uBbxNwimHi5Bv3hUccmOFlUy3ATO6WagTApenHz9KzoIdn0XeACdB12ZJ4cjhuB2WSi80Ez2FWzJnarccriJeA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-spread@7.25.7': + resolution: {integrity: sha512-Mm6aeymI0PBh44xNIv/qvo8nmbkpZze1KvR8MkEqbIREDxoiWTi18Zr2jryfRMwDfVZF9foKh060fWgni44luw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-sticky-regex@7.25.7': + resolution: {integrity: sha512-ZFAeNkpGuLnAQ/NCsXJ6xik7Id+tHuS+NT+ue/2+rn/31zcdnupCdmunOizEaP0JsUmTFSTOPoQY7PkK2pttXw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-template-literals@7.25.7': + resolution: {integrity: sha512-SI274k0nUsFFmyQupiO7+wKATAmMFf8iFgq2O+vVFXZ0SV9lNfT1NGzBEhjquFmD8I9sqHLguH+gZVN3vww2AA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typeof-symbol@7.25.7': + resolution: {integrity: sha512-OmWmQtTHnO8RSUbL0NTdtpbZHeNTnm68Gj5pA4Y2blFNh+V4iZR68V1qL9cI37J21ZN7AaCnkfdHtLExQPf2uA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-escapes@7.25.7': + resolution: {integrity: sha512-BN87D7KpbdiABA+t3HbVqHzKWUDN3dymLaTnPFAMyc8lV+KN3+YzNhVRNdinaCPA4AUqx7ubXbQ9shRjYBl3SQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-property-regex@7.25.7': + resolution: {integrity: sha512-IWfR89zcEPQGB/iB408uGtSPlQd3Jpq11Im86vUgcmSTcoWAiQMCTOa2K2yNNqFJEBVICKhayctee65Ka8OB0w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-regex@7.25.7': + resolution: {integrity: sha512-8JKfg/hiuA3qXnlLx8qtv5HWRbgyFx2hMMtpDDuU2rTckpKkGu4ycK5yYHwuEa16/quXfoxHBIApEsNyMWnt0g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-sets-regex@7.25.7': + resolution: {integrity: sha512-YRW8o9vzImwmh4Q3Rffd09bH5/hvY0pxg+1H1i0f7APoUeg12G7+HhLj9ZFNIrYkgBXhIijPJ+IXypN0hLTIbw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/preset-env@7.25.8': + resolution: {integrity: sha512-58T2yulDHMN8YMUxiLq5YmWUnlDCyY1FsHM+v12VMx+1/FlrUj5tY50iDCpofFQEM8fMYOaY9YRvym2jcjn1Dg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-modules@0.1.6-no-external-plugins': + resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} + peerDependencies: + '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 + + '@babel/register@7.25.7': + resolution: {integrity: sha512-qHTd2Rhn/rKhSUwdY6+n98FmwXN+N+zxSVx3zWqRe9INyvTpv+aQ5gDV2+43ACd3VtMBzPPljbb0gZb8u5ma6Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.25.7': + resolution: {integrity: sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.25.7': + resolution: {integrity: sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.25.7': + resolution: {integrity: sha512-jatJPT1Zjqvh/1FyJs6qAHL+Dzb7sTb+xr7Q+gM1b+1oBsMsQQ4FkVKb6dFlJvLlVssqkRzV05Jzervt9yhnzg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.25.8': + resolution: {integrity: sha512-JWtuCu8VQsMladxVz/P4HzHUGCAwpuqacmowgXFs5XjxIgKuNjnLokQzuVjlTvIzODaDmpjT3oxcC48vyk9EWg==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + + '@eslint-community/eslint-utils@4.7.0': + resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.20.0': + resolution: {integrity: sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.2.2': + resolution: {integrity: sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.14.0': + resolution: {integrity: sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.17.0': + resolution: {integrity: sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.28.0': + resolution: {integrity: sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.6': + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.3.1': + resolution: {integrity: sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@exercism/babel-preset-javascript@0.5.1': + resolution: {integrity: sha512-6NywGKngMLmuDhDVLov1fm6O8MTtirKfQlDmg3q/3cnP4ElErtqzyOoBoI4Om54hHrTfHXlw+UQxQ7NkKeRAfA==} + + '@exercism/eslint-config-javascript@0.8.1': + resolution: {integrity: sha512-KFk43KvV4lUArh/1RUmFMTGXWGp6Pqqs3eXlDXpHQ7xhBKUatbTIL7xbhUB8o366DDyqkcmlxnhOnDsbnL66Qg==} + peerDependencies: + '@exercism/babel-preset-javascript': '>= 0.5.1' + eslint: '>= 9.17' + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} + engines: {node: '>=18.18'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@isaacs/balanced-match@4.0.1': + resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} + engines: {node: 20 || >=22} + + '@isaacs/brace-expansion@5.0.0': + resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} + engines: {node: 20 || >=22} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@istanbuljs/load-nyc-config@1.1.0': + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + engines: {node: '>=8'} + + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + + '@jest/console@29.7.0': + resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/core@29.7.0': + resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/environment@29.7.0': + resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect-utils@29.7.0': + resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect@29.7.0': + resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/fake-timers@29.7.0': + resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/globals@29.7.0': + resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/reporters@29.7.0': + resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/schemas@29.6.3': + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/source-map@29.6.3': + resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/test-result@29.7.0': + resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/test-sequencer@29.7.0': + resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/transform@29.7.0': + resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/types@29.6.3': + resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jridgewell/gen-mapping@0.3.5': + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': + resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@sinclair/typebox@0.27.8': + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + + '@sinonjs/commons@3.0.1': + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + + '@sinonjs/fake-timers@10.3.0': + resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.6.8': + resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.20.6': + resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} + + '@types/estree@1.0.7': + resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + + '@types/graceful-fs@4.1.9': + resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} + + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@24.3.0': + resolution: {integrity: sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==} + + '@types/shelljs@0.8.17': + resolution: {integrity: sha512-IDksKYmQA2W9MkQjiyptbMmcQx+8+Ol6b7h6dPU5S05JyiQDSb/nZKnrMrZqGwgV6VkVdl6/SPCKPDlMRvqECg==} + + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.33': + resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} + + '@typescript-eslint/scope-manager@8.10.0': + resolution: {integrity: sha512-AgCaEjhfql9MDKjMUxWvH7HjLeBqMCBfIaBbzzIcBbQPZE7CPh1m6FF+L75NUMJFMLYhCywJXIDEMa3//1A0dw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/types@8.10.0': + resolution: {integrity: sha512-k/E48uzsfJCRRbGLapdZgrX52csmWJ2rcowwPvOZ8lwPUv3xW6CcFeJAXgx4uJm+Ge4+a4tFOkdYvSpxhRhg1w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.10.0': + resolution: {integrity: sha512-3OE0nlcOHaMvQ8Xu5gAfME3/tWVDpb/HxtpUZ1WeOAksZ/h/gwrBzCklaGzwZT97/lBbbxJ16dMA98JMEngW4w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@8.10.0': + resolution: {integrity: sha512-Oq4uZ7JFr9d1ZunE/QKy5egcDRXT/FrS2z/nlxzPua2VHFtmMvFNDvpq1m/hq0ra+T52aUezfcjGRIB7vNJF9w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + + '@typescript-eslint/visitor-keys@8.10.0': + resolution: {integrity: sha512-k8nekgqwr7FadWk548Lfph6V3r9OVqjzAIVskE7orMZR23cGJjAOVazsZSJW+ElyjfTM4wx/1g88Mi70DDtG9A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.14.1: + resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + engines: {node: '>=12'} + + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-buffer-byte-length@1.0.1: + resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} + engines: {node: '>= 0.4'} + + array.prototype.reduce@1.0.7: + resolution: {integrity: sha512-mzmiUCVwtiD4lgxYP8g7IYy8El8p2CSMePvIbTS7gchKir/L1fgJrk0yDKmAX6mnRQFKNADYIk8nNlTris5H1Q==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.3: + resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} + engines: {node: '>= 0.4'} + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + babel-jest@29.7.0: + resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.8.0 + + babel-plugin-istanbul@6.1.1: + resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} + engines: {node: '>=8'} + + babel-plugin-jest-hoist@29.6.3: + resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + babel-plugin-polyfill-corejs2@0.4.11: + resolution: {integrity: sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-corejs3@0.10.6: + resolution: {integrity: sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-regenerator@0.6.2: + resolution: {integrity: sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-preset-current-node-syntax@1.1.0: + resolution: {integrity: sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==} + peerDependencies: + '@babel/core': ^7.0.0 + + babel-preset-jest@29.6.3: + resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.0.0 + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.24.0: + resolution: {integrity: sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bser@2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.7: + resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + caniuse-lite@1.0.30001669: + resolution: {integrity: sha512-DlWzFDJqstqtIVx1zeSpIMLjunf5SmwOw0N2Ck/QSQdS8PLS4+9HrLaYei4w8BIAL7IB/UEDu889d8vhCTPA0w==} + + chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + + cjs-module-lexer@1.4.1: + resolution: {integrity: sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clone-deep@4.0.1: + resolution: {integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==} + engines: {node: '>=6'} + + co@4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + + collect-v8-coverage@1.0.2: + resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} + + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commander@6.2.1: + resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} + engines: {node: '>= 6'} + + commondir@1.0.1: + resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + core-js-compat@3.38.1: + resolution: {integrity: sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==} + + core-js@3.38.1: + resolution: {integrity: sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==} + + core-js@3.42.0: + resolution: {integrity: sha512-Sz4PP4ZA+Rq4II21qkNqOEDTDrCvcANId3xpIgB34NDkWc3UduWj2dqEtN9yZIq8Dk3HyPI33x9sqqU5C8sr0g==} + + create-jest@29.7.0: + resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + data-view-buffer@1.0.1: + resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.1: + resolution: {integrity: sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.0: + resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} + engines: {node: '>= 0.4'} + + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + dedent@1.5.3: + resolution: {integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + detect-newline@3.1.0: + resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} + engines: {node: '>=8'} + + diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + diff@8.0.2: + resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==} + engines: {node: '>=0.3.1'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + electron-to-chromium@1.5.41: + resolution: {integrity: sha512-dfdv/2xNjX0P8Vzme4cfzHqnPm5xsZXwsolTYr0eyW18IUmNyG08vL+fttvinTfhKfIKdRoqkDIC9e9iWQCNYQ==} + + emittery@0.13.1: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} + engines: {node: '>=12'} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + + es-abstract@1.23.3: + resolution: {integrity: sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==} + engines: {node: '>= 0.4'} + + es-array-method-boxes-properly@1.0.0: + resolution: {integrity: sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==} + + es-define-property@1.0.0: + resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.0.0: + resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.0.3: + resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.2.1: + resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + engines: {node: '>= 0.4'} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-prettier@9.1.0: + resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-plugin-jest@28.10.0: + resolution: {integrity: sha512-hyMWUxkBH99HpXT3p8hc7REbEZK3D+nk8vHXGgpB+XXsi0gO4PxMSP+pjfUzb67GnV9yawV9a53eUmcde1CCZA==} + engines: {node: ^16.10.0 || ^18.12.0 || >=20.0.0} + peerDependencies: + '@typescript-eslint/eslint-plugin': ^6.0.0 || ^7.0.0 || ^8.0.0 + eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 + jest: '*' + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + jest: + optional: true + + eslint-rule-composer@0.3.0: + resolution: {integrity: sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==} + engines: {node: '>=4.0.0'} + + eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + + eslint-scope@8.3.0: + resolution: {integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@2.1.0: + resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} + engines: {node: '>=10'} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.0: + resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.28.0: + resolution: {integrity: sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.3.0: + resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + exit@0.1.2: + resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} + engines: {node: '>= 0.8.0'} + + expect@29.7.0: + resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + + fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-cache-dir@2.1.0: + resolution: {integrity: sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==} + engines: {node: '>=6'} + + find-up@3.0.0: + resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} + engines: {node: '>=6'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + for-each@0.3.3: + resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.1.6: + resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.2.4: + resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} + engines: {node: '>= 0.4'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + get-symbol-description@1.0.2: + resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} + engines: {node: '>= 0.4'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@11.0.3: + resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==} + engines: {node: 20 || >=22} + hasBin: true + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@15.15.0: + resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} + engines: {node: '>=18'} + + globals@16.3.0: + resolution: {integrity: sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==} + engines: {node: '>=18'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + gopd@1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-bigints@1.0.2: + resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.0.3: + resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} + engines: {node: '>= 0.4'} + + has-symbols@1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + homedir-polyfill@1.0.3: + resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} + engines: {node: '>=0.10.0'} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + import-local@3.2.0: + resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} + engines: {node: '>=8'} + hasBin: true + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + internal-slot@1.0.7: + resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} + engines: {node: '>= 0.4'} + + is-array-buffer@3.0.4: + resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} + engines: {node: '>= 0.4'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-bigint@1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + + is-boolean-object@1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.15.1: + resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.1: + resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==} + engines: {node: '>= 0.4'} + + is-date-object@1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-fn@2.1.0: + resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + engines: {node: '>=6'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-plain-object@2.0.4: + resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} + engines: {node: '>=0.10.0'} + + is-regex@1.1.4: + resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.3: + resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} + engines: {node: '>= 0.4'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-string@1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} + + is-symbol@1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.13: + resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} + engines: {node: '>= 0.4'} + + is-weakref@1.0.2: + resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + isobject@3.0.1: + resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} + engines: {node: '>=0.10.0'} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@5.2.1: + resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@6.0.3: + resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} + engines: {node: '>=10'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@4.0.1: + resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} + engines: {node: '>=10'} + + istanbul-reports@3.1.7: + resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + engines: {node: '>=8'} + + jackspeak@4.1.1: + resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==} + engines: {node: 20 || >=22} + + jest-changed-files@29.7.0: + resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-circus@29.7.0: + resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-cli@29.7.0: + resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jest-config@29.7.0: + resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + + jest-diff@29.7.0: + resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-docblock@29.7.0: + resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-each@29.7.0: + resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-environment-node@29.7.0: + resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-get-type@29.6.3: + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-haste-map@29.7.0: + resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-leak-detector@29.7.0: + resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-matcher-utils@29.7.0: + resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-message-util@29.7.0: + resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-mock@29.7.0: + resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-pnp-resolver@1.2.3: + resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} + engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + + jest-regex-util@29.6.3: + resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-resolve-dependencies@29.7.0: + resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-resolve@29.7.0: + resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-runner@29.7.0: + resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-runtime@29.7.0: + resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-snapshot@29.7.0: + resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-util@29.7.0: + resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-validate@29.7.0: + resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-watcher@29.7.0: + resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-worker@29.7.0: + resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest@29.7.0: + resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsesc@3.0.2: + resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + locate-path@3.0.0: + resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} + engines: {node: '>=6'} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lru-cache@11.1.0: + resolution: {integrity: sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==} + engines: {node: 20 || >=22} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + make-dir@2.1.0: + resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} + engines: {node: '>=6'} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + makeerror@1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + minimatch@10.0.3: + resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==} + engines: {node: 20 || >=22} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + node-environment-flags@1.0.6: + resolution: {integrity: sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==} + + node-int64@0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + + node-releases@2.0.18: + resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + object-inspect@1.13.2: + resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} + engines: {node: '>= 0.4'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.5: + resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} + engines: {node: '>= 0.4'} + + object.getownpropertydescriptors@2.1.8: + resolution: {integrity: sha512-qkHIGe4q0lSYMv0XI4SsBTJz3WaURhLvd0lKSgtVuOsJ2krg4SgMw3PIRQFMp07yi++UR3se2mkcLqsBNpBb/A==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@3.0.0: + resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} + engines: {node: '>=6'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parse-passwd@1.0.0: + resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} + engines: {node: '>=0.10.0'} + + path-exists@3.0.0: + resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} + engines: {node: '>=4'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@2.0.0: + resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} + engines: {node: 20 || >=22} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + pify@4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} + + pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} + + pkg-dir@3.0.0: + resolution: {integrity: sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==} + engines: {node: '>=6'} + + pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + + possible-typed-array-names@1.0.0: + resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} + engines: {node: '>= 0.4'} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier@3.6.2: + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + engines: {node: '>=14'} + hasBin: true + + pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + regenerate-unicode-properties@10.2.0: + resolution: {integrity: sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==} + engines: {node: '>=4'} + + regenerate@1.4.2: + resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} + + regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + + regenerator-transform@0.15.2: + resolution: {integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==} + + regexp.prototype.flags@1.5.3: + resolution: {integrity: sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==} + engines: {node: '>= 0.4'} + + regexpu-core@6.1.1: + resolution: {integrity: sha512-k67Nb9jvwJcJmVpw0jPttR1/zVfnKf8Km0IPatrU/zJ5XeG3+Slx0xLXs9HByJSzXzrlz5EDvN6yLNMDc2qdnw==} + engines: {node: '>=4'} + + regjsgen@0.8.0: + resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==} + + regjsparser@0.11.1: + resolution: {integrity: sha512-1DHODs4B8p/mQHU9kr+jv8+wIC9mtG4eBHxWxIq5mhjE3D5oORhCc6deRKzTjs9DcfRFmj9BHSDguZklqCGFWQ==} + hasBin: true + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + resolve-cwd@3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve.exports@2.0.2: + resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} + engines: {node: '>=10'} + + resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + hasBin: true + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-array-concat@1.1.2: + resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} + engines: {node: '>=0.4'} + + safe-regex-test@1.0.3: + resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} + engines: {node: '>= 0.4'} + + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + shallow-clone@3.0.1: + resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==} + engines: {node: '>=8'} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shelljs@0.10.0: + resolution: {integrity: sha512-Jex+xw5Mg2qMZL3qnzXIfaxEtBaC4n7xifqaqtrZDdlheR70OGkydrPJWT0V1cA1k3nanC86x9FwAmQl6w3Klw==} + engines: {node: '>=18'} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + source-map-support@0.5.13: + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + + string-length@4.0.2: + resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} + engines: {node: '>=10'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string.prototype.trim@1.2.9: + resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.8: + resolution: {integrity: sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-bom@4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + + tmpl@1.0.5: + resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + + to-fast-properties@2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + ts-api-utils@1.3.0: + resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + typed-array-buffer@1.0.2: + resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.1: + resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.2: + resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.6: + resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} + engines: {node: '>= 0.4'} + + typescript@5.6.3: + resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} + engines: {node: '>=14.17'} + hasBin: true + + unbox-primitive@1.0.2: + resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + + undici-types@7.10.0: + resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==} + + unicode-canonical-property-names-ecmascript@2.0.1: + resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} + engines: {node: '>=4'} + + unicode-match-property-ecmascript@2.0.0: + resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} + engines: {node: '>=4'} + + unicode-match-property-value-ecmascript@2.2.0: + resolution: {integrity: sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==} + engines: {node: '>=4'} + + unicode-property-aliases-ecmascript@2.1.0: + resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==} + engines: {node: '>=4'} + + update-browserslist-db@1.1.1: + resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + v8-to-istanbul@9.3.0: + resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} + engines: {node: '>=10.12.0'} + + v8flags@3.2.0: + resolution: {integrity: sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==} + engines: {node: '>= 0.10'} + + walker@1.0.8: + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + + which-boxed-primitive@1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + + which-typed-array@1.1.15: + resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + write-file-atomic@4.0.2: + resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + + '@babel/code-frame@7.25.7': + dependencies: + '@babel/highlight': 7.25.7 + picocolors: 1.1.1 + + '@babel/compat-data@7.25.8': {} + + '@babel/core@7.25.8': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.25.7 + '@babel/generator': 7.25.7 + '@babel/helper-compilation-targets': 7.25.7 + '@babel/helper-module-transforms': 7.25.7(@babel/core@7.25.8) + '@babel/helpers': 7.25.7 + '@babel/parser': 7.25.8 + '@babel/template': 7.25.7 + '@babel/traverse': 7.25.7 + '@babel/types': 7.25.8 + convert-source-map: 2.0.0 + debug: 4.4.1 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/eslint-parser@7.25.9(@babel/core@7.25.8)(eslint@9.28.0)': + dependencies: + '@babel/core': 7.25.8 + '@nicolo-ribaudo/eslint-scope-5-internals': 5.1.1-v1 + eslint: 9.28.0 + eslint-visitor-keys: 2.1.0 + semver: 6.3.1 + + '@babel/eslint-plugin@7.25.9(@babel/eslint-parser@7.25.9(@babel/core@7.25.8)(eslint@9.28.0))(eslint@9.28.0)': + dependencies: + '@babel/eslint-parser': 7.25.9(@babel/core@7.25.8)(eslint@9.28.0) + eslint: 9.28.0 + eslint-rule-composer: 0.3.0 + + '@babel/generator@7.25.7': + dependencies: + '@babel/types': 7.25.8 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.0.2 + + '@babel/helper-annotate-as-pure@7.25.7': + dependencies: + '@babel/types': 7.25.8 + + '@babel/helper-builder-binary-assignment-operator-visitor@7.25.7': + dependencies: + '@babel/traverse': 7.25.7 + '@babel/types': 7.25.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-compilation-targets@7.25.7': + dependencies: + '@babel/compat-data': 7.25.8 + '@babel/helper-validator-option': 7.25.7 + browserslist: 4.24.0 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-annotate-as-pure': 7.25.7 + '@babel/helper-member-expression-to-functions': 7.25.7 + '@babel/helper-optimise-call-expression': 7.25.7 + '@babel/helper-replace-supers': 7.25.7(@babel/core@7.25.8) + '@babel/helper-skip-transparent-expression-wrappers': 7.25.7 + '@babel/traverse': 7.25.7 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-create-regexp-features-plugin@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-annotate-as-pure': 7.25.7 + regexpu-core: 6.1.1 + semver: 6.3.1 + + '@babel/helper-define-polyfill-provider@0.6.2(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-compilation-targets': 7.25.7 + '@babel/helper-plugin-utils': 7.25.7 + debug: 4.4.1 + lodash.debounce: 4.0.8 + resolve: 1.22.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-member-expression-to-functions@7.25.7': + dependencies: + '@babel/traverse': 7.25.7 + '@babel/types': 7.25.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.25.7': + dependencies: + '@babel/traverse': 7.25.7 + '@babel/types': 7.25.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-module-imports': 7.25.7 + '@babel/helper-simple-access': 7.25.7 + '@babel/helper-validator-identifier': 7.25.7 + '@babel/traverse': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.25.7': + dependencies: + '@babel/types': 7.25.8 + + '@babel/helper-plugin-utils@7.25.7': {} + + '@babel/helper-remap-async-to-generator@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-annotate-as-pure': 7.25.7 + '@babel/helper-wrap-function': 7.25.7 + '@babel/traverse': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-replace-supers@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-member-expression-to-functions': 7.25.7 + '@babel/helper-optimise-call-expression': 7.25.7 + '@babel/traverse': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-simple-access@7.25.7': + dependencies: + '@babel/traverse': 7.25.7 + '@babel/types': 7.25.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.25.7': + dependencies: + '@babel/traverse': 7.25.7 + '@babel/types': 7.25.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.25.7': {} + + '@babel/helper-validator-identifier@7.25.7': {} + + '@babel/helper-validator-option@7.25.7': {} + + '@babel/helper-wrap-function@7.25.7': + dependencies: + '@babel/template': 7.25.7 + '@babel/traverse': 7.25.7 + '@babel/types': 7.25.8 + transitivePeerDependencies: + - supports-color + + '@babel/helpers@7.25.7': + dependencies: + '@babel/template': 7.25.7 + '@babel/types': 7.25.8 + + '@babel/highlight@7.25.7': + dependencies: + '@babel/helper-validator-identifier': 7.25.7 + chalk: 2.4.2 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/node@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/register': 7.25.7(@babel/core@7.25.8) + commander: 6.2.1 + core-js: 3.42.0 + node-environment-flags: 1.0.6 + regenerator-runtime: 0.14.1 + v8flags: 3.2.0 + + '@babel/parser@7.25.8': + dependencies: + '@babel/types': 7.25.8 + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/traverse': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.7 + '@babel/plugin-transform-optional-chaining': 7.25.8(@babel/core@7.25.8) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/traverse': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-syntax-import-assertions@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-syntax-import-attributes@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-syntax-jsx@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-syntax-typescript@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-create-regexp-features-plugin': 7.25.7(@babel/core@7.25.8) + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-arrow-functions@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-async-generator-functions@7.25.8(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-remap-async-to-generator': 7.25.7(@babel/core@7.25.8) + '@babel/traverse': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-async-to-generator@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-module-imports': 7.25.7 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-remap-async-to-generator': 7.25.7(@babel/core@7.25.8) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-block-scoped-functions@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-block-scoping@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-class-properties@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-create-class-features-plugin': 7.25.7(@babel/core@7.25.8) + '@babel/helper-plugin-utils': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-class-static-block@7.25.8(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-create-class-features-plugin': 7.25.7(@babel/core@7.25.8) + '@babel/helper-plugin-utils': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-classes@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-annotate-as-pure': 7.25.7 + '@babel/helper-compilation-targets': 7.25.7 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-replace-supers': 7.25.7(@babel/core@7.25.8) + '@babel/traverse': 7.25.7 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-computed-properties@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/template': 7.25.7 + + '@babel/plugin-transform-destructuring@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-dotall-regex@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-create-regexp-features-plugin': 7.25.7(@babel/core@7.25.8) + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-duplicate-keys@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-create-regexp-features-plugin': 7.25.7(@babel/core@7.25.8) + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-dynamic-import@7.25.8(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-exponentiation-operator@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-builder-binary-assignment-operator-visitor': 7.25.7 + '@babel/helper-plugin-utils': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-export-namespace-from@7.25.8(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-for-of@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-function-name@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-compilation-targets': 7.25.7 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/traverse': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-json-strings@7.25.8(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-literals@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-logical-assignment-operators@7.25.8(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-member-expression-literals@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-modules-amd@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-module-transforms': 7.25.7(@babel/core@7.25.8) + '@babel/helper-plugin-utils': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-commonjs@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-module-transforms': 7.25.7(@babel/core@7.25.8) + '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-simple-access': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-systemjs@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-module-transforms': 7.25.7(@babel/core@7.25.8) + '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-validator-identifier': 7.25.7 + '@babel/traverse': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-umd@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-module-transforms': 7.25.7(@babel/core@7.25.8) + '@babel/helper-plugin-utils': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-named-capturing-groups-regex@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-create-regexp-features-plugin': 7.25.7(@babel/core@7.25.8) + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-new-target@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-nullish-coalescing-operator@7.25.8(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-numeric-separator@7.25.8(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-object-rest-spread@7.25.8(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-compilation-targets': 7.25.7 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/plugin-transform-parameters': 7.25.7(@babel/core@7.25.8) + + '@babel/plugin-transform-object-super@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-replace-supers': 7.25.7(@babel/core@7.25.8) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-optional-catch-binding@7.25.8(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-optional-chaining@7.25.8(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-parameters@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-private-methods@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-create-class-features-plugin': 7.25.7(@babel/core@7.25.8) + '@babel/helper-plugin-utils': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-private-property-in-object@7.25.8(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-annotate-as-pure': 7.25.7 + '@babel/helper-create-class-features-plugin': 7.25.7(@babel/core@7.25.8) + '@babel/helper-plugin-utils': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-property-literals@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-regenerator@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + regenerator-transform: 0.15.2 + + '@babel/plugin-transform-reserved-words@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-shorthand-properties@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-spread@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-sticky-regex@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-template-literals@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-typeof-symbol@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-unicode-escapes@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-unicode-property-regex@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-create-regexp-features-plugin': 7.25.7(@babel/core@7.25.8) + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-unicode-regex@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-create-regexp-features-plugin': 7.25.7(@babel/core@7.25.8) + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/plugin-transform-unicode-sets-regex@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-create-regexp-features-plugin': 7.25.7(@babel/core@7.25.8) + '@babel/helper-plugin-utils': 7.25.7 + + '@babel/preset-env@7.25.8(@babel/core@7.25.8)': + dependencies: + '@babel/compat-data': 7.25.8 + '@babel/core': 7.25.8 + '@babel/helper-compilation-targets': 7.25.7 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/helper-validator-option': 7.25.7 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.25.8) + '@babel/plugin-syntax-import-assertions': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-syntax-import-attributes': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.25.8) + '@babel/plugin-transform-arrow-functions': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-async-generator-functions': 7.25.8(@babel/core@7.25.8) + '@babel/plugin-transform-async-to-generator': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-block-scoped-functions': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-block-scoping': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-class-properties': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-class-static-block': 7.25.8(@babel/core@7.25.8) + '@babel/plugin-transform-classes': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-computed-properties': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-destructuring': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-dotall-regex': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-duplicate-keys': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-dynamic-import': 7.25.8(@babel/core@7.25.8) + '@babel/plugin-transform-exponentiation-operator': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-export-namespace-from': 7.25.8(@babel/core@7.25.8) + '@babel/plugin-transform-for-of': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-function-name': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-json-strings': 7.25.8(@babel/core@7.25.8) + '@babel/plugin-transform-literals': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-logical-assignment-operators': 7.25.8(@babel/core@7.25.8) + '@babel/plugin-transform-member-expression-literals': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-modules-amd': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-modules-commonjs': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-modules-systemjs': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-modules-umd': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-named-capturing-groups-regex': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-new-target': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-nullish-coalescing-operator': 7.25.8(@babel/core@7.25.8) + '@babel/plugin-transform-numeric-separator': 7.25.8(@babel/core@7.25.8) + '@babel/plugin-transform-object-rest-spread': 7.25.8(@babel/core@7.25.8) + '@babel/plugin-transform-object-super': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-optional-catch-binding': 7.25.8(@babel/core@7.25.8) + '@babel/plugin-transform-optional-chaining': 7.25.8(@babel/core@7.25.8) + '@babel/plugin-transform-parameters': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-private-methods': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-private-property-in-object': 7.25.8(@babel/core@7.25.8) + '@babel/plugin-transform-property-literals': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-regenerator': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-reserved-words': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-shorthand-properties': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-spread': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-sticky-regex': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-template-literals': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-typeof-symbol': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-unicode-escapes': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-unicode-property-regex': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-unicode-regex': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-transform-unicode-sets-regex': 7.25.7(@babel/core@7.25.8) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.25.8) + babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.25.8) + babel-plugin-polyfill-corejs3: 0.10.6(@babel/core@7.25.8) + babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.25.8) + core-js-compat: 3.38.1 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-plugin-utils': 7.25.7 + '@babel/types': 7.25.8 + esutils: 2.0.3 + + '@babel/register@7.25.7(@babel/core@7.25.8)': + dependencies: + '@babel/core': 7.25.8 + clone-deep: 4.0.1 + find-cache-dir: 2.1.0 + make-dir: 2.1.0 + pirates: 4.0.6 + source-map-support: 0.5.21 + + '@babel/runtime@7.25.7': + dependencies: + regenerator-runtime: 0.14.1 + + '@babel/template@7.25.7': + dependencies: + '@babel/code-frame': 7.25.7 + '@babel/parser': 7.25.8 + '@babel/types': 7.25.8 + + '@babel/traverse@7.25.7': + dependencies: + '@babel/code-frame': 7.25.7 + '@babel/generator': 7.25.7 + '@babel/parser': 7.25.8 + '@babel/template': 7.25.7 + '@babel/types': 7.25.8 + debug: 4.4.1 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.25.8': + dependencies: + '@babel/helper-string-parser': 7.25.7 + '@babel/helper-validator-identifier': 7.25.7 + to-fast-properties: 2.0.0 + + '@bcoe/v8-coverage@0.2.3': {} + + '@eslint-community/eslint-utils@4.7.0(eslint@9.28.0)': + dependencies: + eslint: 9.28.0 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/config-array@0.20.0': + dependencies: + '@eslint/object-schema': 2.1.6 + debug: 4.4.1 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.2.2': {} + + '@eslint/core@0.14.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.1': + dependencies: + ajv: 6.12.6 + debug: 4.4.1 + espree: 10.3.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.17.0': {} + + '@eslint/js@9.28.0': {} + + '@eslint/object-schema@2.1.6': {} + + '@eslint/plugin-kit@0.3.1': + dependencies: + '@eslint/core': 0.14.0 + levn: 0.4.1 + + '@exercism/babel-preset-javascript@0.5.1': + dependencies: + '@babel/core': 7.25.8 + '@babel/node': 7.25.7(@babel/core@7.25.8) + '@babel/preset-env': 7.25.8(@babel/core@7.25.8) + core-js: 3.38.1 + transitivePeerDependencies: + - supports-color + + '@exercism/eslint-config-javascript@0.8.1(@babel/core@7.25.8)(@exercism/babel-preset-javascript@0.5.1)(eslint@9.28.0)(jest@29.7.0(@types/node@24.3.0))(typescript@5.6.3)': + dependencies: + '@babel/eslint-parser': 7.25.9(@babel/core@7.25.8)(eslint@9.28.0) + '@babel/eslint-plugin': 7.25.9(@babel/eslint-parser@7.25.9(@babel/core@7.25.8)(eslint@9.28.0))(eslint@9.28.0) + '@eslint/js': 9.17.0 + '@exercism/babel-preset-javascript': 0.5.1 + eslint: 9.28.0 + eslint-config-prettier: 9.1.0(eslint@9.28.0) + eslint-plugin-jest: 28.10.0(eslint@9.28.0)(jest@29.7.0(@types/node@24.3.0))(typescript@5.6.3) + globals: 15.15.0 + transitivePeerDependencies: + - '@babel/core' + - '@typescript-eslint/eslint-plugin' + - jest + - supports-color + - typescript + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.6': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.3.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@isaacs/balanced-match@4.0.1': {} + + '@isaacs/brace-expansion@5.0.0': + dependencies: + '@isaacs/balanced-match': 4.0.1 + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@istanbuljs/load-nyc-config@1.1.0': + dependencies: + camelcase: 5.3.1 + find-up: 4.1.0 + get-package-type: 0.1.0 + js-yaml: 3.14.1 + resolve-from: 5.0.0 + + '@istanbuljs/schema@0.1.3': {} + + '@jest/console@29.7.0': + dependencies: + '@jest/types': 29.6.3 + '@types/node': 24.3.0 + chalk: 4.1.2 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + + '@jest/core@29.7.0': + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 24.3.0 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@24.3.0) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + + '@jest/environment@29.7.0': + dependencies: + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 24.3.0 + jest-mock: 29.7.0 + + '@jest/expect-utils@29.7.0': + dependencies: + jest-get-type: 29.6.3 + + '@jest/expect@29.7.0': + dependencies: + expect: 29.7.0 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + + '@jest/fake-timers@29.7.0': + dependencies: + '@jest/types': 29.6.3 + '@sinonjs/fake-timers': 10.3.0 + '@types/node': 24.3.0 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + '@jest/globals@29.7.0': + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/types': 29.6.3 + jest-mock: 29.7.0 + transitivePeerDependencies: + - supports-color + + '@jest/reporters@29.7.0': + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.25 + '@types/node': 24.3.0 + chalk: 4.1.2 + collect-v8-coverage: 1.0.2 + exit: 0.1.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-instrument: 6.0.3 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 4.0.1 + istanbul-reports: 3.1.7 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + jest-worker: 29.7.0 + slash: 3.0.0 + string-length: 4.0.2 + strip-ansi: 6.0.1 + v8-to-istanbul: 9.3.0 + transitivePeerDependencies: + - supports-color + + '@jest/schemas@29.6.3': + dependencies: + '@sinclair/typebox': 0.27.8 + + '@jest/source-map@29.6.3': + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + callsites: 3.1.0 + graceful-fs: 4.2.11 + + '@jest/test-result@29.7.0': + dependencies: + '@jest/console': 29.7.0 + '@jest/types': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + collect-v8-coverage: 1.0.2 + + '@jest/test-sequencer@29.7.0': + dependencies: + '@jest/test-result': 29.7.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + slash: 3.0.0 + + '@jest/transform@29.7.0': + dependencies: + '@babel/core': 7.25.8 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.25 + babel-plugin-istanbul: 6.1.1 + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + micromatch: 4.0.8 + pirates: 4.0.6 + slash: 3.0.0 + write-file-atomic: 4.0.2 + transitivePeerDependencies: + - supports-color + + '@jest/types@29.6.3': + dependencies: + '@jest/schemas': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 24.3.0 + '@types/yargs': 17.0.33 + chalk: 4.1.2 + + '@jridgewell/gen-mapping@0.3.5': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': + dependencies: + eslint-scope: 5.1.1 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + + '@sinclair/typebox@0.27.8': {} + + '@sinonjs/commons@3.0.1': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/fake-timers@10.3.0': + dependencies: + '@sinonjs/commons': 3.0.1 + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.25.8 + '@babel/types': 7.25.8 + '@types/babel__generator': 7.6.8 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.20.6 + + '@types/babel__generator@7.6.8': + dependencies: + '@babel/types': 7.25.8 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.25.8 + '@babel/types': 7.25.8 + + '@types/babel__traverse@7.20.6': + dependencies: + '@babel/types': 7.25.8 + + '@types/estree@1.0.7': {} + + '@types/graceful-fs@4.1.9': + dependencies: + '@types/node': 24.3.0 + + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/istanbul-lib-report@3.0.3': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + + '@types/istanbul-reports@3.0.4': + dependencies: + '@types/istanbul-lib-report': 3.0.3 + + '@types/json-schema@7.0.15': {} + + '@types/node@24.3.0': + dependencies: + undici-types: 7.10.0 + + '@types/shelljs@0.8.17': + dependencies: + '@types/node': 24.3.0 + glob: 11.0.3 + + '@types/stack-utils@2.0.3': {} + + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.33': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@typescript-eslint/scope-manager@8.10.0': + dependencies: + '@typescript-eslint/types': 8.10.0 + '@typescript-eslint/visitor-keys': 8.10.0 + + '@typescript-eslint/types@8.10.0': {} + + '@typescript-eslint/typescript-estree@8.10.0(typescript@5.6.3)': + dependencies: + '@typescript-eslint/types': 8.10.0 + '@typescript-eslint/visitor-keys': 8.10.0 + debug: 4.4.1 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.6.3 + ts-api-utils: 1.3.0(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.10.0(eslint@9.28.0)(typescript@5.6.3)': + dependencies: + '@eslint-community/eslint-utils': 4.7.0(eslint@9.28.0) + '@typescript-eslint/scope-manager': 8.10.0 + '@typescript-eslint/types': 8.10.0 + '@typescript-eslint/typescript-estree': 8.10.0(typescript@5.6.3) + eslint: 9.28.0 + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/visitor-keys@8.10.0': + dependencies: + '@typescript-eslint/types': 8.10.0 + eslint-visitor-keys: 3.4.3 + + acorn-jsx@5.3.2(acorn@8.14.1): + dependencies: + acorn: 8.14.1 + + acorn@8.14.1: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-regex@5.0.1: {} + + ansi-regex@6.1.0: {} + + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + ansi-styles@6.2.1: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + + array-buffer-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + is-array-buffer: 3.0.4 + + array.prototype.reduce@1.0.7: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-array-method-boxes-properly: 1.0.0 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + is-string: 1.0.7 + + arraybuffer.prototype.slice@1.0.3: + dependencies: + array-buffer-byte-length: 1.0.1 + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + is-array-buffer: 3.0.4 + is-shared-array-buffer: 1.0.3 + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.0.0 + + babel-jest@29.7.0(@babel/core@7.25.8): + dependencies: + '@babel/core': 7.25.8 + '@jest/transform': 29.7.0 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.6.3(@babel/core@7.25.8) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-istanbul@6.1.1: + dependencies: + '@babel/helper-plugin-utils': 7.25.7 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-instrument: 5.2.1 + test-exclude: 6.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-jest-hoist@29.6.3: + dependencies: + '@babel/template': 7.25.7 + '@babel/types': 7.25.8 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.20.6 + + babel-plugin-polyfill-corejs2@0.4.11(@babel/core@7.25.8): + dependencies: + '@babel/compat-data': 7.25.8 + '@babel/core': 7.25.8 + '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.25.8) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-corejs3@0.10.6(@babel/core@7.25.8): + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.25.8) + core-js-compat: 3.38.1 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-regenerator@0.6.2(@babel/core@7.25.8): + dependencies: + '@babel/core': 7.25.8 + '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.25.8) + transitivePeerDependencies: + - supports-color + + babel-preset-current-node-syntax@1.1.0(@babel/core@7.25.8): + dependencies: + '@babel/core': 7.25.8 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.25.8) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.25.8) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.25.8) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.25.8) + '@babel/plugin-syntax-import-attributes': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.25.8) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.25.8) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.25.8) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.25.8) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.25.8) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.25.8) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.25.8) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.25.8) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.25.8) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.25.8) + + babel-preset-jest@29.6.3(@babel/core@7.25.8): + dependencies: + '@babel/core': 7.25.8 + babel-plugin-jest-hoist: 29.6.3 + babel-preset-current-node-syntax: 1.1.0(@babel/core@7.25.8) + + balanced-match@1.0.2: {} + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.24.0: + dependencies: + caniuse-lite: 1.0.30001669 + electron-to-chromium: 1.5.41 + node-releases: 2.0.18 + update-browserslist-db: 1.1.1(browserslist@4.24.0) + + bser@2.1.1: + dependencies: + node-int64: 0.4.0 + + buffer-from@1.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.7: + dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + camelcase@5.3.1: {} + + camelcase@6.3.0: {} + + caniuse-lite@1.0.30001669: {} + + chalk@2.4.2: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + char-regex@1.0.2: {} + + ci-info@3.9.0: {} + + cjs-module-lexer@1.4.1: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clone-deep@4.0.1: + dependencies: + is-plain-object: 2.0.4 + kind-of: 6.0.3 + shallow-clone: 3.0.1 + + co@4.6.0: {} + + collect-v8-coverage@1.0.2: {} + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.3: {} + + color-name@1.1.4: {} + + commander@6.2.1: {} + + commondir@1.0.1: {} + + concat-map@0.0.1: {} + + convert-source-map@2.0.0: {} + + core-js-compat@3.38.1: + dependencies: + browserslist: 4.24.0 + + core-js@3.38.1: {} + + core-js@3.42.0: {} + + create-jest@29.7.0(@types/node@24.3.0): + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@24.3.0) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + data-view-buffer@1.0.1: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + data-view-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + data-view-byte-offset@1.0.0: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-data-view: 1.0.1 + + debug@4.4.1: + dependencies: + ms: 2.1.3 + + dedent@1.5.3: {} + + deep-is@0.1.4: {} + + deepmerge@4.3.1: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 + gopd: 1.0.1 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + detect-newline@3.1.0: {} + + diff-sequences@29.6.3: {} + + diff@8.0.2: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + eastasianwidth@0.2.0: {} + + electron-to-chromium@1.5.41: {} + + emittery@0.13.1: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + error-ex@1.3.2: + dependencies: + is-arrayish: 0.2.1 + + es-abstract@1.23.3: + dependencies: + array-buffer-byte-length: 1.0.1 + arraybuffer.prototype.slice: 1.0.3 + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + data-view-buffer: 1.0.1 + data-view-byte-length: 1.0.1 + data-view-byte-offset: 1.0.0 + es-define-property: 1.0.0 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + es-set-tostringtag: 2.0.3 + es-to-primitive: 1.2.1 + function.prototype.name: 1.1.6 + get-intrinsic: 1.2.4 + get-symbol-description: 1.0.2 + globalthis: 1.0.4 + gopd: 1.0.1 + has-property-descriptors: 1.0.2 + has-proto: 1.0.3 + has-symbols: 1.0.3 + hasown: 2.0.2 + internal-slot: 1.0.7 + is-array-buffer: 3.0.4 + is-callable: 1.2.7 + is-data-view: 1.0.1 + is-negative-zero: 2.0.3 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.3 + is-string: 1.0.7 + is-typed-array: 1.1.13 + is-weakref: 1.0.2 + object-inspect: 1.13.2 + object-keys: 1.1.1 + object.assign: 4.1.5 + regexp.prototype.flags: 1.5.3 + safe-array-concat: 1.1.2 + safe-regex-test: 1.0.3 + string.prototype.trim: 1.2.9 + string.prototype.trimend: 1.0.8 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.2 + typed-array-byte-length: 1.0.1 + typed-array-byte-offset: 1.0.2 + typed-array-length: 1.0.6 + unbox-primitive: 1.0.2 + which-typed-array: 1.1.15 + + es-array-method-boxes-properly@1.0.0: {} + + es-define-property@1.0.0: + dependencies: + get-intrinsic: 1.2.4 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.0.0: + dependencies: + es-errors: 1.3.0 + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.0.3: + dependencies: + get-intrinsic: 1.2.4 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-to-primitive@1.2.1: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.0.5 + is-symbol: 1.0.4 + + escalade@3.2.0: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@2.0.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-config-prettier@9.1.0(eslint@9.28.0): + dependencies: + eslint: 9.28.0 + + eslint-plugin-jest@28.10.0(eslint@9.28.0)(jest@29.7.0(@types/node@24.3.0))(typescript@5.6.3): + dependencies: + '@typescript-eslint/utils': 8.10.0(eslint@9.28.0)(typescript@5.6.3) + eslint: 9.28.0 + optionalDependencies: + jest: 29.7.0(@types/node@24.3.0) + transitivePeerDependencies: + - supports-color + - typescript + + eslint-rule-composer@0.3.0: {} + + eslint-scope@5.1.1: + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + + eslint-scope@8.3.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@2.1.0: {} + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.0: {} + + eslint@9.28.0: + dependencies: + '@eslint-community/eslint-utils': 4.7.0(eslint@9.28.0) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.20.0 + '@eslint/config-helpers': 0.2.2 + '@eslint/core': 0.14.0 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.28.0 + '@eslint/plugin-kit': 0.3.1 + '@humanfs/node': 0.16.6 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.7 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.1 + escape-string-regexp: 4.0.0 + eslint-scope: 8.3.0 + eslint-visitor-keys: 4.2.0 + espree: 10.3.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.3.0: + dependencies: + acorn: 8.14.1 + acorn-jsx: 5.3.2(acorn@8.14.1) + eslint-visitor-keys: 4.2.0 + + esprima@4.0.1: {} + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@4.3.0: {} + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + exit@0.1.2: {} + + expect@29.7.0: + dependencies: + '@jest/expect-utils': 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + fb-watchman@2.0.2: + dependencies: + bser: 2.1.1 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-cache-dir@2.1.0: + dependencies: + commondir: 1.0.1 + make-dir: 2.1.0 + pkg-dir: 3.0.0 + + find-up@3.0.0: + dependencies: + locate-path: 3.0.0 + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + for-each@0.3.3: + dependencies: + is-callable: 1.2.7 + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + function.prototype.name@1.1.6: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + functions-have-names: 1.2.3 + + functions-have-names@1.2.3: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.2.4: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + has-proto: 1.0.3 + has-symbols: 1.0.3 + hasown: 2.0.2 + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-package-type@0.1.0: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@6.0.1: {} + + get-symbol-description@1.0.2: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@11.0.3: + dependencies: + foreground-child: 3.3.1 + jackspeak: 4.1.1 + minimatch: 10.0.3 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 2.0.0 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + globals@11.12.0: {} + + globals@14.0.0: {} + + globals@15.15.0: {} + + globals@16.3.0: {} + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.0.1 + + gopd@1.0.1: + dependencies: + get-intrinsic: 1.2.4 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + has-bigints@1.0.2: {} + + has-flag@3.0.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.0 + + has-proto@1.0.3: {} + + has-symbols@1.0.3: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.0.3 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + homedir-polyfill@1.0.3: + dependencies: + parse-passwd: 1.0.0 + + html-escaper@2.0.2: {} + + human-signals@2.1.0: {} + + ignore@5.3.2: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-local@3.2.0: + dependencies: + pkg-dir: 4.2.0 + resolve-cwd: 3.0.0 + + imurmurhash@0.1.4: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + internal-slot@1.0.7: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + + is-array-buffer@3.0.4: + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + + is-arrayish@0.2.1: {} + + is-bigint@1.0.4: + dependencies: + has-bigints: 1.0.2 + + is-boolean-object@1.1.2: + dependencies: + call-bind: 1.0.7 + has-tostringtag: 1.0.2 + + is-callable@1.2.7: {} + + is-core-module@2.15.1: + dependencies: + hasown: 2.0.2 + + is-data-view@1.0.1: + dependencies: + is-typed-array: 1.1.13 + + is-date-object@1.0.5: + dependencies: + has-tostringtag: 1.0.2 + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-generator-fn@2.1.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-negative-zero@2.0.3: {} + + is-number-object@1.0.7: + dependencies: + has-tostringtag: 1.0.2 + + is-number@7.0.0: {} + + is-plain-object@2.0.4: + dependencies: + isobject: 3.0.1 + + is-regex@1.1.4: + dependencies: + call-bind: 1.0.7 + has-tostringtag: 1.0.2 + + is-shared-array-buffer@1.0.3: + dependencies: + call-bind: 1.0.7 + + is-stream@2.0.1: {} + + is-string@1.0.7: + dependencies: + has-tostringtag: 1.0.2 + + is-symbol@1.0.4: + dependencies: + has-symbols: 1.0.3 + + is-typed-array@1.1.13: + dependencies: + which-typed-array: 1.1.15 + + is-weakref@1.0.2: + dependencies: + call-bind: 1.0.7 + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + isobject@3.0.1: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-instrument@5.2.1: + dependencies: + '@babel/core': 7.25.8 + '@babel/parser': 7.25.8 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + istanbul-lib-instrument@6.0.3: + dependencies: + '@babel/core': 7.25.8 + '@babel/parser': 7.25.8 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 7.6.3 + transitivePeerDependencies: + - supports-color + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@4.0.1: + dependencies: + debug: 4.4.1 + istanbul-lib-coverage: 3.2.2 + source-map: 0.6.1 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.1.7: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + jackspeak@4.1.1: + dependencies: + '@isaacs/cliui': 8.0.2 + + jest-changed-files@29.7.0: + dependencies: + execa: 5.1.1 + jest-util: 29.7.0 + p-limit: 3.1.0 + + jest-circus@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 24.3.0 + chalk: 4.1.2 + co: 4.6.0 + dedent: 1.5.3 + is-generator-fn: 2.1.0 + jest-each: 29.7.0 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + p-limit: 3.1.0 + pretty-format: 29.7.0 + pure-rand: 6.1.0 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-cli@29.7.0(@types/node@24.3.0): + dependencies: + '@jest/core': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0(@types/node@24.3.0) + exit: 0.1.2 + import-local: 3.2.0 + jest-config: 29.7.0(@types/node@24.3.0) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + jest-config@29.7.0(@types/node@24.3.0): + dependencies: + '@babel/core': 7.25.8 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.25.8) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 24.3.0 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-diff@29.7.0: + dependencies: + chalk: 4.1.2 + diff-sequences: 29.6.3 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-docblock@29.7.0: + dependencies: + detect-newline: 3.1.0 + + jest-each@29.7.0: + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + jest-get-type: 29.6.3 + jest-util: 29.7.0 + pretty-format: 29.7.0 + + jest-environment-node@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 24.3.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + jest-get-type@29.6.3: {} + + jest-haste-map@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/graceful-fs': 4.1.9 + '@types/node': 24.3.0 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + jest-worker: 29.7.0 + micromatch: 4.0.8 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 + + jest-leak-detector@29.7.0: + dependencies: + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-matcher-utils@29.7.0: + dependencies: + chalk: 4.1.2 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-message-util@29.7.0: + dependencies: + '@babel/code-frame': 7.25.7 + '@jest/types': 29.6.3 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + stack-utils: 2.0.6 + + jest-mock@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 24.3.0 + jest-util: 29.7.0 + + jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): + optionalDependencies: + jest-resolve: 29.7.0 + + jest-regex-util@29.6.3: {} + + jest-resolve-dependencies@29.7.0: + dependencies: + jest-regex-util: 29.6.3 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + + jest-resolve@29.7.0: + dependencies: + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0) + jest-util: 29.7.0 + jest-validate: 29.7.0 + resolve: 1.22.8 + resolve.exports: 2.0.2 + slash: 3.0.0 + + jest-runner@29.7.0: + dependencies: + '@jest/console': 29.7.0 + '@jest/environment': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 24.3.0 + chalk: 4.1.2 + emittery: 0.13.1 + graceful-fs: 4.2.11 + jest-docblock: 29.7.0 + jest-environment-node: 29.7.0 + jest-haste-map: 29.7.0 + jest-leak-detector: 29.7.0 + jest-message-util: 29.7.0 + jest-resolve: 29.7.0 + jest-runtime: 29.7.0 + jest-util: 29.7.0 + jest-watcher: 29.7.0 + jest-worker: 29.7.0 + p-limit: 3.1.0 + source-map-support: 0.5.13 + transitivePeerDependencies: + - supports-color + + jest-runtime@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/globals': 29.7.0 + '@jest/source-map': 29.6.3 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 24.3.0 + chalk: 4.1.2 + cjs-module-lexer: 1.4.1 + collect-v8-coverage: 1.0.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color + + jest-snapshot@29.7.0: + dependencies: + '@babel/core': 7.25.8 + '@babel/generator': 7.25.7 + '@babel/plugin-syntax-jsx': 7.25.7(@babel/core@7.25.8) + '@babel/plugin-syntax-typescript': 7.25.7(@babel/core@7.25.8) + '@babel/types': 7.25.8 + '@jest/expect-utils': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-preset-current-node-syntax: 1.1.0(@babel/core@7.25.8) + chalk: 4.1.2 + expect: 29.7.0 + graceful-fs: 4.2.11 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + natural-compare: 1.4.0 + pretty-format: 29.7.0 + semver: 7.6.3 + transitivePeerDependencies: + - supports-color + + jest-util@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 24.3.0 + chalk: 4.1.2 + ci-info: 3.9.0 + graceful-fs: 4.2.11 + picomatch: 2.3.1 + + jest-validate@29.7.0: + dependencies: + '@jest/types': 29.6.3 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 29.6.3 + leven: 3.1.0 + pretty-format: 29.7.0 + + jest-watcher@29.7.0: + dependencies: + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 24.3.0 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.13.1 + jest-util: 29.7.0 + string-length: 4.0.2 + + jest-worker@29.7.0: + dependencies: + '@types/node': 24.3.0 + jest-util: 29.7.0 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + jest@29.7.0(@types/node@24.3.0): + dependencies: + '@jest/core': 29.7.0 + '@jest/types': 29.6.3 + import-local: 3.2.0 + jest-cli: 29.7.0(@types/node@24.3.0) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + js-tokens@4.0.0: {} + + js-yaml@3.14.1: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsesc@3.0.2: {} + + json-buffer@3.0.1: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kind-of@6.0.3: {} + + kleur@3.0.3: {} + + leven@3.1.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lines-and-columns@1.2.4: {} + + locate-path@3.0.0: + dependencies: + p-locate: 3.0.0 + path-exists: 3.0.0 + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.debounce@4.0.8: {} + + lodash.merge@4.6.2: {} + + lru-cache@11.1.0: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + make-dir@2.1.0: + dependencies: + pify: 4.0.1 + semver: 5.7.2 + + make-dir@4.0.0: + dependencies: + semver: 7.6.3 + + makeerror@1.0.12: + dependencies: + tmpl: 1.0.5 + + math-intrinsics@1.1.0: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mimic-fn@2.1.0: {} + + minimatch@10.0.3: + dependencies: + '@isaacs/brace-expansion': 5.0.0 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + + minipass@7.1.2: {} + + ms@2.1.3: {} + + natural-compare@1.4.0: {} + + node-environment-flags@1.0.6: + dependencies: + object.getownpropertydescriptors: 2.1.8 + semver: 5.7.2 + + node-int64@0.4.0: {} + + node-releases@2.0.18: {} + + normalize-path@3.0.0: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + object-inspect@1.13.2: {} + + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object.assign@4.1.5: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + has-symbols: 1.0.3 + object-keys: 1.1.1 + + object.getownpropertydescriptors@2.1.8: + dependencies: + array.prototype.reduce: 1.0.7 + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 + gopd: 1.0.1 + safe-array-concat: 1.1.2 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@3.0.0: + dependencies: + p-limit: 2.3.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-try@2.2.0: {} + + package-json-from-dist@1.0.1: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.25.7 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse-passwd@1.0.0: {} + + path-exists@3.0.0: {} + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@2.0.0: + dependencies: + lru-cache: 11.1.0 + minipass: 7.1.2 + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + pify@4.0.1: {} + + pirates@4.0.6: {} + + pkg-dir@3.0.0: + dependencies: + find-up: 3.0.0 + + pkg-dir@4.2.0: + dependencies: + find-up: 4.1.0 + + possible-typed-array-names@1.0.0: {} + + prelude-ls@1.2.1: {} + + prettier@3.6.2: {} + + pretty-format@29.7.0: + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.3.1 + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + punycode@2.3.1: {} + + pure-rand@6.1.0: {} + + queue-microtask@1.2.3: {} + + react-is@18.3.1: {} + + regenerate-unicode-properties@10.2.0: + dependencies: + regenerate: 1.4.2 + + regenerate@1.4.2: {} + + regenerator-runtime@0.14.1: {} + + regenerator-transform@0.15.2: + dependencies: + '@babel/runtime': 7.25.7 + + regexp.prototype.flags@1.5.3: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-errors: 1.3.0 + set-function-name: 2.0.2 + + regexpu-core@6.1.1: + dependencies: + regenerate: 1.4.2 + regenerate-unicode-properties: 10.2.0 + regjsgen: 0.8.0 + regjsparser: 0.11.1 + unicode-match-property-ecmascript: 2.0.0 + unicode-match-property-value-ecmascript: 2.2.0 + + regjsgen@0.8.0: {} + + regjsparser@0.11.1: + dependencies: + jsesc: 3.0.2 + + require-directory@2.1.1: {} + + resolve-cwd@3.0.0: + dependencies: + resolve-from: 5.0.0 + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve.exports@2.0.2: {} + + resolve@1.22.8: + dependencies: + is-core-module: 2.15.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.1.0: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-array-concat@1.1.2: + dependencies: + call-bind: 1.0.7 + get-intrinsic: 1.2.4 + has-symbols: 1.0.3 + isarray: 2.0.5 + + safe-regex-test@1.0.3: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-regex: 1.1.4 + + semver@5.7.2: {} + + semver@6.3.1: {} + + semver@7.6.3: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + gopd: 1.0.1 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + shallow-clone@3.0.1: + dependencies: + kind-of: 6.0.3 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shelljs@0.10.0: + dependencies: + execa: 5.1.1 + fast-glob: 3.3.3 + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + sisteransi@1.0.5: {} + + slash@3.0.0: {} + + source-map-support@0.5.13: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + sprintf-js@1.0.3: {} + + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + + string-length@4.0.2: + dependencies: + char-regex: 1.0.2 + strip-ansi: 6.0.1 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + string.prototype.trim@1.2.9: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-abstract: 1.23.3 + es-object-atoms: 1.0.0 + + string.prototype.trimend@1.0.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.1.0 + + strip-bom@4.0.0: {} + + strip-final-newline@2.0.0: {} + + strip-json-comments@3.1.1: {} + + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + test-exclude@6.0.0: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 7.2.3 + minimatch: 3.1.2 + + tmpl@1.0.5: {} + + to-fast-properties@2.0.0: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + ts-api-utils@1.3.0(typescript@5.6.3): + dependencies: + typescript: 5.6.3 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-detect@4.0.8: {} + + type-fest@0.21.3: {} + + typed-array-buffer@1.0.2: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + is-typed-array: 1.1.13 + + typed-array-byte-length@1.0.1: + dependencies: + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + + typed-array-byte-offset@1.0.2: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + + typed-array-length@1.0.6: + dependencies: + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-proto: 1.0.3 + is-typed-array: 1.1.13 + possible-typed-array-names: 1.0.0 + + typescript@5.6.3: {} + + unbox-primitive@1.0.2: + dependencies: + call-bind: 1.0.7 + has-bigints: 1.0.2 + has-symbols: 1.0.3 + which-boxed-primitive: 1.0.2 + + undici-types@7.10.0: {} + + unicode-canonical-property-names-ecmascript@2.0.1: {} + + unicode-match-property-ecmascript@2.0.0: + dependencies: + unicode-canonical-property-names-ecmascript: 2.0.1 + unicode-property-aliases-ecmascript: 2.1.0 + + unicode-match-property-value-ecmascript@2.2.0: {} + + unicode-property-aliases-ecmascript@2.1.0: {} + + update-browserslist-db@1.1.1(browserslist@4.24.0): + dependencies: + browserslist: 4.24.0 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + v8-to-istanbul@9.3.0: + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + '@types/istanbul-lib-coverage': 2.0.6 + convert-source-map: 2.0.0 + + v8flags@3.2.0: + dependencies: + homedir-polyfill: 1.0.3 + + walker@1.0.8: + dependencies: + makeerror: 1.0.12 + + which-boxed-primitive@1.0.2: + dependencies: + is-bigint: 1.0.4 + is-boolean-object: 1.1.2 + is-number-object: 1.0.7 + is-string: 1.0.7 + is-symbol: 1.0.4 + + which-typed-array@1.1.15: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.7 + for-each: 0.3.3 + gopd: 1.0.1 + has-tostringtag: 1.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + wrappy@1.0.2: {} + + write-file-atomic@4.0.2: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 3.0.7 + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yocto-queue@0.1.0: {} diff --git a/reference/implementing-a-concept-exercise.md b/reference/implementing-a-concept-exercise.md index c998d04e4a..a00f85310b 100644 --- a/reference/implementing-a-concept-exercise.md +++ b/reference/implementing-a-concept-exercise.md @@ -47,27 +47,30 @@ github/exercism | |── config.json | |── design.md | └── exemplar.js - ├── .eslintrc ├── .gitignore ├── babel.config.js + ├── eslint.config.mjs ├── global.d.ts (only if there are complex types required) + ├── jest.config.js ├── <slug>.js ├── <slug>.spec.js ├── package.json - └── yarn.lock + └── pnpm-lock.yaml ## Step 1: Add code files The configuration files may be copied from another exercise. We aim to keep these in sync: -> ⚠ Just like with _practice_ exercises, we will provide a script for you to run. This script needs to be updated from its v2 version, which has not yet been done. +> [!WARNING] +> Just like with _practice_ exercises, we will provide a script for you to run. This script needs to be updated from its v2 version, which has not yet been done. -- `.eslintrc` - `.gitignore` - `babel.config.js` +- `eslint.config.mjs` +- `jest.config.js` - `package.json` -- `yarn.lock` +- `pnpm-lock.yaml` The `package.json` file must be edited: @@ -90,7 +93,7 @@ The `package.json` file must be edited: Now create the following three files: -- `.cs`. the stub implementation file, which is the starting point for students to work on the exercise. +- `.js`. the stub implementation file, which is the starting point for students to work on the exercise. - `.spec.js`: the test suite. - `.meta/exemplar.js`: an exemplar implementation that passes all the tests. It **should** be an idiomatic solution. diff --git a/reference/info/concurrency.md b/reference/info/concurrency.md index 966f795769..f7bf218494 100644 --- a/reference/info/concurrency.md +++ b/reference/info/concurrency.md @@ -1,9 +1,8 @@ # Concurrency -> When people hear the word _concurrency_ they often think of _parallelism_, a related but quite distinct concept. -> In programming, concurrency is the _composition_ of independently executing processes, while parallelism is the -> _simultaneous execution_ of (possibly related) computations. -> [Concurrency is not parallelism](https://blog.golang.org/concurrency-is-not-parallelism) +> [!NOTE] +> When people hear the word _concurrency_ they often think of _parallelism_, a related but quite distinct concept.
    +> In programming, concurrency is the _composition_ of independently executing processes, while parallelism is the
    > _simultaneous execution_ of (possibly related) computations.
    > [Concurrency is not parallelism](https://blog.golang.org/concurrency-is-not-parallelism) See also [Event loop][concept-event-loop]. diff --git a/reference/info/event_loop.md b/reference/info/event_loop.md index cd11078717..074d7a99b4 100644 --- a/reference/info/event_loop.md +++ b/reference/info/event_loop.md @@ -1,6 +1,6 @@ # Event loop -> [JavaScript][language-javascript] has a concurrency model based on an event loop, which is responsible for executing the code, collecting and processing events, and executing queued sub-tasks. This model is quite different from models in other languages like [C][language-c] and Java. +> [!NOTE] > [JavaScript][language-javascript] has a concurrency model based on an event loop, which is responsible for executing the code, collecting and processing events, and executing queued sub-tasks. This model is quite different from models in other languages like [C][language-c] and Java. [language-c]: https://github.com/exercism/v3/blob/main/c/README.md [language-javascript]: ../../README.md diff --git a/reference/info/falsy.md b/reference/info/falsy.md index c16e85c9d2..6c0db4e421 100644 --- a/reference/info/falsy.md +++ b/reference/info/falsy.md @@ -4,6 +4,7 @@ A falsy value is a value that is considered `false` when encountered in a [Boole [JavaScript][language-javascript] uses [Type Conversion][concept-type-coercion] to coerce any value to a [Boolean][type-boolean] in contexts that require it, such as [conditionals][concept-conditionals] and [loops][concept-loops]. +> [!NOTE] > There are 7 falsy values in JavaScript. > > This means that when JavaScript is expecting a boolean and it is given one of the values below, it will always evaluate to "falsy". diff --git a/scripts/checksum b/scripts/checksum deleted file mode 100755 index 578bd7923d..0000000000 --- a/scripts/checksum +++ /dev/null @@ -1,132 +0,0 @@ -#!/usr/bin/env node - -/** - * Run this script (from root directory): npx babel-node scripts/checksum - * - * This will check root `package.json` matches each exercise's `package.json`. - * But the catch is there are some dependencies used for build but not served to end users - * We skip those dependencies while performing checksum. - * See `SKIP_PACKAGES_FOR_CHECKSUM` in helpers.js for list of skipped packages. - */ - -const path = require('path'); -const shell = require('shelljs'); -const helpers = require('./helpers'); - -/** - * Compares the file of the assignment with some known value - * - * @param filename file to check (local to the assignment directory) - * @param assignment slug with type, eg. practice/two-fer - * @param baseFile the file path that {filename} must be compared against - * @param expectedSha known value of {baseFile} - */ -function checksumAssignment(assignment, filename, baseFile, expectedSha) { - if (!assignment) { - return; - } - - const filePath = path.join('exercises', assignment, filename); - - let fileSha; - - if (filename == 'package.json') { - const packageJson = helpers.prepareExercisePackageJson(filePath, false); - - fileSha = helpers.shaPackageJson(packageJson); - } else { - const fileContents = shell.cat(filePath).toString(); - fileSha = helpers.sha(fileContents); - } - - if (fileSha !== expectedSha) { - const chalk = require('chalk'); - const diff = require('diff'); - - // prettier-ignore - shell.echo( - `[Failure] ${filename} did not match for ${assignment} (${chalk.red(expectedSha)} != ${chalk.green(fileSha)})\n`, - `! Expected ${chalk.red(baseFile)} to match ${chalk.green(filePath)}\n`, - `! Did you forget to run ${chalk.bold(`npx babel-node scripts/sync`)}?\n` - ); - - if (chalk.supportsColor) { - const diffParts = diff.diffLines( - shell.cat(filePath).toString(), - shell.cat(baseFile).toString(), - { newlineIsToken: false } - ); - - const output = diffParts - .map((part) => { - const color = part.added - ? chalk.green - : part.removed - ? chalk.red - : chalk.gray; - return color(part.value); - }) - .join(''); - - shell.echo(output); - } - - shell.exit(1); - } -} - -/** - * Check all the exercises, or given, {filename} against {rootFileName} - * - * @param filename filename in the exercise directory - * @param rootFileName filename in the root directory - */ -function checksumAll(filename, rootFileName = filename) { - const assignments = [shell.env['ASSIGNMENT']].filter(Boolean); - - let expectedSha; - - if (rootFileName == 'exercise-package.json') { - expectedSha = shell.cat('exercise-package.json.sha').toString(); - } else { - const fileContents = shell.cat(rootFileName).toString(); - expectedSha = helpers.sha(fileContents); - } - - if (assignments.length > 0) { - if ( - !assignments.every((assignment) => helpers.assertAssignment(assignment)) - ) { - shell.exit(); - } - - return assignments.every((assignment) => { - shell.echo(`Checking integrity of ${assignment}/${filename}...`); - return checksumAssignment( - assignment, - filename, - rootFileName, - expectedSha - ); - }); - } - - shell.echo( - `Checking integrity of ${filename} in all ${helpers.assignments.length} exercises` - ); - - helpers.assignments.forEach((assignment) => - checksumAssignment(assignment, filename, rootFileName, expectedSha) - ); -} - -helpers.registerExitHandler(); -helpers.createExercisePackageJson(true); - -checksumAll('package.json', 'exercise-package.json'); - -['.eslintrc', '.npmrc', 'babel.config.js', 'LICENSE'].forEach((fileToCheck) => { - checksumAll(fileToCheck); -}); - -shell.echo('All files passed the checksum test'); diff --git a/scripts/checksum.mjs b/scripts/checksum.mjs new file mode 100644 index 0000000000..b162d11efe --- /dev/null +++ b/scripts/checksum.mjs @@ -0,0 +1,123 @@ +#!/usr/bin/env node + +/** + * Run this script (from root directory): corepack pnpm node scripts/checksum.mjs + * + * This will check root `package.json` matches each exercise's `package.json`. + * But the catch is there are some dependencies used for build but not served to end users + * We skip those dependencies while performing checksum. + * See `SKIP_PACKAGES_FOR_CHECKSUM` in helpers.js for list of skipped packages. + */ + +import { join } from 'path'; +import shelljs from 'shelljs'; +import { + prepareExercisePackageJson, + shaPackageJson, + sha, + assertAssignment, + assignments as _assignments, + registerExitHandler, + createExercisePackageJson, +} from './helpers.mjs'; + +const { cat, echo, exit, env } = shelljs; + +/** + * Compares the file of the assignment with some known value + * + * @param filename file to check (local to the assignment directory) + * @param assignment slug with type, eg. practice/two-fer + * @param baseFile the file path that {filename} must be compared against + * @param expectedSha known value of {baseFile} + */ +function checksumAssignment(assignment, filename, baseFile, expectedSha) { + if (!assignment) { + return; + } + + const filePath = join('exercises', assignment, filename); + + let fileSha; + + if (filename === 'package.json') { + const packageJson = prepareExercisePackageJson(filePath, false); + + fileSha = shaPackageJson(packageJson); + } else { + const fileContents = cat(filePath).toString(); + fileSha = sha(fileContents); + } + + if (fileSha !== expectedSha) { + // prettier-ignore + echo( + `[Failure] ${filename} did not match for ${assignment} (${expectedSha} != ${fileSha})\n`, + `! Expected ${baseFile} to match ${filePath}\n`, + `! Did you forget to run ${"`corepack pnpm node scripts/sync.mjs`"}?\n` + ); + + exit(1); + } +} + +/** + * Check all the exercises, or given, {filename} against {rootFileName} + * + * @param filename filename in the exercise directory + * @param rootFileName filename in the root directory + */ +function checksumAll(filename, rootFileName = filename) { + const assignments = [env['ASSIGNMENT']].filter(Boolean); + + let expectedSha; + + if (rootFileName === 'exercise-package.json') { + expectedSha = cat('exercise-package.json.sha').toString(); + } else { + const fileContents = cat(rootFileName).toString(); + expectedSha = sha(fileContents); + } + + if (assignments.length > 0) { + if (!assignments.every((assignment) => assertAssignment(assignment))) { + exit(); + } + + return assignments.every((assignment) => { + echo(`Checking integrity of ${assignment}/${filename}...`); + return checksumAssignment( + assignment, + filename, + rootFileName, + expectedSha, + ); + }); + } + + echo( + `Checking integrity of ${filename} in all ${_assignments.length} exercises`, + ); + + _assignments.forEach((assignment) => { + checksumAssignment(assignment, filename, rootFileName, expectedSha); + }); +} + +registerExitHandler(); +createExercisePackageJson(true); + +checksumAll('package.json', 'exercise-package.json'); + +[ + '.gitignore', + '.npmrc', + 'babel.config.js', + 'eslint.config.mjs', + 'jest.config.js', + 'LICENSE', +].forEach((fileToCheck) => { + checksumAll(fileToCheck); +}); + +echo('All files passed the checksum test'); diff --git a/scripts/ci-check b/scripts/ci-check deleted file mode 100755 index e136ea5332..0000000000 --- a/scripts/ci-check +++ /dev/null @@ -1,141 +0,0 @@ -#!/usr/bin/env node - -/** - * Run this script (from root directory): npx babel-node scripts/ci-check - * - * This will run following checks: - * - * 1. Check config in all exercises matches - * 2. Run eslint to check code-style - * 3. Run tests against sample solutions - */ - -const shell = require('shelljs'); -const path = require('path'); - -const { - assignments, - assertAssignment, - registerExitHandler, - envIsThruthy, - hasStub, - cleanUp, - prepare, -} = require('./helpers'); - -const exercises = assignments; -if (exercises.length === 0) { - shell.echo('[Skip] None of the files are inside an exercise directory.'); - shell.exit(0); -} - -if (exercises.length === 1) { - if (!assertAssignment(exercises[0], true)) { - shell.exit(1); - } -} - -registerExitHandler(); - -if (!envIsThruthy('SKIP_STUB', false)) { - shell.echo('\n==========\nEnsure stubs are present\n'); - - // Inline the stub check instead of running scripts/stub-check, to save a - // few seconds. - const noStubs = exercises.filter((assignment) => !hasStub(assignment)); - - if (noStubs.length > 0) { - shell.echo(`[FAILURE] ${noStubs.length} missing a stub`); - noStubs.forEach((stub) => shell.echo(`${stub} is missing a stub file`)); - shell.exit(-1); - } else { - shell.echo('[SUCCES] All stubs are present'); - } -} - -if (!envIsThruthy('SKIP_INTEGRITY', false)) { - shell.echo('\n==========\nCheck configuration and packages integrity\n'); - - // If > 8 exercises, checksum everything as its faster than subprocessing - // TODO: be able to pass in any amount of exercises at once - if (exercises.length >= 8) { - const checkResult = shell.exec( - `npx babel-node ${path.join('scripts', 'checksum')}` - ).code; - - if (checkResult !== 0) { - shell.exit(checkResult); - } - - const nameCheckResult = shell.exec( - `npx babel-node ${path.join('scripts', 'name-check')}` - ).code; - - if (nameCheckResult !== 0) { - shell.exit(nameCheckResult); - } - } else { - exercises.forEach((exercise) => { - shell.env['ASSIGNMENT'] = exercise; - - const checkResult = shell.exec( - `npx babel-node ${path.join('scripts', 'checksum')}` - ).code; - - if (checkResult !== 0) { - shell.exit(checkResult); - } - - const nameCheckResult = shell.exec( - `npx babel-node ${path.join('scripts', 'name-check')}` - ).code; - - if (nameCheckResult !== 0) { - shell.exit(nameCheckResult); - } - }); - } - - const nameUniqResult = shell.exec( - `npx babel-node ${path.join('scripts', 'name-uniq')}` - ).code; - - if (nameUniqResult !== 0) { - shell.exit(nameUniqResult); - } - - const directoryResult = shell.exec( - `npx babel-node ${path.join('scripts', 'directory-check')}` - ).code; - - if (directoryResult !== 0) { - shell.echo( - `scripts/directory-check returned a non-zero exit code: ${directoryResult}` - ); - shell.exit(directoryResult); - } -} - -// Cleanup tmp directory if any exists -cleanUp(); - -/** - * Moves all example and test files to single directory - tmp_exercises - * This allows running the test command together for all files - * which is way faster than running once for each exercise - */ - -shell.echo('\n==========\nLint all the files\n'); - -shell.env['PREPARE'] = false; -shell.env['CLEANUP'] = false; - -exercises.forEach(prepare); - -shell.env['CLEANUP'] = true; - -const checkResult = shell.exec(`npx babel-node ${path.join('scripts', 'lint')}`) - .code; -if (checkResult != 0) { - shell.exit(checkResult); -} diff --git a/scripts/ci-check.mjs b/scripts/ci-check.mjs new file mode 100644 index 0000000000..8b6c2b614b --- /dev/null +++ b/scripts/ci-check.mjs @@ -0,0 +1,145 @@ +#!/usr/bin/env node + +/** + * Run this script (from root directory): corepack pnpm node scripts/ci-check.mjs + * + * This will run following checks: + * + * 1. Check config in all exercises matches + * 2. Run eslint to check code-style + * 3. Run tests against sample solutions + */ + +import shelljs from 'shelljs'; +import { join } from 'node:path'; +import { + assignments, + assertAssignment, + registerExitHandler, + envIsThruthy, + hasStub, + cleanUp, + prepare, +} from './helpers.mjs'; + +const { echo, exit, exec, env } = shelljs; + +const exercises = assignments; +if (exercises.length === 0) { + echo('[Skip] None of the files are inside an exercise directory.'); + exit(0); +} + +if (exercises.length === 1) { + if (!assertAssignment(exercises[0], true)) { + exit(1); + } +} + +registerExitHandler(false); + +if (!envIsThruthy('SKIP_STUB', false)) { + echo('\n==========\nEnsure stubs are present\n'); + + // Inline the stub check instead of running scripts/stub-check, to save a + // few seconds. + const noStubs = exercises.filter((assignment) => !hasStub(assignment)); + + if (noStubs.length > 0) { + echo(`[FAILURE] ${noStubs.length} missing a stub`); + noStubs.forEach((stub) => { + echo(`${stub} is missing a stub file`); + }); + exit(-1); + } else { + echo('[SUCCES] All stubs are present'); + } +} + +if (!envIsThruthy('SKIP_INTEGRITY', false)) { + echo('\n==========\nCheck configuration and packages integrity\n'); + + // If > 8 exercises, checksum everything as its faster than subprocessing + // TODO: be able to pass in any amount of exercises at once + if (exercises.length >= 8) { + const checkResult = exec( + `corepack pnpm node ${join('scripts', 'checksum.mjs')}`, + ).code; + + if (checkResult !== 0) { + exit(checkResult); + } + + const nameCheckResult = exec( + `corepack pnpm node ${join('scripts', 'name-check.mjs')}`, + ).code; + + if (nameCheckResult !== 0) { + exit(nameCheckResult); + } + } else { + exercises.forEach((exercise) => { + env['ASSIGNMENT'] = exercise; + + const checkResult = exec( + `corepack pnpm node ${join('scripts', 'checksum.mjs')}`, + ).code; + + if (checkResult !== 0) { + exit(checkResult); + } + + const nameCheckResult = exec( + `corepack pnpm node ${join('scripts', 'name-check.mjs')}`, + ).code; + + if (nameCheckResult !== 0) { + exit(nameCheckResult); + } + }); + } + + const nameUniqResult = exec( + `corepack pnpm node ${join('scripts', 'name-uniq.mjs')}`, + ).code; + + if (nameUniqResult !== 0) { + exit(nameUniqResult); + } + + const directoryResult = exec( + `corepack pnpm node ${join('scripts', 'directory-check.mjs')}`, + ).code; + + if (directoryResult !== 0) { + echo( + `scripts/directory-check returned a non-zero exit code: ${directoryResult}`, + ); + exit(directoryResult); + } +} + +// Cleanup tmp directory if any exists +cleanUp(); + +/** + * Moves all example and test files to single directory - tmp_exercises + * This allows running the test command together for all files + * which is way faster than running once for each exercise + */ + +echo('\n==========\nLint all the files\n'); + +env['PREPARE'] = false; +env['CLEANUP'] = false; + +exercises.forEach(prepare); + +env['CLEANUP'] = true; + +const checkResult = exec( + `corepack pnpm node ${join('scripts', 'lint.mjs')}`, +).code; +if (checkResult !== 0) { + exit(checkResult); +} diff --git a/scripts/ci b/scripts/ci.mjs old mode 100755 new mode 100644 similarity index 65% rename from scripts/ci rename to scripts/ci.mjs index d3afcb15c8..5828ff19f3 --- a/scripts/ci +++ b/scripts/ci.mjs @@ -3,7 +3,7 @@ /** * Run this script (from root directory): * - * npx babel-node scripts/ci + * corepack pnpm node scripts/ci.mjs * * This will run following checks: * @@ -11,27 +11,28 @@ * 2. Run tests against sample solutions */ -const { +import { prepareAndRun, prepare, cleanUp, registerExitHandler, assignments, -} = require('./helpers'); +} from './helpers.mjs'; +import shelljs from 'shelljs'; -const shell = require('shelljs'); +const { echo, exit, env } = shelljs; const exercises = assignments; if (exercises.length === 0) { - shell.echo('[Skip] None of the files are inside an exercise directory.'); - shell.exit(0); + echo('[Skip] None of the files are inside an exercise directory.'); + exit(0); } -registerExitHandler(); +registerExitHandler(false); -shell.env['PREPARE'] = false; -shell.env['CLEANUP'] = false; +env['PREPARE'] = false; +env['CLEANUP'] = false; const infoStr = `Running tests for ${ exercises.length === 1 ? exercises[0] : `${exercises.length} exercises\n` @@ -42,12 +43,12 @@ const failureStr = '[Failure] Tests failed!'; exercises.forEach(prepare); // Run tests -prepareAndRun('npx jest --bail tmp_exercises', infoStr, failureStr); +prepareAndRun('corepack pnpm jest --bail tmp_exercises', infoStr, failureStr); -shell.echo( +echo( exercises.length === 1 ? `[Success] Tests passed for ${exercises[0]}` - : `[Success] Tests passed for all ${exercises.length} exercises` + : `[Success] Tests passed for all ${exercises.length} exercises`, ); // Cleanup diff --git a/scripts/directory-check b/scripts/directory-check deleted file mode 100644 index 25e10c750e..0000000000 --- a/scripts/directory-check +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env node - -/** - * Run this script (from root directory): npx babel-node scripts/directory-check - * - * This will run following checks: - * - * 1. Package has the correct directory based on the path to the exercise. - * - * This script also allows fixing these names: npx babel-node scripts/directory-check --fix - */ - -const shell = require('shelljs'); -const path = require('path') - -let { assignments, registerExitHandler } = require('./helpers'); - -registerExitHandler(); - -// First 2 arguments are node and script name skip them -// Check if rest has --fix -const fix = process.argv.slice(2).includes('--fix'); - -// Check if package repository directory in each exercises' package.json is of the format "exercises/" - -assignments.forEach((assignment) => { - const filePath = path.join('exercises', assignment, 'package.json'); - const file = JSON.parse(shell.cat(filePath).toString()); - const givenRepositoryDirectory = file['repository']['directory']; - const expectedRepositoryDirectory = `exercises/${assignment}`; - - if (givenRepositoryDirectory === expectedRepositoryDirectory) { - shell.echo(`[Success]: Package repository directory for ${assignment} is in correct format.`); - return; - } - - if (fix) { - file['repository']['directory'] = expectedRepositoryDirectory; - const fileWithFixedRepositoryDirectory = new shell.ShellString( - JSON.stringify(file, undefined, 2) + '\n' - ); - fileWithFixedRepositoryDirectory.to(filePath); - shell.echo(`[Success]: Fixed package repository directory in ${filePath}`); - } else { - shell.echo( - `[Failure]: Package repository directory in ${filePath} must be ${expectedRepositoryDirectory}"` - ); - shell.exit(1); - } -}); - -shell.exit(0); diff --git a/scripts/directory-check.mjs b/scripts/directory-check.mjs new file mode 100644 index 0000000000..13b566dd50 --- /dev/null +++ b/scripts/directory-check.mjs @@ -0,0 +1,63 @@ +#!/usr/bin/env node + +/** + * Run this script (from root directory): + * + * $ corepack pnpm node scripts/directory-check.mjs + * + * This will run following checks: + * + * 1. Package has the correct directory based on the path to the exercise. + * + * This script also allows fixing these names: + * + * $ corepack pnpm node scripts/directory-check.mjs --fix + */ + +import shelljs from 'shelljs'; +import { join } from 'node:path'; + +const { cat, echo, ShellString, exit } = shelljs; + +import { assignments, registerExitHandler } from './helpers.mjs'; + +registerExitHandler(); + +// First 2 arguments are node and script name skip them +// Check if rest has --fix +const fix = process.argv.slice(2).includes('--fix'); + +// Check if package repository directory in each exercises' package.json is of the format "exercises/" + +assignments.forEach((assignment) => { + const filePath = join('exercises', assignment, 'package.json'); + const file = JSON.parse(cat(filePath).toString()); + const givenRepositoryDirectory = file['repository']['directory']; + const expectedRepositoryDirectory = `exercises/${assignment.replace( + '\\', + '/', + )}`; + + if (givenRepositoryDirectory === expectedRepositoryDirectory) { + echo( + `[Success]: Package repository directory for ${assignment} is in correct format.`, + ); + return; + } + + if (fix) { + file['repository']['directory'] = expectedRepositoryDirectory; + const fileWithFixedRepositoryDirectory = new ShellString( + JSON.stringify(file, undefined, 2) + '\n', + ); + fileWithFixedRepositoryDirectory.to(filePath); + echo(`[Success]: Fixed package repository directory in ${filePath}`); + } else { + echo( + `[Failure]: Package repository directory in ${filePath} must be ${expectedRepositoryDirectory}, actual: ${file['repository']['directory']}"`, + ); + exit(1); + } +}); + +exit(0); diff --git a/scripts/format b/scripts/format deleted file mode 100755 index 460f2b2409..0000000000 --- a/scripts/format +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env node - -/* - * Run this script (from root directory): npx babel-node scripts/format - * - * This runs `prettier` on all applicable files, FORCES using the same version - * as the CI uses to check if the files have been formatted. - */ - -const shell = require('shelljs'); -const path = require('path'); -const { basename } = require('path'); - -const basedir = path.resolve(basename(__dirname), '..'); -const workflow = path.join( - basedir, - '.github', - 'workflows', - 'verify-code-formatting.yml' -); - -let EXERCISM_PRETTIER_VERSION; - -const versionLine = shell - .cat(workflow) - .stdout.split('\n') - .map((line) => line.trim()) - .find((line) => line.startsWith('EXERCISM_PRETTIER_VERSION:')); - -if (!versionLine) { - const { stdout: versionFromPackage } = shell.exec("npm list prettier | sed -n -e 's/^.*prettier@//p'"); - EXERCISM_PRETTIER_VERSION = versionFromPackage.trim(); -} else { - EXERCISM_PRETTIER_VERSION = versionLine - .split(':')[1] - .trim() - .replace(/'/g, ''); -} - -const command = `npx "prettier@${EXERCISM_PRETTIER_VERSION}" --write "**/*.{js,jsx,ts,tsx,css,sass,scss,html,json,md,yml}"`; -shell.echo(`[format] ${command}`); - -const result = shell.exec(command); -shell.echo(`[format] ${result}`); diff --git a/scripts/format.mjs b/scripts/format.mjs new file mode 100644 index 0000000000..bea4f521df --- /dev/null +++ b/scripts/format.mjs @@ -0,0 +1,77 @@ +#!/usr/bin/env node + +/* + * Run this script (from root directory): + * + * $ corepack pnpm node scripts/format.mjs + * + * This runs `prettier` on all applicable files, FORCES using the same version + * as the CI uses to check if the files have been formatted. + */ + +import shell from 'shelljs'; +import path, { join, sep } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { mkdtempSync } from 'node:fs'; +import { tmpdir } from 'node:os'; + +const basedir = path.resolve( + path.basename(fileURLToPath(import.meta.url)), + '..', +); + +const workflow = path.join( + basedir, + '.github', + 'workflows', + 'verify-code-formatting.yml', +); + +let EXERCISM_PRETTIER_VERSION; + +const versionLine = shell + .cat(workflow) + .stdout.split('\n') + .map((line) => line.trim()) + .find((line) => line.startsWith('EXERCISM_PRETTIER_VERSION:')); + +if (!versionLine) { + const tempDir = mkdtempSync(`${tmpdir()}${sep}`); + const versionInfo = join(tempDir, 'info.txt'); + shell.touch(versionInfo); + + shell + .exec('corepack pnpm list prettier --parseable', { silent: true }) + .to(versionInfo); + + shell.echo(shell.cat(versionInfo)); + + shell.sed(/^.*prettier@/, '', versionInfo).to(versionInfo); + shell.sed(/\\node_modules\\prettier$/, '', versionInfo).to(versionInfo); + + const versionFromPackage = shell.cat(versionInfo).split('\n')[1].trim(); + shell.rm('-fr', tempDir); + + if (versionFromPackage && !versionFromPackage.includes('Usage Error')) { + EXERCISM_PRETTIER_VERSION = versionFromPackage.trim(); + } else { + EXERCISM_PRETTIER_VERSION = process.env.EXERCISM_PRETTIER_VERSION; + if (!EXERCISM_PRETTIER_VERSION) { + shell.error( + 'Could not find prettier version in corepack pnpm list or ENV', + ); + shell.exit(-1); + } + } +} else { + EXERCISM_PRETTIER_VERSION = versionLine + .split(':')[1] + .trim() + .replace(/'/g, ''); +} + +const command = `corepack pnpm dlx prettier@${EXERCISM_PRETTIER_VERSION} --write "**/*.{js,jsx,cjs,mjs,ts,tsx,css,sass,scss,html,json,md,yml}"`; +shell.echo(`[format] ${command}`); + +// Will print to console +shell.exec(command, { silent: false }); diff --git a/scripts/helpers.js b/scripts/helpers.mjs similarity index 87% rename from scripts/helpers.js rename to scripts/helpers.mjs index eebe3850fc..3c93c453da 100644 --- a/scripts/helpers.js +++ b/scripts/helpers.mjs @@ -2,13 +2,15 @@ * This file provides helper functions & is NOT intended to be run as a script. */ -const shell = require('shelljs'); -const path = require('path'); -const crypto = require('crypto'); +import shell from 'shelljs'; +import path from 'node:path'; +import crypto from 'node:crypto'; const exerciseDirs = shell.ls( '-d', - path.join('exercises', '{practice,concept}', '*') + + // Shell.js ls wants / paths + path.join('exercises', '{practice,concept}', '*').replaceAll('\\', '/'), ); export const packageFiles = exerciseDirs.map((dir) => `${dir}/package.json`); @@ -19,7 +21,7 @@ export const assignments = shell.env['ASSIGNMENT'] function knownAssignments() { return exerciseDirs.map((directory) => - path.join(path.basename(path.dirname(directory)), path.basename(directory)) + path.join(path.basename(path.dirname(directory)), path.basename(directory)), ); } @@ -37,25 +39,24 @@ export function assertAssignment(assignment, shouldExist = true) { } shell.echo("[Failure] that's not a valid assignment reference"); - const chalk = require('chalk'); if (assignment.split(path.sep).length === 1) { // prettier-ignore shell.echo(` -Expected ${chalk.cyan(`{type}${path.sep}{slug}`)}, actual: ${chalk.yellow(assignment)}. -- Use ${chalk.green(`concept${path.sep}${assignment}`)} if ${chalk.yellow(assignment)} is a concept exercise. -- Use ${chalk.green(`practice${path.sep}${assignment}`)} if ${chalk.yellow(assignment)} is a practice exercise. +Expected ${`{type}${path.sep}{slug}`}, actual: ${assignment}. +- Use ${`concept${path.sep}${assignment}`} if ${assignment} is a concept exercise. +- Use ${`practice${path.sep}${assignment}`} if ${assignment} is a practice exercise. `.trim()); } const suggestions = knownAssignments().filter((known) => - known.includes(assignment) + known.includes(assignment), ); if (suggestions.length > 0 && suggestions.length < 5) { shell.echo( '\nDid you mean:\n' + - suggestions.map((suggestion) => `- ${suggestion}`).join('\n') + suggestions.map((suggestion) => `- ${suggestion}`).join('\n'), ); } @@ -77,7 +78,7 @@ export function findExerciseDirectory(input) { return path.join( path.basename(path.dirname(directory)), - path.basename(directory) + path.basename(directory), ); } @@ -86,7 +87,7 @@ export function fileConfiguration(assignment) { 'exercises', assignment, '.meta', - 'config.json' + 'config.json', ); let files = undefined; @@ -104,7 +105,7 @@ export function fileConfiguration(assignment) { files[key] = files[key].map((value) => value .replace('%{kebab_slug}', path.basename(assignment)) - .replace(/[/\\]/g, path.sep) + .replace(/[/\\]/g, path.sep), ); }); @@ -115,7 +116,7 @@ export function hasStub(assignment) { const stubFiles = fileConfiguration(assignment).solution; return stubFiles.every((stubFile) => - shell.test('-f', path.join('exercises', assignment, stubFile)) + shell.test('-f', path.join('exercises', assignment, stubFile)), ); } @@ -172,13 +173,7 @@ export function cleanUp() { // These packages will be skipped while performing checksum. In other words, // these packages are only interesting for maintaining this repository and not // for the student. -const SKIP_PACKAGES_FOR_CHECKSUM = [ - 'shelljs', - '@babel/node', - 'prettier', - 'diff', - 'chalk', -]; +const SKIP_PACKAGES_FOR_CHECKSUM = ['shelljs', 'prettier']; // These fields may differ between package.json files. const SKIP_FIELDS_FOR_CHECKSUM = [ @@ -193,12 +188,12 @@ export function createExercisePackageJson(writeSha = false) { const packageJson = JSON.parse(packageFile); // Filter out some unwanted packages and create package.json for exercises - SKIP_PACKAGES_FOR_CHECKSUM.forEach( - (pkg) => delete packageJson['devDependencies'][pkg] - ); + SKIP_PACKAGES_FOR_CHECKSUM.forEach((pkg) => { + delete packageJson['devDependencies'][pkg]; + }); const shellStr = new shell.ShellString( - JSON.stringify(packageJson, undefined, 2) + '\n' + JSON.stringify(packageJson, undefined, 2) + '\n', ); shellStr.to('exercise-package.json'); @@ -224,7 +219,7 @@ export function mergePackageJsons(basePackageJson, packageJson, assignment) { }); const extraRepositoryKeys = Object.keys(packageJson.repository || {}).filter( - (key) => !basePackageJson.repository[key] + (key) => !basePackageJson.repository[key], ); extraRepositoryKeys.forEach((key) => { @@ -233,7 +228,7 @@ export function mergePackageJsons(basePackageJson, packageJson, assignment) { mergedPackageJson.repository.directory = `exercises/${assignment.replace( '\\', - '/' + '/', )}`; return mergedPackageJson; @@ -287,7 +282,7 @@ export function prepare(assignment) { if (!shell.test('-f', specFile)) { if (specFileName !== 'custom.spec.js') { console.warn( - `Skipped copying test file for ${assignment}: ${specFileName} because it doesn't exist` + `Skipped copying test file for ${assignment}: ${specFileName} because it doesn't exist`, ); } @@ -297,7 +292,7 @@ export function prepare(assignment) { const specFileDestination = path.join( 'tmp_exercises', assignment, - specFileName + specFileName, ); shell.mkdir('-p', path.dirname(specFileDestination)); @@ -314,7 +309,7 @@ export function prepare(assignment) { .sed(/x(test|it)\(/, 'test(', specFileDestination) .to(specFileDestination); shell - .sed('xdescribe', 'describe', specFileDestination) + .sed(/xdescribe\(/, 'describe(', specFileDestination) .to(specFileDestination); }); @@ -325,7 +320,7 @@ export function prepare(assignment) { const exampleFileDestination = path.join( 'tmp_exercises', assignment, - files.solution[i] + files.solution[i], ); shell.sed("from '../", "from './", exampleFile).to(exampleFileDestination); @@ -340,7 +335,7 @@ export function prepare(assignment) { const solutionFileDestination = path.join( 'tmp_exercises', assignment, - extraLibFileName + extraLibFileName, ); shell.cp(solutionFile, solutionFileDestination); @@ -354,7 +349,7 @@ export function prepare(assignment) { const readonlyFileDestination = path.join( 'tmp_exercises', assignment, - readonlyFileName + readonlyFileName, ); shell.cp(readonlyFile, readonlyFileDestination); @@ -366,7 +361,7 @@ export function prepare(assignment) { if (shell.test('-d', libDir)) { shell.cp( path.join(libDir, '*.js'), - path.join('tmp_exercises', assignment, 'lib') + path.join('tmp_exercises', assignment, 'lib'), ); } @@ -377,14 +372,16 @@ export function prepare(assignment) { if (shell.test('-d', dataDir)) { shell.cp( path.join(dataDir, '*'), - path.join('tmp_exercises', assignment, 'data') + path.join('tmp_exercises', assignment, 'data'), ); } } -export function registerExitHandler() { +export function registerExitHandler(forceCleanup = true) { function exitHandler(options, exitCode) { - cleanUp(); + if (shouldCleanup() || forceCleanup) { + cleanUp(); + } if (options.error) { console.error(options.error); @@ -414,6 +411,6 @@ export function registerExitHandler() { //catches uncaught exceptions process.on('uncaughtException', (error) => - exitHandler({ exit: true, error }) + exitHandler({ exit: true, error }), ); } diff --git a/scripts/lint b/scripts/lint deleted file mode 100755 index b57aaaf529..0000000000 --- a/scripts/lint +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env node - -/** - * Run this script (from root directory): npx babel-node scripts/lint - * - * This runs `eslint` on all sample solutions (and test) files - */ - -const shell = require('shelljs'); -const { - registerExitHandler, - prepareAndRun, - assignments: exercises, - shouldPrepare, -} = require('./helpers'); - -registerExitHandler(); - -const assignment = exercises.length === 0 ? exercises[0] : undefined; - -// Prepare exercises and cleanup afterwards -shell.env['PREPARE'] = - shell.env['PREPARE'] === undefined || shell.env['PREPARE']; -shell.env['CLEANUP'] = - shell.env['CLEANUP'] === undefined || shell.env['CLEANUP']; - -const count = shouldPrepare() ? exercises.length : 'all'; - -const infoStr = assignment - ? `Running lint for ${assignment}...` - : `Running lint for ${count} exercises...`; -const failureStr = '[Failure] Lint check failed!'; - -// Run lint all at once -prepareAndRun( - 'npx eslint tmp_exercises -c node_modules/@exercism/eslint-config-javascript/maintainers.js', - infoStr, - failureStr -); - -shell.echo( - assignment - ? `[Success] Lint passed for ${assignment}` - : `[Success] Lint passed for ${count} exercises` -); diff --git a/scripts/lint.mjs b/scripts/lint.mjs new file mode 100644 index 0000000000..47d1f75592 --- /dev/null +++ b/scripts/lint.mjs @@ -0,0 +1,65 @@ +#!/usr/bin/env node + +/** + * Run this script (from root directory): + * + * $ corepack pnpm node scripts/lint.mjs + * + * This runs `eslint` on all sample solutions (and test) files + */ + +import path from 'node:path'; +import shell from 'shelljs'; +import { + registerExitHandler, + prepareAndRun, + assignments as exercises, + shouldPrepare, +} from './helpers.mjs'; + +registerExitHandler(false); + +const assignment = exercises.length === 0 ? exercises[0] : undefined; + +// Prepare exercises and cleanup afterwards +shell.env['PREPARE'] = + shell.env['PREPARE'] === undefined || shell.env['PREPARE']; +shell.env['CLEANUP'] = + shell.env['CLEANUP'] === undefined || shell.env['CLEANUP']; + +const count = shouldPrepare() ? exercises.length : 'all'; + +const infoStr = assignment + ? `Running lint for ${assignment}...` + : `Running lint for ${count} exercises...`; +const failureStr = '[Failure] Lint check failed!'; + +shell.mkdir('-p', 'tmp_exercises'); +shell.cp('babel.config.js', path.join('tmp_exercises', 'babel.config.js')); +shell.cp('eslint.config.mjs', path.join('tmp_exercises', 'eslint.config.mjs')); + +shell.sed( + '-i', + '\\*\\.spec\\.js', + '**/*.spec.js', + path.join('tmp_exercises', 'eslint.config.mjs'), +); +shell.sed( + '-i', + '// <>', + "{ files: ['**/*.spec.js'], rules: { 'no-unused-vars': ['error', { varsIgnorePattern: 'xdescribe|xtest' }] }},", + path.join('tmp_exercises', 'eslint.config.mjs'), +); + +// Run lint all at once +prepareAndRun( + 'corepack pnpm eslint tmp_exercises -c tmp_exercises/eslint.config.mjs', + infoStr, + failureStr, +); + +shell.echo( + assignment + ? `[Success] Lint passed for ${assignment}` + : `[Success] Lint passed for ${count} exercises`, +); diff --git a/scripts/name-check b/scripts/name-check.mjs old mode 100755 new mode 100644 similarity index 58% rename from scripts/name-check rename to scripts/name-check.mjs index 09c490f03e..59fad3ab44 --- a/scripts/name-check +++ b/scripts/name-check.mjs @@ -1,32 +1,44 @@ #!/usr/bin/env node /** - * Run this script (from root directory): npx babel-node scripts/name-check + * Run this script (from root directory): + * + * $ corepack pnpm node scripts/name-check.mjs * * This will run following checks: * * 1. Package name is of the format "@exercism/javascript-" * - * This script also allows fixing these names: npx babel-node scripts/name-check --fix + * This script also allows fixing these names: + * + * $ corepack pnpm node scripts/name-check.mjs --fix */ -const shell = require('shelljs'); - -let { packageFiles, registerExitHandler } = require('./helpers'); +import shell from 'shelljs'; +import path from 'node:path'; +import { packageFiles, registerExitHandler } from './helpers.mjs'; registerExitHandler(); +let exitCode = 0; + // First 2 arguments are node and script name skip them // Check if rest has --fix const fix = process.argv.slice(2).includes('--fix'); -const envAssignment = shell.env['ASSIGNMENT']; -if (envAssignment) { - packageFiles = [`exercises/${envAssignment}/package.json`]; +if (fix) { + shell.echo('=============================================='); + shell.echo('Fixing package names where necessary'); + shell.echo('----------------------------------------------'); } +const envAssignment = shell.env['ASSIGNMENT']; +const finalPackageFiles = envAssignment + ? [path.join('exercises', envAssignment, 'package.json')] + : packageFiles; + // Check if package name in each exercises' package.json is of the format "@exercism/javascript-" -packageFiles.forEach((filePath) => { +finalPackageFiles.forEach((filePath) => { const file = JSON.parse(shell.cat(filePath).toString()); const givenName = file['name']; @@ -41,16 +53,16 @@ packageFiles.forEach((filePath) => { if (fix) { file['name'] = expectedName; const fileWithFixedName = new shell.ShellString( - JSON.stringify(file, undefined, 2) + '\n' + JSON.stringify(file, undefined, 2) + '\n', ); fileWithFixedName.to(filePath); shell.echo(`[Success]: Fixed package name in ${filePath}`); } else { + exitCode = 1; shell.echo( - `[Failure]: Package name in ${filePath} must be ${expectedName}"` + `[Failure]: Package name in ${filePath} must be ${expectedName}"`, ); - shell.exit(1); } }); -shell.exit(0); +shell.exit(exitCode); diff --git a/scripts/name-uniq b/scripts/name-uniq.mjs old mode 100755 new mode 100644 similarity index 62% rename from scripts/name-uniq rename to scripts/name-uniq.mjs index 4a9dc92187..a058bfb1d4 --- a/scripts/name-uniq +++ b/scripts/name-uniq.mjs @@ -1,31 +1,34 @@ #!/usr/bin/env node /** - * Run this script (from root directory): npx babel-node scripts/name-uniq + * Run this script (from root directory): + * + * $ corepack pnpm node scripts/name-uniq.mjs * * This will run following checks: * * 1. All exercises have unique package names in their package.json files. */ -const shell = require('shelljs'); -const { packageFiles, registerExitHandler } = require('./helpers'); +import shell from 'shelljs'; +import { packageFiles, registerExitHandler } from './helpers.mjs'; registerExitHandler(); +let exitCode = 0; const allNames = packageFiles.map( - (filePath) => JSON.parse(shell.cat(filePath).toString())['name'] + (filePath) => JSON.parse(shell.cat(filePath).toString())['name'], ); // Check if all exercises have unique package names const duplicates = allNames.filter((e, i) => allNames.indexOf(e) !== i); - if (duplicates.length !== 0) { + exitCode = 1; shell.echo( - `[Failure] Duplicate package names found: ${duplicates.join(', ')}` + `[Failure] Duplicate package names found: ${duplicates.join(', ')}`, ); - shell.exit(1); } -shell.echo('[Success] All package names are unique.'); -shell.exit(0); +if (exitCode === 0) { + shell.echo('[Success] All package names are unique.'); +} diff --git a/scripts/pr-check b/scripts/pr-check.mjs old mode 100755 new mode 100644 similarity index 75% rename from scripts/pr-check rename to scripts/pr-check.mjs index dbdcf7b38a..6a266d8235 --- a/scripts/pr-check +++ b/scripts/pr-check.mjs @@ -3,7 +3,7 @@ /** * Run this script (from root directory): * - * npx babel-node scripts/pr-check path/1 path/2 path/3 + * $ corepack pnpm node scripts/pr-check.mjs path/1 path/2 path/3 * * This will run following checks: * 1. Find the exercises at all the paths provided @@ -12,23 +12,23 @@ * 4. Run eslint for those exercises to check code-style */ -const { +import shell from 'shelljs'; +import path from 'node:path'; +import { findExerciseDirectory, + prepare, cleanUp, registerExitHandler, envIsThruthy, hasStub, - prepare, assignments, -} = require('./helpers'); +} from './helpers.mjs'; -const shell = require('shelljs'); -const path = require('path'); const files = process.argv.slice(2); if (files.length === 0) { shell.echo( - '[Failure] No files passed in. Pass in paths to exercise directories or its file.' + '[Failure] No files passed in. Pass in paths to exercise directories or its file.', ); shell.exit(-1); } @@ -47,12 +47,12 @@ const hasRootFile = files.some((file) => file === 'package.json'); if (hasRootFile) { shell.echo( - '[Root PR] When package.json is changed, all exercises need to be checked' + '[Root PR] When package.json is changed, all exercises need to be checked', ); } else if (_exercises.length > 8) { shell.echo( '[Big PR] When more than 8 exercises are being checked, all of them are ' + - 'checked as this is likely a PR affecting everything.' + 'checked as this is likely a PR affecting everything.', ); } @@ -64,7 +64,7 @@ if (exercises.length === 0) { shell.exit(0); } -registerExitHandler(); +registerExitHandler(false); if (!envIsThruthy('SKIP_STUB', false)) { shell.echo('\n==========\nEnsure stubs are present\n'); @@ -75,7 +75,9 @@ if (!envIsThruthy('SKIP_STUB', false)) { if (noStubs.length > 0) { shell.echo(`[FAILURE] ${noStubs.length} missing a stub`); - noStubs.forEach((stub) => shell.echo(`${stub} is missing a stub file`)); + noStubs.forEach((stub) => { + shell.echo(`${stub} is missing a stub file`); + }); shell.exit(-1); } else { shell.echo('[SUCCES] All stubs are present'); @@ -89,46 +91,46 @@ if (!envIsThruthy('SKIP_INTEGRITY', false)) { shell.env['ASSIGNMENT'] = exercise; const checkResult = shell.exec( - `npx babel-node ${path.join('scripts', 'checksum')}` + `corepack pnpm node ${path.join('scripts', 'checksum.mjs')}`, ).code; if (checkResult !== 0) { shell.echo( - `scripts/checksum returned a non-zero exit code: ${checkResult}` + `scripts/checksum.mjs returned a non-zero exit code: ${checkResult}`, ); shell.exit(checkResult); } const nameCheckResult = shell.exec( - `npx babel-node ${path.join('scripts', 'name-check')}` + `corepack pnpm node ${path.join('scripts', 'name-check.mjs')}`, ).code; if (nameCheckResult !== 0) { shell.echo( - `scripts/name-check returned a non-zero exit code: ${nameCheckResult}` + `scripts/name-check.mjs returned a non-zero exit code: ${nameCheckResult}`, ); shell.exit(nameCheckResult); } }); const nameUniqResult = shell.exec( - `npx babel-node ${path.join('scripts', 'name-uniq')}` + `corepack pnpm node ${path.join('scripts', 'name-uniq.mjs')}`, ).code; if (nameUniqResult !== 0) { shell.echo( - `scripts/name-uniq returned a non-zero exit code: ${nameUniqResult}` + `scripts/name-uniq.mjs returned a non-zero exit code: ${nameUniqResult}`, ); shell.exit(nameUniqResult); } const directoryResult = shell.exec( - `npx babel-node ${path.join('scripts', 'directory-check')}` + `ncorepack pnpm node ${path.join('scripts', 'directory-check.mjs')}`, ).code; if (directoryResult !== 0) { shell.echo( - `scripts/directory-check returned a non-zero exit code: ${directoryResult}` + `scripts/directory-check.mjs returned a non-zero exit code: ${directoryResult}`, ); shell.exit(directoryResult); } @@ -154,7 +156,7 @@ exercises.forEach(prepare); shell.env['CLEANUP'] = true; const checkResult = shell.exec( - `npx babel-node ${path.join('scripts', 'lint')}` + `corepack pnpm node ${path.join('scripts', 'lint.mjs')}`, ).code; if (checkResult !== 0) { shell.echo(`scripts/lint returned a non-zero exit code: ${checkResult}`); diff --git a/scripts/pr b/scripts/pr.mjs old mode 100755 new mode 100644 similarity index 83% rename from scripts/pr rename to scripts/pr.mjs index a3c27225bf..f8ab927ff6 --- a/scripts/pr +++ b/scripts/pr.mjs @@ -3,7 +3,7 @@ /** * Run this script (from root directory): * - * npx babel-node scripts/pr path/1 path/2 path/3 + * $ corepack pnpm node scripts/pr.mjs path/1 path/2 path/3 * * This will run following checks: * @@ -11,21 +11,21 @@ * 2. Run tests for those exercises against sample solutions */ -const { +import shell from 'shelljs'; +import { findExerciseDirectory, prepareAndRun, prepare, cleanUp, registerExitHandler, assignments, -} = require('./helpers'); +} from './helpers.mjs'; -const shell = require('shelljs'); const files = process.argv.slice(2); if (files.length === 0) { shell.echo( - '[Failure] No files passed in. Pass in paths to exercise directories or its file.' + '[Failure] No files passed in. Pass in paths to exercise directories or its file.', ); shell.exit(-1); } @@ -44,12 +44,12 @@ const hasRootFile = files.some((file) => file === 'package.json'); if (hasRootFile) { shell.echo( - '[Root PR] When package.json is changed, all exercises need to be checked' + '[Root PR] When package.json is changed, all exercises need to be checked', ); } else if (_exercises.length > 8) { shell.echo( '[Big PR] When more than 8 exercises are being checked, all of them are ' + - 'checked as this is likely a PR affecting everything.' + 'checked as this is likely a PR affecting everything.', ); } @@ -61,7 +61,7 @@ if (exercises.length === 0) { shell.exit(0); } -registerExitHandler(); +registerExitHandler(false); shell.env['PREPARE'] = false; shell.env['CLEANUP'] = false; @@ -75,12 +75,16 @@ const failureStr = '[Failure] Tests failed!'; exercises.forEach(prepare); // Run tests -prepareAndRun('npx jest --bail tmp_exercises --runInBand', infoStr, failureStr); +prepareAndRun( + 'corepack pnpm jest --bail tmp_exercises --runInBand', + infoStr, + failureStr, +); shell.echo( exercises.length === 1 ? `[Success] Tests passed for ${exercises[0]}` - : `[Success] Tests passed for all ${exercises.length} exercises` + : `[Success] Tests passed for all ${exercises.length} exercises`, ); // Cleanup diff --git a/scripts/stub-check b/scripts/stub-check deleted file mode 100755 index 10cbfb3e88..0000000000 --- a/scripts/stub-check +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env node - -/** - * Run this script (from root directory): npx babel-node scripts/stub-check - * - * This script checks that all exercises have a stub file. - * Ref: https://github.com/exercism/javascript/issues/705 - */ - -const shell = require("shelljs"); -const helpers = require("./helpers"); - -const noStubs = helpers.assignments.filter( - (assignment) => !helpers.hasStub(assignment) -); - -if (noStubs.length > 0) { - shell.echo("[Error]: No stub files found for following exercises:"); - shell.echo(noStubs.join("\n")); - shell.exit(1); -} - -if (shell.env["ASSIGNMENT"]) { - shell.echo(`[Success]: Stub file present for ${shell.env["ASSIGNMENT"]}`); -} else { - shell.echo("[Success]: Stub files present for all exercises!"); -} diff --git a/scripts/stub-check.mjs b/scripts/stub-check.mjs new file mode 100644 index 0000000000..3ba935af9c --- /dev/null +++ b/scripts/stub-check.mjs @@ -0,0 +1,29 @@ +#!/usr/bin/env node + +/** + * Run this script (from root directory): + * + * $ corepack pnpm node scripts/stub-check.mjs + * + * This script checks that all exercises have a stub file. + * Ref: https://github.com/exercism/javascript/issues/705 + */ + +import shell from 'shelljs'; +import * as helpers from './helpers.mjs'; + +const noStubs = helpers.assignments.filter( + (assignment) => !helpers.hasStub(assignment), +); + +if (noStubs.length > 0) { + shell.echo('[Error]: No stub files found for following exercises:'); + shell.echo(noStubs.join('\n')); + shell.exit(1); +} + +if (shell.env['ASSIGNMENT']) { + shell.echo(`[Success]: Stub file present for ${shell.env['ASSIGNMENT']}`); +} else { + shell.echo('[Success]: Stub files present for all exercises!'); +} diff --git a/scripts/sync b/scripts/sync.mjs old mode 100755 new mode 100644 similarity index 63% rename from scripts/sync rename to scripts/sync.mjs index abf8ede609..dd01fcb401 --- a/scripts/sync +++ b/scripts/sync.mjs @@ -1,24 +1,21 @@ #!/usr/bin/env node /** - * Run this script (from root directory): npx babel-node scripts/sync + * Run this script (from root directory): * - * This script is used to copy the following files to all exercises and keep - * them in sync: + * $ corepack pnpm scripts/sync.mjs * - * - .eslintrc - * - babel.config.js - * - package.json (modified version) - * - .npmrc + * This script is used to copy the required files. * * There is a CI step which checks that package.json in root & exercises match * (see checksum script for more info). */ -const shell = require('shelljs'); -const assignment = shell.env['ASSIGNMENT']; -const helpers = require('./helpers'); -const path = require('path'); +import shell from 'shelljs'; +import * as helpers from './helpers.mjs'; +import path from 'node:path'; + +const assignmentFromEnv = shell.env['ASSIGNMENT']; function copyConfigForAssignment(assignment) { const destination = path.join('exercises', assignment); @@ -26,19 +23,32 @@ function copyConfigForAssignment(assignment) { const packageJson = getCurrentPackageJson(assignmentPackageFilename); const basePackageJson = JSON.parse( - shell.cat('exercise-package.json').toString() + shell.cat('exercise-package.json').toString(), ); const mergedPackageJson = helpers.mergePackageJsons( basePackageJson, packageJson, - assignment + assignment, ); shell .ShellString(JSON.stringify(mergedPackageJson, undefined, 2) + '\n') .to(assignmentPackageFilename); - ['.eslintrc', '.npmrc', 'babel.config.js', 'LICENSE'].forEach((file) => { + // DELETE legacy + ['.eslintignore', '.eslintrc'].forEach((file) => { + const source = path.join(destination, file); + shell.rm('-f', source); + }); + + [ + '.npmrc', + 'babel.config.js', + 'eslint.config.mjs', + 'jest.config.js', + 'LICENSE', + '.gitignore', + ].forEach((file) => { shell.cp(file, destination); }); } @@ -47,7 +57,7 @@ function getCurrentPackageJson(assignmentPackageFilename) { const packageFile = shell.cat(assignmentPackageFilename).toString(); if (!packageFile) { const packageJson = JSON.parse( - shell.cat('exercise-package.json').toString() + shell.cat('exercise-package.json').toString(), ); const conceptName = path @@ -66,13 +76,13 @@ function getCurrentPackageJson(assignmentPackageFilename) { helpers.registerExitHandler(); helpers.createExercisePackageJson(false); -if (assignment) { - if (!helpers.assertAssignment(assignment)) { +if (assignmentFromEnv) { + if (!helpers.assertAssignment(assignmentFromEnv)) { shell.exit(1); } - shell.echo('Syncing ' + assignment + '...'); - copyConfigForAssignment(assignment); + shell.echo('Syncing ' + assignmentFromEnv + '...'); + copyConfigForAssignment(assignmentFromEnv); } else { shell.echo('Syncing all assignments...'); helpers.assignments.forEach((assignment) => { diff --git a/scripts/test b/scripts/test deleted file mode 100755 index fff22bb857..0000000000 --- a/scripts/test +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env node - -/** - * Run this script (from root directory): npx babel-node scripts/test - * - * This runs `jest` tests for all sample solutions - */ - -const shell = require("shelljs"); -const helpers = require("./helpers"); -const assignment = shell.env["ASSIGNMENT"]; - -const infoStr = assignment - ? "\nRunning tests for " + assignment + "..." - : "\nRunning tests for all exercises..."; -const failureStr = "[Failure] Tests failed!"; - -// Copies the necessary files -shell.env["PREPARE"] = true; - -// Cleans up after -shell.env["CLEANUP"] = true; - -helpers.prepareAndRun("npx jest --bail tmp_exercises", infoStr, failureStr); diff --git a/scripts/test.mjs b/scripts/test.mjs new file mode 100644 index 0000000000..193ccdda98 --- /dev/null +++ b/scripts/test.mjs @@ -0,0 +1,26 @@ +#!/usr/bin/env node + +/** + * Run this script (from root directory): + * + * $ corepack pnpm node scripts/test.mjs + * + * This runs `jest` tests for all sample solutions + */ + +import { env } from 'shelljs'; +import { prepareAndRun } from './helpers.mjs'; +const assignment = env['ASSIGNMENT']; + +const infoStr = assignment + ? '\nRunning tests for ' + assignment + '...' + : '\nRunning tests for all exercises...'; +const failureStr = '[Failure] Tests failed!'; + +// Copies the necessary files +env['PREPARE'] = true; + +// Cleans up after +env['CLEANUP'] = true; + +prepareAndRun('corepack pnpm jest --bail tmp_exercises', infoStr, failureStr);