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..12b4dbcd1a 100644 --- a/.github/workflows/ci.js.yml +++ b/.github/workflows/ci.js.yml @@ -13,12 +13,12 @@ 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 + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 with: node-version: 22.x cache: 'pnpm' @@ -37,12 +37,12 @@ jobs: node-version: [22.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..de107554de 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.3 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.3 # ℹ️ 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.3 diff --git a/.github/workflows/pr.ci.js.yml b/.github/workflows/pr.ci.js.yml index 39381f62fd..c15da4725f 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 }} @@ -28,7 +28,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' @@ -48,7 +48,7 @@ jobs: 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/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/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/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/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,