From b92006b6d725c9c07f6efd05f1a4c73d3206d324 Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Sat, 1 Jan 2022 23:05:38 -0500 Subject: [PATCH 01/10] Start of Analyzer for Bird Watcher --- .../bird-watcher/BirdWatcherSolution.ts | 46 ++++++++++++++ src/analyzers/concept/bird-watcher/index.ts | 61 +++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 src/analyzers/concept/bird-watcher/BirdWatcherSolution.ts create mode 100644 src/analyzers/concept/bird-watcher/index.ts diff --git a/src/analyzers/concept/bird-watcher/BirdWatcherSolution.ts b/src/analyzers/concept/bird-watcher/BirdWatcherSolution.ts new file mode 100644 index 00000000..804d74b4 --- /dev/null +++ b/src/analyzers/concept/bird-watcher/BirdWatcherSolution.ts @@ -0,0 +1,46 @@ +import { + AstParser, + extractExports, + extractFunctions, +} from '@exercism/static-analysis' +import { TSESTree } from '@typescript-eslint/typescript-estree' +import { readFileSync } from 'fs' +import path from 'path' +import { Source } from '~src/analyzers/SourceImpl' + +export const TOTAL_BIRD_COUNT = 'totalBirdCount' +export const BIRDS_IN_WEEK = 'birdsInWeek' +export const FIX_BIRD_COUNT_LOG = 'fixBirdCountLog' + +export class BirdWatcherSolution { + private readonly source: Source + + private exemplar!: Source + + constructor(public readonly program: TSESTree.Program, source: string) { + this.source = new Source(source) + + const functions = extractFunctions(program) + const exports = extractExports(program) + } + + public readExemplar(directory: string): void { + const configPath = path.join(directory, '.meta', 'config.json') + const config = JSON.parse(readFileSync(configPath).toString()) + + const exemplarPath = path.join(directory, config.files.exemplar[0]) + this.exemplar = new Source(readFileSync(exemplarPath).toString()) + } + + public get isExemplar(): boolean { + const sourceAst = AstParser.REPRESENTER.parseSync(this.source.toString()) + const exemplarAst = AstParser.REPRESENTER.parseSync( + this.exemplar.toString() + ) + + return ( + JSON.stringify(sourceAst[0].program) === + JSON.stringify(exemplarAst[0].program) + ) + } +} diff --git a/src/analyzers/concept/bird-watcher/index.ts b/src/analyzers/concept/bird-watcher/index.ts new file mode 100644 index 00000000..29385aa2 --- /dev/null +++ b/src/analyzers/concept/bird-watcher/index.ts @@ -0,0 +1,61 @@ +import { + AstParser, + Input, + NoExportError, + NoMethodError, +} from '@exercism/static-analysis' +import { TSESTree } from '@typescript-eslint/typescript-estree' +import { + EXEMPLAR_SOLUTION, + NO_METHOD, + NO_NAMED_EXPORT, +} from '../../../comments/shared' +import { ExecutionOptions, WritableOutput } from '../../../interface' +import { IsolatedAnalyzerImpl } from '../../IsolatedAnalyzerImpl' +import { BirdWatcherSolution } from './BirdWatcherSolution' + +type Program = TSESTree.Program + +export class BirdWatcherAnalyzer extends IsolatedAnalyzerImpl { + private solution!: BirdWatcherSolution + + protected async execute( + input: Input, + output: WritableOutput, + options: ExecutionOptions + ): Promise { + const [parsed] = await AstParser.ANALYZER.parse(input) + + this.solution = this.checkStructure(parsed.program, parsed.source, output) + this.solution.readExemplar(options.inputDir) + + if (this.solution.isExemplar) { + output.add(EXEMPLAR_SOLUTION()) + output.finish() + } + + output.finish() + } + + private checkStructure( + program: Readonly, + source: Readonly, + output: WritableOutput + ): BirdWatcherSolution | never { + try { + return new BirdWatcherSolution(program, source) + } catch (error) { + if (error instanceof NoMethodError) { + output.add(NO_METHOD({ 'method.name': error.method })) + output.finish() + } + + if (error instanceof NoExportError) { + output.add(NO_NAMED_EXPORT({ 'export.name': error.namedExport })) + } + + throw error + } + } +} +export default BirdWatcherAnalyzer From 1b1d220fab0394606f3dd78b697bbd4b4f6bed1a Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Sun, 2 Jan 2022 23:18:07 -0500 Subject: [PATCH 02/10] prepare package and changelog update --- CHANGELOG.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a628fd3f..a85f029f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.22.0 + +- Add analyzer for `concept/bird-watcher` + ## 0.21.0 - Add analyzer for `concept/elyses-analytic-enchantments` diff --git a/package.json b/package.json index b21a3125..63e54d46 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@exercism/javascript-analyzer", - "version": "0.21.0", + "version": "0.22.0", "description": "Exercism analyzer for javascript", "repository": "https://github.com/exercism/javascript-analyzer", "author": "Derk-Jan Karrenbeld ", From 66e16eca96efbd838dd33494c5c4a64e0b11f42a Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Sun, 2 Jan 2022 23:21:58 -0500 Subject: [PATCH 03/10] add analyzer test --- test/analyzers/bird-watcher/exemplar.ts | 41 +++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 test/analyzers/bird-watcher/exemplar.ts diff --git a/test/analyzers/bird-watcher/exemplar.ts b/test/analyzers/bird-watcher/exemplar.ts new file mode 100644 index 00000000..d7b7fba4 --- /dev/null +++ b/test/analyzers/bird-watcher/exemplar.ts @@ -0,0 +1,41 @@ +import { DirectoryWithConfigInput } from '@exercism/static-analysis' +import path from 'path' +import { BirdWatcherAnalyzer } from '~src/analyzers/concept/bird-watcher' +import { EXEMPLAR_SOLUTION } from '~src/comments/shared' +import { makeAnalyze, makeOptions } from '~test/helpers/smoke' + +const inputDir = path.join( + __dirname, + '..', + '..', + 'fixtures', + 'bird-watcher', + 'exemplar' +) + +const analyze = makeAnalyze( + () => new BirdWatcherAnalyzer(), + makeOptions({ + get inputDir(): string { + return inputDir + }, + get exercise(): string { + return 'bird-watcher' + }, + }) +) + +describe('When running analysis on bird-watcher', () => { + it('recognizes the exemplar solution', async () => { + const input = new DirectoryWithConfigInput(inputDir) + + const [solution] = await input.read() + const output = await analyze(solution) + + expect(output.comments.length).toBe(1) + expect(output.comments[0].type).toBe('celebratory') + expect(output.comments[0].externalTemplate).toBe( + EXEMPLAR_SOLUTION().externalTemplate + ) + }) +}) From a8980613c2bc8febdcb35d2e82b9113e41c18639 Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Tue, 4 Jan 2022 20:03:20 -0500 Subject: [PATCH 04/10] more changes --- .../bird-watcher/BirdWatcherSolution.ts | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/analyzers/concept/bird-watcher/BirdWatcherSolution.ts b/src/analyzers/concept/bird-watcher/BirdWatcherSolution.ts index 804d74b4..a1eee4b1 100644 --- a/src/analyzers/concept/bird-watcher/BirdWatcherSolution.ts +++ b/src/analyzers/concept/bird-watcher/BirdWatcherSolution.ts @@ -1,20 +1,48 @@ import { AstParser, + ExtractedFunction, extractExports, extractFunctions, + findFirst, } from '@exercism/static-analysis' -import { TSESTree } from '@typescript-eslint/typescript-estree' +import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/typescript-estree' import { readFileSync } from 'fs' import path from 'path' +import { PublicApi } from '~src/analyzers/PublicApi' import { Source } from '~src/analyzers/SourceImpl' +import { assertPublicApi } from '~src/asserts/assert_public_api' export const TOTAL_BIRD_COUNT = 'totalBirdCount' export const BIRDS_IN_WEEK = 'birdsInWeek' export const FIX_BIRD_COUNT_LOG = 'fixBirdCountLog' +class TotalBirdCount extends PublicApi { + constructor(implementation: ExtractedFunction) { + super(implementation) + } +} + +class BirdsInWeek extends PublicApi { + constructor(implementation: ExtractedFunction) { + super(implementation) + console.log(this.hasFor()) + } + public hasFor(): boolean { + return ( + findFirst( + this.implementation.body, + (node): node is TSESTree.ForStatement => + [AST_NODE_TYPES.ForStatement].some((type) => type === node.type) + ) !== undefined + ) + } +} + export class BirdWatcherSolution { private readonly source: Source + public readonly birdsInWeek: BirdsInWeek + private exemplar!: Source constructor(public readonly program: TSESTree.Program, source: string) { @@ -22,6 +50,10 @@ export class BirdWatcherSolution { const functions = extractFunctions(program) const exports = extractExports(program) + + this.birdsInWeek = new BirdsInWeek( + assertPublicApi(BIRDS_IN_WEEK, exports, functions) + ) } public readExemplar(directory: string): void { From d862d859cc31d4ed175ac4a1a66c9df0ae7d6658 Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Tue, 4 Jan 2022 21:02:34 -0500 Subject: [PATCH 05/10] add check for use of for loop in each function --- .../bird-watcher/BirdWatcherSolution.ts | 37 ++++++++++++++++++- src/analyzers/concept/bird-watcher/index.ts | 35 ++++++++++++++++-- 2 files changed, 66 insertions(+), 6 deletions(-) diff --git a/src/analyzers/concept/bird-watcher/BirdWatcherSolution.ts b/src/analyzers/concept/bird-watcher/BirdWatcherSolution.ts index a1eee4b1..40050303 100644 --- a/src/analyzers/concept/bird-watcher/BirdWatcherSolution.ts +++ b/src/analyzers/concept/bird-watcher/BirdWatcherSolution.ts @@ -20,14 +20,39 @@ class TotalBirdCount extends PublicApi { constructor(implementation: ExtractedFunction) { super(implementation) } + + public get hasFor(): boolean { + return ( + findFirst( + this.implementation.body, + (node): node is TSESTree.ForStatement => + [AST_NODE_TYPES.ForStatement].some((type) => type === node.type) + ) !== undefined + ) + } } class BirdsInWeek extends PublicApi { constructor(implementation: ExtractedFunction) { super(implementation) - console.log(this.hasFor()) } - public hasFor(): boolean { + public get hasFor(): boolean { + return ( + findFirst( + this.implementation.body, + (node): node is TSESTree.ForStatement => + [AST_NODE_TYPES.ForStatement].some((type) => type === node.type) + ) !== undefined + ) + } +} + +class FixBirdCountLog extends PublicApi { + constructor(implementation: ExtractedFunction) { + super(implementation) + } + + public get hasFor(): boolean { return ( findFirst( this.implementation.body, @@ -41,7 +66,9 @@ class BirdsInWeek extends PublicApi { export class BirdWatcherSolution { private readonly source: Source + public readonly totalBirdCount: TotalBirdCount public readonly birdsInWeek: BirdsInWeek + public readonly fixBirdCountLog: FixBirdCountLog private exemplar!: Source @@ -51,9 +78,15 @@ export class BirdWatcherSolution { const functions = extractFunctions(program) const exports = extractExports(program) + this.totalBirdCount = new TotalBirdCount( + assertPublicApi(TOTAL_BIRD_COUNT, exports, functions) + ) this.birdsInWeek = new BirdsInWeek( assertPublicApi(BIRDS_IN_WEEK, exports, functions) ) + this.fixBirdCountLog = new FixBirdCountLog( + assertPublicApi(FIX_BIRD_COUNT_LOG, exports, functions) + ) } public readExemplar(directory: string): void { diff --git a/src/analyzers/concept/bird-watcher/index.ts b/src/analyzers/concept/bird-watcher/index.ts index 29385aa2..494428d4 100644 --- a/src/analyzers/concept/bird-watcher/index.ts +++ b/src/analyzers/concept/bird-watcher/index.ts @@ -5,14 +5,29 @@ import { NoMethodError, } from '@exercism/static-analysis' import { TSESTree } from '@typescript-eslint/typescript-estree' +import { CommentType, factory } from '~src/comments/comment' import { EXEMPLAR_SOLUTION, NO_METHOD, NO_NAMED_EXPORT, -} from '../../../comments/shared' -import { ExecutionOptions, WritableOutput } from '../../../interface' -import { IsolatedAnalyzerImpl } from '../../IsolatedAnalyzerImpl' -import { BirdWatcherSolution } from './BirdWatcherSolution' +} from '~src/comments/shared' +import { ExecutionOptions, WritableOutput } from '~src/interface' +import { IsolatedAnalyzerImpl } from '~src/analyzers/IsolatedAnalyzerImpl' +import { + BIRDS_IN_WEEK, + BirdWatcherSolution, + FIX_BIRD_COUNT_LOG, + TOTAL_BIRD_COUNT, +} from './BirdWatcherSolution' + +const USE_FOR_LOOP = factory<'function'>` +📕 The learning exercise exist to practice or demonstrate your knowledge of \`for-loops\`. +This is different from practice exercises where you are asked to solve the best way you +possibly can. + +💬 Update the function \`${'function'}\` so that it makes use of \`for-loops\`. + +`('javascript.bird-watcher.uses_for_loop', CommentType.Actionable) type Program = TSESTree.Program @@ -34,6 +49,18 @@ export class BirdWatcherAnalyzer extends IsolatedAnalyzerImpl { output.finish() } + if (!this.solution.birdsInWeek.hasFor) { + output.add(USE_FOR_LOOP({ function: BIRDS_IN_WEEK })) + } + + if (!this.solution.totalBirdCount.hasFor) { + output.add(USE_FOR_LOOP({ function: TOTAL_BIRD_COUNT })) + } + + if (!this.solution.fixBirdCountLog.hasFor) { + output.add(USE_FOR_LOOP({ function: FIX_BIRD_COUNT_LOG })) + } + output.finish() } From ccbe12dc4b2c84a3720c1d348bc621d8f9f9ca23 Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Sun, 9 Jan 2022 19:39:37 -0500 Subject: [PATCH 06/10] add shorthand assignment check --- .../bird-watcher/BirdWatcherSolution.ts | 24 ++++++++++++++++ src/analyzers/concept/bird-watcher/index.ts | 28 +++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/src/analyzers/concept/bird-watcher/BirdWatcherSolution.ts b/src/analyzers/concept/bird-watcher/BirdWatcherSolution.ts index 40050303..590ac2e7 100644 --- a/src/analyzers/concept/bird-watcher/BirdWatcherSolution.ts +++ b/src/analyzers/concept/bird-watcher/BirdWatcherSolution.ts @@ -30,6 +30,18 @@ class TotalBirdCount extends PublicApi { ) !== undefined ) } + + public get usesShorthandAssignment(): boolean { + return ( + findFirst( + this.implementation.body, + (node): node is TSESTree.AssignmentExpression => + [AST_NODE_TYPES.AssignmentExpression].some( + (type) => type === node.type + ) + )?.operator === '+=' + ) + } } class BirdsInWeek extends PublicApi { @@ -61,6 +73,18 @@ class FixBirdCountLog extends PublicApi { ) !== undefined ) } + + public get usesShorthandAssignment(): boolean { + return ( + findFirst( + this.implementation.body, + (node): node is TSESTree.AssignmentExpression => + [AST_NODE_TYPES.AssignmentExpression].some( + (type) => type === node.type + ) + )?.operator === '+=' + ) + } } export class BirdWatcherSolution { diff --git a/src/analyzers/concept/bird-watcher/index.ts b/src/analyzers/concept/bird-watcher/index.ts index 494428d4..6cbfc33b 100644 --- a/src/analyzers/concept/bird-watcher/index.ts +++ b/src/analyzers/concept/bird-watcher/index.ts @@ -29,6 +29,28 @@ possibly can. `('javascript.bird-watcher.uses_for_loop', CommentType.Actionable) +const PREFER_SHORTHAND_ASSIGNMENT = factory<'function'>` +You can reduce some cognitive overhead when it comes to assignment in equations. +One way to do this is using shorthand assignment that allows you to remove duplication. + +An example of assignment: + +\`\`\`javascript +x = x + 1 +\`\`\` + +can be in the short hand: + +\`\`\`javascript +x += 1 +\`\`\` + +Usage found in \`${'function'}\` if you are interested in modifying your solution. +`( + 'javascript.bird-watcher.prefer_shorthand_assignment', + CommentType.Informative +) + type Program = TSESTree.Program export class BirdWatcherAnalyzer extends IsolatedAnalyzerImpl { @@ -56,10 +78,16 @@ export class BirdWatcherAnalyzer extends IsolatedAnalyzerImpl { if (!this.solution.totalBirdCount.hasFor) { output.add(USE_FOR_LOOP({ function: TOTAL_BIRD_COUNT })) } + if (!this.solution.totalBirdCount.usesShorthandAssignment) { + output.add(PREFER_SHORTHAND_ASSIGNMENT({ function: TOTAL_BIRD_COUNT })) + } if (!this.solution.fixBirdCountLog.hasFor) { output.add(USE_FOR_LOOP({ function: FIX_BIRD_COUNT_LOG })) } + if (!this.solution.fixBirdCountLog.usesShorthandAssignment) { + output.add(PREFER_SHORTHAND_ASSIGNMENT({ function: FIX_BIRD_COUNT_LOG })) + } output.finish() } From b62cc59f2c80e1242354af38ee2ca4c33b60a477 Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Mon, 10 Jan 2022 00:09:47 -0500 Subject: [PATCH 07/10] check for incremental counter --- .../concept/bird-watcher/BirdWatcherSolution.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/analyzers/concept/bird-watcher/BirdWatcherSolution.ts b/src/analyzers/concept/bird-watcher/BirdWatcherSolution.ts index 590ac2e7..c26dff5e 100644 --- a/src/analyzers/concept/bird-watcher/BirdWatcherSolution.ts +++ b/src/analyzers/concept/bird-watcher/BirdWatcherSolution.ts @@ -74,6 +74,16 @@ class FixBirdCountLog extends PublicApi { ) } + public get usesIncrementalCounter(): boolean { + return ( + findFirst( + this.implementation.body, + (node): node is TSESTree.ForStatement => + [AST_NODE_TYPES.ForStatement].some((type) => type === node.type) + )?.body?.body[0].expression.operator === '++' + ) + } + public get usesShorthandAssignment(): boolean { return ( findFirst( From 1914aac740ee83f44dc705bc92dfc6349648626e Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Mon, 10 Jan 2022 00:14:29 -0500 Subject: [PATCH 08/10] update expression is incremental counter --- .../concept/bird-watcher/BirdWatcherSolution.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/analyzers/concept/bird-watcher/BirdWatcherSolution.ts b/src/analyzers/concept/bird-watcher/BirdWatcherSolution.ts index c26dff5e..7c829bcd 100644 --- a/src/analyzers/concept/bird-watcher/BirdWatcherSolution.ts +++ b/src/analyzers/concept/bird-watcher/BirdWatcherSolution.ts @@ -42,6 +42,16 @@ class TotalBirdCount extends PublicApi { )?.operator === '+=' ) } + + public get usesIncrementalCounter(): boolean { + return ( + findFirst( + this.implementation.body, + (node): node is TSESTree.UpdateExpression => + [AST_NODE_TYPES.UpdateExpression].some((type) => type === node.type) + )?.operator === '++' + ) + } } class BirdsInWeek extends PublicApi { From a3df4a387037849a064af3839e90339cf34a3032 Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Mon, 10 Jan 2022 00:29:42 -0500 Subject: [PATCH 09/10] check for initial total value set --- .../concept/bird-watcher/BirdWatcherSolution.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/analyzers/concept/bird-watcher/BirdWatcherSolution.ts b/src/analyzers/concept/bird-watcher/BirdWatcherSolution.ts index 7c829bcd..7c51c37e 100644 --- a/src/analyzers/concept/bird-watcher/BirdWatcherSolution.ts +++ b/src/analyzers/concept/bird-watcher/BirdWatcherSolution.ts @@ -21,6 +21,16 @@ class TotalBirdCount extends PublicApi { super(implementation) } + public get hasInitialTotalValue(): boolean { + return ( + findFirst( + this.implementation.body, + (node): node is TSESTree.VariableDeclarator => + [AST_NODE_TYPES.VariableDeclarator].some((type) => type === node.type) + )?.init?.value === 0 + ) + } + public get hasFor(): boolean { return ( findFirst( From 8b83f2e4263a328e8df3a9753e6066536a93733b Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Mon, 10 Jan 2022 00:44:39 -0500 Subject: [PATCH 10/10] setup check for using uneccessary if --- .../bird-watcher/BirdWatcherSolution.ts | 10 +++ .../bird-watcher/uses-if/.meta/config.json | 11 +++ .../bird-watcher/uses-if/.meta/exemplar.js | 49 ++++++++++++ .../bird-watcher/uses-if/bird-watcher.js | 50 ++++++++++++ .../bird-watcher/uses-if/bird-watcher.spec.js | 80 +++++++++++++++++++ 5 files changed, 200 insertions(+) create mode 100644 test/fixtures/bird-watcher/uses-if/.meta/config.json create mode 100644 test/fixtures/bird-watcher/uses-if/.meta/exemplar.js create mode 100644 test/fixtures/bird-watcher/uses-if/bird-watcher.js create mode 100644 test/fixtures/bird-watcher/uses-if/bird-watcher.spec.js diff --git a/src/analyzers/concept/bird-watcher/BirdWatcherSolution.ts b/src/analyzers/concept/bird-watcher/BirdWatcherSolution.ts index 7c51c37e..dcad526c 100644 --- a/src/analyzers/concept/bird-watcher/BirdWatcherSolution.ts +++ b/src/analyzers/concept/bird-watcher/BirdWatcherSolution.ts @@ -94,6 +94,16 @@ class FixBirdCountLog extends PublicApi { ) } + public get usesIfStatement(): boolean { + return ( + findFirst( + this.implementation.body, + (node): node is TSESTree.IfStatement => + [AST_NODE_TYPES.IfStatement].some((type) => type === node.type) + ) !== undefined + ) + } + public get usesIncrementalCounter(): boolean { return ( findFirst( diff --git a/test/fixtures/bird-watcher/uses-if/.meta/config.json b/test/fixtures/bird-watcher/uses-if/.meta/config.json new file mode 100644 index 00000000..ec8c67c2 --- /dev/null +++ b/test/fixtures/bird-watcher/uses-if/.meta/config.json @@ -0,0 +1,11 @@ +{ + "blurb": "Professionalize counting the birds in your garden with for loops and increment/decrement operators", + "authors": ["junedev"], + "contributors": [], + "files": { + "solution": ["bird-watcher.js"], + "test": ["bird-watcher.spec.js"], + "exemplar": [".meta/exemplar.js"] + }, + "forked_from": ["csharp/bird-watcher"] +} diff --git a/test/fixtures/bird-watcher/uses-if/.meta/exemplar.js b/test/fixtures/bird-watcher/uses-if/.meta/exemplar.js new file mode 100644 index 00000000..616b6c88 --- /dev/null +++ b/test/fixtures/bird-watcher/uses-if/.meta/exemplar.js @@ -0,0 +1,49 @@ +// @ts-check +// +// The line above enables type checking for this file. Various IDEs interpret +// the @ts-check directive. It will give you helpful autocompletion when +// implementing this exercise. + +/** + * Calculates the total bird count. + * + * @param {number[]} birdsPerDay + * @returns {number} total bird count + */ +export function totalBirdCount(birdsPerDay) { + let total = 0; + for (let i = 0; i < birdsPerDay.length; i++) { + total += birdsPerDay[i]; + } + return total; +} + +/** + * Calculates the total number of birds seen in a specific week. + * + * @param {number[]} birdsPerDay + * @param {number} week + * @returns {number} birds counted in the given week + */ +export function birdsInWeek(birdsPerDay, week) { + let total = 0; + const start = 7 * (week - 1); + for (let i = start; i < start + 7; i++) { + total += birdsPerDay[i]; + } + return total; +} + +/** + * Fixes the counting mistake by increasing the bird count + * by one for every second day. + * + * @param {number[]} birdsPerDay + * @returns {number[]} corrected bird count data + */ +export function fixBirdCountLog(birdsPerDay) { + for (let i = 0; i < birdsPerDay.length; i += 2) { + birdsPerDay[i]++; + } + return birdsPerDay; +} diff --git a/test/fixtures/bird-watcher/uses-if/bird-watcher.js b/test/fixtures/bird-watcher/uses-if/bird-watcher.js new file mode 100644 index 00000000..a22f69bb --- /dev/null +++ b/test/fixtures/bird-watcher/uses-if/bird-watcher.js @@ -0,0 +1,50 @@ +// @ts-check +// +// The line above enables type checking for this file. Various IDEs interpret +// the @ts-check directive. It will give you helpful autocompletion when +// implementing this exercise. + +/** + * Calculates the total bird count. + * + * @param {number[]} birdsPerDay + * @returns {number} total bird count + */ +export function totalBirdCount(birdsPerDay) { + let total = 0; + for (let i = 0; i < birdsPerDay.length; i++) { + total += birdsPerDay[i]; + } + return total; +} + +/** + * Calculates the total number of birds seen in a specific week. + * + * @param {number[]} birdsPerDay + * @param {number} week + * @returns {number} birds counted in the given week + */ +export function birdsInWeek(birdsPerDay, week) { + let total = 0; + const start = 7 * (week - 1); + for (let i = start; i < start + 7; i++) { + total += birdsPerDay[i]; + } + return total; +} + +/** + * Fixes the counting mistake by increasing the bird count + * by one for every second day. + * + * @param {number[]} birdsPerDay + * @returns {number[]} corrected bird count data + */ +export function fixBirdCountLog(birdsPerDay) { + if (birdsPerDay) {return} + for (let i = 0; i < birdsPerDay.length; i += 2) { + birdsPerDay[i]++; + } + return birdsPerDay; +} diff --git a/test/fixtures/bird-watcher/uses-if/bird-watcher.spec.js b/test/fixtures/bird-watcher/uses-if/bird-watcher.spec.js new file mode 100644 index 00000000..3339045c --- /dev/null +++ b/test/fixtures/bird-watcher/uses-if/bird-watcher.spec.js @@ -0,0 +1,80 @@ +import { totalBirdCount, birdsInWeek, fixBirdCountLog } from './bird-watcher'; + +describe('bird watcher', () => { + describe('totalBirdCount', () => { + test('calculates the correct total number of birds', () => { + const birdsPerDay = [9, 0, 8, 4, 5, 1, 3]; + expect(totalBirdCount(birdsPerDay)).toBe(30); + }); + + test('works for a short bird count list', () => { + const birdsPerDay = [2]; + expect(totalBirdCount(birdsPerDay)).toBe(2); + }); + + test('works for a long bird count list', () => { + // prettier-ignore + const birdsPerDay = [2, 8, 4, 1, 3, 5, 0, 4, 1, 6, 0, 3, 0, 1, 5, 4, 1, 1, 2, 6]; + expect(totalBirdCount(birdsPerDay)).toBe(57); + }); + }); + + describe('birdsInWeek', () => { + test('calculates the number of birds in the first week', () => { + const birdsPerDay = [3, 0, 5, 1, 0, 4, 1, 0, 3, 4, 3, 0, 8, 0]; + expect(birdsInWeek(birdsPerDay, 1)).toBe(14); + }); + + test('calculates the number of birds for a week in the middle of the log', () => { + // prettier-ignore + const birdsPerDay = [4, 7, 3, 2, 1, 1, 2, 0, 2, 3, 2, 7, 1, 3, 0, 6, 5, 3, 7, 2, 3]; + expect(birdsInWeek(birdsPerDay, 2)).toBe(18); + }); + + test('works when there is only one week', () => { + const birdsPerDay = [3, 0, 3, 3, 2, 1, 0]; + expect(birdsInWeek(birdsPerDay, 1)).toBe(12); + }); + + test('works for a long bird count list', () => { + const week21 = [2, 0, 1, 4, 1, 3, 0]; + const birdsPerDay = randomArray(20 * 7) + .concat(week21) + .concat(randomArray(10 * 7)); + + expect(birdsInWeek(birdsPerDay, 21)).toBe(11); + }); + }); + + describe('fixBirdCountLog', () => { + test('returns a bird count list with the corrected values', () => { + const birdsPerDay = [3, 0, 5, 1, 0, 4, 1, 0, 3, 4, 3, 0]; + const expected = [4, 0, 6, 1, 1, 4, 2, 0, 4, 4, 4, 0]; + expect(fixBirdCountLog(birdsPerDay)).toEqual(expected); + }); + + test('does not create a new array', () => { + const birdsPerDay = [2, 0, 1, 4, 1, 3, 0]; + + // this follows the suggestion from the Jest docs to avoid a confusing test report + // https://jestjs.io/docs/expect#tobevalue + expect(Object.is(fixBirdCountLog(birdsPerDay), birdsPerDay)).toBe(true); + }); + + test('works for a short bird count list', () => { + expect(fixBirdCountLog([4, 2])).toEqual([5, 2]); + }); + + test('works for a long bird count list', () => { + // prettier-ignore + const birdsPerDay = [2, 8, 4, 1, 3, 5, 0, 4, 1, 6, 0, 3, 0, 1, 5, 4, 1, 1, 2, 6]; + // prettier-ignore + const expected = [3, 8, 5, 1, 4, 5, 1, 4, 2, 6, 1, 3, 1, 1, 6, 4, 2, 1, 3, 6]; + expect(fixBirdCountLog(birdsPerDay)).toEqual(expected); + }); + }); +}); + +function randomArray(length) { + return Array.from({ length: length }, () => Math.floor(Math.random() * 8)); +}