fallow-type-aware 3.34.1 → 3.36.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/package.json +10 -1
- package/src/backend-preflight.mjs +33 -4
- package/src/backend-selection.mjs +18 -0
- package/src/backend.mjs +169 -0
- package/src/computed-member-access.mjs +45 -0
- package/src/generated-protocol.mjs +12 -0
- package/src/mutant-typecheck.mjs +13 -10
- package/src/namespace-uses.mjs +499 -0
- package/src/project-package.mjs +37 -0
- package/src/project-state.mjs +12 -10
- package/src/protocol.mjs +7 -16
- package/src/reference-names.mjs +84 -0
- package/src/semantic-identity.mjs +7 -5
- package/src/semantic.mjs +94 -197
- package/src/shorthand-references.mjs +243 -0
- package/src/symbol-impact.mjs +3 -6
- package/src/symbol-targets.mjs +74 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fallow-type-aware",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.36.0",
|
|
4
4
|
"description": "Optional TypeScript-Go semantic refinement sidecar for Fallow",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -23,6 +23,14 @@
|
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"typescript": "7.0.2"
|
|
25
25
|
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"tsrs": "0.18.0-ts7.1.0-dev.20260929"
|
|
28
|
+
},
|
|
29
|
+
"peerDependenciesMeta": {
|
|
30
|
+
"tsrs": {
|
|
31
|
+
"optional": true
|
|
32
|
+
}
|
|
33
|
+
},
|
|
26
34
|
"engines": {
|
|
27
35
|
"node": ">=20"
|
|
28
36
|
},
|
|
@@ -33,6 +41,7 @@
|
|
|
33
41
|
"devDependencies": {
|
|
34
42
|
"@codspeed/tinybench-plugin": "5.7.1",
|
|
35
43
|
"tinybench": "6.1.2",
|
|
44
|
+
"tsrs": "0.18.0-ts7.1.0-dev.20260929",
|
|
36
45
|
"zod": "4.4.3"
|
|
37
46
|
}
|
|
38
47
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { selectBackend } from "./backend-selection.mjs";
|
|
5
|
+
import { BACKEND_VERSION, BACKEND_FAMILY } from "./generated-protocol.mjs";
|
|
5
6
|
|
|
6
7
|
const REQUIRED_MAJOR = Number.parseInt(BACKEND_VERSION, 10);
|
|
7
8
|
const INSTALL_HINT =
|
|
@@ -9,7 +10,7 @@ const INSTALL_HINT =
|
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Verify that module resolution hands the sidecar a typescript install that
|
|
12
|
-
* provides the `typescript
|
|
13
|
+
* provides the `typescript` sync API backend entry point.
|
|
13
14
|
*
|
|
14
15
|
* The semantic modules import that entry point statically, so an incompatible
|
|
15
16
|
* resolution (for example a typescript 6 hoisted into an ancestor
|
|
@@ -18,11 +19,23 @@ const INSTALL_HINT =
|
|
|
18
19
|
* and names the resolved version, its location, and the fix. Exact
|
|
19
20
|
* backend-version parity stays enforced in `protocol.mjs`.
|
|
20
21
|
*
|
|
22
|
+
* A backend that `FALLOW_TYPE_AWARE_BACKEND` selects instead of the default
|
|
23
|
+
* must resolve at its exact pinned version.
|
|
24
|
+
*
|
|
21
25
|
* @param {string | URL} resolveFrom Module URL or file path to resolve from;
|
|
22
26
|
* defaults to this module so the check mirrors the semantic imports.
|
|
27
|
+
* @param {NodeJS.ProcessEnv} env Environment that selects the backend.
|
|
23
28
|
*/
|
|
24
|
-
export const assertTypescriptBackendResolvable = (
|
|
29
|
+
export const assertTypescriptBackendResolvable = (
|
|
30
|
+
resolveFrom = import.meta.url,
|
|
31
|
+
env = process.env,
|
|
32
|
+
) => {
|
|
33
|
+
const backend = selectBackend(env);
|
|
25
34
|
const sidecarRequire = createRequire(resolveFrom);
|
|
35
|
+
if (backend.family !== BACKEND_FAMILY) {
|
|
36
|
+
assertPinnedBackendResolvable(sidecarRequire, backend);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
26
39
|
let manifestPath;
|
|
27
40
|
try {
|
|
28
41
|
manifestPath = sidecarRequire.resolve("typescript/package.json");
|
|
@@ -36,7 +49,23 @@ export const assertTypescriptBackendResolvable = (resolveFrom = import.meta.url)
|
|
|
36
49
|
}
|
|
37
50
|
throw new Error(
|
|
38
51
|
`resolved typescript ${version} from ${path.dirname(manifestPath)}, which lacks the ` +
|
|
39
|
-
`typescript
|
|
52
|
+
`typescript sync API backend; fallow-type-aware needs typescript ${BACKEND_VERSION}; ` +
|
|
40
53
|
INSTALL_HINT,
|
|
41
54
|
);
|
|
42
55
|
};
|
|
56
|
+
|
|
57
|
+
const assertPinnedBackendResolvable = (sidecarRequire, backend) => {
|
|
58
|
+
const install = `install ${backend.package}@${backend.version} where fallow-type-aware can resolve it`;
|
|
59
|
+
let manifestPath;
|
|
60
|
+
try {
|
|
61
|
+
manifestPath = sidecarRequire.resolve(`${backend.package}/package.json`);
|
|
62
|
+
} catch {
|
|
63
|
+
throw new Error(`the ${backend.family} backend is not installed; ${install}`);
|
|
64
|
+
}
|
|
65
|
+
const { version } = sidecarRequire(manifestPath);
|
|
66
|
+
if (version === backend.version) return;
|
|
67
|
+
throw new Error(
|
|
68
|
+
`resolved ${backend.package} ${version} from ${path.dirname(manifestPath)}; ` +
|
|
69
|
+
`the ${backend.family} backend needs exactly ${backend.version}; ${install}`,
|
|
70
|
+
);
|
|
71
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { BACKENDS, BACKEND_FAMILY } from "./generated-protocol.mjs";
|
|
2
|
+
|
|
3
|
+
const BACKEND_ENV = "FALLOW_TYPE_AWARE_BACKEND";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Resolve the backend that `FALLOW_TYPE_AWARE_BACKEND` selects. An unset or
|
|
7
|
+
* empty value selects the default backend.
|
|
8
|
+
*
|
|
9
|
+
* @param {NodeJS.ProcessEnv} env
|
|
10
|
+
* @returns {{ family: string, package: string, version: string }}
|
|
11
|
+
*/
|
|
12
|
+
export const selectBackend = (env = process.env) => {
|
|
13
|
+
const requested = env[BACKEND_ENV]?.trim() || BACKEND_FAMILY;
|
|
14
|
+
const backend = BACKENDS.find((candidate) => candidate.family === requested);
|
|
15
|
+
if (backend) return backend;
|
|
16
|
+
const known = BACKENDS.map((candidate) => candidate.family).join(", ");
|
|
17
|
+
throw new Error(`unknown ${BACKEND_ENV} value \`${requested}\`; expected one of: ${known}`);
|
|
18
|
+
};
|
package/src/backend.mjs
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import { selectBackend } from "./backend-selection.mjs";
|
|
5
|
+
|
|
6
|
+
export const BACKEND = selectBackend();
|
|
7
|
+
|
|
8
|
+
const { version: resolvedVersion } = await import(BACKEND.package);
|
|
9
|
+
// TypeScript 7.1 serves the API at `typescript/sync`. TypeScript 7.0 and its
|
|
10
|
+
// 7.1 dev builds serve it at `typescript/unstable/sync`.
|
|
11
|
+
const importApi = async (subpath) => {
|
|
12
|
+
try {
|
|
13
|
+
return await import(`${BACKEND.package}/${subpath}`);
|
|
14
|
+
} catch (error) {
|
|
15
|
+
if (error?.code !== "ERR_PACKAGE_PATH_NOT_EXPORTED") throw error;
|
|
16
|
+
return import(`${BACKEND.package}/unstable/${subpath}`);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
export const sync = await importApi("sync");
|
|
20
|
+
export const ast = await importApi("ast/is");
|
|
21
|
+
export const { SyntaxKind } = await importApi("ast");
|
|
22
|
+
const { serverFS } = await importApi("fs");
|
|
23
|
+
|
|
24
|
+
if (resolvedVersion !== BACKEND.version) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
`${BACKEND.package} backend version ${resolvedVersion} does not match protocol ${BACKEND.version}`,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The value a `readFile` callback returns to read the file from disk.
|
|
32
|
+
* TypeScript 7.0 reads from disk on `undefined`. TypeScript 7.1 treats
|
|
33
|
+
* `undefined` as a missing file and needs the `serverFS.useOS` sentinel.
|
|
34
|
+
*/
|
|
35
|
+
export const READ_FROM_DISK = serverFS?.useOS;
|
|
36
|
+
|
|
37
|
+
const FS_CALLBACK_DEFAULTS = serverFS
|
|
38
|
+
? {
|
|
39
|
+
readFile: serverFS.useOS,
|
|
40
|
+
fileExists: serverFS.useOS,
|
|
41
|
+
directoryExists: serverFS.useOS,
|
|
42
|
+
getAccessibleEntries: serverFS.useOS,
|
|
43
|
+
realpath: serverFS.useOS,
|
|
44
|
+
stat: serverFS.useOS,
|
|
45
|
+
writeFile: serverFS.useOS,
|
|
46
|
+
removeFile: serverFS.useOS,
|
|
47
|
+
}
|
|
48
|
+
: {};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* TypeScript 7.0 finds a project by its config file name. TypeScript 7.1 keys
|
|
52
|
+
* `getProject` by project id and finds a config file through
|
|
53
|
+
* `getConfiguredProject`.
|
|
54
|
+
*/
|
|
55
|
+
const getProject = (snapshot, name) =>
|
|
56
|
+
snapshot.getProject(name) ?? snapshot.getConfiguredProject(name);
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Give a TypeScript 7.1 API the TypeScript 7.0 `updateSnapshot(changes)`
|
|
60
|
+
* contract: each call applies `changes` to the newest snapshot and returns
|
|
61
|
+
* current programs.
|
|
62
|
+
*
|
|
63
|
+
* - 7.0 `fileChanges` become 7.1 `fileNotifications`, and `ensurePrograms`
|
|
64
|
+
* rebuilds the programs that those notifications make dirty.
|
|
65
|
+
* - 7.1 derives a snapshot from a live base snapshot, so a snapshot that the
|
|
66
|
+
* caller disposes stays alive while it is the newest one. The caller's
|
|
67
|
+
* handle still acts as disposed, as it does on 7.0.
|
|
68
|
+
* - 7.1 rejects the whole update when an `openFiles` entry does not exist.
|
|
69
|
+
* 7.0 ignores that file, so the wrapper drops missing files first.
|
|
70
|
+
*/
|
|
71
|
+
const withLinearSnapshots = (api) => {
|
|
72
|
+
const entries = new Set();
|
|
73
|
+
let head;
|
|
74
|
+
const disposeWhenUnused = (entry) => {
|
|
75
|
+
if (entry === head || entry.open > 0) return;
|
|
76
|
+
entries.delete(entry);
|
|
77
|
+
if (!entry.snapshot.isDisposed()) entry.snapshot.dispose();
|
|
78
|
+
};
|
|
79
|
+
const handle = (entry) => {
|
|
80
|
+
entry.open += 1;
|
|
81
|
+
let released = false;
|
|
82
|
+
const dispose = () => {
|
|
83
|
+
if (released) return;
|
|
84
|
+
released = true;
|
|
85
|
+
entry.open -= 1;
|
|
86
|
+
disposeWhenUnused(entry);
|
|
87
|
+
};
|
|
88
|
+
return new Proxy(entry.snapshot, {
|
|
89
|
+
get(target, property) {
|
|
90
|
+
if (property === "dispose" || property === Symbol.dispose) return dispose;
|
|
91
|
+
if (property === "isDisposed") return () => released || target.isDisposed();
|
|
92
|
+
if (released) throw new Error("Snapshot is disposed");
|
|
93
|
+
if (property === "getProject") return getProject.bind(undefined, target);
|
|
94
|
+
const value = Reflect.get(target, property, target);
|
|
95
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
const updateSnapshot = ({ fileChanges, openFiles, ...rest } = {}) => {
|
|
100
|
+
const changes = {
|
|
101
|
+
...rest,
|
|
102
|
+
...(openFiles ? { openFiles: openFiles.filter((file) => existsSync(file)) } : {}),
|
|
103
|
+
...(fileChanges ? { fileNotifications: fileChanges } : {}),
|
|
104
|
+
ensurePrograms: true,
|
|
105
|
+
};
|
|
106
|
+
const previous = head;
|
|
107
|
+
head = {
|
|
108
|
+
snapshot: previous ? previous.snapshot.update(changes) : api.createSnapshot(changes),
|
|
109
|
+
open: 0,
|
|
110
|
+
};
|
|
111
|
+
entries.add(head);
|
|
112
|
+
if (previous) disposeWhenUnused(previous);
|
|
113
|
+
return handle(head);
|
|
114
|
+
};
|
|
115
|
+
const close = () => {
|
|
116
|
+
for (const entry of entries) {
|
|
117
|
+
if (!entry.snapshot.isDisposed()) entry.snapshot.dispose();
|
|
118
|
+
}
|
|
119
|
+
entries.clear();
|
|
120
|
+
head = undefined;
|
|
121
|
+
api.close();
|
|
122
|
+
};
|
|
123
|
+
return new Proxy(api, {
|
|
124
|
+
get(target, property) {
|
|
125
|
+
if (property === "updateSnapshot") return updateSnapshot;
|
|
126
|
+
if (property === "close") return close;
|
|
127
|
+
const value = Reflect.get(target, property, target);
|
|
128
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Create a backend API with the TypeScript 7.0 snapshot contract.
|
|
135
|
+
*
|
|
136
|
+
* @param {{ cwd?: string, fs?: Record<string, unknown> }} options
|
|
137
|
+
*/
|
|
138
|
+
export const createApi = (options = {}) => {
|
|
139
|
+
const fs = options.fs ? { ...FS_CALLBACK_DEFAULTS, ...options.fs } : undefined;
|
|
140
|
+
const api = new sync.API({ ...options, ...(fs ? { fs } : {}) });
|
|
141
|
+
return typeof api.createSnapshot === "function" ? withLinearSnapshots(api) : api;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const INFERRED_PROJECT_ID = "/dev/null/inferred";
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Whether a project is the inferred project. Both versions give it the id
|
|
148
|
+
* `/dev/null/inferred`. Only 7.0 also uses that id as its config file name.
|
|
149
|
+
*/
|
|
150
|
+
export const isInferredProject = (project) =>
|
|
151
|
+
project.id.split(path.sep).join("/") === INFERRED_PROJECT_ID;
|
|
152
|
+
|
|
153
|
+
/** Config file name with the 7.0 value `/dev/null/inferred` for the inferred project. */
|
|
154
|
+
export const projectConfigFileName = (project) =>
|
|
155
|
+
isInferredProject(project) ? INFERRED_PROJECT_ID : project.configFileName;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Name node of a heritage clause type. TypeScript 7.0 wraps it in an
|
|
159
|
+
* ExpressionWithTypeArguments (`expression`). TypeScript 7.1 uses a
|
|
160
|
+
* TypeReference (`typeName`) in `implements` and interface `extends` clauses.
|
|
161
|
+
*/
|
|
162
|
+
export const heritageTypeName = (heritageType) => heritageType.expression ?? heritageType.typeName;
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Compiler options of a project. TypeScript 7.1 beta moves them from
|
|
166
|
+
* `compilerOptions` to `parsedCommandLine.options`.
|
|
167
|
+
*/
|
|
168
|
+
export const projectCompilerOptions = (project) =>
|
|
169
|
+
project.compilerOptions ?? project.parsedCommandLine?.options ?? {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ast } from "./backend.mjs";
|
|
2
|
+
import { patternOfKey, patternSourceType } from "./shorthand-references.mjs";
|
|
3
|
+
import { matchingSymbolEntries, recordSymbolUncertainty } from "./symbol-targets.mjs";
|
|
4
|
+
|
|
5
|
+
const { isComputedPropertyName, isElementAccessExpression, isStringLiteralLikeNode } = ast;
|
|
6
|
+
|
|
7
|
+
const isComputedAccess = (node) =>
|
|
8
|
+
isElementAccessExpression(node) &&
|
|
9
|
+
node.argumentExpression !== undefined &&
|
|
10
|
+
!isStringLiteralLikeNode(node.argumentExpression);
|
|
11
|
+
|
|
12
|
+
const isComputedKey = (node) =>
|
|
13
|
+
isComputedPropertyName(node) && !isStringLiteralLikeNode(node.expression);
|
|
14
|
+
|
|
15
|
+
// `object[key]` and `const { [key]: value } = object` both read a member of
|
|
16
|
+
// `object` that the checker cannot name. A null type is an unknown receiver.
|
|
17
|
+
const computedReceiverType = (project, node) => {
|
|
18
|
+
if (isComputedAccess(node)) return project.checker.getTypeAtLocation(node.expression);
|
|
19
|
+
if (!isComputedKey(node)) return undefined;
|
|
20
|
+
const pattern = patternOfKey(node.expression);
|
|
21
|
+
if (!pattern) return undefined;
|
|
22
|
+
return patternSourceType(project, pattern) ?? null;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Records a `dynamic-member-access` uncertainty for the class members that a
|
|
27
|
+
* computed member access or a computed pattern key can read.
|
|
28
|
+
*/
|
|
29
|
+
export const recordComputedMemberAccess = (state, project, entries, node) => {
|
|
30
|
+
if (entries.size === 0 || state.projectsWithUnknownDynamicAccess.has(project)) return;
|
|
31
|
+
const receiverType = computedReceiverType(project, node);
|
|
32
|
+
if (receiverType === undefined) return;
|
|
33
|
+
const receiverProperties = receiverType ? project.checker.getPropertiesOfType(receiverType) : [];
|
|
34
|
+
if (receiverProperties.length === 0) {
|
|
35
|
+
entries.forEach((entry) => recordSymbolUncertainty(state, entry, "dynamic-member-access"));
|
|
36
|
+
state.projectsWithUnknownDynamicAccess.add(project);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const matched = new Set(
|
|
40
|
+
receiverProperties
|
|
41
|
+
.filter((property) => state.candidateNames.has(property.name))
|
|
42
|
+
.flatMap((property) => [...matchingSymbolEntries(project, property, "value", state)]),
|
|
43
|
+
);
|
|
44
|
+
matched.forEach((entry) => recordSymbolUncertainty(state, entry, "dynamic-member-access"));
|
|
45
|
+
};
|
|
@@ -11,7 +11,15 @@ export const TYPE_AWARE_PROTOCOL = Object.freeze({
|
|
|
11
11
|
backend: {
|
|
12
12
|
family: "typescript-go",
|
|
13
13
|
version: "7.0.2",
|
|
14
|
+
package: "typescript",
|
|
14
15
|
},
|
|
16
|
+
experimental_backends: [
|
|
17
|
+
{
|
|
18
|
+
family: "tsrs",
|
|
19
|
+
package: "tsrs",
|
|
20
|
+
version: "0.18.0-ts7.1.0-dev.20260929",
|
|
21
|
+
},
|
|
22
|
+
],
|
|
15
23
|
sidecar: {
|
|
16
24
|
package: "fallow-type-aware",
|
|
17
25
|
version_source: "workspace-package",
|
|
@@ -26,4 +34,8 @@ export const QUERY_OPERATIONS = Object.freeze(TYPE_AWARE_PROTOCOL.query_operatio
|
|
|
26
34
|
export const SESSION_ENVELOPE_TYPES = Object.freeze(TYPE_AWARE_PROTOCOL.session_envelope_types);
|
|
27
35
|
export const BACKEND_FAMILY = TYPE_AWARE_PROTOCOL.backend.family;
|
|
28
36
|
export const BACKEND_VERSION = TYPE_AWARE_PROTOCOL.backend.version;
|
|
37
|
+
export const BACKENDS = Object.freeze([
|
|
38
|
+
TYPE_AWARE_PROTOCOL.backend,
|
|
39
|
+
...TYPE_AWARE_PROTOCOL.experimental_backends,
|
|
40
|
+
]);
|
|
29
41
|
export const SIDECAR_PACKAGE = TYPE_AWARE_PROTOCOL.sidecar.package;
|
package/src/mutant-typecheck.mjs
CHANGED
|
@@ -10,10 +10,18 @@ import { createHash } from "node:crypto";
|
|
|
10
10
|
import { readFileSync } from "node:fs";
|
|
11
11
|
import path from "node:path";
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
READ_FROM_DISK,
|
|
15
|
+
createApi as createBackendApi,
|
|
16
|
+
isInferredProject,
|
|
17
|
+
projectCompilerOptions,
|
|
18
|
+
sync,
|
|
19
|
+
} from "./backend.mjs";
|
|
14
20
|
|
|
15
21
|
import { canonicalFileIdentity } from "./file-identity.mjs";
|
|
16
22
|
|
|
23
|
+
const { DiagnosticCategory } = sync;
|
|
24
|
+
|
|
17
25
|
const JAVASCRIPT_EXTENSIONS = new Set([".js", ".jsx", ".mjs", ".cjs"]);
|
|
18
26
|
const LEADING_COMMENTS = /^(?:\s|\/\/[^\n]*|\/\*[\s\S]*?\*\/)*/u;
|
|
19
27
|
const NO_CHECK_PRAGMA = /@ts-nocheck\b/u;
|
|
@@ -21,15 +29,12 @@ const MAX_CODES = 8;
|
|
|
21
29
|
|
|
22
30
|
const sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
23
31
|
|
|
24
|
-
const isInferredProject = (project) =>
|
|
25
|
-
project.configFileName.split(path.sep).join("/").endsWith("/dev/null/inferred");
|
|
26
|
-
|
|
27
32
|
const hasNoCheckPragma = (text) => NO_CHECK_PRAGMA.test(LEADING_COMMENTS.exec(text)?.[0] ?? "");
|
|
28
33
|
|
|
29
34
|
const isChecked = (project, absolutePath, text) => {
|
|
30
35
|
if (hasNoCheckPragma(text)) return false;
|
|
31
36
|
if (!JAVASCRIPT_EXTENSIONS.has(path.extname(absolutePath).toLowerCase())) return true;
|
|
32
|
-
return project.
|
|
37
|
+
return projectCompilerOptions(project).checkJs === true;
|
|
33
38
|
};
|
|
34
39
|
|
|
35
40
|
const errorKey = (diagnostic) => `${diagnostic.code}\u0000${diagnostic.text}`;
|
|
@@ -99,7 +104,8 @@ const createOverlayApi = (root, createApi) => {
|
|
|
99
104
|
cwd: root,
|
|
100
105
|
fs: {
|
|
101
106
|
readFile: (fileName) =>
|
|
102
|
-
overlay.size === 0 ? undefined : overlay.get(canonicalFileIdentity(fileName))
|
|
107
|
+
(overlay.size === 0 ? undefined : overlay.get(canonicalFileIdentity(fileName))) ??
|
|
108
|
+
READ_FROM_DISK,
|
|
103
109
|
},
|
|
104
110
|
});
|
|
105
111
|
return { api, overlay };
|
|
@@ -163,10 +169,7 @@ const checkFile = (context, file) => {
|
|
|
163
169
|
};
|
|
164
170
|
|
|
165
171
|
/** Type-checks each mutant of a parsed `mutant-typecheck` request. */
|
|
166
|
-
export const checkMutants = (
|
|
167
|
-
{ root, files },
|
|
168
|
-
{ createApi = (options) => new API(options) } = {},
|
|
169
|
-
) => {
|
|
172
|
+
export const checkMutants = ({ root, files }, { createApi = createBackendApi } = {}) => {
|
|
170
173
|
if (files.length === 0) return [];
|
|
171
174
|
const context = createOverlayApi(root, createApi);
|
|
172
175
|
try {
|