diff --git a/.github/workflows/action-format.yml b/.github/workflows/action-format.yml index df002f03e0..8026e38ebb 100644 --- a/.github/workflows/action-format.yml +++ b/.github/workflows/action-format.yml @@ -65,7 +65,7 @@ jobs: run: corepack enable pnpm - name: Use Node.js LTS (22.x) - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 with: node-version: 22.x cache: 'pnpm' diff --git a/.github/workflows/ci.js.yml b/.github/workflows/ci.js.yml index 0b572c1613..3fa3a49874 100644 --- a/.github/workflows/ci.js.yml +++ b/.github/workflows/ci.js.yml @@ -13,14 +13,14 @@ jobs: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - 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@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e + - name: Use Node.js LTS (24.x) + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 with: - node-version: 22.x + node-version: 24.x cache: 'pnpm' - name: Install project dependencies @@ -34,15 +34,15 @@ jobs: strategy: matrix: - node-version: [22.x] + node-version: [24.x, 26.x] steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - 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@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d5b22faf94..3741caf460 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -29,11 +29,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v4 + uses: github/codeql-action/init@v4.37.9 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@v4 + uses: github/codeql-action/autobuild@v4.37.9 # ℹ️ 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@v4 + uses: github/codeql-action/analyze@v4.37.9 diff --git a/.github/workflows/pr.ci.js.yml b/.github/workflows/pr.ci.js.yml index 39381f62fd..d9eb35b6a5 100644 --- a/.github/workflows/pr.ci.js.yml +++ b/.github/workflows/pr.ci.js.yml @@ -11,7 +11,7 @@ jobs: steps: - name: Checkout PR - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with: fetch-depth: ${{ github.event_name == 'pull_request' && 2 || 0 }} @@ -27,10 +27,10 @@ jobs: - 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@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e + - name: Use Node.js LTS (24.x) + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 with: - node-version: 22.x + node-version: 24.x cache: 'pnpm' - name: Install project dependencies @@ -44,11 +44,11 @@ jobs: strategy: matrix: - node-version: [22.x] + node-version: [24.x, 26.x] steps: - name: Checkout PR - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 with: fetch-depth: ${{ github.event_name == 'pull_request' && 2 || 0 }} @@ -65,7 +65,7 @@ jobs: run: corepack enable pnpm - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 with: node-version: ${{ matrix.node-version }} cache: 'pnpm' diff --git a/.github/workflows/verify-code-formatting.yml b/.github/workflows/verify-code-formatting.yml index 99c5b307e1..3e4471371b 100644 --- a/.github/workflows/verify-code-formatting.yml +++ b/.github/workflows/verify-code-formatting.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-24.04 steps: - name: 'Checkout code' - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 - name: 'Verify formatting of all files' run: ./bin/check-formatting.sh diff --git a/concepts/type-checking/about.md b/concepts/type-checking/about.md index d855cc9dda..0afed179a3 100644 --- a/concepts/type-checking/about.md +++ b/concepts/type-checking/about.md @@ -74,7 +74,7 @@ The `Array` class has a method called `Array.isArray()` that checks if its argum 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. +This is because the Array class has a different constructor in each realm, and each `iframe` has its own realm, 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. diff --git a/concepts/type-checking/introduction.md b/concepts/type-checking/introduction.md index d855cc9dda..0afed179a3 100644 --- a/concepts/type-checking/introduction.md +++ b/concepts/type-checking/introduction.md @@ -74,7 +74,7 @@ The `Array` class has a method called `Array.isArray()` that checks if its argum 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. +This is because the Array class has a different constructor in each realm, and each `iframe` has its own realm, 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. diff --git a/config.json b/config.json index 0ac62ffcd5..6fcb8932ad 100644 --- a/config.json +++ b/config.json @@ -2770,6 +2770,14 @@ "practices": [], "prerequisites": [], "difficulty": 2 + }, + { + "slug": "error-handling", + "name": "Error Handling", + "uuid": "de1c75f2-2461-4347-b5ca-b3cfaafe4d79", + "practices": [], + "prerequisites": [], + "difficulty": 4 } ] }, diff --git a/exercises/concept/recycling-robot/.docs/introduction.md b/exercises/concept/recycling-robot/.docs/introduction.md index 21f826324b..8628305814 100644 --- a/exercises/concept/recycling-robot/.docs/introduction.md +++ b/exercises/concept/recycling-robot/.docs/introduction.md @@ -74,7 +74,7 @@ The `Array` class has a method called `Array.isArray()` that checks if its argum 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. +This is because the Array class has a different constructor in each realm, and each `iframe` has its own realm, 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. diff --git a/exercises/practice/anagram/.meta/config.json b/exercises/practice/anagram/.meta/config.json index 42cb3ee2a3..132d0a755a 100644 --- a/exercises/practice/anagram/.meta/config.json +++ b/exercises/practice/anagram/.meta/config.json @@ -27,7 +27,7 @@ ".meta/proof.ci.js" ] }, - "blurb": "Given a word and a list of possible anagrams, select the correct sublist.", + "blurb": "Find the words that use the same letters as another word.", "source": "Inspired by the Extreme Startup game", "source_url": "https://github.com/rchatley/extreme_startup", "custom": { diff --git a/exercises/practice/bob/.meta/tests.toml b/exercises/practice/bob/.meta/tests.toml index 5299e2895f..c6a4649601 100644 --- a/exercises/practice/bob/.meta/tests.toml +++ b/exercises/practice/bob/.meta/tests.toml @@ -9,17 +9,20 @@ # 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" +[8a2e771d-d6f1-4e3f-b6c6-b41495556e37] +description = "asking a question" [73a966dc-8017-47d6-bb32-cf07d1a5fcd9] description = "shouting" -[d6c98afd-df35-4806-b55e-2c457c3ab748] -description = "shouting gibberish" +[a5193c61-4a92-4f68-93e2-f554eb385ec6] +description = "forceful question" -[8a2e771d-d6f1-4e3f-b6c6-b41495556e37] -description = "asking a question" +[bc39f7c6-f543-41be-9a43-fd1c2f753fc0] +description = "silence" + +[e162fead-606f-437a-a166-d051915cea8e] +description = "stating something" [81080c62-4e4d-4066-b30a-48d8d76920d9] description = "asking a numeric question" @@ -27,23 +30,34 @@ description = "asking a numeric question" [2a02716d-685b-4e2e-a804-2adaf281c01e] description = "asking gibberish" -[c02f9179-ab16-4aa7-a8dc-940145c385f7] -description = "talking forcefully" +[bb0011c5-cd52-4a5b-8bfb-a87b6283b0e2] +description = "question with no letters" -[153c0e25-9bb5-4ec5-966e-598463658bcd] -description = "using acronyms in regular speech" +[9bfc677d-ea3a-45f2-be44-35bc8fa3753e] +description = "non-letters with question" -[a5193c61-4a92-4f68-93e2-f554eb385ec6] -description = "forceful question" +[8608c508-f7de-4b17-985b-811878b3cf45] +description = "prattling on" -[a20e0c54-2224-4dde-8b10-bd2cdd4f61bc] -description = "shouting numbers" +[05b304d6-f83b-46e7-81e0-4cd3ca647900] +description = "ending with whitespace" -[f7bc4b92-bdff-421e-a238-ae97f230ccac] -description = "no letters" +[66953780-165b-4e7e-8ce3-4bcb80b6385a] +description = "multiple line question" +include = false -[bb0011c5-cd52-4a5b-8bfb-a87b6283b0e2] -description = "question with no letters" +[2c7278ac-f955-4eb4-bf8f-e33eb4116a15] +description = "multiple line question" +reimplements = "66953780-165b-4e7e-8ce3-4bcb80b6385a" + +[d6c98afd-df35-4806-b55e-2c457c3ab748] +description = "shouting gibberish" + +[3c954328-86fb-4c71-8961-e18d6a5e2517] +description = "shouting a statement containing a question mark" + +[a20e0c54-2224-4dde-8b10-bd2cdd4f61bc] +description = "shouting numbers" [496143c8-1c31-4c01-8a08-88427af85c66] description = "shouting with special characters" @@ -51,40 +65,29 @@ description = "shouting with special characters" [e6793c1c-43bd-4b8d-bc11-499aea73925f] description = "shouting with no exclamation mark" -[aa8097cc-c548-4951-8856-14a404dd236a] -description = "statement containing question mark" - -[9bfc677d-ea3a-45f2-be44-35bc8fa3753e] -description = "non-letters with question" - -[8608c508-f7de-4b17-985b-811878b3cf45] -description = "prattling on" - -[bc39f7c6-f543-41be-9a43-fd1c2f753fc0] -description = "silence" - [d6c47565-372b-4b09-b1dd-c40552b8378b] description = "prolonged silence" [4428f28d-4100-4d85-a902-e5a78cb0ecd3] description = "alternate silence" -[66953780-165b-4e7e-8ce3-4bcb80b6385a] -description = "multiple line question" -include = false +[72bd5ad3-9b2f-4931-a988-dce1f5771de2] +description = "other whitespace" -[5371ef75-d9ea-4103-bcfa-2da973ddec1b] -description = "starting with whitespace" +[c02f9179-ab16-4aa7-a8dc-940145c385f7] +description = "talking forcefully" -[05b304d6-f83b-46e7-81e0-4cd3ca647900] -description = "ending with whitespace" +[153c0e25-9bb5-4ec5-966e-598463658bcd] +description = "using acronyms in regular speech" -[72bd5ad3-9b2f-4931-a988-dce1f5771de2] -description = "other whitespace" +[f7bc4b92-bdff-421e-a238-ae97f230ccac] +description = "no letters" + +[aa8097cc-c548-4951-8856-14a404dd236a] +description = "statement containing question mark" + +[5371ef75-d9ea-4103-bcfa-2da973ddec1b] +description = "starting with 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/bob.spec.js b/exercises/practice/bob/bob.spec.js index f423c26044..af65227007 100644 --- a/exercises/practice/bob/bob.spec.js +++ b/exercises/practice/bob/bob.spec.js @@ -2,9 +2,9 @@ import { describe, expect, test, xtest } from '@jest/globals'; import { hey } from './bob'; describe('Bob', () => { - test('stating something', () => { - const result = hey('Tom-ay-to, tom-aaaah-to.'); - expect(result).toEqual('Whatever.'); + test('asking a question', () => { + const result = hey('Does this cryogenic chamber make me look fat?'); + expect(result).toEqual('Sure.'); }); xtest('shouting', () => { @@ -12,14 +12,19 @@ describe('Bob', () => { expect(result).toEqual('Whoa, chill out!'); }); - xtest('shouting gibberish', () => { - const result = hey('FCECDFCAAB'); - expect(result).toEqual('Whoa, chill out!'); + xtest('forceful question', () => { + const result = hey('WHAT THE HELL WERE YOU THINKING?'); + expect(result).toEqual("Calm down, I know what I'm doing!"); }); - xtest('asking a question', () => { - const result = hey('Does this cryogenic chamber make me look fat?'); - expect(result).toEqual('Sure.'); + xtest('silence', () => { + const result = hey(''); + expect(result).toEqual('Fine. Be that way!'); + }); + + xtest('stating something', () => { + const result = hey('Tom-ay-to, tom-aaaah-to.'); + expect(result).toEqual('Whatever.'); }); xtest('asking a numeric question', () => { @@ -32,64 +37,54 @@ describe('Bob', () => { expect(result).toEqual('Sure.'); }); - xtest('talking forcefully', () => { - const result = hey("Let's go make out behind the gym!"); - expect(result).toEqual('Whatever.'); - }); - - xtest('using acronyms in regular speech', () => { - const result = hey("It's OK if you don't want to go to the DMV."); - expect(result).toEqual('Whatever.'); + xtest('question with no letters', () => { + const result = hey('4?'); + expect(result).toEqual('Sure.'); }); - xtest('forceful question', () => { - const result = hey('WHAT THE HELL WERE YOU THINKING?'); - expect(result).toEqual("Calm down, I know what I'm doing!"); + xtest('non-letters with question', () => { + const result = hey(':) ?'); + expect(result).toEqual('Sure.'); }); - xtest('shouting numbers', () => { - const result = hey('1, 2, 3 GO!'); - expect(result).toEqual('Whoa, chill out!'); + xtest('prattling on', () => { + const result = hey('Wait! Hang on. Are you going to be OK?'); + expect(result).toEqual('Sure.'); }); - xtest('no letters', () => { - const result = hey('1, 2, 3'); - expect(result).toEqual('Whatever.'); + xtest('ending with whitespace', () => { + const result = hey('Okay if like my spacebar quite a bit? '); + expect(result).toEqual('Sure.'); }); - xtest('question with no letters', () => { - const result = hey('4?'); + xtest('multiple line question', () => { + const result = hey('\nDoes this cryogenic chamber make\n me look fat?'); expect(result).toEqual('Sure.'); }); - xtest('shouting with special characters', () => { - const result = hey('ZOMG THE %^*@#$(*^ ZOMBIES ARE COMING!!11!!1!'); + xtest('shouting gibberish', () => { + const result = hey('FCECDFCAAB'); expect(result).toEqual('Whoa, chill out!'); }); - xtest('shouting with no exclamation mark', () => { - const result = hey('I HATE YOU'); + xtest('shouting a statement containing a question mark', () => { + const result = hey('DO LIONS EAT PEOPLE? AHHHHH.'); expect(result).toEqual('Whoa, chill out!'); }); - xtest('statement containing question mark', () => { - const result = hey('Ending with a ? means a question.'); - expect(result).toEqual('Whatever.'); - }); - - xtest('non-letters with question', () => { - const result = hey(':) ?'); - expect(result).toEqual('Sure.'); + xtest('shouting numbers', () => { + const result = hey('1, 2, 3 GO!'); + expect(result).toEqual('Whoa, chill out!'); }); - xtest('prattling on', () => { - const result = hey('Wait! Hang on. Are you going to be OK?'); - expect(result).toEqual('Sure.'); + xtest('shouting with special characters', () => { + const result = hey('ZOMG THE %^*@#$(*^ ZOMBIES ARE COMING!!11!!1!'); + expect(result).toEqual('Whoa, chill out!'); }); - xtest('silence', () => { - const result = hey(''); - expect(result).toEqual('Fine. Be that way!'); + xtest('shouting with no exclamation mark', () => { + const result = hey('I HATE YOU'); + expect(result).toEqual('Whoa, chill out!'); }); xtest('prolonged silence', () => { @@ -102,24 +97,34 @@ describe('Bob', () => { expect(result).toEqual('Fine. Be that way!'); }); - xtest('multiple line question', () => { - const result = hey('\nDoes this cryogenic chamber make\n me look fat?'); - expect(result).toEqual('Sure.'); + xtest('other whitespace', () => { + const result = hey('\n\r \t'); + expect(result).toEqual('Fine. Be that way!'); }); - xtest('starting with whitespace', () => { - const result = hey(' hmmmmmmm...'); + xtest('talking forcefully', () => { + const result = hey("Let's go make out behind the gym!"); expect(result).toEqual('Whatever.'); }); - xtest('ending with whitespace', () => { - const result = hey('Okay if like my spacebar quite a bit? '); - expect(result).toEqual('Sure.'); + xtest('using acronyms in regular speech', () => { + const result = hey("It's OK if you don't want to go to the DMV."); + expect(result).toEqual('Whatever.'); }); - xtest('other whitespace', () => { - const result = hey('\n\r \t'); - expect(result).toEqual('Fine. Be that way!'); + xtest('no letters', () => { + const result = hey('1, 2, 3'); + expect(result).toEqual('Whatever.'); + }); + + xtest('statement containing question mark', () => { + const result = hey('Ending with a ? means a question.'); + expect(result).toEqual('Whatever.'); + }); + + xtest('starting with whitespace', () => { + const result = hey(' hmmmmmmm...'); + expect(result).toEqual('Whatever.'); }); xtest('non-question ending with whitespace', () => { diff --git a/exercises/practice/collatz-conjecture/.docs/instructions.append.md b/exercises/practice/collatz-conjecture/.docs/instructions.append.md index 5ab8ae4861..df87531ce4 100644 --- a/exercises/practice/collatz-conjecture/.docs/instructions.append.md +++ b/exercises/practice/collatz-conjecture/.docs/instructions.append.md @@ -7,5 +7,5 @@ If `n` is not a positive integer, stop the program from being executed further a In JavaScript, this can be done using the `throw` statement. ```javascript -throw new Error('Only positive numbers are allowed'); +throw new Error('Only positive integers are allowed'); ``` diff --git a/exercises/practice/connect/.meta/tests.toml b/exercises/practice/connect/.meta/tests.toml index 59ec615e39..951b87e5c4 100644 --- a/exercises/practice/connect/.meta/tests.toml +++ b/exercises/practice/connect/.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. [6eff0df4-3e92-478d-9b54-d3e8b354db56] description = "an empty board has no winner" @@ -23,6 +30,12 @@ description = "nobody wins crossing adjacent angles" [cd61c143-92f6-4a8d-84d9-cb2b359e226b] description = "X wins crossing from left to right" +[495e33ed-30a9-4012-b46e-d7c4d5fe13c3] +description = "X wins with left-hand dead end fork" + +[ab167ab0-4a98-4d0f-a1c0-e1cddddc3d58] +description = "X wins with right-hand dead end fork" + [73d1eda6-16ab-4460-9904-b5f5dd401d0b] description = "O wins crossing from top to bottom" diff --git a/exercises/practice/connect/connect.spec.js b/exercises/practice/connect/connect.spec.js index a5fcca9ac7..2147bf255a 100644 --- a/exercises/practice/connect/connect.spec.js +++ b/exercises/practice/connect/connect.spec.js @@ -61,6 +61,16 @@ describe('Judging a game of connect', () => { expect(new Board(board).winner()).toEqual('X'); }); + xtest('X wins with left-hand dead end fork', () => { + const board = ['. . X .', ' X X . .', ' . X X X', ' O O O O']; + expect(new Board(board).winner()).toEqual('X'); + }); + + xtest('X wins with right-hand dead end fork', () => { + const board = ['. . X X', ' X X . .', ' . X X .', ' O O O O']; + expect(new Board(board).winner()).toEqual('X'); + }); + xtest('O wins crossing from top to bottom', () => { const board = [ '. O . .', diff --git a/exercises/practice/error-handling/.docs/instructions.md b/exercises/practice/error-handling/.docs/instructions.md new file mode 100644 index 0000000000..25dd4d2928 --- /dev/null +++ b/exercises/practice/error-handling/.docs/instructions.md @@ -0,0 +1,8 @@ +# Instructions + +Implement various kinds of error handling and resource management. + +An important point of programming is how to handle errors and close resources even if errors occur. + +This exercise requires you to handle various errors. +Because error handling is rather programming language specific you'll have to refer to the tests for your track to see what's exactly required. diff --git a/exercises/practice/error-handling/.docs/introduction.md b/exercises/practice/error-handling/.docs/introduction.md new file mode 100644 index 0000000000..eb6b220e45 --- /dev/null +++ b/exercises/practice/error-handling/.docs/introduction.md @@ -0,0 +1,26 @@ +# Error Handling + +In this exercise, you will implement a function called `processString` that processes a given input string with proper error handling. + +You will learn how to: + +- Check input types and throw errors for invalid inputs. +- Throw errors for specific cases (e.g., empty strings). +- Return the uppercase version of the string if it is valid. +- Handle and catch errors using a try..catch block + + +Implement the processString function using a `try…catch` block. + +Inside the `try` block: +- If the input is not a string, throw a `TypeError`. +- If the input is an empty string, return `null`. +- If input length is greater than 100, or less than 10, throw a `RangeError` +- If input contains a mix of letters and numbers, throw a `SyntaxError`. +- Otherwise, return the input in `uppercase`. + +*Don't forget to attach appropriate error messages then you throw! An informative and well structured error message can save you hours of debugging.* + +Inside the `catch` block: +- log the error's message using `console.log` +- `throw` the `error` so it can be tested for its type. diff --git a/exercises/practice/error-handling/.gitignore b/exercises/practice/error-handling/.gitignore new file mode 100644 index 0000000000..0c88ff6ec3 --- /dev/null +++ b/exercises/practice/error-handling/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin/configlet +/bin/configlet.exe +/package-lock.json +/yarn.lock diff --git a/exercises/practice/error-handling/.meta/config.json b/exercises/practice/error-handling/.meta/config.json new file mode 100644 index 0000000000..d657caee5e --- /dev/null +++ b/exercises/practice/error-handling/.meta/config.json @@ -0,0 +1,17 @@ +{ + "authors": [ + "A-O-Emmanuel" + ], + "files": { + "solution": [ + "error-handling.js" + ], + "test": [ + "error-handling.spec.js" + ], + "example": [ + ".meta/proof.ci.js" + ] + }, + "blurb": "Implement various kinds of error handling and resource management." +} diff --git a/exercises/practice/error-handling/.meta/proof.ci.js b/exercises/practice/error-handling/.meta/proof.ci.js new file mode 100644 index 0000000000..f020a402af --- /dev/null +++ b/exercises/practice/error-handling/.meta/proof.ci.js @@ -0,0 +1,26 @@ +export const processString = (input) => { + try { + if (typeof input !== 'string') { + throw new TypeError('Input must be a string'); + } + if (input === '') { + return null; + } + if (input.length > 100) { + throw new RangeError('Input is too long'); + } + if (input.length < 10) { + throw new RangeError('Input is too short'); + } + if (/[a-zA-Z]/.test(input) && /\d/.test(input)) { + throw new SyntaxError( + 'Input cannot contain a mix of letters and numbers', + ); + } + + return input.toUpperCase(); + } catch (error) { + console.log(error.message); + throw error; + } +}; diff --git a/exercises/practice/error-handling/.npmrc b/exercises/practice/error-handling/.npmrc new file mode 100644 index 0000000000..d26df800bb --- /dev/null +++ b/exercises/practice/error-handling/.npmrc @@ -0,0 +1 @@ +audit=false diff --git a/exercises/practice/error-handling/LICENSE b/exercises/practice/error-handling/LICENSE new file mode 100644 index 0000000000..90e73be03b --- /dev/null +++ b/exercises/practice/error-handling/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/error-handling/babel.config.js b/exercises/practice/error-handling/babel.config.js new file mode 100644 index 0000000000..a638497df1 --- /dev/null +++ b/exercises/practice/error-handling/babel.config.js @@ -0,0 +1,4 @@ +module.exports = { + presets: [['@exercism/babel-preset-javascript', { corejs: '3.40' }]], + plugins: [], +}; diff --git a/exercises/practice/error-handling/error-handling.js b/exercises/practice/error-handling/error-handling.js new file mode 100644 index 0000000000..42665c2b23 --- /dev/null +++ b/exercises/practice/error-handling/error-handling.js @@ -0,0 +1,8 @@ +// +// This is only a SKELETON file for the 'Error handling' exercise. It's been provided as a +// convenience to get you started writing code faster. +// + +export const processString = (input) => { + throw new Error('Remove this line and implement the function'); +}; diff --git a/exercises/practice/error-handling/error-handling.spec.js b/exercises/practice/error-handling/error-handling.spec.js new file mode 100644 index 0000000000..c83c69e87d --- /dev/null +++ b/exercises/practice/error-handling/error-handling.spec.js @@ -0,0 +1,72 @@ +import { describe, expect, test, xtest } from '@jest/globals'; +import { processString } from './error-handling'; + +describe('Error Handling', () => { + test('never throws a generic Error for any invalid input', () => { + const invalidInputs = [ + 42, // TypeError + 'short', // RangeError (too short) + 'a'.repeat(101), // RangeError (too long) + '12345test6789text', // SyntaxError (mixed) + ]; + + for (const input of invalidInputs) { + let error; + + try { + processString(input); + } catch (err) { + error = err; + } + + expect(error).toBeInstanceOf(Error); + expect(error.constructor).not.toBe(Error); + expect(error.message).toEqual(expect.stringMatching(/.+/)); + } + }); + + xtest('throws TypeError if input is not a string', () => { + expect(() => processString(42)).toThrow( + expect.objectContaining({ + name: 'TypeError', + message: expect.stringMatching(/.+/), + }), + ); + }); + + xtest('throws error if input is too short', () => { + expect(() => processString('short')).toThrow( + expect.objectContaining({ + name: 'RangeError', + message: expect.stringMatching(/.+/), + }), + ); + }); + + xtest('throws error if input is too long', () => { + const longString = 'a'.repeat(101); + expect(() => processString(longString)).toThrow( + expect.objectContaining({ + name: 'RangeError', + message: expect.stringMatching(/.+/), + }), + ); + }); + + xtest('throws error if input contains a mix of letters and numbers', () => { + expect(() => processString('12345test6789text')).toThrow( + expect.objectContaining({ + name: 'SyntaxError', + message: expect.stringMatching(/.+/), + }), + ); + }); + + xtest('returns null if string is empty', () => { + expect(processString('')).toBeNull(); + }); + + xtest('returns uppercase string if input is valid', () => { + expect(processString('hellotherefriend')).toBe('HELLOTHEREFRIEND'); + }); +}); diff --git a/exercises/practice/error-handling/eslint.config.mjs b/exercises/practice/error-handling/eslint.config.mjs new file mode 100644 index 0000000000..ca517111ed --- /dev/null +++ b/exercises/practice/error-handling/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/error-handling/jest.config.js b/exercises/practice/error-handling/jest.config.js new file mode 100644 index 0000000000..ec8e908127 --- /dev/null +++ b/exercises/practice/error-handling/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/error-handling/package.json b/exercises/practice/error-handling/package.json new file mode 100644 index 0000000000..c2631ea755 --- /dev/null +++ b/exercises/practice/error-handling/package.json @@ -0,0 +1,38 @@ +{ + "name": "@exercism/javascript-practice-error-handling", + "description": "Exercism practice exercise on error-handling", + "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/practice/error-handling" + }, + "devDependencies": { + "@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" + }, + "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/grep/.docs/introduction.md b/exercises/practice/grep/.docs/introduction.md new file mode 100644 index 0000000000..4041290465 --- /dev/null +++ b/exercises/practice/grep/.docs/introduction.md @@ -0,0 +1,5 @@ +# Introduction + +You have taken a job at a local library helping organize their collection of old books. +The student patrons are often hunting for half-remembered quotes to cite in their term papers. +Rather than manually read every book from cover to cover, you decide to build a small tool to scan them, looking for these partial quotes. diff --git a/exercises/practice/grep/.meta/config.json b/exercises/practice/grep/.meta/config.json index 4e6e96b3bf..01667c85ab 100644 --- a/exercises/practice/grep/.meta/config.json +++ b/exercises/practice/grep/.meta/config.json @@ -17,7 +17,7 @@ ".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.", + "blurb": "Search a file for lines matching a regular expression pattern.", "source": "Conversation with Nate Foster.", "source_url": "https://www.cs.cornell.edu/courses/cs3110/2014sp/hw/0/ps0.pdf", "custom": { diff --git a/exercises/practice/isogram/.meta/config.json b/exercises/practice/isogram/.meta/config.json index 486f11ad0b..4294d83e25 100644 --- a/exercises/practice/isogram/.meta/config.json +++ b/exercises/practice/isogram/.meta/config.json @@ -22,7 +22,7 @@ ".meta/proof.ci.js" ] }, - "blurb": "Determine if a word or phrase is an isogram.", + "blurb": "Determine whether a phrase is an isogram, a word with no repeated letters.", "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Isogram", "custom": { diff --git a/exercises/practice/line-up/.meta/tests.toml b/exercises/practice/line-up/.meta/tests.toml index 36fdf1d0cd..dd66fc1585 100644 --- a/exercises/practice/line-up/.meta/tests.toml +++ b/exercises/practice/line-up/.meta/tests.toml @@ -51,9 +51,24 @@ description = "format non-exceptional ordinal numeral 13" [2bdcebc5-c029-4874-b6cc-e9bec80d603a] description = "format exceptional ordinal numeral 21" +[a98e2e22-ab41-4557-a7c2-efedc19c16da] +description = "format exceptional ordinal numeral 22 ending in nd even though it is a multiple of 11" + +[ab45d2fb-e0ee-4016-b605-76917584db0a] +description = "format exceptional ordinal numeral 33 ending in rd even though it is a multiple of 11" + +[c9243603-9f17-45b3-9a41-db9ebdbf08e1] +description = "format exceptional ordinal numeral 52 ending in nd even though it is a multiple of 13" + [74ee2317-0295-49d2-baf0-d56bcefa14e3] description = "format exceptional ordinal numeral 62" +[3f6c408c-4331-42b6-bb6c-3ad0823e568a] +description = "format non-exceptional ordinal numeral 72 ending in nd even though it is a multiple of 12" + +[8db52cd9-9689-413f-a812-6c36fcfd0d07] +description = "format exceptional ordinal numeral 91 ending in st even though it is a multiple of 13" + [b37c332d-7f68-40e3-8503-e43cbd67a0c4] description = "format exceptional ordinal numeral 100" @@ -65,3 +80,6 @@ description = "format non-exceptional ordinal numeral 112" [06b62efe-199e-4ce7-970d-4bf73945713f] description = "format exceptional ordinal numeral 123" + +[6792c54e-59a7-4faf-839a-c4bb61014229] +description = "format large number 972 ending in nd even though it is a multiple of 12" diff --git a/exercises/practice/line-up/line-up.spec.js b/exercises/practice/line-up/line-up.spec.js index 62877284de..467c94b843 100644 --- a/exercises/practice/line-up/line-up.spec.js +++ b/exercises/practice/line-up/line-up.spec.js @@ -86,12 +86,42 @@ describe('Line Up', () => { ); }); + xtest('format exceptional ordinal numeral 22 ending in nd even though it is a multiple of 11', () => { + expect(format('Ingrid', 22)).toBe( + 'Ingrid, you are the 22nd customer we serve today. Thank you!', + ); + }); + + xtest('format exceptional ordinal numeral 33 ending in rd even though it is a multiple of 11', () => { + expect(format('Mario', 33)).toBe( + 'Mario, you are the 33rd customer we serve today. Thank you!', + ); + }); + + xtest('format exceptional ordinal numeral 52 ending in nd even though it is a multiple of 13', () => { + expect(format('Quentin', 52)).toBe( + 'Quentin, you are the 52nd customer we serve today. Thank you!', + ); + }); + xtest('format exceptional ordinal numeral 62', () => { expect(format('Nayra', 62)).toBe( 'Nayra, you are the 62nd customer we serve today. Thank you!', ); }); + xtest('format non-exceptional ordinal numeral 72 ending in nd even though it is a multiple of 12', () => { + expect(format('Ugo', 72)).toBe( + 'Ugo, you are the 72nd customer we serve today. Thank you!', + ); + }); + + xtest('format exceptional ordinal numeral 91 ending in st even though it is a multiple of 13', () => { + expect(format('Boris', 91)).toBe( + 'Boris, you are the 91st customer we serve today. Thank you!', + ); + }); + xtest('format exceptional ordinal numeral 100', () => { expect(format('John', 100)).toBe( 'John, you are the 100th customer we serve today. Thank you!', @@ -115,4 +145,10 @@ describe('Line Up', () => { 'Yma, you are the 123rd customer we serve today. Thank you!', ); }); + + xtest('format large number 972 ending in nd even though it is a multiple of 12', () => { + expect(format('Elias', 972)).toBe( + 'Elias, you are the 972nd customer we serve today. Thank you!', + ); + }); }); diff --git a/exercises/practice/nucleotide-count/.meta/config.json b/exercises/practice/nucleotide-count/.meta/config.json index d2f001af9d..35f321a378 100644 --- a/exercises/practice/nucleotide-count/.meta/config.json +++ b/exercises/practice/nucleotide-count/.meta/config.json @@ -20,7 +20,7 @@ ] }, "blurb": "Given a DNA string, compute how many times each nucleotide occurs in the string.", - "source": "The Calculating DNA Nucleotides_problem at Rosalind", + "source": "The Counting DNA Nucleotides problem at Rosalind", "source_url": "https://rosalind.info/problems/dna/", "custom": { "version.tests.compatibility": "jest-27", diff --git a/exercises/practice/pangram/.meta/config.json b/exercises/practice/pangram/.meta/config.json index 13c4d2e850..cd92723bb7 100644 --- a/exercises/practice/pangram/.meta/config.json +++ b/exercises/practice/pangram/.meta/config.json @@ -24,7 +24,7 @@ ".meta/proof.ci.js" ] }, - "blurb": "Determine if a sentence is a pangram.", + "blurb": "Determine whether a phrase uses every letter in the Latin alphabet.", "source": "Wikipedia", "source_url": "https://en.wikipedia.org/wiki/Pangram", "custom": { diff --git a/exercises/practice/perfect-numbers/.meta/config.json b/exercises/practice/perfect-numbers/.meta/config.json index 834b86858e..275e5ba8ae 100644 --- a/exercises/practice/perfect-numbers/.meta/config.json +++ b/exercises/practice/perfect-numbers/.meta/config.json @@ -23,7 +23,7 @@ }, "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": "https://www.oreilly.com/library/view/functional-thinking/9781449365509/", + "source_url": "https://nealford.com/books/functionalthinking.html", "custom": { "version.tests.compatibility": "jest-27", "flag.tests.task-per-describe": false, 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 index ac6750df6d..9d774d25ae 100644 --- a/exercises/practice/state-of-tic-tac-toe/.meta/proof.ci.js +++ b/exercises/practice/state-of-tic-tac-toe/.meta/proof.ci.js @@ -27,7 +27,8 @@ export const gamestate = (board) => { 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): + case (scorringArray.includes(gridSize) && numberOfX === numberOfO) || + (scorringArray.includes(-gridSize) && numberOfX > numberOfO): throw new Error( 'Impossible board: game should have ended after the game was won', ); diff --git a/exercises/practice/state-of-tic-tac-toe/.meta/tests.toml b/exercises/practice/state-of-tic-tac-toe/.meta/tests.toml index 8fc25e2118..5f574b2a12 100644 --- a/exercises/practice/state-of-tic-tac-toe/.meta/tests.toml +++ b/exercises/practice/state-of-tic-tac-toe/.meta/tests.toml @@ -99,3 +99,9 @@ reimplements = "b1dc8b13-46c4-47db-a96d-aa90eedc4e8d" [4801cda2-f5b7-4c36-8317-3cdd167ac22c] description = "Invalid boards -> Invalid board: players kept playing after a win" + +[5a84757a-fc86-4328-aec9-a5759e6ed35d] +description = "Invalid boards -> Invalid board: O kept playing after X wins" + +[cf25543d-583a-4656-b9ab-f82dc00a4a02] +description = "Invalid boards -> Invalid board: X kept playing after O wins" 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 index d6cdda3750..4810a83954 100644 --- 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 @@ -200,4 +200,22 @@ describe('Invalid boards', () => { const actual = () => gamestate(board); expect(actual).toThrow(expected); }); + + xtest('Invalid board: O kept playing after X wins', () => { + const board = ['OO ', 'XXX', ' O ']; + 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: X kept playing after O wins', () => { + const board = ['XX ', 'OOO', ' XX']; + 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/sublist/.meta/config.json b/exercises/practice/sublist/.meta/config.json index 502e8e0c84..3cc4445b5f 100644 --- a/exercises/practice/sublist/.meta/config.json +++ b/exercises/practice/sublist/.meta/config.json @@ -22,7 +22,7 @@ ".meta/proof.ci.js" ] }, - "blurb": "Write a function to determine if a list is a sublist of another list.", + "blurb": "Determine if a list is a sublist of another list.", "custom": { "version.tests.compatibility": "jest-27", "flag.tests.task-per-describe": false,