@systemfsoftware/stryker-js-typescript-checker 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +15 -0
- package/.turbo/turbo-lint.log +3 -0
- package/LICENSE +21 -0
- package/dist/index.d.mts +122 -0
- package/dist/index.mjs +602 -0
- package/oxlint.config.ts +9 -0
- package/package.json +37 -0
- package/schema/typescript-checker-options.json +22 -0
- package/src/fs/hybrid-file-system.ts +118 -0
- package/src/fs/index.ts +2 -0
- package/src/fs/script-file.ts +44 -0
- package/src/grouping/create-groups.ts +77 -0
- package/src/grouping/ts-file-node.ts +54 -0
- package/src/index.ts +18 -0
- package/src/plugin-tokens.ts +2 -0
- package/src/tsconfig-helpers.ts +168 -0
- package/src/typescript-checker-options-with-stryker-options.ts +9 -0
- package/src/typescript-checker.ts +223 -0
- package/src/typescript-compiler.ts +406 -0
- package/test/integration/e2e-plugin-entry.it.spec.ts +153 -0
- package/test/integration/project-references.it.spec.ts +146 -0
- package/test/integration/project-with-ts-buildinfo.it.spec.ts +92 -0
- package/test/integration/single-project.it.spec.ts +263 -0
- package/test/integration/typescript-checkers-errors.it.spec.ts +87 -0
- package/test/unit/fs/hybrid-file-system.spec.ts +109 -0
- package/test/unit/grouping/create-groups.spec.ts +122 -0
- package/test/unit/grouping/ts-file-node.spec.ts +132 -0
- package/testResources/errors/compile-error/add.ts +3 -0
- package/testResources/errors/compile-error/tsconfig.json +6 -0
- package/testResources/errors/empty-dir/.gitkeep +0 -0
- package/testResources/errors/invalid-tsconfig/tsconfig.json +1 -0
- package/testResources/project-references/src/index.ts +5 -0
- package/testResources/project-references/src/job.ts +6 -0
- package/testResources/project-references/src/src.tsbuildinfo +1 -0
- package/testResources/project-references/src/tsconfig.json +10 -0
- package/testResources/project-references/tsconfig.root.json +7 -0
- package/testResources/project-references/tsconfig.settings.json +17 -0
- package/testResources/project-references/utils/math.ts +3 -0
- package/testResources/project-references/utils/text.ts +3 -0
- package/testResources/project-references/utils/tsconfig.json +7 -0
- package/testResources/project-references/utils/utils.tsbuildinfo +1 -0
- package/testResources/project-with-ts-buildinfo/do-not-delete.tsbuildinfo +1 -0
- package/testResources/project-with-ts-buildinfo/src/index.ts +3 -0
- package/testResources/project-with-ts-buildinfo/tsconfig.json +17 -0
- package/testResources/single-project/src/counter.ts +9 -0
- package/testResources/single-project/src/errorInFileAbove2Mutants/counter.ts +9 -0
- package/testResources/single-project/src/errorInFileAbove2Mutants/todo-counter.ts +7 -0
- package/testResources/single-project/src/errorInFileAbove2Mutants/todo.spec.ts +11 -0
- package/testResources/single-project/src/errorInFileAbove2Mutants/todo.ts +22 -0
- package/testResources/single-project/src/not-type-checked.js +1 -0
- package/testResources/single-project/src/todo.spec.ts +11 -0
- package/testResources/single-project/src/todo.ts +22 -0
- package/testResources/single-project/tsconfig.json +15 -0
- package/tsconfig.json +10 -0
- package/vitest.config.ts +11 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { readFileSync } from 'fs'
|
|
2
|
+
|
|
3
|
+
import type { FileSystem, FileSystemEntries } from 'typescript/unstable/fs'
|
|
4
|
+
|
|
5
|
+
import { toPosixFileName } from '../tsconfig-helpers.js'
|
|
6
|
+
|
|
7
|
+
import { ScriptFile } from './script-file.js'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A very simple hybrid file system.
|
|
11
|
+
* * Readonly from disk
|
|
12
|
+
* * Writes in-memory
|
|
13
|
+
* * Hard caching
|
|
14
|
+
* * Ability to mutate one file
|
|
15
|
+
*/
|
|
16
|
+
export class HybridFileSystem implements FileSystem {
|
|
17
|
+
private readonly files = new Map<string, ScriptFile | undefined>()
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Map of absolute tsconfig file paths to their adjusted JSON content.
|
|
21
|
+
* This allows the TS7 API to read overridden compiler options.
|
|
22
|
+
*/
|
|
23
|
+
public tsConfigOverrides = new Map<string, string>()
|
|
24
|
+
|
|
25
|
+
public readFile = (fileName: string): string | null | undefined => {
|
|
26
|
+
const normalized = toPosixFileName(fileName)
|
|
27
|
+
if (this.fileNameIsBuildInfo(normalized)) {
|
|
28
|
+
return null
|
|
29
|
+
}
|
|
30
|
+
const override = this.tsConfigOverrides.get(normalized)
|
|
31
|
+
if (override !== undefined) {
|
|
32
|
+
return override
|
|
33
|
+
}
|
|
34
|
+
const file = this.files.get(normalized)
|
|
35
|
+
if (file) {
|
|
36
|
+
return file.content
|
|
37
|
+
}
|
|
38
|
+
if (file === undefined && this.files.has(normalized)) {
|
|
39
|
+
// File was previously read and does not exist
|
|
40
|
+
return null
|
|
41
|
+
}
|
|
42
|
+
return undefined
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public fileExists = (fileName: string): boolean | undefined => {
|
|
46
|
+
const normalized = toPosixFileName(fileName)
|
|
47
|
+
if (this.fileNameIsBuildInfo(normalized)) {
|
|
48
|
+
return false
|
|
49
|
+
}
|
|
50
|
+
if (this.tsConfigOverrides.has(normalized)) {
|
|
51
|
+
return true
|
|
52
|
+
}
|
|
53
|
+
if (this.files.has(normalized)) {
|
|
54
|
+
return this.files.get(normalized) !== undefined
|
|
55
|
+
}
|
|
56
|
+
return undefined
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public directoryExists = (): boolean | undefined => {
|
|
60
|
+
return undefined
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public getAccessibleEntries = (): FileSystemEntries | undefined => {
|
|
64
|
+
return undefined
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public realpath = (): string | undefined => {
|
|
68
|
+
return undefined
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public writeFile(fileName: string, data: string): void {
|
|
72
|
+
const normalized = toPosixFileName(fileName)
|
|
73
|
+
const existingFile = this.files.get(normalized)
|
|
74
|
+
if (existingFile) {
|
|
75
|
+
existingFile.write(data)
|
|
76
|
+
} else {
|
|
77
|
+
this.files.set(normalized, new ScriptFile(data, normalized))
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
public getFile(fileName: string): ScriptFile | undefined {
|
|
82
|
+
const normalized = toPosixFileName(fileName)
|
|
83
|
+
if (!this.files.has(normalized)) {
|
|
84
|
+
try {
|
|
85
|
+
const content = readFileSync(normalized, 'utf-8')
|
|
86
|
+
this.files.set(normalized, new ScriptFile(content, normalized))
|
|
87
|
+
} catch {
|
|
88
|
+
this.files.set(normalized, undefined)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return this.files.get(normalized)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
public mutateFile(
|
|
95
|
+
fileName: string,
|
|
96
|
+
mutant: Parameters<ScriptFile['mutate']>[0],
|
|
97
|
+
): void {
|
|
98
|
+
const file = this.getFile(fileName)
|
|
99
|
+
if (!file) {
|
|
100
|
+
throw new Error(`Tried to mutate file "${fileName}" but it could not be found.`)
|
|
101
|
+
}
|
|
102
|
+
file.mutate(mutant)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
public resetFile(fileName: string): void {
|
|
106
|
+
const file = this.getFile(fileName)
|
|
107
|
+
file?.resetMutant()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
public existsInMemory(fileName: string): boolean {
|
|
111
|
+
const file = this.files.get(toPosixFileName(fileName))
|
|
112
|
+
return file !== undefined
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private fileNameIsBuildInfo(fileName: string): boolean {
|
|
116
|
+
return fileName.endsWith('.tsbuildinfo')
|
|
117
|
+
}
|
|
118
|
+
}
|
package/src/fs/index.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Mutant, Position } from '@stryker-mutator/api/core'
|
|
2
|
+
|
|
3
|
+
export class ScriptFile {
|
|
4
|
+
private readonly originalContent: string
|
|
5
|
+
|
|
6
|
+
constructor(
|
|
7
|
+
public content: string,
|
|
8
|
+
public fileName: string,
|
|
9
|
+
public modifiedTime = new Date(),
|
|
10
|
+
) {
|
|
11
|
+
this.originalContent = content
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public write(content: string): void {
|
|
15
|
+
this.content = content
|
|
16
|
+
this.touch()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public mutate(mutant: Pick<Mutant, 'location' | 'replacement'>): void {
|
|
20
|
+
const start = this.getOffset(mutant.location.start)
|
|
21
|
+
const end = this.getOffset(mutant.location.end)
|
|
22
|
+
this.content = `${this.originalContent.slice(0, start)}${mutant.replacement}${this.originalContent.slice(end)}`
|
|
23
|
+
this.touch()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private getOffset(pos: Position): number {
|
|
27
|
+
const lines = this.originalContent.split('\n')
|
|
28
|
+
let offset = 0
|
|
29
|
+
for (let i = 0; i < pos.line && i < lines.length; i++) {
|
|
30
|
+
offset += lines[i]!.length + 1 // +1 for the newline character
|
|
31
|
+
}
|
|
32
|
+
offset += pos.column
|
|
33
|
+
return offset
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public resetMutant(): void {
|
|
37
|
+
this.content = this.originalContent
|
|
38
|
+
this.touch()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private touch(): void {
|
|
42
|
+
this.modifiedTime = new Date()
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { Mutant } from '@stryker-mutator/api/core'
|
|
2
|
+
|
|
3
|
+
import { toPosixFileName } from '../tsconfig-helpers.js'
|
|
4
|
+
|
|
5
|
+
import { TSFileNode } from './ts-file-node.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* To speed up the type-checking we want to check multiple mutants at once.
|
|
9
|
+
* When multiple mutants in different files don't have overlap in affected files (or have small overlap), we can type-check them simultaneously.
|
|
10
|
+
* These mutants who can be tested at the same time are called a group.
|
|
11
|
+
* Therefore, the return type is an array of arrays, in other words: an array of groups.
|
|
12
|
+
*
|
|
13
|
+
* @param mutants All the mutants of the test project.
|
|
14
|
+
* @param nodes A graph representation of the test project.
|
|
15
|
+
*/
|
|
16
|
+
export function createGroups(
|
|
17
|
+
mutants: Mutant[],
|
|
18
|
+
nodes: Map<string, TSFileNode>,
|
|
19
|
+
): string[][] {
|
|
20
|
+
const groups: string[][] = []
|
|
21
|
+
const mutantsToGroup = new Set(mutants)
|
|
22
|
+
|
|
23
|
+
while (mutantsToGroup.size) {
|
|
24
|
+
const group: string[] = []
|
|
25
|
+
const groupNodes = new Set<TSFileNode>()
|
|
26
|
+
const nodesToIgnore = new Set<TSFileNode>()
|
|
27
|
+
|
|
28
|
+
for (const currentMutant of mutantsToGroup) {
|
|
29
|
+
const currentNode = findNode(currentMutant.fileName, nodes)
|
|
30
|
+
if (
|
|
31
|
+
!nodesToIgnore.has(currentNode) &&
|
|
32
|
+
!parentsHaveOverlapWith(currentNode, groupNodes)
|
|
33
|
+
) {
|
|
34
|
+
group.push(currentMutant.id)
|
|
35
|
+
groupNodes.add(currentNode)
|
|
36
|
+
mutantsToGroup.delete(currentMutant)
|
|
37
|
+
addRangeOfNodesToSet(
|
|
38
|
+
nodesToIgnore,
|
|
39
|
+
currentNode.getAllParentReferencesIncludingSelf(),
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
groups.push(group)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return groups
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function addRangeOfNodesToSet(
|
|
50
|
+
nodes: Set<TSFileNode>,
|
|
51
|
+
nodesToAdd: Iterable<TSFileNode>,
|
|
52
|
+
) {
|
|
53
|
+
for (const parent of nodesToAdd) {
|
|
54
|
+
nodes.add(parent)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function findNode(fileName: string, nodes: Map<string, TSFileNode>) {
|
|
59
|
+
const node = nodes.get(toPosixFileName(fileName))
|
|
60
|
+
if (node == null) {
|
|
61
|
+
throw new Error(`Node not in graph: ${fileName}`)
|
|
62
|
+
}
|
|
63
|
+
return node
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function parentsHaveOverlapWith(
|
|
67
|
+
currentNode: TSFileNode,
|
|
68
|
+
groupNodes: Set<TSFileNode>,
|
|
69
|
+
) {
|
|
70
|
+
for (const parentNode of currentNode.getAllParentReferencesIncludingSelf()) {
|
|
71
|
+
if (groupNodes.has(parentNode)) {
|
|
72
|
+
return true
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return false
|
|
77
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { Mutant } from '@stryker-mutator/api/core'
|
|
2
|
+
|
|
3
|
+
import { toPosixFileName } from '../tsconfig-helpers.js'
|
|
4
|
+
|
|
5
|
+
// This class exists so we can have a two-way dependency graph.
|
|
6
|
+
// The two-way dependency graph is used to search for mutants related to typescript errors.
|
|
7
|
+
export class TSFileNode {
|
|
8
|
+
constructor(
|
|
9
|
+
public fileName: string,
|
|
10
|
+
public parents: TSFileNode[],
|
|
11
|
+
public children: TSFileNode[],
|
|
12
|
+
) {}
|
|
13
|
+
|
|
14
|
+
public getAllParentReferencesIncludingSelf(
|
|
15
|
+
allParentReferences: Set<TSFileNode> = new Set<TSFileNode>(),
|
|
16
|
+
): Set<TSFileNode> {
|
|
17
|
+
allParentReferences.add(this)
|
|
18
|
+
this.parents.forEach((parent) => {
|
|
19
|
+
if (!allParentReferences.has(parent)) {
|
|
20
|
+
parent.getAllParentReferencesIncludingSelf(allParentReferences)
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
return allParentReferences
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public getAllChildReferencesIncludingSelf(
|
|
27
|
+
allChildReferences: Set<TSFileNode> = new Set<TSFileNode>(),
|
|
28
|
+
): Set<TSFileNode> {
|
|
29
|
+
allChildReferences.add(this)
|
|
30
|
+
this.children.forEach((child) => {
|
|
31
|
+
if (!allChildReferences.has(child)) {
|
|
32
|
+
child.getAllChildReferencesIncludingSelf(allChildReferences)
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
return allChildReferences
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public getMutantsWithReferenceToChildrenOrSelf(
|
|
39
|
+
mutants: Mutant[],
|
|
40
|
+
nodesChecked: string[] = [],
|
|
41
|
+
): Mutant[] {
|
|
42
|
+
if (nodesChecked.includes(this.fileName)) {
|
|
43
|
+
return []
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
nodesChecked.push(this.fileName)
|
|
47
|
+
|
|
48
|
+
const relatedMutants = mutants.filter(
|
|
49
|
+
(m) => toPosixFileName(m.fileName) === this.fileName,
|
|
50
|
+
)
|
|
51
|
+
const childResult = this.children.flatMap((c) => c.getMutantsWithReferenceToChildrenOrSelf(mutants, nodesChecked))
|
|
52
|
+
return [...relatedMutants, ...childResult]
|
|
53
|
+
}
|
|
54
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { readFileSync } from 'fs'
|
|
2
|
+
|
|
3
|
+
import { declareFactoryPlugin, PluginKind } from '@stryker-mutator/api/plugin'
|
|
4
|
+
|
|
5
|
+
import { create } from './typescript-checker.js'
|
|
6
|
+
|
|
7
|
+
export const strykerPlugins = [
|
|
8
|
+
declareFactoryPlugin(PluginKind.Checker, 'typescript', create),
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
export const createTypescriptChecker = create
|
|
12
|
+
|
|
13
|
+
export const strykerValidationSchema: Record<string, unknown> = JSON.parse(
|
|
14
|
+
readFileSync(
|
|
15
|
+
new URL('../schema/typescript-checker-options.json', import.meta.url),
|
|
16
|
+
'utf-8',
|
|
17
|
+
),
|
|
18
|
+
)
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { readFileSync } from 'fs'
|
|
2
|
+
import { createRequire } from 'module'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
|
|
5
|
+
import semver from 'semver'
|
|
6
|
+
|
|
7
|
+
// Override some compiler options that have to do with code quality. When mutating, we're not interested in the resulting code quality
|
|
8
|
+
// See https://github.com/stryker-mutator/stryker-js/issues/391 for more info
|
|
9
|
+
const COMPILER_OPTIONS_OVERRIDES: Readonly<Record<string, unknown>> = Object.freeze({
|
|
10
|
+
allowUnreachableCode: true,
|
|
11
|
+
noUnusedLocals: false,
|
|
12
|
+
noUnusedParameters: false,
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
// When we're running in 'single-project' mode, we can safely disable emit
|
|
16
|
+
const NO_EMIT_OPTIONS_FOR_SINGLE_PROJECT: Readonly<Record<string, unknown>> = Object.freeze({
|
|
17
|
+
noEmit: true,
|
|
18
|
+
incremental: false, // incremental and composite off: https://github.com/microsoft/TypeScript/issues/36917
|
|
19
|
+
tsBuildInfoFile: undefined,
|
|
20
|
+
composite: false,
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
// When we're running in 'project references' mode, we need to enable declaration output
|
|
24
|
+
const LOW_EMIT_OPTIONS_FOR_PROJECT_REFERENCES: Readonly<Record<string, unknown>> = Object.freeze({
|
|
25
|
+
emitDeclarationOnly: true,
|
|
26
|
+
noEmit: false,
|
|
27
|
+
declarationMap: true,
|
|
28
|
+
declaration: true,
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
let cachedTSVersion: string | undefined
|
|
32
|
+
|
|
33
|
+
export function getTSVersion(): string {
|
|
34
|
+
if (cachedTSVersion === undefined) {
|
|
35
|
+
const require = createRequire(import.meta.url)
|
|
36
|
+
const pkg = JSON.parse(
|
|
37
|
+
readFileSync(require.resolve('typescript/package.json'), 'utf-8'),
|
|
38
|
+
) as { version: string }
|
|
39
|
+
cachedTSVersion = pkg.version
|
|
40
|
+
}
|
|
41
|
+
return cachedTSVersion
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function guardTSVersion(version = getTSVersion()): void {
|
|
45
|
+
if (!semver.satisfies(version, '>=7.0.0', { includePrerelease: true })) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`@systemfsoftware/stryker-js-typescript-checker only supports typescript@7.0.0 or higher. Found typescript@${version}`,
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Determines whether or not to use `--build` mode based on "references" being there in the config file
|
|
54
|
+
* @param tsconfigFileName The tsconfig file to parse
|
|
55
|
+
*/
|
|
56
|
+
export interface ParsedConfig {
|
|
57
|
+
config?: unknown
|
|
58
|
+
error?: Error
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function stripJsonComments(json: string): string {
|
|
62
|
+
return json
|
|
63
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
64
|
+
.replace(/\/\/.*$/gm, '')
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function parseConfigFileTextToJson(fileName: string, jsonText: string): ParsedConfig {
|
|
68
|
+
try {
|
|
69
|
+
const stripped = stripJsonComments(jsonText)
|
|
70
|
+
return { config: JSON.parse(stripped) }
|
|
71
|
+
} catch (error) {
|
|
72
|
+
return { error: error as Error }
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function determineBuildModeEnabled(tsconfigFileName: string): boolean {
|
|
77
|
+
const tsconfigFile = readFileSync(tsconfigFileName, 'utf-8')
|
|
78
|
+
const parsed = parseConfigFileTextToJson(tsconfigFileName, tsconfigFile)
|
|
79
|
+
if (parsed.error) {
|
|
80
|
+
return false
|
|
81
|
+
}
|
|
82
|
+
const useProjectReferences = 'references' in (parsed.config as { references?: unknown[] })
|
|
83
|
+
return useProjectReferences
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Overrides some options to speed up compilation and disable some code quality checks we don't want during mutation testing
|
|
88
|
+
* @param parsedConfig The parsed config file
|
|
89
|
+
* @param useBuildMode whether or not `--build` mode is used
|
|
90
|
+
*/
|
|
91
|
+
export function overrideOptions(parsedConfig: ParsedConfig, useBuildMode: boolean): string {
|
|
92
|
+
const config = (parsedConfig.config ?? {}) as { compilerOptions?: Record<string, unknown> }
|
|
93
|
+
const compilerOptions: Record<string, unknown> = {
|
|
94
|
+
...config.compilerOptions,
|
|
95
|
+
...COMPILER_OPTIONS_OVERRIDES,
|
|
96
|
+
...(useBuildMode ? LOW_EMIT_OPTIONS_FOR_PROJECT_REFERENCES : NO_EMIT_OPTIONS_FOR_SINGLE_PROJECT),
|
|
97
|
+
// TypeScript 7 removed some legacy defaults that the upstream fixtures still use.
|
|
98
|
+
target: 'es2022',
|
|
99
|
+
moduleResolution: 'bundler',
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (
|
|
103
|
+
!useBuildMode &&
|
|
104
|
+
compilerOptions['declarationDir'] !== undefined &&
|
|
105
|
+
compilerOptions['declarationDir'] !== null
|
|
106
|
+
) {
|
|
107
|
+
// because composite and/or declaration was disabled in non-build mode, we have to disable declarationDir as well
|
|
108
|
+
// otherwise, error TS5069: Option 'declarationDir' cannot be specified without specifying option 'declaration' or option 'composite'.
|
|
109
|
+
delete compilerOptions['declarationDir']
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (useBuildMode) {
|
|
113
|
+
// Remove the options to place declarations files in different locations to decrease the complexity of searching the source file in the TypescriptCompiler class.
|
|
114
|
+
delete compilerOptions['inlineSourceMap']
|
|
115
|
+
delete compilerOptions['inlineSources']
|
|
116
|
+
delete compilerOptions['mapRoute']
|
|
117
|
+
delete compilerOptions['sourceRoot']
|
|
118
|
+
delete compilerOptions['outFile']
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return JSON.stringify({
|
|
122
|
+
...config,
|
|
123
|
+
compilerOptions,
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
interface ProjectReference {
|
|
128
|
+
path: string
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Retrieves the referenced config files based on parsed configuration
|
|
133
|
+
* @param parsedConfig The parsed config file
|
|
134
|
+
* @param fromDirName The directory where to resolve from
|
|
135
|
+
*/
|
|
136
|
+
export function retrieveReferencedProjects(parsedConfig: ParsedConfig, fromDirName: string): string[] {
|
|
137
|
+
const config = parsedConfig.config as { references?: ProjectReference[] } | undefined
|
|
138
|
+
if (Array.isArray(config?.references)) {
|
|
139
|
+
return config!.references.map((reference) => {
|
|
140
|
+
let resolved = path.resolve(fromDirName, reference.path)
|
|
141
|
+
if (!path.basename(resolved).endsWith('.json')) {
|
|
142
|
+
resolved = path.join(resolved, 'tsconfig.json')
|
|
143
|
+
}
|
|
144
|
+
return toPosixFileName(resolved)
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
return []
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Replaces backslashes with forward slashes (used by typescript)
|
|
152
|
+
* @param fileName The file name that may contain backslashes `\`
|
|
153
|
+
* @returns posix and ts complaint file name (with `/`)
|
|
154
|
+
*/
|
|
155
|
+
export function toPosixFileName(fileName: string): string {
|
|
156
|
+
return fileName.replace(/\\/g, '/')
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Find source file in declaration file
|
|
161
|
+
* @param content The content of the declaration file
|
|
162
|
+
* @returns URL of the source file or undefined if not found
|
|
163
|
+
*/
|
|
164
|
+
const findSourceMapRegex = /\/\/# sourceMappingURL=(.+)$/m
|
|
165
|
+
export function getSourceMappingURL(content: string): string | undefined {
|
|
166
|
+
findSourceMapRegex.lastIndex = 0
|
|
167
|
+
return findSourceMapRegex.exec(content)?.[1]
|
|
168
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { StrykerOptions } from '@stryker-mutator/api/core'
|
|
2
|
+
|
|
3
|
+
export interface TypescriptCheckerPluginOptions {
|
|
4
|
+
typescriptChecker: {
|
|
5
|
+
prioritizePerformanceOverAccuracy?: boolean
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface TypescriptCheckerOptionsWithStrykerOptions extends TypescriptCheckerPluginOptions, StrykerOptions {}
|