From c64665c3d0600a56c5d58033d7d234652c9e9758 Mon Sep 17 00:00:00 2001 From: Thierry Rietveld Date: Fri, 28 Oct 2022 11:46:22 +0200 Subject: [PATCH 01/77] Added Graph --- packages/typescript-checker/src/graph/node.ts | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 packages/typescript-checker/src/graph/node.ts diff --git a/packages/typescript-checker/src/graph/node.ts b/packages/typescript-checker/src/graph/node.ts new file mode 100644 index 0000000000..a474d1e476 --- /dev/null +++ b/packages/typescript-checker/src/graph/node.ts @@ -0,0 +1,3 @@ +export class Node { + constructor(public fileName: string, public parents: Node[] | null, public childs: Node[] | null) {} +} From 67af494232f20beef54e6accc6629528a1ed8a8c Mon Sep 17 00:00:00 2001 From: Odin van der Linden Date: Fri, 28 Oct 2022 13:28:09 +0200 Subject: [PATCH 02/77] GetAllParentReferences signature --- packages/typescript-checker/src/graph/node.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/typescript-checker/src/graph/node.ts b/packages/typescript-checker/src/graph/node.ts index a474d1e476..f051c23840 100644 --- a/packages/typescript-checker/src/graph/node.ts +++ b/packages/typescript-checker/src/graph/node.ts @@ -1,3 +1,7 @@ export class Node { constructor(public fileName: string, public parents: Node[] | null, public childs: Node[] | null) {} + + public GetAllParentReferences(): Node[] { + return []; + } } From b2b99b4082561ad18f74228ad6c3be39053fadd6 Mon Sep 17 00:00:00 2001 From: Thierry Rietveld Date: Fri, 28 Oct 2022 13:30:44 +0200 Subject: [PATCH 03/77] Begin groups --- .../src/graph/create-groups.ts | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 packages/typescript-checker/src/graph/create-groups.ts diff --git a/packages/typescript-checker/src/graph/create-groups.ts b/packages/typescript-checker/src/graph/create-groups.ts new file mode 100644 index 0000000000..aec71f359a --- /dev/null +++ b/packages/typescript-checker/src/graph/create-groups.ts @@ -0,0 +1,65 @@ +import { Mutant } from '@stryker-mutator/api/src/core/index.js'; +import { Node } from './node.js'; + +function createTempGraph(): Node[] { + const nodeA: Node = { + parents: [], + childs: [], + fileName: 'A.js', + }; + + const nodeB: Node = { + parents: [], + childs: [], + fileName: 'B.js', + }; + + const nodeC: Node = { + parents: [], + childs: [], + fileName: 'C.js', + }; + + const nodeD: Node = { + parents: [], + childs: [], + fileName: 'D.js', + }; + + nodeA.childs = [nodeB, nodeC]; + + nodeB.parents = [nodeA]; + + nodeC.childs = [nodeD]; + nodeC.parents = [nodeA]; + + nodeD.parents = [nodeC]; + + return [nodeA, nodeB, nodeC, nodeD]; +} + +const graph: Node = createTempGraph(); + +export function createGroups(mutants: Mutant[], nodes: Node[]): Promise { + const mutantSelector = new MutantSelector(mutants); + const mutant = getNewMutant(mutants); + const negeerlijst: Node[]; + const groep: Node[]; + + const leaf = getLeaf(graph, negeerlijst); + + return graph; +} + +class MutantSelector { + constructor(private mutants: Mutant[]) {}; + + public getNewMutant(): Mutant { + + } +} + +function getNewMutant(mutants: Mutant[]) { + throw new Error('Function not implemented.'); +} + From b63fa0148dcd5342ece63fdba44227b14c8c5cd5 Mon Sep 17 00:00:00 2001 From: Thierry Rietveld Date: Fri, 28 Oct 2022 13:30:56 +0200 Subject: [PATCH 04/77] a --- packages/typescript-checker/src/graph/node.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typescript-checker/src/graph/node.ts b/packages/typescript-checker/src/graph/node.ts index a474d1e476..6f3ed17eb9 100644 --- a/packages/typescript-checker/src/graph/node.ts +++ b/packages/typescript-checker/src/graph/node.ts @@ -1,3 +1,3 @@ export class Node { - constructor(public fileName: string, public parents: Node[] | null, public childs: Node[] | null) {} + constructor(public fileName: string, public parents: Node[], public childs: Node[]) {} } From e87e930c8c1d2bcfd0319a87b383ce30e31f1ec5 Mon Sep 17 00:00:00 2001 From: Odin van der Linden Date: Fri, 28 Oct 2022 13:39:49 +0200 Subject: [PATCH 05/77] getAllParentReferences implementation --- packages/typescript-checker/src/graph/node.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/typescript-checker/src/graph/node.ts b/packages/typescript-checker/src/graph/node.ts index f051c23840..96f8715e87 100644 --- a/packages/typescript-checker/src/graph/node.ts +++ b/packages/typescript-checker/src/graph/node.ts @@ -1,7 +1,14 @@ export class Node { constructor(public fileName: string, public parents: Node[] | null, public childs: Node[] | null) {} - public GetAllParentReferences(): Node[] { - return []; + public getAllParentReferences(): Node[] { + const allParentReferences: Node[] = []; + this.parents?.forEach((parent) => { + const innerParents = parent.getAllParentReferences(); + innerParents.forEach((node) => { + allParentReferences.push(node); + }); + }); + return allParentReferences; } } From 12048fa68d648d15f1e585b30fdbe9a61535718b Mon Sep 17 00:00:00 2001 From: Odin van der Linden Date: Fri, 28 Oct 2022 14:04:20 +0200 Subject: [PATCH 06/77] rename of getAllParentReferences + unit tests --- packages/typescript-checker/src/graph/node.ts | 6 ++--- .../test/unit/graph/node.spec.ts | 25 +++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 packages/typescript-checker/test/unit/graph/node.spec.ts diff --git a/packages/typescript-checker/src/graph/node.ts b/packages/typescript-checker/src/graph/node.ts index 96f8715e87..5f35675233 100644 --- a/packages/typescript-checker/src/graph/node.ts +++ b/packages/typescript-checker/src/graph/node.ts @@ -1,10 +1,10 @@ export class Node { constructor(public fileName: string, public parents: Node[] | null, public childs: Node[] | null) {} - public getAllParentReferences(): Node[] { - const allParentReferences: Node[] = []; + public getAllNodesToIgnore(): Node[] { + const allParentReferences: Node[] = [this]; this.parents?.forEach((parent) => { - const innerParents = parent.getAllParentReferences(); + const innerParents = parent.getAllNodesToIgnore(); innerParents.forEach((node) => { allParentReferences.push(node); }); diff --git a/packages/typescript-checker/test/unit/graph/node.spec.ts b/packages/typescript-checker/test/unit/graph/node.spec.ts new file mode 100644 index 0000000000..2e77dfc9e0 --- /dev/null +++ b/packages/typescript-checker/test/unit/graph/node.spec.ts @@ -0,0 +1,25 @@ +import { expect } from 'chai'; + +import { Node } from '../../../src/graph/node.js'; + +describe('node', () => { + it('getAllNodesToIgnore without parent should return array of 1 node ', () => { + const node = new Node('NodeA', null, null); + expect(node.getAllNodesToIgnore()).to.have.lengthOf(1); + }); + + it('getAllNodesToIgnore with 1 parent should return array of 2 nodes ', () => { + const node = new Node('NodeA', [new Node('', [], [])], null); + expect(node.getAllNodesToIgnore()).to.have.lengthOf(2); + }); + + it('getAllNodesToIgnore with recursive depth of 2 should return 3 nodes ', () => { + const node = new Node('NodeA', [new Node('', [new Node('', [], [])], [])], null); + expect(node.getAllNodesToIgnore()).to.have.lengthOf(3); + }); + + it('getAllNodesToIgnore with recursive depth of 2 and multiple parents should return 4 nodes ', () => { + const node = new Node('NodeA', [new Node('', [new Node('', [], []), new Node('', [], [])], [])], null); + expect(node.getAllNodesToIgnore()).to.have.lengthOf(4); + }); +}); From bfca8b493295c7fdb48b2f09d885244dd70e8b22 Mon Sep 17 00:00:00 2001 From: Odin van der Linden Date: Fri, 28 Oct 2022 14:11:31 +0200 Subject: [PATCH 07/77] method rename --- packages/typescript-checker/src/graph/node.ts | 4 ++-- packages/typescript-checker/test/unit/graph/node.spec.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/typescript-checker/src/graph/node.ts b/packages/typescript-checker/src/graph/node.ts index 5f35675233..5499d593ed 100644 --- a/packages/typescript-checker/src/graph/node.ts +++ b/packages/typescript-checker/src/graph/node.ts @@ -1,10 +1,10 @@ export class Node { constructor(public fileName: string, public parents: Node[] | null, public childs: Node[] | null) {} - public getAllNodesToIgnore(): Node[] { + public getAllParentReferencesIncludingSelf(): Node[] { const allParentReferences: Node[] = [this]; this.parents?.forEach((parent) => { - const innerParents = parent.getAllNodesToIgnore(); + const innerParents = parent.getAllParentReferencesIncludingSelf(); innerParents.forEach((node) => { allParentReferences.push(node); }); diff --git a/packages/typescript-checker/test/unit/graph/node.spec.ts b/packages/typescript-checker/test/unit/graph/node.spec.ts index 2e77dfc9e0..2b32bdb06d 100644 --- a/packages/typescript-checker/test/unit/graph/node.spec.ts +++ b/packages/typescript-checker/test/unit/graph/node.spec.ts @@ -5,21 +5,21 @@ import { Node } from '../../../src/graph/node.js'; describe('node', () => { it('getAllNodesToIgnore without parent should return array of 1 node ', () => { const node = new Node('NodeA', null, null); - expect(node.getAllNodesToIgnore()).to.have.lengthOf(1); + expect(node.getAllParentReferencesIncludingSelf()).to.have.lengthOf(1); }); it('getAllNodesToIgnore with 1 parent should return array of 2 nodes ', () => { const node = new Node('NodeA', [new Node('', [], [])], null); - expect(node.getAllNodesToIgnore()).to.have.lengthOf(2); + expect(node.getAllParentReferencesIncludingSelf()).to.have.lengthOf(2); }); it('getAllNodesToIgnore with recursive depth of 2 should return 3 nodes ', () => { const node = new Node('NodeA', [new Node('', [new Node('', [], [])], [])], null); - expect(node.getAllNodesToIgnore()).to.have.lengthOf(3); + expect(node.getAllParentReferencesIncludingSelf()).to.have.lengthOf(3); }); it('getAllNodesToIgnore with recursive depth of 2 and multiple parents should return 4 nodes ', () => { const node = new Node('NodeA', [new Node('', [new Node('', [], []), new Node('', [], [])], [])], null); - expect(node.getAllNodesToIgnore()).to.have.lengthOf(4); + expect(node.getAllParentReferencesIncludingSelf()).to.have.lengthOf(4); }); }); From d2f0c64ef20b16fc4be82b61e485013d093915b1 Mon Sep 17 00:00:00 2001 From: Thierry Rietveld Date: Fri, 28 Oct 2022 14:36:30 +0200 Subject: [PATCH 08/77] Create helpers --- .../src/graph/create-groups.ts | 40 +++++++------------ .../src/graph/mutant-selector-helpers.ts | 21 ++++++++++ 2 files changed, 36 insertions(+), 25 deletions(-) create mode 100644 packages/typescript-checker/src/graph/mutant-selector-helpers.ts diff --git a/packages/typescript-checker/src/graph/create-groups.ts b/packages/typescript-checker/src/graph/create-groups.ts index ef0e773d9f..4dc056d8be 100644 --- a/packages/typescript-checker/src/graph/create-groups.ts +++ b/packages/typescript-checker/src/graph/create-groups.ts @@ -1,5 +1,7 @@ /* eslint-disable */ import { Mutant } from '@stryker-mutator/api/src/core/index.js'; +import { group } from 'console'; +import { MutantSelectorHelpers } from './mutant-selector-helpers.js'; import { Node } from './node.js'; @@ -52,40 +54,28 @@ function createTempGraph(): Node[] { return [nodeA, nodeB, nodeC, nodeD]; } -const graph: Node = createTempGraph(); - export function createGroups(mutants: Mutant[], nodes: Node[]): Promise { - const mutantSelector: MutantSelectorHelpers = new MutantSelectorHelpers(mutants, nodes); - - let mutant: Mutant | null = mutantSelector.getNewMutant(); + const mutantSelectorHelper: MutantSelectorHelpers = new MutantSelectorHelpers(mutants, nodes); - while (mutant != null) { + let mutant: Mutant | null = mutantSelectorHelper.getNewMutant(); + const groups: Mutant[][] = []; - mutant = mutantSelector.getNewMutant(); + while (mutant != null) { + const group: Mutant = []; + const node = mutantSelectorHelper.selectNode(mutant.fileName); + + if (node === null) throw new Error('Node not in graph'); + + const negeerlijst: Set = node.GetAllParentReferences(); + + groups.push(group); + mutant = mutantSelectorHelper.getNewMutant(); } - const negeerlijst: Node[]; - const groep: Node[]; - const leaf = getLeaf(graph, negeerlijst); return graph; } -class MutantSelectorHelpers { - constructor(private mutants: Mutant[], nodes: Node[]) {}; - - public getNewMutant(): Mutant | null { - return null; - } - - public selectNode(mutant: Mutant) { - - } -} - -function getNewMutant(mutants: Mutant[]) { - throw new Error('Function not implemented.'); -} diff --git a/packages/typescript-checker/src/graph/mutant-selector-helpers.ts b/packages/typescript-checker/src/graph/mutant-selector-helpers.ts new file mode 100644 index 0000000000..11ad04ae11 --- /dev/null +++ b/packages/typescript-checker/src/graph/mutant-selector-helpers.ts @@ -0,0 +1,21 @@ +import { Mutant } from '@stryker-mutator/api/src/core'; + +import { Node } from './node.js'; + +export class MutantSelectorHelpers { + constructor(private readonly mutants: Mutant[], private readonly nodes: Node[]) {}; + + public getNewMutant(): Mutant | null { + const mutant = this.mutants[0]; + this.mutants.splice(0, 1); + return mutant; + } + + public selectNode(fileName: string): Node | null { + for (const node of this.nodes) { + if (node.fileName === fileName) return node; + } + + return null; + } +} From 21cdb7e743d006a8ed4384a3365a766cef0db45a Mon Sep 17 00:00:00 2001 From: Odin van der Linden Date: Fri, 28 Oct 2022 14:40:33 +0200 Subject: [PATCH 09/77] replaced null with empty array --- packages/typescript-checker/test/unit/graph/node.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/typescript-checker/test/unit/graph/node.spec.ts b/packages/typescript-checker/test/unit/graph/node.spec.ts index 2b32bdb06d..a51dc45bd0 100644 --- a/packages/typescript-checker/test/unit/graph/node.spec.ts +++ b/packages/typescript-checker/test/unit/graph/node.spec.ts @@ -4,22 +4,22 @@ import { Node } from '../../../src/graph/node.js'; describe('node', () => { it('getAllNodesToIgnore without parent should return array of 1 node ', () => { - const node = new Node('NodeA', null, null); + const node = new Node('NodeA', [], []); expect(node.getAllParentReferencesIncludingSelf()).to.have.lengthOf(1); }); it('getAllNodesToIgnore with 1 parent should return array of 2 nodes ', () => { - const node = new Node('NodeA', [new Node('', [], [])], null); + const node = new Node('NodeA', [new Node('', [], [])], []); expect(node.getAllParentReferencesIncludingSelf()).to.have.lengthOf(2); }); it('getAllNodesToIgnore with recursive depth of 2 should return 3 nodes ', () => { - const node = new Node('NodeA', [new Node('', [new Node('', [], [])], [])], null); + const node = new Node('NodeA', [new Node('', [new Node('', [], [])], [])], []); expect(node.getAllParentReferencesIncludingSelf()).to.have.lengthOf(3); }); it('getAllNodesToIgnore with recursive depth of 2 and multiple parents should return 4 nodes ', () => { - const node = new Node('NodeA', [new Node('', [new Node('', [], []), new Node('', [], [])], [])], null); + const node = new Node('NodeA', [new Node('', [new Node('', [], []), new Node('', [], [])], [])], []); expect(node.getAllParentReferencesIncludingSelf()).to.have.lengthOf(4); }); }); From d43dd95375c31af5b740191bd4ded9b0d54f6eca Mon Sep 17 00:00:00 2001 From: Odin van der Linden Date: Fri, 28 Oct 2022 14:52:12 +0200 Subject: [PATCH 10/77] temp mutants --- packages/typescript-checker/src/graph/create-groups.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/typescript-checker/src/graph/create-groups.ts b/packages/typescript-checker/src/graph/create-groups.ts index 4dc056d8be..13b55acaea 100644 --- a/packages/typescript-checker/src/graph/create-groups.ts +++ b/packages/typescript-checker/src/graph/create-groups.ts @@ -78,4 +78,14 @@ export function createGroups(mutants: Mutant[], nodes: Node[]): Promise Date: Fri, 28 Oct 2022 14:57:59 +0200 Subject: [PATCH 11/77] Hier joh --- .../src/{graph => grouping}/create-groups.ts | 70 ++++++++----------- .../mutant-selector-helpers.ts | 0 .../src/{graph => grouping}/node.ts | 0 .../test/unit/graph/node.spec.ts | 2 +- 4 files changed, 29 insertions(+), 43 deletions(-) rename packages/typescript-checker/src/{graph => grouping}/create-groups.ts (50%) rename packages/typescript-checker/src/{graph => grouping}/mutant-selector-helpers.ts (100%) rename packages/typescript-checker/src/{graph => grouping}/node.ts (100%) diff --git a/packages/typescript-checker/src/graph/create-groups.ts b/packages/typescript-checker/src/grouping/create-groups.ts similarity index 50% rename from packages/typescript-checker/src/graph/create-groups.ts rename to packages/typescript-checker/src/grouping/create-groups.ts index 4dc056d8be..89dadc55df 100644 --- a/packages/typescript-checker/src/graph/create-groups.ts +++ b/packages/typescript-checker/src/grouping/create-groups.ts @@ -6,41 +6,28 @@ import { MutantSelectorHelpers } from './mutant-selector-helpers.js'; import { Node } from './node.js'; function createTempGraph(): Node[] { - const nodeA: Node = { - parents: [], - childs: [], - fileName: 'A.js', - GetAllParentReferences() { - return []; - }, - }; - - const nodeB: Node = { - parents: [], - childs: [], - fileName: 'B.js', - GetAllParentReferences() { - return []; - }, - }; - - const nodeC: Node = { - parents: [], - childs: [], - fileName: 'C.js', - GetAllParentReferences() { - return []; - }, - }; - - const nodeD: Node = { - parents: [], - childs: [], - fileName: 'D.js', - GetAllParentReferences() { - return []; - }, - }; + const nodeA: Node = new Node('a.js', [], []); + const nodeB: Node = new Node('b.js', [], []); + const nodeC: Node = new Node('c.js', [], []); + const nodeD: Node = new Node('d.js', [], []); + + nodeA.childs = [nodeB, nodeC]; + + nodeB.parents = [nodeA]; + + nodeC.childs = [nodeD]; + nodeC.parents = [nodeA]; + + nodeD.parents = [nodeC]; + + return [nodeA, nodeB, nodeC, nodeD]; +} + +function createTempMutents(): Node[] { + const nodeA: Node = new Node('a.js', [], []); + const nodeB: Node = new Node('b.js', [], []); + const nodeC: Node = new Node('c.js', [], []); + const nodeD: Node = new Node('d.js', [], []); nodeA.childs = [nodeB, nodeC]; @@ -57,25 +44,24 @@ function createTempGraph(): Node[] { export function createGroups(mutants: Mutant[], nodes: Node[]): Promise { const mutantSelectorHelper: MutantSelectorHelpers = new MutantSelectorHelpers(mutants, nodes); - let mutant: Mutant | null = mutantSelectorHelper.getNewMutant(); + let mutant: Mutant | null = mutants.splice(0, 1)[0] ?? null; const groups: Mutant[][] = []; while (mutant != null) { - const group: Mutant = []; + const mutantCopy = [...mutants]; + const group: Mutant[] = []; const node = mutantSelectorHelper.selectNode(mutant.fileName); if (node === null) throw new Error('Node not in graph'); - const negeerlijst: Set = node.GetAllParentReferences(); + const nodesToIgnore: Set = node.getAllParentReferencesIncludingSelf(); groups.push(group); - mutant = mutantSelectorHelper.getNewMutant(); + mutant = mutants.splice(0, 1)[0] ?? null; } - const leaf = getLeaf(graph, negeerlijst); - return graph; } - +createGroups(); diff --git a/packages/typescript-checker/src/graph/mutant-selector-helpers.ts b/packages/typescript-checker/src/grouping/mutant-selector-helpers.ts similarity index 100% rename from packages/typescript-checker/src/graph/mutant-selector-helpers.ts rename to packages/typescript-checker/src/grouping/mutant-selector-helpers.ts diff --git a/packages/typescript-checker/src/graph/node.ts b/packages/typescript-checker/src/grouping/node.ts similarity index 100% rename from packages/typescript-checker/src/graph/node.ts rename to packages/typescript-checker/src/grouping/node.ts diff --git a/packages/typescript-checker/test/unit/graph/node.spec.ts b/packages/typescript-checker/test/unit/graph/node.spec.ts index 2b32bdb06d..2d5316eebc 100644 --- a/packages/typescript-checker/test/unit/graph/node.spec.ts +++ b/packages/typescript-checker/test/unit/graph/node.spec.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; -import { Node } from '../../../src/graph/node.js'; +import { Node } from '../../../src/grouping/node.js'; describe('node', () => { it('getAllNodesToIgnore without parent should return array of 1 node ', () => { From 19290fd9f6c94a06e4630403073c505bda4e7e95 Mon Sep 17 00:00:00 2001 From: Danny Berkelaar Date: Fri, 28 Oct 2022 15:14:17 +0200 Subject: [PATCH 12/77] Update selectNode to findNode --- .../src/graph/mutant-selector-helpers.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/typescript-checker/src/graph/mutant-selector-helpers.ts b/packages/typescript-checker/src/graph/mutant-selector-helpers.ts index 11ad04ae11..828bb8ff13 100644 --- a/packages/typescript-checker/src/graph/mutant-selector-helpers.ts +++ b/packages/typescript-checker/src/graph/mutant-selector-helpers.ts @@ -10,12 +10,12 @@ export class MutantSelectorHelpers { this.mutants.splice(0, 1); return mutant; } +} - public selectNode(fileName: string): Node | null { - for (const node of this.nodes) { - if (node.fileName === fileName) return node; - } - - return null; +export function findNode(fileName: string, nodes: Node[]): Node | null { + for (const node of nodes) { + if (node.fileName === fileName) return node; } + + return null; } From a9384ae05342f520abc439c0e5f972ddfe6d58c3 Mon Sep 17 00:00:00 2001 From: Thierry Rietveld Date: Fri, 28 Oct 2022 15:15:15 +0200 Subject: [PATCH 13/77] halve --- .../src/grouping/create-groups.ts | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/typescript-checker/src/grouping/create-groups.ts b/packages/typescript-checker/src/grouping/create-groups.ts index ab09bccf2c..57e5089b9c 100644 --- a/packages/typescript-checker/src/grouping/create-groups.ts +++ b/packages/typescript-checker/src/grouping/create-groups.ts @@ -44,21 +44,27 @@ function createTempMutents(): Node[] { export function createGroups(mutants: Mutant[], nodes: Node[]): Promise { const mutantSelectorHelper: MutantSelectorHelpers = new MutantSelectorHelpers(mutants, nodes); - let mutant: Mutant | null = mutants.splice(0, 1)[0] ?? null; - const groups: Mutant[][] = []; + let mutant: Mutant | null = selectNewMutant(mutans, groups); + while (mutant != null) { const mutantCopy = [...mutants]; - const group: Mutant[] = []; + const group: Mutant[] = [mutant]; const node = mutantSelectorHelper.selectNode(mutant.fileName); if (node === null) throw new Error('Node not in graph'); - + const nodesToIgnore: Set = node.getAllParentReferencesIncludingSelf(); + + for (const mutantSelected of mutantCopy) { + + } + + groups.push(group); - mutant = mutants.splice(0, 1)[0] ?? null; + mutant = selectNewMutant(mutans, groups); } return graph; @@ -73,3 +79,7 @@ function createTempMutants(): Mutant[]{ {fileName: 'A.js', replacement: '', id: '5', location: {start: {line:1, column:1}, end: {line:1, column:1}}, mutatorName: 'test'}, ] } + +function selectNewMutant(mutans: Mutants[], groups: Mutant[][]): Mutant | null { + throw new Error('Function not implemented.'); +} From f141542fbe7421c047768f6a469aea7443f76f00 Mon Sep 17 00:00:00 2001 From: Odin van der Linden Date: Fri, 28 Oct 2022 15:35:12 +0200 Subject: [PATCH 14/77] resolved circular recursion --- packages/typescript-checker/src/graph/node.ts | 16 ++++++++++------ .../test/unit/graph/node.spec.ts | 16 ++++++++++++---- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/packages/typescript-checker/src/graph/node.ts b/packages/typescript-checker/src/graph/node.ts index ae63f7c4d7..62dc481dfa 100644 --- a/packages/typescript-checker/src/graph/node.ts +++ b/packages/typescript-checker/src/graph/node.ts @@ -1,13 +1,17 @@ export class Node { constructor(public fileName: string, public parents: Node[], public childs: Node[]) {} - public getAllParentReferencesIncludingSelf(): Node[] { - const allParentReferences: Node[] = [this]; + public getAllParentReferencesIncludingSelf(allParentReferences: Set = new Set()): Set { + allParentReferences.add(this); this.parents?.forEach((parent) => { - const innerParents = parent.getAllParentReferencesIncludingSelf(); - innerParents.forEach((node) => { - allParentReferences.push(node); - }); + if (!allParentReferences.has(parent)) { + const innerParents = parent.getAllParentReferencesIncludingSelf(allParentReferences); + innerParents.forEach((node) => { + if (!allParentReferences.has(node)) { + allParentReferences.add(node); + } + }); + } }); return allParentReferences; } diff --git a/packages/typescript-checker/test/unit/graph/node.spec.ts b/packages/typescript-checker/test/unit/graph/node.spec.ts index a51dc45bd0..b65034a375 100644 --- a/packages/typescript-checker/test/unit/graph/node.spec.ts +++ b/packages/typescript-checker/test/unit/graph/node.spec.ts @@ -3,23 +3,31 @@ import { expect } from 'chai'; import { Node } from '../../../src/graph/node.js'; describe('node', () => { - it('getAllNodesToIgnore without parent should return array of 1 node ', () => { + it('getAllParentReferencesIncludingSelf without parent should return array of 1 node ', () => { const node = new Node('NodeA', [], []); expect(node.getAllParentReferencesIncludingSelf()).to.have.lengthOf(1); }); - it('getAllNodesToIgnore with 1 parent should return array of 2 nodes ', () => { + it('getAllParentReferencesIncludingSelf with 1 parent should return array of 2 nodes ', () => { const node = new Node('NodeA', [new Node('', [], [])], []); expect(node.getAllParentReferencesIncludingSelf()).to.have.lengthOf(2); }); - it('getAllNodesToIgnore with recursive depth of 2 should return 3 nodes ', () => { + it('getAllParentReferencesIncludingSelf with recursive depth of 2 should return 3 nodes ', () => { const node = new Node('NodeA', [new Node('', [new Node('', [], [])], [])], []); expect(node.getAllParentReferencesIncludingSelf()).to.have.lengthOf(3); }); - it('getAllNodesToIgnore with recursive depth of 2 and multiple parents should return 4 nodes ', () => { + it('getAllParentReferencesIncludingSelf with recursive depth of 2 and multiple parents should return 4 nodes ', () => { const node = new Node('NodeA', [new Node('', [new Node('', [], []), new Node('', [], [])], [])], []); expect(node.getAllParentReferencesIncludingSelf()).to.have.lengthOf(4); }); + + it('getAllParentReferencesIncludingSelf with circular dependency should skip circular dependency node ', () => { + const nodeA = new Node('NodeA', [], []); + const nodeC = new Node('NodeB', [nodeA], []); + const nodeB = new Node('NodeB', [nodeC], []); + nodeA.parents.push(nodeB); + expect(nodeA.getAllParentReferencesIncludingSelf()).to.have.lengthOf(3); + }); }); From 7bebab3fd190499986b58bd66e8c6e441e92bc1d Mon Sep 17 00:00:00 2001 From: Odin van der Linden Date: Fri, 28 Oct 2022 15:38:15 +0200 Subject: [PATCH 15/77] removed not used code --- packages/typescript-checker/src/grouping/node.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/typescript-checker/src/grouping/node.ts b/packages/typescript-checker/src/grouping/node.ts index 62dc481dfa..c2eac75b14 100644 --- a/packages/typescript-checker/src/grouping/node.ts +++ b/packages/typescript-checker/src/grouping/node.ts @@ -5,12 +5,7 @@ export class Node { allParentReferences.add(this); this.parents?.forEach((parent) => { if (!allParentReferences.has(parent)) { - const innerParents = parent.getAllParentReferencesIncludingSelf(allParentReferences); - innerParents.forEach((node) => { - if (!allParentReferences.has(node)) { - allParentReferences.add(node); - } - }); + parent.getAllParentReferencesIncludingSelf(allParentReferences); } }); return allParentReferences; From d7eec3977126b5d2aa43f19d9c999497be812a7f Mon Sep 17 00:00:00 2001 From: Thierry Rietveld Date: Fri, 28 Oct 2022 15:40:46 +0200 Subject: [PATCH 16/77] Created createGroups --- .../src/grouping/create-groups.ts | 54 +++++++++++++------ 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/packages/typescript-checker/src/grouping/create-groups.ts b/packages/typescript-checker/src/grouping/create-groups.ts index 57e5089b9c..4fd13eebb5 100644 --- a/packages/typescript-checker/src/grouping/create-groups.ts +++ b/packages/typescript-checker/src/grouping/create-groups.ts @@ -1,7 +1,7 @@ /* eslint-disable */ import { Mutant } from '@stryker-mutator/api/src/core/index.js'; import { group } from 'console'; -import { MutantSelectorHelpers } from './mutant-selector-helpers.js'; +import { findNode, MutantSelectorHelpers } from './mutant-selector-helpers.js'; import { Node } from './node.js'; @@ -42,35 +42,46 @@ function createTempMutents(): Node[] { } export function createGroups(mutants: Mutant[], nodes: Node[]): Promise { - const mutantSelectorHelper: MutantSelectorHelpers = new MutantSelectorHelpers(mutants, nodes); - const groups: Mutant[][] = []; - let mutant: Mutant | null = selectNewMutant(mutans, groups); + let mutant: Mutant | null = selectNewMutant(mutants, groups); + // Loop unil all the mutants are in a group while (mutant != null) { - const mutantCopy = [...mutants]; + // Copy the list of mutants who are not in a group + const mutantWhoAreNotInAGroup = [...mutants]; + // The first mutant is always in a group const group: Mutant[] = [mutant]; - const node = mutantSelectorHelper.selectNode(mutant.fileName); - + const node = findNode(mutant.fileName, nodes); + if (node === null) throw new Error('Node not in graph'); - const nodesToIgnore: Set = node.getAllParentReferencesIncludingSelf(); + // Fill the ignorelist + let nodesToIgnore: Set = new Set(node.getAllParentReferencesIncludingSelf()); + + // Loop through the nodes who can possibly go in the group + for (const mutantSelected of mutantWhoAreNotInAGroup) { + let nodeSelected = findNode(mutantSelected.fileName, nodes); + + if (nodeSelected === null) throw new Error('Node not in graph'); + + // See if the node can be in the group + if (nodesToIgnore.has(nodeSelected)) continue; - for (const mutantSelected of mutantCopy) { - + // Push the group + group.push(mutantSelected); + // Add to the ignorelist + nodesToIgnore = new Set([...nodesToIgnore, ...nodeSelected.getAllParentReferencesIncludingSelf()]); } - - groups.push(group); - mutant = selectNewMutant(mutans, groups); + mutant = selectNewMutant(mutants, groups); } - return graph; + return Promise.resolve(groupsToString(groups)); } -function createTempMutants(): Mutant[]{ +function createTempMutants(): Mutant[] { return [ {fileName: 'A.js', replacement: '', id: '1', location: {start: {line:1, column:1}, end: {line:1, column:1}}, mutatorName: 'test'}, {fileName: 'B.js', replacement: '', id: '2', location: {start: {line:1, column:1}, end: {line:1, column:1}}, mutatorName: 'test'}, @@ -80,6 +91,15 @@ function createTempMutants(): Mutant[]{ ] } -function selectNewMutant(mutans: Mutants[], groups: Mutant[][]): Mutant | null { - throw new Error('Function not implemented.'); +function selectNewMutant(mutants: Mutant[], groups: Mutant[][]): Mutant | null { + const flatGroups = groups.flat(); + for (const mutant of mutants) { + if (!flatGroups.includes(mutant)) return mutant; + } + return null; +} + +function groupsToString(groups: Mutant[][]): string[][] { + return groups.map(group => group.map(mutant => mutant.fileName)); } + From a53120ca5dbad5c3e2c042636963b3bb733b0c18 Mon Sep 17 00:00:00 2001 From: Thierry Rietveld Date: Fri, 28 Oct 2022 15:45:19 +0200 Subject: [PATCH 17/77] Fixed selectNewMutant --- .../typescript-checker/src/grouping/create-groups.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/typescript-checker/src/grouping/create-groups.ts b/packages/typescript-checker/src/grouping/create-groups.ts index 4fd13eebb5..acb38875e4 100644 --- a/packages/typescript-checker/src/grouping/create-groups.ts +++ b/packages/typescript-checker/src/grouping/create-groups.ts @@ -93,9 +93,14 @@ function createTempMutants(): Mutant[] { function selectNewMutant(mutants: Mutant[], groups: Mutant[][]): Mutant | null { const flatGroups = groups.flat(); - for (const mutant of mutants) { - if (!flatGroups.includes(mutant)) return mutant; + + for (let i = 0; i < mutants.length; i++) { + if (!flatGroups.includes(mutants[i])) { + mutants.splice(i, 1) + return mutants[i]; + } } + return null; } From c2f801ec51f62b2082d64c79473fb52f303d0544 Mon Sep 17 00:00:00 2001 From: Danny Berkelaar Date: Fri, 28 Oct 2022 16:19:16 +0200 Subject: [PATCH 18/77] Changed ts compiler --- .../typescript-checker/src/plugin-tokens.ts | 1 + .../src/typescript-checker.ts | 210 ++++++----------- .../src/typescript-compiler.ts | 220 ++++++++++++++++++ 3 files changed, 286 insertions(+), 145 deletions(-) create mode 100644 packages/typescript-checker/src/typescript-compiler.ts diff --git a/packages/typescript-checker/src/plugin-tokens.ts b/packages/typescript-checker/src/plugin-tokens.ts index ecb64f9a01..7e49d56de0 100644 --- a/packages/typescript-checker/src/plugin-tokens.ts +++ b/packages/typescript-checker/src/plugin-tokens.ts @@ -1 +1,2 @@ export const fs = 'fs'; +export const tsCompiler = 'tsCompiler'; diff --git a/packages/typescript-checker/src/typescript-checker.ts b/packages/typescript-checker/src/typescript-checker.ts index 9732364664..e6ba3f6712 100644 --- a/packages/typescript-checker/src/typescript-checker.ts +++ b/packages/typescript-checker/src/typescript-checker.ts @@ -1,24 +1,12 @@ -import { EOL } from 'os'; -import path from 'path'; - import ts from 'typescript'; import { Checker, CheckResult, CheckStatus } from '@stryker-mutator/api/check'; import { tokens, commonTokens, PluginContext, Injector, Scope } from '@stryker-mutator/api/plugin'; import { Logger, LoggerFactoryMethod } from '@stryker-mutator/api/logging'; -import { Task, propertyPath } from '@stryker-mutator/util'; import { Mutant, StrykerOptions } from '@stryker-mutator/api/core'; import { HybridFileSystem } from './fs/index.js'; -import { determineBuildModeEnabled, overrideOptions, retrieveReferencedProjects, guardTSVersion, toPosixFileName } from './tsconfig-helpers.js'; import * as pluginTokens from './plugin-tokens.js'; - -const diagnosticsHost: ts.FormatDiagnosticsHost = { - getCanonicalFileName: (fileName) => fileName, - getCurrentDirectory: process.cwd, - getNewLine: () => EOL, -}; - -const FILE_CHANGE_DETECTED_DIAGNOSTIC_CODE = 6032; +import { TypescriptCompiler } from './typescript-compiler.js'; typescriptCheckerLoggerFactory.inject = tokens(commonTokens.getLogger, commonTokens.target); // eslint-disable-next-line @typescript-eslint/ban-types @@ -33,6 +21,7 @@ export function create(injector: Injector): TypescriptChecker { return injector .provideFactory(commonTokens.logger, typescriptCheckerLoggerFactory, Scope.Transient) .provideClass(pluginTokens.fs, HybridFileSystem) + .provideClass(pluginTokens.tsCompiler, TypescriptCompiler) .injectClass(TypescriptChecker); } @@ -40,93 +29,30 @@ export function create(injector: Injector): TypescriptChecker { * An in-memory type checker implementation which validates type errors of mutants. */ export class TypescriptChecker implements Checker { - private currentTask = new Task(); private readonly currentErrors: ts.Diagnostic[] = []; /** * Keep track of all tsconfig files which are read during compilation (for project references) */ - private readonly allTSConfigFiles: Set; - public static inject = tokens(commonTokens.logger, commonTokens.options, pluginTokens.fs); - private readonly tsconfigFile: string; + public static inject = tokens(commonTokens.logger, commonTokens.options, pluginTokens.fs, pluginTokens.tsCompiler); - constructor(private readonly logger: Logger, options: StrykerOptions, private readonly fs: HybridFileSystem) { - this.tsconfigFile = toPosixFileName(options.tsconfigFile); - this.allTSConfigFiles = new Set([path.resolve(this.tsconfigFile)]); - } + constructor( + private readonly logger: Logger, + options: StrykerOptions, + private readonly fs: HybridFileSystem, + private readonly tsCompiler: TypescriptCompiler + ) { } /** * Starts the typescript compiler and does a dry run */ public async init(): Promise { - guardTSVersion(); - this.guardTSConfigFileExists(); - this.currentTask = new Task(); - const buildModeEnabled = determineBuildModeEnabled(this.tsconfigFile); - const compiler = ts.createSolutionBuilderWithWatch( - ts.createSolutionBuilderWithWatchHost( - { - ...ts.sys, - readFile: (fileName) => { - const content = this.fs.getFile(fileName)?.content; - if (content && this.allTSConfigFiles.has(path.resolve(fileName))) { - return this.adjustTSConfigFile(fileName, content, buildModeEnabled); - } - return content; - }, - watchFile: (filePath: string, callback: ts.FileWatcherCallback) => { - this.fs.watchFile(filePath, callback); - return { - close: () => { - delete this.fs.getFile(filePath)!.watcher; - }, - }; - }, - writeFile: (filePath, data) => { - this.fs.writeFile(filePath, data); - }, - createDirectory: () => { - // Idle, no need to create directories in the hybrid fs - }, - clearScreen() { - // idle, never clear the screen - }, - getModifiedTime: (fileName) => { - return this.fs.getFile(fileName)?.modifiedTime; - }, - watchDirectory: (): ts.FileWatcher => { - // this is used to see if new files are added to a directory. Can safely be ignored for mutation testing. - return { - // eslint-disable-next-line @typescript-eslint/no-empty-function - close() {}, - }; - }, - }, - undefined, - (error) => this.currentErrors.push(error), - (status) => this.logDiagnostic('status')(status), - (summary) => { - this.logDiagnostic('summary')(summary); - summary.code !== FILE_CHANGE_DETECTED_DIAGNOSTIC_CODE && this.resolveCheckResult(); - } - ), - [this.tsconfigFile], - {} - ); - compiler.build(); - const result = await this.currentTask.promise; - if (result.status === CheckStatus.CompileError) { - throw new Error(`TypeScript error(s) found in dry run compilation: ${result.reason}`); - } - } + const errors = await this.tsCompiler.init(); - private guardTSConfigFileExists() { - if (!ts.sys.fileExists(this.tsconfigFile)) { - throw new Error( - `The tsconfig file does not exist at: "${path.resolve( - this.tsconfigFile - )}". Please configure the tsconfig file in your stryker.conf file using "${propertyPath()('tsconfigFile')}"` - ); + if (errors.length) { + // todo + // throw new Error(`TypeScript error(s) found in dry run compilation: ${this.formatErrors(errors)}`); + throw new Error(`TypeScript error(s) found in dry run compilation: ${errors.length}`); } } @@ -136,73 +62,67 @@ export class TypescriptChecker implements Checker { * @param mutant The mutant to check */ public async check(mutants: Mutant[]): Promise> { - const mutant = mutants[0]; - - if (this.fs.existsInMemory(mutant.fileName)) { - this.clearCheckState(); - this.fs.mutate(mutant); + const errors = await this.tsCompiler.check(mutants); + this.logger.info(`Found errors: ${errors.length}`); - return { - [mutant.id]: await this.currentTask.promise, + const result: Record = {}; + mutants.forEach((mutant) => { + result[mutant.id] = { + status: CheckStatus.Passed, }; - } else { - // We allow people to mutate files that are not included in this ts project - return { - [mutant.id]: { - status: CheckStatus.Passed, - }, + }); + errors.forEach((error) => { + return; + const mutant = mutants.find((m) => m.fileName == error.file!.fileName); + result[mutant!.id] = { + status: CheckStatus.CompileError, + reason: 'todo', }; - } + }); + + return result; + + // const mutant = mutants[0]; + + // if (this.fs.existsInMemory(mutant.fileName)) { + // this.fs.mutate(mutant); + + // return { + // [mutant.id]: { + // status: CheckStatus.Passed, + // }, + // }; + // } else { + // // We allow people to mutate files that are not included in this ts project + // return { + // [mutant.id]: { + // status: CheckStatus.Passed, + // }, + // }; + // } } - /** - * Post processes the content of a tsconfig file. Adjusts some options for speed and alters quality options. - * @param fileName The tsconfig file name - * @param content The tsconfig content - * @param buildModeEnabled Whether or not `--build` mode is used - */ - private adjustTSConfigFile(fileName: string, content: string, buildModeEnabled: boolean) { - const parsedConfig = ts.parseConfigFileTextToJson(fileName, content); - if (parsedConfig.error) { - return content; // let the ts compiler deal with this error - } else { - for (const referencedProject of retrieveReferencedProjects(parsedConfig, path.dirname(fileName))) { - this.allTSConfigFiles.add(referencedProject); - } - return overrideOptions(parsedConfig, buildModeEnabled); - } + public async group?(mutants: Mutant[]): Promise { + await this.tsCompiler.check(mutants); + const nodes = this.tsCompiler.getFileRelation(); + return mutants.map((m) => [m.id]); } /** * Resolves the task that is currently running. Will report back the check result. */ private resolveCheckResult(): void { - if (this.currentErrors.length) { - const errorText = ts.formatDiagnostics(this.currentErrors, { - getCanonicalFileName: (fileName) => fileName, - getCurrentDirectory: process.cwd, - getNewLine: () => EOL, - }); - this.currentTask.resolve({ - status: CheckStatus.CompileError, - reason: errorText, - }); - } - this.currentTask.resolve({ status: CheckStatus.Passed }); - } - - /** - * Clear state between checks - */ - private clearCheckState() { - while (this.currentErrors.pop()) { - // Idle - } - this.currentTask = new Task(); + // if (this.currentErrors.length) { + // const errorText = ts.formatDiagnostics(this.currentErrors, { + // getCanonicalFileName: (fileName) => fileName, + // getCurrentDirectory: process.cwd, + // getNewLine: () => EOL, + // }); + // this.currentTask.resolve({ + // status: CheckStatus.CompileError, + // reason: errorText, + // }); + // } + // this.currentTask.resolve({ status: CheckStatus.Passed }); } - private readonly logDiagnostic = (label: string) => { - return (d: ts.Diagnostic) => { - this.logger.trace(`${label} ${ts.formatDiagnostics([d], diagnosticsHost)}`); - }; - }; } diff --git a/packages/typescript-checker/src/typescript-compiler.ts b/packages/typescript-checker/src/typescript-compiler.ts new file mode 100644 index 0000000000..ba071b6962 --- /dev/null +++ b/packages/typescript-checker/src/typescript-compiler.ts @@ -0,0 +1,220 @@ +import path from 'path'; + +import ts from 'typescript'; +import { propertyPath, Task } from '@stryker-mutator/util'; +import { Mutant, StrykerOptions } from '@stryker-mutator/api/core'; +import { Logger } from '@stryker-mutator/api/logging'; + +import { tokens, commonTokens } from '@stryker-mutator/api/plugin'; + +import { HybridFileSystem } from './fs/index.js'; +import { determineBuildModeEnabled, guardTSVersion, overrideOptions, retrieveReferencedProjects, toPosixFileName } from './tsconfig-helpers.js'; +import { Node } from './grouping/node.js'; +import * as pluginTokens from './plugin-tokens.js'; +import { findNode } from './grouping/mutant-selector-helpers.js'; + +export interface ITypescriptCompiler { + init(): Promise; + check(mutants: Mutant[]): Promise; // todo set return type + getFileRelation(): Node[]; +} + +export interface IFileRelationCreator { + getFileRelation(): void; +} + +export type SourceFiles = Map< + string, + { + fileName: string; + imports: Set; + } +>; +const FILE_CHANGE_DETECTED_DIAGNOSTIC_CODE = 6032; + +export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCreator { + public static inject = tokens(commonTokens.logger, commonTokens.options, pluginTokens.fs); + + private readonly allTSConfigFiles: Set; + private readonly tsconfigFile: string; + private currentTask = new Task(); + private currentErrors: ts.Diagnostic[] = []; + private readonly sourceFiles: SourceFiles = new Map(); + + constructor(private readonly log: Logger, private readonly options: StrykerOptions, private readonly fs: HybridFileSystem) { + this.tsconfigFile = toPosixFileName(this.options.tsconfigFile); + this.allTSConfigFiles = new Set([path.resolve(this.tsconfigFile)]); + } + + public async init(): Promise { + guardTSVersion(); + this.guardTSConfigFileExists(); + const buildModeEnabled = determineBuildModeEnabled(this.tsconfigFile); + + const host = ts.createSolutionBuilderWithWatchHost( + { + ...ts.sys, + readFile: (fileName) => { + const content = this.fs.getFile(fileName)?.content; + if (content && this.allTSConfigFiles.has(path.resolve(fileName))) { + return this.adjustTSConfigFile(fileName, content, buildModeEnabled); + } + return content; + }, + fileExists: (filePath: string) => { + // We want to ignore the buildinfo files. With them the compiler skips the program part we want to use. + if (filePath.endsWith('.tsbuildinfo')) { + return false; + } + return ts.sys.fileExists(filePath); + }, + getModifiedTime: (pathName: string) => { + return this.fs.getFile(pathName)?.modifiedTime; + }, + watchFile: (filePath: string, callback: ts.FileWatcherCallback) => { + const file = this.fs.getFile(filePath); + + if (file) file.watcher = callback; + + return { + close: () => { + delete this.fs.getFile(filePath)!.watcher; + }, + }; + }, + writeFile: (fileName, data) => { + this.fs.writeFile(fileName, data); + }, + watchDirectory: (): ts.FileWatcher => { + // this is used to see if new files are added to a directory. Can safely be ignored for mutation testing. + return { + // eslint-disable-next-line @typescript-eslint/no-empty-function + close() {}, + }; + }, + }, + (...args) => { + const program = ts.createEmitAndSemanticDiagnosticsBuilderProgram(...args); + program + .getSourceFiles() + .filter(filterDependency) + .forEach((file) => { + this.sourceFiles.set(file.fileName, { + fileName: toPosixFileName(file.fileName), + imports: new Set( + program + .getAllDependencies(file) + .filter((importFile) => !importFile.includes('/node_modules/') && file.fileName !== importFile) + .flatMap((importFile) => this.resolveFilename(importFile)) + ), + }); + }); + + function filterDependency(file: ts.SourceFile) { + if (file.fileName.includes('.d.ts')) return false; + if (file.fileName.includes('node_modules')) return false; + return true; + } + + return program; + }, + (error) => { + this.currentErrors.push(error); + }, + (status) => { + this.log.debug(status.messageText.toString()); + }, + (summary) => { + summary.code !== FILE_CHANGE_DETECTED_DIAGNOSTIC_CODE && this.currentTask.resolve(); + } + ); + + const compiler = ts.createSolutionBuilderWithWatch(host, [this.tsconfigFile], {}); + compiler.build(); + const errors = await this.check([]); + return errors; + } + + public async check(mutants: Mutant[]): Promise { + mutants.forEach((mutant) => this.fs.getFile(mutant.fileName)?.mutate(mutant)); + await this.currentTask.promise; + + // todo make this better? + const errors = [...this.currentErrors]; + this.currentTask = new Task(); + this.currentErrors = []; + mutants.forEach((mutant) => this.fs.getFile(mutant.fileName)?.resetMutant()); + return errors; + } + + public getFileRelation(): Node[] { + const nodes: Node[] = []; + + // create nodes + for (const [fileName] of this.sourceFiles) { + const node = new Node(fileName, [], []); + nodes.push(node); + } + + // set imports + for (const [fileName, file] of this.sourceFiles) { + const node = findNode(fileName, nodes); + if (node == null) { + throw new Error('todo'); + } + const importFileNames = [...file.imports]; + node.childs = nodes.filter((n) => importFileNames.includes(n.fileName)); + } + + // todo set parents + for (const node of nodes) { + node.parents = nodes.filter((n) => n.childs?.includes(node)); // todo remove ? when childs isnt nullable + } + + return nodes; + } + + private resolveFilename(fileName: string): string[] { + if (!fileName.includes('.d.ts')) return [fileName]; + + const file = this.fs.getFile(fileName); + if (!file) throw new Error(`Could not find ${fileName}`); + const sourceMappingURL = this.getSourceMappingURL(file.content); + + if (!sourceMappingURL) return [fileName]; + + const sourceMapFileName = path.resolve(fileName, '..', sourceMappingURL); + const sourceMap = this.fs.getFile(sourceMapFileName); + if (!sourceMap) throw new Error(`Could not find ${sourceMapFileName}`); + + const content = JSON.parse(sourceMap.content); + + return content.sources.map((sourcePath: string) => toPosixFileName(path.resolve(sourceMappingURL, '..', sourcePath))); + } + + private getSourceMappingURL(content: string): string | undefined { + return /\/\/# sourceMappingURL=(.+)$/.exec(content)?.[1]; + } + + private adjustTSConfigFile(fileName: string, content: string, buildModeEnabled: boolean) { + const parsedConfig = ts.parseConfigFileTextToJson(fileName, content); + if (parsedConfig.error) { + return content; // let the ts compiler deal with this error + } else { + for (const referencedProject of retrieveReferencedProjects(parsedConfig, path.dirname(fileName))) { + this.allTSConfigFiles.add(referencedProject); + } + return overrideOptions(parsedConfig, buildModeEnabled); + } + } + + private guardTSConfigFileExists() { + if (!ts.sys.fileExists(this.tsconfigFile)) { + throw new Error( + `The tsconfig file does not exist at: "${path.resolve( + this.tsconfigFile + )}". Please configure the tsconfig file in your stryker.conf file using "${propertyPath()('tsconfigFile')}"` + ); + } + } +} From 1fb26527f3867c6181e6dcc82988cf7348827d01 Mon Sep 17 00:00:00 2001 From: Odin van der Linden Date: Fri, 28 Oct 2022 20:08:00 +0200 Subject: [PATCH 19/77] =?UTF-8?q?WORKING=20ALGORITHM=20=F0=9F=A7=99?= =?UTF-8?q?=E2=80=8D=E2=99=82=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../typescript-checker/.vscode/launch.json | 34 +++++++ .../src/grouping/create-groups.ts | 92 +++++++------------ .../test/unit/grouping/create-groups.spec.ts | 91 ++++++++++++++++++ .../unit/{graph => grouping}/node.spec.ts | 0 4 files changed, 160 insertions(+), 57 deletions(-) create mode 100644 packages/typescript-checker/test/unit/grouping/create-groups.spec.ts rename packages/typescript-checker/test/unit/{graph => grouping}/node.spec.ts (100%) diff --git a/packages/typescript-checker/.vscode/launch.json b/packages/typescript-checker/.vscode/launch.json index 427d8e5417..fb07f2e5ce 100644 --- a/packages/typescript-checker/.vscode/launch.json +++ b/packages/typescript-checker/.vscode/launch.json @@ -18,6 +18,40 @@ "dist/test/unit/**/*.js", "dist/test/integration/**/*.js" ] + }, + { + "type": "node", + "request": "launch", + "name": "💙 Unit tests", + "program": "${workspaceRoot}/../../node_modules/mocha/bin/_mocha", + "internalConsoleOptions": "openOnSessionStart", + "outFiles": [ + "${workspaceRoot}/dist/**/*.js" + ], + "skipFiles": [ + "/**" + ], + "args": [ + "--no-timeout", + "dist/test/unit/**/*.js", + ] + }, + { + "type": "node", + "request": "launch", + "name": "💙 Integration tests", + "program": "${workspaceRoot}/../../node_modules/mocha/bin/_mocha", + "internalConsoleOptions": "openOnSessionStart", + "outFiles": [ + "${workspaceRoot}/dist/**/*.js" + ], + "skipFiles": [ + "/**" + ], + "args": [ + "--no-timeout", + "dist/test/integration/**/*.js" + ] } ] } diff --git a/packages/typescript-checker/src/grouping/create-groups.ts b/packages/typescript-checker/src/grouping/create-groups.ts index acb38875e4..1cc8b4d9db 100644 --- a/packages/typescript-checker/src/grouping/create-groups.ts +++ b/packages/typescript-checker/src/grouping/create-groups.ts @@ -1,52 +1,15 @@ -/* eslint-disable */ import { Mutant } from '@stryker-mutator/api/src/core/index.js'; -import { group } from 'console'; -import { findNode, MutantSelectorHelpers } from './mutant-selector-helpers.js'; -import { Node } from './node.js'; - -function createTempGraph(): Node[] { - const nodeA: Node = new Node('a.js', [], []); - const nodeB: Node = new Node('b.js', [], []); - const nodeC: Node = new Node('c.js', [], []); - const nodeD: Node = new Node('d.js', [], []); - - nodeA.childs = [nodeB, nodeC]; - - nodeB.parents = [nodeA]; - - nodeC.childs = [nodeD]; - nodeC.parents = [nodeA]; - - nodeD.parents = [nodeC]; - - return [nodeA, nodeB, nodeC, nodeD]; -} - -function createTempMutents(): Node[] { - const nodeA: Node = new Node('a.js', [], []); - const nodeB: Node = new Node('b.js', [], []); - const nodeC: Node = new Node('c.js', [], []); - const nodeD: Node = new Node('d.js', [], []); - - nodeA.childs = [nodeB, nodeC]; - - nodeB.parents = [nodeA]; +import { findNode } from './mutant-selector-helpers.js'; - nodeC.childs = [nodeD]; - nodeC.parents = [nodeA]; - - nodeD.parents = [nodeC]; - - return [nodeA, nodeB, nodeC, nodeD]; -} +import { Node } from './node.js'; export function createGroups(mutants: Mutant[], nodes: Node[]): Promise { const groups: Mutant[][] = []; let mutant: Mutant | null = selectNewMutant(mutants, groups); - // Loop unil all the mutants are in a group + // Loop until all the mutants are in a group while (mutant != null) { // Copy the list of mutants who are not in a group const mutantWhoAreNotInAGroup = [...mutants]; @@ -56,21 +19,26 @@ export function createGroups(mutants: Mutant[], nodes: Node[]): Promise = new Set(node.getAllParentReferencesIncludingSelf()); + // Fill the ignoreList + let nodesToIgnore: Set = node.getAllParentReferencesIncludingSelf(); // Loop through the nodes who can possibly go in the group for (const mutantSelected of mutantWhoAreNotInAGroup) { - let nodeSelected = findNode(mutantSelected.fileName, nodes); + const nodeSelected = findNode(mutantSelected.fileName, nodes); if (nodeSelected === null) throw new Error('Node not in graph'); + // check of er al mutants in deze groep zitten die in de parent structuur van nodeSelected voorkomt + const groupNodes: Set = getNodesFromMutants(group, nodes); + if (currentNodeHasParentsInNodesToIgnoreList(nodeSelected, new Set(groupNodes))) continue; + // See if the node can be in the group if (nodesToIgnore.has(nodeSelected)) continue; // Push the group group.push(mutantSelected); - // Add to the ignorelist + mutants.splice(mutants.indexOf(mutantSelected), 1); + // Add to the ignoreList nodesToIgnore = new Set([...nodesToIgnore, ...nodeSelected.getAllParentReferencesIncludingSelf()]); } @@ -81,23 +49,13 @@ export function createGroups(mutants: Mutant[], nodes: Node[]): Promise group.map(mutant => mutant.fileName)); + return groups.map((group) => group.map((mutant) => mutant.id)); } +function currentNodeHasParentsInNodesToIgnoreList(nodeSelected: Node, nodesToIgnore: Set) { + let result = false; + nodeSelected.getAllParentReferencesIncludingSelf().forEach((parentNode) => { + if (nodesToIgnore.has(parentNode)) { + result = true; + } + }); + return result; +} +function getNodesFromMutants(group: Mutant[], nodes: Node[]): Set { + return new Set( + group.map((mutant) => { + const node = findNode(mutant.fileName, nodes); + if (!node) { + throw new Error('node not found'); + } + return node; + }) + ); +} diff --git a/packages/typescript-checker/test/unit/grouping/create-groups.spec.ts b/packages/typescript-checker/test/unit/grouping/create-groups.spec.ts new file mode 100644 index 0000000000..6eaaf0fb62 --- /dev/null +++ b/packages/typescript-checker/test/unit/grouping/create-groups.spec.ts @@ -0,0 +1,91 @@ +import { expect } from 'chai'; + +import { factory } from '@stryker-mutator/test-helpers'; + +import { Node } from '../../../src/grouping/node.js'; + +import { createGroups } from '../../../src/grouping/create-groups.js'; + +describe('create-group createGroups', () => { + it('single mutant should create single group', async () => { + const mutants = [factory.mutant({ fileName: 'a.js', id: '1' })]; + const mutantsClone = structuredClone(mutants); + const nodes = [new Node('a.js', [], [])]; + const groups = await createGroups(mutants, nodes); + expect(groups).to.have.lengthOf(1); + expect(groups[0][0]).to.be.equal(mutantsClone[0].id); + }); + + it('two mutants in different files without reference to each other should create single group', async () => { + const mutants = [factory.mutant({ fileName: 'a.js', id: '1' }), factory.mutant({ fileName: 'b.js', id: '2' })]; + const mutantsClone = structuredClone(mutants); + const nodes = [new Node('a.js', [], []), new Node('b.js', [], [])]; + const groups = await createGroups(mutants, nodes); + expect(groups).to.have.lengthOf(1); + expect(groups[0][0]).to.be.equal(mutantsClone[0].id); + expect(groups[0][1]).to.be.equal(mutantsClone[1].id); + }); + + it('two mutants in different files with reference to each other should create 2 groups', async () => { + const mutants = [factory.mutant({ fileName: 'a.js', id: '1' }), factory.mutant({ fileName: 'b.js', id: '2' })]; + const mutantsClone = structuredClone(mutants); + const nodeA = new Node('a.js', [], []); + const nodeB = new Node('b.js', [nodeA], []); + const nodes = [nodeA, nodeB]; + const groups = await createGroups(mutants, nodes); + expect(groups).to.have.lengthOf(2); + expect(groups[0][0]).to.be.equal(mutantsClone[0].id); + expect(groups[1][0]).to.be.equal(mutantsClone[1].id); + }); + + it('two mutants in different files with circular dependency to each other should create 2 groups', async () => { + const mutants = [factory.mutant({ fileName: 'a.js', id: '1' }), factory.mutant({ fileName: 'b.js', id: '2' })]; + const mutantsClone = structuredClone(mutants); + const nodeA = new Node('a.js', [], []); + const nodeB = new Node('b.js', [nodeA], []); + nodeA.parents.push(nodeB); + const nodes = [nodeA, nodeB]; + const groups = await createGroups(mutants, nodes); + expect(groups).to.have.lengthOf(2); + expect(groups[0][0]).to.be.equal(mutantsClone[0].id); + expect(groups[1][0]).to.be.equal(mutantsClone[1].id); + }); + + it('two mutants in same file should create 2 groups', async () => { + const mutants = [factory.mutant({ fileName: 'a.js', id: '1' }), factory.mutant({ fileName: 'a.js', id: '2' })]; + const mutantsClone = structuredClone(mutants); + const nodeA = new Node('a.js', [], []); + const nodes = [nodeA]; + const groups = await createGroups(mutants, nodes); + expect(groups).to.have.lengthOf(2); + expect(groups[0][0]).to.be.equal(mutantsClone[0].id); + expect(groups[1][0]).to.be.equal(mutantsClone[1].id); + }); + + it('complex graph should contain multiples 4 groups', async () => { + const mutants = [ + factory.mutant({ fileName: 'a.js', id: '1' }), + factory.mutant({ fileName: 'b.js', id: '2' }), + factory.mutant({ fileName: 'c.js', id: '3' }), + factory.mutant({ fileName: 'd.js', id: '4' }), + factory.mutant({ fileName: 'e.js', id: '5' }), + factory.mutant({ fileName: 'f.js', id: '6' }), + ]; + const mutantsClone = structuredClone(mutants); + const nodeA = new Node('a.js', [], []); + const nodeB = new Node('b.js', [nodeA], []); + const nodeC = new Node('c.js', [nodeA], []); + const nodeD = new Node('d.js', [nodeC], []); + const nodeE = new Node('e.js', [nodeA], []); + const nodeF = new Node('f.js', [nodeE, nodeD], []); + const nodes = [nodeA, nodeB, nodeC, nodeD, nodeE, nodeF]; + const groups = await createGroups(mutants, nodes); + expect(groups).to.have.lengthOf(4); + expect(groups[0][0]).to.be.equal(mutantsClone[0].id); + expect(groups[1][0]).to.be.equal(mutantsClone[1].id); + expect(groups[1][1]).to.be.equal(mutantsClone[2].id); + expect(groups[1][2]).to.be.equal(mutantsClone[4].id); + expect(groups[2][0]).to.be.equal(mutantsClone[3].id); + expect(groups[3][0]).to.be.equal(mutantsClone[5].id); + }); +}); diff --git a/packages/typescript-checker/test/unit/graph/node.spec.ts b/packages/typescript-checker/test/unit/grouping/node.spec.ts similarity index 100% rename from packages/typescript-checker/test/unit/graph/node.spec.ts rename to packages/typescript-checker/test/unit/grouping/node.spec.ts From ffd5c33bc7f0d9d68a41750335e682ec70001021 Mon Sep 17 00:00:00 2001 From: Danny Berkelaar Date: Fri, 28 Oct 2022 20:30:23 +0200 Subject: [PATCH 20/77] Update node --- .../src/grouping/mutant-selector-helpers.ts | 4 ++- .../src/tsconfig-helpers.ts | 5 ++-- .../src/typescript-checker.ts | 28 ++++--------------- .../src/typescript-compiler.ts | 28 +++++++++++-------- 4 files changed, 28 insertions(+), 37 deletions(-) diff --git a/packages/typescript-checker/src/grouping/mutant-selector-helpers.ts b/packages/typescript-checker/src/grouping/mutant-selector-helpers.ts index 828bb8ff13..e3495fb0ad 100644 --- a/packages/typescript-checker/src/grouping/mutant-selector-helpers.ts +++ b/packages/typescript-checker/src/grouping/mutant-selector-helpers.ts @@ -1,5 +1,7 @@ import { Mutant } from '@stryker-mutator/api/src/core'; +import { toPosixFileName } from '../tsconfig-helpers.js'; + import { Node } from './node.js'; export class MutantSelectorHelpers { @@ -14,7 +16,7 @@ export class MutantSelectorHelpers { export function findNode(fileName: string, nodes: Node[]): Node | null { for (const node of nodes) { - if (node.fileName === fileName) return node; + if (node.fileName === toPosixFileName(fileName)) return node; } return null; diff --git a/packages/typescript-checker/src/tsconfig-helpers.ts b/packages/typescript-checker/src/tsconfig-helpers.ts index 7bb5b9c8df..95f920a1ce 100644 --- a/packages/typescript-checker/src/tsconfig-helpers.ts +++ b/packages/typescript-checker/src/tsconfig-helpers.ts @@ -15,16 +15,17 @@ const COMPILER_OPTIONS_OVERRIDES: Readonly> = Object const NO_EMIT_OPTIONS_FOR_SINGLE_PROJECT: Readonly> = Object.freeze({ noEmit: true, incremental: false, // incremental and composite off: https://github.com/microsoft/TypeScript/issues/36917 + tsBuildInfoFile: undefined, composite: false, declaration: false, - declarationMap: false, + declarationMap: true, }); // When we're running in 'project references' mode, we need to enable declaration output const LOW_EMIT_OPTIONS_FOR_PROJECT_REFERENCES: Readonly> = Object.freeze({ emitDeclarationOnly: true, noEmit: false, - declarationMap: false, + declarationMap: true, }); export function guardTSVersion(): void { diff --git a/packages/typescript-checker/src/typescript-checker.ts b/packages/typescript-checker/src/typescript-checker.ts index e6ba3f6712..1222cb5a74 100644 --- a/packages/typescript-checker/src/typescript-checker.ts +++ b/packages/typescript-checker/src/typescript-checker.ts @@ -7,6 +7,7 @@ import { Mutant, StrykerOptions } from '@stryker-mutator/api/core'; import { HybridFileSystem } from './fs/index.js'; import * as pluginTokens from './plugin-tokens.js'; import { TypescriptCompiler } from './typescript-compiler.js'; +import { createGroups } from './grouping/create-groups.js'; typescriptCheckerLoggerFactory.inject = tokens(commonTokens.getLogger, commonTokens.target); // eslint-disable-next-line @typescript-eslint/ban-types @@ -41,7 +42,7 @@ export class TypescriptChecker implements Checker { options: StrykerOptions, private readonly fs: HybridFileSystem, private readonly tsCompiler: TypescriptCompiler - ) { } + ) {} /** * Starts the typescript compiler and does a dry run @@ -81,31 +82,12 @@ export class TypescriptChecker implements Checker { }); return result; - - // const mutant = mutants[0]; - - // if (this.fs.existsInMemory(mutant.fileName)) { - // this.fs.mutate(mutant); - - // return { - // [mutant.id]: { - // status: CheckStatus.Passed, - // }, - // }; - // } else { - // // We allow people to mutate files that are not included in this ts project - // return { - // [mutant.id]: { - // status: CheckStatus.Passed, - // }, - // }; - // } } - public async group?(mutants: Mutant[]): Promise { - await this.tsCompiler.check(mutants); + public async group(mutants: Mutant[]): Promise { const nodes = this.tsCompiler.getFileRelation(); - return mutants.map((m) => [m.id]); + const result = await createGroups(mutants, nodes); + return result; } /** diff --git a/packages/typescript-checker/src/typescript-compiler.ts b/packages/typescript-checker/src/typescript-compiler.ts index ba071b6962..56b473f893 100644 --- a/packages/typescript-checker/src/typescript-compiler.ts +++ b/packages/typescript-checker/src/typescript-compiler.ts @@ -55,30 +55,36 @@ export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCre { ...ts.sys, readFile: (fileName) => { + if (fileName.endsWith('.tsbuildinfo')) { + return undefined; + } const content = this.fs.getFile(fileName)?.content; if (content && this.allTSConfigFiles.has(path.resolve(fileName))) { return this.adjustTSConfigFile(fileName, content, buildModeEnabled); } return content; }, - fileExists: (filePath: string) => { + fileExists: (fileName: string) => { // We want to ignore the buildinfo files. With them the compiler skips the program part we want to use. - if (filePath.endsWith('.tsbuildinfo')) { + if (fileName.endsWith('.tsbuildinfo')) { return false; } - return ts.sys.fileExists(filePath); + return ts.sys.fileExists(fileName); }, - getModifiedTime: (pathName: string) => { - return this.fs.getFile(pathName)?.modifiedTime; + getModifiedTime: (fileName: string) => { + if (fileName.endsWith('.tsbuildinfo')) { + return undefined; + } + return this.fs.getFile(fileName)?.modifiedTime; }, - watchFile: (filePath: string, callback: ts.FileWatcherCallback) => { - const file = this.fs.getFile(filePath); + watchFile: (fileName: string, callback: ts.FileWatcherCallback) => { + const file = this.fs.getFile(fileName); if (file) file.watcher = callback; return { close: () => { - delete this.fs.getFile(filePath)!.watcher; + delete this.fs.getFile(fileName)!.watcher; }, }; }, @@ -131,8 +137,8 @@ export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCre const compiler = ts.createSolutionBuilderWithWatch(host, [this.tsconfigFile], {}); compiler.build(); - const errors = await this.check([]); - return errors; + await this.currentTask.promise; + return this.currentErrors; } public async check(mutants: Mutant[]): Promise { @@ -189,7 +195,7 @@ export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCre const content = JSON.parse(sourceMap.content); - return content.sources.map((sourcePath: string) => toPosixFileName(path.resolve(sourceMappingURL, '..', sourcePath))); + return content.sources.map((sourcePath: string) => toPosixFileName(path.resolve(sourceMapFileName, '..', sourcePath))); } private getSourceMappingURL(content: string): string | undefined { From 0a605189a08735997734ac2685c17104629eec5f Mon Sep 17 00:00:00 2001 From: Odin van der Linden Date: Fri, 28 Oct 2022 21:28:22 +0200 Subject: [PATCH 21/77] implemented check function --- .../typescript-checker/src/grouping/node.ts | 10 ++ .../src/typescript-checker.ts | 103 +++++++++--------- .../src/typescript-compiler.ts | 40 +++---- 3 files changed, 84 insertions(+), 69 deletions(-) diff --git a/packages/typescript-checker/src/grouping/node.ts b/packages/typescript-checker/src/grouping/node.ts index c2eac75b14..6ad7a4bb26 100644 --- a/packages/typescript-checker/src/grouping/node.ts +++ b/packages/typescript-checker/src/grouping/node.ts @@ -10,4 +10,14 @@ export class Node { }); return allParentReferences; } + + public getAllChildReferencesIncludingSelf(allChildReferences: Set = new Set()): Set { + allChildReferences.add(this); + this.parents?.forEach((parent) => { + if (!allChildReferences.has(parent)) { + parent.getAllChildReferencesIncludingSelf(allChildReferences); + } + }); + return allChildReferences; + } } diff --git a/packages/typescript-checker/src/typescript-checker.ts b/packages/typescript-checker/src/typescript-checker.ts index e6ba3f6712..0fa40e75e7 100644 --- a/packages/typescript-checker/src/typescript-checker.ts +++ b/packages/typescript-checker/src/typescript-checker.ts @@ -1,3 +1,5 @@ +import { EOL } from 'os'; + import ts from 'typescript'; import { Checker, CheckResult, CheckStatus } from '@stryker-mutator/api/check'; import { tokens, commonTokens, PluginContext, Injector, Scope } from '@stryker-mutator/api/plugin'; @@ -41,7 +43,7 @@ export class TypescriptChecker implements Checker { options: StrykerOptions, private readonly fs: HybridFileSystem, private readonly tsCompiler: TypescriptCompiler - ) { } + ) {} /** * Starts the typescript compiler and does a dry run @@ -62,67 +64,70 @@ export class TypescriptChecker implements Checker { * @param mutant The mutant to check */ public async check(mutants: Mutant[]): Promise> { - const errors = await this.tsCompiler.check(mutants); - this.logger.info(`Found errors: ${errors.length}`); - const result: Record = {}; mutants.forEach((mutant) => { result[mutant.id] = { status: CheckStatus.Passed, }; }); - errors.forEach((error) => { - return; - const mutant = mutants.find((m) => m.fileName == error.file!.fileName); - result[mutant!.id] = { - status: CheckStatus.CompileError, - reason: 'todo', - }; - }); - + const mutantErrorRelationMap = await this.checkErrors(mutants, {}); + for (const [id, errors] of Object.entries(mutantErrorRelationMap)) { + result[id] = { status: CheckStatus.CompileError, reason: this.createErrorText(errors) }; + } return result; - - // const mutant = mutants[0]; - - // if (this.fs.existsInMemory(mutant.fileName)) { - // this.fs.mutate(mutant); - - // return { - // [mutant.id]: { - // status: CheckStatus.Passed, - // }, - // }; - // } else { - // // We allow people to mutate files that are not included in this ts project - // return { - // [mutant.id]: { - // status: CheckStatus.Passed, - // }, - // }; - // } } public async group?(mutants: Mutant[]): Promise { await this.tsCompiler.check(mutants); - const nodes = this.tsCompiler.getFileRelation(); return mutants.map((m) => [m.id]); } - /** - * Resolves the task that is currently running. Will report back the check result. - */ - private resolveCheckResult(): void { - // if (this.currentErrors.length) { - // const errorText = ts.formatDiagnostics(this.currentErrors, { - // getCanonicalFileName: (fileName) => fileName, - // getCurrentDirectory: process.cwd, - // getNewLine: () => EOL, - // }); - // this.currentTask.resolve({ - // status: CheckStatus.CompileError, - // reason: errorText, - // }); - // } - // this.currentTask.resolve({ status: CheckStatus.Passed }); + // string is id van de mutant + private async checkErrors(mutants: Mutant[], errorsMap: Record): Promise> { + const errors = await this.tsCompiler.check(mutants); + this.logger.info(`Found errors: ${errors.length}`); + + errors.forEach((error) => { + if (mutants.length === 1) { + if (errorsMap[mutants[0].id]) { + errorsMap[mutants[0].id].push(error); + } else { + errorsMap[mutants[0].id] = [error]; + } + } else { + const nodeErrorWasThrownIn = this.tsCompiler.getFileRelation().find((node) => (node.fileName = error.file!.fileName)); + if (!nodeErrorWasThrownIn) { + throw new Error('Error not found in any node'); + } + const allNodesWrongMutantsCanBeIn = nodeErrorWasThrownIn.getAllChildReferencesIncludingSelf(); + const fileNamesToCheck: string[] = []; + allNodesWrongMutantsCanBeIn.forEach((node) => { + fileNamesToCheck.push(node.fileName); + }); + const mutantsRelatedToError = mutants.filter((mutant) => { + return fileNamesToCheck.includes(mutant.fileName); + }); + if (mutantsRelatedToError.length === 1) { + if (errorsMap[mutants[0].id]) { + errorsMap[mutants[0].id].push(error); + } else { + errorsMap[mutants[0].id] = [error]; + } + } else { + mutantsRelatedToError.forEach(async (mutant) => { + await this.checkErrors([mutant], errorsMap); + }); + } + } + }); + return errorsMap; + } + + private createErrorText(errors: ts.Diagnostic[]): string { + return ts.formatDiagnostics(errors, { + getCanonicalFileName: (fileName) => fileName, + getCurrentDirectory: process.cwd, + getNewLine: () => EOL, + }); } } diff --git a/packages/typescript-checker/src/typescript-compiler.ts b/packages/typescript-checker/src/typescript-compiler.ts index ba071b6962..7afdafb896 100644 --- a/packages/typescript-checker/src/typescript-compiler.ts +++ b/packages/typescript-checker/src/typescript-compiler.ts @@ -40,6 +40,7 @@ export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCre private currentTask = new Task(); private currentErrors: ts.Diagnostic[] = []; private readonly sourceFiles: SourceFiles = new Map(); + private readonly nodes: Node[] = []; constructor(private readonly log: Logger, private readonly options: StrykerOptions, private readonly fs: HybridFileSystem) { this.tsconfigFile = toPosixFileName(this.options.tsconfigFile); @@ -148,30 +149,29 @@ export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCre } public getFileRelation(): Node[] { - const nodes: Node[] = []; - - // create nodes - for (const [fileName] of this.sourceFiles) { - const node = new Node(fileName, [], []); - nodes.push(node); - } + if (!this.nodes.length) { + // create nodes + for (const [fileName] of this.sourceFiles) { + const node = new Node(fileName, [], []); + this.nodes.push(node); + } - // set imports - for (const [fileName, file] of this.sourceFiles) { - const node = findNode(fileName, nodes); - if (node == null) { - throw new Error('todo'); + // set imports + for (const [fileName, file] of this.sourceFiles) { + const node = findNode(fileName, this.nodes); + if (node == null) { + throw new Error('todo'); + } + const importFileNames = [...file.imports]; + node.childs = this.nodes.filter((n) => importFileNames.includes(n.fileName)); } - const importFileNames = [...file.imports]; - node.childs = nodes.filter((n) => importFileNames.includes(n.fileName)); - } - // todo set parents - for (const node of nodes) { - node.parents = nodes.filter((n) => n.childs?.includes(node)); // todo remove ? when childs isnt nullable + // todo set parents + for (const node of this.nodes) { + node.parents = this.nodes.filter((n) => n.childs?.includes(node)); // todo remove ? when childs isnt nullable + } } - - return nodes; + return this.nodes; } private resolveFilename(fileName: string): string[] { From 300e3ec3314827c1f58d162e3ee11ea450344486 Mon Sep 17 00:00:00 2001 From: Odin van der Linden Date: Fri, 28 Oct 2022 21:30:25 +0200 Subject: [PATCH 22/77] getFileRelationsAsNodes rename --- packages/typescript-checker/src/typescript-checker.ts | 4 ++-- packages/typescript-checker/src/typescript-compiler.ts | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/typescript-checker/src/typescript-checker.ts b/packages/typescript-checker/src/typescript-checker.ts index 3842e805f5..99b0b3cc7d 100644 --- a/packages/typescript-checker/src/typescript-checker.ts +++ b/packages/typescript-checker/src/typescript-checker.ts @@ -79,7 +79,7 @@ export class TypescriptChecker implements Checker { } public async group(mutants: Mutant[]): Promise { - const nodes = this.tsCompiler.getFileRelation(); + const nodes = this.tsCompiler.getFileRelationsAsNodes(); const result = await createGroups(mutants, nodes); return result; } @@ -97,7 +97,7 @@ export class TypescriptChecker implements Checker { errorsMap[mutants[0].id] = [error]; } } else { - const nodeErrorWasThrownIn = this.tsCompiler.getFileRelation().find((node) => (node.fileName = error.file!.fileName)); + const nodeErrorWasThrownIn = this.tsCompiler.getFileRelationsAsNodes().find((node) => (node.fileName = error.file!.fileName)); if (!nodeErrorWasThrownIn) { throw new Error('Error not found in any node'); } diff --git a/packages/typescript-checker/src/typescript-compiler.ts b/packages/typescript-checker/src/typescript-compiler.ts index 42298459c9..5252e19fad 100644 --- a/packages/typescript-checker/src/typescript-compiler.ts +++ b/packages/typescript-checker/src/typescript-compiler.ts @@ -16,11 +16,10 @@ import { findNode } from './grouping/mutant-selector-helpers.js'; export interface ITypescriptCompiler { init(): Promise; check(mutants: Mutant[]): Promise; // todo set return type - getFileRelation(): Node[]; } export interface IFileRelationCreator { - getFileRelation(): void; + getFileRelationsAsNodes(): void; } export type SourceFiles = Map< @@ -154,7 +153,7 @@ export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCre return errors; } - public getFileRelation(): Node[] { + public getFileRelationsAsNodes(): Node[] { if (!this.nodes.length) { // create nodes for (const [fileName] of this.sourceFiles) { From 6b025fb5bc5763318fc9c18b9f5043c21c6ba87f Mon Sep 17 00:00:00 2001 From: Danny Berkelaar Date: Sat, 29 Oct 2022 09:25:05 +0200 Subject: [PATCH 23/77] refacoted ts compiler --- .../src/typescript-compiler.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/typescript-checker/src/typescript-compiler.ts b/packages/typescript-checker/src/typescript-compiler.ts index 56b473f893..458cd92d3e 100644 --- a/packages/typescript-checker/src/typescript-compiler.ts +++ b/packages/typescript-checker/src/typescript-compiler.ts @@ -55,7 +55,7 @@ export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCre { ...ts.sys, readFile: (fileName) => { - if (fileName.endsWith('.tsbuildinfo')) { + if (this.fileNameIsBuildInfo(fileName)) { return undefined; } const content = this.fs.getFile(fileName)?.content; @@ -66,13 +66,13 @@ export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCre }, fileExists: (fileName: string) => { // We want to ignore the buildinfo files. With them the compiler skips the program part we want to use. - if (fileName.endsWith('.tsbuildinfo')) { + if (this.fileNameIsBuildInfo(fileName)) { return false; } return ts.sys.fileExists(fileName); }, getModifiedTime: (fileName: string) => { - if (fileName.endsWith('.tsbuildinfo')) { + if (this.fileNameIsBuildInfo(fileName)) { return undefined; } return this.fs.getFile(fileName)?.modifiedTime; @@ -137,14 +137,12 @@ export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCre const compiler = ts.createSolutionBuilderWithWatch(host, [this.tsconfigFile], {}); compiler.build(); - await this.currentTask.promise; - return this.currentErrors; + return await this.check([]); } public async check(mutants: Mutant[]): Promise { mutants.forEach((mutant) => this.fs.getFile(mutant.fileName)?.mutate(mutant)); await this.currentTask.promise; - // todo make this better? const errors = [...this.currentErrors]; this.currentTask = new Task(); @@ -172,9 +170,8 @@ export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCre node.childs = nodes.filter((n) => importFileNames.includes(n.fileName)); } - // todo set parents for (const node of nodes) { - node.parents = nodes.filter((n) => n.childs?.includes(node)); // todo remove ? when childs isnt nullable + node.parents = nodes.filter((n) => n.childs.includes(node)); } return nodes; @@ -223,4 +220,8 @@ export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCre ); } } + + private fileNameIsBuildInfo(fileName: string): boolean { + return fileName.endsWith('.tsbuildinfo'); + } } From dfad8cbf7b9b9a4c3b2b151082f8b88383c2d640 Mon Sep 17 00:00:00 2001 From: Thierry Rietveld Date: Sat, 29 Oct 2022 09:25:40 +0200 Subject: [PATCH 24/77] Commands and code style --- .../src/grouping/create-groups.ts | 8 +++---- .../src/grouping/mutant-selector-helpers.ts | 12 ---------- .../src/typescript-checker.ts | 24 +++++++++++++++---- 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/packages/typescript-checker/src/grouping/create-groups.ts b/packages/typescript-checker/src/grouping/create-groups.ts index 1cc8b4d9db..5c05c697c2 100644 --- a/packages/typescript-checker/src/grouping/create-groups.ts +++ b/packages/typescript-checker/src/grouping/create-groups.ts @@ -4,9 +4,8 @@ import { findNode } from './mutant-selector-helpers.js'; import { Node } from './node.js'; -export function createGroups(mutants: Mutant[], nodes: Node[]): Promise { +export function createGroups(mutants: Mutant[], nodes: Node[]): string[][] { const groups: Mutant[][] = []; - let mutant: Mutant | null = selectNewMutant(mutants, groups); // Loop until all the mutants are in a group @@ -28,7 +27,7 @@ export function createGroups(mutants: Mutant[], nodes: Node[]): Promise = getNodesFromMutants(group, nodes); if (currentNodeHasParentsInNodesToIgnoreList(nodeSelected, new Set(groupNodes))) continue; @@ -46,7 +45,7 @@ export function createGroups(mutants: Mutant[], nodes: Node[]): Promise { return new Set( group.map((mutant) => { diff --git a/packages/typescript-checker/src/grouping/mutant-selector-helpers.ts b/packages/typescript-checker/src/grouping/mutant-selector-helpers.ts index e3495fb0ad..fc0267a195 100644 --- a/packages/typescript-checker/src/grouping/mutant-selector-helpers.ts +++ b/packages/typescript-checker/src/grouping/mutant-selector-helpers.ts @@ -1,19 +1,7 @@ -import { Mutant } from '@stryker-mutator/api/src/core'; - import { toPosixFileName } from '../tsconfig-helpers.js'; import { Node } from './node.js'; -export class MutantSelectorHelpers { - constructor(private readonly mutants: Mutant[], private readonly nodes: Node[]) {}; - - public getNewMutant(): Mutant | null { - const mutant = this.mutants[0]; - this.mutants.splice(0, 1); - return mutant; - } -} - export function findNode(fileName: string, nodes: Node[]): Node | null { for (const node of nodes) { if (node.fileName === toPosixFileName(fileName)) return node; diff --git a/packages/typescript-checker/src/typescript-checker.ts b/packages/typescript-checker/src/typescript-checker.ts index 99b0b3cc7d..586cecf783 100644 --- a/packages/typescript-checker/src/typescript-checker.ts +++ b/packages/typescript-checker/src/typescript-checker.ts @@ -62,25 +62,35 @@ export class TypescriptChecker implements Checker { /** * Checks whether or not a mutant results in a compile error. * Will simply pass through if the file mutated isn't part of the typescript project - * @param mutant The mutant to check + * @param mutants The mutants to check */ public async check(mutants: Mutant[]): Promise> { const result: Record = {}; + mutants.forEach((mutant) => { result[mutant.id] = { status: CheckStatus.Passed, }; }); + const mutantErrorRelationMap = await this.checkErrors(mutants, {}); + for (const [id, errors] of Object.entries(mutantErrorRelationMap)) { result[id] = { status: CheckStatus.CompileError, reason: this.createErrorText(errors) }; } + return result; } + /** + * Creates groups of the mutants. + * These groups will get send to the check method. + * @param mutants All the mutants to group. + */ public async group(mutants: Mutant[]): Promise { const nodes = this.tsCompiler.getFileRelationsAsNodes(); - const result = await createGroups(mutants, nodes); + const result = createGroups(mutants, nodes); + return result; } @@ -98,17 +108,20 @@ export class TypescriptChecker implements Checker { } } else { const nodeErrorWasThrownIn = this.tsCompiler.getFileRelationsAsNodes().find((node) => (node.fileName = error.file!.fileName)); - if (!nodeErrorWasThrownIn) { - throw new Error('Error not found in any node'); - } + + if (!nodeErrorWasThrownIn) throw new Error('Error not found in any node'); + const allNodesWrongMutantsCanBeIn = nodeErrorWasThrownIn.getAllChildReferencesIncludingSelf(); const fileNamesToCheck: string[] = []; + allNodesWrongMutantsCanBeIn.forEach((node) => { fileNamesToCheck.push(node.fileName); }); + const mutantsRelatedToError = mutants.filter((mutant) => { return fileNamesToCheck.includes(mutant.fileName); }); + if (mutantsRelatedToError.length === 1) { if (errorsMap[mutants[0].id]) { errorsMap[mutants[0].id].push(error); @@ -122,6 +135,7 @@ export class TypescriptChecker implements Checker { } } }); + return errorsMap; } From 60a8971f1a9d71bdda2bb4c60d05d1d7c27918d8 Mon Sep 17 00:00:00 2001 From: Thierry Rietveld Date: Sat, 29 Oct 2022 09:27:50 +0200 Subject: [PATCH 25/77] Add command --- .../src/grouping/create-groups.ts | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/packages/typescript-checker/src/grouping/create-groups.ts b/packages/typescript-checker/src/grouping/create-groups.ts index 5c05c697c2..f186dd08a9 100644 --- a/packages/typescript-checker/src/grouping/create-groups.ts +++ b/packages/typescript-checker/src/grouping/create-groups.ts @@ -4,6 +4,39 @@ import { findNode } from './mutant-selector-helpers.js'; import { Node } from './node.js'; +/** + * To speed up the type checking we want to check multiple mutants at once. + * When multiple mutants in different files who can't throw errors in each other we can type check them simultaneously. + * These mutants who can be tested at the same time are called a group. + * Therefore the return type is an array of arrays in other words: an array of groups. + * + * @example + * Let's assume we got tho following project structure and in every file is one mutant. + * + * ======== + * = A.ts = + * ======== + * / \ + * ======== ======== + * = B.ts = = C.ts = + * ======== ======== + * \ + * ======== + * = D.ts = + * ======== + * + * A imports B and C + * C imports D + * + * In this example we can type check B and D at the same time. + * This is because these files can't throw errors in each other. + * If we type check them and let's say B throws an error. + * We know for sure that the mutant in B was the one creating the type error. + * If we type check B and D at the same time it is possible that an error shows up in A. + * When this happens we go down de dependency graph and individual test the mutants who were in that group. + * + * In this function we create the groups of mutants who can be tested at the same time. + */ export function createGroups(mutants: Mutant[], nodes: Node[]): string[][] { const groups: Mutant[][] = []; let mutant: Mutant | null = selectNewMutant(mutants, groups); From 40c8723cfeb12cb3e70593c7e0decb91f6a33523 Mon Sep 17 00:00:00 2001 From: Thierry Rietveld Date: Sat, 29 Oct 2022 09:48:37 +0200 Subject: [PATCH 26/77] Refactored group mutants --- .../src/grouping/create-groups.ts | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/packages/typescript-checker/src/grouping/create-groups.ts b/packages/typescript-checker/src/grouping/create-groups.ts index f186dd08a9..3792557bb8 100644 --- a/packages/typescript-checker/src/grouping/create-groups.ts +++ b/packages/typescript-checker/src/grouping/create-groups.ts @@ -10,6 +10,9 @@ import { Node } from './node.js'; * These mutants who can be tested at the same time are called a group. * Therefore the return type is an array of arrays in other words: an array of groups. * + * @param mutants All the mutants of the test project. + * @param nodes A graph representation of the test project. + * * @example * Let's assume we got tho following project structure and in every file is one mutant. * @@ -39,20 +42,20 @@ import { Node } from './node.js'; */ export function createGroups(mutants: Mutant[], nodes: Node[]): string[][] { const groups: Mutant[][] = []; - let mutant: Mutant | null = selectNewMutant(mutants, groups); + let mutant = selectNewMutant(mutants, groups); // Loop until all the mutants are in a group while (mutant != null) { // Copy the list of mutants who are not in a group const mutantWhoAreNotInAGroup = [...mutants]; - // The first mutant is always in a group - const group: Mutant[] = [mutant]; + // The first mutant is always in the group + const group = [mutant]; const node = findNode(mutant.fileName, nodes); if (node === null) throw new Error('Node not in graph'); // Fill the ignoreList - let nodesToIgnore: Set = node.getAllParentReferencesIncludingSelf(); + let nodesToIgnore = node.getAllParentReferencesIncludingSelf(); // Loop through the nodes who can possibly go in the group for (const mutantSelected of mutantWhoAreNotInAGroup) { @@ -61,14 +64,15 @@ export function createGroups(mutants: Mutant[], nodes: Node[]): string[][] { if (nodeSelected === null) throw new Error('Node not in graph'); // Check if parents of node are not in the group - const groupNodes: Set = getNodesFromMutants(group, nodes); - if (currentNodeHasParentsInNodesToIgnoreList(nodeSelected, new Set(groupNodes))) continue; + const groupNodes = getNodesFromMutants(group, nodes); + if (nodeSelectedHasParentsInCurrentGroup(nodeSelected, groupNodes)) continue; // See if the node can be in the group if (nodesToIgnore.has(nodeSelected)) continue; // Push the group group.push(mutantSelected); + // Mutate the original list and remove the mutant put in the group mutants.splice(mutants.indexOf(mutantSelected), 1); // Add to the ignoreList nodesToIgnore = new Set([...nodesToIgnore, ...nodeSelected.getAllParentReferencesIncludingSelf()]); @@ -86,8 +90,7 @@ function selectNewMutant(mutants: Mutant[], groups: Mutant[][]): Mutant | null { for (let i = 0; i < mutants.length; i++) { if (!flatGroups.includes(mutants[i])) { - const mutant = mutants.splice(i, 1); - return mutant[0]; + return mutants.splice(i, 1)[0]; } } @@ -98,14 +101,12 @@ function groupsToString(groups: Mutant[][]): string[][] { return groups.map((group) => group.map((mutant) => mutant.id)); } -function currentNodeHasParentsInNodesToIgnoreList(nodeSelected: Node, nodesToIgnore: Set) { - let result = false; - nodeSelected.getAllParentReferencesIncludingSelf().forEach((parentNode) => { - if (nodesToIgnore.has(parentNode)) { - result = true; - } - }); - return result; +function nodeSelectedHasParentsInCurrentGroup(nodeSelected: Node, groupNodes: Set) { + for (const parentNode of nodeSelected.getAllParentReferencesIncludingSelf()) { + if (groupNodes.has(parentNode)) return true; + } + + return false; } function getNodesFromMutants(group: Mutant[], nodes: Node[]): Set { @@ -113,7 +114,7 @@ function getNodesFromMutants(group: Mutant[], nodes: Node[]): Set { group.map((mutant) => { const node = findNode(mutant.fileName, nodes); if (!node) { - throw new Error('node not found'); + throw new Error('Node not in graph'); } return node; }) From 8279015815c6f0ffa98747d6d0187a5c3f954f20 Mon Sep 17 00:00:00 2001 From: Danny Berkelaar Date: Sat, 29 Oct 2022 11:14:06 +0200 Subject: [PATCH 27/77] Changes --- .../src/typescript-checker.ts | 40 ++++++++++++++----- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/packages/typescript-checker/src/typescript-checker.ts b/packages/typescript-checker/src/typescript-checker.ts index 99b0b3cc7d..db2d5e3268 100644 --- a/packages/typescript-checker/src/typescript-checker.ts +++ b/packages/typescript-checker/src/typescript-checker.ts @@ -10,6 +10,8 @@ import { HybridFileSystem } from './fs/index.js'; import * as pluginTokens from './plugin-tokens.js'; import { TypescriptCompiler } from './typescript-compiler.js'; import { createGroups } from './grouping/create-groups.js'; +import { toPosixFileName } from './tsconfig-helpers.js'; +import { Node } from './grouping/node.js'; typescriptCheckerLoggerFactory.inject = tokens(commonTokens.getLogger, commonTokens.target); // eslint-disable-next-line @typescript-eslint/ban-types @@ -71,7 +73,7 @@ export class TypescriptChecker implements Checker { status: CheckStatus.Passed, }; }); - const mutantErrorRelationMap = await this.checkErrors(mutants, {}); + const mutantErrorRelationMap = await this.checkErrors(mutants, {}, this.tsCompiler.getFileRelationsAsNodes()); for (const [id, errors] of Object.entries(mutantErrorRelationMap)) { result[id] = { status: CheckStatus.CompileError, reason: this.createErrorText(errors) }; } @@ -79,17 +81,27 @@ export class TypescriptChecker implements Checker { } public async group(mutants: Mutant[]): Promise { + const e = mutants.filter((m) => m.fileName.includes('jest-test-adapter-factory.ts')); + const a = mutants.filter((m) => !m.fileName.includes('jest-test-adapter-factory.ts')); const nodes = this.tsCompiler.getFileRelationsAsNodes(); - const result = await createGroups(mutants, nodes); - return result; + const result1 = await createGroups(e, nodes); + const result2 = await createGroups(a, nodes); + + return [...result1, ...result2]; + // return mutants.map((m) => [m.id]); + // const nodes = this.tsCompiler.getFileRelationsAsNodes(); + // const result = await createGroups(mutants, nodes); + // return result; } // string is id van de mutant - private async checkErrors(mutants: Mutant[], errorsMap: Record): Promise> { + // todo make private + public async checkErrors(mutants: Mutant[], errorsMap: Record, nodes: Node[]): Promise> { const errors = await this.tsCompiler.check(mutants); this.logger.info(`Found errors: ${errors.length}`); - errors.forEach((error) => { + for (const error of errors) { + // errors.forEach((error) => { if (mutants.length === 1) { if (errorsMap[mutants[0].id]) { errorsMap[mutants[0].id].push(error); @@ -97,7 +109,7 @@ export class TypescriptChecker implements Checker { errorsMap[mutants[0].id] = [error]; } } else { - const nodeErrorWasThrownIn = this.tsCompiler.getFileRelationsAsNodes().find((node) => (node.fileName = error.file!.fileName)); + const nodeErrorWasThrownIn = nodes.find((node) => (node.fileName = error.file!.fileName)); if (!nodeErrorWasThrownIn) { throw new Error('Error not found in any node'); } @@ -107,7 +119,8 @@ export class TypescriptChecker implements Checker { fileNamesToCheck.push(node.fileName); }); const mutantsRelatedToError = mutants.filter((mutant) => { - return fileNamesToCheck.includes(mutant.fileName); + // todo fix all posix + return fileNamesToCheck.map((f) => toPosixFileName(f)).includes(toPosixFileName(mutant.fileName)); }); if (mutantsRelatedToError.length === 1) { if (errorsMap[mutants[0].id]) { @@ -115,13 +128,18 @@ export class TypescriptChecker implements Checker { } else { errorsMap[mutants[0].id] = [error]; } + } else if (mutantsRelatedToError.length === 0) { + throw new Error('No related mutants found.'); } else { - mutantsRelatedToError.forEach(async (mutant) => { - await this.checkErrors([mutant], errorsMap); - }); + for (const mutant of mutantsRelatedToError) { + await this.checkErrors([mutant], errorsMap, nodes); + } + // mutantsRelatedToError.forEach(async (mutant) => { + // await this.checkErrors([mutant], errorsMap, nodes); + // }); } } - }); + } return errorsMap; } From c1763e93c74fad045aa8a2f755a0920e8fa704c8 Mon Sep 17 00:00:00 2001 From: Thierry Rietveld Date: Sat, 29 Oct 2022 12:35:44 +0200 Subject: [PATCH 28/77] ding --- .../src/grouping/create-groups.ts | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/packages/typescript-checker/src/grouping/create-groups.ts b/packages/typescript-checker/src/grouping/create-groups.ts index 3792557bb8..b85200a1f6 100644 --- a/packages/typescript-checker/src/grouping/create-groups.ts +++ b/packages/typescript-checker/src/grouping/create-groups.ts @@ -86,10 +86,10 @@ export function createGroups(mutants: Mutant[], nodes: Node[]): string[][] { } function selectNewMutant(mutants: Mutant[], groups: Mutant[][]): Mutant | null { - const flatGroups = groups.flat(); + const groupsFlattened = groups.flat(); for (let i = 0; i < mutants.length; i++) { - if (!flatGroups.includes(mutants[i])) { + if (!groupsFlattened.includes(mutants[i])) { return mutants.splice(i, 1)[0]; } } @@ -101,22 +101,19 @@ function groupsToString(groups: Mutant[][]): string[][] { return groups.map((group) => group.map((mutant) => mutant.id)); } -function nodeSelectedHasParentsInCurrentGroup(nodeSelected: Node, groupNodes: Set) { +function nodeSelectedHasParentsInCurrentGroup(nodeSelected: Node, groupNodes: Node[]) { for (const parentNode of nodeSelected.getAllParentReferencesIncludingSelf()) { - if (groupNodes.has(parentNode)) return true; + if (groupNodes.includes(parentNode)) return true; } return false; } -function getNodesFromMutants(group: Mutant[], nodes: Node[]): Set { - return new Set( - group.map((mutant) => { - const node = findNode(mutant.fileName, nodes); - if (!node) { - throw new Error('Node not in graph'); - } - return node; - }) - ); +function getNodesFromMutants(group: Mutant[], nodes: Node[]): Node[] { + return group.map((mutant) => { + const node = findNode(mutant.fileName, nodes); + if (node === null) throw new Error('Node not in graph'); + + return node; + }); } From f09a3f94b39d68bb035819e9532e590a1491bb71 Mon Sep 17 00:00:00 2001 From: Danny Berkelaar Date: Sat, 29 Oct 2022 12:36:06 +0200 Subject: [PATCH 29/77] Added debug --- .../typescript-checker/src/fs/script-file.ts | 8 +++--- .../typescript-checker/src/grouping/node.ts | 8 +++--- .../src/typescript-checker.ts | 26 ++++++++++++------- .../src/typescript-compiler.ts | 8 ++++-- 4 files changed, 30 insertions(+), 20 deletions(-) diff --git a/packages/typescript-checker/src/fs/script-file.ts b/packages/typescript-checker/src/fs/script-file.ts index b9abf87943..68b87cec1c 100644 --- a/packages/typescript-checker/src/fs/script-file.ts +++ b/packages/typescript-checker/src/fs/script-file.ts @@ -10,7 +10,7 @@ export class ScriptFile { public write(content: string): void { this.content = content; - this.touch(); + // this.touch(); } public watcher: ts.FileWatcherCallback | undefined; @@ -21,7 +21,7 @@ export class ScriptFile { const start = this.getOffset(mutant.location.start); const end = this.getOffset(mutant.location.end); this.content = `${this.originalContent.substr(0, start)}${mutant.replacement}${this.originalContent.substr(end)}`; - this.touch(); + // this.touch(); } private getOffset(pos: Position): number { @@ -34,7 +34,7 @@ export class ScriptFile { public resetMutant(): void { this.guardMutationIsWatched(); this.content = this.originalContent; - this.touch(); + // this.touch(); } private guardMutationIsWatched() { @@ -45,7 +45,7 @@ export class ScriptFile { } } - private touch() { + public touch() { this.modifiedTime = new Date(); this.watcher?.(this.fileName, ts.FileWatcherEventKind.Changed); } diff --git a/packages/typescript-checker/src/grouping/node.ts b/packages/typescript-checker/src/grouping/node.ts index 6ad7a4bb26..b952969bb7 100644 --- a/packages/typescript-checker/src/grouping/node.ts +++ b/packages/typescript-checker/src/grouping/node.ts @@ -3,7 +3,7 @@ export class Node { public getAllParentReferencesIncludingSelf(allParentReferences: Set = new Set()): Set { allParentReferences.add(this); - this.parents?.forEach((parent) => { + this.parents.forEach((parent) => { if (!allParentReferences.has(parent)) { parent.getAllParentReferencesIncludingSelf(allParentReferences); } @@ -13,9 +13,9 @@ export class Node { public getAllChildReferencesIncludingSelf(allChildReferences: Set = new Set()): Set { allChildReferences.add(this); - this.parents?.forEach((parent) => { - if (!allChildReferences.has(parent)) { - parent.getAllChildReferencesIncludingSelf(allChildReferences); + this.childs.forEach((child) => { + if (!allChildReferences.has(child)) { + child.getAllChildReferencesIncludingSelf(allChildReferences); } }); return allChildReferences; diff --git a/packages/typescript-checker/src/typescript-checker.ts b/packages/typescript-checker/src/typescript-checker.ts index c6113ef30c..09dcfa46f5 100644 --- a/packages/typescript-checker/src/typescript-checker.ts +++ b/packages/typescript-checker/src/typescript-checker.ts @@ -34,7 +34,6 @@ export function create(injector: Injector): TypescriptChecker { * An in-memory type checker implementation which validates type errors of mutants. */ export class TypescriptChecker implements Checker { - private readonly currentErrors: ts.Diagnostic[] = []; /** * Keep track of all tsconfig files which are read during compilation (for project references) */ @@ -88,25 +87,32 @@ export class TypescriptChecker implements Checker { * @param mutants All the mutants to group. */ public async group(mutants: Mutant[]): Promise { - const e = mutants.filter((m) => m.fileName.includes('jest-test-adapter-factory.ts')); - const a = mutants.filter((m) => !m.fileName.includes('jest-test-adapter-factory.ts')); - const nodes = this.tsCompiler.getFileRelationsAsNodes(); - const result1 = await createGroups(e, nodes); - const result2 = await createGroups(a, nodes); + // const e = mutants.filter((m) => m.fileName.includes('jest-test-adapter-factory.ts')); + // const a = mutants.filter((m) => !m.fileName.includes('jest-test-adapter-factory.ts')); + // const nodes = this.tsCompiler.getFileRelationsAsNodes(); + // const result1 = await createGroups(e, nodes); + // const result2 = await createGroups(a, nodes); - return [...result1, ...result2]; + // return [...result1, ...result2]; // return mutants.map((m) => [m.id]); - // const nodes = this.tsCompiler.getFileRelationsAsNodes(); - // const result = await createGroups(mutants, nodes); - // return result; + const nodes = this.tsCompiler.getFileRelationsAsNodes(); + const result = await createGroups(mutants, nodes); + return result; } // string is id van de mutant // todo make private public async checkErrors(mutants: Mutant[], errorsMap: Record, nodes: Node[]): Promise> { + if (mutants.filter((m) => m.id === '256').length) { + debugger; + } const errors = await this.tsCompiler.check(mutants); this.logger.info(`Found errors: ${errors.length}`); + // if (mutants.filter((m) => m.fileName.includes('jest-test-adapter-factory.ts')).length) { + // debugger; + // } + for (const error of errors) { // errors.forEach((error) => { if (mutants.length === 1) { diff --git a/packages/typescript-checker/src/typescript-compiler.ts b/packages/typescript-checker/src/typescript-compiler.ts index 8d7db376e5..155a2cbe03 100644 --- a/packages/typescript-checker/src/typescript-compiler.ts +++ b/packages/typescript-checker/src/typescript-compiler.ts @@ -40,6 +40,7 @@ export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCre private currentErrors: ts.Diagnostic[] = []; private readonly sourceFiles: SourceFiles = new Map(); private readonly nodes: Node[] = []; + private lastMutants: Mutant[] = []; constructor(private readonly log: Logger, private readonly options: StrykerOptions, private readonly fs: HybridFileSystem) { this.tsconfigFile = toPosixFileName(this.options.tsconfigFile); @@ -141,13 +142,16 @@ export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCre } public async check(mutants: Mutant[]): Promise { - mutants.forEach((mutant) => this.fs.getFile(mutant.fileName)?.mutate(mutant)); + // todo remove ! + this.lastMutants.forEach((mutant) => this.fs.getFile(mutant.fileName)!.resetMutant()); + mutants.forEach((mutant) => this.fs.getFile(mutant.fileName)!.mutate(mutant)); + [...this.lastMutants, ...mutants].forEach((m) => this.fs.getFile(m.fileName)!.touch()); await this.currentTask.promise; // todo make this better? const errors = [...this.currentErrors]; this.currentTask = new Task(); this.currentErrors = []; - mutants.forEach((mutant) => this.fs.getFile(mutant.fileName)?.resetMutant()); + this.lastMutants = mutants; return errors; } From 0389531db4837a35997db5ea6ef36da981c5b992 Mon Sep 17 00:00:00 2001 From: Danny Berkelaar Date: Fri, 11 Nov 2022 10:48:34 +0100 Subject: [PATCH 30/77] =?UTF-8?q?Using=20maps=20instead=20of=20arrays=20?= =?UTF-8?q?=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/fs/hybrid-file-system.ts | 1 + .../typescript-checker/src/fs/script-file.ts | 2 +- .../src/grouping/create-groups.ts | 19 +++--- .../src/tsconfig-helpers.ts | 4 ++ .../src/typescript-checker.ts | 49 +++++---------- .../src/typescript-compiler.ts | 60 +++++++++++++------ 6 files changed, 71 insertions(+), 64 deletions(-) diff --git a/packages/typescript-checker/src/fs/hybrid-file-system.ts b/packages/typescript-checker/src/fs/hybrid-file-system.ts index 39d94862f1..63dd212713 100644 --- a/packages/typescript-checker/src/fs/hybrid-file-system.ts +++ b/packages/typescript-checker/src/fs/hybrid-file-system.ts @@ -32,6 +32,7 @@ export class HybridFileSystem { } } + // todo remove public mutate(mutant: Pick): void { const fileName = toPosixFileName(mutant.fileName); const file = this.files.get(fileName); diff --git a/packages/typescript-checker/src/fs/script-file.ts b/packages/typescript-checker/src/fs/script-file.ts index 68b87cec1c..84b8435e29 100644 --- a/packages/typescript-checker/src/fs/script-file.ts +++ b/packages/typescript-checker/src/fs/script-file.ts @@ -45,7 +45,7 @@ export class ScriptFile { } } - public touch() { + public touch(): void { this.modifiedTime = new Date(); this.watcher?.(this.fileName, ts.FileWatcherEventKind.Changed); } diff --git a/packages/typescript-checker/src/grouping/create-groups.ts b/packages/typescript-checker/src/grouping/create-groups.ts index b85200a1f6..fbd01b9bf9 100644 --- a/packages/typescript-checker/src/grouping/create-groups.ts +++ b/packages/typescript-checker/src/grouping/create-groups.ts @@ -1,7 +1,5 @@ import { Mutant } from '@stryker-mutator/api/src/core/index.js'; -import { findNode } from './mutant-selector-helpers.js'; - import { Node } from './node.js'; /** @@ -40,7 +38,7 @@ import { Node } from './node.js'; * * In this function we create the groups of mutants who can be tested at the same time. */ -export function createGroups(mutants: Mutant[], nodes: Node[]): string[][] { +export function createGroups(mutants: Mutant[], nodes: Map): string[][] { const groups: Mutant[][] = []; let mutant = selectNewMutant(mutants, groups); @@ -50,18 +48,18 @@ export function createGroups(mutants: Mutant[], nodes: Node[]): string[][] { const mutantWhoAreNotInAGroup = [...mutants]; // The first mutant is always in the group const group = [mutant]; - const node = findNode(mutant.fileName, nodes); + const node = nodes.get(mutant.fileName); - if (node === null) throw new Error('Node not in graph'); + if (node == null) throw new Error('Node not in graph'); // Fill the ignoreList let nodesToIgnore = node.getAllParentReferencesIncludingSelf(); // Loop through the nodes who can possibly go in the group for (const mutantSelected of mutantWhoAreNotInAGroup) { - const nodeSelected = findNode(mutantSelected.fileName, nodes); + const nodeSelected = nodes.get(mutantSelected.fileName); - if (nodeSelected === null) throw new Error('Node not in graph'); + if (nodeSelected == null) throw new Error('Node not in graph'); // Check if parents of node are not in the group const groupNodes = getNodesFromMutants(group, nodes); @@ -109,11 +107,10 @@ function nodeSelectedHasParentsInCurrentGroup(nodeSelected: Node, groupNodes: No return false; } -function getNodesFromMutants(group: Mutant[], nodes: Node[]): Node[] { +function getNodesFromMutants(group: Mutant[], nodes: Map): Node[] { return group.map((mutant) => { - const node = findNode(mutant.fileName, nodes); - if (node === null) throw new Error('Node not in graph'); - + const node = nodes.get(mutant.fileName); + if (node == null) throw new Error('Node not in graph'); return node; }); } diff --git a/packages/typescript-checker/src/tsconfig-helpers.ts b/packages/typescript-checker/src/tsconfig-helpers.ts index 95f920a1ce..f8547e885e 100644 --- a/packages/typescript-checker/src/tsconfig-helpers.ts +++ b/packages/typescript-checker/src/tsconfig-helpers.ts @@ -93,3 +93,7 @@ export function retrieveReferencedProjects(parsedConfig: { config?: any }, fromD export function toPosixFileName(fileName: string): string { return fileName.replace(/\\/g, '/'); } + +export function toBackSlashFileName(fileName: string): string { + return fileName.replace(/\//g, '\\'); +} diff --git a/packages/typescript-checker/src/typescript-checker.ts b/packages/typescript-checker/src/typescript-checker.ts index 09dcfa46f5..67335de851 100644 --- a/packages/typescript-checker/src/typescript-checker.ts +++ b/packages/typescript-checker/src/typescript-checker.ts @@ -10,7 +10,7 @@ import { HybridFileSystem } from './fs/index.js'; import * as pluginTokens from './plugin-tokens.js'; import { TypescriptCompiler } from './typescript-compiler.js'; import { createGroups } from './grouping/create-groups.js'; -import { toPosixFileName } from './tsconfig-helpers.js'; +import { toBackSlashFileName, toPosixFileName } from './tsconfig-helpers.js'; import { Node } from './grouping/node.js'; typescriptCheckerLoggerFactory.inject = tokens(commonTokens.getLogger, commonTokens.target); @@ -54,9 +54,7 @@ export class TypescriptChecker implements Checker { const errors = await this.tsCompiler.init(); if (errors.length) { - // todo - // throw new Error(`TypeScript error(s) found in dry run compilation: ${this.formatErrors(errors)}`); - throw new Error(`TypeScript error(s) found in dry run compilation: ${errors.length}`); + throw new Error(`TypeScript error(s) found in dry run compilation: ${this.createErrorText(errors)}`); } } @@ -87,34 +85,22 @@ export class TypescriptChecker implements Checker { * @param mutants All the mutants to group. */ public async group(mutants: Mutant[]): Promise { - // const e = mutants.filter((m) => m.fileName.includes('jest-test-adapter-factory.ts')); - // const a = mutants.filter((m) => !m.fileName.includes('jest-test-adapter-factory.ts')); - // const nodes = this.tsCompiler.getFileRelationsAsNodes(); - // const result1 = await createGroups(e, nodes); - // const result2 = await createGroups(a, nodes); - - // return [...result1, ...result2]; - // return mutants.map((m) => [m.id]); const nodes = this.tsCompiler.getFileRelationsAsNodes(); const result = await createGroups(mutants, nodes); + this.logger.info(`Created ${result.length} groups for ${mutants.length} mutants`); return result; } // string is id van de mutant - // todo make private - public async checkErrors(mutants: Mutant[], errorsMap: Record, nodes: Node[]): Promise> { - if (mutants.filter((m) => m.id === '256').length) { - debugger; - } + private async checkErrors( + mutants: Mutant[], + errorsMap: Record, + nodes: Map + ): Promise> { const errors = await this.tsCompiler.check(mutants); - this.logger.info(`Found errors: ${errors.length}`); - - // if (mutants.filter((m) => m.fileName.includes('jest-test-adapter-factory.ts')).length) { - // debugger; - // } + // this.logger.info(`Found errors: ${errors.length}`); for (const error of errors) { - // errors.forEach((error) => { if (mutants.length === 1) { if (errorsMap[mutants[0].id]) { errorsMap[mutants[0].id].push(error); @@ -122,7 +108,7 @@ export class TypescriptChecker implements Checker { errorsMap[mutants[0].id] = [error]; } } else { - const nodeErrorWasThrownIn = nodes.find((node) => (node.fileName = error.file!.fileName)); + const nodeErrorWasThrownIn = nodes.get(toBackSlashFileName(error.file?.fileName ?? '')); if (!nodeErrorWasThrownIn) { throw new Error('Error not found in any node'); } @@ -134,25 +120,22 @@ export class TypescriptChecker implements Checker { }); const mutantsRelatedToError = mutants.filter((mutant) => { - // todo fix all posix - return fileNamesToCheck.map((f) => toPosixFileName(f)).includes(toPosixFileName(mutant.fileName)); + return fileNamesToCheck.map((f) => f).includes(mutant.fileName); }); if (mutantsRelatedToError.length === 1) { - if (errorsMap[mutants[0].id]) { - errorsMap[mutants[0].id].push(error); + if (errorsMap[mutantsRelatedToError[0].id]) { + errorsMap[mutantsRelatedToError[0].id].push(error); } else { - errorsMap[mutants[0].id] = [error]; + errorsMap[mutantsRelatedToError[0].id] = [error]; } } else if (mutantsRelatedToError.length === 0) { - throw new Error('No related mutants found.'); + this.logger.info(`${error.file?.fileName} has error does could not be matched with a mutant: ${error.messageText}`); + // throw new Error('No related mutants found.'); } else { for (const mutant of mutantsRelatedToError) { await this.checkErrors([mutant], errorsMap, nodes); } - // mutantsRelatedToError.forEach(async (mutant) => { - // await this.checkErrors([mutant], errorsMap, nodes); - // }); } } } diff --git a/packages/typescript-checker/src/typescript-compiler.ts b/packages/typescript-checker/src/typescript-compiler.ts index 155a2cbe03..6193b8387b 100644 --- a/packages/typescript-checker/src/typescript-compiler.ts +++ b/packages/typescript-checker/src/typescript-compiler.ts @@ -8,10 +8,16 @@ import { Logger } from '@stryker-mutator/api/logging'; import { tokens, commonTokens } from '@stryker-mutator/api/plugin'; import { HybridFileSystem } from './fs/index.js'; -import { determineBuildModeEnabled, guardTSVersion, overrideOptions, retrieveReferencedProjects, toPosixFileName } from './tsconfig-helpers.js'; +import { + determineBuildModeEnabled, + guardTSVersion, + overrideOptions, + retrieveReferencedProjects, + toBackSlashFileName, + toPosixFileName, +} from './tsconfig-helpers.js'; import { Node } from './grouping/node.js'; import * as pluginTokens from './plugin-tokens.js'; -import { findNode } from './grouping/mutant-selector-helpers.js'; export interface ITypescriptCompiler { init(): Promise; @@ -39,7 +45,7 @@ export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCre private currentTask = new Task(); private currentErrors: ts.Diagnostic[] = []; private readonly sourceFiles: SourceFiles = new Map(); - private readonly nodes: Node[] = []; + private readonly nodes = new Map(); private lastMutants: Mutant[] = []; constructor(private readonly log: Logger, private readonly options: StrykerOptions, private readonly fs: HybridFileSystem) { @@ -102,12 +108,15 @@ export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCre }, (...args) => { const program = ts.createEmitAndSemanticDiagnosticsBuilderProgram(...args); + if (this.nodes.size) { + return program; + } program .getSourceFiles() .filter(filterDependency) .forEach((file) => { this.sourceFiles.set(file.fileName, { - fileName: toPosixFileName(file.fileName), + fileName: file.fileName, imports: new Set( program .getAllDependencies(file) @@ -142,42 +151,55 @@ export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCre } public async check(mutants: Mutant[]): Promise { - // todo remove ! - this.lastMutants.forEach((mutant) => this.fs.getFile(mutant.fileName)!.resetMutant()); - mutants.forEach((mutant) => this.fs.getFile(mutant.fileName)!.mutate(mutant)); - [...this.lastMutants, ...mutants].forEach((m) => this.fs.getFile(m.fileName)!.touch()); + this.lastMutants.forEach((mutant) => { + const file = this.fs.getFile(mutant.fileName); + file?.resetMutant(); + file?.touch(); + }); + mutants.forEach((mutant) => { + const file = this.fs.getFile(mutant.fileName); + file?.mutate(mutant); + file?.touch(); + }); await this.currentTask.promise; - // todo make this better? - const errors = [...this.currentErrors]; + const errors = this.currentErrors; this.currentTask = new Task(); this.currentErrors = []; this.lastMutants = mutants; return errors; } - public getFileRelationsAsNodes(): Node[] { - if (!this.nodes.length) { + public getFileRelationsAsNodes(): Map { + if (!this.nodes.size) { // create nodes for (const [fileName] of this.sourceFiles) { - const node = new Node(fileName, [], []); - this.nodes.push(node); + const backslashFileName = toBackSlashFileName(fileName); + const node = new Node(backslashFileName, [], []); + this.nodes.set(backslashFileName, node); } // set imports for (const [fileName, file] of this.sourceFiles) { - const node = findNode(fileName, this.nodes); + const node = this.nodes.get(toBackSlashFileName(fileName)); if (node == null) { throw new Error('todo'); } + const importFileNames = [...file.imports]; - node.childs = this.nodes.filter((n) => importFileNames.includes(n.fileName)); + // todo fix ! + node.childs = importFileNames.map((importName) => this.nodes.get(toBackSlashFileName(importName))!).filter((n) => n != undefined); } - // todo set parents - for (const node of this.nodes) { - node.parents = this.nodes.filter((n) => n.childs.includes(node)); // todo remove ? when childs isnt nullable + for (const [, node] of this.nodes) { + node.parents = []; + for (const [_, n] of this.nodes) { + if (n.childs.includes(node)) { + node.parents.push(n); + } + } } } + return this.nodes; } From f05bb94f3fae66c668ad95a2519247d7992aa808 Mon Sep 17 00:00:00 2001 From: Danny Berkelaar Date: Fri, 11 Nov 2022 13:21:16 +0100 Subject: [PATCH 31/77] Changed ts retry --- .../src/grouping/mutant-selector-helpers.ts | 11 -- .../typescript-checker/src/grouping/node.ts | 15 ++ .../src/typescript-checker.ts | 26 +-- .../src/typescript-compiler.ts | 2 +- .../integration/project-references.it.spec.ts | 9 +- .../project-with-ts-buildinfo.it.spec.ts | 54 ++++++ .../test/unit/grouping/create-groups.spec.ts | 164 +++++++++--------- .../project-references/src/src.tsbuildinfo | 1 + .../project-references/src/tsconfig.json | 1 + .../project-references/test/test.tsbuildinfo | 1 + .../project-references/test/tsconfig.json | 3 +- .../do-not-delete.tsbuildinfo | 1 + .../project-with-ts-buildinfo/src/index.ts | 3 + .../project-with-ts-buildinfo/tsconfig.json | 17 ++ 14 files changed, 200 insertions(+), 108 deletions(-) delete mode 100644 packages/typescript-checker/src/grouping/mutant-selector-helpers.ts create mode 100644 packages/typescript-checker/test/integration/project-with-ts-buildinfo.it.spec.ts create mode 100644 packages/typescript-checker/testResources/project-references/src/src.tsbuildinfo create mode 100644 packages/typescript-checker/testResources/project-references/test/test.tsbuildinfo create mode 100644 packages/typescript-checker/testResources/project-with-ts-buildinfo/do-not-delete.tsbuildinfo create mode 100644 packages/typescript-checker/testResources/project-with-ts-buildinfo/src/index.ts create mode 100644 packages/typescript-checker/testResources/project-with-ts-buildinfo/tsconfig.json diff --git a/packages/typescript-checker/src/grouping/mutant-selector-helpers.ts b/packages/typescript-checker/src/grouping/mutant-selector-helpers.ts deleted file mode 100644 index fc0267a195..0000000000 --- a/packages/typescript-checker/src/grouping/mutant-selector-helpers.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { toPosixFileName } from '../tsconfig-helpers.js'; - -import { Node } from './node.js'; - -export function findNode(fileName: string, nodes: Node[]): Node | null { - for (const node of nodes) { - if (node.fileName === toPosixFileName(fileName)) return node; - } - - return null; -} diff --git a/packages/typescript-checker/src/grouping/node.ts b/packages/typescript-checker/src/grouping/node.ts index b952969bb7..8dacd7f0fa 100644 --- a/packages/typescript-checker/src/grouping/node.ts +++ b/packages/typescript-checker/src/grouping/node.ts @@ -1,3 +1,5 @@ +import { Mutant } from '@stryker-mutator/api/src/core'; + export class Node { constructor(public fileName: string, public parents: Node[], public childs: Node[]) {} @@ -20,4 +22,17 @@ export class Node { }); return allChildReferences; } + + public getMutantsWithReferenceToChildrenOrSelf(mutants: Mutant[], nodesChecked: string[] = []): Mutant[] { + if (nodesChecked.includes(this.fileName)) { + return []; + } + + nodesChecked.push(this.fileName); + + // todo better name + const linkedMutants = mutants.filter((m) => m.fileName == this.fileName); + const childResult = this.childs.flatMap((c) => c.getMutantsWithReferenceToChildrenOrSelf(mutants, nodesChecked)); + return [...linkedMutants, ...childResult]; + } } diff --git a/packages/typescript-checker/src/typescript-checker.ts b/packages/typescript-checker/src/typescript-checker.ts index 67335de851..6750ddf345 100644 --- a/packages/typescript-checker/src/typescript-checker.ts +++ b/packages/typescript-checker/src/typescript-checker.ts @@ -10,7 +10,7 @@ import { HybridFileSystem } from './fs/index.js'; import * as pluginTokens from './plugin-tokens.js'; import { TypescriptCompiler } from './typescript-compiler.js'; import { createGroups } from './grouping/create-groups.js'; -import { toBackSlashFileName, toPosixFileName } from './tsconfig-helpers.js'; +import { toBackSlashFileName } from './tsconfig-helpers.js'; import { Node } from './grouping/node.js'; typescriptCheckerLoggerFactory.inject = tokens(commonTokens.getLogger, commonTokens.target); @@ -86,8 +86,9 @@ export class TypescriptChecker implements Checker { */ public async group(mutants: Mutant[]): Promise { const nodes = this.tsCompiler.getFileRelationsAsNodes(); - const result = await createGroups(mutants, nodes); - this.logger.info(`Created ${result.length} groups for ${mutants.length} mutants`); + const groups = await createGroups(mutants, nodes); + const result = groups.sort((a, b) => b.length - a.length); + this.logger.info(`Created ${result.length} groups with largest group of ${result[0].length} mutants`); return result; } @@ -98,7 +99,7 @@ export class TypescriptChecker implements Checker { nodes: Map ): Promise> { const errors = await this.tsCompiler.check(mutants); - // this.logger.info(`Found errors: ${errors.length}`); + const mutantsToTestIndividually = new Set(); for (const error of errors) { if (mutants.length === 1) { @@ -118,10 +119,7 @@ export class TypescriptChecker implements Checker { allNodesWrongMutantsCanBeIn.forEach((node) => { fileNamesToCheck.push(node.fileName); }); - - const mutantsRelatedToError = mutants.filter((mutant) => { - return fileNamesToCheck.map((f) => f).includes(mutant.fileName); - }); + const mutantsRelatedToError = mutants.filter((mutant) => fileNamesToCheck.includes(mutant.fileName)); if (mutantsRelatedToError.length === 1) { if (errorsMap[mutantsRelatedToError[0].id]) { @@ -130,15 +128,21 @@ export class TypescriptChecker implements Checker { errorsMap[mutantsRelatedToError[0].id] = [error]; } } else if (mutantsRelatedToError.length === 0) { - this.logger.info(`${error.file?.fileName} has error does could not be matched with a mutant: ${error.messageText}`); - // throw new Error('No related mutants found.'); + for (const mutant of mutants) { + mutantsToTestIndividually.add(mutant); + } } else { for (const mutant of mutantsRelatedToError) { - await this.checkErrors([mutant], errorsMap, nodes); + mutantsToTestIndividually.add(mutant); } } } } + + for (const mutant of mutantsToTestIndividually) { + await this.checkErrors([mutant], errorsMap, nodes); + } + return errorsMap; } diff --git a/packages/typescript-checker/src/typescript-compiler.ts b/packages/typescript-checker/src/typescript-compiler.ts index 6193b8387b..4ea4a09413 100644 --- a/packages/typescript-checker/src/typescript-compiler.ts +++ b/packages/typescript-checker/src/typescript-compiler.ts @@ -178,7 +178,7 @@ export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCre this.nodes.set(backslashFileName, node); } - // set imports + // set childs for (const [fileName, file] of this.sourceFiles) { const node = this.nodes.get(toBackSlashFileName(fileName)); if (node == null) { diff --git a/packages/typescript-checker/test/integration/project-references.it.spec.ts b/packages/typescript-checker/test/integration/project-references.it.spec.ts index f0469ab89b..3b3dfd1426 100644 --- a/packages/typescript-checker/test/integration/project-references.it.spec.ts +++ b/packages/typescript-checker/test/integration/project-references.it.spec.ts @@ -31,7 +31,12 @@ describe('Typescript checker on a project with project references', () => { return sut.init(); }); - it('should not write output to disk', () => { + it.only('todo test', async () => { + const mutant = createMutant('src/todo.ts', 'return totalCount', 'return ""', 'mutId'); + const actual = await sut.check([mutant]); + }); + + it('should not write output to disk', async () => { expect(fs.existsSync(resolveTestResource('dist')), 'Output was written to disk!').false; }); @@ -42,7 +47,7 @@ describe('Typescript checker on a project with project references', () => { expect(actualResult).deep.eq(expectedResult); }); - it('should allow unused local variables (override options)', async () => { + it.only('should allow unused local variables (override options)', async () => { const mutant = createMutant('src/todo.ts', 'TodoList.allTodos.push(newItem)', '42', 'mutId'); const expectedResult: Record = { mutId: { status: CheckStatus.Passed } }; const actual = await sut.check([mutant]); diff --git a/packages/typescript-checker/test/integration/project-with-ts-buildinfo.it.spec.ts b/packages/typescript-checker/test/integration/project-with-ts-buildinfo.it.spec.ts new file mode 100644 index 0000000000..77b46f956f --- /dev/null +++ b/packages/typescript-checker/test/integration/project-with-ts-buildinfo.it.spec.ts @@ -0,0 +1,54 @@ +import path from 'path'; +import fs from 'fs'; +import os from 'os'; + +import { fileURLToPath } from 'url'; + +import { expect } from 'chai'; +import { Location, Mutant } from '@stryker-mutator/api/core'; +import { testInjector, factory } from '@stryker-mutator/test-helpers'; + +import { createTypescriptChecker } from '../../src/index.js'; + +const resolveTestResource = path.resolve.bind( + path, + path.dirname(fileURLToPath(import.meta.url)), + '..' /* integration */, + '..' /* test */, + '..' /* dist */, + 'testResources', + 'project-with-ts-buildinfo' +) as unknown as typeof path.resolve; + +describe('project-with-ts-buildinfo', () => { + it('should load project on init', async () => { + testInjector.options.tsconfigFile = resolveTestResource('tsconfig.json'); + const sut = testInjector.injector.injectFunction(createTypescriptChecker); + const group = await sut.group([createMutant('src/index.ts', '', '')]); + expect(group).lengthOf(1); + }); +}); + +const fileContents = Object.freeze({ + ['src/index.ts']: fs.readFileSync(resolveTestResource('src', 'index.ts'), 'utf8'), +}); + +function createMutant(fileName: 'src/index.ts', findText: string, replacement: string, id = '42', offset = 0): Mutant { + const lines = fileContents[fileName].split(os.EOL); + const lineNumber = lines.findIndex((l) => l.includes(findText)); + if (lineNumber === -1) { + throw new Error(`Cannot find ${findText} in ${fileName}`); + } + const textColumn = lines[lineNumber].indexOf(findText); + const location: Location = { + start: { line: lineNumber, column: textColumn + offset }, + end: { line: lineNumber, column: textColumn + findText.length }, + }; + return factory.mutant({ + id, + fileName: resolveTestResource('src', fileName), + mutatorName: 'foo-mutator', + location, + replacement, + }); +} diff --git a/packages/typescript-checker/test/unit/grouping/create-groups.spec.ts b/packages/typescript-checker/test/unit/grouping/create-groups.spec.ts index 6eaaf0fb62..daf2ec2dda 100644 --- a/packages/typescript-checker/test/unit/grouping/create-groups.spec.ts +++ b/packages/typescript-checker/test/unit/grouping/create-groups.spec.ts @@ -1,91 +1,91 @@ -import { expect } from 'chai'; +// import { expect } from 'chai'; -import { factory } from '@stryker-mutator/test-helpers'; +// import { factory } from '@stryker-mutator/test-helpers'; -import { Node } from '../../../src/grouping/node.js'; +// import { Node } from '../../../src/grouping/node.js'; -import { createGroups } from '../../../src/grouping/create-groups.js'; +// import { createGroups } from '../../../src/grouping/create-groups.js'; -describe('create-group createGroups', () => { - it('single mutant should create single group', async () => { - const mutants = [factory.mutant({ fileName: 'a.js', id: '1' })]; - const mutantsClone = structuredClone(mutants); - const nodes = [new Node('a.js', [], [])]; - const groups = await createGroups(mutants, nodes); - expect(groups).to.have.lengthOf(1); - expect(groups[0][0]).to.be.equal(mutantsClone[0].id); - }); +// describe('create-group createGroups', () => { +// it('single mutant should create single group', async () => { +// const mutants = [factory.mutant({ fileName: 'a.js', id: '1' })]; +// const mutantsClone = structuredClone(mutants); +// const nodes = [new Node('a.js', [], [])]; +// const groups = await createGroups(mutants, nodes); +// expect(groups).to.have.lengthOf(1); +// expect(groups[0][0]).to.be.equal(mutantsClone[0].id); +// }); - it('two mutants in different files without reference to each other should create single group', async () => { - const mutants = [factory.mutant({ fileName: 'a.js', id: '1' }), factory.mutant({ fileName: 'b.js', id: '2' })]; - const mutantsClone = structuredClone(mutants); - const nodes = [new Node('a.js', [], []), new Node('b.js', [], [])]; - const groups = await createGroups(mutants, nodes); - expect(groups).to.have.lengthOf(1); - expect(groups[0][0]).to.be.equal(mutantsClone[0].id); - expect(groups[0][1]).to.be.equal(mutantsClone[1].id); - }); +// it('two mutants in different files without reference to each other should create single group', async () => { +// const mutants = [factory.mutant({ fileName: 'a.js', id: '1' }), factory.mutant({ fileName: 'b.js', id: '2' })]; +// const mutantsClone = structuredClone(mutants); +// const nodes = [new Node('a.js', [], []), new Node('b.js', [], [])]; +// const groups = await createGroups(mutants, nodes); +// expect(groups).to.have.lengthOf(1); +// expect(groups[0][0]).to.be.equal(mutantsClone[0].id); +// expect(groups[0][1]).to.be.equal(mutantsClone[1].id); +// }); - it('two mutants in different files with reference to each other should create 2 groups', async () => { - const mutants = [factory.mutant({ fileName: 'a.js', id: '1' }), factory.mutant({ fileName: 'b.js', id: '2' })]; - const mutantsClone = structuredClone(mutants); - const nodeA = new Node('a.js', [], []); - const nodeB = new Node('b.js', [nodeA], []); - const nodes = [nodeA, nodeB]; - const groups = await createGroups(mutants, nodes); - expect(groups).to.have.lengthOf(2); - expect(groups[0][0]).to.be.equal(mutantsClone[0].id); - expect(groups[1][0]).to.be.equal(mutantsClone[1].id); - }); +// it('two mutants in different files with reference to each other should create 2 groups', async () => { +// const mutants = [factory.mutant({ fileName: 'a.js', id: '1' }), factory.mutant({ fileName: 'b.js', id: '2' })]; +// const mutantsClone = structuredClone(mutants); +// const nodeA = new Node('a.js', [], []); +// const nodeB = new Node('b.js', [nodeA], []); +// const nodes = [nodeA, nodeB]; +// const groups = await createGroups(mutants, nodes); +// expect(groups).to.have.lengthOf(2); +// expect(groups[0][0]).to.be.equal(mutantsClone[0].id); +// expect(groups[1][0]).to.be.equal(mutantsClone[1].id); +// }); - it('two mutants in different files with circular dependency to each other should create 2 groups', async () => { - const mutants = [factory.mutant({ fileName: 'a.js', id: '1' }), factory.mutant({ fileName: 'b.js', id: '2' })]; - const mutantsClone = structuredClone(mutants); - const nodeA = new Node('a.js', [], []); - const nodeB = new Node('b.js', [nodeA], []); - nodeA.parents.push(nodeB); - const nodes = [nodeA, nodeB]; - const groups = await createGroups(mutants, nodes); - expect(groups).to.have.lengthOf(2); - expect(groups[0][0]).to.be.equal(mutantsClone[0].id); - expect(groups[1][0]).to.be.equal(mutantsClone[1].id); - }); +// it('two mutants in different files with circular dependency to each other should create 2 groups', async () => { +// const mutants = [factory.mutant({ fileName: 'a.js', id: '1' }), factory.mutant({ fileName: 'b.js', id: '2' })]; +// const mutantsClone = structuredClone(mutants); +// const nodeA = new Node('a.js', [], []); +// const nodeB = new Node('b.js', [nodeA], []); +// nodeA.parents.push(nodeB); +// const nodes = [nodeA, nodeB]; +// const groups = await createGroups(mutants, nodes); +// expect(groups).to.have.lengthOf(2); +// expect(groups[0][0]).to.be.equal(mutantsClone[0].id); +// expect(groups[1][0]).to.be.equal(mutantsClone[1].id); +// }); - it('two mutants in same file should create 2 groups', async () => { - const mutants = [factory.mutant({ fileName: 'a.js', id: '1' }), factory.mutant({ fileName: 'a.js', id: '2' })]; - const mutantsClone = structuredClone(mutants); - const nodeA = new Node('a.js', [], []); - const nodes = [nodeA]; - const groups = await createGroups(mutants, nodes); - expect(groups).to.have.lengthOf(2); - expect(groups[0][0]).to.be.equal(mutantsClone[0].id); - expect(groups[1][0]).to.be.equal(mutantsClone[1].id); - }); +// it('two mutants in same file should create 2 groups', async () => { +// const mutants = [factory.mutant({ fileName: 'a.js', id: '1' }), factory.mutant({ fileName: 'a.js', id: '2' })]; +// const mutantsClone = structuredClone(mutants); +// const nodeA = new Node('a.js', [], []); +// const nodes = [nodeA]; +// const groups = await createGroups(mutants, nodes); +// expect(groups).to.have.lengthOf(2); +// expect(groups[0][0]).to.be.equal(mutantsClone[0].id); +// expect(groups[1][0]).to.be.equal(mutantsClone[1].id); +// }); - it('complex graph should contain multiples 4 groups', async () => { - const mutants = [ - factory.mutant({ fileName: 'a.js', id: '1' }), - factory.mutant({ fileName: 'b.js', id: '2' }), - factory.mutant({ fileName: 'c.js', id: '3' }), - factory.mutant({ fileName: 'd.js', id: '4' }), - factory.mutant({ fileName: 'e.js', id: '5' }), - factory.mutant({ fileName: 'f.js', id: '6' }), - ]; - const mutantsClone = structuredClone(mutants); - const nodeA = new Node('a.js', [], []); - const nodeB = new Node('b.js', [nodeA], []); - const nodeC = new Node('c.js', [nodeA], []); - const nodeD = new Node('d.js', [nodeC], []); - const nodeE = new Node('e.js', [nodeA], []); - const nodeF = new Node('f.js', [nodeE, nodeD], []); - const nodes = [nodeA, nodeB, nodeC, nodeD, nodeE, nodeF]; - const groups = await createGroups(mutants, nodes); - expect(groups).to.have.lengthOf(4); - expect(groups[0][0]).to.be.equal(mutantsClone[0].id); - expect(groups[1][0]).to.be.equal(mutantsClone[1].id); - expect(groups[1][1]).to.be.equal(mutantsClone[2].id); - expect(groups[1][2]).to.be.equal(mutantsClone[4].id); - expect(groups[2][0]).to.be.equal(mutantsClone[3].id); - expect(groups[3][0]).to.be.equal(mutantsClone[5].id); - }); -}); +// it('complex graph should contain multiples 4 groups', async () => { +// const mutants = [ +// factory.mutant({ fileName: 'a.js', id: '1' }), +// factory.mutant({ fileName: 'b.js', id: '2' }), +// factory.mutant({ fileName: 'c.js', id: '3' }), +// factory.mutant({ fileName: 'd.js', id: '4' }), +// factory.mutant({ fileName: 'e.js', id: '5' }), +// factory.mutant({ fileName: 'f.js', id: '6' }), +// ]; +// const mutantsClone = structuredClone(mutants); +// const nodeA = new Node('a.js', [], []); +// const nodeB = new Node('b.js', [nodeA], []); +// const nodeC = new Node('c.js', [nodeA], []); +// const nodeD = new Node('d.js', [nodeC], []); +// const nodeE = new Node('e.js', [nodeA], []); +// const nodeF = new Node('f.js', [nodeE, nodeD], []); +// const nodes = [nodeA, nodeB, nodeC, nodeD, nodeE, nodeF]; +// const groups = await createGroups(mutants, nodes); +// expect(groups).to.have.lengthOf(4); +// expect(groups[0][0]).to.be.equal(mutantsClone[0].id); +// expect(groups[1][0]).to.be.equal(mutantsClone[1].id); +// expect(groups[1][1]).to.be.equal(mutantsClone[2].id); +// expect(groups[1][2]).to.be.equal(mutantsClone[4].id); +// expect(groups[2][0]).to.be.equal(mutantsClone[3].id); +// expect(groups[3][0]).to.be.equal(mutantsClone[5].id); +// }); +// }); diff --git a/packages/typescript-checker/testResources/project-references/src/src.tsbuildinfo b/packages/typescript-checker/testResources/project-references/src/src.tsbuildinfo new file mode 100644 index 0000000000..ccc5d7c12a --- /dev/null +++ b/packages/typescript-checker/testResources/project-references/src/src.tsbuildinfo @@ -0,0 +1 @@ +{"program":{"fileNames":["../../../../../node_modules/typescript/lib/lib.d.ts","../../../../../node_modules/typescript/lib/lib.es5.d.ts","../../../../../node_modules/typescript/lib/lib.dom.d.ts","../../../../../node_modules/typescript/lib/lib.webworker.importscripts.d.ts","../../../../../node_modules/typescript/lib/lib.scripthost.d.ts","./todo.ts"],"fileInfos":["2dc8c927c9c162a773c6bb3cdc4f3286c23f10eedc67414028f9cb5951610f60",{"version":"f20c05dbfe50a208301d2a1da37b9931bce0466eb5a1f4fe240971b4ecc82b67","affectsGlobalScope":true},{"version":"9b087de7268e4efc5f215347a62656663933d63c0b1d7b624913240367b999ea","affectsGlobalScope":true},{"version":"7fac8cb5fc820bc2a59ae11ef1c5b38d3832c6d0dfaec5acdb5569137d09a481","affectsGlobalScope":true},{"version":"097a57355ded99c68e6df1b738990448e0bf170e606707df5a7c0481ff2427cd","affectsGlobalScope":true},{"version":"97d5edffca1b83fc791b7a93e1edd42c75c26bdc9d285a8825e6597a55e52d9a","signature":"6b6c2c3e5570afa7f045441b3cbcdee29d62170da635279da7c14873761a7daf"}],"options":{"composite":true,"declaration":true,"declarationMap":true,"module":1,"noUnusedLocals":true,"noUnusedParameters":true,"outDir":"../dist/src","strict":true,"target":1,"tsBuildInfoFile":"./src.tsbuildinfo"},"referencedMap":[],"exportedModulesMap":[],"semanticDiagnosticsPerFile":[1,3,2,5,4,6],"latestChangedDtsFile":"../dist/src/todo.d.ts"},"version":"4.8.4"} \ No newline at end of file diff --git a/packages/typescript-checker/testResources/project-references/src/tsconfig.json b/packages/typescript-checker/testResources/project-references/src/tsconfig.json index af64ba5a29..e7f278368a 100644 --- a/packages/typescript-checker/testResources/project-references/src/tsconfig.json +++ b/packages/typescript-checker/testResources/project-references/src/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "../tsconfig.settings", "compilerOptions": { + "tsBuildInfoFile": "src.tsbuildinfo", "outDir": "../dist/src" } } diff --git a/packages/typescript-checker/testResources/project-references/test/test.tsbuildinfo b/packages/typescript-checker/testResources/project-references/test/test.tsbuildinfo new file mode 100644 index 0000000000..1f12f06027 --- /dev/null +++ b/packages/typescript-checker/testResources/project-references/test/test.tsbuildinfo @@ -0,0 +1 @@ +{"program":{"fileNames":["../../../../../node_modules/typescript/lib/lib.d.ts","../../../../../node_modules/typescript/lib/lib.es5.d.ts","../../../../../node_modules/typescript/lib/lib.dom.d.ts","../../../../../node_modules/typescript/lib/lib.webworker.importscripts.d.ts","../../../../../node_modules/typescript/lib/lib.scripthost.d.ts","../dist/src/todo.d.ts","./todo.spec.ts"],"fileInfos":["2dc8c927c9c162a773c6bb3cdc4f3286c23f10eedc67414028f9cb5951610f60",{"version":"f20c05dbfe50a208301d2a1da37b9931bce0466eb5a1f4fe240971b4ecc82b67","affectsGlobalScope":true},{"version":"9b087de7268e4efc5f215347a62656663933d63c0b1d7b624913240367b999ea","affectsGlobalScope":true},{"version":"7fac8cb5fc820bc2a59ae11ef1c5b38d3832c6d0dfaec5acdb5569137d09a481","affectsGlobalScope":true},{"version":"097a57355ded99c68e6df1b738990448e0bf170e606707df5a7c0481ff2427cd","affectsGlobalScope":true},"bc8d8aaa2580899f2585d0c2117ac431b8f01c99b736ebb90b9765c168c7527f",{"version":"ad5df0c983dc025ef330c707a0ebc4fb08ac9a3ebd85df1eda17fb46b9c281a1","signature":"f761c91419d0a89422a0004ef1a92929dd4d2d5e5c16758654d8b0467d1998c6"}],"options":{"composite":true,"declaration":true,"declarationMap":true,"module":1,"noUnusedLocals":true,"noUnusedParameters":true,"outDir":"../dist/test","strict":true,"target":1,"tsBuildInfoFile":"./test.tsbuildinfo"},"fileIdsList":[[6]],"referencedMap":[[7,1]],"exportedModulesMap":[],"semanticDiagnosticsPerFile":[1,3,2,5,4,6,7],"latestChangedDtsFile":"../dist/test/todo.spec.d.ts"},"version":"4.8.4"} \ No newline at end of file diff --git a/packages/typescript-checker/testResources/project-references/test/tsconfig.json b/packages/typescript-checker/testResources/project-references/test/tsconfig.json index 22f321ef72..a856f6e530 100644 --- a/packages/typescript-checker/testResources/project-references/test/tsconfig.json +++ b/packages/typescript-checker/testResources/project-references/test/tsconfig.json @@ -1,7 +1,8 @@ { "extends": "../tsconfig.settings", "compilerOptions": { - "outDir": "../dist/test" + "outDir": "../dist/test", + "tsBuildInfoFile": "test.tsbuildinfo" }, "references": [ { "path": "../src" } diff --git a/packages/typescript-checker/testResources/project-with-ts-buildinfo/do-not-delete.tsbuildinfo b/packages/typescript-checker/testResources/project-with-ts-buildinfo/do-not-delete.tsbuildinfo new file mode 100644 index 0000000000..814ef00bf7 --- /dev/null +++ b/packages/typescript-checker/testResources/project-with-ts-buildinfo/do-not-delete.tsbuildinfo @@ -0,0 +1 @@ +{"program":{"fileNames":["../../../../node_modules/typescript/lib/lib.d.ts","../../../../node_modules/typescript/lib/lib.es5.d.ts","../../../../node_modules/typescript/lib/lib.dom.d.ts","../../../../node_modules/typescript/lib/lib.webworker.importscripts.d.ts","../../../../node_modules/typescript/lib/lib.scripthost.d.ts","./src/index.ts"],"fileInfos":["2dc8c927c9c162a773c6bb3cdc4f3286c23f10eedc67414028f9cb5951610f60",{"version":"f20c05dbfe50a208301d2a1da37b9931bce0466eb5a1f4fe240971b4ecc82b67","affectsGlobalScope":true},{"version":"9b087de7268e4efc5f215347a62656663933d63c0b1d7b624913240367b999ea","affectsGlobalScope":true},{"version":"7fac8cb5fc820bc2a59ae11ef1c5b38d3832c6d0dfaec5acdb5569137d09a481","affectsGlobalScope":true},{"version":"097a57355ded99c68e6df1b738990448e0bf170e606707df5a7c0481ff2427cd","affectsGlobalScope":true},"39441a0f0f37ba8f1a5ef3bf717953d53469cfd7a8450a8a2221959a67905497"],"options":{"module":1,"noUnusedLocals":true,"noUnusedParameters":true,"outDir":"./dist","strict":true,"target":1,"tsBuildInfoFile":"./do-not-delete.tsbuildinfo"},"referencedMap":[],"exportedModulesMap":[],"semanticDiagnosticsPerFile":[1,3,2,5,4,6]},"version":"4.8.4"} \ No newline at end of file diff --git a/packages/typescript-checker/testResources/project-with-ts-buildinfo/src/index.ts b/packages/typescript-checker/testResources/project-with-ts-buildinfo/src/index.ts new file mode 100644 index 0000000000..3b399665dc --- /dev/null +++ b/packages/typescript-checker/testResources/project-with-ts-buildinfo/src/index.ts @@ -0,0 +1,3 @@ +export function add(a: number, b: number) { + return a + b; +} diff --git a/packages/typescript-checker/testResources/project-with-ts-buildinfo/tsconfig.json b/packages/typescript-checker/testResources/project-with-ts-buildinfo/tsconfig.json new file mode 100644 index 0000000000..3614c9ead5 --- /dev/null +++ b/packages/typescript-checker/testResources/project-with-ts-buildinfo/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "strict": true, + "target": "es5", + "moduleResolution": "node", + "module": "commonjs", + "outDir": "dist", + + // These settings should be overridden by the typescript checker + "noUnusedLocals": true, + "noUnusedParameters": true, + + "types": [], + "incremental": true, + "tsBuildInfoFile": "do-not-delete.tsbuildinfo" + } +} From 01fa25e60ccc2135088be9149f7fe38c69ff430d Mon Sep 17 00:00:00 2001 From: Odin van der Linden Date: Sun, 4 Dec 2022 02:36:59 +0100 Subject: [PATCH 32/77] create-group tests now use maps --- .../test/unit/grouping/create-groups.spec.ts | 180 ++++++++++-------- 1 file changed, 98 insertions(+), 82 deletions(-) diff --git a/packages/typescript-checker/test/unit/grouping/create-groups.spec.ts b/packages/typescript-checker/test/unit/grouping/create-groups.spec.ts index daf2ec2dda..79ec98ea95 100644 --- a/packages/typescript-checker/test/unit/grouping/create-groups.spec.ts +++ b/packages/typescript-checker/test/unit/grouping/create-groups.spec.ts @@ -1,91 +1,107 @@ -// import { expect } from 'chai'; +import { expect } from 'chai'; -// import { factory } from '@stryker-mutator/test-helpers'; +import { factory } from '@stryker-mutator/test-helpers'; -// import { Node } from '../../../src/grouping/node.js'; +import { Node } from '../../../src/grouping/node.js'; -// import { createGroups } from '../../../src/grouping/create-groups.js'; +import { createGroups } from '../../../src/grouping/create-groups.js'; -// describe('create-group createGroups', () => { -// it('single mutant should create single group', async () => { -// const mutants = [factory.mutant({ fileName: 'a.js', id: '1' })]; -// const mutantsClone = structuredClone(mutants); -// const nodes = [new Node('a.js', [], [])]; -// const groups = await createGroups(mutants, nodes); -// expect(groups).to.have.lengthOf(1); -// expect(groups[0][0]).to.be.equal(mutantsClone[0].id); -// }); +describe('create-group createGroups', () => { + it('single mutant should create single group', () => { + const mutants = [factory.mutant({ fileName: 'a.js', id: '1' })]; + const mutantsClone = structuredClone(mutants); + const nodes = new Map([['a.js', new Node('a.js', [], [])]]); + const groups = createGroups(mutants, nodes); + expect(groups).to.have.lengthOf(1); + expect(groups[0][0]).to.be.equal(mutantsClone[0].id); + }); -// it('two mutants in different files without reference to each other should create single group', async () => { -// const mutants = [factory.mutant({ fileName: 'a.js', id: '1' }), factory.mutant({ fileName: 'b.js', id: '2' })]; -// const mutantsClone = structuredClone(mutants); -// const nodes = [new Node('a.js', [], []), new Node('b.js', [], [])]; -// const groups = await createGroups(mutants, nodes); -// expect(groups).to.have.lengthOf(1); -// expect(groups[0][0]).to.be.equal(mutantsClone[0].id); -// expect(groups[0][1]).to.be.equal(mutantsClone[1].id); -// }); + it('two mutants in different files without reference to each other should create single group', () => { + const mutants = [factory.mutant({ fileName: 'a.js', id: '1' }), factory.mutant({ fileName: 'b.js', id: '2' })]; + const mutantsClone = structuredClone(mutants); + const nodes = new Map([ + ['a.js', new Node('a.js', [], [])], + ['b.js', new Node('b.js', [], [])], + ]); + const groups = createGroups(mutants, nodes); + expect(groups).to.have.lengthOf(1); + expect(groups[0][0]).to.be.equal(mutantsClone[0].id); + expect(groups[0][1]).to.be.equal(mutantsClone[1].id); + }); -// it('two mutants in different files with reference to each other should create 2 groups', async () => { -// const mutants = [factory.mutant({ fileName: 'a.js', id: '1' }), factory.mutant({ fileName: 'b.js', id: '2' })]; -// const mutantsClone = structuredClone(mutants); -// const nodeA = new Node('a.js', [], []); -// const nodeB = new Node('b.js', [nodeA], []); -// const nodes = [nodeA, nodeB]; -// const groups = await createGroups(mutants, nodes); -// expect(groups).to.have.lengthOf(2); -// expect(groups[0][0]).to.be.equal(mutantsClone[0].id); -// expect(groups[1][0]).to.be.equal(mutantsClone[1].id); -// }); + it('two mutants in different files with reference to each other should create 2 groups', () => { + const mutants = [factory.mutant({ fileName: 'a.js', id: '1' }), factory.mutant({ fileName: 'b.js', id: '2' })]; + const mutantsClone = structuredClone(mutants); + const nodeA = new Node('a.js', [], []); + const nodeB = new Node('b.js', [nodeA], []); + const nodes = new Map([ + [nodeA.fileName, nodeA], + [nodeB.fileName, nodeB], + ]); + const groups = createGroups(mutants, nodes); + expect(groups).to.have.lengthOf(2); + expect(groups[0][0]).to.be.equal(mutantsClone[0].id); + expect(groups[1][0]).to.be.equal(mutantsClone[1].id); + }); -// it('two mutants in different files with circular dependency to each other should create 2 groups', async () => { -// const mutants = [factory.mutant({ fileName: 'a.js', id: '1' }), factory.mutant({ fileName: 'b.js', id: '2' })]; -// const mutantsClone = structuredClone(mutants); -// const nodeA = new Node('a.js', [], []); -// const nodeB = new Node('b.js', [nodeA], []); -// nodeA.parents.push(nodeB); -// const nodes = [nodeA, nodeB]; -// const groups = await createGroups(mutants, nodes); -// expect(groups).to.have.lengthOf(2); -// expect(groups[0][0]).to.be.equal(mutantsClone[0].id); -// expect(groups[1][0]).to.be.equal(mutantsClone[1].id); -// }); + it('two mutants in different files with circular dependency to each other should create 2 groups', () => { + const mutants = [factory.mutant({ fileName: 'a.js', id: '1' }), factory.mutant({ fileName: 'b.js', id: '2' })]; + const mutantsClone = structuredClone(mutants); + const nodeA = new Node('a.js', [], []); + const nodeB = new Node('b.js', [nodeA], []); + nodeA.parents.push(nodeB); + const nodes = new Map([ + [nodeA.fileName, nodeA], + [nodeB.fileName, nodeB], + ]); + const groups = createGroups(mutants, nodes); + expect(groups).to.have.lengthOf(2); + expect(groups[0][0]).to.be.equal(mutantsClone[0].id); + expect(groups[1][0]).to.be.equal(mutantsClone[1].id); + }); -// it('two mutants in same file should create 2 groups', async () => { -// const mutants = [factory.mutant({ fileName: 'a.js', id: '1' }), factory.mutant({ fileName: 'a.js', id: '2' })]; -// const mutantsClone = structuredClone(mutants); -// const nodeA = new Node('a.js', [], []); -// const nodes = [nodeA]; -// const groups = await createGroups(mutants, nodes); -// expect(groups).to.have.lengthOf(2); -// expect(groups[0][0]).to.be.equal(mutantsClone[0].id); -// expect(groups[1][0]).to.be.equal(mutantsClone[1].id); -// }); + it('two mutants in same file should create 2 groups', () => { + const mutants = [factory.mutant({ fileName: 'a.js', id: '1' }), factory.mutant({ fileName: 'a.js', id: '2' })]; + const mutantsClone = structuredClone(mutants); + const nodeA = new Node('a.js', [], []); + const nodes = new Map([[nodeA.fileName, nodeA]]); + const groups = createGroups(mutants, nodes); + expect(groups).to.have.lengthOf(2); + expect(groups[0][0]).to.be.equal(mutantsClone[0].id); + expect(groups[1][0]).to.be.equal(mutantsClone[1].id); + }); -// it('complex graph should contain multiples 4 groups', async () => { -// const mutants = [ -// factory.mutant({ fileName: 'a.js', id: '1' }), -// factory.mutant({ fileName: 'b.js', id: '2' }), -// factory.mutant({ fileName: 'c.js', id: '3' }), -// factory.mutant({ fileName: 'd.js', id: '4' }), -// factory.mutant({ fileName: 'e.js', id: '5' }), -// factory.mutant({ fileName: 'f.js', id: '6' }), -// ]; -// const mutantsClone = structuredClone(mutants); -// const nodeA = new Node('a.js', [], []); -// const nodeB = new Node('b.js', [nodeA], []); -// const nodeC = new Node('c.js', [nodeA], []); -// const nodeD = new Node('d.js', [nodeC], []); -// const nodeE = new Node('e.js', [nodeA], []); -// const nodeF = new Node('f.js', [nodeE, nodeD], []); -// const nodes = [nodeA, nodeB, nodeC, nodeD, nodeE, nodeF]; -// const groups = await createGroups(mutants, nodes); -// expect(groups).to.have.lengthOf(4); -// expect(groups[0][0]).to.be.equal(mutantsClone[0].id); -// expect(groups[1][0]).to.be.equal(mutantsClone[1].id); -// expect(groups[1][1]).to.be.equal(mutantsClone[2].id); -// expect(groups[1][2]).to.be.equal(mutantsClone[4].id); -// expect(groups[2][0]).to.be.equal(mutantsClone[3].id); -// expect(groups[3][0]).to.be.equal(mutantsClone[5].id); -// }); -// }); + it('complex graph should contain multiples 4 groups', () => { + const mutants = [ + factory.mutant({ fileName: 'a.js', id: '1' }), + factory.mutant({ fileName: 'b.js', id: '2' }), + factory.mutant({ fileName: 'c.js', id: '3' }), + factory.mutant({ fileName: 'd.js', id: '4' }), + factory.mutant({ fileName: 'e.js', id: '5' }), + factory.mutant({ fileName: 'f.js', id: '6' }), + ]; + const mutantsClone = structuredClone(mutants); + const nodeA = new Node('a.js', [], []); + const nodeB = new Node('b.js', [nodeA], []); + const nodeC = new Node('c.js', [nodeA], []); + const nodeD = new Node('d.js', [nodeC], []); + const nodeE = new Node('e.js', [nodeA], []); + const nodeF = new Node('f.js', [nodeE, nodeD], []); + const nodes = new Map([ + [nodeA.fileName, nodeA], + [nodeB.fileName, nodeB], + [nodeC.fileName, nodeC], + [nodeD.fileName, nodeD], + [nodeE.fileName, nodeE], + [nodeF.fileName, nodeF], + ]); + const groups = createGroups(mutants, nodes); + expect(groups).to.have.lengthOf(4); + expect(groups[0][0]).to.be.equal(mutantsClone[0].id); + expect(groups[1][0]).to.be.equal(mutantsClone[1].id); + expect(groups[1][1]).to.be.equal(mutantsClone[2].id); + expect(groups[1][2]).to.be.equal(mutantsClone[4].id); + expect(groups[2][0]).to.be.equal(mutantsClone[3].id); + expect(groups[3][0]).to.be.equal(mutantsClone[5].id); + }); +}); From 457e9133356371982913fda15b36b430fd58ef06 Mon Sep 17 00:00:00 2001 From: Danny Berkelaar Date: Fri, 9 Dec 2022 09:34:28 +0100 Subject: [PATCH 33/77] Changes --- .../src/fs/hybrid-file-system.ts | 15 --------- .../src/typescript-checker.ts | 31 +++++++++---------- 2 files changed, 15 insertions(+), 31 deletions(-) diff --git a/packages/typescript-checker/src/fs/hybrid-file-system.ts b/packages/typescript-checker/src/fs/hybrid-file-system.ts index 63dd212713..451e3039ce 100644 --- a/packages/typescript-checker/src/fs/hybrid-file-system.ts +++ b/packages/typescript-checker/src/fs/hybrid-file-system.ts @@ -16,7 +16,6 @@ import { ScriptFile } from './script-file.js'; */ export class HybridFileSystem { private readonly files = new Map(); - private mutatedFile: ScriptFile | undefined; public static inject = tokens(commonTokens.logger); constructor(private readonly log: Logger) {} @@ -32,20 +31,6 @@ export class HybridFileSystem { } } - // todo remove - public mutate(mutant: Pick): void { - const fileName = toPosixFileName(mutant.fileName); - const file = this.files.get(fileName); - if (!file) { - throw new Error(`File "${mutant.fileName}" cannot be found.`); - } - if (this.mutatedFile && this.mutatedFile !== file) { - this.mutatedFile.resetMutant(); - } - file.mutate(mutant); - this.mutatedFile = file; - } - public watchFile(fileName: string, watcher: ts.FileWatcherCallback): void { const file = this.getFile(fileName); if (file) { diff --git a/packages/typescript-checker/src/typescript-checker.ts b/packages/typescript-checker/src/typescript-checker.ts index 6750ddf345..791be24528 100644 --- a/packages/typescript-checker/src/typescript-checker.ts +++ b/packages/typescript-checker/src/typescript-checker.ts @@ -92,7 +92,6 @@ export class TypescriptChecker implements Checker { return result; } - // string is id van de mutant private async checkErrors( mutants: Mutant[], errorsMap: Record, @@ -101,25 +100,15 @@ export class TypescriptChecker implements Checker { const errors = await this.tsCompiler.check(mutants); const mutantsToTestIndividually = new Set(); - for (const error of errors) { - if (mutants.length === 1) { - if (errorsMap[mutants[0].id]) { - errorsMap[mutants[0].id].push(error); - } else { - errorsMap[mutants[0].id] = [error]; - } - } else { + if (errors.length && mutants.length === 1) { + errorsMap[mutants[0].id] = errors; + } else { + for (const error of errors) { const nodeErrorWasThrownIn = nodes.get(toBackSlashFileName(error.file?.fileName ?? '')); if (!nodeErrorWasThrownIn) { throw new Error('Error not found in any node'); } - const allNodesWrongMutantsCanBeIn = nodeErrorWasThrownIn.getAllChildReferencesIncludingSelf(); - const fileNamesToCheck: string[] = []; - - allNodesWrongMutantsCanBeIn.forEach((node) => { - fileNamesToCheck.push(node.fileName); - }); - const mutantsRelatedToError = mutants.filter((mutant) => fileNamesToCheck.includes(mutant.fileName)); + const mutantsRelatedToError = nodeErrorWasThrownIn.getMutantsWithReferenceToChildrenOrSelf(mutants); if (mutantsRelatedToError.length === 1) { if (errorsMap[mutantsRelatedToError[0].id]) { @@ -139,7 +128,17 @@ export class TypescriptChecker implements Checker { } } + if (mutantsToTestIndividually.size) { + // todo make fix + this.logger.info(`Checking ${mutantsToTestIndividually.size} mutants individually.`); + const begin = new Date(); + await this.tsCompiler.check([]); + const end = new Date(); + this.logger.info(`Checking nothing cost ${(end.getTime() - begin.getTime()) / 1000}`); + } for (const mutant of mutantsToTestIndividually) { + if (errorsMap[mutant.id]) continue; + this.logger.debug(`Having to check mutant ${mutant.id} individually.`); await this.checkErrors([mutant], errorsMap, nodes); } From 58ecb0833ea79718261b0049e078cc46bf1e20a0 Mon Sep 17 00:00:00 2001 From: Danny Berkelaar Date: Fri, 9 Dec 2022 15:15:10 +0100 Subject: [PATCH 34/77] Changes --- .../src/fs/hybrid-file-system.ts | 1 - .../typescript-checker/src/fs/script-file.ts | 6 +- .../src/typescript-checker.ts | 9 +- .../src/typescript-compiler.ts | 2 - .../test/unit/fs/hybrid-file-system.spec.ts | 96 ++----------------- 5 files changed, 15 insertions(+), 99 deletions(-) diff --git a/packages/typescript-checker/src/fs/hybrid-file-system.ts b/packages/typescript-checker/src/fs/hybrid-file-system.ts index 451e3039ce..b4a3bfd2d9 100644 --- a/packages/typescript-checker/src/fs/hybrid-file-system.ts +++ b/packages/typescript-checker/src/fs/hybrid-file-system.ts @@ -1,5 +1,4 @@ import ts from 'typescript'; -import { Mutant } from '@stryker-mutator/api/core'; import { Logger } from '@stryker-mutator/api/logging'; import { tokens, commonTokens } from '@stryker-mutator/api/plugin'; diff --git a/packages/typescript-checker/src/fs/script-file.ts b/packages/typescript-checker/src/fs/script-file.ts index 84b8435e29..c2dbaad279 100644 --- a/packages/typescript-checker/src/fs/script-file.ts +++ b/packages/typescript-checker/src/fs/script-file.ts @@ -10,7 +10,7 @@ export class ScriptFile { public write(content: string): void { this.content = content; - // this.touch(); + this.touch(); } public watcher: ts.FileWatcherCallback | undefined; @@ -21,7 +21,7 @@ export class ScriptFile { const start = this.getOffset(mutant.location.start); const end = this.getOffset(mutant.location.end); this.content = `${this.originalContent.substr(0, start)}${mutant.replacement}${this.originalContent.substr(end)}`; - // this.touch(); + this.touch(); } private getOffset(pos: Position): number { @@ -34,7 +34,7 @@ export class ScriptFile { public resetMutant(): void { this.guardMutationIsWatched(); this.content = this.originalContent; - // this.touch(); + this.touch(); } private guardMutationIsWatched() { diff --git a/packages/typescript-checker/src/typescript-checker.ts b/packages/typescript-checker/src/typescript-checker.ts index 791be24528..c65753d504 100644 --- a/packages/typescript-checker/src/typescript-checker.ts +++ b/packages/typescript-checker/src/typescript-checker.ts @@ -129,16 +129,11 @@ export class TypescriptChecker implements Checker { } if (mutantsToTestIndividually.size) { - // todo make fix - this.logger.info(`Checking ${mutantsToTestIndividually.size} mutants individually.`); - const begin = new Date(); + // todo summary await this.tsCompiler.check([]); - const end = new Date(); - this.logger.info(`Checking nothing cost ${(end.getTime() - begin.getTime()) / 1000}`); } for (const mutant of mutantsToTestIndividually) { - if (errorsMap[mutant.id]) continue; - this.logger.debug(`Having to check mutant ${mutant.id} individually.`); + if (errorsMap[mutant.id]) continue; // todo not add to list instead of continue await this.checkErrors([mutant], errorsMap, nodes); } diff --git a/packages/typescript-checker/src/typescript-compiler.ts b/packages/typescript-checker/src/typescript-compiler.ts index 4ea4a09413..68e43514f6 100644 --- a/packages/typescript-checker/src/typescript-compiler.ts +++ b/packages/typescript-checker/src/typescript-compiler.ts @@ -154,12 +154,10 @@ export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCre this.lastMutants.forEach((mutant) => { const file = this.fs.getFile(mutant.fileName); file?.resetMutant(); - file?.touch(); }); mutants.forEach((mutant) => { const file = this.fs.getFile(mutant.fileName); file?.mutate(mutant); - file?.touch(); }); await this.currentTask.promise; const errors = this.currentErrors; diff --git a/packages/typescript-checker/test/unit/fs/hybrid-file-system.spec.ts b/packages/typescript-checker/test/unit/fs/hybrid-file-system.spec.ts index 8866f25eac..6b9afaa9bb 100644 --- a/packages/typescript-checker/test/unit/fs/hybrid-file-system.spec.ts +++ b/packages/typescript-checker/test/unit/fs/hybrid-file-system.spec.ts @@ -1,7 +1,7 @@ import sinon from 'sinon'; import ts from 'typescript'; import { expect } from 'chai'; -import { factory, testInjector } from '@stryker-mutator/test-helpers'; +import { testInjector } from '@stryker-mutator/test-helpers'; import { HybridFileSystem } from '../../../src/fs/index.js'; @@ -92,6 +92,7 @@ describe('fs', () => { const watcherCallback = sinon.stub(); // Act + sut.writeFile('foo.js', 'some-content'); sut.watchFile('foo.js', watcherCallback); sut.writeFile('foo.js', 'some-content'); @@ -106,16 +107,6 @@ describe('fs', () => { expect(helper.readFileStub).calledWith('test/foo/a.js'); }); - it("should not throw if file isn't loaded", () => { - // Should ignore the file watch - const watchCallback = sinon.stub(); - sut.watchFile('node_modules/chai/package.json', watchCallback); - - // If it was successfully ignored, than `mutate` should throw - expect(() => sut.mutate(factory.mutant({ fileName: 'node_modules/chai/package.json' }))).throws(); - expect(watchCallback).not.called; - }); - it('should log that the file is watched', () => { helper.readFileStub.returns('foobar'); const watcherCallback = sinon.stub(); @@ -123,82 +114,15 @@ describe('fs', () => { expect(testInjector.logger.trace).calledWith('Registering watcher for file "%s"', 'foo.js'); }); }); - - describe(HybridFileSystem.prototype.mutate.name, () => { - it('should mutate the file in-memory', () => { - // Arrange - helper.readFileStub.returns('a + b'); - sut.watchFile('a.js', sinon.stub()); - - // Act - sut.mutate({ fileName: 'a.js', location: { start: { line: 0, column: 2 }, end: { line: 0, column: 3 } }, replacement: '-' }); - - // Assert - expect(sut.getFile('a.js')!.content).eq('a - b'); - }); - - it('should convert path separator to forward slashes', () => { - helper.readFileStub.returns('a + b'); - sut.watchFile('test/foo/a.js', sinon.stub()); - sut.mutate({ fileName: 'test\\foo\\a.js', location: { start: { line: 0, column: 2 }, end: { line: 0, column: 3 } }, replacement: '-' }); - expect(sut.getFile('test/foo/a.js')!.content).eq('a - b'); - }); - - it('should notify the watcher', () => { - // Arrange - const watcher = sinon.stub(); - helper.readFileStub.returns('a + b'); - sut.watchFile('a.js', watcher); - - // Act - sut.mutate({ fileName: 'a.js', location: { start: { line: 0, column: 2 }, end: { line: 0, column: 3 } }, replacement: '-' }); - - // Assert - expect(watcher).calledWith('a.js', ts.FileWatcherEventKind.Changed); - }); - - it('should reset previously mutated file', () => { - // Arrange - helper.readFileStub.withArgs('a.js').returns('a + b').withArgs('b.js').returns('"foo" + "bar"'); - sut.watchFile('a.js', sinon.stub()); - sut.watchFile('b.js', sinon.stub()); - - // Act - sut.mutate({ fileName: 'a.js', location: { start: { line: 0, column: 2 }, end: { line: 0, column: 3 } }, replacement: '-' }); - sut.mutate({ fileName: 'b.js', location: { start: { line: 0, column: 6 }, end: { line: 0, column: 7 } }, replacement: '-' }); - - // Assert - expect(sut.getFile('a.js')!.content).eq('a + b'); - expect(sut.getFile('b.js')!.content).eq('"foo" - "bar"'); - }); - - it("should throw if file doesn't exist", () => { - expect(() => - sut.mutate({ fileName: 'a.js', location: { start: { line: 0, column: 2 }, end: { line: 0, column: 3 } }, replacement: '-' }) - ).throws('File "a.js" cannot be found.'); - }); - }); - describe(HybridFileSystem.prototype.existsInMemory.name, () => { - it('should return true if it exists', () => { - sut.writeFile('a.js', 'a + b'); - expect(sut.existsInMemory('a.js')).true; - }); - - it('should return false if it does not exist', () => { - sut.writeFile('b.js', 'a + b'); - expect(sut.existsInMemory('a.js')).false; - }); - - it('should return false it is cached to not exist', () => { - helper.readFileStub.returns(undefined); - sut.getFile('a.js'); // caches that it doesn't exists - expect(sut.existsInMemory('a.js')).false; - }); - - it('should convert path separator to forward slashes', () => { - sut.writeFile('test/foo/a.js', 'foobar'); - expect(sut.existsInMemory('test\\foo\\a.js')).true; + it('should return true if file does exists', () => { + const fileName = 'test-file'; + sut.writeFile(fileName, ''); + expect(sut.existsInMemory(fileName)).true; + }); + it('should return false if file does not exists', () => { + const fileName = 'test-file'; + expect(sut.existsInMemory(fileName)).false; }); }); }); From 5834a4ad89991a1a19dc55c72b8d63354a4bbb32 Mon Sep 17 00:00:00 2001 From: Odin van der Linden Date: Fri, 9 Dec 2022 15:01:50 +0100 Subject: [PATCH 35/77] comments to clarify --- .../src/typescript-checker.ts | 56 ++++++++++--------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/packages/typescript-checker/src/typescript-checker.ts b/packages/typescript-checker/src/typescript-checker.ts index c65753d504..8ab135760c 100644 --- a/packages/typescript-checker/src/typescript-checker.ts +++ b/packages/typescript-checker/src/typescript-checker.ts @@ -98,42 +98,46 @@ export class TypescriptChecker implements Checker { nodes: Map ): Promise> { const errors = await this.tsCompiler.check(mutants); - const mutantsToTestIndividually = new Set(); + const mutantsThatCouldNotBeTestedInGroups = new Set(); + //If there is only a single mutant the error has to originate from the single mutant if (errors.length && mutants.length === 1) { errorsMap[mutants[0].id] = errors; - } else { - for (const error of errors) { - const nodeErrorWasThrownIn = nodes.get(toBackSlashFileName(error.file?.fileName ?? '')); - if (!nodeErrorWasThrownIn) { - throw new Error('Error not found in any node'); - } - const mutantsRelatedToError = nodeErrorWasThrownIn.getMutantsWithReferenceToChildrenOrSelf(mutants); - - if (mutantsRelatedToError.length === 1) { - if (errorsMap[mutantsRelatedToError[0].id]) { - errorsMap[mutantsRelatedToError[0].id].push(error); - } else { - errorsMap[mutantsRelatedToError[0].id] = [error]; - } - } else if (mutantsRelatedToError.length === 0) { - for (const mutant of mutants) { - mutantsToTestIndividually.add(mutant); - } + return errorsMap; + } + + for (const error of errors) { + const nodeErrorWasThrownIn = nodes.get(toBackSlashFileName(error.file?.fileName ?? '')); + if (!nodeErrorWasThrownIn) { + throw new Error('Error not found in any node'); + } + const mutantsRelatedToError = nodeErrorWasThrownIn.getMutantsWithReferenceToChildrenOrSelf(mutants); + + if (mutantsRelatedToError.length === 1) { + if (errorsMap[mutantsRelatedToError[0].id]) { + errorsMap[mutantsRelatedToError[0].id].push(error); } else { - for (const mutant of mutantsRelatedToError) { - mutantsToTestIndividually.add(mutant); - } + errorsMap[mutantsRelatedToError[0].id] = [error]; + } + } else if (mutantsRelatedToError.length === 0) { + for (const mutant of mutants) { + mutantsThatCouldNotBeTestedInGroups.add(mutant); + } + } else { + for (const mutant of mutantsRelatedToError) { + mutantsThatCouldNotBeTestedInGroups.add(mutant); } } } - if (mutantsToTestIndividually.size) { - // todo summary + if (mutantsThatCouldNotBeTestedInGroups.size) { + //Because at this point the filesystem contains all the mutants from the group we need to reset back + //to the original state of the files to make it possible to test the first mutant + //if we wouldnt do this the first mutant would not be noticed by the compiler because it was already in the filesystem await this.tsCompiler.check([]); } - for (const mutant of mutantsToTestIndividually) { - if (errorsMap[mutant.id]) continue; // todo not add to list instead of continue + for (const mutant of mutantsThatCouldNotBeTestedInGroups) { + if (errorsMap[mutant.id]) continue; await this.checkErrors([mutant], errorsMap, nodes); } From 93cf60fe74f30062f852a8d36fc1e314106dc897 Mon Sep 17 00:00:00 2001 From: Odin van der Linden Date: Fri, 9 Dec 2022 15:30:00 +0100 Subject: [PATCH 36/77] extra node.ts tests --- .../test/unit/grouping/node.spec.ts | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/packages/typescript-checker/test/unit/grouping/node.spec.ts b/packages/typescript-checker/test/unit/grouping/node.spec.ts index 31e6c5fc92..49ea4cf080 100644 --- a/packages/typescript-checker/test/unit/grouping/node.spec.ts +++ b/packages/typescript-checker/test/unit/grouping/node.spec.ts @@ -1,5 +1,7 @@ import { expect } from 'chai'; +import { Mutant } from '@stryker-mutator/api/src/core'; + import { Node } from '../../../src/grouping/node.js'; describe('node', () => { @@ -30,4 +32,32 @@ describe('node', () => { nodeA.parents.push(nodeB); expect(nodeA.getAllParentReferencesIncludingSelf()).to.have.lengthOf(3); }); + + it('getAllChildReferencesIncludingSelf without parent should return array of 1 node ', () => { + const node = new Node('NodeA', [], []); + expect(node.getAllChildReferencesIncludingSelf()).to.have.lengthOf(1); + }); + + it('getAllChildReferencesIncludingSelf with 1 child should return array of 2 nodes ', () => { + const node = new Node('NodeA', [], [new Node('', [], [])]); + expect(node.getAllChildReferencesIncludingSelf()).to.have.lengthOf(2); + }); + + it('getAllChildReferencesIncludingSelf with recursive depth of 2 should return 3 nodes ', () => { + const node = new Node('NodeA', [], [new Node('', [], [new Node('', [], [])])]); + expect(node.getAllChildReferencesIncludingSelf()).to.have.lengthOf(3); + }); + + it('getAllChildReferencesIncludingSelf with recursive depth of 2 and multiple parents should return 4 nodes ', () => { + const node = new Node('NodeA', [], [new Node('', [], [new Node('', [], []), new Node('', [], [])])]); + expect(node.getAllChildReferencesIncludingSelf()).to.have.lengthOf(4); + }); + + it('getMutantsWithReferenceToChildrenOrSelf with single mutant in file should return 1 mutant', () => { + const node = new Node('NodeA.js', [], []); + const mutants: Mutant[] = [ + { fileName: 'NodeA.js', id: '0', replacement: '-', location: { start: { line: 1, column: 1 }, end: { line: 1, column: 1 } }, mutatorName: '' }, + ]; + expect(node.getMutantsWithReferenceToChildrenOrSelf(mutants)).to.have.lengthOf(1); + }); }); From b09326ef46ff77cb4e38498f34dc2e4e7196a4a0 Mon Sep 17 00:00:00 2001 From: Danny Berkelaar Date: Fri, 9 Dec 2022 15:41:26 +0100 Subject: [PATCH 37/77] Fixed typo in comment --- packages/typescript-checker/src/typescript-checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typescript-checker/src/typescript-checker.ts b/packages/typescript-checker/src/typescript-checker.ts index 8ab135760c..49b24b172e 100644 --- a/packages/typescript-checker/src/typescript-checker.ts +++ b/packages/typescript-checker/src/typescript-checker.ts @@ -133,7 +133,7 @@ export class TypescriptChecker implements Checker { if (mutantsThatCouldNotBeTestedInGroups.size) { //Because at this point the filesystem contains all the mutants from the group we need to reset back //to the original state of the files to make it possible to test the first mutant - //if we wouldnt do this the first mutant would not be noticed by the compiler because it was already in the filesystem + //if we wouldn't do this the first mutant would not be noticed by the compiler because it was already in the filesystem await this.tsCompiler.check([]); } for (const mutant of mutantsThatCouldNotBeTestedInGroups) { From d103d88e5416bdd66522a89f928a3b8800bae9a7 Mon Sep 17 00:00:00 2001 From: Danny Berkelaar Date: Fri, 9 Dec 2022 16:00:02 +0100 Subject: [PATCH 38/77] Fixed typescript-helpers tests --- .../src/tsconfig-helpers.ts | 3 +-- .../test/unit/typescript-helpers.spec.ts | 22 ++++++++++++------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/packages/typescript-checker/src/tsconfig-helpers.ts b/packages/typescript-checker/src/tsconfig-helpers.ts index f8547e885e..6da47e09a4 100644 --- a/packages/typescript-checker/src/tsconfig-helpers.ts +++ b/packages/typescript-checker/src/tsconfig-helpers.ts @@ -9,6 +9,7 @@ const COMPILER_OPTIONS_OVERRIDES: Readonly> = Object allowUnreachableCode: true, noUnusedLocals: false, noUnusedParameters: false, + declarationMap: true, }); // When we're running in 'single-project' mode, we can safely disable emit @@ -18,14 +19,12 @@ const NO_EMIT_OPTIONS_FOR_SINGLE_PROJECT: Readonly> tsBuildInfoFile: undefined, composite: false, declaration: false, - declarationMap: true, }); // When we're running in 'project references' mode, we need to enable declaration output const LOW_EMIT_OPTIONS_FOR_PROJECT_REFERENCES: Readonly> = Object.freeze({ emitDeclarationOnly: true, noEmit: false, - declarationMap: true, }); export function guardTSVersion(): void { diff --git a/packages/typescript-checker/test/unit/typescript-helpers.spec.ts b/packages/typescript-checker/test/unit/typescript-helpers.spec.ts index 990eaa9910..ff849f7695 100644 --- a/packages/typescript-checker/test/unit/typescript-helpers.spec.ts +++ b/packages/typescript-checker/test/unit/typescript-helpers.spec.ts @@ -6,7 +6,7 @@ import { expect } from 'chai'; import { determineBuildModeEnabled, overrideOptions, retrieveReferencedProjects, guardTSVersion } from '../../src/tsconfig-helpers.js'; -describe('typescript-helpers', () => { +describe.only('typescript-helpers', () => { describe(determineBuildModeEnabled.name, () => { let readFileStub: sinon.SinonStub; @@ -54,7 +54,7 @@ describe('typescript-helpers', () => { incremental: false, composite: false, declaration: false, - declarationMap: false, + declarationMap: true, }); expect( JSON.parse( @@ -66,7 +66,7 @@ describe('typescript-helpers', () => { incremental: true, composite: true, declaration: true, - declarationMap: false, + declarationMap: true, }, }, }, @@ -78,7 +78,7 @@ describe('typescript-helpers', () => { incremental: false, composite: false, declaration: false, - declarationMap: false, + declarationMap: true, }); }); @@ -93,7 +93,7 @@ describe('typescript-helpers', () => { incremental: true, composite: true, declaration: true, - declarationMap: false, + declarationMap: true, declarationDir: '.', }, }, @@ -112,7 +112,7 @@ describe('typescript-helpers', () => { incremental: true, composite: true, declaration: true, - declarationMap: false, + declarationMap: true, declarationDir: '', }, }, @@ -127,7 +127,7 @@ describe('typescript-helpers', () => { expect(JSON.parse(overrideOptions({ config: {} }, true)).compilerOptions).deep.include({ emitDeclarationOnly: true, noEmit: false, - declarationMap: false, + declarationMap: true, }); expect( JSON.parse( @@ -147,7 +147,13 @@ describe('typescript-helpers', () => { ).deep.include({ emitDeclarationOnly: true, noEmit: false, - declarationMap: false, + declarationMap: true, + }); + }); + + it('should set --declarationMap to true', () => { + expect(JSON.parse(overrideOptions({ config: { declarationMap: false } }, true)).compilerOptions).deep.include({ + declarationMap: true, }); }); }); From a5481d527fb1bbf56f03a028b579f87b16c36d50 Mon Sep 17 00:00:00 2001 From: Odin van der Linden Date: Fri, 9 Dec 2022 15:53:10 +0100 Subject: [PATCH 39/77] more node.ts tests --- .../typescript-checker/test/unit/grouping/node.spec.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/typescript-checker/test/unit/grouping/node.spec.ts b/packages/typescript-checker/test/unit/grouping/node.spec.ts index 49ea4cf080..047d0c23ae 100644 --- a/packages/typescript-checker/test/unit/grouping/node.spec.ts +++ b/packages/typescript-checker/test/unit/grouping/node.spec.ts @@ -60,4 +60,14 @@ describe('node', () => { ]; expect(node.getMutantsWithReferenceToChildrenOrSelf(mutants)).to.have.lengthOf(1); }); + + it('getMutantsWithReferenceToChildrenOrSelf with single mutant in child should return 1 mutant', () => { + const node = new Node('NodeA.js', [], []); + const nodeB = new Node('NodeB.js', [], []); + node.childs.push(nodeB); + const mutants: Mutant[] = [ + { fileName: 'NodeB.js', id: '0', replacement: '-', location: { start: { line: 1, column: 1 }, end: { line: 1, column: 1 } }, mutatorName: '' }, + ]; + expect(node.getMutantsWithReferenceToChildrenOrSelf(mutants)).to.have.lengthOf(1); + }); }); From 245dd1626129d8c724a29762176a4c18fc1d6a80 Mon Sep 17 00:00:00 2001 From: Danny Berkelaar Date: Fri, 9 Dec 2022 16:06:59 +0100 Subject: [PATCH 40/77] removed test.only --- .../integration/project-references.it.spec.ts | 20 +++++++++---------- .../test/unit/typescript-helpers.spec.ts | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/typescript-checker/test/integration/project-references.it.spec.ts b/packages/typescript-checker/test/integration/project-references.it.spec.ts index 3b3dfd1426..060afd9a01 100644 --- a/packages/typescript-checker/test/integration/project-references.it.spec.ts +++ b/packages/typescript-checker/test/integration/project-references.it.spec.ts @@ -31,10 +31,10 @@ describe('Typescript checker on a project with project references', () => { return sut.init(); }); - it.only('todo test', async () => { - const mutant = createMutant('src/todo.ts', 'return totalCount', 'return ""', 'mutId'); - const actual = await sut.check([mutant]); - }); + // it.only('todo test', async () => { + // const mutant = createMutant('src/todo.ts', 'return totalCount', 'return ""', 'mutId'); + // const actual = await sut.check([mutant]); + // }); it('should not write output to disk', async () => { expect(fs.existsSync(resolveTestResource('dist')), 'Output was written to disk!').false; @@ -47,12 +47,12 @@ describe('Typescript checker on a project with project references', () => { expect(actualResult).deep.eq(expectedResult); }); - it.only('should allow unused local variables (override options)', async () => { - const mutant = createMutant('src/todo.ts', 'TodoList.allTodos.push(newItem)', '42', 'mutId'); - const expectedResult: Record = { mutId: { status: CheckStatus.Passed } }; - const actual = await sut.check([mutant]); - expect(actual).deep.eq(expectedResult); - }); + // it.only('should allow unused local variables (override options)', async () => { + // const mutant = createMutant('src/todo.ts', 'TodoList.allTodos.push(newItem)', '42', 'mutId'); + // const expectedResult: Record = { mutId: { status: CheckStatus.Passed } }; + // const actual = await sut.check([mutant]); + // expect(actual).deep.eq(expectedResult); + // }); }); const fileContents = Object.freeze({ diff --git a/packages/typescript-checker/test/unit/typescript-helpers.spec.ts b/packages/typescript-checker/test/unit/typescript-helpers.spec.ts index ff849f7695..803fee3258 100644 --- a/packages/typescript-checker/test/unit/typescript-helpers.spec.ts +++ b/packages/typescript-checker/test/unit/typescript-helpers.spec.ts @@ -6,7 +6,7 @@ import { expect } from 'chai'; import { determineBuildModeEnabled, overrideOptions, retrieveReferencedProjects, guardTSVersion } from '../../src/tsconfig-helpers.js'; -describe.only('typescript-helpers', () => { +describe('typescript-helpers', () => { describe(determineBuildModeEnabled.name, () => { let readFileStub: sinon.SinonStub; From 1b256349c3f2337d4835fdddf4e7e64848d5815e Mon Sep 17 00:00:00 2001 From: Nico Jansen Date: Tue, 13 Dec 2022 15:17:30 +0100 Subject: [PATCH 41/77] Simplify grouping alorithm --- .../src/grouping/create-groups.ts | 92 +++++++------------ 1 file changed, 31 insertions(+), 61 deletions(-) diff --git a/packages/typescript-checker/src/grouping/create-groups.ts b/packages/typescript-checker/src/grouping/create-groups.ts index fbd01b9bf9..0e70f31d1e 100644 --- a/packages/typescript-checker/src/grouping/create-groups.ts +++ b/packages/typescript-checker/src/grouping/create-groups.ts @@ -39,78 +39,48 @@ import { Node } from './node.js'; * In this function we create the groups of mutants who can be tested at the same time. */ export function createGroups(mutants: Mutant[], nodes: Map): string[][] { - const groups: Mutant[][] = []; - let mutant = selectNewMutant(mutants, groups); - - // Loop until all the mutants are in a group - while (mutant != null) { - // Copy the list of mutants who are not in a group - const mutantWhoAreNotInAGroup = [...mutants]; - // The first mutant is always in the group - const group = [mutant]; - const node = nodes.get(mutant.fileName); - - if (node == null) throw new Error('Node not in graph'); - - // Fill the ignoreList - let nodesToIgnore = node.getAllParentReferencesIncludingSelf(); - - // Loop through the nodes who can possibly go in the group - for (const mutantSelected of mutantWhoAreNotInAGroup) { - const nodeSelected = nodes.get(mutantSelected.fileName); - - if (nodeSelected == null) throw new Error('Node not in graph'); - - // Check if parents of node are not in the group - const groupNodes = getNodesFromMutants(group, nodes); - if (nodeSelectedHasParentsInCurrentGroup(nodeSelected, groupNodes)) continue; - - // See if the node can be in the group - if (nodesToIgnore.has(nodeSelected)) continue; - - // Push the group - group.push(mutantSelected); - // Mutate the original list and remove the mutant put in the group - mutants.splice(mutants.indexOf(mutantSelected), 1); - // Add to the ignoreList - nodesToIgnore = new Set([...nodesToIgnore, ...nodeSelected.getAllParentReferencesIncludingSelf()]); + const groups: string[][] = []; + const mutantsToGroup = new Set(mutants); + + while (mutantsToGroup.size) { + const group: string[] = []; + const groupNodes = new Set(); + const nodesToIgnore = new Set(); + + for (const currentMutant of mutantsToGroup) { + const currentNode = findNode(currentMutant.fileName, nodes); + if (!nodesToIgnore.has(currentNode) && !parentsHaveOverlapWith(currentNode, groupNodes)) { + group.push(currentMutant.id); + groupNodes.add(currentNode); + mutantsToGroup.delete(currentMutant); + addAll(nodesToIgnore, currentNode.getAllParentReferencesIncludingSelf()); + } } - groups.push(group); - mutant = selectNewMutant(mutants, groups); } - return groupsToString(groups); + return groups; } -function selectNewMutant(mutants: Mutant[], groups: Mutant[][]): Mutant | null { - const groupsFlattened = groups.flat(); - - for (let i = 0; i < mutants.length; i++) { - if (!groupsFlattened.includes(mutants[i])) { - return mutants.splice(i, 1)[0]; - } +function addAll(nodes: Set, nodesToAdd: Iterable) { + for (const parent of nodesToAdd) { + nodes.add(parent); } - - return null; } -function groupsToString(groups: Mutant[][]): string[][] { - return groups.map((group) => group.map((mutant) => mutant.id)); +function findNode(fileName: string, nodes: Map) { + const node = nodes.get(fileName); + if (node == null) { + throw new Error(`Node not in graph: "${fileName}"`); + } + return node; } - -function nodeSelectedHasParentsInCurrentGroup(nodeSelected: Node, groupNodes: Node[]) { - for (const parentNode of nodeSelected.getAllParentReferencesIncludingSelf()) { - if (groupNodes.includes(parentNode)) return true; +function parentsHaveOverlapWith(currentNode: Node, groupNodes: Set) { + for (const parentNode of currentNode.getAllParentReferencesIncludingSelf()) { + if (groupNodes.has(parentNode)) { + return true; + } } return false; } - -function getNodesFromMutants(group: Mutant[], nodes: Map): Node[] { - return group.map((mutant) => { - const node = nodes.get(mutant.fileName); - if (node == null) throw new Error('Node not in graph'); - return node; - }); -} From 8019a49e292876025606867185c5235766c9cfb6 Mon Sep 17 00:00:00 2001 From: Danny Berkelaar Date: Fri, 23 Dec 2022 09:10:53 +0100 Subject: [PATCH 42/77] Fixed some tests --- .../src/tsconfig-helpers.ts | 2 +- .../integration/project-references.it.spec.ts | 29 ++++++++----------- .../project-references/src/todo.ts | 2 +- .../project-references/test/todo.spec.ts | 2 -- 4 files changed, 14 insertions(+), 21 deletions(-) diff --git a/packages/typescript-checker/src/tsconfig-helpers.ts b/packages/typescript-checker/src/tsconfig-helpers.ts index 6da47e09a4..bf7805ccb2 100644 --- a/packages/typescript-checker/src/tsconfig-helpers.ts +++ b/packages/typescript-checker/src/tsconfig-helpers.ts @@ -10,6 +10,7 @@ const COMPILER_OPTIONS_OVERRIDES: Readonly> = Object noUnusedLocals: false, noUnusedParameters: false, declarationMap: true, + declaration: true, }); // When we're running in 'single-project' mode, we can safely disable emit @@ -18,7 +19,6 @@ const NO_EMIT_OPTIONS_FOR_SINGLE_PROJECT: Readonly> incremental: false, // incremental and composite off: https://github.com/microsoft/TypeScript/issues/36917 tsBuildInfoFile: undefined, composite: false, - declaration: false, }); // When we're running in 'project references' mode, we need to enable declaration output diff --git a/packages/typescript-checker/test/integration/project-references.it.spec.ts b/packages/typescript-checker/test/integration/project-references.it.spec.ts index 060afd9a01..217754bd25 100644 --- a/packages/typescript-checker/test/integration/project-references.it.spec.ts +++ b/packages/typescript-checker/test/integration/project-references.it.spec.ts @@ -31,37 +31,32 @@ describe('Typescript checker on a project with project references', () => { return sut.init(); }); - // it.only('todo test', async () => { - // const mutant = createMutant('src/todo.ts', 'return totalCount', 'return ""', 'mutId'); - // const actual = await sut.check([mutant]); - // }); - it('should not write output to disk', async () => { expect(fs.existsSync(resolveTestResource('dist')), 'Output was written to disk!').false; }); it('should be able to validate a mutant', async () => { - const mutant = createMutant('src/todo.ts', 'TodoList.allTodos.push(newItem)', 'newItem ? 42 : 43', 'mutId'); - const expectedResult: Record = { mutId: { status: CheckStatus.Passed } }; + const mutant = createMutant('todo.ts', 'TodoList.allTodos.push(newItem)', 'newItem ? 42 : 43'); + const expectedResult: Record = { [mutant.id]: { status: CheckStatus.Passed } }; const actualResult = await sut.check([mutant]); expect(actualResult).deep.eq(expectedResult); }); - // it.only('should allow unused local variables (override options)', async () => { - // const mutant = createMutant('src/todo.ts', 'TodoList.allTodos.push(newItem)', '42', 'mutId'); - // const expectedResult: Record = { mutId: { status: CheckStatus.Passed } }; - // const actual = await sut.check([mutant]); - // expect(actual).deep.eq(expectedResult); - // }); + it('should allow unused local variables (override options)', async () => { + const mutant = createMutant('todo.ts', 'TodoList.allTodos.push(newItem)', '42'); + const expectedResult: Record = { [mutant.id]: { status: CheckStatus.Passed } }; + const actual = await sut.check([mutant]); + expect(actual).deep.eq(expectedResult); + }); }); const fileContents = Object.freeze({ - ['src/todo.ts']: fs.readFileSync(resolveTestResource('src', 'todo.ts'), 'utf8'), - ['test/todo.spec.ts']: fs.readFileSync(resolveTestResource('test', 'todo.spec.ts'), 'utf8'), + ['todo.ts']: fs.readFileSync(resolveTestResource('src', 'todo.ts'), 'utf8'), + ['todo.spec.ts']: fs.readFileSync(resolveTestResource('test', 'todo.spec.ts'), 'utf8'), }); -function createMutant(fileName: 'src/todo.ts' | 'test/todo.spec.ts', findText: string, replacement: string, id = '42', offset = 0): Mutant { - const lines = fileContents[fileName].split(os.EOL); +function createMutant(fileName: 'todo.spec.ts' | 'todo.ts', findText: string, replacement: string, id = '42', offset = 0): Mutant { + const lines = fileContents[fileName].split('\n'); // todo fix this \n const lineNumber = lines.findIndex((l) => l.includes(findText)); if (lineNumber === -1) { throw new Error(`Cannot find ${findText} in ${fileName}`); diff --git a/packages/typescript-checker/testResources/project-references/src/todo.ts b/packages/typescript-checker/testResources/project-references/src/todo.ts index 7ac1dd6631..1873791080 100644 --- a/packages/typescript-checker/testResources/project-references/src/todo.ts +++ b/packages/typescript-checker/testResources/project-references/src/todo.ts @@ -10,6 +10,7 @@ class Todo implements ITodo { export class TodoList { public static allTodos: Todo[] = []; + createTodoItem(name: string, description: string) { let newItem = new Todo(name, description, false); let totalCount: number = TodoList.allTodos.push(newItem); @@ -20,4 +21,3 @@ export class TodoList { return TodoList.allTodos; } } - diff --git a/packages/typescript-checker/testResources/project-references/test/todo.spec.ts b/packages/typescript-checker/testResources/project-references/test/todo.spec.ts index 60dc360347..cf9adc35ad 100644 --- a/packages/typescript-checker/testResources/project-references/test/todo.spec.ts +++ b/packages/typescript-checker/testResources/project-references/test/todo.spec.ts @@ -1,8 +1,6 @@ import { TodoList } from '../src/todo.js'; const list = new TodoList(); -const n: number = list.createTodoItem('Mow lawn', 'Mow moving forward.') -console.log(n); function addTodo(name = 'test', description = 'test') { list.createTodoItem(name, description); From ba38fc33ba8021d461c89ebf727164f95f51e73b Mon Sep 17 00:00:00 2001 From: Danny Berkelaar Date: Fri, 23 Dec 2022 09:49:12 +0100 Subject: [PATCH 43/77] change addAll to addRangeOfNodesToSet --- packages/typescript-checker/src/grouping/create-groups.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/typescript-checker/src/grouping/create-groups.ts b/packages/typescript-checker/src/grouping/create-groups.ts index 0e70f31d1e..0e782774c7 100644 --- a/packages/typescript-checker/src/grouping/create-groups.ts +++ b/packages/typescript-checker/src/grouping/create-groups.ts @@ -53,7 +53,7 @@ export function createGroups(mutants: Mutant[], nodes: Map): strin group.push(currentMutant.id); groupNodes.add(currentNode); mutantsToGroup.delete(currentMutant); - addAll(nodesToIgnore, currentNode.getAllParentReferencesIncludingSelf()); + addRangeOfNodesToSet(nodesToIgnore, currentNode.getAllParentReferencesIncludingSelf()); } } groups.push(group); @@ -62,7 +62,7 @@ export function createGroups(mutants: Mutant[], nodes: Map): strin return groups; } -function addAll(nodes: Set, nodesToAdd: Iterable) { +function addRangeOfNodesToSet(nodes: Set, nodesToAdd: Iterable) { for (const parent of nodesToAdd) { nodes.add(parent); } @@ -75,6 +75,7 @@ function findNode(fileName: string, nodes: Map) { } return node; } + function parentsHaveOverlapWith(currentNode: Node, groupNodes: Set) { for (const parentNode of currentNode.getAllParentReferencesIncludingSelf()) { if (groupNodes.has(parentNode)) { From 730ad3de766d0ff16d317994b025e2e3208f4fbc Mon Sep 17 00:00:00 2001 From: Danny Berkelaar Date: Fri, 23 Dec 2022 10:05:38 +0100 Subject: [PATCH 44/77] fixed tests --- packages/typescript-checker/src/typescript-checker.ts | 11 ++++++++++- .../test/unit/typescript-helpers.spec.ts | 6 +++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/typescript-checker/src/typescript-checker.ts b/packages/typescript-checker/src/typescript-checker.ts index 49b24b172e..0bb501bd40 100644 --- a/packages/typescript-checker/src/typescript-checker.ts +++ b/packages/typescript-checker/src/typescript-checker.ts @@ -71,6 +71,12 @@ export class TypescriptChecker implements Checker { status: CheckStatus.Passed, }; }); + + // Check if this is the group with unrelated files and return al + if (!this.tsCompiler.getFileRelationsAsNodes().get(mutants[0].fileName)) { + return result; + } + const mutantErrorRelationMap = await this.checkErrors(mutants, {}, this.tsCompiler.getFileRelationsAsNodes()); for (const [id, errors] of Object.entries(mutantErrorRelationMap)) { result[id] = { status: CheckStatus.CompileError, reason: this.createErrorText(errors) }; @@ -86,8 +92,11 @@ export class TypescriptChecker implements Checker { */ public async group(mutants: Mutant[]): Promise { const nodes = this.tsCompiler.getFileRelationsAsNodes(); + const mutantsOutSideProject = mutants.filter((m) => nodes.get(m.fileName) == null).map((m) => m.id); + mutants = mutants.filter((m) => nodes.get(m.fileName) != null); + const groups = await createGroups(mutants, nodes); - const result = groups.sort((a, b) => b.length - a.length); + const result = [mutantsOutSideProject, ...groups.sort((a, b) => b.length - a.length)]; this.logger.info(`Created ${result.length} groups with largest group of ${result[0].length} mutants`); return result; } diff --git a/packages/typescript-checker/test/unit/typescript-helpers.spec.ts b/packages/typescript-checker/test/unit/typescript-helpers.spec.ts index 803fee3258..a57c01ab24 100644 --- a/packages/typescript-checker/test/unit/typescript-helpers.spec.ts +++ b/packages/typescript-checker/test/unit/typescript-helpers.spec.ts @@ -53,7 +53,7 @@ describe('typescript-helpers', () => { noEmit: true, incremental: false, composite: false, - declaration: false, + declaration: true, declarationMap: true, }); expect( @@ -65,7 +65,7 @@ describe('typescript-helpers', () => { noEmit: false, incremental: true, composite: true, - declaration: true, + declaration: false, declarationMap: true, }, }, @@ -77,7 +77,7 @@ describe('typescript-helpers', () => { noEmit: true, incremental: false, composite: false, - declaration: false, + declaration: true, declarationMap: true, }); }); From 35feb9de15d3551c3203deb61923c663dc7f61d6 Mon Sep 17 00:00:00 2001 From: Danny Berkelaar Date: Fri, 23 Dec 2022 10:13:37 +0100 Subject: [PATCH 45/77] Changed grouping mutants outside of the project --- packages/typescript-checker/src/typescript-checker.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/typescript-checker/src/typescript-checker.ts b/packages/typescript-checker/src/typescript-checker.ts index 0bb501bd40..e56d77ac57 100644 --- a/packages/typescript-checker/src/typescript-checker.ts +++ b/packages/typescript-checker/src/typescript-checker.ts @@ -92,11 +92,14 @@ export class TypescriptChecker implements Checker { */ public async group(mutants: Mutant[]): Promise { const nodes = this.tsCompiler.getFileRelationsAsNodes(); + const mutantsOutSideProject = mutants.filter((m) => nodes.get(m.fileName) == null).map((m) => m.id); - mutants = mutants.filter((m) => nodes.get(m.fileName) != null); + const mutantsToTest = mutants.filter((m) => nodes.get(m.fileName) != null); + + const groups = await createGroups(mutantsToTest, nodes); + const sortedGroups = groups.sort((a, b) => b.length - a.length); + const result = mutantsOutSideProject.length ? [mutantsOutSideProject, ...sortedGroups] : sortedGroups; - const groups = await createGroups(mutants, nodes); - const result = [mutantsOutSideProject, ...groups.sort((a, b) => b.length - a.length)]; this.logger.info(`Created ${result.length} groups with largest group of ${result[0].length} mutants`); return result; } From 9fd6e48cfe4a1de811e74f2cf2864cb8b93d943d Mon Sep 17 00:00:00 2001 From: Danny Berkelaar Date: Fri, 23 Dec 2022 11:10:41 +0100 Subject: [PATCH 46/77] Changed back to posixFileName --- .../src/grouping/create-groups.ts | 4 +++- .../typescript-checker/src/tsconfig-helpers.ts | 4 ---- .../src/typescript-checker.ts | 10 +++++----- .../src/typescript-compiler.ts | 18 +++++------------- 4 files changed, 13 insertions(+), 23 deletions(-) diff --git a/packages/typescript-checker/src/grouping/create-groups.ts b/packages/typescript-checker/src/grouping/create-groups.ts index 0e782774c7..83de8b6d6c 100644 --- a/packages/typescript-checker/src/grouping/create-groups.ts +++ b/packages/typescript-checker/src/grouping/create-groups.ts @@ -1,5 +1,7 @@ import { Mutant } from '@stryker-mutator/api/src/core/index.js'; +import { toPosixFileName } from '../tsconfig-helpers.js'; + import { Node } from './node.js'; /** @@ -69,7 +71,7 @@ function addRangeOfNodesToSet(nodes: Set, nodesToAdd: Iterable) { } function findNode(fileName: string, nodes: Map) { - const node = nodes.get(fileName); + const node = nodes.get(toPosixFileName(fileName)); if (node == null) { throw new Error(`Node not in graph: "${fileName}"`); } diff --git a/packages/typescript-checker/src/tsconfig-helpers.ts b/packages/typescript-checker/src/tsconfig-helpers.ts index bf7805ccb2..6c600db9d4 100644 --- a/packages/typescript-checker/src/tsconfig-helpers.ts +++ b/packages/typescript-checker/src/tsconfig-helpers.ts @@ -92,7 +92,3 @@ export function retrieveReferencedProjects(parsedConfig: { config?: any }, fromD export function toPosixFileName(fileName: string): string { return fileName.replace(/\\/g, '/'); } - -export function toBackSlashFileName(fileName: string): string { - return fileName.replace(/\//g, '\\'); -} diff --git a/packages/typescript-checker/src/typescript-checker.ts b/packages/typescript-checker/src/typescript-checker.ts index e56d77ac57..3ed4af384e 100644 --- a/packages/typescript-checker/src/typescript-checker.ts +++ b/packages/typescript-checker/src/typescript-checker.ts @@ -10,7 +10,7 @@ import { HybridFileSystem } from './fs/index.js'; import * as pluginTokens from './plugin-tokens.js'; import { TypescriptCompiler } from './typescript-compiler.js'; import { createGroups } from './grouping/create-groups.js'; -import { toBackSlashFileName } from './tsconfig-helpers.js'; +import { toPosixFileName } from './tsconfig-helpers.js'; import { Node } from './grouping/node.js'; typescriptCheckerLoggerFactory.inject = tokens(commonTokens.getLogger, commonTokens.target); @@ -73,7 +73,7 @@ export class TypescriptChecker implements Checker { }); // Check if this is the group with unrelated files and return al - if (!this.tsCompiler.getFileRelationsAsNodes().get(mutants[0].fileName)) { + if (!this.tsCompiler.getFileRelationsAsNodes().get(toPosixFileName(mutants[0].fileName))) { return result; } @@ -93,8 +93,8 @@ export class TypescriptChecker implements Checker { public async group(mutants: Mutant[]): Promise { const nodes = this.tsCompiler.getFileRelationsAsNodes(); - const mutantsOutSideProject = mutants.filter((m) => nodes.get(m.fileName) == null).map((m) => m.id); - const mutantsToTest = mutants.filter((m) => nodes.get(m.fileName) != null); + const mutantsOutSideProject = mutants.filter((m) => nodes.get(toPosixFileName(m.fileName)) == null).map((m) => m.id); + const mutantsToTest = mutants.filter((m) => nodes.get(toPosixFileName(m.fileName)) != null); const groups = await createGroups(mutantsToTest, nodes); const sortedGroups = groups.sort((a, b) => b.length - a.length); @@ -119,7 +119,7 @@ export class TypescriptChecker implements Checker { } for (const error of errors) { - const nodeErrorWasThrownIn = nodes.get(toBackSlashFileName(error.file?.fileName ?? '')); + const nodeErrorWasThrownIn = nodes.get(error.file?.fileName ?? ''); if (!nodeErrorWasThrownIn) { throw new Error('Error not found in any node'); } diff --git a/packages/typescript-checker/src/typescript-compiler.ts b/packages/typescript-checker/src/typescript-compiler.ts index 68e43514f6..b56bad8662 100644 --- a/packages/typescript-checker/src/typescript-compiler.ts +++ b/packages/typescript-checker/src/typescript-compiler.ts @@ -8,14 +8,7 @@ import { Logger } from '@stryker-mutator/api/logging'; import { tokens, commonTokens } from '@stryker-mutator/api/plugin'; import { HybridFileSystem } from './fs/index.js'; -import { - determineBuildModeEnabled, - guardTSVersion, - overrideOptions, - retrieveReferencedProjects, - toBackSlashFileName, - toPosixFileName, -} from './tsconfig-helpers.js'; +import { determineBuildModeEnabled, guardTSVersion, overrideOptions, retrieveReferencedProjects, toPosixFileName } from './tsconfig-helpers.js'; import { Node } from './grouping/node.js'; import * as pluginTokens from './plugin-tokens.js'; @@ -171,21 +164,20 @@ export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCre if (!this.nodes.size) { // create nodes for (const [fileName] of this.sourceFiles) { - const backslashFileName = toBackSlashFileName(fileName); - const node = new Node(backslashFileName, [], []); - this.nodes.set(backslashFileName, node); + const node = new Node(fileName, [], []); + this.nodes.set(fileName, node); } // set childs for (const [fileName, file] of this.sourceFiles) { - const node = this.nodes.get(toBackSlashFileName(fileName)); + const node = this.nodes.get(fileName); if (node == null) { throw new Error('todo'); } const importFileNames = [...file.imports]; // todo fix ! - node.childs = importFileNames.map((importName) => this.nodes.get(toBackSlashFileName(importName))!).filter((n) => n != undefined); + node.childs = importFileNames.map((importName) => this.nodes.get(importName)!).filter((n) => n != undefined); } for (const [, node] of this.nodes) { From 7f551b0500d5fd05b06bc3cc8abf7130647dddbe Mon Sep 17 00:00:00 2001 From: odinvanderlinden Date: Fri, 23 Dec 2022 13:09:30 +0100 Subject: [PATCH 47/77] pr suggestions + ts checker strategy option --- .../typescript-checker/.vscode/launch.json | 18 ----------- .../schema/typescript-checker-options.json | 31 +++++++++++++++++++ .../src/grouping/create-groups.ts | 20 ++++++------ .../typescript-checker/src/grouping/node.ts | 6 ++-- ...pt-checker-options-with-stryker-options.ts | 5 +++ .../src/typescript-checker.ts | 29 +++++++++++------ .../src/typescript-compiler.ts | 4 +-- .../integration/project-references.it.spec.ts | 3 +- .../project-with-ts-buildinfo.it.spec.ts | 2 ++ .../integration/single-project.it.spec.ts | 2 ++ .../typescript-checkers-errors.it.spec.ts | 4 +++ .../test/unit/grouping/node.spec.ts | 2 +- 12 files changed, 82 insertions(+), 44 deletions(-) create mode 100644 packages/typescript-checker/schema/typescript-checker-options.json create mode 100644 packages/typescript-checker/src/typescript-checker-options-with-stryker-options.ts diff --git a/packages/typescript-checker/.vscode/launch.json b/packages/typescript-checker/.vscode/launch.json index fb07f2e5ce..ea87390aee 100644 --- a/packages/typescript-checker/.vscode/launch.json +++ b/packages/typescript-checker/.vscode/launch.json @@ -1,24 +1,6 @@ { "version": "0.2.0", "configurations": [ - { - "type": "node", - "request": "launch", - "name": "💙 Unit / Integration tests", - "program": "${workspaceRoot}/../../node_modules/mocha/bin/_mocha", - "internalConsoleOptions": "openOnSessionStart", - "outFiles": [ - "${workspaceRoot}/dist/**/*.js" - ], - "skipFiles": [ - "/**" - ], - "args": [ - "--no-timeout", - "dist/test/unit/**/*.js", - "dist/test/integration/**/*.js" - ] - }, { "type": "node", "request": "launch", diff --git a/packages/typescript-checker/schema/typescript-checker-options.json b/packages/typescript-checker/schema/typescript-checker-options.json new file mode 100644 index 0000000000..06082e3d6c --- /dev/null +++ b/packages/typescript-checker/schema/typescript-checker-options.json @@ -0,0 +1,31 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "title": "TypeScriptCheckerOptions", + "type": "object", + "additionalProperties": false, + "properties": { + "typeScriptChecker": { + "description": "Configuration for @stryker-mutator/typescript-checker", + "title": "StrykerTypeScriptCheckerSetup", + "additionalProperties": false, + "type": "object", + "default": {}, + "properties": { + "strategy": { + "$ref": "#/definitions/typeScriptCheckerStrategy", + "default": "noGrouping" + } + } + } + }, + "definitions": { + "typeScriptCheckerStrategy": { + "title": "TypeScriptCheckerStrategy", + "description": "Specify which strategy should be used for the typescript checker", + "enum": [ + "grouping", + "noGrouping" + ] + } + } +} diff --git a/packages/typescript-checker/src/grouping/create-groups.ts b/packages/typescript-checker/src/grouping/create-groups.ts index 0e782774c7..403855ce46 100644 --- a/packages/typescript-checker/src/grouping/create-groups.ts +++ b/packages/typescript-checker/src/grouping/create-groups.ts @@ -3,16 +3,16 @@ import { Mutant } from '@stryker-mutator/api/src/core/index.js'; import { Node } from './node.js'; /** - * To speed up the type checking we want to check multiple mutants at once. - * When multiple mutants in different files who can't throw errors in each other we can type check them simultaneously. + * To speed up the type-checking we want to check multiple mutants at once. + * When multiple mutants in different files don't have overlap in affected files (or have small overlap), we can type-check them simultaneously. * These mutants who can be tested at the same time are called a group. - * Therefore the return type is an array of arrays in other words: an array of groups. + * Therefore, the return type is an array of arrays, in other words: an array of groups. * * @param mutants All the mutants of the test project. * @param nodes A graph representation of the test project. * * @example - * Let's assume we got tho following project structure and in every file is one mutant. + * Let's assume we got the following dependencies in files of a project, and in every file is one mutant. * * ======== * = A.ts = @@ -29,14 +29,14 @@ import { Node } from './node.js'; * A imports B and C * C imports D * - * In this example we can type check B and D at the same time. + * In this example, we can type-check B and D simultaneously. * This is because these files can't throw errors in each other. - * If we type check them and let's say B throws an error. - * We know for sure that the mutant in B was the one creating the type error. - * If we type check B and D at the same time it is possible that an error shows up in A. - * When this happens we go down de dependency graph and individual test the mutants who were in that group. + * If we type check them, let's say B reports an error. + * We know that the mutant in B created the type error. + * If we type check B and D at the same time, it is possible that an error shows up in A. + * When this happens, we go down the dependency graph and individually test the mutants in that group. * - * In this function we create the groups of mutants who can be tested at the same time. + * In this function, we create groups of mutants who can be tested at the same time. */ export function createGroups(mutants: Mutant[], nodes: Map): string[][] { const groups: string[][] = []; diff --git a/packages/typescript-checker/src/grouping/node.ts b/packages/typescript-checker/src/grouping/node.ts index 8dacd7f0fa..4cba3e23dd 100644 --- a/packages/typescript-checker/src/grouping/node.ts +++ b/packages/typescript-checker/src/grouping/node.ts @@ -1,7 +1,7 @@ import { Mutant } from '@stryker-mutator/api/src/core'; export class Node { - constructor(public fileName: string, public parents: Node[], public childs: Node[]) {} + constructor(public fileName: string, public parents: Node[], public children: Node[]) {} public getAllParentReferencesIncludingSelf(allParentReferences: Set = new Set()): Set { allParentReferences.add(this); @@ -15,7 +15,7 @@ export class Node { public getAllChildReferencesIncludingSelf(allChildReferences: Set = new Set()): Set { allChildReferences.add(this); - this.childs.forEach((child) => { + this.children.forEach((child) => { if (!allChildReferences.has(child)) { child.getAllChildReferencesIncludingSelf(allChildReferences); } @@ -32,7 +32,7 @@ export class Node { // todo better name const linkedMutants = mutants.filter((m) => m.fileName == this.fileName); - const childResult = this.childs.flatMap((c) => c.getMutantsWithReferenceToChildrenOrSelf(mutants, nodesChecked)); + const childResult = this.children.flatMap((c) => c.getMutantsWithReferenceToChildrenOrSelf(mutants, nodesChecked)); return [...linkedMutants, ...childResult]; } } diff --git a/packages/typescript-checker/src/typescript-checker-options-with-stryker-options.ts b/packages/typescript-checker/src/typescript-checker-options-with-stryker-options.ts new file mode 100644 index 0000000000..2a0d9945c5 --- /dev/null +++ b/packages/typescript-checker/src/typescript-checker-options-with-stryker-options.ts @@ -0,0 +1,5 @@ +import { StrykerOptions } from '@stryker-mutator/api/core'; + +import { TypeScriptCheckerOptions } from '../src-generated/typescript-checker-options'; + +export interface TypeScriptCheckerOptionsWithStrykerOptions extends TypeScriptCheckerOptions, StrykerOptions {} diff --git a/packages/typescript-checker/src/typescript-checker.ts b/packages/typescript-checker/src/typescript-checker.ts index e56d77ac57..8042e78b54 100644 --- a/packages/typescript-checker/src/typescript-checker.ts +++ b/packages/typescript-checker/src/typescript-checker.ts @@ -6,12 +6,15 @@ import { tokens, commonTokens, PluginContext, Injector, Scope } from '@stryker-m import { Logger, LoggerFactoryMethod } from '@stryker-mutator/api/logging'; import { Mutant, StrykerOptions } from '@stryker-mutator/api/core'; -import { HybridFileSystem } from './fs/index.js'; +import { TypeScriptCheckerOptions } from '../src-generated/typescript-checker-options.js'; + import * as pluginTokens from './plugin-tokens.js'; import { TypescriptCompiler } from './typescript-compiler.js'; import { createGroups } from './grouping/create-groups.js'; import { toBackSlashFileName } from './tsconfig-helpers.js'; import { Node } from './grouping/node.js'; +import { TypeScriptCheckerOptionsWithStrykerOptions } from './typescript-checker-options-with-stryker-options.js'; +import { HybridFileSystem } from './fs/hybrid-file-system.js'; typescriptCheckerLoggerFactory.inject = tokens(commonTokens.getLogger, commonTokens.target); // eslint-disable-next-line @typescript-eslint/ban-types @@ -38,14 +41,12 @@ export class TypescriptChecker implements Checker { * Keep track of all tsconfig files which are read during compilation (for project references) */ - public static inject = tokens(commonTokens.logger, commonTokens.options, pluginTokens.fs, pluginTokens.tsCompiler); + public static inject = tokens(commonTokens.logger, commonTokens.options, pluginTokens.tsCompiler); + private readonly typeScriptCheckeroptions: TypeScriptCheckerOptions; - constructor( - private readonly logger: Logger, - options: StrykerOptions, - private readonly fs: HybridFileSystem, - private readonly tsCompiler: TypescriptCompiler - ) {} + constructor(private readonly logger: Logger, options: StrykerOptions, private readonly tsCompiler: TypescriptCompiler) { + this.typeScriptCheckeroptions = this.loadSetup(options); + } /** * Starts the typescript compiler and does a dry run @@ -91,12 +92,15 @@ export class TypescriptChecker implements Checker { * @param mutants All the mutants to group. */ public async group(mutants: Mutant[]): Promise { + if (this.typeScriptCheckeroptions.typeScriptChecker.strategy === 'noGrouping') { + return [mutants.map((m) => m.id)]; + } const nodes = this.tsCompiler.getFileRelationsAsNodes(); const mutantsOutSideProject = mutants.filter((m) => nodes.get(m.fileName) == null).map((m) => m.id); const mutantsToTest = mutants.filter((m) => nodes.get(m.fileName) != null); - const groups = await createGroups(mutantsToTest, nodes); + const groups = createGroups(mutantsToTest, nodes); const sortedGroups = groups.sort((a, b) => b.length - a.length); const result = mutantsOutSideProject.length ? [mutantsOutSideProject, ...sortedGroups] : sortedGroups; @@ -163,4 +167,11 @@ export class TypescriptChecker implements Checker { getNewLine: () => EOL, }); } + + private loadSetup(options: StrykerOptions): TypeScriptCheckerOptions { + const defaultTypeScriptCheckerConfig: TypeScriptCheckerOptions = { + typeScriptChecker: { strategy: 'noGrouping' }, + }; + return Object.assign(defaultTypeScriptCheckerConfig, (options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker); + } } diff --git a/packages/typescript-checker/src/typescript-compiler.ts b/packages/typescript-checker/src/typescript-compiler.ts index 68e43514f6..6f04992760 100644 --- a/packages/typescript-checker/src/typescript-compiler.ts +++ b/packages/typescript-checker/src/typescript-compiler.ts @@ -185,13 +185,13 @@ export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCre const importFileNames = [...file.imports]; // todo fix ! - node.childs = importFileNames.map((importName) => this.nodes.get(toBackSlashFileName(importName))!).filter((n) => n != undefined); + node.children = importFileNames.map((importName) => this.nodes.get(toBackSlashFileName(importName))!).filter((n) => n != undefined); } for (const [, node] of this.nodes) { node.parents = []; for (const [_, n] of this.nodes) { - if (n.childs.includes(node)) { + if (n.children.includes(node)) { node.parents.push(n); } } diff --git a/packages/typescript-checker/test/integration/project-references.it.spec.ts b/packages/typescript-checker/test/integration/project-references.it.spec.ts index 217754bd25..316c16e3d3 100644 --- a/packages/typescript-checker/test/integration/project-references.it.spec.ts +++ b/packages/typescript-checker/test/integration/project-references.it.spec.ts @@ -1,6 +1,5 @@ import path from 'path'; import fs from 'fs'; -import os from 'os'; import { fileURLToPath } from 'url'; @@ -11,6 +10,7 @@ import { testInjector, factory } from '@stryker-mutator/test-helpers'; import { createTypescriptChecker } from '../../src/index.js'; import { TypescriptChecker } from '../../src/typescript-checker.js'; +import { TypeScriptCheckerOptionsWithStrykerOptions } from '../../src/typescript-checker-options-with-stryker-options.js'; const resolveTestResource = path.resolve.bind( path, @@ -26,6 +26,7 @@ describe('Typescript checker on a project with project references', () => { let sut: TypescriptChecker; beforeEach(() => { + (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker.strategy = 'grouping'; testInjector.options.tsconfigFile = resolveTestResource('tsconfig.root.json'); sut = testInjector.injector.injectFunction(createTypescriptChecker); return sut.init(); diff --git a/packages/typescript-checker/test/integration/project-with-ts-buildinfo.it.spec.ts b/packages/typescript-checker/test/integration/project-with-ts-buildinfo.it.spec.ts index 77b46f956f..822207ae00 100644 --- a/packages/typescript-checker/test/integration/project-with-ts-buildinfo.it.spec.ts +++ b/packages/typescript-checker/test/integration/project-with-ts-buildinfo.it.spec.ts @@ -9,6 +9,7 @@ import { Location, Mutant } from '@stryker-mutator/api/core'; import { testInjector, factory } from '@stryker-mutator/test-helpers'; import { createTypescriptChecker } from '../../src/index.js'; +import { TypeScriptCheckerOptionsWithStrykerOptions } from '../../src/typescript-checker-options-with-stryker-options.js'; const resolveTestResource = path.resolve.bind( path, @@ -22,6 +23,7 @@ const resolveTestResource = path.resolve.bind( describe('project-with-ts-buildinfo', () => { it('should load project on init', async () => { + (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker.strategy = 'grouping'; testInjector.options.tsconfigFile = resolveTestResource('tsconfig.json'); const sut = testInjector.injector.injectFunction(createTypescriptChecker); const group = await sut.group([createMutant('src/index.ts', '', '')]); diff --git a/packages/typescript-checker/test/integration/single-project.it.spec.ts b/packages/typescript-checker/test/integration/single-project.it.spec.ts index b3f372f84d..0f37060065 100644 --- a/packages/typescript-checker/test/integration/single-project.it.spec.ts +++ b/packages/typescript-checker/test/integration/single-project.it.spec.ts @@ -9,6 +9,7 @@ import { CheckResult, CheckStatus } from '@stryker-mutator/api/check'; import { createTypescriptChecker } from '../../src/index.js'; import { TypescriptChecker } from '../../src/typescript-checker.js'; +import { TypeScriptCheckerOptionsWithStrykerOptions } from '../../src/typescript-checker-options-with-stryker-options.js'; const resolveTestResource = path.resolve.bind( path, @@ -24,6 +25,7 @@ describe('Typescript checker on a single project', () => { let sut: TypescriptChecker; beforeEach(() => { + (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker.strategy = 'grouping'; testInjector.options.tsconfigFile = resolveTestResource('tsconfig.json'); sut = testInjector.injector.injectFunction(createTypescriptChecker); return sut.init(); diff --git a/packages/typescript-checker/test/integration/typescript-checkers-errors.it.spec.ts b/packages/typescript-checker/test/integration/typescript-checkers-errors.it.spec.ts index a64a8334e2..860529d86f 100644 --- a/packages/typescript-checker/test/integration/typescript-checkers-errors.it.spec.ts +++ b/packages/typescript-checker/test/integration/typescript-checkers-errors.it.spec.ts @@ -6,6 +6,7 @@ import { testInjector } from '@stryker-mutator/test-helpers'; import { expect } from 'chai'; import { createTypescriptChecker } from '../../src/index.js'; +import { TypeScriptCheckerOptionsWithStrykerOptions } from '../../src/typescript-checker-options-with-stryker-options.js'; const resolveTestResource = path.resolve.bind( path, @@ -19,6 +20,7 @@ const resolveTestResource = path.resolve.bind( describe('Typescript checker errors', () => { it('should reject initialization if initial compilation failed', async () => { + (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker.strategy = 'grouping'; testInjector.options.tsconfigFile = resolveTestResource('compile-error', 'tsconfig.json'); const sut = testInjector.injector.injectFunction(createTypescriptChecker); await expect(sut.init()).rejectedWith( @@ -27,6 +29,7 @@ describe('Typescript checker errors', () => { }); it('should reject initialization if tsconfig was invalid', async () => { + (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker.strategy = 'grouping'; testInjector.options.tsconfigFile = resolveTestResource('invalid-tsconfig', 'tsconfig.json'); const sut = testInjector.injector.injectFunction(createTypescriptChecker); await expect(sut.init()).rejectedWith( @@ -35,6 +38,7 @@ describe('Typescript checker errors', () => { }); it("should reject when tsconfig file doesn't exist", async () => { + (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker.strategy = 'grouping'; testInjector.options.tsconfigFile = resolveTestResource('empty-dir', 'tsconfig.json'); const sut = testInjector.injector.injectFunction(createTypescriptChecker); await expect(sut.init()).rejectedWith( diff --git a/packages/typescript-checker/test/unit/grouping/node.spec.ts b/packages/typescript-checker/test/unit/grouping/node.spec.ts index 047d0c23ae..6a4fc835f2 100644 --- a/packages/typescript-checker/test/unit/grouping/node.spec.ts +++ b/packages/typescript-checker/test/unit/grouping/node.spec.ts @@ -64,7 +64,7 @@ describe('node', () => { it('getMutantsWithReferenceToChildrenOrSelf with single mutant in child should return 1 mutant', () => { const node = new Node('NodeA.js', [], []); const nodeB = new Node('NodeB.js', [], []); - node.childs.push(nodeB); + node.children.push(nodeB); const mutants: Mutant[] = [ { fileName: 'NodeB.js', id: '0', replacement: '-', location: { start: { line: 1, column: 1 }, end: { line: 1, column: 1 } }, mutatorName: '' }, ]; From fe3875d772be352fee473a342de2f8985df9d7bb Mon Sep 17 00:00:00 2001 From: odinvanderlinden Date: Fri, 23 Dec 2022 16:22:45 +0100 Subject: [PATCH 48/77] multiple tests --- .../src/typescript-checker.ts | 4 +-- .../integration/project-references.it.spec.ts | 2 +- .../project-with-ts-buildinfo.it.spec.ts | 2 +- .../integration/single-project.it.spec.ts | 36 +++++++++++++++++-- .../typescript-checkers-errors.it.spec.ts | 6 ++-- .../test/unit/typescript-checker.spec.ts | 21 +++++++++++ .../single-project/src/counter.ts | 9 +++++ .../src/errorInFileAbove2Mutants/counter.ts | 9 +++++ .../errorInFileAbove2Mutants/todo-counter.ts | 7 ++++ .../src/errorInFileAbove2Mutants/todo.spec.ts | 12 +++++++ .../src/errorInFileAbove2Mutants/todo.ts | 22 ++++++++++++ 11 files changed, 121 insertions(+), 9 deletions(-) create mode 100644 packages/typescript-checker/test/unit/typescript-checker.spec.ts create mode 100644 packages/typescript-checker/testResources/single-project/src/counter.ts create mode 100644 packages/typescript-checker/testResources/single-project/src/errorInFileAbove2Mutants/counter.ts create mode 100644 packages/typescript-checker/testResources/single-project/src/errorInFileAbove2Mutants/todo-counter.ts create mode 100644 packages/typescript-checker/testResources/single-project/src/errorInFileAbove2Mutants/todo.spec.ts create mode 100644 packages/typescript-checker/testResources/single-project/src/errorInFileAbove2Mutants/todo.ts diff --git a/packages/typescript-checker/src/typescript-checker.ts b/packages/typescript-checker/src/typescript-checker.ts index 25c4e1602d..399f4a9c16 100644 --- a/packages/typescript-checker/src/typescript-checker.ts +++ b/packages/typescript-checker/src/typescript-checker.ts @@ -93,7 +93,7 @@ export class TypescriptChecker implements Checker { */ public async group(mutants: Mutant[]): Promise { if (this.typeScriptCheckeroptions.typeScriptChecker.strategy === 'noGrouping') { - return [mutants.map((m) => m.id)]; + return mutants.map((m) => [m.id]); } const nodes = this.tsCompiler.getFileRelationsAsNodes(); @@ -172,6 +172,6 @@ export class TypescriptChecker implements Checker { const defaultTypeScriptCheckerConfig: TypeScriptCheckerOptions = { typeScriptChecker: { strategy: 'noGrouping' }, }; - return Object.assign(defaultTypeScriptCheckerConfig, (options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker); + return Object.assign(defaultTypeScriptCheckerConfig, options as TypeScriptCheckerOptionsWithStrykerOptions); } } diff --git a/packages/typescript-checker/test/integration/project-references.it.spec.ts b/packages/typescript-checker/test/integration/project-references.it.spec.ts index 316c16e3d3..379b9ace14 100644 --- a/packages/typescript-checker/test/integration/project-references.it.spec.ts +++ b/packages/typescript-checker/test/integration/project-references.it.spec.ts @@ -26,7 +26,7 @@ describe('Typescript checker on a project with project references', () => { let sut: TypescriptChecker; beforeEach(() => { - (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker.strategy = 'grouping'; + (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker = { strategy: 'grouping' }; testInjector.options.tsconfigFile = resolveTestResource('tsconfig.root.json'); sut = testInjector.injector.injectFunction(createTypescriptChecker); return sut.init(); diff --git a/packages/typescript-checker/test/integration/project-with-ts-buildinfo.it.spec.ts b/packages/typescript-checker/test/integration/project-with-ts-buildinfo.it.spec.ts index 822207ae00..029e7f5340 100644 --- a/packages/typescript-checker/test/integration/project-with-ts-buildinfo.it.spec.ts +++ b/packages/typescript-checker/test/integration/project-with-ts-buildinfo.it.spec.ts @@ -23,7 +23,7 @@ const resolveTestResource = path.resolve.bind( describe('project-with-ts-buildinfo', () => { it('should load project on init', async () => { - (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker.strategy = 'grouping'; + (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker = { strategy: 'grouping' }; testInjector.options.tsconfigFile = resolveTestResource('tsconfig.json'); const sut = testInjector.injector.injectFunction(createTypescriptChecker); const group = await sut.group([createMutant('src/index.ts', '', '')]); diff --git a/packages/typescript-checker/test/integration/single-project.it.spec.ts b/packages/typescript-checker/test/integration/single-project.it.spec.ts index 0f37060065..bb1bec41eb 100644 --- a/packages/typescript-checker/test/integration/single-project.it.spec.ts +++ b/packages/typescript-checker/test/integration/single-project.it.spec.ts @@ -25,7 +25,7 @@ describe('Typescript checker on a single project', () => { let sut: TypescriptChecker; beforeEach(() => { - (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker.strategy = 'grouping'; + (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker = { strategy: 'grouping' }; testInjector.options.tsconfigFile = resolveTestResource('tsconfig.json'); sut = testInjector.injector.injectFunction(createTypescriptChecker); return sut.init(); @@ -94,16 +94,48 @@ describe('Typescript checker on a single project', () => { const actual = await sut.check([mutant]); expect(actual).deep.eq(expectedResult); }); + it('should be able invalidate 2 mutants that do result in a compile errors', async () => { + const mutant = createMutant('todo.ts', 'TodoList.allTodos.push(newItem)', '"This should not be a string 🙄"', 'mutId'); + const mutant2 = createMutant('counter.ts', 'return this.currentNumber;', 'return "This should not return a string 🙄"', 'mutId2'); + const actual = await sut.check([mutant, mutant2]); + assertions.expectCompileError(actual.mutId); + assertions.expectCompileError(actual.mutId2); + expect(actual.mutId.reason).has.string('todo.ts(15,9): error TS2322'); + expect(actual.mutId2.reason).has.string('counter.ts(7,5): error TS2322'); + }); + it('should be able invalidate 2 mutants that do result in a compile error in file above', async () => { + const mutant = createMutant('errorInFileAbove2Mutants/todo.ts', 'TodoList.allTodos.push(newItem)', '"This should not be a string 🙄"', 'mutId'); + const mutant2 = createMutant( + 'errorInFileAbove2Mutants/counter.ts', + 'return (this.currentNumber += numberToIncrementBy);', + 'return "This should not return a string 🙄"', + 'mutId2' + ); + const actual = await sut.check([mutant, mutant2]); + assertions.expectCompileError(actual.mutId); + assertions.expectCompileError(actual.mutId2); + expect(actual.mutId.reason).has.string('todo.ts(15,9): error TS2322'); + expect(actual.mutId2.reason).has.string('errorInFileAbove2Mutants/todo-counter.ts(7,7): error TS2322'); + }); }); const fileContents = Object.freeze({ + ['errorInFileAbove2Mutants/todo.ts']: fs.readFileSync(resolveTestResource('src', 'errorInFileAbove2Mutants', 'todo.ts'), 'utf8'), + ['errorInFileAbove2Mutants/counter.ts']: fs.readFileSync(resolveTestResource('src', 'errorInFileAbove2Mutants', 'counter.ts'), 'utf8'), ['todo.ts']: fs.readFileSync(resolveTestResource('src', 'todo.ts'), 'utf8'), + ['counter.ts']: fs.readFileSync(resolveTestResource('src', 'counter.ts'), 'utf8'), ['todo.spec.ts']: fs.readFileSync(resolveTestResource('src', 'todo.spec.ts'), 'utf8'), ['not-type-checked.js']: fs.readFileSync(resolveTestResource('src', 'not-type-checked.js'), 'utf8'), }); function createMutant( - fileName: 'not-type-checked.js' | 'todo.spec.ts' | 'todo.ts', + fileName: + | 'counter.ts' + | 'errorInFileAbove2Mutants/counter.ts' + | 'errorInFileAbove2Mutants/todo.ts' + | 'not-type-checked.js' + | 'todo.spec.ts' + | 'todo.ts', findText: string, replacement: string, id = '42', diff --git a/packages/typescript-checker/test/integration/typescript-checkers-errors.it.spec.ts b/packages/typescript-checker/test/integration/typescript-checkers-errors.it.spec.ts index 860529d86f..2f9a17bf89 100644 --- a/packages/typescript-checker/test/integration/typescript-checkers-errors.it.spec.ts +++ b/packages/typescript-checker/test/integration/typescript-checkers-errors.it.spec.ts @@ -20,7 +20,7 @@ const resolveTestResource = path.resolve.bind( describe('Typescript checker errors', () => { it('should reject initialization if initial compilation failed', async () => { - (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker.strategy = 'grouping'; + (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker = { strategy: 'grouping' }; testInjector.options.tsconfigFile = resolveTestResource('compile-error', 'tsconfig.json'); const sut = testInjector.injector.injectFunction(createTypescriptChecker); await expect(sut.init()).rejectedWith( @@ -29,7 +29,7 @@ describe('Typescript checker errors', () => { }); it('should reject initialization if tsconfig was invalid', async () => { - (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker.strategy = 'grouping'; + (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker = { strategy: 'grouping' }; testInjector.options.tsconfigFile = resolveTestResource('invalid-tsconfig', 'tsconfig.json'); const sut = testInjector.injector.injectFunction(createTypescriptChecker); await expect(sut.init()).rejectedWith( @@ -38,7 +38,7 @@ describe('Typescript checker errors', () => { }); it("should reject when tsconfig file doesn't exist", async () => { - (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker.strategy = 'grouping'; + (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker = { strategy: 'grouping' }; testInjector.options.tsconfigFile = resolveTestResource('empty-dir', 'tsconfig.json'); const sut = testInjector.injector.injectFunction(createTypescriptChecker); await expect(sut.init()).rejectedWith( diff --git a/packages/typescript-checker/test/unit/typescript-checker.spec.ts b/packages/typescript-checker/test/unit/typescript-checker.spec.ts new file mode 100644 index 0000000000..965b0911f4 --- /dev/null +++ b/packages/typescript-checker/test/unit/typescript-checker.spec.ts @@ -0,0 +1,21 @@ +import { testInjector, factory } from '@stryker-mutator/test-helpers'; +import { expect } from 'chai'; + +import { createTypescriptChecker } from '../../src/index.js'; + +import { TypescriptChecker } from '../../src/typescript-checker'; +import { TypeScriptCheckerOptionsWithStrykerOptions } from '../../src/typescript-checker-options-with-stryker-options'; + +describe('typescript-checker', () => { + let sut: TypescriptChecker; + beforeEach(() => { + (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker = { strategy: 'noGrouping' }; + sut = testInjector.injector.injectFunction(createTypescriptChecker); + return sut.init(); + }); + + it('noGrouping setting should not group mutants', async () => { + const result = await sut.group([factory.mutant(), factory.mutant(), factory.mutant()]); + expect(result.length).to.be.eq(3); + }); +}); diff --git a/packages/typescript-checker/testResources/single-project/src/counter.ts b/packages/typescript-checker/testResources/single-project/src/counter.ts new file mode 100644 index 0000000000..bf3f92304e --- /dev/null +++ b/packages/typescript-checker/testResources/single-project/src/counter.ts @@ -0,0 +1,9 @@ +export class Counter { + constructor(private currentNumber: number) {} + public increment(numberToIncrementBy = 1): number { + return (this.currentNumber += numberToIncrementBy); + } + get getCurrentNumber(): number { + return this.currentNumber; + } +} diff --git a/packages/typescript-checker/testResources/single-project/src/errorInFileAbove2Mutants/counter.ts b/packages/typescript-checker/testResources/single-project/src/errorInFileAbove2Mutants/counter.ts new file mode 100644 index 0000000000..37fef80437 --- /dev/null +++ b/packages/typescript-checker/testResources/single-project/src/errorInFileAbove2Mutants/counter.ts @@ -0,0 +1,9 @@ +export class Counter { + constructor(private currentNumber: number) {} + public increment(numberToIncrementBy = 1) { + return (this.currentNumber += numberToIncrementBy); + } + get getCurrentNumber(): number { + return this.currentNumber; + } +} diff --git a/packages/typescript-checker/testResources/single-project/src/errorInFileAbove2Mutants/todo-counter.ts b/packages/typescript-checker/testResources/single-project/src/errorInFileAbove2Mutants/todo-counter.ts new file mode 100644 index 0000000000..4fda514818 --- /dev/null +++ b/packages/typescript-checker/testResources/single-project/src/errorInFileAbove2Mutants/todo-counter.ts @@ -0,0 +1,7 @@ +import { Counter } from './counter'; +import { TodoList } from './todo'; + +const counter = new Counter(1); +const todoList = new TodoList(); +todoList.createTodoItem('test', 'test description'); +const newCount: number = counter.increment(); diff --git a/packages/typescript-checker/testResources/single-project/src/errorInFileAbove2Mutants/todo.spec.ts b/packages/typescript-checker/testResources/single-project/src/errorInFileAbove2Mutants/todo.spec.ts new file mode 100644 index 0000000000..dca331ed86 --- /dev/null +++ b/packages/typescript-checker/testResources/single-project/src/errorInFileAbove2Mutants/todo.spec.ts @@ -0,0 +1,12 @@ +import { TodoList } from './todo.js'; + +const list = new TodoList(); +const n: number = list.createTodoItem('Mow lawn', 'Mow moving forward.') +console.log(n); + +function addTodo(name = 'test', description = 'test') { + list.createTodoItem(name, description); +} + + +addTodo(); diff --git a/packages/typescript-checker/testResources/single-project/src/errorInFileAbove2Mutants/todo.ts b/packages/typescript-checker/testResources/single-project/src/errorInFileAbove2Mutants/todo.ts new file mode 100644 index 0000000000..966e627313 --- /dev/null +++ b/packages/typescript-checker/testResources/single-project/src/errorInFileAbove2Mutants/todo.ts @@ -0,0 +1,22 @@ +export interface ITodo { + name: string; + description: string; + completed: boolean; +} + +class Todo implements ITodo { + constructor(public name: string, public description: string, public completed: boolean) {} +} + +export class TodoList { + public static allTodos: Todo[] = []; + createTodoItem(name: string, description: string) { + let newItem = new Todo(name, description, false); + let totalCount: number = TodoList.allTodos.push(newItem); + return totalCount; + } + + allTodoItems(): ITodo[] { + return TodoList.allTodos; + } +} From 1fcc250708f975ed316c0b6ffb076a321595e0c3 Mon Sep 17 00:00:00 2001 From: odinvanderlinden Date: Fri, 23 Dec 2022 16:28:28 +0100 Subject: [PATCH 49/77] typo --- packages/typescript-checker/src/typescript-compiler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typescript-checker/src/typescript-compiler.ts b/packages/typescript-checker/src/typescript-compiler.ts index 6ce4de0281..8d097cf6c7 100644 --- a/packages/typescript-checker/src/typescript-compiler.ts +++ b/packages/typescript-checker/src/typescript-compiler.ts @@ -168,7 +168,7 @@ export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCre this.nodes.set(fileName, node); } - // set childs + // set children for (const [fileName, file] of this.sourceFiles) { const node = this.nodes.get(fileName); if (node == null) { From f1caccb3723c4901dfc9e4ca22a2f0f34edd3d08 Mon Sep 17 00:00:00 2001 From: odinvanderlinden Date: Tue, 3 Jan 2023 17:43:16 +0100 Subject: [PATCH 50/77] Clarification with comments + some refactoring --- .../src/grouping/create-groups.ts | 14 ++--- .../typescript-checker/src/grouping/node.ts | 10 ++-- .../src/typescript-checker.ts | 52 ++++++++----------- .../src/typescript-compiler.ts | 30 +++++------ .../test/unit/grouping/create-groups.spec.ts | 40 +++++++------- .../test/unit/grouping/node.spec.ts | 30 +++++------ 6 files changed, 86 insertions(+), 90 deletions(-) diff --git a/packages/typescript-checker/src/grouping/create-groups.ts b/packages/typescript-checker/src/grouping/create-groups.ts index 2d8eae8c56..48d35761a1 100644 --- a/packages/typescript-checker/src/grouping/create-groups.ts +++ b/packages/typescript-checker/src/grouping/create-groups.ts @@ -2,7 +2,7 @@ import { Mutant } from '@stryker-mutator/api/src/core/index.js'; import { toPosixFileName } from '../tsconfig-helpers.js'; -import { Node } from './node.js'; +import { TSFileNode } from './node.js'; /** * To speed up the type-checking we want to check multiple mutants at once. @@ -40,14 +40,14 @@ import { Node } from './node.js'; * * In this function, we create groups of mutants who can be tested at the same time. */ -export function createGroups(mutants: Mutant[], nodes: Map): string[][] { +export function createGroups(mutants: Mutant[], nodes: Map): string[][] { const groups: string[][] = []; const mutantsToGroup = new Set(mutants); while (mutantsToGroup.size) { const group: string[] = []; - const groupNodes = new Set(); - const nodesToIgnore = new Set(); + const groupNodes = new Set(); + const nodesToIgnore = new Set(); for (const currentMutant of mutantsToGroup) { const currentNode = findNode(currentMutant.fileName, nodes); @@ -64,13 +64,13 @@ export function createGroups(mutants: Mutant[], nodes: Map): strin return groups; } -function addRangeOfNodesToSet(nodes: Set, nodesToAdd: Iterable) { +function addRangeOfNodesToSet(nodes: Set, nodesToAdd: Iterable) { for (const parent of nodesToAdd) { nodes.add(parent); } } -function findNode(fileName: string, nodes: Map) { +function findNode(fileName: string, nodes: Map) { const node = nodes.get(toPosixFileName(fileName)); if (node == null) { throw new Error(`Node not in graph: "${fileName}"`); @@ -78,7 +78,7 @@ function findNode(fileName: string, nodes: Map) { return node; } -function parentsHaveOverlapWith(currentNode: Node, groupNodes: Set) { +function parentsHaveOverlapWith(currentNode: TSFileNode, groupNodes: Set) { for (const parentNode of currentNode.getAllParentReferencesIncludingSelf()) { if (groupNodes.has(parentNode)) { return true; diff --git a/packages/typescript-checker/src/grouping/node.ts b/packages/typescript-checker/src/grouping/node.ts index 4cba3e23dd..043f0530b3 100644 --- a/packages/typescript-checker/src/grouping/node.ts +++ b/packages/typescript-checker/src/grouping/node.ts @@ -1,9 +1,11 @@ import { Mutant } from '@stryker-mutator/api/src/core'; -export class Node { - constructor(public fileName: string, public parents: Node[], public children: Node[]) {} +// This class exist so we can have a two way dependency graph. +// the two way dependecay graph is used to search for mutants related to typescript errors +export class TSFileNode { + constructor(public fileName: string, public parents: TSFileNode[], public children: TSFileNode[]) {} - public getAllParentReferencesIncludingSelf(allParentReferences: Set = new Set()): Set { + public getAllParentReferencesIncludingSelf(allParentReferences: Set = new Set()): Set { allParentReferences.add(this); this.parents.forEach((parent) => { if (!allParentReferences.has(parent)) { @@ -13,7 +15,7 @@ export class Node { return allParentReferences; } - public getAllChildReferencesIncludingSelf(allChildReferences: Set = new Set()): Set { + public getAllChildReferencesIncludingSelf(allChildReferences: Set = new Set()): Set { allChildReferences.add(this); this.children.forEach((child) => { if (!allChildReferences.has(child)) { diff --git a/packages/typescript-checker/src/typescript-checker.ts b/packages/typescript-checker/src/typescript-checker.ts index 55c8b9f053..d3aa101539 100644 --- a/packages/typescript-checker/src/typescript-checker.ts +++ b/packages/typescript-checker/src/typescript-checker.ts @@ -12,7 +12,7 @@ import * as pluginTokens from './plugin-tokens.js'; import { TypescriptCompiler } from './typescript-compiler.js'; import { createGroups } from './grouping/create-groups.js'; import { toPosixFileName } from './tsconfig-helpers.js'; -import { Node } from './grouping/node.js'; +import { TSFileNode } from './grouping/node.js'; import { TypeScriptCheckerOptionsWithStrykerOptions } from './typescript-checker-options-with-stryker-options.js'; import { HybridFileSystem } from './fs/hybrid-file-system.js'; @@ -45,7 +45,7 @@ export class TypescriptChecker implements Checker { private readonly typeScriptCheckeroptions: TypeScriptCheckerOptions; constructor(private readonly logger: Logger, options: StrykerOptions, private readonly tsCompiler: TypescriptCompiler) { - this.typeScriptCheckeroptions = this.loadSetup(options); + this.typeScriptCheckeroptions = options as TypeScriptCheckerOptionsWithStrykerOptions; } /** @@ -65,20 +65,14 @@ export class TypescriptChecker implements Checker { * @param mutants The mutants to check */ public async check(mutants: Mutant[]): Promise> { - const result: Record = {}; - - mutants.forEach((mutant) => { - result[mutant.id] = { - status: CheckStatus.Passed, - }; - }); + const result: Record = Object.fromEntries(mutants.map((mutant) => [mutant.id, { status: CheckStatus.Passed }])); // Check if this is the group with unrelated files and return al - if (!this.tsCompiler.getFileRelationsAsNodes().get(toPosixFileName(mutants[0].fileName))) { + if (!this.tsCompiler.nodes.get(toPosixFileName(mutants[0].fileName))) { return result; } - const mutantErrorRelationMap = await this.checkErrors(mutants, {}, this.tsCompiler.getFileRelationsAsNodes()); + const mutantErrorRelationMap = await this.checkErrors(mutants, {}, this.tsCompiler.nodes); for (const [id, errors] of Object.entries(mutantErrorRelationMap)) { result[id] = { status: CheckStatus.CompileError, reason: this.createErrorText(errors) }; } @@ -95,7 +89,7 @@ export class TypescriptChecker implements Checker { if (this.typeScriptCheckeroptions.typeScriptChecker.strategy === 'noGrouping') { return mutants.map((m) => [m.id]); } - const nodes = this.tsCompiler.getFileRelationsAsNodes(); + const nodes = this.tsCompiler.nodes; const mutantsOutSideProject = mutants.filter((m) => nodes.get(toPosixFileName(m.fileName)) == null).map((m) => m.id); const mutantsToTest = mutants.filter((m) => nodes.get(toPosixFileName(m.fileName)) != null); @@ -112,7 +106,7 @@ export class TypescriptChecker implements Checker { private async checkErrors( mutants: Mutant[], errorsMap: Record, - nodes: Map + nodes: Map ): Promise> { const errors = await this.tsCompiler.check(mutants); const mutantsThatCouldNotBeTestedInGroups = new Set(); @@ -124,23 +118,30 @@ export class TypescriptChecker implements Checker { } for (const error of errors) { - const nodeErrorWasThrownIn = nodes.get(error.file?.fileName ?? ''); + if (!error.file?.fileName) { + throw new Error( + `Typescript error: '${error.messageText}' doesnt have a corresponding file, if you think this is a bug please open an issue on the stryker-js github` + ); + } + const nodeErrorWasThrownIn = nodes.get(error.file?.fileName); if (!nodeErrorWasThrownIn) { - throw new Error('Error not found in any node'); + throw new Error( + 'Typescript error located in a file that is not part of your project or doesnt have a reference to your project. This shouldnt happen, please open an issue on the stryker-js github' + ); } const mutantsRelatedToError = nodeErrorWasThrownIn.getMutantsWithReferenceToChildrenOrSelf(mutants); if (mutantsRelatedToError.length === 1) { - if (errorsMap[mutantsRelatedToError[0].id]) { - errorsMap[mutantsRelatedToError[0].id].push(error); + // There is only one mutant related to the typescript error so we can add it to the errorsRelatedToMutant + let errorsRelatedToMutant = errorsMap[mutantsRelatedToError[0].id]; + if (errorsRelatedToMutant) { + errorsRelatedToMutant.push(error); } else { - errorsMap[mutantsRelatedToError[0].id] = [error]; - } - } else if (mutantsRelatedToError.length === 0) { - for (const mutant of mutants) { - mutantsThatCouldNotBeTestedInGroups.add(mutant); + errorsRelatedToMutant = [error]; } } else { + // If there are more than one mutants related to the error we should check them individually + // Also in rare cases there are no mutants related to the typescript error so then we also need to check the mutants individually for (const mutant of mutantsRelatedToError) { mutantsThatCouldNotBeTestedInGroups.add(mutant); } @@ -168,11 +169,4 @@ export class TypescriptChecker implements Checker { getNewLine: () => EOL, }); } - - private loadSetup(options: StrykerOptions): TypeScriptCheckerOptions { - const defaultTypeScriptCheckerConfig: TypeScriptCheckerOptions = { - typeScriptChecker: { strategy: 'noGrouping' }, - }; - return Object.assign(defaultTypeScriptCheckerConfig, options as TypeScriptCheckerOptionsWithStrykerOptions); - } } diff --git a/packages/typescript-checker/src/typescript-compiler.ts b/packages/typescript-checker/src/typescript-compiler.ts index 8d097cf6c7..ba2f44e50b 100644 --- a/packages/typescript-checker/src/typescript-compiler.ts +++ b/packages/typescript-checker/src/typescript-compiler.ts @@ -9,7 +9,7 @@ import { tokens, commonTokens } from '@stryker-mutator/api/plugin'; import { HybridFileSystem } from './fs/index.js'; import { determineBuildModeEnabled, guardTSVersion, overrideOptions, retrieveReferencedProjects, toPosixFileName } from './tsconfig-helpers.js'; -import { Node } from './grouping/node.js'; +import { TSFileNode } from './grouping/node.js'; import * as pluginTokens from './plugin-tokens.js'; export interface ITypescriptCompiler { @@ -18,7 +18,7 @@ export interface ITypescriptCompiler { } export interface IFileRelationCreator { - getFileRelationsAsNodes(): void; + get nodes(): Map; } export type SourceFiles = Map< @@ -38,7 +38,7 @@ export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCre private currentTask = new Task(); private currentErrors: ts.Diagnostic[] = []; private readonly sourceFiles: SourceFiles = new Map(); - private readonly nodes = new Map(); + private readonly _nodes = new Map(); private lastMutants: Mutant[] = []; constructor(private readonly log: Logger, private readonly options: StrykerOptions, private readonly fs: HybridFileSystem) { @@ -101,7 +101,7 @@ export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCre }, (...args) => { const program = ts.createEmitAndSemanticDiagnosticsBuilderProgram(...args); - if (this.nodes.size) { + if (this._nodes.size) { return program; } program @@ -146,11 +146,11 @@ export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCre public async check(mutants: Mutant[]): Promise { this.lastMutants.forEach((mutant) => { const file = this.fs.getFile(mutant.fileName); - file?.resetMutant(); + file!.resetMutant(); }); mutants.forEach((mutant) => { const file = this.fs.getFile(mutant.fileName); - file?.mutate(mutant); + file!.mutate(mutant); }); await this.currentTask.promise; const errors = this.currentErrors; @@ -160,29 +160,29 @@ export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCre return errors; } - public getFileRelationsAsNodes(): Map { - if (!this.nodes.size) { + public get nodes(): Map { + if (!this._nodes.size) { // create nodes for (const [fileName] of this.sourceFiles) { - const node = new Node(fileName, [], []); - this.nodes.set(fileName, node); + const node = new TSFileNode(fileName, [], []); + this._nodes.set(fileName, node); } // set children for (const [fileName, file] of this.sourceFiles) { - const node = this.nodes.get(fileName); + const node = this._nodes.get(fileName); if (node == null) { throw new Error('todo'); } const importFileNames = [...file.imports]; // todo fix ! - node.children = importFileNames.map((importName) => this.nodes.get(importName)!).filter((n) => n != undefined); + node.children = importFileNames.map((importName) => this._nodes.get(importName)!).filter((n) => n != undefined); } - for (const [, node] of this.nodes) { + for (const [, node] of this._nodes) { node.parents = []; - for (const [_, n] of this.nodes) { + for (const [_, n] of this._nodes) { if (n.children.includes(node)) { node.parents.push(n); } @@ -190,7 +190,7 @@ export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCre } } - return this.nodes; + return this._nodes; } private resolveFilename(fileName: string): string[] { diff --git a/packages/typescript-checker/test/unit/grouping/create-groups.spec.ts b/packages/typescript-checker/test/unit/grouping/create-groups.spec.ts index 877f6f9f97..74cbdbe1c5 100644 --- a/packages/typescript-checker/test/unit/grouping/create-groups.spec.ts +++ b/packages/typescript-checker/test/unit/grouping/create-groups.spec.ts @@ -2,14 +2,14 @@ import { expect } from 'chai'; import { factory } from '@stryker-mutator/test-helpers'; -import { Node } from '../../../src/grouping/node.js'; +import { TSFileNode } from '../../../src/grouping/node.js'; import { createGroups } from '../../../src/grouping/create-groups.js'; describe('create-group createGroups', () => { it('single mutant should create single group', () => { const mutants = [factory.mutant({ fileName: 'a.js', id: 'mutant-1' })]; - const nodes = new Map([['a.js', new Node('a.js', [], [])]]); + const nodes = new Map([['a.js', new TSFileNode('a.js', [], [])]]); const groups = createGroups(mutants, nodes); expect(groups).to.have.lengthOf(1); expect(groups[0]).to.have.lengthOf(1); @@ -19,9 +19,9 @@ describe('create-group createGroups', () => { it('two mutants in different files without reference to each other should create single group', () => { const mutants = [factory.mutant({ fileName: 'a.js', id: '1' }), factory.mutant({ fileName: 'b.js', id: '2' })]; const mutantsClone = structuredClone(mutants); - const nodes = new Map([ - ['a.js', new Node('a.js', [], [])], - ['b.js', new Node('b.js', [], [])], + const nodes = new Map([ + ['a.js', new TSFileNode('a.js', [], [])], + ['b.js', new TSFileNode('b.js', [], [])], ]); const groups = createGroups(mutants, nodes); expect(groups).to.have.lengthOf(1); @@ -32,9 +32,9 @@ describe('create-group createGroups', () => { it('two mutants in different files with reference to each other should create 2 groups', () => { const mutants = [factory.mutant({ fileName: 'a.js', id: '1' }), factory.mutant({ fileName: 'b.js', id: '2' })]; const mutantsClone = structuredClone(mutants); - const nodeA = new Node('a.js', [], []); - const nodeB = new Node('b.js', [nodeA], []); - const nodes = new Map([ + const nodeA = new TSFileNode('a.js', [], []); + const nodeB = new TSFileNode('b.js', [nodeA], []); + const nodes = new Map([ [nodeA.fileName, nodeA], [nodeB.fileName, nodeB], ]); @@ -47,10 +47,10 @@ describe('create-group createGroups', () => { it('two mutants in different files with circular dependency to each other should create 2 groups', () => { const mutants = [factory.mutant({ fileName: 'a.js', id: '1' }), factory.mutant({ fileName: 'b.js', id: '2' })]; const mutantsClone = structuredClone(mutants); - const nodeA = new Node('a.js', [], []); - const nodeB = new Node('b.js', [nodeA], []); + const nodeA = new TSFileNode('a.js', [], []); + const nodeB = new TSFileNode('b.js', [nodeA], []); nodeA.parents.push(nodeB); - const nodes = new Map([ + const nodes = new Map([ [nodeA.fileName, nodeA], [nodeB.fileName, nodeB], ]); @@ -63,8 +63,8 @@ describe('create-group createGroups', () => { it('two mutants in same file should create 2 groups', () => { const mutants = [factory.mutant({ fileName: 'a.js', id: '1' }), factory.mutant({ fileName: 'a.js', id: '2' })]; const mutantsClone = structuredClone(mutants); - const nodeA = new Node('a.js', [], []); - const nodes = new Map([[nodeA.fileName, nodeA]]); + const nodeA = new TSFileNode('a.js', [], []); + const nodes = new Map([[nodeA.fileName, nodeA]]); const groups = createGroups(mutants, nodes); expect(groups).to.have.lengthOf(2); expect(groups[0][0]).to.be.equal(mutantsClone[0].id); @@ -81,13 +81,13 @@ describe('create-group createGroups', () => { factory.mutant({ fileName: 'f.js', id: '6' }), ]; const mutantsClone = structuredClone(mutants); - const nodeA = new Node('a.js', [], []); - const nodeB = new Node('b.js', [nodeA], []); - const nodeC = new Node('c.js', [nodeA], []); - const nodeD = new Node('d.js', [nodeC], []); - const nodeE = new Node('e.js', [nodeA], []); - const nodeF = new Node('f.js', [nodeE, nodeD], []); - const nodes = new Map([ + const nodeA = new TSFileNode('a.js', [], []); + const nodeB = new TSFileNode('b.js', [nodeA], []); + const nodeC = new TSFileNode('c.js', [nodeA], []); + const nodeD = new TSFileNode('d.js', [nodeC], []); + const nodeE = new TSFileNode('e.js', [nodeA], []); + const nodeF = new TSFileNode('f.js', [nodeE, nodeD], []); + const nodes = new Map([ [nodeA.fileName, nodeA], [nodeB.fileName, nodeB], [nodeC.fileName, nodeC], diff --git a/packages/typescript-checker/test/unit/grouping/node.spec.ts b/packages/typescript-checker/test/unit/grouping/node.spec.ts index 6a4fc835f2..55cc79c826 100644 --- a/packages/typescript-checker/test/unit/grouping/node.spec.ts +++ b/packages/typescript-checker/test/unit/grouping/node.spec.ts @@ -2,59 +2,59 @@ import { expect } from 'chai'; import { Mutant } from '@stryker-mutator/api/src/core'; -import { Node } from '../../../src/grouping/node.js'; +import { TSFileNode } from '../../../src/grouping/node.js'; describe('node', () => { it('getAllParentReferencesIncludingSelf without parent should return array of 1 node ', () => { - const node = new Node('NodeA', [], []); + const node = new TSFileNode('NodeA', [], []); expect(node.getAllParentReferencesIncludingSelf()).to.have.lengthOf(1); }); it('getAllParentReferencesIncludingSelf with 1 parent should return array of 2 nodes ', () => { - const node = new Node('NodeA', [new Node('', [], [])], []); + const node = new TSFileNode('NodeA', [new TSFileNode('', [], [])], []); expect(node.getAllParentReferencesIncludingSelf()).to.have.lengthOf(2); }); it('getAllParentReferencesIncludingSelf with recursive depth of 2 should return 3 nodes ', () => { - const node = new Node('NodeA', [new Node('', [new Node('', [], [])], [])], []); + const node = new TSFileNode('NodeA', [new TSFileNode('', [new TSFileNode('', [], [])], [])], []); expect(node.getAllParentReferencesIncludingSelf()).to.have.lengthOf(3); }); it('getAllParentReferencesIncludingSelf with recursive depth of 2 and multiple parents should return 4 nodes ', () => { - const node = new Node('NodeA', [new Node('', [new Node('', [], []), new Node('', [], [])], [])], []); + const node = new TSFileNode('NodeA', [new TSFileNode('', [new TSFileNode('', [], []), new TSFileNode('', [], [])], [])], []); expect(node.getAllParentReferencesIncludingSelf()).to.have.lengthOf(4); }); it('getAllParentReferencesIncludingSelf with circular dependency should skip circular dependency node ', () => { - const nodeA = new Node('NodeA', [], []); - const nodeC = new Node('NodeB', [nodeA], []); - const nodeB = new Node('NodeB', [nodeC], []); + const nodeA = new TSFileNode('NodeA', [], []); + const nodeC = new TSFileNode('NodeB', [nodeA], []); + const nodeB = new TSFileNode('NodeB', [nodeC], []); nodeA.parents.push(nodeB); expect(nodeA.getAllParentReferencesIncludingSelf()).to.have.lengthOf(3); }); it('getAllChildReferencesIncludingSelf without parent should return array of 1 node ', () => { - const node = new Node('NodeA', [], []); + const node = new TSFileNode('NodeA', [], []); expect(node.getAllChildReferencesIncludingSelf()).to.have.lengthOf(1); }); it('getAllChildReferencesIncludingSelf with 1 child should return array of 2 nodes ', () => { - const node = new Node('NodeA', [], [new Node('', [], [])]); + const node = new TSFileNode('NodeA', [], [new TSFileNode('', [], [])]); expect(node.getAllChildReferencesIncludingSelf()).to.have.lengthOf(2); }); it('getAllChildReferencesIncludingSelf with recursive depth of 2 should return 3 nodes ', () => { - const node = new Node('NodeA', [], [new Node('', [], [new Node('', [], [])])]); + const node = new TSFileNode('NodeA', [], [new TSFileNode('', [], [new TSFileNode('', [], [])])]); expect(node.getAllChildReferencesIncludingSelf()).to.have.lengthOf(3); }); it('getAllChildReferencesIncludingSelf with recursive depth of 2 and multiple parents should return 4 nodes ', () => { - const node = new Node('NodeA', [], [new Node('', [], [new Node('', [], []), new Node('', [], [])])]); + const node = new TSFileNode('NodeA', [], [new TSFileNode('', [], [new TSFileNode('', [], []), new TSFileNode('', [], [])])]); expect(node.getAllChildReferencesIncludingSelf()).to.have.lengthOf(4); }); it('getMutantsWithReferenceToChildrenOrSelf with single mutant in file should return 1 mutant', () => { - const node = new Node('NodeA.js', [], []); + const node = new TSFileNode('NodeA.js', [], []); const mutants: Mutant[] = [ { fileName: 'NodeA.js', id: '0', replacement: '-', location: { start: { line: 1, column: 1 }, end: { line: 1, column: 1 } }, mutatorName: '' }, ]; @@ -62,8 +62,8 @@ describe('node', () => { }); it('getMutantsWithReferenceToChildrenOrSelf with single mutant in child should return 1 mutant', () => { - const node = new Node('NodeA.js', [], []); - const nodeB = new Node('NodeB.js', [], []); + const node = new TSFileNode('NodeA.js', [], []); + const nodeB = new TSFileNode('NodeB.js', [], []); node.children.push(nodeB); const mutants: Mutant[] = [ { fileName: 'NodeB.js', id: '0', replacement: '-', location: { start: { line: 1, column: 1 }, end: { line: 1, column: 1 } }, mutatorName: '' }, From 62ce2788bd43d32005c691be403f84fae935e765 Mon Sep 17 00:00:00 2001 From: odinvanderlinden Date: Tue, 3 Jan 2023 18:24:54 +0100 Subject: [PATCH 51/77] fix test --- packages/typescript-checker/src/typescript-checker.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/typescript-checker/src/typescript-checker.ts b/packages/typescript-checker/src/typescript-checker.ts index d3aa101539..fdbe2c475c 100644 --- a/packages/typescript-checker/src/typescript-checker.ts +++ b/packages/typescript-checker/src/typescript-checker.ts @@ -133,11 +133,14 @@ export class TypescriptChecker implements Checker { if (mutantsRelatedToError.length === 1) { // There is only one mutant related to the typescript error so we can add it to the errorsRelatedToMutant - let errorsRelatedToMutant = errorsMap[mutantsRelatedToError[0].id]; - if (errorsRelatedToMutant) { - errorsRelatedToMutant.push(error); + if (errorsMap[mutantsRelatedToError[0].id]) { + errorsMap[mutantsRelatedToError[0].id].push(error); } else { - errorsRelatedToMutant = [error]; + errorsMap[mutantsRelatedToError[0].id] = [error]; + } + } else if (mutantsRelatedToError.length === 0) { + for (const mutant of mutants) { + mutantsThatCouldNotBeTestedInGroups.add(mutant); } } else { // If there are more than one mutants related to the error we should check them individually From 2da1fc6f3f64314875383bd39c5aa8eea662bcee Mon Sep 17 00:00:00 2001 From: odinvanderlinden Date: Tue, 3 Jan 2023 21:28:28 +0100 Subject: [PATCH 52/77] revert options setup removal --- .../schema/typescript-checker-options.json | 2 +- .../typescript-checker/src/typescript-checker.ts | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/typescript-checker/schema/typescript-checker-options.json b/packages/typescript-checker/schema/typescript-checker-options.json index 4f7efaaadc..a019fbda0c 100644 --- a/packages/typescript-checker/schema/typescript-checker-options.json +++ b/packages/typescript-checker/schema/typescript-checker-options.json @@ -6,7 +6,7 @@ "properties": { "typeScriptChecker": { "description": "Configuration for @stryker-mutator/typescript-checker", - "title": "TypeScriptCheckerOptions", + "title": "TypeScriptCheckerSetup", "additionalProperties": false, "type": "object", "default": {}, diff --git a/packages/typescript-checker/src/typescript-checker.ts b/packages/typescript-checker/src/typescript-checker.ts index fdbe2c475c..c892fb7da8 100644 --- a/packages/typescript-checker/src/typescript-checker.ts +++ b/packages/typescript-checker/src/typescript-checker.ts @@ -6,7 +6,7 @@ import { tokens, commonTokens, PluginContext, Injector, Scope } from '@stryker-m import { Logger, LoggerFactoryMethod } from '@stryker-mutator/api/logging'; import { Mutant, StrykerOptions } from '@stryker-mutator/api/core'; -import { TypeScriptCheckerOptions } from '../src-generated/typescript-checker-options.js'; +import { TypeScriptCheckerOptions, TypeScriptCheckerSetup } from '../src-generated/typescript-checker-options.js'; import * as pluginTokens from './plugin-tokens.js'; import { TypescriptCompiler } from './typescript-compiler.js'; @@ -42,10 +42,10 @@ export class TypescriptChecker implements Checker { */ public static inject = tokens(commonTokens.logger, commonTokens.options, pluginTokens.tsCompiler); - private readonly typeScriptCheckeroptions: TypeScriptCheckerOptions; + private readonly typeScriptCheckerSetup: TypeScriptCheckerOptions; constructor(private readonly logger: Logger, options: StrykerOptions, private readonly tsCompiler: TypescriptCompiler) { - this.typeScriptCheckeroptions = options as TypeScriptCheckerOptionsWithStrykerOptions; + this.typeScriptCheckerSetup = this.loadSetup(options); } /** @@ -86,7 +86,7 @@ export class TypescriptChecker implements Checker { * @param mutants All the mutants to group. */ public async group(mutants: Mutant[]): Promise { - if (this.typeScriptCheckeroptions.typeScriptChecker.strategy === 'noGrouping') { + if (this.typeScriptCheckerSetup.typeScriptChecker.strategy === 'noGrouping') { return mutants.map((m) => [m.id]); } const nodes = this.tsCompiler.nodes; @@ -172,4 +172,11 @@ export class TypescriptChecker implements Checker { getNewLine: () => EOL, }); } + + private loadSetup(options: StrykerOptions): TypeScriptCheckerOptions { + const defaultTypeScriptCheckerConfig: TypeScriptCheckerOptions = { + typeScriptChecker: { strategy: 'noGrouping' }, + }; + return Object.assign(defaultTypeScriptCheckerConfig, (options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker); + } } From 3522d45bece69fbe9c1324a95a0c11edc1719164 Mon Sep 17 00:00:00 2001 From: Danny Date: Thu, 5 Jan 2023 12:10:25 +0100 Subject: [PATCH 53/77] Update packages/typescript-checker/src/fs/script-file.ts Co-authored-by: Nico Jansen --- packages/typescript-checker/src/fs/script-file.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typescript-checker/src/fs/script-file.ts b/packages/typescript-checker/src/fs/script-file.ts index c2dbaad279..675d0912ad 100644 --- a/packages/typescript-checker/src/fs/script-file.ts +++ b/packages/typescript-checker/src/fs/script-file.ts @@ -45,7 +45,7 @@ export class ScriptFile { } } - public touch(): void { + private touch(): void { this.modifiedTime = new Date(); this.watcher?.(this.fileName, ts.FileWatcherEventKind.Changed); } From 75386e455d923f51e1572ade6e8cd49122545067 Mon Sep 17 00:00:00 2001 From: Danny Date: Thu, 5 Jan 2023 13:03:55 +0100 Subject: [PATCH 54/77] Update packages/typescript-checker/schema/typescript-checker-options.json Co-authored-by: Nico Jansen --- .../typescript-checker/schema/typescript-checker-options.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typescript-checker/schema/typescript-checker-options.json b/packages/typescript-checker/schema/typescript-checker-options.json index 06082e3d6c..4f7efaaadc 100644 --- a/packages/typescript-checker/schema/typescript-checker-options.json +++ b/packages/typescript-checker/schema/typescript-checker-options.json @@ -6,7 +6,7 @@ "properties": { "typeScriptChecker": { "description": "Configuration for @stryker-mutator/typescript-checker", - "title": "StrykerTypeScriptCheckerSetup", + "title": "TypeScriptCheckerOptions", "additionalProperties": false, "type": "object", "default": {}, From bf6dda77dee4bf04ee1d3c91a49e218d81c020d4 Mon Sep 17 00:00:00 2001 From: Danny Date: Fri, 6 Jan 2023 09:00:32 +0100 Subject: [PATCH 55/77] Update packages/typescript-checker/src/typescript-checker.ts Co-authored-by: Nico Jansen --- packages/typescript-checker/src/typescript-checker.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/typescript-checker/src/typescript-checker.ts b/packages/typescript-checker/src/typescript-checker.ts index 399f4a9c16..55c8b9f053 100644 --- a/packages/typescript-checker/src/typescript-checker.ts +++ b/packages/typescript-checker/src/typescript-checker.ts @@ -100,12 +100,13 @@ export class TypescriptChecker implements Checker { const mutantsOutSideProject = mutants.filter((m) => nodes.get(toPosixFileName(m.fileName)) == null).map((m) => m.id); const mutantsToTest = mutants.filter((m) => nodes.get(toPosixFileName(m.fileName)) != null); - const groups = createGroups(mutantsToTest, nodes); - const sortedGroups = groups.sort((a, b) => b.length - a.length); - const result = mutantsOutSideProject.length ? [mutantsOutSideProject, ...sortedGroups] : sortedGroups; + const groups = createGroups(mutantsToTest, nodes).sort((a, b) => b.length - a.length); + this.logger.info(`Created ${groups.length} groups with largest group of ${groups[0]?.length ?? 0} mutants`); + if (mutantsOutSideProject.length) { + groups.unshift(mutantsOutSideProject); + } - this.logger.info(`Created ${result.length} groups with largest group of ${result[0].length} mutants`); - return result; + return groups; } private async checkErrors( From 5b209c5f31ec3ca385cd0bbfcb52178792c6fc26 Mon Sep 17 00:00:00 2001 From: Danny Date: Fri, 6 Jan 2023 09:03:37 +0100 Subject: [PATCH 56/77] Update packages/typescript-checker/test/unit/grouping/create-groups.spec.ts Co-authored-by: Nico Jansen --- .../test/unit/grouping/create-groups.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/typescript-checker/test/unit/grouping/create-groups.spec.ts b/packages/typescript-checker/test/unit/grouping/create-groups.spec.ts index 79ec98ea95..877f6f9f97 100644 --- a/packages/typescript-checker/test/unit/grouping/create-groups.spec.ts +++ b/packages/typescript-checker/test/unit/grouping/create-groups.spec.ts @@ -8,12 +8,12 @@ import { createGroups } from '../../../src/grouping/create-groups.js'; describe('create-group createGroups', () => { it('single mutant should create single group', () => { - const mutants = [factory.mutant({ fileName: 'a.js', id: '1' })]; - const mutantsClone = structuredClone(mutants); + const mutants = [factory.mutant({ fileName: 'a.js', id: 'mutant-1' })]; const nodes = new Map([['a.js', new Node('a.js', [], [])]]); const groups = createGroups(mutants, nodes); expect(groups).to.have.lengthOf(1); - expect(groups[0][0]).to.be.equal(mutantsClone[0].id); + expect(groups[0]).to.have.lengthOf(1); + expect(groups[0][0]).to.be.equal('mutant-1'); }); it('two mutants in different files without reference to each other should create single group', () => { From f1c10d2b6bf65313c8ea1d4ee2f7b5c3e76b0dec Mon Sep 17 00:00:00 2001 From: Danny Berkelaar Date: Fri, 6 Jan 2023 11:23:15 +0100 Subject: [PATCH 57/77] Fixed typo's --- packages/typescript-checker/src/tsconfig-helpers.ts | 6 +++--- packages/typescript-checker/src/typescript-compiler.ts | 8 ++++---- .../test/unit/typescript-helpers.spec.ts | 8 +------- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/packages/typescript-checker/src/tsconfig-helpers.ts b/packages/typescript-checker/src/tsconfig-helpers.ts index 6c600db9d4..9f478164ad 100644 --- a/packages/typescript-checker/src/tsconfig-helpers.ts +++ b/packages/typescript-checker/src/tsconfig-helpers.ts @@ -9,8 +9,6 @@ const COMPILER_OPTIONS_OVERRIDES: Readonly> = Object allowUnreachableCode: true, noUnusedLocals: false, noUnusedParameters: false, - declarationMap: true, - declaration: true, }); // When we're running in 'single-project' mode, we can safely disable emit @@ -25,11 +23,13 @@ const NO_EMIT_OPTIONS_FOR_SINGLE_PROJECT: Readonly> const LOW_EMIT_OPTIONS_FOR_PROJECT_REFERENCES: Readonly> = Object.freeze({ emitDeclarationOnly: true, noEmit: false, + declarationMap: true, + declaration: true, }); export function guardTSVersion(): void { if (!semver.satisfies(ts.version, '>=3.6')) { - throw new Error(`@stryker-mutator/typescript-checker only supports typescript@3.6 our higher. Found typescript@${ts.version}`); + throw new Error(`@stryker-mutator/typescript-checker only supports typescript@3.6 or higher. Found typescript@${ts.version}`); } } diff --git a/packages/typescript-checker/src/typescript-compiler.ts b/packages/typescript-checker/src/typescript-compiler.ts index 6ce4de0281..04565f69b7 100644 --- a/packages/typescript-checker/src/typescript-compiler.ts +++ b/packages/typescript-checker/src/typescript-compiler.ts @@ -14,7 +14,7 @@ import * as pluginTokens from './plugin-tokens.js'; export interface ITypescriptCompiler { init(): Promise; - check(mutants: Mutant[]): Promise; // todo set return type + check(mutants: Mutant[]): Promise; } export interface IFileRelationCreator { @@ -168,7 +168,7 @@ export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCre this.nodes.set(fileName, node); } - // set childs + // set children for (const [fileName, file] of this.sourceFiles) { const node = this.nodes.get(fileName); if (node == null) { @@ -176,13 +176,13 @@ export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCre } const importFileNames = [...file.imports]; - // todo fix ! node.children = importFileNames.map((importName) => this.nodes.get(importName)!).filter((n) => n != undefined); } + // set parents for (const [, node] of this.nodes) { node.parents = []; - for (const [_, n] of this.nodes) { + for (const [, n] of this.nodes) { if (n.children.includes(node)) { node.parents.push(n); } diff --git a/packages/typescript-checker/test/unit/typescript-helpers.spec.ts b/packages/typescript-checker/test/unit/typescript-helpers.spec.ts index a57c01ab24..7f9915b779 100644 --- a/packages/typescript-checker/test/unit/typescript-helpers.spec.ts +++ b/packages/typescript-checker/test/unit/typescript-helpers.spec.ts @@ -53,8 +53,6 @@ describe('typescript-helpers', () => { noEmit: true, incremental: false, composite: false, - declaration: true, - declarationMap: true, }); expect( JSON.parse( @@ -65,8 +63,6 @@ describe('typescript-helpers', () => { noEmit: false, incremental: true, composite: true, - declaration: false, - declarationMap: true, }, }, }, @@ -77,8 +73,6 @@ describe('typescript-helpers', () => { noEmit: true, incremental: false, composite: false, - declaration: true, - declarationMap: true, }); }); @@ -175,7 +169,7 @@ describe('typescript-helpers', () => { describe(guardTSVersion.name, () => { it('should throw if typescript@2.5.0', () => { sinon.stub(ts, 'version').value('3.5.0'); - expect(guardTSVersion).throws('@stryker-mutator/typescript-checker only supports typescript@3.6 our higher. Found typescript@3.5.0'); + expect(guardTSVersion).throws('@stryker-mutator/typescript-checker only supports typescript@3.6 or higher. Found typescript@3.5.0'); }); it('should not throw if typescript@3.6.0', () => { sinon.stub(ts, 'version').value('3.6.0'); From 9b33db45ff3ef2f68def19ce1f9850cc716ed064 Mon Sep 17 00:00:00 2001 From: Danny Berkelaar Date: Fri, 6 Jan 2023 11:28:16 +0100 Subject: [PATCH 58/77] Fix typo --- packages/typescript-checker/src/grouping/node.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typescript-checker/src/grouping/node.ts b/packages/typescript-checker/src/grouping/node.ts index 043f0530b3..725f203963 100644 --- a/packages/typescript-checker/src/grouping/node.ts +++ b/packages/typescript-checker/src/grouping/node.ts @@ -1,7 +1,7 @@ import { Mutant } from '@stryker-mutator/api/src/core'; // This class exist so we can have a two way dependency graph. -// the two way dependecay graph is used to search for mutants related to typescript errors +// the two way dependency graph is used to search for mutants related to typescript errors export class TSFileNode { constructor(public fileName: string, public parents: TSFileNode[], public children: TSFileNode[]) {} From f9f0c3df3f35a224063bd74381777471915eac66 Mon Sep 17 00:00:00 2001 From: Danny Berkelaar Date: Fri, 6 Jan 2023 11:37:54 +0100 Subject: [PATCH 59/77] Fixed some comments --- .../src/typescript-checker.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/typescript-checker/src/typescript-checker.ts b/packages/typescript-checker/src/typescript-checker.ts index fdbe2c475c..505e2bb4c4 100644 --- a/packages/typescript-checker/src/typescript-checker.ts +++ b/packages/typescript-checker/src/typescript-checker.ts @@ -120,31 +120,32 @@ export class TypescriptChecker implements Checker { for (const error of errors) { if (!error.file?.fileName) { throw new Error( - `Typescript error: '${error.messageText}' doesnt have a corresponding file, if you think this is a bug please open an issue on the stryker-js github` + `Typescript error: '${error.messageText}' doesn\'t have a corresponding file, if you think this is a bug please open an issue on the stryker-js github` ); } const nodeErrorWasThrownIn = nodes.get(error.file?.fileName); if (!nodeErrorWasThrownIn) { throw new Error( - 'Typescript error located in a file that is not part of your project or doesnt have a reference to your project. This shouldnt happen, please open an issue on the stryker-js github' + "Typescript error located in a file that is not part of your project or doesn't have a reference to your project. This shouldn't happen, please open an issue on the stryker-js github" ); } const mutantsRelatedToError = nodeErrorWasThrownIn.getMutantsWithReferenceToChildrenOrSelf(mutants); - if (mutantsRelatedToError.length === 1) { + if (mutantsRelatedToError.length === 0) { + // In rare cases there are no mutants related to the typescript error + // Having to test all mutants individually to know which mutant thrown the error + for (const mutant of mutants) { + mutantsThatCouldNotBeTestedInGroups.add(mutant); + } + } else if (mutantsRelatedToError.length === 1) { // There is only one mutant related to the typescript error so we can add it to the errorsRelatedToMutant if (errorsMap[mutantsRelatedToError[0].id]) { errorsMap[mutantsRelatedToError[0].id].push(error); } else { errorsMap[mutantsRelatedToError[0].id] = [error]; } - } else if (mutantsRelatedToError.length === 0) { - for (const mutant of mutants) { - mutantsThatCouldNotBeTestedInGroups.add(mutant); - } } else { - // If there are more than one mutants related to the error we should check them individually - // Also in rare cases there are no mutants related to the typescript error so then we also need to check the mutants individually + // If there are more than one mutants related to the error we should check them individually for (const mutant of mutantsRelatedToError) { mutantsThatCouldNotBeTestedInGroups.add(mutant); } From 73f049c84c61ff82980c1155a57a5fbe8b05e1ef Mon Sep 17 00:00:00 2001 From: Danny Berkelaar Date: Fri, 6 Jan 2023 11:40:16 +0100 Subject: [PATCH 60/77] Fixed typo in comment --- packages/typescript-checker/src/typescript-checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typescript-checker/src/typescript-checker.ts b/packages/typescript-checker/src/typescript-checker.ts index 505e2bb4c4..f4ed14e85a 100644 --- a/packages/typescript-checker/src/typescript-checker.ts +++ b/packages/typescript-checker/src/typescript-checker.ts @@ -67,7 +67,7 @@ export class TypescriptChecker implements Checker { public async check(mutants: Mutant[]): Promise> { const result: Record = Object.fromEntries(mutants.map((mutant) => [mutant.id, { status: CheckStatus.Passed }])); - // Check if this is the group with unrelated files and return al + // Check if this is the group with unrelated files and return check status passed if so if (!this.tsCompiler.nodes.get(toPosixFileName(mutants[0].fileName))) { return result; } From 605a5835201dd31bb289c001f63d90079719dc5d Mon Sep 17 00:00:00 2001 From: Danny Berkelaar Date: Fri, 6 Jan 2023 13:50:51 +0100 Subject: [PATCH 61/77] Updated declaration test --- .../typescript-checker/test/unit/typescript-helpers.spec.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/typescript-checker/test/unit/typescript-helpers.spec.ts b/packages/typescript-checker/test/unit/typescript-helpers.spec.ts index 7f9915b779..413e54e10e 100644 --- a/packages/typescript-checker/test/unit/typescript-helpers.spec.ts +++ b/packages/typescript-checker/test/unit/typescript-helpers.spec.ts @@ -145,9 +145,10 @@ describe('typescript-helpers', () => { }); }); - it('should set --declarationMap to true', () => { - expect(JSON.parse(overrideOptions({ config: { declarationMap: false } }, true)).compilerOptions).deep.include({ + it('should set --declarationMap and --declaration options when `--build` mode is on', () => { + expect(JSON.parse(overrideOptions({ config: { declarationMap: false, declaration: false } }, true)).compilerOptions).deep.include({ declarationMap: true, + declaration: true, }); }); }); From 2d8ce3c237e27238fdf42edc888e56b8b541c032 Mon Sep 17 00:00:00 2001 From: Danny Berkelaar Date: Fri, 20 Jan 2023 15:04:30 +0100 Subject: [PATCH 62/77] Fixed PR comments --- .../schema/typescript-checker-options.json | 15 +-- .../src/grouping/create-groups.ts | 4 +- .../src/grouping/{node.ts => ts-file-node.ts} | 6 +- packages/typescript-checker/src/index.ts | 6 + .../src/tsconfig-helpers.ts | 20 ++++ .../src/typescript-checker.ts | 8 +- .../src/typescript-compiler.ts | 59 +++++++--- .../integration/project-references.it.spec.ts | 2 +- .../project-with-ts-buildinfo.it.spec.ts | 2 +- .../integration/single-project.it.spec.ts | 2 +- .../typescript-checkers-errors.it.spec.ts | 6 +- .../test/unit/grouping/create-groups.spec.ts | 12 +- .../test/unit/grouping/node.spec.ts | 73 ------------ .../test/unit/grouping/ts-file-node.spec.ts | 108 ++++++++++++++++++ .../test/unit/typescript-checker.spec.ts | 18 ++- .../test/unit/typescript-helpers.spec.ts | 50 +++++++- 16 files changed, 269 insertions(+), 122 deletions(-) rename packages/typescript-checker/src/grouping/{node.ts => ts-file-node.ts} (88%) delete mode 100644 packages/typescript-checker/test/unit/grouping/node.spec.ts create mode 100644 packages/typescript-checker/test/unit/grouping/ts-file-node.spec.ts diff --git a/packages/typescript-checker/schema/typescript-checker-options.json b/packages/typescript-checker/schema/typescript-checker-options.json index 4f7efaaadc..1a99235d0d 100644 --- a/packages/typescript-checker/schema/typescript-checker-options.json +++ b/packages/typescript-checker/schema/typescript-checker-options.json @@ -11,21 +11,18 @@ "type": "object", "default": {}, "properties": { - "strategy": { - "$ref": "#/definitions/typeScriptCheckerStrategy", - "default": "noGrouping" + "prioritizePerformanceOverAccuracy": { + "$ref": "#/definitions/typeScriptCheckerStrategy" } } } }, "definitions": { "typeScriptCheckerStrategy": { - "title": "TypeScriptCheckerStrategy", - "description": "Specify which strategy should be used for the typescript checker", - "enum": [ - "grouping", - "noGrouping" - ] + "title": "PrioritizePerformanceOverAccuracy", + "type": "boolean", + "default": true, + "description": "Configure the performance of the TypeScriptChecker. Setting this to false results in a slower, but more accurate result." } } } diff --git a/packages/typescript-checker/src/grouping/create-groups.ts b/packages/typescript-checker/src/grouping/create-groups.ts index 48d35761a1..7f5cc3900a 100644 --- a/packages/typescript-checker/src/grouping/create-groups.ts +++ b/packages/typescript-checker/src/grouping/create-groups.ts @@ -2,7 +2,7 @@ import { Mutant } from '@stryker-mutator/api/src/core/index.js'; import { toPosixFileName } from '../tsconfig-helpers.js'; -import { TSFileNode } from './node.js'; +import { TSFileNode } from './ts-file-node.js'; /** * To speed up the type-checking we want to check multiple mutants at once. @@ -73,7 +73,7 @@ function addRangeOfNodesToSet(nodes: Set, nodesToAdd: Iterable) { const node = nodes.get(toPosixFileName(fileName)); if (node == null) { - throw new Error(`Node not in graph: "${fileName}"`); + throw new Error(`Node not in graph: ${fileName}`); } return node; } diff --git a/packages/typescript-checker/src/grouping/node.ts b/packages/typescript-checker/src/grouping/ts-file-node.ts similarity index 88% rename from packages/typescript-checker/src/grouping/node.ts rename to packages/typescript-checker/src/grouping/ts-file-node.ts index 725f203963..e7346db747 100644 --- a/packages/typescript-checker/src/grouping/node.ts +++ b/packages/typescript-checker/src/grouping/ts-file-node.ts @@ -25,6 +25,7 @@ export class TSFileNode { return allChildReferences; } + // todo mogelijk mutants een map van maken [filename]: mutant[] public getMutantsWithReferenceToChildrenOrSelf(mutants: Mutant[], nodesChecked: string[] = []): Mutant[] { if (nodesChecked.includes(this.fileName)) { return []; @@ -32,9 +33,8 @@ export class TSFileNode { nodesChecked.push(this.fileName); - // todo better name - const linkedMutants = mutants.filter((m) => m.fileName == this.fileName); + const relatedMutants = mutants.filter((m) => m.fileName == this.fileName); const childResult = this.children.flatMap((c) => c.getMutantsWithReferenceToChildrenOrSelf(mutants, nodesChecked)); - return [...linkedMutants, ...childResult]; + return [...relatedMutants, ...childResult]; } } diff --git a/packages/typescript-checker/src/index.ts b/packages/typescript-checker/src/index.ts index aac48b8b27..12ce986969 100644 --- a/packages/typescript-checker/src/index.ts +++ b/packages/typescript-checker/src/index.ts @@ -1,3 +1,5 @@ +import fs from 'fs'; + import { PluginKind, declareFactoryPlugin } from '@stryker-mutator/api/plugin'; import { create } from './typescript-checker.js'; @@ -5,3 +7,7 @@ import { create } from './typescript-checker.js'; export const strykerPlugins = [declareFactoryPlugin(PluginKind.Checker, 'typescript', create)]; export const createTypescriptChecker = create; + +export const strykerValidationSchema: typeof import('../schema/typescript-checker-options.json') = JSON.parse( + fs.readFileSync(new URL('../schema/typescript-checker-options.json', import.meta.url), 'utf-8') +); diff --git a/packages/typescript-checker/src/tsconfig-helpers.ts b/packages/typescript-checker/src/tsconfig-helpers.ts index 9f478164ad..f2372123cb 100644 --- a/packages/typescript-checker/src/tsconfig-helpers.ts +++ b/packages/typescript-checker/src/tsconfig-helpers.ts @@ -64,6 +64,15 @@ export function overrideOptions(parsedConfig: { config?: any }, useBuildMode: bo delete compilerOptions.declarationDir; } + if (useBuildMode) { + // Remove the options to place declarations files in different locations to decrease the complexity of searching the source file in the TypescriptCompiler class. + delete compilerOptions.inlineSourceMap; + delete compilerOptions.inlineSources; + delete compilerOptions.mapRoute; + delete compilerOptions.sourceRoot; + delete compilerOptions.outFile; + } + return JSON.stringify({ ...parsedConfig.config, compilerOptions, @@ -92,3 +101,14 @@ export function retrieveReferencedProjects(parsedConfig: { config?: any }, fromD export function toPosixFileName(fileName: string): string { return fileName.replace(/\\/g, '/'); } + +/** + * Find source file in declaration file + * @param content The content of the declaration file + * @returns URL of the source file or undefined if not found + */ +const findSourceMapRegex = /^\/\/# sourceMappingURL=(.+)$/; +export function getSourceMappingURL(content: string): string | undefined { + findSourceMapRegex.lastIndex = 0; + return findSourceMapRegex.exec(content)?.[1]; +} diff --git a/packages/typescript-checker/src/typescript-checker.ts b/packages/typescript-checker/src/typescript-checker.ts index f4ed14e85a..0784a03740 100644 --- a/packages/typescript-checker/src/typescript-checker.ts +++ b/packages/typescript-checker/src/typescript-checker.ts @@ -12,7 +12,7 @@ import * as pluginTokens from './plugin-tokens.js'; import { TypescriptCompiler } from './typescript-compiler.js'; import { createGroups } from './grouping/create-groups.js'; import { toPosixFileName } from './tsconfig-helpers.js'; -import { TSFileNode } from './grouping/node.js'; +import { TSFileNode } from './grouping/ts-file-node.js'; import { TypeScriptCheckerOptionsWithStrykerOptions } from './typescript-checker-options-with-stryker-options.js'; import { HybridFileSystem } from './fs/hybrid-file-system.js'; @@ -42,10 +42,10 @@ export class TypescriptChecker implements Checker { */ public static inject = tokens(commonTokens.logger, commonTokens.options, pluginTokens.tsCompiler); - private readonly typeScriptCheckeroptions: TypeScriptCheckerOptions; + private readonly typeScriptCheckerOptions: TypeScriptCheckerOptions; constructor(private readonly logger: Logger, options: StrykerOptions, private readonly tsCompiler: TypescriptCompiler) { - this.typeScriptCheckeroptions = options as TypeScriptCheckerOptionsWithStrykerOptions; + this.typeScriptCheckerOptions = options as TypeScriptCheckerOptionsWithStrykerOptions; } /** @@ -86,7 +86,7 @@ export class TypescriptChecker implements Checker { * @param mutants All the mutants to group. */ public async group(mutants: Mutant[]): Promise { - if (this.typeScriptCheckeroptions.typeScriptChecker.strategy === 'noGrouping') { + if (!this.typeScriptCheckerOptions.typeScriptChecker.prioritizePerformanceOverAccuracy) { return mutants.map((m) => [m.id]); } const nodes = this.tsCompiler.nodes; diff --git a/packages/typescript-checker/src/typescript-compiler.ts b/packages/typescript-checker/src/typescript-compiler.ts index 45a52123e6..46a23402ca 100644 --- a/packages/typescript-checker/src/typescript-compiler.ts +++ b/packages/typescript-checker/src/typescript-compiler.ts @@ -8,8 +8,15 @@ import { Logger } from '@stryker-mutator/api/logging'; import { tokens, commonTokens } from '@stryker-mutator/api/plugin'; import { HybridFileSystem } from './fs/index.js'; -import { determineBuildModeEnabled, guardTSVersion, overrideOptions, retrieveReferencedProjects, toPosixFileName } from './tsconfig-helpers.js'; -import { TSFileNode } from './grouping/node.js'; +import { + determineBuildModeEnabled, + getSourceMappingURL, + guardTSVersion, + overrideOptions, + retrieveReferencedProjects, + toPosixFileName, +} from './tsconfig-helpers.js'; +import { TSFileNode } from './grouping/ts-file-node.js'; import * as pluginTokens from './plugin-tokens.js'; export interface ITypescriptCompiler { @@ -80,7 +87,9 @@ export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCre watchFile: (fileName: string, callback: ts.FileWatcherCallback) => { const file = this.fs.getFile(fileName); - if (file) file.watcher = callback; + if (file) { + file.watcher = callback; + } return { close: () => { @@ -114,14 +123,16 @@ export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCre program .getAllDependencies(file) .filter((importFile) => !importFile.includes('/node_modules/') && file.fileName !== importFile) - .flatMap((importFile) => this.resolveFilename(importFile)) + .flatMap((importFile) => this.resolveTSInputFile(importFile)) ), }); }); function filterDependency(file: ts.SourceFile) { - if (file.fileName.includes('.d.ts')) return false; - if (file.fileName.includes('node_modules')) return false; + if (file.fileName.endsWith('.d.ts') || file.fileName.includes('node_modules')) { + return false; + } + return true; } @@ -172,7 +183,9 @@ export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCre for (const [fileName, file] of this.sourceFiles) { const node = this._nodes.get(fileName); if (node == null) { - throw new Error('todo'); + throw new Error( + `Node for file '${fileName}' could not be found. This should not happen. This shouldn't happen, please open an issue on the stryker-js github` + ); } const importFileNames = [...file.imports]; @@ -193,26 +206,38 @@ export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCre return this._nodes; } - private resolveFilename(fileName: string): string[] { - if (!fileName.includes('.d.ts')) return [fileName]; + /** + * Resolves TS files to TS source files. + * @param fileName The file name that may be a declaration file + * @returns TS source file if found (fallbacks to input filename) + */ + private resolveTSInputFile(fileName: string): string { + if (!fileName.endsWith('.d.ts')) { + return fileName; + } const file = this.fs.getFile(fileName); - if (!file) throw new Error(`Could not find ${fileName}`); - const sourceMappingURL = this.getSourceMappingURL(file.content); + if (!file) { + throw new Error(`Could not find ${fileName}`); + } - if (!sourceMappingURL) return [fileName]; + const sourceMappingURL = getSourceMappingURL(file.content); + if (!sourceMappingURL) { + return fileName; + } const sourceMapFileName = path.resolve(fileName, '..', sourceMappingURL); const sourceMap = this.fs.getFile(sourceMapFileName); if (!sourceMap) throw new Error(`Could not find ${sourceMapFileName}`); - const content = JSON.parse(sourceMap.content); + const sources: string[] = JSON.parse(sourceMap.content).sources; - return content.sources.map((sourcePath: string) => toPosixFileName(path.resolve(sourceMapFileName, '..', sourcePath))); - } + if (sources.length === 1) { + const sourcePath = sources[0]; + return toPosixFileName(path.resolve(sourceMapFileName, '..', sourcePath)); + } - private getSourceMappingURL(content: string): string | undefined { - return /\/\/# sourceMappingURL=(.+)$/.exec(content)?.[1]; + return fileName; } private adjustTSConfigFile(fileName: string, content: string, buildModeEnabled: boolean) { diff --git a/packages/typescript-checker/test/integration/project-references.it.spec.ts b/packages/typescript-checker/test/integration/project-references.it.spec.ts index 379b9ace14..074c4d68db 100644 --- a/packages/typescript-checker/test/integration/project-references.it.spec.ts +++ b/packages/typescript-checker/test/integration/project-references.it.spec.ts @@ -26,7 +26,7 @@ describe('Typescript checker on a project with project references', () => { let sut: TypescriptChecker; beforeEach(() => { - (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker = { strategy: 'grouping' }; + (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker = { prioritizePerformanceOverAccuracy: true }; testInjector.options.tsconfigFile = resolveTestResource('tsconfig.root.json'); sut = testInjector.injector.injectFunction(createTypescriptChecker); return sut.init(); diff --git a/packages/typescript-checker/test/integration/project-with-ts-buildinfo.it.spec.ts b/packages/typescript-checker/test/integration/project-with-ts-buildinfo.it.spec.ts index 029e7f5340..e8495b9687 100644 --- a/packages/typescript-checker/test/integration/project-with-ts-buildinfo.it.spec.ts +++ b/packages/typescript-checker/test/integration/project-with-ts-buildinfo.it.spec.ts @@ -23,7 +23,7 @@ const resolveTestResource = path.resolve.bind( describe('project-with-ts-buildinfo', () => { it('should load project on init', async () => { - (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker = { strategy: 'grouping' }; + (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker = { prioritizePerformanceOverAccuracy: true }; testInjector.options.tsconfigFile = resolveTestResource('tsconfig.json'); const sut = testInjector.injector.injectFunction(createTypescriptChecker); const group = await sut.group([createMutant('src/index.ts', '', '')]); diff --git a/packages/typescript-checker/test/integration/single-project.it.spec.ts b/packages/typescript-checker/test/integration/single-project.it.spec.ts index bb1bec41eb..f720bb82a3 100644 --- a/packages/typescript-checker/test/integration/single-project.it.spec.ts +++ b/packages/typescript-checker/test/integration/single-project.it.spec.ts @@ -25,7 +25,7 @@ describe('Typescript checker on a single project', () => { let sut: TypescriptChecker; beforeEach(() => { - (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker = { strategy: 'grouping' }; + (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker = { prioritizePerformanceOverAccuracy: true }; testInjector.options.tsconfigFile = resolveTestResource('tsconfig.json'); sut = testInjector.injector.injectFunction(createTypescriptChecker); return sut.init(); diff --git a/packages/typescript-checker/test/integration/typescript-checkers-errors.it.spec.ts b/packages/typescript-checker/test/integration/typescript-checkers-errors.it.spec.ts index 2f9a17bf89..75b1a8a0a0 100644 --- a/packages/typescript-checker/test/integration/typescript-checkers-errors.it.spec.ts +++ b/packages/typescript-checker/test/integration/typescript-checkers-errors.it.spec.ts @@ -20,7 +20,7 @@ const resolveTestResource = path.resolve.bind( describe('Typescript checker errors', () => { it('should reject initialization if initial compilation failed', async () => { - (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker = { strategy: 'grouping' }; + (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker = { prioritizePerformanceOverAccuracy: true }; testInjector.options.tsconfigFile = resolveTestResource('compile-error', 'tsconfig.json'); const sut = testInjector.injector.injectFunction(createTypescriptChecker); await expect(sut.init()).rejectedWith( @@ -29,7 +29,7 @@ describe('Typescript checker errors', () => { }); it('should reject initialization if tsconfig was invalid', async () => { - (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker = { strategy: 'grouping' }; + (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker = { prioritizePerformanceOverAccuracy: true }; testInjector.options.tsconfigFile = resolveTestResource('invalid-tsconfig', 'tsconfig.json'); const sut = testInjector.injector.injectFunction(createTypescriptChecker); await expect(sut.init()).rejectedWith( @@ -38,7 +38,7 @@ describe('Typescript checker errors', () => { }); it("should reject when tsconfig file doesn't exist", async () => { - (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker = { strategy: 'grouping' }; + (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker = { prioritizePerformanceOverAccuracy: true }; testInjector.options.tsconfigFile = resolveTestResource('empty-dir', 'tsconfig.json'); const sut = testInjector.injector.injectFunction(createTypescriptChecker); await expect(sut.init()).rejectedWith( diff --git a/packages/typescript-checker/test/unit/grouping/create-groups.spec.ts b/packages/typescript-checker/test/unit/grouping/create-groups.spec.ts index 74cbdbe1c5..ff3dca904e 100644 --- a/packages/typescript-checker/test/unit/grouping/create-groups.spec.ts +++ b/packages/typescript-checker/test/unit/grouping/create-groups.spec.ts @@ -2,11 +2,11 @@ import { expect } from 'chai'; import { factory } from '@stryker-mutator/test-helpers'; -import { TSFileNode } from '../../../src/grouping/node.js'; +import { TSFileNode } from '../../../src/grouping/ts-file-node.js'; import { createGroups } from '../../../src/grouping/create-groups.js'; -describe('create-group createGroups', () => { +describe(createGroups.name, () => { it('single mutant should create single group', () => { const mutants = [factory.mutant({ fileName: 'a.js', id: 'mutant-1' })]; const nodes = new Map([['a.js', new TSFileNode('a.js', [], [])]]); @@ -104,4 +104,12 @@ describe('create-group createGroups', () => { expect(groups[2][0]).to.be.equal(mutantsClone[3].id); expect(groups[3][0]).to.be.equal(mutantsClone[5].id); }); + + it('should throw error when not is not in graph', () => { + const mutants = [factory.mutant({ fileName: 'a.js', id: '1' })]; + const nodeA = new TSFileNode('.js', [], []); + const nodes = new Map([[nodeA.fileName, nodeA]]); + + expect(createGroups.bind(null, mutants, nodes)).throw('Node not in graph: a.js'); + }); }); diff --git a/packages/typescript-checker/test/unit/grouping/node.spec.ts b/packages/typescript-checker/test/unit/grouping/node.spec.ts deleted file mode 100644 index 55cc79c826..0000000000 --- a/packages/typescript-checker/test/unit/grouping/node.spec.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { expect } from 'chai'; - -import { Mutant } from '@stryker-mutator/api/src/core'; - -import { TSFileNode } from '../../../src/grouping/node.js'; - -describe('node', () => { - it('getAllParentReferencesIncludingSelf without parent should return array of 1 node ', () => { - const node = new TSFileNode('NodeA', [], []); - expect(node.getAllParentReferencesIncludingSelf()).to.have.lengthOf(1); - }); - - it('getAllParentReferencesIncludingSelf with 1 parent should return array of 2 nodes ', () => { - const node = new TSFileNode('NodeA', [new TSFileNode('', [], [])], []); - expect(node.getAllParentReferencesIncludingSelf()).to.have.lengthOf(2); - }); - - it('getAllParentReferencesIncludingSelf with recursive depth of 2 should return 3 nodes ', () => { - const node = new TSFileNode('NodeA', [new TSFileNode('', [new TSFileNode('', [], [])], [])], []); - expect(node.getAllParentReferencesIncludingSelf()).to.have.lengthOf(3); - }); - - it('getAllParentReferencesIncludingSelf with recursive depth of 2 and multiple parents should return 4 nodes ', () => { - const node = new TSFileNode('NodeA', [new TSFileNode('', [new TSFileNode('', [], []), new TSFileNode('', [], [])], [])], []); - expect(node.getAllParentReferencesIncludingSelf()).to.have.lengthOf(4); - }); - - it('getAllParentReferencesIncludingSelf with circular dependency should skip circular dependency node ', () => { - const nodeA = new TSFileNode('NodeA', [], []); - const nodeC = new TSFileNode('NodeB', [nodeA], []); - const nodeB = new TSFileNode('NodeB', [nodeC], []); - nodeA.parents.push(nodeB); - expect(nodeA.getAllParentReferencesIncludingSelf()).to.have.lengthOf(3); - }); - - it('getAllChildReferencesIncludingSelf without parent should return array of 1 node ', () => { - const node = new TSFileNode('NodeA', [], []); - expect(node.getAllChildReferencesIncludingSelf()).to.have.lengthOf(1); - }); - - it('getAllChildReferencesIncludingSelf with 1 child should return array of 2 nodes ', () => { - const node = new TSFileNode('NodeA', [], [new TSFileNode('', [], [])]); - expect(node.getAllChildReferencesIncludingSelf()).to.have.lengthOf(2); - }); - - it('getAllChildReferencesIncludingSelf with recursive depth of 2 should return 3 nodes ', () => { - const node = new TSFileNode('NodeA', [], [new TSFileNode('', [], [new TSFileNode('', [], [])])]); - expect(node.getAllChildReferencesIncludingSelf()).to.have.lengthOf(3); - }); - - it('getAllChildReferencesIncludingSelf with recursive depth of 2 and multiple parents should return 4 nodes ', () => { - const node = new TSFileNode('NodeA', [], [new TSFileNode('', [], [new TSFileNode('', [], []), new TSFileNode('', [], [])])]); - expect(node.getAllChildReferencesIncludingSelf()).to.have.lengthOf(4); - }); - - it('getMutantsWithReferenceToChildrenOrSelf with single mutant in file should return 1 mutant', () => { - const node = new TSFileNode('NodeA.js', [], []); - const mutants: Mutant[] = [ - { fileName: 'NodeA.js', id: '0', replacement: '-', location: { start: { line: 1, column: 1 }, end: { line: 1, column: 1 } }, mutatorName: '' }, - ]; - expect(node.getMutantsWithReferenceToChildrenOrSelf(mutants)).to.have.lengthOf(1); - }); - - it('getMutantsWithReferenceToChildrenOrSelf with single mutant in child should return 1 mutant', () => { - const node = new TSFileNode('NodeA.js', [], []); - const nodeB = new TSFileNode('NodeB.js', [], []); - node.children.push(nodeB); - const mutants: Mutant[] = [ - { fileName: 'NodeB.js', id: '0', replacement: '-', location: { start: { line: 1, column: 1 }, end: { line: 1, column: 1 } }, mutatorName: '' }, - ]; - expect(node.getMutantsWithReferenceToChildrenOrSelf(mutants)).to.have.lengthOf(1); - }); -}); diff --git a/packages/typescript-checker/test/unit/grouping/ts-file-node.spec.ts b/packages/typescript-checker/test/unit/grouping/ts-file-node.spec.ts new file mode 100644 index 0000000000..42517999ab --- /dev/null +++ b/packages/typescript-checker/test/unit/grouping/ts-file-node.spec.ts @@ -0,0 +1,108 @@ +import { expect } from 'chai'; + +import { Mutant } from '@stryker-mutator/api/src/core'; + +import { TSFileNode } from '../../../src/grouping/ts-file-node.js'; + +describe('TSFileNode', () => { + describe(TSFileNode.prototype.getAllParentReferencesIncludingSelf.name, () => { + it('getAllParentReferencesIncludingSelf without parent should return array of 1 node ', () => { + const node = new TSFileNode('NodeA', [], []); + expect(node.getAllParentReferencesIncludingSelf()).to.have.lengthOf(1); + }); + + it('getAllParentReferencesIncludingSelf with 1 parent should return array of 2 nodes ', () => { + const node = new TSFileNode('NodeA', [new TSFileNode('', [], [])], []); + expect(node.getAllParentReferencesIncludingSelf()).to.have.lengthOf(2); + }); + + it('getAllParentReferencesIncludingSelf with recursive depth of 2 should return 3 nodes ', () => { + const node = new TSFileNode('NodeA', [new TSFileNode('', [new TSFileNode('', [], [])], [])], []); + expect(node.getAllParentReferencesIncludingSelf()).to.have.lengthOf(3); + }); + + it('getAllParentReferencesIncludingSelf with recursive depth of 2 and multiple parents should return 4 nodes ', () => { + const node = new TSFileNode('NodeA', [new TSFileNode('', [new TSFileNode('', [], []), new TSFileNode('', [], [])], [])], []); + expect(node.getAllParentReferencesIncludingSelf()).to.have.lengthOf(4); + }); + + it('getAllParentReferencesIncludingSelf with circular dependency should skip circular dependency node ', () => { + const nodeA = new TSFileNode('NodeA', [], []); + const nodeC = new TSFileNode('NodeB', [nodeA], []); + const nodeB = new TSFileNode('NodeB', [nodeC], []); + nodeA.parents.push(nodeB); + expect(nodeA.getAllParentReferencesIncludingSelf()).to.have.lengthOf(3); + }); + }); + + describe(TSFileNode.prototype.getAllChildReferencesIncludingSelf.name, () => { + it('getAllChildReferencesIncludingSelf without parent should return array of 1 node ', () => { + const node = new TSFileNode('NodeA', [], []); + expect(node.getAllChildReferencesIncludingSelf()).to.have.lengthOf(1); + }); + + it('getAllChildReferencesIncludingSelf with 1 child should return array of 2 nodes ', () => { + const node = new TSFileNode('NodeA', [], [new TSFileNode('', [], [])]); + expect(node.getAllChildReferencesIncludingSelf()).to.have.lengthOf(2); + }); + + it('getAllChildReferencesIncludingSelf with recursive depth of 2 should return 3 nodes ', () => { + const node = new TSFileNode('NodeA', [], [new TSFileNode('', [], [new TSFileNode('', [], [])])]); + expect(node.getAllChildReferencesIncludingSelf()).to.have.lengthOf(3); + }); + + it('getAllChildReferencesIncludingSelf with recursive depth of 2 and multiple parents should return 4 nodes ', () => { + const node = new TSFileNode('NodeA', [], [new TSFileNode('', [], [new TSFileNode('', [], []), new TSFileNode('', [], [])])]); + expect(node.getAllChildReferencesIncludingSelf()).to.have.lengthOf(4); + }); + }); + + describe(TSFileNode.prototype.getMutantsWithReferenceToChildrenOrSelf.name, () => { + it('getMutantsWithReferenceToChildrenOrSelf with single mutant in file should return 1 mutant', () => { + const node = new TSFileNode('NodeA.js', [], []); + const mutants: Mutant[] = [ + { + fileName: 'NodeA.js', + id: '0', + replacement: '-', + location: { start: { line: 1, column: 1 }, end: { line: 1, column: 1 } }, + mutatorName: '', + }, + ]; + expect(node.getMutantsWithReferenceToChildrenOrSelf(mutants)).to.have.lengthOf(1); + }); + + it('getMutantsWithReferenceToChildrenOrSelf with single mutant in child should return 1 mutant', () => { + const node = new TSFileNode('NodeA.js', [], []); + const nodeB = new TSFileNode('NodeB.js', [], []); + node.children.push(nodeB); + const mutants: Mutant[] = [ + { + fileName: 'NodeB.js', + id: '0', + replacement: '-', + location: { start: { line: 1, column: 1 }, end: { line: 1, column: 1 } }, + mutatorName: '', + }, + ]; + expect(node.getMutantsWithReferenceToChildrenOrSelf(mutants)).to.have.lengthOf(1); + }); + + it('should not create endless loop', () => { + const node = new TSFileNode('NodeA.js', [], []); + node.children = [node]; + + const mutants: Mutant[] = [ + { + fileName: 'NodeA.js', + id: '0', + replacement: '-', + location: { start: { line: 1, column: 1 }, end: { line: 1, column: 1 } }, + mutatorName: '', + }, + ]; + + expect(node.getMutantsWithReferenceToChildrenOrSelf(mutants)).to.have.lengthOf(1); + }); + }); +}); diff --git a/packages/typescript-checker/test/unit/typescript-checker.spec.ts b/packages/typescript-checker/test/unit/typescript-checker.spec.ts index 965b0911f4..837141fc7c 100644 --- a/packages/typescript-checker/test/unit/typescript-checker.spec.ts +++ b/packages/typescript-checker/test/unit/typescript-checker.spec.ts @@ -3,19 +3,27 @@ import { expect } from 'chai'; import { createTypescriptChecker } from '../../src/index.js'; -import { TypescriptChecker } from '../../src/typescript-checker'; +import { TypescriptChecker } from '../../src/typescript-checker.js'; import { TypeScriptCheckerOptionsWithStrykerOptions } from '../../src/typescript-checker-options-with-stryker-options'; describe('typescript-checker', () => { let sut: TypescriptChecker; beforeEach(() => { - (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker = { strategy: 'noGrouping' }; sut = testInjector.injector.injectFunction(createTypescriptChecker); return sut.init(); }); - it('noGrouping setting should not group mutants', async () => { - const result = await sut.group([factory.mutant(), factory.mutant(), factory.mutant()]); - expect(result.length).to.be.eq(3); + describe(TypescriptChecker.prototype.group.name, () => { + it('should not group mutants if prioritizePerformanceOverAccuracy is false', async () => { + (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker = { prioritizePerformanceOverAccuracy: false }; + const result = await sut.group([factory.mutant(), factory.mutant(), factory.mutant()]); + expect(result.length).to.be.eq(3); + }); + + it('should group mutants if prioritizePerformanceOverAccuracy is true', async () => { + (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker = { prioritizePerformanceOverAccuracy: true }; + const result = await sut.group([factory.mutant(), factory.mutant(), factory.mutant()]); + expect(result.length).to.be.eq(1); + }); }); }); diff --git a/packages/typescript-checker/test/unit/typescript-helpers.spec.ts b/packages/typescript-checker/test/unit/typescript-helpers.spec.ts index 413e54e10e..58baad29fa 100644 --- a/packages/typescript-checker/test/unit/typescript-helpers.spec.ts +++ b/packages/typescript-checker/test/unit/typescript-helpers.spec.ts @@ -4,7 +4,13 @@ import sinon from 'sinon'; import ts from 'typescript'; import { expect } from 'chai'; -import { determineBuildModeEnabled, overrideOptions, retrieveReferencedProjects, guardTSVersion } from '../../src/tsconfig-helpers.js'; +import { + determineBuildModeEnabled, + overrideOptions, + retrieveReferencedProjects, + guardTSVersion, + getSourceMappingURL, +} from '../../src/tsconfig-helpers.js'; describe('typescript-helpers', () => { describe(determineBuildModeEnabled.name, () => { @@ -151,6 +157,25 @@ describe('typescript-helpers', () => { declaration: true, }); }); + + it('should delete declarations properties if `--build` mode is on', () => { + expect( + JSON.parse( + overrideOptions( + { + config: { + inlineSourceMap: '', + inlineSources: '', + mapRoute: '', + sourceRoot: '', + outFile: '', + }, + }, + true + ) + ).compilerOptions + ).deep.include({}); + }); }); describe(retrieveReferencedProjects.name, () => { @@ -181,4 +206,27 @@ describe('typescript-helpers', () => { expect(guardTSVersion).not.throws(); }); }); + + describe(getSourceMappingURL.name, () => { + it('should return undefined when no sourceMap is provided', () => { + const content = 'let sum = 2 + 6;'; + const result = getSourceMappingURL(content); + expect(result).to.be.undefined; + }); + + it('should be able to get multiple sourceFiles in sequence', () => { + const content = '//# sourceMappingURL=/url.ts'; + const result1 = getSourceMappingURL(content); + const result2 = getSourceMappingURL(content); + expect(result1).to.be.eq('/url.ts'); + expect(result2).to.be.eq('/url.ts'); + }); + + it('should not hit when sourceMappingURL is not on the end of the file', () => { + const content = `const regex = //# sourceMappingURL=/url.ts + console.log(regex);`; + const result = getSourceMappingURL(content); + expect(result).to.be.undefined; + }); + }); }); From 14e430cbd6cb0739f80adb06697761acd84f240f Mon Sep 17 00:00:00 2001 From: Danny Berkelaar Date: Fri, 20 Jan 2023 16:18:56 +0100 Subject: [PATCH 63/77] Change info log to debug log --- packages/typescript-checker/src/typescript-checker.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/typescript-checker/src/typescript-checker.ts b/packages/typescript-checker/src/typescript-checker.ts index 0784a03740..061f44ecd9 100644 --- a/packages/typescript-checker/src/typescript-checker.ts +++ b/packages/typescript-checker/src/typescript-checker.ts @@ -95,7 +95,8 @@ export class TypescriptChecker implements Checker { const mutantsToTest = mutants.filter((m) => nodes.get(toPosixFileName(m.fileName)) != null); const groups = createGroups(mutantsToTest, nodes).sort((a, b) => b.length - a.length); - this.logger.info(`Created ${groups.length} groups with largest group of ${groups[0]?.length ?? 0} mutants`); + this.logger.debug(`Created ${groups.length} groups with largest group of ${groups[0]?.length ?? 0} mutants`); + if (mutantsOutSideProject.length) { groups.unshift(mutantsOutSideProject); } From afa479e056b5260cd9f47ae6902e3403a03eb240 Mon Sep 17 00:00:00 2001 From: Danny Berkelaar Date: Fri, 20 Jan 2023 17:27:58 +0100 Subject: [PATCH 64/77] Fixed default options --- .../schema/typescript-checker-options.json | 14 ++++--------- .../src/grouping/ts-file-node.ts | 1 - ...pt-checker-options-with-stryker-options.ts | 4 ++-- .../src/typescript-checker.ts | 8 +++----- .../src/typescript-compiler.ts | 20 +++++++++---------- 5 files changed, 19 insertions(+), 28 deletions(-) diff --git a/packages/typescript-checker/schema/typescript-checker-options.json b/packages/typescript-checker/schema/typescript-checker-options.json index 1a99235d0d..9bcf2fcd1f 100644 --- a/packages/typescript-checker/schema/typescript-checker-options.json +++ b/packages/typescript-checker/schema/typescript-checker-options.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema", - "title": "TypeScriptCheckerOptions", + "title": "TypeScriptCheckerPluginOptions", "type": "object", "additionalProperties": false, "properties": { @@ -12,17 +12,11 @@ "default": {}, "properties": { "prioritizePerformanceOverAccuracy": { - "$ref": "#/definitions/typeScriptCheckerStrategy" + "description": "Configures the performance of the TypeScriptChecker. Setting this to false results in a slower, but more accurate result.", + "type": "boolean", + "default": true } } } - }, - "definitions": { - "typeScriptCheckerStrategy": { - "title": "PrioritizePerformanceOverAccuracy", - "type": "boolean", - "default": true, - "description": "Configure the performance of the TypeScriptChecker. Setting this to false results in a slower, but more accurate result." - } } } diff --git a/packages/typescript-checker/src/grouping/ts-file-node.ts b/packages/typescript-checker/src/grouping/ts-file-node.ts index e7346db747..52124bd73c 100644 --- a/packages/typescript-checker/src/grouping/ts-file-node.ts +++ b/packages/typescript-checker/src/grouping/ts-file-node.ts @@ -25,7 +25,6 @@ export class TSFileNode { return allChildReferences; } - // todo mogelijk mutants een map van maken [filename]: mutant[] public getMutantsWithReferenceToChildrenOrSelf(mutants: Mutant[], nodesChecked: string[] = []): Mutant[] { if (nodesChecked.includes(this.fileName)) { return []; diff --git a/packages/typescript-checker/src/typescript-checker-options-with-stryker-options.ts b/packages/typescript-checker/src/typescript-checker-options-with-stryker-options.ts index 2a0d9945c5..7ce3bfee3e 100644 --- a/packages/typescript-checker/src/typescript-checker-options-with-stryker-options.ts +++ b/packages/typescript-checker/src/typescript-checker-options-with-stryker-options.ts @@ -1,5 +1,5 @@ import { StrykerOptions } from '@stryker-mutator/api/core'; -import { TypeScriptCheckerOptions } from '../src-generated/typescript-checker-options'; +import { TypeScriptCheckerPluginOptions } from '../src-generated/typescript-checker-options'; -export interface TypeScriptCheckerOptionsWithStrykerOptions extends TypeScriptCheckerOptions, StrykerOptions {} +export interface TypeScriptCheckerOptionsWithStrykerOptions extends TypeScriptCheckerPluginOptions, StrykerOptions {} diff --git a/packages/typescript-checker/src/typescript-checker.ts b/packages/typescript-checker/src/typescript-checker.ts index 061f44ecd9..1236de9903 100644 --- a/packages/typescript-checker/src/typescript-checker.ts +++ b/packages/typescript-checker/src/typescript-checker.ts @@ -6,8 +6,6 @@ import { tokens, commonTokens, PluginContext, Injector, Scope } from '@stryker-m import { Logger, LoggerFactoryMethod } from '@stryker-mutator/api/logging'; import { Mutant, StrykerOptions } from '@stryker-mutator/api/core'; -import { TypeScriptCheckerOptions } from '../src-generated/typescript-checker-options.js'; - import * as pluginTokens from './plugin-tokens.js'; import { TypescriptCompiler } from './typescript-compiler.js'; import { createGroups } from './grouping/create-groups.js'; @@ -42,10 +40,10 @@ export class TypescriptChecker implements Checker { */ public static inject = tokens(commonTokens.logger, commonTokens.options, pluginTokens.tsCompiler); - private readonly typeScriptCheckerOptions: TypeScriptCheckerOptions; + private readonly options: TypeScriptCheckerOptionsWithStrykerOptions; constructor(private readonly logger: Logger, options: StrykerOptions, private readonly tsCompiler: TypescriptCompiler) { - this.typeScriptCheckerOptions = options as TypeScriptCheckerOptionsWithStrykerOptions; + this.options = options as TypeScriptCheckerOptionsWithStrykerOptions; } /** @@ -86,7 +84,7 @@ export class TypescriptChecker implements Checker { * @param mutants All the mutants to group. */ public async group(mutants: Mutant[]): Promise { - if (!this.typeScriptCheckerOptions.typeScriptChecker.prioritizePerformanceOverAccuracy) { + if (!this.options.typeScriptChecker.prioritizePerformanceOverAccuracy) { return mutants.map((m) => [m.id]); } const nodes = this.tsCompiler.nodes; diff --git a/packages/typescript-checker/src/typescript-compiler.ts b/packages/typescript-checker/src/typescript-compiler.ts index 46a23402ca..259095ac42 100644 --- a/packages/typescript-checker/src/typescript-compiler.ts +++ b/packages/typescript-checker/src/typescript-compiler.ts @@ -207,26 +207,26 @@ export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCre } /** - * Resolves TS files to TS source files. - * @param fileName The file name that may be a declaration file + * Resolves TS input file based on a dependency of a input file + * @param dependencyFileName The dependency file name. With TS project references this can be a declaration file * @returns TS source file if found (fallbacks to input filename) */ - private resolveTSInputFile(fileName: string): string { - if (!fileName.endsWith('.d.ts')) { - return fileName; + private resolveTSInputFile(dependencyFileName: string): string { + if (!dependencyFileName.endsWith('.d.ts')) { + return dependencyFileName; } - const file = this.fs.getFile(fileName); + const file = this.fs.getFile(dependencyFileName); if (!file) { - throw new Error(`Could not find ${fileName}`); + throw new Error(`Could not find ${dependencyFileName}`); } const sourceMappingURL = getSourceMappingURL(file.content); if (!sourceMappingURL) { - return fileName; + return dependencyFileName; } - const sourceMapFileName = path.resolve(fileName, '..', sourceMappingURL); + const sourceMapFileName = path.resolve(dependencyFileName, '..', sourceMappingURL); const sourceMap = this.fs.getFile(sourceMapFileName); if (!sourceMap) throw new Error(`Could not find ${sourceMapFileName}`); @@ -237,7 +237,7 @@ export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCre return toPosixFileName(path.resolve(sourceMapFileName, '..', sourcePath)); } - return fileName; + return dependencyFileName; } private adjustTSConfigFile(fileName: string, content: string, buildModeEnabled: boolean) { From 236b31017f7de31a7c26352f17a14992a72b23a5 Mon Sep 17 00:00:00 2001 From: Danny Berkelaar Date: Fri, 20 Jan 2023 17:55:23 +0100 Subject: [PATCH 65/77] Added prioritizePerformanceOverAccuracy option to read me --- docs/typescript-checker.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/typescript-checker.md b/docs/typescript-checker.md index bfe366edf4..3fc3b04a34 100644 --- a/docs/typescript-checker.md +++ b/docs/typescript-checker.md @@ -30,7 +30,10 @@ You can configure the typescript checker in the `stryker.conf.js` (or `stryker.c ```json { "checkers": ["typescript"], - "tsconfigFile": "tsconfig.json" + "tsconfigFile": "tsconfig.json", + "typeScriptChecker": { + "prioritizePerformanceOverAccuracy": true + } } ``` @@ -52,6 +55,12 @@ _Note: the following compiler options are always overridden by @stryker-mutator/ } ``` +### `typeScriptChecker.prioritizePerformanceOverAccuracy` [`boolean`] + +Default: `true` + +Sets the performance strategy for the typescript-checker. Defaults to `true` which the fastest strategy with the consequence of losing some accuracy. The accuracy that is lost comes down to having mutants with a status other than `CompileError` while they should have this status. This result in a report that may not be 100% accurate. Setting this option to `false` results in an accurate report but may take (way) longer. + ## Peer dependencies The `@stryker-mutator/typescript-checker` package for `stryker` to enable `typescript` support. As such, you should make sure you have the correct versions of its dependencies installed: From 3ead12aa4d299b4327c72b3837963939c2009478 Mon Sep 17 00:00:00 2001 From: Danny Berkelaar Date: Fri, 20 Jan 2023 18:03:13 +0100 Subject: [PATCH 66/77] Removed structuredClone from tests --- .../test/unit/grouping/create-groups.spec.ts | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/packages/typescript-checker/test/unit/grouping/create-groups.spec.ts b/packages/typescript-checker/test/unit/grouping/create-groups.spec.ts index ff3dca904e..c5ed9b2b63 100644 --- a/packages/typescript-checker/test/unit/grouping/create-groups.spec.ts +++ b/packages/typescript-checker/test/unit/grouping/create-groups.spec.ts @@ -18,20 +18,18 @@ describe(createGroups.name, () => { it('two mutants in different files without reference to each other should create single group', () => { const mutants = [factory.mutant({ fileName: 'a.js', id: '1' }), factory.mutant({ fileName: 'b.js', id: '2' })]; - const mutantsClone = structuredClone(mutants); const nodes = new Map([ ['a.js', new TSFileNode('a.js', [], [])], ['b.js', new TSFileNode('b.js', [], [])], ]); const groups = createGroups(mutants, nodes); expect(groups).to.have.lengthOf(1); - expect(groups[0][0]).to.be.equal(mutantsClone[0].id); - expect(groups[0][1]).to.be.equal(mutantsClone[1].id); + expect(groups[0][0]).to.be.equal('1'); + expect(groups[0][1]).to.be.equal('2'); }); it('two mutants in different files with reference to each other should create 2 groups', () => { const mutants = [factory.mutant({ fileName: 'a.js', id: '1' }), factory.mutant({ fileName: 'b.js', id: '2' })]; - const mutantsClone = structuredClone(mutants); const nodeA = new TSFileNode('a.js', [], []); const nodeB = new TSFileNode('b.js', [nodeA], []); const nodes = new Map([ @@ -40,13 +38,12 @@ describe(createGroups.name, () => { ]); const groups = createGroups(mutants, nodes); expect(groups).to.have.lengthOf(2); - expect(groups[0][0]).to.be.equal(mutantsClone[0].id); - expect(groups[1][0]).to.be.equal(mutantsClone[1].id); + expect(groups[0][0]).to.be.equal('1'); + expect(groups[1][0]).to.be.equal('2'); }); it('two mutants in different files with circular dependency to each other should create 2 groups', () => { const mutants = [factory.mutant({ fileName: 'a.js', id: '1' }), factory.mutant({ fileName: 'b.js', id: '2' })]; - const mutantsClone = structuredClone(mutants); const nodeA = new TSFileNode('a.js', [], []); const nodeB = new TSFileNode('b.js', [nodeA], []); nodeA.parents.push(nodeB); @@ -56,19 +53,18 @@ describe(createGroups.name, () => { ]); const groups = createGroups(mutants, nodes); expect(groups).to.have.lengthOf(2); - expect(groups[0][0]).to.be.equal(mutantsClone[0].id); - expect(groups[1][0]).to.be.equal(mutantsClone[1].id); + expect(groups[0][0]).to.be.equal('1'); + expect(groups[1][0]).to.be.equal('2'); }); it('two mutants in same file should create 2 groups', () => { const mutants = [factory.mutant({ fileName: 'a.js', id: '1' }), factory.mutant({ fileName: 'a.js', id: '2' })]; - const mutantsClone = structuredClone(mutants); const nodeA = new TSFileNode('a.js', [], []); const nodes = new Map([[nodeA.fileName, nodeA]]); const groups = createGroups(mutants, nodes); expect(groups).to.have.lengthOf(2); - expect(groups[0][0]).to.be.equal(mutantsClone[0].id); - expect(groups[1][0]).to.be.equal(mutantsClone[1].id); + expect(groups[0][0]).to.be.equal('1'); + expect(groups[1][0]).to.be.equal('2'); }); it('complex graph should contain multiples 4 groups', () => { @@ -80,7 +76,6 @@ describe(createGroups.name, () => { factory.mutant({ fileName: 'e.js', id: '5' }), factory.mutant({ fileName: 'f.js', id: '6' }), ]; - const mutantsClone = structuredClone(mutants); const nodeA = new TSFileNode('a.js', [], []); const nodeB = new TSFileNode('b.js', [nodeA], []); const nodeC = new TSFileNode('c.js', [nodeA], []); @@ -97,12 +92,12 @@ describe(createGroups.name, () => { ]); const groups = createGroups(mutants, nodes); expect(groups).to.have.lengthOf(4); - expect(groups[0][0]).to.be.equal(mutantsClone[0].id); - expect(groups[1][0]).to.be.equal(mutantsClone[1].id); - expect(groups[1][1]).to.be.equal(mutantsClone[2].id); - expect(groups[1][2]).to.be.equal(mutantsClone[4].id); - expect(groups[2][0]).to.be.equal(mutantsClone[3].id); - expect(groups[3][0]).to.be.equal(mutantsClone[5].id); + expect(groups[0][0]).to.be.equal('1'); + expect(groups[1][0]).to.be.equal('2'); + expect(groups[1][1]).to.be.equal('3'); + expect(groups[1][2]).to.be.equal('5'); + expect(groups[2][0]).to.be.equal('4'); + expect(groups[3][0]).to.be.equal('6'); }); it('should throw error when not is not in graph', () => { From eaaf1588b25f3f6b1144b8ea767be8ae5c92557b Mon Sep 17 00:00:00 2001 From: Danny Berkelaar Date: Fri, 20 Jan 2023 21:21:54 +0100 Subject: [PATCH 67/77] Changed [Tt]ypeScript to [Tt]ypescript --- .../schema/typescript-checker-options.json | 8 ++++---- ...ypescript-checker-options-with-stryker-options.ts | 4 ++-- .../typescript-checker/src/typescript-checker.ts | 10 +++++----- .../test/integration/project-references.it.spec.ts | 4 ++-- .../integration/project-with-ts-buildinfo.it.spec.ts | 4 ++-- .../test/integration/single-project.it.spec.ts | 4 ++-- .../typescript-checkers-errors.it.spec.ts | 12 ++++++------ .../test/unit/typescript-checker.spec.ts | 6 +++--- 8 files changed, 26 insertions(+), 26 deletions(-) diff --git a/packages/typescript-checker/schema/typescript-checker-options.json b/packages/typescript-checker/schema/typescript-checker-options.json index 9bcf2fcd1f..0812ab8820 100644 --- a/packages/typescript-checker/schema/typescript-checker-options.json +++ b/packages/typescript-checker/schema/typescript-checker-options.json @@ -1,18 +1,18 @@ { "$schema": "http://json-schema.org/draft-07/schema", - "title": "TypeScriptCheckerPluginOptions", + "title": "TypescriptCheckerPluginOptions", "type": "object", "additionalProperties": false, "properties": { - "typeScriptChecker": { + "typescriptChecker": { "description": "Configuration for @stryker-mutator/typescript-checker", - "title": "TypeScriptCheckerOptions", + "title": "TypescriptCheckerOptions", "additionalProperties": false, "type": "object", "default": {}, "properties": { "prioritizePerformanceOverAccuracy": { - "description": "Configures the performance of the TypeScriptChecker. Setting this to false results in a slower, but more accurate result.", + "description": "Configures the performance of the TypescriptChecker. Setting this to false results in a slower, but more accurate result.", "type": "boolean", "default": true } diff --git a/packages/typescript-checker/src/typescript-checker-options-with-stryker-options.ts b/packages/typescript-checker/src/typescript-checker-options-with-stryker-options.ts index 7ce3bfee3e..2384f00a51 100644 --- a/packages/typescript-checker/src/typescript-checker-options-with-stryker-options.ts +++ b/packages/typescript-checker/src/typescript-checker-options-with-stryker-options.ts @@ -1,5 +1,5 @@ import { StrykerOptions } from '@stryker-mutator/api/core'; -import { TypeScriptCheckerPluginOptions } from '../src-generated/typescript-checker-options'; +import { TypescriptCheckerPluginOptions } from '../src-generated/typescript-checker-options'; -export interface TypeScriptCheckerOptionsWithStrykerOptions extends TypeScriptCheckerPluginOptions, StrykerOptions {} +export interface TypescriptCheckerOptionsWithStrykerOptions extends TypescriptCheckerPluginOptions, StrykerOptions {} diff --git a/packages/typescript-checker/src/typescript-checker.ts b/packages/typescript-checker/src/typescript-checker.ts index 1236de9903..418c5af091 100644 --- a/packages/typescript-checker/src/typescript-checker.ts +++ b/packages/typescript-checker/src/typescript-checker.ts @@ -11,7 +11,7 @@ import { TypescriptCompiler } from './typescript-compiler.js'; import { createGroups } from './grouping/create-groups.js'; import { toPosixFileName } from './tsconfig-helpers.js'; import { TSFileNode } from './grouping/ts-file-node.js'; -import { TypeScriptCheckerOptionsWithStrykerOptions } from './typescript-checker-options-with-stryker-options.js'; +import { TypescriptCheckerOptionsWithStrykerOptions } from './typescript-checker-options-with-stryker-options.js'; import { HybridFileSystem } from './fs/hybrid-file-system.js'; typescriptCheckerLoggerFactory.inject = tokens(commonTokens.getLogger, commonTokens.target); @@ -40,10 +40,10 @@ export class TypescriptChecker implements Checker { */ public static inject = tokens(commonTokens.logger, commonTokens.options, pluginTokens.tsCompiler); - private readonly options: TypeScriptCheckerOptionsWithStrykerOptions; + private readonly options: TypescriptCheckerOptionsWithStrykerOptions; constructor(private readonly logger: Logger, options: StrykerOptions, private readonly tsCompiler: TypescriptCompiler) { - this.options = options as TypeScriptCheckerOptionsWithStrykerOptions; + this.options = options as TypescriptCheckerOptionsWithStrykerOptions; } /** @@ -53,7 +53,7 @@ export class TypescriptChecker implements Checker { const errors = await this.tsCompiler.init(); if (errors.length) { - throw new Error(`TypeScript error(s) found in dry run compilation: ${this.createErrorText(errors)}`); + throw new Error(`Typescript error(s) found in dry run compilation: ${this.createErrorText(errors)}`); } } @@ -84,7 +84,7 @@ export class TypescriptChecker implements Checker { * @param mutants All the mutants to group. */ public async group(mutants: Mutant[]): Promise { - if (!this.options.typeScriptChecker.prioritizePerformanceOverAccuracy) { + if (!this.options.typescriptChecker.prioritizePerformanceOverAccuracy) { return mutants.map((m) => [m.id]); } const nodes = this.tsCompiler.nodes; diff --git a/packages/typescript-checker/test/integration/project-references.it.spec.ts b/packages/typescript-checker/test/integration/project-references.it.spec.ts index 074c4d68db..98e3f7cf52 100644 --- a/packages/typescript-checker/test/integration/project-references.it.spec.ts +++ b/packages/typescript-checker/test/integration/project-references.it.spec.ts @@ -10,7 +10,7 @@ import { testInjector, factory } from '@stryker-mutator/test-helpers'; import { createTypescriptChecker } from '../../src/index.js'; import { TypescriptChecker } from '../../src/typescript-checker.js'; -import { TypeScriptCheckerOptionsWithStrykerOptions } from '../../src/typescript-checker-options-with-stryker-options.js'; +import { TypescriptCheckerOptionsWithStrykerOptions } from '../../src/typescript-checker-options-with-stryker-options.js'; const resolveTestResource = path.resolve.bind( path, @@ -26,7 +26,7 @@ describe('Typescript checker on a project with project references', () => { let sut: TypescriptChecker; beforeEach(() => { - (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker = { prioritizePerformanceOverAccuracy: true }; + (testInjector.options as TypescriptCheckerOptionsWithStrykerOptions).typescriptChecker = { prioritizePerformanceOverAccuracy: true }; testInjector.options.tsconfigFile = resolveTestResource('tsconfig.root.json'); sut = testInjector.injector.injectFunction(createTypescriptChecker); return sut.init(); diff --git a/packages/typescript-checker/test/integration/project-with-ts-buildinfo.it.spec.ts b/packages/typescript-checker/test/integration/project-with-ts-buildinfo.it.spec.ts index e8495b9687..86066a6228 100644 --- a/packages/typescript-checker/test/integration/project-with-ts-buildinfo.it.spec.ts +++ b/packages/typescript-checker/test/integration/project-with-ts-buildinfo.it.spec.ts @@ -9,7 +9,7 @@ import { Location, Mutant } from '@stryker-mutator/api/core'; import { testInjector, factory } from '@stryker-mutator/test-helpers'; import { createTypescriptChecker } from '../../src/index.js'; -import { TypeScriptCheckerOptionsWithStrykerOptions } from '../../src/typescript-checker-options-with-stryker-options.js'; +import { TypescriptCheckerOptionsWithStrykerOptions } from '../../src/typescript-checker-options-with-stryker-options.js'; const resolveTestResource = path.resolve.bind( path, @@ -23,7 +23,7 @@ const resolveTestResource = path.resolve.bind( describe('project-with-ts-buildinfo', () => { it('should load project on init', async () => { - (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker = { prioritizePerformanceOverAccuracy: true }; + (testInjector.options as TypescriptCheckerOptionsWithStrykerOptions).typescriptChecker = { prioritizePerformanceOverAccuracy: true }; testInjector.options.tsconfigFile = resolveTestResource('tsconfig.json'); const sut = testInjector.injector.injectFunction(createTypescriptChecker); const group = await sut.group([createMutant('src/index.ts', '', '')]); diff --git a/packages/typescript-checker/test/integration/single-project.it.spec.ts b/packages/typescript-checker/test/integration/single-project.it.spec.ts index f720bb82a3..6ecd9f1aad 100644 --- a/packages/typescript-checker/test/integration/single-project.it.spec.ts +++ b/packages/typescript-checker/test/integration/single-project.it.spec.ts @@ -9,7 +9,7 @@ import { CheckResult, CheckStatus } from '@stryker-mutator/api/check'; import { createTypescriptChecker } from '../../src/index.js'; import { TypescriptChecker } from '../../src/typescript-checker.js'; -import { TypeScriptCheckerOptionsWithStrykerOptions } from '../../src/typescript-checker-options-with-stryker-options.js'; +import { TypescriptCheckerOptionsWithStrykerOptions } from '../../src/typescript-checker-options-with-stryker-options.js'; const resolveTestResource = path.resolve.bind( path, @@ -25,7 +25,7 @@ describe('Typescript checker on a single project', () => { let sut: TypescriptChecker; beforeEach(() => { - (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker = { prioritizePerformanceOverAccuracy: true }; + (testInjector.options as TypescriptCheckerOptionsWithStrykerOptions).typescriptChecker = { prioritizePerformanceOverAccuracy: true }; testInjector.options.tsconfigFile = resolveTestResource('tsconfig.json'); sut = testInjector.injector.injectFunction(createTypescriptChecker); return sut.init(); diff --git a/packages/typescript-checker/test/integration/typescript-checkers-errors.it.spec.ts b/packages/typescript-checker/test/integration/typescript-checkers-errors.it.spec.ts index 75b1a8a0a0..788453a7fe 100644 --- a/packages/typescript-checker/test/integration/typescript-checkers-errors.it.spec.ts +++ b/packages/typescript-checker/test/integration/typescript-checkers-errors.it.spec.ts @@ -6,7 +6,7 @@ import { testInjector } from '@stryker-mutator/test-helpers'; import { expect } from 'chai'; import { createTypescriptChecker } from '../../src/index.js'; -import { TypeScriptCheckerOptionsWithStrykerOptions } from '../../src/typescript-checker-options-with-stryker-options.js'; +import { TypescriptCheckerOptionsWithStrykerOptions } from '../../src/typescript-checker-options-with-stryker-options.js'; const resolveTestResource = path.resolve.bind( path, @@ -20,25 +20,25 @@ const resolveTestResource = path.resolve.bind( describe('Typescript checker errors', () => { it('should reject initialization if initial compilation failed', async () => { - (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker = { prioritizePerformanceOverAccuracy: true }; + (testInjector.options as TypescriptCheckerOptionsWithStrykerOptions).typescriptChecker = { prioritizePerformanceOverAccuracy: true }; testInjector.options.tsconfigFile = resolveTestResource('compile-error', 'tsconfig.json'); const sut = testInjector.injector.injectFunction(createTypescriptChecker); await expect(sut.init()).rejectedWith( - 'TypeScript error(s) found in dry run compilation: testResources/errors/compile-error/add.ts(2,3): error TS2322:' + 'Typescript error(s) found in dry run compilation: testResources/errors/compile-error/add.ts(2,3): error TS2322:' ); }); it('should reject initialization if tsconfig was invalid', async () => { - (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker = { prioritizePerformanceOverAccuracy: true }; + (testInjector.options as TypescriptCheckerOptionsWithStrykerOptions).typescriptChecker = { prioritizePerformanceOverAccuracy: true }; testInjector.options.tsconfigFile = resolveTestResource('invalid-tsconfig', 'tsconfig.json'); const sut = testInjector.injector.injectFunction(createTypescriptChecker); await expect(sut.init()).rejectedWith( - 'TypeScript error(s) found in dry run compilation: testResources/errors/invalid-tsconfig/tsconfig.json(1,1): error TS1005:' + 'Typescript error(s) found in dry run compilation: testResources/errors/invalid-tsconfig/tsconfig.json(1,1): error TS1005:' ); }); it("should reject when tsconfig file doesn't exist", async () => { - (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker = { prioritizePerformanceOverAccuracy: true }; + (testInjector.options as TypescriptCheckerOptionsWithStrykerOptions).typescriptChecker = { prioritizePerformanceOverAccuracy: true }; testInjector.options.tsconfigFile = resolveTestResource('empty-dir', 'tsconfig.json'); const sut = testInjector.injector.injectFunction(createTypescriptChecker); await expect(sut.init()).rejectedWith( diff --git a/packages/typescript-checker/test/unit/typescript-checker.spec.ts b/packages/typescript-checker/test/unit/typescript-checker.spec.ts index 837141fc7c..8a9d7e29ce 100644 --- a/packages/typescript-checker/test/unit/typescript-checker.spec.ts +++ b/packages/typescript-checker/test/unit/typescript-checker.spec.ts @@ -4,7 +4,7 @@ import { expect } from 'chai'; import { createTypescriptChecker } from '../../src/index.js'; import { TypescriptChecker } from '../../src/typescript-checker.js'; -import { TypeScriptCheckerOptionsWithStrykerOptions } from '../../src/typescript-checker-options-with-stryker-options'; +import { TypescriptCheckerOptionsWithStrykerOptions } from '../../src/typescript-checker-options-with-stryker-options'; describe('typescript-checker', () => { let sut: TypescriptChecker; @@ -15,13 +15,13 @@ describe('typescript-checker', () => { describe(TypescriptChecker.prototype.group.name, () => { it('should not group mutants if prioritizePerformanceOverAccuracy is false', async () => { - (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker = { prioritizePerformanceOverAccuracy: false }; + (testInjector.options as TypescriptCheckerOptionsWithStrykerOptions).typescriptChecker = { prioritizePerformanceOverAccuracy: false }; const result = await sut.group([factory.mutant(), factory.mutant(), factory.mutant()]); expect(result.length).to.be.eq(3); }); it('should group mutants if prioritizePerformanceOverAccuracy is true', async () => { - (testInjector.options as TypeScriptCheckerOptionsWithStrykerOptions).typeScriptChecker = { prioritizePerformanceOverAccuracy: true }; + (testInjector.options as TypescriptCheckerOptionsWithStrykerOptions).typescriptChecker = { prioritizePerformanceOverAccuracy: true }; const result = await sut.group([factory.mutant(), factory.mutant(), factory.mutant()]); expect(result.length).to.be.eq(1); }); From 68fcdbf9ff3c2954a9e5a6103694bca5de02ab2f Mon Sep 17 00:00:00 2001 From: Danny Berkelaar Date: Fri, 20 Jan 2023 21:29:21 +0100 Subject: [PATCH 68/77] Removed unnecessary init in test --- packages/typescript-checker/test/unit/typescript-checker.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/typescript-checker/test/unit/typescript-checker.spec.ts b/packages/typescript-checker/test/unit/typescript-checker.spec.ts index 8a9d7e29ce..fb024db590 100644 --- a/packages/typescript-checker/test/unit/typescript-checker.spec.ts +++ b/packages/typescript-checker/test/unit/typescript-checker.spec.ts @@ -10,7 +10,6 @@ describe('typescript-checker', () => { let sut: TypescriptChecker; beforeEach(() => { sut = testInjector.injector.injectFunction(createTypescriptChecker); - return sut.init(); }); describe(TypescriptChecker.prototype.group.name, () => { From 2f04be89aae5ce3862cec78a0192da654e66da23 Mon Sep 17 00:00:00 2001 From: Nico Jansen Date: Mon, 23 Jan 2023 12:03:58 +0100 Subject: [PATCH 69/77] refactor(split): mutant array by iterating once --- .../src/typescript-checker.ts | 12 ++--- packages/util/src/index.ts | 1 + packages/util/src/split.ts | 13 ++++++ packages/util/test/unit/split.spec.ts | 44 +++++++++++++++++++ 4 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 packages/util/src/split.ts create mode 100644 packages/util/test/unit/split.spec.ts diff --git a/packages/typescript-checker/src/typescript-checker.ts b/packages/typescript-checker/src/typescript-checker.ts index 418c5af091..3d4d685692 100644 --- a/packages/typescript-checker/src/typescript-checker.ts +++ b/packages/typescript-checker/src/typescript-checker.ts @@ -6,6 +6,8 @@ import { tokens, commonTokens, PluginContext, Injector, Scope } from '@stryker-m import { Logger, LoggerFactoryMethod } from '@stryker-mutator/api/logging'; import { Mutant, StrykerOptions } from '@stryker-mutator/api/core'; +import { split } from '@stryker-mutator/util'; + import * as pluginTokens from './plugin-tokens.js'; import { TypescriptCompiler } from './typescript-compiler.js'; import { createGroups } from './grouping/create-groups.js'; @@ -89,15 +91,13 @@ export class TypescriptChecker implements Checker { } const nodes = this.tsCompiler.nodes; - const mutantsOutSideProject = mutants.filter((m) => nodes.get(toPosixFileName(m.fileName)) == null).map((m) => m.id); - const mutantsToTest = mutants.filter((m) => nodes.get(toPosixFileName(m.fileName)) != null); + const [mutantsOutsideProject, mutantsToTest] = split(mutants, (m) => nodes.get(toPosixFileName(m.fileName)) == null); const groups = createGroups(mutantsToTest, nodes).sort((a, b) => b.length - a.length); - this.logger.debug(`Created ${groups.length} groups with largest group of ${groups[0]?.length ?? 0} mutants`); - - if (mutantsOutSideProject.length) { - groups.unshift(mutantsOutSideProject); + if (mutantsOutsideProject.length) { + groups.unshift(mutantsOutsideProject.map((m) => m.id)); } + this.logger.debug(`Created ${groups.length} groups with largest group of ${groups[0]?.length ?? 0} mutants`); return groups; } diff --git a/packages/util/src/index.ts b/packages/util/src/index.ts index 46e2c85eca..b6161078ca 100644 --- a/packages/util/src/index.ts +++ b/packages/util/src/index.ts @@ -13,3 +13,4 @@ export * from './require-resolve.js'; export * from './deep-merge.js'; export * from './find-unserializables.js'; export * from './platform.js'; +export * from './split.js'; diff --git a/packages/util/src/split.ts b/packages/util/src/split.ts new file mode 100644 index 0000000000..b5dcdfb64b --- /dev/null +++ b/packages/util/src/split.ts @@ -0,0 +1,13 @@ +export function split(values: Iterable, predicate: (value: T, index: number) => boolean): [T[], T[]] { + const left: T[] = []; + const right: T[] = []; + let index = 0; + for (const value of values) { + if (predicate(value, index++)) { + left.push(value); + } else { + right.push(value); + } + } + return [left, right]; +} diff --git a/packages/util/test/unit/split.spec.ts b/packages/util/test/unit/split.spec.ts new file mode 100644 index 0000000000..1645afa843 --- /dev/null +++ b/packages/util/test/unit/split.spec.ts @@ -0,0 +1,44 @@ +import { expect } from 'chai'; + +import { split } from '../../src/index.js'; + +describe(split.name, () => { + it('should split into empty arrays when input is empty', () => { + const [left, right] = split([], () => true); + + expect(left).lengthOf(0); + expect(right).lengthOf(0); + }); + + it('should split all values left when predicate is true', () => { + const input = [1, 2, 3]; + const [left, right] = split(input, () => true); + + expect(left).deep.eq(input); + expect(right).lengthOf(0); + }); + + it('should split all values right when predicate is false', () => { + const input = [1, 2, 3]; + const [left, right] = split(input, () => false); + + expect(right).deep.eq(input); + expect(left).lengthOf(0); + }); + + it('should provide value to predicate', () => { + const input = [1, 2, 3]; + const [left, right] = split(input, (value) => value % 2 === 0); + + expect(left).deep.eq([2]); + expect(right).deep.eq([1, 3]); + }); + + it('should provide index to predicate', () => { + const input = ['f', 'o', 'b']; + const [left, right] = split(input, (_, index) => index % 2 === 0); + + expect(left).deep.eq(['f', 'b']); + expect(right).deep.eq(['o']); + }); +}); From 4029df7d9a642eecbed1f2c8ee3165795fbaec64 Mon Sep 17 00:00:00 2001 From: Nico Jansen Date: Mon, 23 Jan 2023 12:07:58 +0100 Subject: [PATCH 70/77] feat(log): correctly log the groupsize --- packages/typescript-checker/src/typescript-checker.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/typescript-checker/src/typescript-checker.ts b/packages/typescript-checker/src/typescript-checker.ts index 3d4d685692..428a53f73c 100644 --- a/packages/typescript-checker/src/typescript-checker.ts +++ b/packages/typescript-checker/src/typescript-checker.ts @@ -93,11 +93,8 @@ export class TypescriptChecker implements Checker { const [mutantsOutsideProject, mutantsToTest] = split(mutants, (m) => nodes.get(toPosixFileName(m.fileName)) == null); - const groups = createGroups(mutantsToTest, nodes).sort((a, b) => b.length - a.length); - if (mutantsOutsideProject.length) { - groups.unshift(mutantsOutsideProject.map((m) => m.id)); - } - this.logger.debug(`Created ${groups.length} groups with largest group of ${groups[0]?.length ?? 0} mutants`); + const groups = [mutantsOutsideProject.map((m) => m.id), ...createGroups(mutantsToTest, nodes)].sort((a, b) => b.length - a.length); + this.logger.info(`Created ${groups.length} groups with largest group of ${groups[0]?.length ?? 0} mutants`); return groups; } From 51e08f480588aed5ca636bc3b2b426b8ef7bb9ee Mon Sep 17 00:00:00 2001 From: Nico Jansen Date: Wed, 25 Jan 2023 08:05:11 +0100 Subject: [PATCH 71/77] Add unit tests for the typescript checker --- .../src/typescript-checker.ts | 20 ++++--- packages/typescript-checker/test/helpers.ts | 13 +++++ .../test/unit/typescript-checker.spec.ts | 56 +++++++++++++++++-- packages/util/src/string-utils.ts | 10 ++++ packages/util/test/unit/string-utils.spec.ts | 22 +++++++- 5 files changed, 107 insertions(+), 14 deletions(-) create mode 100644 packages/typescript-checker/test/helpers.ts diff --git a/packages/typescript-checker/src/typescript-checker.ts b/packages/typescript-checker/src/typescript-checker.ts index 428a53f73c..c469c67108 100644 --- a/packages/typescript-checker/src/typescript-checker.ts +++ b/packages/typescript-checker/src/typescript-checker.ts @@ -5,8 +5,7 @@ import { Checker, CheckResult, CheckStatus } from '@stryker-mutator/api/check'; import { tokens, commonTokens, PluginContext, Injector, Scope } from '@stryker-mutator/api/plugin'; import { Logger, LoggerFactoryMethod } from '@stryker-mutator/api/logging'; import { Mutant, StrykerOptions } from '@stryker-mutator/api/core'; - -import { split } from '@stryker-mutator/util'; +import { split, strykerReportBugUrl } from '@stryker-mutator/util'; import * as pluginTokens from './plugin-tokens.js'; import { TypescriptCompiler } from './typescript-compiler.js'; @@ -90,10 +89,9 @@ export class TypescriptChecker implements Checker { return mutants.map((m) => [m.id]); } const nodes = this.tsCompiler.nodes; + const [mutantsOutsideProject, mutantsInProject] = split(mutants, (m) => nodes.get(toPosixFileName(m.fileName)) == null); - const [mutantsOutsideProject, mutantsToTest] = split(mutants, (m) => nodes.get(toPosixFileName(m.fileName)) == null); - - const groups = [mutantsOutsideProject.map((m) => m.id), ...createGroups(mutantsToTest, nodes)].sort((a, b) => b.length - a.length); + const groups = [mutantsOutsideProject.map((m) => m.id), ...createGroups(mutantsInProject, nodes)].sort((a, b) => b.length - a.length); this.logger.info(`Created ${groups.length} groups with largest group of ${groups[0]?.length ?? 0} mutants`); return groups; @@ -116,13 +114,21 @@ export class TypescriptChecker implements Checker { for (const error of errors) { if (!error.file?.fileName) { throw new Error( - `Typescript error: '${error.messageText}' doesn\'t have a corresponding file, if you think this is a bug please open an issue on the stryker-js github` + `Typescript error: '${ + error.messageText + }' was reported without a corresponding file. This shouldn't happen. Please open an issue using this link: ${strykerReportBugUrl( + `[BUG]: TypeScript checker reports compile error without a corresponding file: ${error.messageText}` + )}` ); } const nodeErrorWasThrownIn = nodes.get(error.file?.fileName); if (!nodeErrorWasThrownIn) { throw new Error( - "Typescript error located in a file that is not part of your project or doesn't have a reference to your project. This shouldn't happen, please open an issue on the stryker-js github" + `Typescript error: '${error.messageText}' was reported in an unrelated file (${ + error.file.fileName + }). This file is not part of your project, or referenced from your project. This shouldn't happen, please open an issue using this link: ${strykerReportBugUrl( + `[BUG]: TypeScript checker reports compile error in an unrelated file: ${error.messageText}` + )}` ); } const mutantsRelatedToError = nodeErrorWasThrownIn.getMutantsWithReferenceToChildrenOrSelf(mutants); diff --git a/packages/typescript-checker/test/helpers.ts b/packages/typescript-checker/test/helpers.ts new file mode 100644 index 0000000000..989f5c9ba3 --- /dev/null +++ b/packages/typescript-checker/test/helpers.ts @@ -0,0 +1,13 @@ +import ts from 'typescript'; + +export function createTSDiagnostic(overrides?: Partial): ts.Diagnostic { + return { + category: ts.DiagnosticCategory.Error, + code: 42, + file: undefined, + length: undefined, + messageText: 'foo', + start: undefined, + ...overrides, + }; +} diff --git a/packages/typescript-checker/test/unit/typescript-checker.spec.ts b/packages/typescript-checker/test/unit/typescript-checker.spec.ts index fb024db590..570b570bda 100644 --- a/packages/typescript-checker/test/unit/typescript-checker.spec.ts +++ b/packages/typescript-checker/test/unit/typescript-checker.spec.ts @@ -1,28 +1,72 @@ import { testInjector, factory } from '@stryker-mutator/test-helpers'; import { expect } from 'chai'; - -import { createTypescriptChecker } from '../../src/index.js'; +import sinon from 'sinon'; +import { strykerReportBugUrl } from '@stryker-mutator/util'; +import ts from 'typescript'; import { TypescriptChecker } from '../../src/typescript-checker.js'; import { TypescriptCheckerOptionsWithStrykerOptions } from '../../src/typescript-checker-options-with-stryker-options'; +import { TypescriptCompiler } from '../../src/typescript-compiler.js'; +import { TSFileNode } from '../../src/grouping/ts-file-node.js'; +import * as pluginTokens from '../../src/plugin-tokens.js'; +import { createTSDiagnostic } from '../helpers.js'; -describe('typescript-checker', () => { +describe(TypescriptChecker.name, () => { let sut: TypescriptChecker; + let compilerMock: sinon.SinonStubbedInstance; + let options: TypescriptCheckerOptionsWithStrykerOptions; + let nodes: Map; beforeEach(() => { - sut = testInjector.injector.injectFunction(createTypescriptChecker); + nodes = new Map(); + options = testInjector.options as TypescriptCheckerOptionsWithStrykerOptions; + options.typescriptChecker = { prioritizePerformanceOverAccuracy: true }; + compilerMock = sinon.createStubInstance(TypescriptCompiler); + sinon.stub(compilerMock, 'nodes').get(() => nodes); + sut = testInjector.injector.provideValue(pluginTokens.tsCompiler, compilerMock).injectClass(TypescriptChecker); }); describe(TypescriptChecker.prototype.group.name, () => { it('should not group mutants if prioritizePerformanceOverAccuracy is false', async () => { - (testInjector.options as TypescriptCheckerOptionsWithStrykerOptions).typescriptChecker = { prioritizePerformanceOverAccuracy: false }; + options.typescriptChecker.prioritizePerformanceOverAccuracy = false; const result = await sut.group([factory.mutant(), factory.mutant(), factory.mutant()]); expect(result.length).to.be.eq(3); }); it('should group mutants if prioritizePerformanceOverAccuracy is true', async () => { - (testInjector.options as TypescriptCheckerOptionsWithStrykerOptions).typescriptChecker = { prioritizePerformanceOverAccuracy: true }; + options.typescriptChecker.prioritizePerformanceOverAccuracy = true; const result = await sut.group([factory.mutant(), factory.mutant(), factory.mutant()]); expect(result.length).to.be.eq(1); }); }); + + describe(TypescriptChecker.prototype.check.name, () => { + it("should reject when errors don't provide a file name", async () => { + // Arrange + const mutants = [factory.mutant({ fileName: 'foo.ts', id: '41' }), factory.mutant({ fileName: 'bar.js', id: '42' })]; + compilerMock.nodes.set('foo.ts', new TSFileNode('foo.ts', [], [])); + compilerMock.check.resolves([createTSDiagnostic({ file: undefined, messageText: 'fooError' })]); + + // Act + await expect(sut.check(mutants)).rejectedWith( + `Typescript error: 'fooError' was reported without a corresponding file. This shouldn't happen. Please open an issue using this link: ${strykerReportBugUrl( + '[BUG]: TypeScript checker reports compile error without a corresponding file: fooError' + )}` + ); + }); + it('should reject when errors relate to an unrelated file', async () => { + // Arrange + const mutants = [factory.mutant({ fileName: 'foo.ts', id: '41' }), factory.mutant({ fileName: 'foo.ts', id: '42' })]; + compilerMock.nodes.set('foo.ts', new TSFileNode('foo.ts', [], [])); + compilerMock.check.resolves([ + createTSDiagnostic({ file: ts.createSourceFile('bar.ts', '', ts.ScriptTarget.Latest, false, undefined), messageText: 'fooError' }), + ]); + + // Act + await expect(sut.check(mutants)).rejectedWith( + `Typescript error: 'fooError' was reported in an unrelated file (bar.ts). This file is not part of your project, or referenced from your project. This shouldn't happen, please open an issue using this link: ${strykerReportBugUrl( + '[BUG]: TypeScript checker reports compile error in an unrelated file: fooError' + )}` + ); + }); + }); }); diff --git a/packages/util/src/string-utils.ts b/packages/util/src/string-utils.ts index 03a9e19d7c..6fd3b1f36f 100644 --- a/packages/util/src/string-utils.ts +++ b/packages/util/src/string-utils.ts @@ -57,3 +57,13 @@ export function escapeRegExp(input: string): string { export function normalizeFileName(fileName: string): string { return fileName.replace(/\\/g, '/'); } + +/** + * Creates a URL to the page where you can report a bug. + * @param titleSuggestion The title to be prefilled in. + */ +export function strykerReportBugUrl(titleSuggestion: string): string { + return `https://github.com/stryker-mutator/stryker-js/issues/new?assignees=&labels=%F0%9F%90%9B+Bug&template=bug_report.md&title=${encodeURIComponent( + titleSuggestion + )}`; +} diff --git a/packages/util/test/unit/string-utils.spec.ts b/packages/util/test/unit/string-utils.spec.ts index 72105b91cf..e30e5858a2 100644 --- a/packages/util/test/unit/string-utils.spec.ts +++ b/packages/util/test/unit/string-utils.spec.ts @@ -1,6 +1,14 @@ import { expect } from 'chai'; -import { normalizeWhitespaces, propertyPath, escapeRegExpLiteral, escapeRegExp, normalizeFileName, normalizeLineEndings } from '../../src/index.js'; +import { + normalizeWhitespaces, + propertyPath, + escapeRegExpLiteral, + escapeRegExp, + normalizeFileName, + normalizeLineEndings, + strykerReportBugUrl, +} from '../../src/index.js'; describe('stringUtils', () => { describe(normalizeWhitespaces.name, () => { @@ -119,4 +127,16 @@ describe('stringUtils', () => { expect(normalizeFileName('test/util/foo.spec.js')).eq('test/util/foo.spec.js'); }); }); + + describe(strykerReportBugUrl.name, () => { + it('should format a correct url', () => { + expect(strykerReportBugUrl('theTitle')).eq( + 'https://github.com/stryker-mutator/stryker-js/issues/new?assignees=&labels=%F0%9F%90%9B+Bug&template=bug_report.md&title=theTitle' + ); + }); + + it('should url-encode the title suggestion', () => { + expect(strykerReportBugUrl('the title').endsWith('the&title')); + }); + }); }); From c5505170bf71816b809b44a3f90e8b33de0f8afc Mon Sep 17 00:00:00 2001 From: Nico Jansen Date: Wed, 25 Jan 2023 14:05:29 +0100 Subject: [PATCH 72/77] fix: don't create empty groups --- .../typescript-checker/src/typescript-checker.ts | 10 ++++++---- .../test/unit/typescript-checker.spec.ts | 13 +++++++++++-- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/typescript-checker/src/typescript-checker.ts b/packages/typescript-checker/src/typescript-checker.ts index c469c67108..187a1b95aa 100644 --- a/packages/typescript-checker/src/typescript-checker.ts +++ b/packages/typescript-checker/src/typescript-checker.ts @@ -91,10 +91,12 @@ export class TypescriptChecker implements Checker { const nodes = this.tsCompiler.nodes; const [mutantsOutsideProject, mutantsInProject] = split(mutants, (m) => nodes.get(toPosixFileName(m.fileName)) == null); - const groups = [mutantsOutsideProject.map((m) => m.id), ...createGroups(mutantsInProject, nodes)].sort((a, b) => b.length - a.length); - this.logger.info(`Created ${groups.length} groups with largest group of ${groups[0]?.length ?? 0} mutants`); - - return groups; + const groups = createGroups(mutantsInProject, nodes); + if (mutantsOutsideProject.length) { + return [mutantsOutsideProject.map((m) => m.id), ...groups]; + } else { + return groups; + } } private async checkErrors( diff --git a/packages/typescript-checker/test/unit/typescript-checker.spec.ts b/packages/typescript-checker/test/unit/typescript-checker.spec.ts index 570b570bda..7bbe8a2ee7 100644 --- a/packages/typescript-checker/test/unit/typescript-checker.spec.ts +++ b/packages/typescript-checker/test/unit/typescript-checker.spec.ts @@ -29,13 +29,22 @@ describe(TypescriptChecker.name, () => { it('should not group mutants if prioritizePerformanceOverAccuracy is false', async () => { options.typescriptChecker.prioritizePerformanceOverAccuracy = false; const result = await sut.group([factory.mutant(), factory.mutant(), factory.mutant()]); - expect(result.length).to.be.eq(3); + expect(result).lengthOf(3); }); it('should group mutants if prioritizePerformanceOverAccuracy is true', async () => { options.typescriptChecker.prioritizePerformanceOverAccuracy = true; const result = await sut.group([factory.mutant(), factory.mutant(), factory.mutant()]); - expect(result.length).to.be.eq(1); + expect(result).lengthOf(1); + }); + + it('should not add an empty group when there are no mutants that fall outside of the project', async () => { + const mutants = [factory.mutant({ fileName: 'foo.ts', id: '41' })]; + compilerMock.nodes.set('foo.ts', new TSFileNode('foo.ts', [], [])); + + const result = await sut.group(mutants); + expect(result).lengthOf(1); + expect(result[0]).lengthOf(1); }); }); From 8642187b2c767405f805a588bceeed0ce3f68063 Mon Sep 17 00:00:00 2001 From: Danny Berkelaar Date: Thu, 2 Feb 2023 12:50:55 +0100 Subject: [PATCH 73/77] Fixed findSourceMapRegex and added project reference test --- packages/typescript-checker/src/tsconfig-helpers.ts | 2 +- .../test/integration/project-references.it.spec.ts | 7 +++++++ .../test/unit/typescript-checker.spec.ts | 3 ++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/typescript-checker/src/tsconfig-helpers.ts b/packages/typescript-checker/src/tsconfig-helpers.ts index f2372123cb..35384dc8aa 100644 --- a/packages/typescript-checker/src/tsconfig-helpers.ts +++ b/packages/typescript-checker/src/tsconfig-helpers.ts @@ -107,7 +107,7 @@ export function toPosixFileName(fileName: string): string { * @param content The content of the declaration file * @returns URL of the source file or undefined if not found */ -const findSourceMapRegex = /^\/\/# sourceMappingURL=(.+)$/; +const findSourceMapRegex = /\/\/# sourceMappingURL=(.+)$/; export function getSourceMappingURL(content: string): string | undefined { findSourceMapRegex.lastIndex = 0; return findSourceMapRegex.exec(content)?.[1]; diff --git a/packages/typescript-checker/test/integration/project-references.it.spec.ts b/packages/typescript-checker/test/integration/project-references.it.spec.ts index 98e3f7cf52..9360b084e5 100644 --- a/packages/typescript-checker/test/integration/project-references.it.spec.ts +++ b/packages/typescript-checker/test/integration/project-references.it.spec.ts @@ -49,6 +49,13 @@ describe('Typescript checker on a project with project references', () => { const actual = await sut.check([mutant]); expect(actual).deep.eq(expectedResult); }); + + it('should create multiple groups if reference between project', async () => { + const mutantInSourceProject = createMutant('todo.ts', 'TodoList.allTodos.push(newItem)', '', '42'); + const mutantInProjectWithReference = createMutant('todo.spec.ts', "name = 'test'", "name = 'stryker'", '43'); + const result = await sut.group([mutantInSourceProject, mutantInProjectWithReference]); + expect(result).to.have.lengthOf(2); + }); }); const fileContents = Object.freeze({ diff --git a/packages/typescript-checker/test/unit/typescript-checker.spec.ts b/packages/typescript-checker/test/unit/typescript-checker.spec.ts index 7bbe8a2ee7..3e7696425c 100644 --- a/packages/typescript-checker/test/unit/typescript-checker.spec.ts +++ b/packages/typescript-checker/test/unit/typescript-checker.spec.ts @@ -28,8 +28,9 @@ describe(TypescriptChecker.name, () => { describe(TypescriptChecker.prototype.group.name, () => { it('should not group mutants if prioritizePerformanceOverAccuracy is false', async () => { options.typescriptChecker.prioritizePerformanceOverAccuracy = false; - const result = await sut.group([factory.mutant(), factory.mutant(), factory.mutant()]); + const result = await sut.group([factory.mutant({ id: '1' }), factory.mutant({ id: '2' }), factory.mutant({ id: '3' })]); expect(result).lengthOf(3); + expect(result.reduce((prev, group) => prev + group.length, 0)).lengthOf(3); }); it('should group mutants if prioritizePerformanceOverAccuracy is true', async () => { From 8b82423ad6f7dc297f976709403a2a6bc1e157ba Mon Sep 17 00:00:00 2001 From: Danny Berkelaar Date: Thu, 2 Feb 2023 15:55:38 +0100 Subject: [PATCH 74/77] Fix broken test --- .../typescript-checker/test/unit/typescript-checker.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typescript-checker/test/unit/typescript-checker.spec.ts b/packages/typescript-checker/test/unit/typescript-checker.spec.ts index 3e7696425c..c20285a4fd 100644 --- a/packages/typescript-checker/test/unit/typescript-checker.spec.ts +++ b/packages/typescript-checker/test/unit/typescript-checker.spec.ts @@ -30,7 +30,7 @@ describe(TypescriptChecker.name, () => { options.typescriptChecker.prioritizePerformanceOverAccuracy = false; const result = await sut.group([factory.mutant({ id: '1' }), factory.mutant({ id: '2' }), factory.mutant({ id: '3' })]); expect(result).lengthOf(3); - expect(result.reduce((prev, group) => prev + group.length, 0)).lengthOf(3); + expect(result.reduce((prev, group) => prev + group.length, 0)).eq(3); }); it('should group mutants if prioritizePerformanceOverAccuracy is true', async () => { From 0a38a7e79fdf19fff28e06d4c2871b3a0d9b5618 Mon Sep 17 00:00:00 2001 From: Danny Berkelaar Date: Fri, 3 Feb 2023 15:38:31 +0100 Subject: [PATCH 75/77] Changed testResource --- .../src/grouping/ts-file-node.ts | 4 +++- .../src/typescript-compiler.ts | 13 ++++++---- .../integration/project-references.it.spec.ts | 19 ++++++++------- .../test/unit/grouping/ts-file-node.spec.ts | 24 +++++++++++++++++++ .../test/unit/typescript-helpers.spec.ts | 14 ++++++----- .../project-references/src/index.ts | 5 ++++ .../project-references/src/job.ts | 6 +++++ .../project-references/src/src.tsbuildinfo | 2 +- .../project-references/src/todo.ts | 23 ------------------ .../project-references/src/tsconfig.json | 5 +++- .../project-references/test/test.tsbuildinfo | 1 - .../project-references/test/todo.spec.ts | 9 ------- .../project-references/test/tsconfig.json | 10 -------- .../project-references/tsconfig.root.json | 2 +- .../project-references/utils/math.ts | 3 +++ .../project-references/utils/text.ts | 3 +++ .../project-references/utils/tsconfig.json | 7 ++++++ .../utils/utils.tsbuildinfo | 1 + workspace.code-workspace | 2 +- 19 files changed, 86 insertions(+), 67 deletions(-) create mode 100644 packages/typescript-checker/testResources/project-references/src/index.ts create mode 100644 packages/typescript-checker/testResources/project-references/src/job.ts delete mode 100644 packages/typescript-checker/testResources/project-references/src/todo.ts delete mode 100644 packages/typescript-checker/testResources/project-references/test/test.tsbuildinfo delete mode 100644 packages/typescript-checker/testResources/project-references/test/todo.spec.ts delete mode 100644 packages/typescript-checker/testResources/project-references/test/tsconfig.json create mode 100644 packages/typescript-checker/testResources/project-references/utils/math.ts create mode 100644 packages/typescript-checker/testResources/project-references/utils/text.ts create mode 100644 packages/typescript-checker/testResources/project-references/utils/tsconfig.json create mode 100644 packages/typescript-checker/testResources/project-references/utils/utils.tsbuildinfo diff --git a/packages/typescript-checker/src/grouping/ts-file-node.ts b/packages/typescript-checker/src/grouping/ts-file-node.ts index 52124bd73c..eebe180405 100644 --- a/packages/typescript-checker/src/grouping/ts-file-node.ts +++ b/packages/typescript-checker/src/grouping/ts-file-node.ts @@ -1,5 +1,7 @@ import { Mutant } from '@stryker-mutator/api/src/core'; +import { toPosixFileName } from '../tsconfig-helpers.js'; + // This class exist so we can have a two way dependency graph. // the two way dependency graph is used to search for mutants related to typescript errors export class TSFileNode { @@ -32,7 +34,7 @@ export class TSFileNode { nodesChecked.push(this.fileName); - const relatedMutants = mutants.filter((m) => m.fileName == this.fileName); + const relatedMutants = mutants.filter((m) => toPosixFileName(m.fileName) == this.fileName); const childResult = this.children.flatMap((c) => c.getMutantsWithReferenceToChildrenOrSelf(mutants, nodesChecked)); return [...relatedMutants, ...childResult]; } diff --git a/packages/typescript-checker/src/typescript-compiler.ts b/packages/typescript-checker/src/typescript-compiler.ts index 259095ac42..87671d0153 100644 --- a/packages/typescript-checker/src/typescript-compiler.ts +++ b/packages/typescript-checker/src/typescript-compiler.ts @@ -226,15 +226,18 @@ export class TypescriptCompiler implements ITypescriptCompiler, IFileRelationCre return dependencyFileName; } - const sourceMapFileName = path.resolve(dependencyFileName, '..', sourceMappingURL); + const sourceMapFileName = path.resolve(path.dirname(dependencyFileName), sourceMappingURL); const sourceMap = this.fs.getFile(sourceMapFileName); - if (!sourceMap) throw new Error(`Could not find ${sourceMapFileName}`); + if (!sourceMap) { + this.log.warn(`Could not find sourcemap ${sourceMapFileName}`); + return dependencyFileName; + } - const sources: string[] = JSON.parse(sourceMap.content).sources; + const sources: string[] | undefined = JSON.parse(sourceMap.content).sources; - if (sources.length === 1) { + if (sources?.length === 1) { const sourcePath = sources[0]; - return toPosixFileName(path.resolve(sourceMapFileName, '..', sourcePath)); + return toPosixFileName(path.resolve(path.dirname(sourceMapFileName), sourcePath)); } return dependencyFileName; diff --git a/packages/typescript-checker/test/integration/project-references.it.spec.ts b/packages/typescript-checker/test/integration/project-references.it.spec.ts index 9360b084e5..3ad68b3806 100644 --- a/packages/typescript-checker/test/integration/project-references.it.spec.ts +++ b/packages/typescript-checker/test/integration/project-references.it.spec.ts @@ -37,33 +37,36 @@ describe('Typescript checker on a project with project references', () => { }); it('should be able to validate a mutant', async () => { - const mutant = createMutant('todo.ts', 'TodoList.allTodos.push(newItem)', 'newItem ? 42 : 43'); + const mutant = createMutant('job.ts', 'Starting job', 'stryker was here'); const expectedResult: Record = { [mutant.id]: { status: CheckStatus.Passed } }; const actualResult = await sut.check([mutant]); expect(actualResult).deep.eq(expectedResult); }); it('should allow unused local variables (override options)', async () => { - const mutant = createMutant('todo.ts', 'TodoList.allTodos.push(newItem)', '42'); + const mutant = createMutant('job.ts', 'toUpperCase(logText)', 'toUpperCase("")'); const expectedResult: Record = { [mutant.id]: { status: CheckStatus.Passed } }; const actual = await sut.check([mutant]); expect(actual).deep.eq(expectedResult); }); it('should create multiple groups if reference between project', async () => { - const mutantInSourceProject = createMutant('todo.ts', 'TodoList.allTodos.push(newItem)', '', '42'); - const mutantInProjectWithReference = createMutant('todo.spec.ts', "name = 'test'", "name = 'stryker'", '43'); - const result = await sut.group([mutantInSourceProject, mutantInProjectWithReference]); + const mutantInSourceProject = createMutant('job.ts', 'Starting job', '', '42'); + const mutantInProjectWithReference = createMutant('text.ts', 'toUpperCase()', 'toLowerCase()', '43'); + const mutantOutsideOfReference = createMutant('math.ts', 'array.length', '1', '44'); + const result = await sut.group([mutantInSourceProject, mutantInProjectWithReference, mutantOutsideOfReference]); expect(result).to.have.lengthOf(2); }); }); const fileContents = Object.freeze({ - ['todo.ts']: fs.readFileSync(resolveTestResource('src', 'todo.ts'), 'utf8'), - ['todo.spec.ts']: fs.readFileSync(resolveTestResource('test', 'todo.spec.ts'), 'utf8'), + ['index.ts']: fs.readFileSync(resolveTestResource('src', 'index.ts'), 'utf8'), + ['job.ts']: fs.readFileSync(resolveTestResource('src', 'job.ts'), 'utf8'), + ['math.ts']: fs.readFileSync(resolveTestResource('utils', 'math.ts'), 'utf8'), + ['text.ts']: fs.readFileSync(resolveTestResource('utils', 'text.ts'), 'utf8'), }); -function createMutant(fileName: 'todo.spec.ts' | 'todo.ts', findText: string, replacement: string, id = '42', offset = 0): Mutant { +function createMutant(fileName: 'index.ts' | 'job.ts' | 'math.ts' | 'text.ts', findText: string, replacement: string, id = '42', offset = 0): Mutant { const lines = fileContents[fileName].split('\n'); // todo fix this \n const lineNumber = lines.findIndex((l) => l.includes(findText)); if (lineNumber === -1) { diff --git a/packages/typescript-checker/test/unit/grouping/ts-file-node.spec.ts b/packages/typescript-checker/test/unit/grouping/ts-file-node.spec.ts index 42517999ab..f8e3b97ea6 100644 --- a/packages/typescript-checker/test/unit/grouping/ts-file-node.spec.ts +++ b/packages/typescript-checker/test/unit/grouping/ts-file-node.spec.ts @@ -104,5 +104,29 @@ describe('TSFileNode', () => { expect(node.getMutantsWithReferenceToChildrenOrSelf(mutants)).to.have.lengthOf(1); }); + + it('should find mutant with backward slashes and forward slashes', () => { + const node = new TSFileNode('path/NodeA.js', [], []); + node.children = [node]; + + const mutants: Mutant[] = [ + { + fileName: 'path/NodeA.js', + id: '0', + replacement: '-', + location: { start: { line: 1, column: 1 }, end: { line: 1, column: 1 } }, + mutatorName: '', + }, + { + fileName: 'path\\NodeA.js', + id: '0', + replacement: '-', + location: { start: { line: 1, column: 1 }, end: { line: 1, column: 1 } }, + mutatorName: '', + }, + ]; + + expect(node.getMutantsWithReferenceToChildrenOrSelf(mutants)).to.have.lengthOf(2); + }); }); }); diff --git a/packages/typescript-checker/test/unit/typescript-helpers.spec.ts b/packages/typescript-checker/test/unit/typescript-helpers.spec.ts index 58baad29fa..8770c84c10 100644 --- a/packages/typescript-checker/test/unit/typescript-helpers.spec.ts +++ b/packages/typescript-checker/test/unit/typescript-helpers.spec.ts @@ -164,17 +164,19 @@ describe('typescript-helpers', () => { overrideOptions( { config: { - inlineSourceMap: '', - inlineSources: '', - mapRoute: '', - sourceRoot: '', - outFile: '', + compilerOptions: { + inlineSourceMap: '.', + inlineSources: '.', + mapRoute: '.', + sourceRoot: '.', + outFile: '.', + }, }, }, true ) ).compilerOptions - ).deep.include({}); + ).to.not.have.any.keys('inlineSourceMap', 'inlineSources', 'mapRoute', 'sourceRoot', 'outFile'); }); }); diff --git a/packages/typescript-checker/testResources/project-references/src/index.ts b/packages/typescript-checker/testResources/project-references/src/index.ts new file mode 100644 index 0000000000..407ccaaa23 --- /dev/null +++ b/packages/typescript-checker/testResources/project-references/src/index.ts @@ -0,0 +1,5 @@ +import { count } from '../utils/math.js'; + +export function countArrayLength(todo: any[]): number { + return count(todo); +} diff --git a/packages/typescript-checker/testResources/project-references/src/job.ts b/packages/typescript-checker/testResources/project-references/src/job.ts new file mode 100644 index 0000000000..b27edc10e4 --- /dev/null +++ b/packages/typescript-checker/testResources/project-references/src/job.ts @@ -0,0 +1,6 @@ +import { toUpperCase } from '../utils/text.js'; + +export function start(): void { + const logText = "Starting job"; + console.log(toUpperCase(logText)); +} diff --git a/packages/typescript-checker/testResources/project-references/src/src.tsbuildinfo b/packages/typescript-checker/testResources/project-references/src/src.tsbuildinfo index ccc5d7c12a..61a22ff808 100644 --- a/packages/typescript-checker/testResources/project-references/src/src.tsbuildinfo +++ b/packages/typescript-checker/testResources/project-references/src/src.tsbuildinfo @@ -1 +1 @@ -{"program":{"fileNames":["../../../../../node_modules/typescript/lib/lib.d.ts","../../../../../node_modules/typescript/lib/lib.es5.d.ts","../../../../../node_modules/typescript/lib/lib.dom.d.ts","../../../../../node_modules/typescript/lib/lib.webworker.importscripts.d.ts","../../../../../node_modules/typescript/lib/lib.scripthost.d.ts","./todo.ts"],"fileInfos":["2dc8c927c9c162a773c6bb3cdc4f3286c23f10eedc67414028f9cb5951610f60",{"version":"f20c05dbfe50a208301d2a1da37b9931bce0466eb5a1f4fe240971b4ecc82b67","affectsGlobalScope":true},{"version":"9b087de7268e4efc5f215347a62656663933d63c0b1d7b624913240367b999ea","affectsGlobalScope":true},{"version":"7fac8cb5fc820bc2a59ae11ef1c5b38d3832c6d0dfaec5acdb5569137d09a481","affectsGlobalScope":true},{"version":"097a57355ded99c68e6df1b738990448e0bf170e606707df5a7c0481ff2427cd","affectsGlobalScope":true},{"version":"97d5edffca1b83fc791b7a93e1edd42c75c26bdc9d285a8825e6597a55e52d9a","signature":"6b6c2c3e5570afa7f045441b3cbcdee29d62170da635279da7c14873761a7daf"}],"options":{"composite":true,"declaration":true,"declarationMap":true,"module":1,"noUnusedLocals":true,"noUnusedParameters":true,"outDir":"../dist/src","strict":true,"target":1,"tsBuildInfoFile":"./src.tsbuildinfo"},"referencedMap":[],"exportedModulesMap":[],"semanticDiagnosticsPerFile":[1,3,2,5,4,6],"latestChangedDtsFile":"../dist/src/todo.d.ts"},"version":"4.8.4"} \ No newline at end of file +{"program":{"fileNames":["../../../../../node_modules/typescript/lib/lib.d.ts","../../../../../node_modules/typescript/lib/lib.es5.d.ts","../../../../../node_modules/typescript/lib/lib.dom.d.ts","../../../../../node_modules/typescript/lib/lib.webworker.importscripts.d.ts","../../../../../node_modules/typescript/lib/lib.scripthost.d.ts","../dist/utils/math.d.ts","./index.ts","../dist/utils/text.d.ts","./job.ts"],"fileInfos":["2dc8c927c9c162a773c6bb3cdc4f3286c23f10eedc67414028f9cb5951610f60",{"version":"f20c05dbfe50a208301d2a1da37b9931bce0466eb5a1f4fe240971b4ecc82b67","affectsGlobalScope":true},{"version":"9b087de7268e4efc5f215347a62656663933d63c0b1d7b624913240367b999ea","affectsGlobalScope":true},{"version":"7fac8cb5fc820bc2a59ae11ef1c5b38d3832c6d0dfaec5acdb5569137d09a481","affectsGlobalScope":true},{"version":"097a57355ded99c68e6df1b738990448e0bf170e606707df5a7c0481ff2427cd","affectsGlobalScope":true},"80dbf481ae698a44d6d4b60f3c36d84a94b2a5eb14927eae5347b82f33ec0277",{"version":"c9b6bdd48b8bdb8d8e7690c7cc18897a494b6ab17dc58083dacfaf14b846ab4f","signature":"40b6409b8d0dced1f6c3964012b7a7c1cd50e24c3242095d1c8cfc6cabe8bd31"},"cdf6a65d46d64de68df5d8a322621f74327b1ee02c3fde41f736e11d307fcfb1",{"version":"e4c28c497fe6cc6364b113c181c32ba58e70f02d824295e72b15d9570b403104","signature":"9be66c79f48b4876970daed5167e069d7f12f1a1ca616ecaa0ca8280946344ca"}],"options":{"composite":true,"declaration":true,"declarationMap":true,"module":1,"noUnusedLocals":true,"noUnusedParameters":true,"outDir":"../dist/src","strict":true,"target":1,"tsBuildInfoFile":"./src.tsbuildinfo"},"fileIdsList":[[6],[8]],"referencedMap":[[7,1],[9,2]],"exportedModulesMap":[],"semanticDiagnosticsPerFile":[1,3,2,5,4,6,8,7,9],"latestChangedDtsFile":"../dist/src/job.d.ts"},"version":"4.8.4"} \ No newline at end of file diff --git a/packages/typescript-checker/testResources/project-references/src/todo.ts b/packages/typescript-checker/testResources/project-references/src/todo.ts deleted file mode 100644 index 1873791080..0000000000 --- a/packages/typescript-checker/testResources/project-references/src/todo.ts +++ /dev/null @@ -1,23 +0,0 @@ -export interface ITodo { - name: string; - description: string; - completed: boolean; -} - -class Todo implements ITodo { - constructor(public name: string, public description: string, public completed: boolean) { } -} - -export class TodoList { - public static allTodos: Todo[] = []; - - createTodoItem(name: string, description: string) { - let newItem = new Todo(name, description, false); - let totalCount: number = TodoList.allTodos.push(newItem); - return totalCount; - } - - allTodoItems(): ITodo[] { - return TodoList.allTodos; - } -} diff --git a/packages/typescript-checker/testResources/project-references/src/tsconfig.json b/packages/typescript-checker/testResources/project-references/src/tsconfig.json index e7f278368a..4ef4b2942f 100644 --- a/packages/typescript-checker/testResources/project-references/src/tsconfig.json +++ b/packages/typescript-checker/testResources/project-references/src/tsconfig.json @@ -3,5 +3,8 @@ "compilerOptions": { "tsBuildInfoFile": "src.tsbuildinfo", "outDir": "../dist/src" - } + }, + "references": [ + { "path": "../utils" } + ] } diff --git a/packages/typescript-checker/testResources/project-references/test/test.tsbuildinfo b/packages/typescript-checker/testResources/project-references/test/test.tsbuildinfo deleted file mode 100644 index 1f12f06027..0000000000 --- a/packages/typescript-checker/testResources/project-references/test/test.tsbuildinfo +++ /dev/null @@ -1 +0,0 @@ -{"program":{"fileNames":["../../../../../node_modules/typescript/lib/lib.d.ts","../../../../../node_modules/typescript/lib/lib.es5.d.ts","../../../../../node_modules/typescript/lib/lib.dom.d.ts","../../../../../node_modules/typescript/lib/lib.webworker.importscripts.d.ts","../../../../../node_modules/typescript/lib/lib.scripthost.d.ts","../dist/src/todo.d.ts","./todo.spec.ts"],"fileInfos":["2dc8c927c9c162a773c6bb3cdc4f3286c23f10eedc67414028f9cb5951610f60",{"version":"f20c05dbfe50a208301d2a1da37b9931bce0466eb5a1f4fe240971b4ecc82b67","affectsGlobalScope":true},{"version":"9b087de7268e4efc5f215347a62656663933d63c0b1d7b624913240367b999ea","affectsGlobalScope":true},{"version":"7fac8cb5fc820bc2a59ae11ef1c5b38d3832c6d0dfaec5acdb5569137d09a481","affectsGlobalScope":true},{"version":"097a57355ded99c68e6df1b738990448e0bf170e606707df5a7c0481ff2427cd","affectsGlobalScope":true},"bc8d8aaa2580899f2585d0c2117ac431b8f01c99b736ebb90b9765c168c7527f",{"version":"ad5df0c983dc025ef330c707a0ebc4fb08ac9a3ebd85df1eda17fb46b9c281a1","signature":"f761c91419d0a89422a0004ef1a92929dd4d2d5e5c16758654d8b0467d1998c6"}],"options":{"composite":true,"declaration":true,"declarationMap":true,"module":1,"noUnusedLocals":true,"noUnusedParameters":true,"outDir":"../dist/test","strict":true,"target":1,"tsBuildInfoFile":"./test.tsbuildinfo"},"fileIdsList":[[6]],"referencedMap":[[7,1]],"exportedModulesMap":[],"semanticDiagnosticsPerFile":[1,3,2,5,4,6,7],"latestChangedDtsFile":"../dist/test/todo.spec.d.ts"},"version":"4.8.4"} \ No newline at end of file diff --git a/packages/typescript-checker/testResources/project-references/test/todo.spec.ts b/packages/typescript-checker/testResources/project-references/test/todo.spec.ts deleted file mode 100644 index cf9adc35ad..0000000000 --- a/packages/typescript-checker/testResources/project-references/test/todo.spec.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { TodoList } from '../src/todo.js'; - -const list = new TodoList(); - -function addTodo(name = 'test', description = 'test') { - list.createTodoItem(name, description); -} - -addTodo(); diff --git a/packages/typescript-checker/testResources/project-references/test/tsconfig.json b/packages/typescript-checker/testResources/project-references/test/tsconfig.json deleted file mode 100644 index a856f6e530..0000000000 --- a/packages/typescript-checker/testResources/project-references/test/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../tsconfig.settings", - "compilerOptions": { - "outDir": "../dist/test", - "tsBuildInfoFile": "test.tsbuildinfo" - }, - "references": [ - { "path": "../src" } - ] -} diff --git a/packages/typescript-checker/testResources/project-references/tsconfig.root.json b/packages/typescript-checker/testResources/project-references/tsconfig.root.json index d54e2806ef..d0aa0fdd2a 100644 --- a/packages/typescript-checker/testResources/project-references/tsconfig.root.json +++ b/packages/typescript-checker/testResources/project-references/tsconfig.root.json @@ -2,6 +2,6 @@ "files": [], "references": [ { "path": "./src" }, - { "path": "./test" } + { "path": "./utils" } ] } diff --git a/packages/typescript-checker/testResources/project-references/utils/math.ts b/packages/typescript-checker/testResources/project-references/utils/math.ts new file mode 100644 index 0000000000..23cbcf7eda --- /dev/null +++ b/packages/typescript-checker/testResources/project-references/utils/math.ts @@ -0,0 +1,3 @@ +export function count(array: any[]) { + return array.length; +} diff --git a/packages/typescript-checker/testResources/project-references/utils/text.ts b/packages/typescript-checker/testResources/project-references/utils/text.ts new file mode 100644 index 0000000000..33d172474d --- /dev/null +++ b/packages/typescript-checker/testResources/project-references/utils/text.ts @@ -0,0 +1,3 @@ +export function toUpperCase(text: string) { + return text.toUpperCase(); +} diff --git a/packages/typescript-checker/testResources/project-references/utils/tsconfig.json b/packages/typescript-checker/testResources/project-references/utils/tsconfig.json new file mode 100644 index 0000000000..e7f2d3376c --- /dev/null +++ b/packages/typescript-checker/testResources/project-references/utils/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../tsconfig.settings", + "compilerOptions": { + "outDir": "../dist/utils", + "tsBuildInfoFile": "utils.tsbuildinfo" + } +} diff --git a/packages/typescript-checker/testResources/project-references/utils/utils.tsbuildinfo b/packages/typescript-checker/testResources/project-references/utils/utils.tsbuildinfo new file mode 100644 index 0000000000..5901147f8f --- /dev/null +++ b/packages/typescript-checker/testResources/project-references/utils/utils.tsbuildinfo @@ -0,0 +1 @@ +{"program":{"fileNames":["../../../../../node_modules/typescript/lib/lib.d.ts","../../../../../node_modules/typescript/lib/lib.es5.d.ts","../../../../../node_modules/typescript/lib/lib.dom.d.ts","../../../../../node_modules/typescript/lib/lib.webworker.importscripts.d.ts","../../../../../node_modules/typescript/lib/lib.scripthost.d.ts","./math.ts","./text.ts"],"fileInfos":["2dc8c927c9c162a773c6bb3cdc4f3286c23f10eedc67414028f9cb5951610f60",{"version":"f20c05dbfe50a208301d2a1da37b9931bce0466eb5a1f4fe240971b4ecc82b67","affectsGlobalScope":true},{"version":"9b087de7268e4efc5f215347a62656663933d63c0b1d7b624913240367b999ea","affectsGlobalScope":true},{"version":"7fac8cb5fc820bc2a59ae11ef1c5b38d3832c6d0dfaec5acdb5569137d09a481","affectsGlobalScope":true},{"version":"097a57355ded99c68e6df1b738990448e0bf170e606707df5a7c0481ff2427cd","affectsGlobalScope":true},{"version":"6198e7d4a43aabb174a72ec9f0e8d2962912ad59ad90010aac3930868a8f62a4","signature":"0400cb85cef49e897c47df13e38b5cd199e0c900253f2d2ddf2e3491c27bc0a8"},{"version":"becd081df112726ab94c1ca1c05d6a59268fe0dabf7ad076d16ea851bf99e8fb","signature":"6039d94241358544e8d62a3a0ba90752a9973b3b2b422c187e2bcf7256fcda2e"}],"options":{"composite":true,"declaration":true,"declarationMap":true,"module":1,"noUnusedLocals":true,"noUnusedParameters":true,"outDir":"../dist/utils","strict":true,"target":1,"tsBuildInfoFile":"./utils.tsbuildinfo"},"referencedMap":[],"exportedModulesMap":[],"semanticDiagnosticsPerFile":[1,3,2,5,4,6,7],"latestChangedDtsFile":"../dist/utils/text.d.ts"},"version":"4.8.4"} \ No newline at end of file diff --git a/workspace.code-workspace b/workspace.code-workspace index 889e946268..bd4ceecd85 100644 --- a/workspace.code-workspace +++ b/workspace.code-workspace @@ -116,7 +116,7 @@ "Whitespaces" ], "cSpell.language": "en", - "liveServer.settings.multiRootWorkspaceName": "core" + "liveServer.settings.multiRootWorkspaceName": "typescript-checker" }, "extensions": { "recommendations": ["dbaeumer.vscode-eslint", "EditorConfig.EditorConfig"] From 4a1316ac98828738280535e48588cee28adcf633 Mon Sep 17 00:00:00 2001 From: Danny Berkelaar Date: Fri, 10 Feb 2023 09:24:49 +0100 Subject: [PATCH 76/77] Added ts-project-references e2e tests --- .../package.json | 24 +++++++++++++++++++ .../src/core/index.ts | 5 ++++ .../src/core/job.ts | 7 ++++++ .../src/core/tsconfig.json | 9 +++++++ .../src/core/tsconfig.tsbuildinfo | 1 + .../src/utils/math.ts | 3 +++ .../src/utils/text.ts | 3 +++ .../src/utils/tsconfig.json | 6 +++++ .../src/utils/tsconfig.tsbuildinfo | 1 + .../stryker.conf.json | 17 +++++++++++++ .../test/job.spec.ts | 12 ++++++++++ .../tsconfig.json | 3 +++ .../tsconfig.settings.json | 17 +++++++++++++ .../verify/package.json | 3 +++ .../verify/verify.js | 7 ++++++ 15 files changed, 118 insertions(+) create mode 100644 e2e/test/typescript-project-references/package.json create mode 100644 e2e/test/typescript-project-references/src/core/index.ts create mode 100644 e2e/test/typescript-project-references/src/core/job.ts create mode 100644 e2e/test/typescript-project-references/src/core/tsconfig.json create mode 100644 e2e/test/typescript-project-references/src/core/tsconfig.tsbuildinfo create mode 100644 e2e/test/typescript-project-references/src/utils/math.ts create mode 100644 e2e/test/typescript-project-references/src/utils/text.ts create mode 100644 e2e/test/typescript-project-references/src/utils/tsconfig.json create mode 100644 e2e/test/typescript-project-references/src/utils/tsconfig.tsbuildinfo create mode 100644 e2e/test/typescript-project-references/stryker.conf.json create mode 100644 e2e/test/typescript-project-references/test/job.spec.ts create mode 100644 e2e/test/typescript-project-references/tsconfig.json create mode 100644 e2e/test/typescript-project-references/tsconfig.settings.json create mode 100644 e2e/test/typescript-project-references/verify/package.json create mode 100644 e2e/test/typescript-project-references/verify/verify.js diff --git a/e2e/test/typescript-project-references/package.json b/e2e/test/typescript-project-references/package.json new file mode 100644 index 0000000000..f287d3fc88 --- /dev/null +++ b/e2e/test/typescript-project-references/package.json @@ -0,0 +1,24 @@ +{ + "name": "typescript-project-references", + "version": "0.0.0", + "private": true, + "description": "A module to perform an integration test", + "main": "index.js", + "scripts": { + "clean": "rimraf dist", + "prebuild": "npm run clean", + "build": "tsc -b tsconfig.json", + "pretest:unit": "npm run build", + "test:unit": "mocha", + "pretest": "rimraf \"reports\" \"dist\" \"stryker.log\"", + "test": "stryker run", + "posttest": "mocha --no-config --no-package --timeout 0 verify/verify.js" + }, + "mocha": { + "spec": [ + "test/**/*.js" + ] + }, + "author": "", + "license": "ISC" +} diff --git a/e2e/test/typescript-project-references/src/core/index.ts b/e2e/test/typescript-project-references/src/core/index.ts new file mode 100644 index 0000000000..407ccaaa23 --- /dev/null +++ b/e2e/test/typescript-project-references/src/core/index.ts @@ -0,0 +1,5 @@ +import { count } from '../utils/math.js'; + +export function countArrayLength(todo: any[]): number { + return count(todo); +} diff --git a/e2e/test/typescript-project-references/src/core/job.ts b/e2e/test/typescript-project-references/src/core/job.ts new file mode 100644 index 0000000000..41bc38f3b9 --- /dev/null +++ b/e2e/test/typescript-project-references/src/core/job.ts @@ -0,0 +1,7 @@ +import { toUpperCase } from '../utils/text.js'; + +export function start(): string { + const logText = "Starting job"; + console.log(toUpperCase(logText)); + return logText; +} diff --git a/e2e/test/typescript-project-references/src/core/tsconfig.json b/e2e/test/typescript-project-references/src/core/tsconfig.json new file mode 100644 index 0000000000..d3f8804bed --- /dev/null +++ b/e2e/test/typescript-project-references/src/core/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.settings", + "compilerOptions": { + "outDir": "../dist/src" + }, + "references": [ + { "path": "../utils" } + ] +} diff --git a/e2e/test/typescript-project-references/src/core/tsconfig.tsbuildinfo b/e2e/test/typescript-project-references/src/core/tsconfig.tsbuildinfo new file mode 100644 index 0000000000..436aea16eb --- /dev/null +++ b/e2e/test/typescript-project-references/src/core/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"program":{"fileNames":["../../../../node_modules/typescript/lib/lib.d.ts","../../../../node_modules/typescript/lib/lib.es5.d.ts","../../../../node_modules/typescript/lib/lib.dom.d.ts","../../../../node_modules/typescript/lib/lib.webworker.importscripts.d.ts","../../../../node_modules/typescript/lib/lib.scripthost.d.ts","./index.ts","./job.ts"],"fileInfos":["2dc8c927c9c162a773c6bb3cdc4f3286c23f10eedc67414028f9cb5951610f60",{"version":"f20c05dbfe50a208301d2a1da37b9931bce0466eb5a1f4fe240971b4ecc82b67","affectsGlobalScope":true},{"version":"9b087de7268e4efc5f215347a62656663933d63c0b1d7b624913240367b999ea","affectsGlobalScope":true},{"version":"7fac8cb5fc820bc2a59ae11ef1c5b38d3832c6d0dfaec5acdb5569137d09a481","affectsGlobalScope":true},{"version":"097a57355ded99c68e6df1b738990448e0bf170e606707df5a7c0481ff2427cd","affectsGlobalScope":true},{"version":"c9b6bdd48b8bdb8d8e7690c7cc18897a494b6ab17dc58083dacfaf14b846ab4f","signature":"40b6409b8d0dced1f6c3964012b7a7c1cd50e24c3242095d1c8cfc6cabe8bd31"},{"version":"e4c28c497fe6cc6364b113c181c32ba58e70f02d824295e72b15d9570b403104","signature":"9be66c79f48b4876970daed5167e069d7f12f1a1ca616ecaa0ca8280946344ca"}],"options":{"composite":true,"declaration":true,"declarationMap":true,"module":1,"noUnusedLocals":true,"noUnusedParameters":true,"strict":true,"target":1},"referencedMap":[],"exportedModulesMap":[],"semanticDiagnosticsPerFile":[1,3,2,5,4],"changeFileSet":[6,7],"latestChangedDtsFile":"./job.d.ts"},"version":"4.8.4"} \ No newline at end of file diff --git a/e2e/test/typescript-project-references/src/utils/math.ts b/e2e/test/typescript-project-references/src/utils/math.ts new file mode 100644 index 0000000000..23cbcf7eda --- /dev/null +++ b/e2e/test/typescript-project-references/src/utils/math.ts @@ -0,0 +1,3 @@ +export function count(array: any[]) { + return array.length; +} diff --git a/e2e/test/typescript-project-references/src/utils/text.ts b/e2e/test/typescript-project-references/src/utils/text.ts new file mode 100644 index 0000000000..33d172474d --- /dev/null +++ b/e2e/test/typescript-project-references/src/utils/text.ts @@ -0,0 +1,3 @@ +export function toUpperCase(text: string) { + return text.toUpperCase(); +} diff --git a/e2e/test/typescript-project-references/src/utils/tsconfig.json b/e2e/test/typescript-project-references/src/utils/tsconfig.json new file mode 100644 index 0000000000..e3eb572925 --- /dev/null +++ b/e2e/test/typescript-project-references/src/utils/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.settings", + "compilerOptions": { + "outDir": "../dist/utils", + } +} diff --git a/e2e/test/typescript-project-references/src/utils/tsconfig.tsbuildinfo b/e2e/test/typescript-project-references/src/utils/tsconfig.tsbuildinfo new file mode 100644 index 0000000000..768ec50cbc --- /dev/null +++ b/e2e/test/typescript-project-references/src/utils/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"program":{"fileNames":["../../../../node_modules/typescript/lib/lib.d.ts","../../../../node_modules/typescript/lib/lib.es5.d.ts","../../../../node_modules/typescript/lib/lib.dom.d.ts","../../../../node_modules/typescript/lib/lib.webworker.importscripts.d.ts","../../../../node_modules/typescript/lib/lib.scripthost.d.ts","./math.ts","./text.ts"],"fileInfos":["2dc8c927c9c162a773c6bb3cdc4f3286c23f10eedc67414028f9cb5951610f60",{"version":"f20c05dbfe50a208301d2a1da37b9931bce0466eb5a1f4fe240971b4ecc82b67","affectsGlobalScope":true},{"version":"9b087de7268e4efc5f215347a62656663933d63c0b1d7b624913240367b999ea","affectsGlobalScope":true},{"version":"7fac8cb5fc820bc2a59ae11ef1c5b38d3832c6d0dfaec5acdb5569137d09a481","affectsGlobalScope":true},{"version":"097a57355ded99c68e6df1b738990448e0bf170e606707df5a7c0481ff2427cd","affectsGlobalScope":true},{"version":"6198e7d4a43aabb174a72ec9f0e8d2962912ad59ad90010aac3930868a8f62a4","signature":"0400cb85cef49e897c47df13e38b5cd199e0c900253f2d2ddf2e3491c27bc0a8"},{"version":"becd081df112726ab94c1ca1c05d6a59268fe0dabf7ad076d16ea851bf99e8fb","signature":"6039d94241358544e8d62a3a0ba90752a9973b3b2b422c187e2bcf7256fcda2e"}],"options":{"composite":true,"declaration":true,"declarationMap":true,"module":1,"noUnusedLocals":true,"noUnusedParameters":true,"strict":true,"target":1},"referencedMap":[],"exportedModulesMap":[],"semanticDiagnosticsPerFile":[6,7,1,3,2,5,4],"latestChangedDtsFile":"./text.d.ts"},"version":"4.8.4"} \ No newline at end of file diff --git a/e2e/test/typescript-project-references/stryker.conf.json b/e2e/test/typescript-project-references/stryker.conf.json new file mode 100644 index 0000000000..58b5d0879e --- /dev/null +++ b/e2e/test/typescript-project-references/stryker.conf.json @@ -0,0 +1,17 @@ +{ + "$schema": "../../node_modules/@stryker-mutator/core/schema/stryker-schema.json", + "packageManager": "npm", + "disableTypeChecks": true, + "testRunner": "mocha", + "concurrency": 1, + "coverageAnalysis": "perTest", + "reporters": ["json", "html", "progress", "clear-text"], + "checkers": ["typescript"], + "tsconfigFile": "src/core/tsconfig.json", + "fileLogLevel": "warn", + "buildCommand": "npm run build", + "plugins": [ + "@stryker-mutator/mocha-runner", + "@stryker-mutator/typescript-checker" + ] +} diff --git a/e2e/test/typescript-project-references/test/job.spec.ts b/e2e/test/typescript-project-references/test/job.spec.ts new file mode 100644 index 0000000000..d5d1b4d0f8 --- /dev/null +++ b/e2e/test/typescript-project-references/test/job.spec.ts @@ -0,0 +1,12 @@ +import { expect } from 'chai'; +import {start} from '../src/core/job'; + +describe(start.name, () => { + it('should format a correct message', () => { + // Act + const result = start(); + + // Assert + expect(result).eq("Starting job"); + }); +}); diff --git a/e2e/test/typescript-project-references/tsconfig.json b/e2e/test/typescript-project-references/tsconfig.json new file mode 100644 index 0000000000..0fd2bab04d --- /dev/null +++ b/e2e/test/typescript-project-references/tsconfig.json @@ -0,0 +1,3 @@ +{ + "include": ["src/core", "src/utils", "test"] +} diff --git a/e2e/test/typescript-project-references/tsconfig.settings.json b/e2e/test/typescript-project-references/tsconfig.settings.json new file mode 100644 index 0000000000..3bb8b4a435 --- /dev/null +++ b/e2e/test/typescript-project-references/tsconfig.settings.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "strict": true, + "target": "es5", + "moduleResolution": "node", + "module": "commonjs", + "composite": true, + "declaration": true, + "declarationMap": true, + + // These settings should be overridden by the typescript checker + "noUnusedLocals": true, + "noUnusedParameters": true, + + "types": [] + } +} diff --git a/e2e/test/typescript-project-references/verify/package.json b/e2e/test/typescript-project-references/verify/package.json new file mode 100644 index 0000000000..3dbc1ca591 --- /dev/null +++ b/e2e/test/typescript-project-references/verify/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/e2e/test/typescript-project-references/verify/verify.js b/e2e/test/typescript-project-references/verify/verify.js new file mode 100644 index 0000000000..fca3cfce0f --- /dev/null +++ b/e2e/test/typescript-project-references/verify/verify.js @@ -0,0 +1,7 @@ +import { expectMetricsJsonToMatchSnapshot } from '../../../helpers.js'; + +describe('Verify stryker has ran correctly', () => { + it('should report correct score', async () => { + await expectMetricsJsonToMatchSnapshot(); + }); +}); From 7bccbd63ee7843077b429283a1a6f61ba800c969 Mon Sep 17 00:00:00 2001 From: Danny Berkelaar Date: Fri, 10 Feb 2023 10:32:48 +0100 Subject: [PATCH 77/77] Added .snap for typescript-project-references --- .../verify/verify.js.snap | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 e2e/test/typescript-project-references/verify/verify.js.snap diff --git a/e2e/test/typescript-project-references/verify/verify.js.snap b/e2e/test/typescript-project-references/verify/verify.js.snap new file mode 100644 index 0000000000..11235cc953 --- /dev/null +++ b/e2e/test/typescript-project-references/verify/verify.js.snap @@ -0,0 +1,21 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Verify stryker has ran correctly should report correct score 1`] = ` +Object { + "compileErrors": 3, + "ignored": 0, + "killed": 1, + "mutationScore": 33.33333333333333, + "mutationScoreBasedOnCoveredCode": 33.33333333333333, + "noCoverage": 0, + "runtimeErrors": 0, + "survived": 2, + "timeout": 0, + "totalCovered": 3, + "totalDetected": 1, + "totalInvalid": 3, + "totalMutants": 6, + "totalUndetected": 2, + "totalValid": 3, +} +`;