supercov 0.0.41 → 0.0.43
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/README.md +24 -4
- package/analyzers/typescript/README.md +55 -0
- package/analyzers/typescript/bin/compiler-identity.mjs +78 -0
- package/analyzers/typescript/bin/identity.mjs +67 -0
- package/analyzers/typescript/bin/query.mjs +29 -0
- package/analyzers/typescript/dist/analyze.js +3972 -0
- package/analyzers/typescript/dist/archive.js +309 -0
- package/analyzers/typescript/dist/build-identity.json +1 -0
- package/analyzers/typescript/dist/compiler.js +32 -0
- package/analyzers/typescript/dist/frontend.js +75 -0
- package/analyzers/typescript/dist/native-frontend.js +271 -0
- package/analyzers/typescript/dist/pragmas.js +143 -0
- package/analyzers/typescript/dist/types.js +1 -0
- package/analyzers/typescript/package.json +27 -0
- package/analyzers/typescript/src/analyze.ts +4538 -0
- package/analyzers/typescript/src/archive.ts +438 -0
- package/analyzers/typescript/src/compiler.ts +49 -0
- package/analyzers/typescript/src/frontend.ts +136 -0
- package/analyzers/typescript/src/native-frontend.ts +315 -0
- package/analyzers/typescript/src/pragmas.ts +218 -0
- package/analyzers/typescript/src/types.ts +45 -0
- package/analyzers/typescript/tsconfig.json +12 -0
- package/docs/agent-loop.md +13 -0
- package/docs/assertion-evidence.md +135 -0
- package/docs/cli.md +10 -0
- package/docs/code-verification.md +182 -0
- package/docs/supported-suites.md +44 -11
- package/docs/troubleshooting.md +13 -0
- package/docs/verification.md +12 -0
- package/package.json +33 -15
- package/runtime/javascript/jest.cjs +134 -0
- package/runtime/javascript/jest.config.mjs +39 -0
- package/runtime/javascript/jestReporter.mjs +77 -0
- package/runtime/javascript/register.mjs +22 -4
- package/runtime/javascript/runtime.mjs +51 -13
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { analyzeWithFrontend } from "./analyze.js";
|
|
4
|
+
import { analysisPath } from "./compiler.js";
|
|
5
|
+
import { createFrontend } from "./frontend.js";
|
|
6
|
+
export const PROTOCOL = {
|
|
7
|
+
abi: 1,
|
|
8
|
+
factsSchema: 1,
|
|
9
|
+
rules: "source-linked-v3/archive-3",
|
|
10
|
+
capabilities: [
|
|
11
|
+
"requiresTotal-v1",
|
|
12
|
+
"assertion-witness-issues-v1",
|
|
13
|
+
"assertion-hints-v1",
|
|
14
|
+
],
|
|
15
|
+
};
|
|
16
|
+
function sameScope(a, b) {
|
|
17
|
+
return (!!a &&
|
|
18
|
+
!!b &&
|
|
19
|
+
a.version === b.version &&
|
|
20
|
+
a.runId === b.runId &&
|
|
21
|
+
a.workerId === b.workerId &&
|
|
22
|
+
a.testId === b.testId &&
|
|
23
|
+
a.testKey === b.testKey &&
|
|
24
|
+
a.retry === b.retry &&
|
|
25
|
+
a.attemptId === b.attemptId);
|
|
26
|
+
}
|
|
27
|
+
function serverSnapshot(records, scope) {
|
|
28
|
+
const hits = [], events = [];
|
|
29
|
+
const decisions = [];
|
|
30
|
+
for (const record of records) {
|
|
31
|
+
if (!sameScope(record.scope, scope))
|
|
32
|
+
continue;
|
|
33
|
+
const id = record.type === "decision" ? record.meta?.id : record.id;
|
|
34
|
+
if (!id)
|
|
35
|
+
throw new Error("Missing scoped server event identity");
|
|
36
|
+
if (record.type === "decision" && record.meta && record.vector)
|
|
37
|
+
decisions.push({ meta: record.meta, vectors: [record.vector] });
|
|
38
|
+
else if (record.type === "hit")
|
|
39
|
+
hits.push(id);
|
|
40
|
+
else
|
|
41
|
+
throw new Error("Invalid scoped server event");
|
|
42
|
+
events.push({
|
|
43
|
+
type: record.type,
|
|
44
|
+
id,
|
|
45
|
+
phaseId: record.phaseId,
|
|
46
|
+
statementId: record.statementId,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return { hits, events, decisions };
|
|
50
|
+
}
|
|
51
|
+
export function analyzeArchive(input, suppliedCompiler) {
|
|
52
|
+
const frontend = createFrontend(input.projectRoot, suppliedCompiler);
|
|
53
|
+
try {
|
|
54
|
+
return analyzeArchiveWithFrontend(input, frontend);
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
frontend.close();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function analyzeArchiveWithFrontend(input, frontend) {
|
|
61
|
+
const compiler = frontend.syntax;
|
|
62
|
+
if (input.protocol.abi !== PROTOCOL.abi ||
|
|
63
|
+
input.protocol.factsSchema !== PROTOCOL.factsSchema ||
|
|
64
|
+
input.protocol.rules !== PROTOCOL.rules ||
|
|
65
|
+
JSON.stringify(input.protocol.capabilities) !==
|
|
66
|
+
JSON.stringify(PROTOCOL.capabilities))
|
|
67
|
+
throw new Error("Unsupported assertion analyzer protocol/rule revision/capabilities");
|
|
68
|
+
const root = analysisPath(input.projectRoot);
|
|
69
|
+
const sources = new Map(input.sourceFiles.map((file) => [
|
|
70
|
+
file,
|
|
71
|
+
frontend.parseSource(file, readFileSync(resolve(root, file), "utf8")),
|
|
72
|
+
]));
|
|
73
|
+
function offset(file, p) {
|
|
74
|
+
const sf = sources.get(file);
|
|
75
|
+
if (!sf || p.line < 1 || p.column < 1)
|
|
76
|
+
throw new Error(`Invalid site position in ${file}`);
|
|
77
|
+
const n = sf.getPositionOfLineAndCharacter(p.line - 1, p.column - 1);
|
|
78
|
+
if (n > sf.text.length)
|
|
79
|
+
throw new Error(`Site outside source: ${file}`);
|
|
80
|
+
return n;
|
|
81
|
+
}
|
|
82
|
+
const sites = input.effects.map((e) => ({
|
|
83
|
+
...e,
|
|
84
|
+
kind: "effect",
|
|
85
|
+
fn: e.function,
|
|
86
|
+
pos: offset(e.file, e.start),
|
|
87
|
+
endPos: offset(e.file, e.end),
|
|
88
|
+
}));
|
|
89
|
+
// Decision atoms retain their archived decision id and index. Logical-value operands are not
|
|
90
|
+
// fabricated from truthiness: in particular ?? does not establish the truthiness of its left side.
|
|
91
|
+
for (const d of input.manifest.decisions) {
|
|
92
|
+
const sf = sources.get(d.file);
|
|
93
|
+
if (!sf)
|
|
94
|
+
continue;
|
|
95
|
+
const start = offset(d.file, d);
|
|
96
|
+
let expression;
|
|
97
|
+
function visit(n) {
|
|
98
|
+
if (n.getStart(sf) === start && n.getText(sf) === d.source)
|
|
99
|
+
expression ??= n;
|
|
100
|
+
compiler.forEachChild(n, visit);
|
|
101
|
+
}
|
|
102
|
+
visit(sf);
|
|
103
|
+
if (!expression)
|
|
104
|
+
throw new Error(`Archived decision no longer matches source: ${d.id}`);
|
|
105
|
+
const atoms = [];
|
|
106
|
+
function flatten(n) {
|
|
107
|
+
if (compiler.isParenthesizedExpression(n))
|
|
108
|
+
flatten(n.expression);
|
|
109
|
+
else if (compiler.isBinaryExpression(n) &&
|
|
110
|
+
[
|
|
111
|
+
compiler.SyntaxKind.AmpersandAmpersandToken,
|
|
112
|
+
compiler.SyntaxKind.BarBarToken,
|
|
113
|
+
].includes(n.operatorToken.kind)) {
|
|
114
|
+
flatten(n.left);
|
|
115
|
+
flatten(n.right);
|
|
116
|
+
}
|
|
117
|
+
else
|
|
118
|
+
atoms.push(n);
|
|
119
|
+
}
|
|
120
|
+
flatten(expression);
|
|
121
|
+
if (atoms.length !== d.conditions.length)
|
|
122
|
+
throw new Error(`Unsupported decision atom layout: ${d.id}`);
|
|
123
|
+
atoms.forEach((n, i) => {
|
|
124
|
+
const begin = sf.getLineAndCharacterOfPosition(n.getStart(sf)), end = sf.getLineAndCharacterOfPosition(n.getEnd());
|
|
125
|
+
// The flow pass resolves the actual AST owner; this fallback is only for presentation.
|
|
126
|
+
let parent = n.parent, owner = "<module>";
|
|
127
|
+
while (parent) {
|
|
128
|
+
if (compiler.isFunctionDeclaration(parent) && parent.name) {
|
|
129
|
+
owner = parent.name.text;
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
parent = parent.parent;
|
|
133
|
+
}
|
|
134
|
+
sites.push({
|
|
135
|
+
id: `${d.id}#${i}`,
|
|
136
|
+
file: d.file,
|
|
137
|
+
kind: "decision",
|
|
138
|
+
category: "condition",
|
|
139
|
+
classification: "contractual",
|
|
140
|
+
start: { line: begin.line + 1, column: begin.character + 1 },
|
|
141
|
+
end: { line: end.line + 1, column: end.character + 1 },
|
|
142
|
+
pos: n.getStart(sf),
|
|
143
|
+
endPos: n.getEnd(),
|
|
144
|
+
text: n.getText(sf),
|
|
145
|
+
fn: owner,
|
|
146
|
+
owner,
|
|
147
|
+
exported: false,
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
const files = {
|
|
152
|
+
"inventory.json": JSON.stringify({ sites }),
|
|
153
|
+
};
|
|
154
|
+
const points = new Map(input.manifest.points.map((p) => [p.id, p]));
|
|
155
|
+
const decisions = new Map(input.manifest.decisions.map((d) => [d.id, d]));
|
|
156
|
+
const locations = new Map(points);
|
|
157
|
+
for (const b of input.manifest.branches)
|
|
158
|
+
for (const a of b.alternatives)
|
|
159
|
+
locations.set(a.id, b);
|
|
160
|
+
const key = (p) => `${p.file}:${p.line}:${p.column}`;
|
|
161
|
+
const index = [];
|
|
162
|
+
const executionLinks = [];
|
|
163
|
+
const attempts = [];
|
|
164
|
+
const limitations = new Set(input.limitations);
|
|
165
|
+
limitations.add("Candidate analysis: no site has a checked semantic proof; no assertion percentage is reported.");
|
|
166
|
+
limitations.add("Phase/statement execution is not data dependence. Candidate flow rules remain unverified; helper-name contracts and temporal value inference are disabled.");
|
|
167
|
+
limitations.add("Logical-value operand sites and type-dependent effect classifications are not yet a complete denominator.");
|
|
168
|
+
const accepted = input.records.filter((r) => r.role === "test" &&
|
|
169
|
+
r.status === "passed" &&
|
|
170
|
+
!r.flaky &&
|
|
171
|
+
r.expectedStatus !== "failed" &&
|
|
172
|
+
r.scope &&
|
|
173
|
+
r.scope.runId === input.runId &&
|
|
174
|
+
// Keep uncertain/retried/multiple records out instead of mixing their observations.
|
|
175
|
+
(r.retry ?? 0) === 0 &&
|
|
176
|
+
r.scope.retry === 0 &&
|
|
177
|
+
input.records.filter((other) => other.role === "test" &&
|
|
178
|
+
(other.testId ?? other.test) === (r.testId ?? r.test)).length === 1);
|
|
179
|
+
if (!accepted.length)
|
|
180
|
+
throw new Error("No uniquely attributed, passed, non-retried test attempts in archive");
|
|
181
|
+
for (const [i, r] of accepted.entries()) {
|
|
182
|
+
const id = `A${i + 1}`;
|
|
183
|
+
if (!r.testFile) {
|
|
184
|
+
limitations.add("A passed test has no source file.");
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (r.browser.length)
|
|
188
|
+
limitations.add("Browser evidence is not joined in this archive adapter.");
|
|
189
|
+
if (r.server.some((record) => !sameScope(record.scope, r.scope)))
|
|
190
|
+
limitations.add("Server events with missing or foreign attempt scopes were excluded.");
|
|
191
|
+
const snapshots = [...r.runtime, serverSnapshot(r.server, r.scope)];
|
|
192
|
+
const marked = new Map();
|
|
193
|
+
function mark(p) {
|
|
194
|
+
if (!p || !sources.has(p.file))
|
|
195
|
+
return;
|
|
196
|
+
if (!marked.has(p.file))
|
|
197
|
+
marked.set(p.file, new Set());
|
|
198
|
+
marked.get(p.file).add(p.line);
|
|
199
|
+
}
|
|
200
|
+
const outcomes = {};
|
|
201
|
+
const events = snapshots.flatMap((s) => s.events ?? []);
|
|
202
|
+
for (const snap of snapshots) {
|
|
203
|
+
for (const h of snap.hits ?? []) {
|
|
204
|
+
if (!locations.has(h))
|
|
205
|
+
throw new Error(`Unknown archived hit ${h}`);
|
|
206
|
+
mark(locations.get(h));
|
|
207
|
+
}
|
|
208
|
+
for (const d of snap.decisions ?? []) {
|
|
209
|
+
const meta = decisions.get(d.meta.id);
|
|
210
|
+
if (!meta)
|
|
211
|
+
throw new Error(`Unknown archived decision ${d.meta.id}`);
|
|
212
|
+
mark(meta);
|
|
213
|
+
for (const v of d.vectors) {
|
|
214
|
+
(outcomes[`${key(meta)}#d`] ??= [0, 0])[v.outcome ? 0 : 1] = 1;
|
|
215
|
+
v.values.forEach((value, n) => {
|
|
216
|
+
if (typeof value === "boolean")
|
|
217
|
+
(outcomes[`${key(meta)}#${n}`] ??= [0, 0])[value ? 0 : 1] = 1;
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
function describe(events) {
|
|
223
|
+
return {
|
|
224
|
+
fns: [
|
|
225
|
+
...new Set(events
|
|
226
|
+
.filter((e) => e.type === "hit" && points.get(e.id)?.kind === "function")
|
|
227
|
+
.map((e) => key(points.get(e.id)))),
|
|
228
|
+
],
|
|
229
|
+
decs: [
|
|
230
|
+
...new Set(events
|
|
231
|
+
.filter((e) => e.type === "decision" && decisions.has(e.id))
|
|
232
|
+
.map((e) => key(decisions.get(e.id)))),
|
|
233
|
+
],
|
|
234
|
+
stmts: events.filter((e) => e.type === "hit" && points.get(e.id)?.kind === "statement").length,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
const phases = r.phases
|
|
238
|
+
.filter((p) => p.kind === "assertion" && p.source)
|
|
239
|
+
.map((p) => {
|
|
240
|
+
const evs = events.filter((e) => e.phaseId === p.id);
|
|
241
|
+
for (const e of p.status === "passed" ? evs : [])
|
|
242
|
+
executionLinks.push({
|
|
243
|
+
attempt: id,
|
|
244
|
+
point: e.id,
|
|
245
|
+
location: points.get(e.id) ?? decisions.get(e.id) ?? locations.get(e.id),
|
|
246
|
+
phase: p.id,
|
|
247
|
+
statement: e.statementId,
|
|
248
|
+
operation: p.operation,
|
|
249
|
+
assertionSource: p.source,
|
|
250
|
+
meaning: "execution-only",
|
|
251
|
+
});
|
|
252
|
+
return {
|
|
253
|
+
op: p.operation,
|
|
254
|
+
source: p.source,
|
|
255
|
+
status: p.status,
|
|
256
|
+
...describe(evs),
|
|
257
|
+
};
|
|
258
|
+
});
|
|
259
|
+
const statements = {};
|
|
260
|
+
for (const e of events)
|
|
261
|
+
if (e.statementId && !statements[e.statementId])
|
|
262
|
+
statements[e.statementId] = describe(events.filter((other) => other.statementId === e.statementId));
|
|
263
|
+
const phaseLines = phases
|
|
264
|
+
.map((p) => /^(.*):(\d+):(\d+)$/.exec(p.source))
|
|
265
|
+
.filter((m) => m && m[1] === r.testFile)
|
|
266
|
+
.map((m) => Number(m[2]));
|
|
267
|
+
index.push({
|
|
268
|
+
id,
|
|
269
|
+
name: r.test,
|
|
270
|
+
title: r.title ?? r.test,
|
|
271
|
+
file: r.testFile,
|
|
272
|
+
line: 0,
|
|
273
|
+
ok: true,
|
|
274
|
+
phaseLines,
|
|
275
|
+
});
|
|
276
|
+
attempts.push({
|
|
277
|
+
id,
|
|
278
|
+
testId: r.testId,
|
|
279
|
+
name: r.test,
|
|
280
|
+
file: r.testFile,
|
|
281
|
+
retry: r.retry ?? 0,
|
|
282
|
+
});
|
|
283
|
+
files[`cov/${id}.lcov`] = [...marked]
|
|
284
|
+
.map(([file, lines]) => `SF:${file}\n${[...lines].map((l) => `DA:${l},1`).join("\n")}\nend_of_record\n`)
|
|
285
|
+
.join("");
|
|
286
|
+
files[`cov/${id}.outcomes.json`] = JSON.stringify(outcomes);
|
|
287
|
+
files[`cov/${id}.phases.json`] = JSON.stringify(phases);
|
|
288
|
+
files[`cov/${id}.statements.json`] = JSON.stringify(statements);
|
|
289
|
+
}
|
|
290
|
+
if (accepted.length !== input.records.filter((r) => r.role === "test").length)
|
|
291
|
+
limitations.add("Failed, flaky, retried, duplicate or unattributed test records were excluded; this is not whole-suite assertion coverage.");
|
|
292
|
+
files["cov/index.json"] = JSON.stringify(index);
|
|
293
|
+
const result = analyzeWithFrontend({
|
|
294
|
+
projectRoot: root,
|
|
295
|
+
evidenceFiles: files,
|
|
296
|
+
sourceFiles: input.sourceFiles,
|
|
297
|
+
testFiles: [
|
|
298
|
+
...new Set(accepted.flatMap((r) => (r.testFile ? [r.testFile] : []))),
|
|
299
|
+
],
|
|
300
|
+
}, frontend);
|
|
301
|
+
return {
|
|
302
|
+
protocol: PROTOCOL,
|
|
303
|
+
...result,
|
|
304
|
+
inventory: sites,
|
|
305
|
+
executionLinks,
|
|
306
|
+
attempts,
|
|
307
|
+
limitations: [...limitations, ...frontend.limitations].sort(),
|
|
308
|
+
};
|
|
309
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schema":1,"sourceSha256":"58c5fd835878413942c6944032f3bcd0b6c76ce82fd0ae6048b0aeef4bd65612","compiledSha256":"8c2ee2f19f780465bffd589b5cff23cec80234744316a2067063318b2dc7906f"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4
|
+
/** Canonical Rust paths may carry a Windows namespace prefix. Node/compiler paths do not. */
|
|
5
|
+
export function analysisPath(path) {
|
|
6
|
+
return fileURLToPath(pathToFileURL(resolve(path)));
|
|
7
|
+
}
|
|
8
|
+
/** Use the project's semantics rather than silently substituting the analyzer's compiler version. */
|
|
9
|
+
export function projectCompilerPath(projectRoot) {
|
|
10
|
+
try {
|
|
11
|
+
return createRequire(pathToFileURL(resolve(projectRoot, "package.json"))).resolve("typescript");
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
throw new Error(`Assertion analysis requires the project's TypeScript compiler API, including for JavaScript projects. TypeScript 5.8.3 and native 7.0.2 are tested; install a compatible compiler in ${projectRoot}, rerun the tests, then query again.`, { cause: error });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** Legacy API loader; createFrontend selects the separate native backend. */
|
|
18
|
+
export function loadProjectCompiler(projectRoot) {
|
|
19
|
+
const require = createRequire(pathToFileURL(resolve(projectRoot, "package.json")));
|
|
20
|
+
let compiler;
|
|
21
|
+
try {
|
|
22
|
+
compiler = require(projectCompilerPath(projectRoot));
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
throw new Error(`Cannot load the TypeScript compiler API from ${projectRoot}; install TypeScript in that project or supply options.typescript.`, { cause: error });
|
|
26
|
+
}
|
|
27
|
+
if (typeof compiler.createProgram !== "function" ||
|
|
28
|
+
typeof compiler.parseJsonConfigFileContent !== "function") {
|
|
29
|
+
throw new Error(`The TypeScript installation in ${projectRoot} does not expose the compiler API required by this analyzer (createProgram and parseJsonConfigFileContent). TypeScript 5.8.3 is tested; TypeScript 7's different API is not supported. No fallback compiler was substituted.`);
|
|
30
|
+
}
|
|
31
|
+
return compiler;
|
|
32
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import { analysisPath, loadProjectCompiler, projectCompilerPath, } from "./compiler.js";
|
|
6
|
+
import { nativeFrontend } from "./native-frontend.js";
|
|
7
|
+
export function selectedRootFiles(options, configured) {
|
|
8
|
+
const root = analysisPath(options.projectRoot);
|
|
9
|
+
if (options.sourceFiles)
|
|
10
|
+
return [...options.sourceFiles, ...(options.testFiles ?? [])].map((f) => resolve(root, f));
|
|
11
|
+
if (!options.sourceDir)
|
|
12
|
+
return configured;
|
|
13
|
+
function walk(dir) {
|
|
14
|
+
if (!existsSync(dir))
|
|
15
|
+
return [];
|
|
16
|
+
return readdirSync(dir, { withFileTypes: true }).flatMap((e) => {
|
|
17
|
+
if (e.name === "node_modules" || e.name.startsWith("."))
|
|
18
|
+
return [];
|
|
19
|
+
const file = resolve(dir, e.name);
|
|
20
|
+
return e.isDirectory()
|
|
21
|
+
? walk(file)
|
|
22
|
+
: /\.(ts|tsx|mts)$/.test(e.name) && !e.name.endsWith(".d.ts")
|
|
23
|
+
? [file]
|
|
24
|
+
: [];
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return [
|
|
28
|
+
...walk(resolve(root, options.sourceDir)),
|
|
29
|
+
...walk(resolve(root, options.testDir ?? "tests")),
|
|
30
|
+
...(existsSync(resolve(root, "env.d.ts"))
|
|
31
|
+
? [resolve(root, "env.d.ts")]
|
|
32
|
+
: []),
|
|
33
|
+
];
|
|
34
|
+
}
|
|
35
|
+
function legacyFrontend(compiler) {
|
|
36
|
+
return {
|
|
37
|
+
kind: "typescript-legacy",
|
|
38
|
+
version: compiler.version,
|
|
39
|
+
syntax: compiler,
|
|
40
|
+
limitations: new Set(),
|
|
41
|
+
close() { },
|
|
42
|
+
openProgram(options) {
|
|
43
|
+
const root = analysisPath(options.projectRoot);
|
|
44
|
+
const configPath = resolve(root, options.tsconfig ?? "tsconfig.json");
|
|
45
|
+
const cfg = options.sourceFiles && !existsSync(configPath)
|
|
46
|
+
? { config: { compilerOptions: { allowJs: true, checkJs: true } } }
|
|
47
|
+
: compiler.readConfigFile(configPath, compiler.sys.readFile);
|
|
48
|
+
if (cfg.error)
|
|
49
|
+
throw new Error(compiler.flattenDiagnosticMessageText(cfg.error.messageText, "\n"));
|
|
50
|
+
const parsed = compiler.parseJsonConfigFileContent(cfg.config, compiler.sys, dirname(configPath));
|
|
51
|
+
const program = compiler.createProgram(selectedRootFiles(options, parsed.fileNames), {
|
|
52
|
+
...parsed.options,
|
|
53
|
+
...(options.sourceFiles ? { allowJs: true, checkJs: true } : {}),
|
|
54
|
+
noEmit: true,
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
files: program.getSourceFiles(),
|
|
58
|
+
checker: program.getTypeChecker(),
|
|
59
|
+
resolveModule: (spec, from) => compiler.resolveModuleName(spec, from, parsed.options, compiler.sys)
|
|
60
|
+
.resolvedModule?.resolvedFileName,
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
parseSource: (file, text) => compiler.createSourceFile(file, text, compiler.ScriptTarget.Latest, true),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export function createFrontend(root, supplied) {
|
|
67
|
+
if (supplied)
|
|
68
|
+
return legacyFrontend(supplied);
|
|
69
|
+
const entry = projectCompilerPath(root);
|
|
70
|
+
const require = createRequire(pathToFileURL(entry));
|
|
71
|
+
const version = require(entry).version;
|
|
72
|
+
if (version === "7.0.2")
|
|
73
|
+
return nativeFrontend(entry, root);
|
|
74
|
+
return legacyFrontend(loadProjectCompiler(root));
|
|
75
|
+
}
|