diff --git a/.eslintrc b/.eslintrc index ba7900080..a79793605 100644 --- a/.eslintrc +++ b/.eslintrc @@ -6,14 +6,15 @@ "ecmaFeatures": { "jsx": true }, - "ecmaVersion": 2018, + "ecmaVersion": 2020, "sourceType": "module" }, "env": { - "browser": true, + "browser": false, "es6": true, "jest": true, - "node": true + "node": true, + "shelljs": true }, "extends": [ "eslint:recommended", @@ -29,7 +30,7 @@ }, "plugins": ["@typescript-eslint"], "rules": { - "@typescript-eslint/array-type": ["error", { "default": "array-simple" }], + "@typescript-eslint/array-type": "off", "@typescript-eslint/explicit-function-return-type": [ "warn", { diff --git a/exercises/practice/crypto-square/.meta/proof.ci.ts b/exercises/practice/crypto-square/.meta/proof.ci.ts index 602f84ebd..26d5e0fa4 100644 --- a/exercises/practice/crypto-square/.meta/proof.ci.ts +++ b/exercises/practice/crypto-square/.meta/proof.ci.ts @@ -1,11 +1,11 @@ export class Crypto { constructor(private readonly input: string) {} - get plaintext() { + private get plaintext(): string { return this.input.toLowerCase().replace(/[^a-zA-Z0-9]/g, '') } - get ciphertext() { + public get ciphertext(): string { const chunkSize = this.size if (chunkSize === 0) { return '' @@ -19,18 +19,18 @@ export class Crypto { .join(' ') } - get size(): number { + public get size(): number { const realLength = Math.sqrt(this.plaintext.length) return Math.ceil(realLength) } - ciphertextSegments() { + private ciphertextSegments(): string[] { const textSegments = this.plaintextSegments() - const columns = [] - let i - let j - let currentSegment - let currentLetter + const columns: string[][] = [] + let i: number + let j: number + let currentSegment: RegExpMatchArray[number] + let currentLetter: RegExpMatchArray[number][number] for (i = 0; i < this.size; i += 1) { columns.push([]) @@ -45,14 +45,15 @@ export class Crypto { } } + const result: string[] = [] for (i = 0; i < columns.length; i += 1) { - columns[i] = columns[i].join('') + result[i] = columns[i].join('') } - return columns + return result } - plaintextSegments() { + private plaintextSegments(): RegExpMatchArray { const plainText = this.plaintext const chunkSize = this.size diff --git a/exercises/practice/diffie-hellman/.meta/proof.ci.ts b/exercises/practice/diffie-hellman/.meta/proof.ci.ts index 81800c974..c0f31dd56 100644 --- a/exercises/practice/diffie-hellman/.meta/proof.ci.ts +++ b/exercises/practice/diffie-hellman/.meta/proof.ci.ts @@ -76,7 +76,7 @@ const PRIMES = [ 7591, 7603, 7607, 7621, 7639, 7643, 7649, 7669, 7673, 7681, 7687, 7691, 7699, 7703, 7717, 7723, 7727, 7741, 7753, 7757, 7759, 7789, 7793, 7817, 7823, 7829, 7841, 7853, 7867, 7873, 7877, 7879, 7883, 7901, 7907, 7919, -]; +] export class DiffieHellman { constructor(private readonly p: number, private readonly g: number) { diff --git a/exercises/practice/grade-school/.meta/proof.ci.ts b/exercises/practice/grade-school/.meta/proof.ci.ts index 453ab7332..223095fdd 100644 --- a/exercises/practice/grade-school/.meta/proof.ci.ts +++ b/exercises/practice/grade-school/.meta/proof.ci.ts @@ -3,24 +3,24 @@ type Grade = number type StudentRooster = Record type StudentGrades = Map export class GradeSchool { - students: StudentGrades + private students: StudentGrades constructor() { this.students = new Map() } - add(student: Student, level: Grade) { + public add(student: Student, level: Grade): void { this.students.set(student, level) } - grade(level: Grade) { + public grade(level: Grade): Student[] { return Array.from(this.students.entries()) .filter(([, studentGrade]) => studentGrade === level) .map(([student]) => student) .sort() } - roster(): StudentRooster { + public roster(): StudentRooster { const result: StudentRooster = {} Array.from(this.students.entries()).forEach(([, studentGrade]) => { diff --git a/exercises/practice/largest-series-product/.meta/proof.ci.ts b/exercises/practice/largest-series-product/.meta/proof.ci.ts index 06f2d65d9..b537f9192 100644 --- a/exercises/practice/largest-series-product/.meta/proof.ci.ts +++ b/exercises/practice/largest-series-product/.meta/proof.ci.ts @@ -1,4 +1,4 @@ -export const largestProduct = (digits: string, seriesLength: number) => { +export function largestProduct(digits: string, seriesLength: number): number { if (seriesLength === 0) { return 1 } diff --git a/exercises/practice/list-ops/.docs/instructions.append.md b/exercises/practice/list-ops/.docs/instructions.append.md index 95e3839f6..67f6f3d13 100644 --- a/exercises/practice/list-ops/.docs/instructions.append.md +++ b/exercises/practice/list-ops/.docs/instructions.append.md @@ -1,3 +1,13 @@ # Instructions append Using core language features to build and deconstruct arrays via destructuring, and using the array literal `[]` are allowed, but no functions from the `Array.prototype` should be used. + +In order to be able to test your solution, ensure `forEach` is implemented. + +```typescript +const list = List.create(1, 2) +list.forEach((item) => console.log(item)) +// => +// 1 +// 2 +``` diff --git a/exercises/practice/list-ops/.meta/proof.ci.ts b/exercises/practice/list-ops/.meta/proof.ci.ts index 40b164fd5..70cc33479 100644 --- a/exercises/practice/list-ops/.meta/proof.ci.ts +++ b/exercises/practice/list-ops/.meta/proof.ci.ts @@ -5,15 +5,13 @@ const Null: Cons = { get next() { return this }, - get values() { - return [] - }, get() { return this.value }, push(item): Cons { + // eslint-disable-next-line @typescript-eslint/no-use-before-define return new Cons(item, this) }, length() { @@ -25,16 +23,22 @@ const Null: Cons = { concat(): Cons { return this }, - forEach() { + forEach(): void { /* done */ }, - foldl(_, initial): any { - return initial + foldl( + _: (initial: TReturn, value: TValue) => TReturn, + initial?: TReturn + ): TReturn { + return initial as TReturn }, - foldr(_, initial) { - return initial + foldr( + _: (initial: TReturn, value: TValue) => TReturn, + initial?: TReturn + ): TReturn { + return initial as TReturn }, - filter() { + filter(): Cons { return Null }, reverse(): Cons { @@ -44,79 +48,101 @@ const Null: Cons = { return this }, } - class Cons { - static fromArray([head, ...tail]: any[]) { - if (head === undefined) { - return Null - } - - return new Cons(head, Cons.fromArray(tail || [])) - } - - constructor(public readonly value: any, public next: Cons = Null) {} - - get values() { - return [this.value, ...this.next.values] - } + constructor(public readonly value: unknown, public next: Cons = Null) {} - get(i: number) { + public get(i: number): unknown { return i === 0 ? this.value : this.next.get(i - 1) } - push(item: any): this { + public push(item: unknown): this { this.next = this.next.push(item) return this } - length(): number { + public length(): number { return 1 + this.next.length() } - append(other: Cons): Cons { + public append(other: Cons): Cons { return other.foldl((result, item) => result.push(item), this) } - concat(others: Cons): Cons { - return others.foldl((result, other) => result.append(other), this) + public concat(others: Cons): Cons { + return others.foldl( + (result, other) => result.append(other), + this + ) } - foldl( - callback: (initial: any, value: any) => any, - initial: any = undefined - ): any { - return this.next.foldl(callback, callback(initial, this.value)) + public foldl( + callback: (initial: TValue, value: TValue) => TValue + ): TValue + public foldl( + callback: (initial: TReturn, value: TValue) => TReturn, + initial: TReturn + ): TReturn + + public foldl( + callback: (initial: TReturn | undefined, value: TValue) => TReturn, + initial?: TReturn + ): TReturn { + return this.next.foldl( + callback, + callback(initial, this.value as TValue) + ) } - forEach(callback: (value: any) => void): void { + public forEach(callback: (value: unknown) => void): void { this.foldl((_, item) => callback(item)) } - foldr( - callback: (initial: any, value: any) => any, - initial: any = undefined - ): any { - return callback(this.next.foldr(callback, initial), this.value) + public foldr( + callback: (initial: TValue, value: TValue) => TValue + ): TValue + public foldr( + callback: (initial: TReturn, value: TValue) => TReturn, + initial: TReturn + ): TReturn + + public foldr( + callback: (initial: TReturn, value: TValue) => TReturn, + initial?: TReturn + ): TReturn { + return callback( + this.next.foldr(callback, initial as TReturn), + this.value as TValue + ) } - filter(predicate: (value: any) => boolean): Cons { - return this.foldl( + public filter(predicate: (value: TValue) => boolean): Cons { + return this.foldl( (result, item) => (predicate(item) && result.push(item)) || result, Null ) } - map(expression: (value: any) => any): Cons { - return this.foldl((result, item) => result.push(expression(item)), Null) + public map( + expression: (value: TValue) => TReturn + ): Cons { + return this.foldl( + (result, item) => result.push(expression(item)), + Null + ) } - reverse(): Cons { + public reverse(): Cons { return this.next.reverse().push(this.value) } } - export class List { - constructor(values = []) { - return Cons.fromArray(values) + public static create(...values: unknown[]): Cons { + const [head, ...tail] = values + + if (head === undefined) { + return Null + } + + return new Cons(head, List.create(...tail)) } } diff --git a/exercises/practice/list-ops/list-ops.test.ts b/exercises/practice/list-ops/list-ops.test.ts index 63ac3ac66..8f64b8ac4 100644 --- a/exercises/practice/list-ops/list-ops.test.ts +++ b/exercises/practice/list-ops/list-ops.test.ts @@ -1,125 +1,162 @@ import { List } from './list-ops' +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace jest { + interface Matchers { + toHaveValues(...expected: unknown[]): CustomMatcherResult + } + } +} + +expect.extend({ + toHaveValues( + received: ReturnType, + ...expected: unknown[] + ): jest.CustomMatcherResult { + if (!('forEach' in received)) { + return { + pass: false, + message: (): string => `Implement .forEach(callback) on your list`, + } + } + + const values: unknown[] = [] + received.forEach((item) => values.push(item)) + + const pass = JSON.stringify(values) === JSON.stringify(expected) + return { + pass, + message: (): string => + pass + ? '' + : `Expected to see the following values: ${JSON.stringify( + expected + )}, actual: ${JSON.stringify(values)}`, + } + }, +}) + describe('append entries to a list and return the new list', () => { test('empty lists', () => { - const list1 = new List() - const list2 = new List() - expect(list1.append(list2)).toEqual(new List()) + const list1 = List.create() + const list2 = List.create() + expect(list1.append(list2)).toEqual(List.create()) }) xtest('empty list to list', () => { - const list1 = new List([1, 2, 3, 4]) - const list2 = new List() + const list1 = List.create(1, 2, 3, 4) + const list2 = List.create() expect(list1.append(list2)).toEqual(list1) }) xtest('non-empty lists', () => { - const list1 = new List([1, 2]) - const list2 = new List([2, 3, 4, 5]) - expect(list1.append(list2).values).toEqual([1, 2, 2, 3, 4, 5]) + const list1 = List.create(1, 2) + const list2 = List.create(2, 3, 4, 5) + expect(list1.append(list2)).toHaveValues(1, 2, 2, 3, 4, 5) }) }) describe('concat lists and lists of lists into new list', () => { xtest('empty list', () => { - const list1 = new List() - const list2 = new List() - expect(list1.concat(list2).values).toEqual([]) + const list1 = List.create() + const list2 = List.create() + expect(list1.concat(list2)).toHaveValues() }) xtest('list of lists', () => { - const list1 = new List([1, 2]) - const list2 = new List([3]) - const list3 = new List([]) - const list4 = new List([4, 5, 6]) - const listOfLists = new List([list2, list3, list4]) - expect(list1.concat(listOfLists).values).toEqual([1, 2, 3, 4, 5, 6]) + const list1 = List.create(1, 2) + const list2 = List.create(3) + const list3 = List.create() + const list4 = List.create(4, 5, 6) + const listOfLists = List.create(list2, list3, list4) + expect(list1.concat(listOfLists)).toHaveValues(1, 2, 3, 4, 5, 6) }) }) describe('filter list returning only values that satisfy the filter function', () => { xtest('empty list', () => { - const list1 = new List([]) - expect(list1.filter((el) => el % 2 === 1).values).toEqual([]) + const list1 = List.create() + expect(list1.filter((el) => el % 2 === 1)).toHaveValues() }) xtest('non empty list', () => { - const list1 = new List([1, 2, 3, 5]) - expect(list1.filter((el) => el % 2 === 1).values).toEqual([1, 3, 5]) + const list1 = List.create(1, 2, 3, 5) + expect(list1.filter((el) => el % 2 === 1)).toHaveValues(1, 3, 5) }) }) describe('returns the length of a list', () => { xtest('empty list', () => { - const list1 = new List() + const list1 = List.create() expect(list1.length()).toEqual(0) }) xtest('non-empty list', () => { - const list1 = new List([1, 2, 3, 4]) + const list1 = List.create(1, 2, 3, 4) expect(list1.length()).toEqual(4) }) }) describe('returns a list of elements whose values equal the list value transformed by the mapping function', () => { xtest('empty list', () => { - const list1 = new List() - expect(list1.map((el) => ++el).values).toEqual([]) + const list1 = List.create() + expect(list1.map((el) => ++el)).toHaveValues() }) xtest('non-empty list', () => { - const list1 = new List([1, 3, 5, 7]) - expect(list1.map((el) => ++el).values).toEqual([2, 4, 6, 8]) + const list1 = List.create(1, 3, 5, 7) + expect(list1.map((el) => ++el)).toHaveValues(2, 4, 6, 8) }) }) describe('folds (reduces) the given list from the left with a function', () => { xtest('empty list', () => { - const list1 = new List() - expect(list1.foldl((acc, el) => el * acc, 2)).toEqual(2) + const list1 = List.create() + expect(list1.foldl((acc, el) => el * acc, 2)).toEqual(2) }) xtest('direction independent function applied to non-empty list', () => { - const list1 = new List([1, 2, 3, 4]) - expect(list1.foldl((acc, el) => acc + el, 5)).toEqual(15) + const list1 = List.create(1, 2, 3, 4) + expect(list1.foldl((acc, el) => acc + el, 5)).toEqual(15) }) xtest('direction dependent function applied to non-empty list', () => { - const list1 = new List([1, 2, 3, 4]) - expect(list1.foldl((acc, el) => el / acc, 24)).toEqual(64) + const list1 = List.create(1, 2, 3, 4) + expect(list1.foldl((acc, el) => el / acc, 24)).toEqual(64) }) }) describe('folds (reduces) the given list from the right with a function', () => { xtest('empty list', () => { - const list1 = new List() - expect(list1.foldr((acc, el) => el * acc, 2)).toEqual(2) + const list1 = List.create() + expect(list1.foldr((acc, el) => el * acc, 2)).toEqual(2) }) xtest('direction independent function applied to non-empty list', () => { - const list1 = new List([1, 2, 3, 4]) - expect(list1.foldr((acc, el) => acc + el, 5)).toEqual(15) + const list1 = List.create(1, 2, 3, 4) + expect(list1.foldr((acc, el) => acc + el, 5)).toEqual(15) }) xtest('direction dependent function applied to non-empty list', () => { - const list1 = new List([1, 2, 3, 4]) - expect(list1.foldr((acc, el) => el / acc, 24)).toEqual(9) + const list1 = List.create(1, 2, 3, 4) + expect(list1.foldr((acc, el) => el / acc, 24)).toEqual(9) }) }) describe('reverse the elements of a list', () => { xtest('empty list', () => { - const list1 = new List() - expect(list1.reverse().values).toEqual([]) + const list1 = List.create() + expect(list1.reverse()).toHaveValues() }) xtest('non-empty list', () => { - const list1 = new List([1, 3, 5, 7]) - expect(list1.reverse().values).toEqual([7, 5, 3, 1]) + const list1 = List.create(1, 3, 5, 7) + expect(list1.reverse()).toHaveValues(7, 5, 3, 1) }) xtest('list of lists is not flattened', () => { - const list1 = new List([[1, 2], [3], [], [4, 5, 6]]) - expect(list1.reverse().values).toEqual([[4, 5, 6], [], [3], [1, 2]]) + const list1 = List.create([1, 2], [3], [], [4, 5, 6]) + expect(list1.reverse()).toHaveValues([4, 5, 6], [], [3], [1, 2]) }) }) diff --git a/exercises/practice/list-ops/list-ops.ts b/exercises/practice/list-ops/list-ops.ts index ccdc4f13d..88f227d87 100644 --- a/exercises/practice/list-ops/list-ops.ts +++ b/exercises/practice/list-ops/list-ops.ts @@ -1,37 +1,10 @@ export class List { - constructor() { - throw new Error('Remove this statement and implement this function') - } - - append() { - throw new Error('Remove this statement and implement this function') - } - - concat() { - throw new Error('Remove this statement and implement this function') - } - - filter() { - throw new Error('Remove this statement and implement this function') - } - - map() { - throw new Error('Remove this statement and implement this function') - } - - length() { - throw new Error('Remove this statement and implement this function') - } - - foldl() { - throw new Error('Remove this statement and implement this function') - } - - foldr() { - throw new Error('Remove this statement and implement this function') - } + public static create(...values: unknown[]): unknown { + // Do *not* construct any array literal ([]) in your solution. + // Do *not* construct any arrays through new Array in your solution. + // DO *not* use any of the Array.prototype methods in your solution. - reverse() { + // You may use the destructuring and spreading (...) syntax from Iterable. throw new Error('Remove this statement and implement this function') } } diff --git a/exercises/practice/matching-brackets/.meta/proof.ci.ts b/exercises/practice/matching-brackets/.meta/proof.ci.ts index c8dbbf3e0..c4fb53f29 100644 --- a/exercises/practice/matching-brackets/.meta/proof.ci.ts +++ b/exercises/practice/matching-brackets/.meta/proof.ci.ts @@ -1,22 +1,35 @@ -export function isPaired(input: string): boolean { - const brackets = input.replace(/[^{(\[\])}]/g, '') - const bracketsAreMatching = (leftBracket, rightBracket) => +type LeftBracket = '(' | '[' | '{' +type RightBracket = ')' | ']' | '}' +type AnyBracket = LeftBracket | RightBracket + +function bracketsAreMatching( + leftBracket: AnyBracket, + rightBracket: AnyBracket +): boolean { + return ( (leftBracket === '(' && rightBracket === ')') || (leftBracket === '[' && rightBracket === ']') || (leftBracket === '{' && rightBracket === '}') + ) +} + +export function isPaired(input: string): boolean { + const brackets = input.replace(/[^{([\])}]/g, '') + + const openBrackets: AnyBracket[] = [] - let arr = [] - for (let letter of brackets) { - if (arr.length >= 1) { - const lastBracket = arr[arr.length - 1] - if (bracketsAreMatching(lastBracket, letter)) { - arr.pop() + for (const letter of brackets) { + const bracket = letter as AnyBracket + if (openBrackets.length >= 1) { + const lastBracket = openBrackets[openBrackets.length - 1] + if (bracketsAreMatching(lastBracket, bracket)) { + openBrackets.pop() } else { - arr.push(letter) + openBrackets.push(bracket) } } else { - arr.push(letter) + openBrackets.push(bracket) } } - return arr.length === 0 + return openBrackets.length === 0 } diff --git a/exercises/practice/palindrome-products/.meta/proof.ci.ts b/exercises/practice/palindrome-products/.meta/proof.ci.ts index 6eead0c62..bd184521c 100644 --- a/exercises/practice/palindrome-products/.meta/proof.ci.ts +++ b/exercises/practice/palindrome-products/.meta/proof.ci.ts @@ -3,48 +3,46 @@ interface Input { minFactor?: number } -interface Palindrome { +type Factors = [number, number][] +interface PalindromeShape { value: number - factors: Array<[number, number]> + factors: Factors } -const reverseString = (str: string) => str.split('').reverse().join('') +const reverseString = (str: string): string => str.split('').reverse().join('') -export function generate(params: Input): Palindromes { - if ((params.minFactor || 1) > params.maxFactor) { - throw new Error('min must be <= max') - } - return new Palindromes(params.maxFactor, params.minFactor || 1) -} +class Palindrome implements PalindromeShape { + public readonly value: number + public readonly factors: Factors -class Palindrome { constructor(factor1: number, factor2: number) { this.value = factor1 * factor2 this.factors = [[factor1, factor2].sort() as [number, number]] } - withFactors(factors) { + public withFactors(factors: Factors[number]): this { this.factors.push(factors.sort()) - this.factors = this.factors.sort() + this.factors.sort() + return this } - valid() { + public valid(): boolean { const s = `${this.value}` return s === reverseString(s) } - merge(other) { + public merge(other: Palindrome): this { other.factors.forEach((f) => this.factors.push(f)) - this.factors = this.factors.sort() + this.factors.sort() + return this } } - -export class Palindromes { +class Palindromes { constructor(public maxFactor: number, public minFactor = 1) {} - get largest() { + public get largest(): PalindromeShape { let best = new Palindrome(this.minFactor, this.minFactor) for (let m = this.maxFactor; m >= this.minFactor; m -= 1) { let p = null @@ -62,10 +60,11 @@ export class Palindromes { if (best.valid()) { return best } + return { value: null, factors: [] } } - get smallest() { + public get smallest(): PalindromeShape { for (let m = this.minFactor; m <= this.maxFactor; m += 1) { for (let n = this.minFactor; n <= this.maxFactor; n += 1) { const p = new Palindrome(m, n) @@ -77,3 +76,10 @@ export class Palindromes { return { value: null, factors: [] } } } + +export function generate(params: Input): Palindromes { + if ((params.minFactor || 1) > params.maxFactor) { + throw new Error('min must be <= max') + } + return new Palindromes(params.maxFactor, params.minFactor || 1) +} diff --git a/exercises/practice/palindrome-products/palindrome-products.test.ts b/exercises/practice/palindrome-products/palindrome-products.test.ts index 3eb1a018f..431f46e4c 100644 --- a/exercises/practice/palindrome-products/palindrome-products.test.ts +++ b/exercises/practice/palindrome-products/palindrome-products.test.ts @@ -128,8 +128,7 @@ describe('Palindromes', () => { }) }) -function sortFactors( - factors: ReturnType['smallest']['factors'] -) { +type Factors = ReturnType['smallest']['factors'] +function sortFactors(factors: Factors): Factors { return factors.map((f) => f.sort()).sort() } diff --git a/exercises/practice/phone-number/.meta/proof.ci.ts b/exercises/practice/phone-number/.meta/proof.ci.ts index e6e919c28..4a1acf4a6 100644 --- a/exercises/practice/phone-number/.meta/proof.ci.ts +++ b/exercises/practice/phone-number/.meta/proof.ci.ts @@ -1,4 +1,4 @@ -export const clean = (number: string) => { +export const clean = (number: string): string => { if (/[a-zA-Z]/.test(number)) { throw new Error('Letters not permitted') } else if (/[@:!]/.test(number)) { diff --git a/exercises/practice/protein-translation/.meta/proof.ci.ts b/exercises/practice/protein-translation/.meta/proof.ci.ts index d954cc712..a221f7f83 100644 --- a/exercises/practice/protein-translation/.meta/proof.ci.ts +++ b/exercises/practice/protein-translation/.meta/proof.ci.ts @@ -16,11 +16,15 @@ const ACID_PROTEIN_MAP = { UAA: 'STOP', UAG: 'STOP', UGA: 'STOP', -} +} as const + +type Codon = keyof typeof ACID_PROTEIN_MAP +type Protein = typeof ACID_PROTEIN_MAP[Codon] -const getProtein = (codon: string) => ACID_PROTEIN_MAP[codon] || 'INVALID' +const getProtein = (codon: string): Protein | 'INVALID' => + ACID_PROTEIN_MAP[codon] || 'INVALID' -export const translate = (rnaStrand: string) => { +export const translate = (rnaStrand: string): Protein[] => { const proteins: typeof ACID_PROTEIN_MAP[keyof typeof ACID_PROTEIN_MAP][] = [] if (rnaStrand) { diff --git a/exercises/practice/pythagorean-triplet/.meta/proof.ci.ts b/exercises/practice/pythagorean-triplet/.meta/proof.ci.ts index b1d24a20c..55f121950 100644 --- a/exercises/practice/pythagorean-triplet/.meta/proof.ci.ts +++ b/exercises/practice/pythagorean-triplet/.meta/proof.ci.ts @@ -4,35 +4,37 @@ type Options = { sum: number } +type TripletArray = [number, number, number] + class Triplet { constructor( - private readonly a: number, - private readonly b: number, - private readonly c: number + private readonly a: TripletArray[0], + private readonly b: TripletArray[1], + private readonly c: TripletArray[2] ) {} - toArray() { + public toArray(): TripletArray { return [this.a, this.b, this.c] } - get pythagorean() { + public get pythagorean(): boolean { return this.a * this.a + this.b * this.b === this.c * this.c } - get sum() { + public get sum(): number { return this.a + this.b + this.c } } -export function triplets({ minFactor, maxFactor, sum }: Options) { +export function triplets({ minFactor, maxFactor, sum }: Options): Triplet[] { const min = minFactor || 1 const max = maxFactor || sum - 1 - const isDesired = (triplet: Triplet) => { + const isDesired = (triplet: Triplet): boolean => { return triplet.pythagorean && (!sum || triplet.sum === sum) } - const triplets = [] + const triplets: Triplet[] = [] for (let a = min; a < max - 1; a += 1) { for (let b = a + 1; b < max; b += 1) { diff --git a/exercises/practice/pythagorean-triplet/pythagorean-triplet.test.ts b/exercises/practice/pythagorean-triplet/pythagorean-triplet.test.ts index 6a08682a0..1450d223b 100644 --- a/exercises/practice/pythagorean-triplet/pythagorean-triplet.test.ts +++ b/exercises/practice/pythagorean-triplet/pythagorean-triplet.test.ts @@ -1,6 +1,8 @@ import { triplets } from './pythagorean-triplet' -function tripletsWithSum(sum: number, options = {}) { +type Triplet = [number, number, number] + +function tripletsWithSum(sum: number, options = {}): Triplet[] { return triplets({ ...options, sum }).map((triplet) => triplet.toArray().sort((a, b) => a - b) ) diff --git a/exercises/practice/queen-attack/.meta/proof.ci.ts b/exercises/practice/queen-attack/.meta/proof.ci.ts index 48293a90f..753a35503 100644 --- a/exercises/practice/queen-attack/.meta/proof.ci.ts +++ b/exercises/practice/queen-attack/.meta/proof.ci.ts @@ -3,13 +3,14 @@ const H = 8 const STARTING: Positions = { black: [0, 3], white: [7, 3] } as const type Position = readonly [number, number] +type Board = string[] type Positions = { white: Position black: Position } -function invalidPosition({ white, black }: Positions) { +function invalidPosition({ white, black }: Positions): boolean { if (white[0] < 0 || white[0] >= H || white[1] < 0 || white[1] >= W) { return true } @@ -21,15 +22,15 @@ function invalidPosition({ white, black }: Positions) { return false } -function samePosition({ white, black }: Positions) { +function samePosition({ white, black }: Positions): boolean { return white[0] === black[0] && white[1] === black[1] } -function constructBoard() { +function constructBoard(): Board { return new Array(W * H).fill('_') } -function placePieces(self: QueenAttack) { +function placePieces(self: QueenAttack): void { const board = self.board const [blackRow, blackColumn] = self.black const [whiteRow, whiteColumn] = self.white @@ -62,7 +63,7 @@ export class QueenAttack { return this } - get canAttack(): boolean { + public get canAttack(): boolean { // Same row or column if (this.black[0] === this.white[0] || this.black[1] === this.white[1]) { return true @@ -75,7 +76,7 @@ export class QueenAttack { ) } - toString(): string { + public toString(): string { return Array.from({ length: H }, (_, row) => this.board.slice(row * H, row * H + W).join(' ') ).join('\n') diff --git a/exercises/practice/resistor-color-duo/.meta/proof.ci.ts b/exercises/practice/resistor-color-duo/.meta/proof.ci.ts index c17219460..52f9dc400 100644 --- a/exercises/practice/resistor-color-duo/.meta/proof.ci.ts +++ b/exercises/practice/resistor-color-duo/.meta/proof.ci.ts @@ -12,8 +12,8 @@ const COLORS = [ 'white', ] -const colorCode = (color: string) => COLORS.indexOf(color) +const colorCode = (color: string): number => COLORS.indexOf(color) // resistor-color solution END -export const decodedValue = ([tens, ones]: string[]) => +export const decodedValue = ([tens, ones]: string[]): number => colorCode(tens) * 10 + colorCode(ones) diff --git a/exercises/practice/robot-simulator/.meta/proof.ci.ts b/exercises/practice/robot-simulator/.meta/proof.ci.ts index e99da9b54..bad5583f2 100644 --- a/exercises/practice/robot-simulator/.meta/proof.ci.ts +++ b/exercises/practice/robot-simulator/.meta/proof.ci.ts @@ -5,11 +5,19 @@ export class InvalidInputError extends Error { } } +type Bearing = 'north' | 'south' | 'east' | 'west' +type Instruction = 'turnLeft' | 'turnRight' | 'advance' + +const validDirections: Bearing[] = ['north', 'south', 'east', 'west'] + +function isValidBearing(next: string): next is Bearing { + return validDirections.includes(next as Bearing) +} export class Robot { - coordinates: number[] - bearing: string + public coordinates: [number, number] + public bearing: Bearing - static instructions(s: string) { + private static instructions(s: string): Instruction[] { return [...s].map((character) => { switch (character) { case 'L': @@ -31,16 +39,15 @@ export class Robot { this.bearing = 'north' } - set direction(next: string) { - const validDirections = ['north', 'south', 'east', 'west'] - if (!validDirections.includes(next)) { + private set direction(next: string) { + if (!isValidBearing(next)) { throw new InvalidInputError('Invalid Robot Bearing') } this.bearing = next } - advance() { + private advance(): void { if (this.bearing === 'north') { this.coordinates[1] += 1 } else if (this.bearing === 'south') { @@ -52,7 +59,7 @@ export class Robot { } } - turnLeft() { + private turnLeft(): void { if (this.bearing === 'north') { this.direction = 'west' } else if (this.bearing === 'south') { @@ -64,7 +71,7 @@ export class Robot { } } - turnRight() { + private turnRight(): void { if (this.bearing === 'north') { this.direction = 'east' } else if (this.bearing === 'south') { @@ -76,12 +83,12 @@ export class Robot { } } - place(args: { x: number; y: number; direction: string }) { + public place(args: { x: number; y: number; direction: string }): void { this.coordinates = [args.x, args.y] this.direction = args.direction } - evaluate(s: string) { + public evaluate(s: string): void { Robot.instructions(s).forEach((instruction) => { this[instruction]() }) diff --git a/exercises/practice/robot-simulator/robot-simulator.test.ts b/exercises/practice/robot-simulator/robot-simulator.test.ts index 94cf158d9..363068463 100644 --- a/exercises/practice/robot-simulator/robot-simulator.test.ts +++ b/exercises/practice/robot-simulator/robot-simulator.test.ts @@ -1,14 +1,14 @@ import { Robot, InvalidInputError } from './robot-simulator' -function turnRight(robot: Robot) { +function turnRight(robot: Robot): void { robot.evaluate('R') } -function turnLeft(robot: Robot) { +function turnLeft(robot: Robot): void { robot.evaluate('L') } -function advance(robot: Robot) { +function advance(robot: Robot): void { robot.evaluate('A') } diff --git a/exercises/practice/robot-simulator/robot-simulator.ts b/exercises/practice/robot-simulator/robot-simulator.ts index 3cc97dc3b..0af105472 100644 --- a/exercises/practice/robot-simulator/robot-simulator.ts +++ b/exercises/practice/robot-simulator/robot-simulator.ts @@ -6,17 +6,18 @@ export class InvalidInputError extends Error { } type Direction = 'north' | 'east' | 'south' | 'west' +type Coordinates = [number, number] export class Robot { - get bearing() { + get bearing(): Direction { throw new Error('Remove this statement and implement this function') } - get coordinates() { + get coordinates(): Coordinates { throw new Error('Remove this statement and implement this function') } - place({}: { x: number; y: number; direction: Direction }) { + place({}: { x: number; y: number; direction: string }) { throw new Error('Remove this statement and implement this function') } diff --git a/exercises/practice/secret-handshake/.meta/proof.ci.ts b/exercises/practice/secret-handshake/.meta/proof.ci.ts index 3689fd9cb..68d2405e7 100644 --- a/exercises/practice/secret-handshake/.meta/proof.ci.ts +++ b/exercises/practice/secret-handshake/.meta/proof.ci.ts @@ -1,15 +1,13 @@ -const handshakeCommands = ['wink', 'double blink', 'close your eyes', 'jump'] +const COMMANDS = ['wink', 'double blink', 'close your eyes', 'jump'] as const +type Command = typeof COMMANDS[number] +type Commands = Command[] -export const commands = (handshake: string) => { - if (typeof handshake !== 'number') { - throw new Error('Handshake must be a number') - } - - const shakeWith = handshakeCommands.filter( - (_, i) => handshake & Math.pow(2, i) - ) +export const commands = (handshake: number): Commands => { + const shakeWith = COMMANDS.filter((_, i) => handshake & Math.pow(2, i)) - if (handshake & Math.pow(2, 4)) shakeWith.reverse() + if (handshake & Math.pow(2, 4)) { + shakeWith.reverse() + } return shakeWith } diff --git a/exercises/practice/series/.meta/proof.ci.ts b/exercises/practice/series/.meta/proof.ci.ts index 77b532eb6..e9539098a 100644 --- a/exercises/practice/series/.meta/proof.ci.ts +++ b/exercises/practice/series/.meta/proof.ci.ts @@ -1,6 +1,6 @@ export class Series { - numberString: string - digits: number[] + private readonly numberString: string + private readonly digits: number[] constructor(numberString: string) { if (!numberString) { @@ -11,11 +11,11 @@ export class Series { this.digits = this.getDigits() } - getDigits(): number[] { + private getDigits(): number[] { return [...this.numberString].map((digit) => parseInt(digit, 10)) } - slices(sliceSize: number): number[][] { + public slices(sliceSize: number): number[][] { const result: number[][] = [] let slice: number[] = [] diff --git a/exercises/practice/space-age/.meta/proof.ci.ts b/exercises/practice/space-age/.meta/proof.ci.ts index a8dd01378..53cb2bf99 100644 --- a/exercises/practice/space-age/.meta/proof.ci.ts +++ b/exercises/practice/space-age/.meta/proof.ci.ts @@ -12,7 +12,7 @@ const EARTH_TO_OTHER_PLANETS = { export const age = ( planet: keyof typeof EARTH_TO_OTHER_PLANETS, seconds: number -) => { +): number => { const earthYears = seconds / 31557600 const years = earthYears / EARTH_TO_OTHER_PLANETS[planet] diff --git a/exercises/practice/sum-of-multiples/.meta/proof.ci.ts b/exercises/practice/sum-of-multiples/.meta/proof.ci.ts index aa4596aa6..8a500524a 100644 --- a/exercises/practice/sum-of-multiples/.meta/proof.ci.ts +++ b/exercises/practice/sum-of-multiples/.meta/proof.ci.ts @@ -1,4 +1,4 @@ -export const sum = (multiples: number[], limit: number) => { +export const sum = (multiples: number[], limit: number): number => { let sum = 0 for (let i = 1; i < limit; i++) { if (multiples.some((multiple) => i % multiple === 0)) { diff --git a/exercises/practice/triangle/.meta/proof.ci.ts b/exercises/practice/triangle/.meta/proof.ci.ts index 9dd2ab19a..a6a3816b3 100644 --- a/exercises/practice/triangle/.meta/proof.ci.ts +++ b/exercises/practice/triangle/.meta/proof.ci.ts @@ -1,11 +1,11 @@ export class Triangle { - sides: number[] + private readonly sides: number[] constructor(...sides: number[]) { this.sides = sides } - get isValid() { + public get isValid(): boolean { const [s1, s2, s3] = this.sides const sidesArePositive = s1 > 0 && s2 > 0 && s3 > 0 const validatesTriangleInequality = @@ -13,7 +13,7 @@ export class Triangle { return sidesArePositive && validatesTriangleInequality } - get isEquilateral() { + public get isEquilateral(): boolean { if (!this.isValid) { return false } @@ -21,7 +21,7 @@ export class Triangle { return s1 === s2 && s2 === s3 && s1 === s3 } - get isIsosceles() { + public get isIsosceles(): boolean { if (!this.isValid) { return false } @@ -29,7 +29,7 @@ export class Triangle { return s1 === s2 || s1 === s3 || s2 === s3 } - get isScalene() { + public get isScalene(): boolean { if (!this.isValid) { return false } diff --git a/exercises/practice/wordy/.meta/proof.ci.ts b/exercises/practice/wordy/.meta/proof.ci.ts index 6d70ebe1d..33b5b0c2a 100644 --- a/exercises/practice/wordy/.meta/proof.ci.ts +++ b/exercises/practice/wordy/.meta/proof.ci.ts @@ -15,7 +15,7 @@ const compute = ( } } -export const answer = (question: string) => { +export const answer = (question: string): number => { const operationsPattern = new RegExp(/plus|minus|divided by|multiplied by/g) if ( !operationsPattern.test(question) &&