fallow-type-aware 3.15.0 → 3.17.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/README.md +7 -0
- package/fallow-type-aware.mjs +6 -1
- package/package.json +3 -2
- package/src/backend-preflight.mjs +42 -0
- package/src/generated-protocol.mjs +2 -2
- package/src/project-state.mjs +111 -10
- package/src/semantic-identity.mjs +25 -2
- package/src/semantic.mjs +59 -30
- package/src/type-coupling.mjs +1 -2
- package/src/windows-child-process.mjs +25 -0
package/README.md
CHANGED
|
@@ -25,6 +25,13 @@ and SHA-256 declaration guard, but Fallow owns the final decision and fix
|
|
|
25
25
|
policy. The sidecar does not emit TypeScript compiler diagnostics as Fallow
|
|
26
26
|
findings and does not implement generic typed lint rules.
|
|
27
27
|
|
|
28
|
+
The raw TypeScript-Go host cannot currently expose Svelte virtual-module named
|
|
29
|
+
exports. If source code imports or re-exports such a name and checker resolution
|
|
30
|
+
has no declaration target, the sidecar returns
|
|
31
|
+
`svelte-virtual-module-exports` instead of claiming complete evidence. Run
|
|
32
|
+
`svelte-check` for framework diagnostics; Fallow stays fail-closed until a
|
|
33
|
+
supported host seam can preserve virtual source identity and mappings.
|
|
34
|
+
|
|
28
35
|
## Run locally
|
|
29
36
|
|
|
30
37
|
```sh
|
package/fallow-type-aware.mjs
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { installWindowsChildProcessPolicy } from "./src/windows-child-process.mjs";
|
|
4
|
+
|
|
5
|
+
installWindowsChildProcessPolicy();
|
|
4
6
|
|
|
5
7
|
try {
|
|
8
|
+
const { assertTypescriptBackendResolvable } = await import("./src/backend-preflight.mjs");
|
|
9
|
+
assertTypescriptBackendResolvable();
|
|
10
|
+
const { run } = await import("./src/cli.mjs");
|
|
6
11
|
await run({ input: process.stdin, output: process.stdout, args: process.argv.slice(2) });
|
|
7
12
|
} catch (error) {
|
|
8
13
|
const message = error instanceof Error ? error.message : String(error);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fallow-type-aware",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.17.0",
|
|
4
4
|
"description": "Optional TypeScript-Go semantic refinement sidecar for Fallow",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@codspeed/tinybench-plugin": "5.7.1",
|
|
35
|
-
"tinybench": "6.1.2"
|
|
35
|
+
"tinybench": "6.1.2",
|
|
36
|
+
"zod": "4.4.3"
|
|
36
37
|
}
|
|
37
38
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import { BACKEND_VERSION } from "./generated-protocol.mjs";
|
|
5
|
+
|
|
6
|
+
const REQUIRED_MAJOR = Number.parseInt(BACKEND_VERSION, 10);
|
|
7
|
+
const INSTALL_HINT =
|
|
8
|
+
"run `npm ci` in tools/type-aware-sidecar so the sidecar resolves its own typescript install";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Verify that module resolution hands the sidecar a typescript install that
|
|
12
|
+
* provides the `typescript/unstable/sync` backend entry point.
|
|
13
|
+
*
|
|
14
|
+
* The semantic modules import that entry point statically, so an incompatible
|
|
15
|
+
* resolution (for example a typescript 6 hoisted into an ancestor
|
|
16
|
+
* `node_modules`) fails at link time with a bare module path instead of the
|
|
17
|
+
* version conflict that caused it. This preflight runs before those imports
|
|
18
|
+
* and names the resolved version, its location, and the fix. Exact
|
|
19
|
+
* backend-version parity stays enforced in `protocol.mjs`.
|
|
20
|
+
*
|
|
21
|
+
* @param {string | URL} resolveFrom Module URL or file path to resolve from;
|
|
22
|
+
* defaults to this module so the check mirrors the semantic imports.
|
|
23
|
+
*/
|
|
24
|
+
export const assertTypescriptBackendResolvable = (resolveFrom = import.meta.url) => {
|
|
25
|
+
const sidecarRequire = createRequire(resolveFrom);
|
|
26
|
+
let manifestPath;
|
|
27
|
+
try {
|
|
28
|
+
manifestPath = sidecarRequire.resolve("typescript/package.json");
|
|
29
|
+
} catch {
|
|
30
|
+
throw new Error(`typescript is not installed; ${INSTALL_HINT}`);
|
|
31
|
+
}
|
|
32
|
+
const { version } = sidecarRequire(manifestPath);
|
|
33
|
+
const major = Number.parseInt(version, 10);
|
|
34
|
+
if (Number.isFinite(major) && major >= REQUIRED_MAJOR) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
throw new Error(
|
|
38
|
+
`resolved typescript ${version} from ${path.dirname(manifestPath)}, which lacks the ` +
|
|
39
|
+
`typescript/unstable/sync backend; fallow-type-aware needs typescript ${BACKEND_VERSION}; ` +
|
|
40
|
+
INSTALL_HINT,
|
|
41
|
+
);
|
|
42
|
+
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Generated from crates/api/type-aware-protocol.json. Do not edit.
|
|
2
2
|
export const TYPE_AWARE_PROTOCOL = Object.freeze({
|
|
3
3
|
schema_version: 1,
|
|
4
|
-
wire_protocol_version:
|
|
5
|
-
semantic_schema_version:
|
|
4
|
+
wire_protocol_version: 7,
|
|
5
|
+
semantic_schema_version: 3,
|
|
6
6
|
analysis_operation: "semantic-queries",
|
|
7
7
|
status_operation: "status",
|
|
8
8
|
query_operations: ["symbol-use", "symbol-trace", "api-surface", "symbol-impact", "type-coupling"],
|
package/src/project-state.mjs
CHANGED
|
@@ -2,18 +2,107 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
|
+
import { SymbolFlags } from "typescript/unstable/sync";
|
|
6
|
+
import {
|
|
7
|
+
isExportDeclaration,
|
|
8
|
+
isImportDeclaration,
|
|
9
|
+
isNamedExports,
|
|
10
|
+
isNamedImports,
|
|
11
|
+
} from "typescript/unstable/ast/is";
|
|
12
|
+
|
|
5
13
|
import { canonicalFileIdentity } from "./file-identity.mjs";
|
|
6
|
-
import { relativePath } from "./semantic-identity.mjs";
|
|
14
|
+
import { projectSourceFiles, relativePath } from "./semantic-identity.mjs";
|
|
7
15
|
|
|
8
16
|
const INFERRED_PROJECT = "<inferred>";
|
|
9
17
|
const compareText = (left, right) => Buffer.compare(Buffer.from(left), Buffer.from(right));
|
|
10
18
|
const slash = (value) => value.split(path.sep).join("/");
|
|
11
19
|
|
|
12
|
-
const
|
|
13
|
-
project.program.getConfigFileParsingDiagnostics()
|
|
14
|
-
project.program.getProgramDiagnostics()
|
|
15
|
-
project.program.getSyntacticDiagnostics()
|
|
16
|
-
project.program.getBindDiagnostics()
|
|
20
|
+
const structuralDiagnostics = (project) => [
|
|
21
|
+
...project.program.getConfigFileParsingDiagnostics(),
|
|
22
|
+
...project.program.getProgramDiagnostics(),
|
|
23
|
+
...project.program.getSyntacticDiagnostics(),
|
|
24
|
+
...project.program.getBindDiagnostics(),
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const isProjectLocalDiagnostic = (project, diagnostic) => {
|
|
28
|
+
if (!diagnostic.fileName) return true;
|
|
29
|
+
const sourceFile = project.program.getSourceFile(diagnostic.fileName);
|
|
30
|
+
if (!sourceFile) return true;
|
|
31
|
+
return (
|
|
32
|
+
!project.program.isSourceFileDefaultLibrary(sourceFile) &&
|
|
33
|
+
!project.program.isSourceFileFromExternalLibrary(sourceFile)
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const isSvelteSpecifier = (node) =>
|
|
38
|
+
typeof node.moduleSpecifier?.text === "string" && node.moduleSpecifier.text.endsWith(".svelte");
|
|
39
|
+
|
|
40
|
+
const isUnknownAlias = (checker, specifier) => {
|
|
41
|
+
const symbol = checker.getSymbolAtLocation(specifier.name);
|
|
42
|
+
if (!symbol) return true;
|
|
43
|
+
return checker.isUnknownSymbol(checker.getAliasedSymbol(symbol));
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const concreteExportSymbol = (checker, symbol) => {
|
|
47
|
+
const target =
|
|
48
|
+
(symbol.flags & SymbolFlags.Alias) === 0 ? symbol : checker.getAliasedSymbol(symbol);
|
|
49
|
+
return !checker.isUnknownSymbol(target) && (target.declarations?.length ?? 0) > 0;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const hasProvableNamedExports = (project, declaration) => {
|
|
53
|
+
const moduleSymbol = project.checker.getSymbolAtLocation(declaration.moduleSpecifier);
|
|
54
|
+
if (!moduleSymbol) return false;
|
|
55
|
+
const hasConcreteModuleDeclaration = moduleSymbol.declarations?.some((moduleDeclaration) => {
|
|
56
|
+
const declarationPath =
|
|
57
|
+
moduleDeclaration.path ??
|
|
58
|
+
moduleDeclaration.fileName ??
|
|
59
|
+
moduleDeclaration.getSourceFile?.().fileName ??
|
|
60
|
+
"";
|
|
61
|
+
return declarationPath.endsWith(".d.svelte") || declarationPath.endsWith(".d.svelte.ts");
|
|
62
|
+
});
|
|
63
|
+
if (!hasConcreteModuleDeclaration) return false;
|
|
64
|
+
const namedExports = project.checker
|
|
65
|
+
.getExportsOfModule(moduleSymbol)
|
|
66
|
+
.filter((symbol) => symbol.name !== "default");
|
|
67
|
+
return namedExports.every((symbol) => concreteExportSymbol(project.checker, symbol));
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const svelteDeclarationHasGap = (project, node) => {
|
|
71
|
+
if (!isSvelteSpecifier(node)) return false;
|
|
72
|
+
if (isImportDeclaration(node)) {
|
|
73
|
+
const bindings = node.importClause?.namedBindings;
|
|
74
|
+
return (
|
|
75
|
+
bindings !== undefined &&
|
|
76
|
+
isNamedImports(bindings) &&
|
|
77
|
+
bindings.elements.some((item) => isUnknownAlias(project.checker, item))
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
if (!isExportDeclaration(node)) return false;
|
|
81
|
+
if (!node.exportClause) return !hasProvableNamedExports(project, node);
|
|
82
|
+
if (!isNamedExports(node.exportClause)) return !hasProvableNamedExports(project, node);
|
|
83
|
+
return node.exportClause.elements.some((item) => isUnknownAlias(project.checker, item));
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const sourceHasSvelteVirtualModuleGap = (project, sourceFile) => {
|
|
87
|
+
let gap = false;
|
|
88
|
+
const visit = (node) => {
|
|
89
|
+
if (gap) return;
|
|
90
|
+
gap = svelteDeclarationHasGap(project, node);
|
|
91
|
+
if (gap) return;
|
|
92
|
+
node.forEachChild((child) => {
|
|
93
|
+
visit(child);
|
|
94
|
+
return undefined;
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
visit(sourceFile);
|
|
98
|
+
return gap;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const hasSvelteVirtualModuleGap = (project) =>
|
|
102
|
+
projectSourceFiles(project).some(
|
|
103
|
+
(sourceFile) =>
|
|
104
|
+
sourceFile.text.includes(".svelte") && sourceHasSvelteVirtualModuleGap(project, sourceFile),
|
|
105
|
+
);
|
|
17
106
|
|
|
18
107
|
const configPath = (root, project) => {
|
|
19
108
|
const normalized = slash(project.configFileName);
|
|
@@ -173,15 +262,27 @@ const effectiveProjectConfigHash = (root, project) => {
|
|
|
173
262
|
};
|
|
174
263
|
|
|
175
264
|
export const projectState = (root, project, source) => {
|
|
176
|
-
const
|
|
265
|
+
const diagnostics = structuralDiagnostics(project);
|
|
266
|
+
const localDiagnosticCount = diagnostics.filter((diagnostic) =>
|
|
267
|
+
isProjectLocalDiagnostic(project, diagnostic),
|
|
268
|
+
).length;
|
|
269
|
+
const hasLocalDiagnostics = localDiagnosticCount > 0;
|
|
270
|
+
const svelteVirtualModuleGap = !hasLocalDiagnostics && hasSvelteVirtualModuleGap(project);
|
|
271
|
+
const reasonCode = hasLocalDiagnostics
|
|
272
|
+
? "blocking-diagnostics"
|
|
273
|
+
: svelteVirtualModuleGap
|
|
274
|
+
? "svelte-virtual-module-exports"
|
|
275
|
+
: diagnostics.length > 0
|
|
276
|
+
? "blocking-diagnostics"
|
|
277
|
+
: null;
|
|
177
278
|
return {
|
|
178
279
|
project,
|
|
179
280
|
config: configPath(root, project),
|
|
180
281
|
effective_config_hash: effectiveProjectConfigHash(root, project),
|
|
181
282
|
source,
|
|
182
|
-
status:
|
|
183
|
-
reason_code:
|
|
184
|
-
blocking_diagnostic_count:
|
|
283
|
+
status: reasonCode === null ? "complete" : "unavailable",
|
|
284
|
+
reason_code: reasonCode,
|
|
285
|
+
blocking_diagnostic_count: reasonCode === "blocking-diagnostics" ? diagnostics.length : 0,
|
|
185
286
|
source_file_count: project.program.getSourceFileNames().length,
|
|
186
287
|
program_reused: false,
|
|
187
288
|
candidate_count: 0,
|
|
@@ -12,9 +12,11 @@ import {
|
|
|
12
12
|
isMethodDeclaration,
|
|
13
13
|
isMethodSignatureDeclaration,
|
|
14
14
|
isModuleDeclaration,
|
|
15
|
+
isNamespaceExport,
|
|
15
16
|
isPropertyDeclaration,
|
|
16
17
|
isPropertySignatureDeclaration,
|
|
17
18
|
isSetAccessorDeclaration,
|
|
19
|
+
isSourceFile,
|
|
18
20
|
isTypeAliasDeclaration,
|
|
19
21
|
isVariableDeclaration,
|
|
20
22
|
} from "typescript/unstable/ast/is";
|
|
@@ -45,7 +47,12 @@ const TYPE_ONLY_DECLARATIONS = [
|
|
|
45
47
|
isTypeAliasDeclaration,
|
|
46
48
|
isPropertySignatureDeclaration,
|
|
47
49
|
];
|
|
48
|
-
const DUAL_NAMESPACE_DECLARATIONS = [
|
|
50
|
+
const DUAL_NAMESPACE_DECLARATIONS = [
|
|
51
|
+
isClassDeclaration,
|
|
52
|
+
isClassExpression,
|
|
53
|
+
isEnumDeclaration,
|
|
54
|
+
isSourceFile,
|
|
55
|
+
];
|
|
49
56
|
const DECLARATION_OWNER_NODES = [isClassDeclaration, isClassExpression, isInterfaceDeclaration];
|
|
50
57
|
|
|
51
58
|
const slash = (value) => value.split(path.sep).join("/");
|
|
@@ -101,6 +108,8 @@ export const ownerDeclaration = (node) => {
|
|
|
101
108
|
|
|
102
109
|
export const isDeclaration = (node) => declarationKind(node) !== undefined;
|
|
103
110
|
|
|
111
|
+
export const isExportAnchor = (node) => isExportSpecifier(node) || isNamespaceExport(node);
|
|
112
|
+
|
|
104
113
|
const visit = (node, callback) => {
|
|
105
114
|
callback(node);
|
|
106
115
|
node.forEachChild((child) => {
|
|
@@ -151,6 +160,16 @@ const indexExportSpecifier = (index, node) => {
|
|
|
151
160
|
);
|
|
152
161
|
};
|
|
153
162
|
|
|
163
|
+
const indexNamespaceExport = (index, node) => {
|
|
164
|
+
const exportedName = nodeText(node.name);
|
|
165
|
+
if (!exportedName) return;
|
|
166
|
+
[node.parent, node]
|
|
167
|
+
.flatMap((candidate) => positions(candidate))
|
|
168
|
+
.forEach((position) =>
|
|
169
|
+
indexExportPosition(index, node, { localName: exportedName, exportedName }, position),
|
|
170
|
+
);
|
|
171
|
+
};
|
|
172
|
+
|
|
154
173
|
const defaultModifierFor = (node) =>
|
|
155
174
|
node.modifiers?.find((modifier) => modifier.getText(node.getSourceFile()) === "default");
|
|
156
175
|
|
|
@@ -197,6 +216,10 @@ const declarationIndex = (sourceFile) => {
|
|
|
197
216
|
indexExportSpecifier(index, node);
|
|
198
217
|
return;
|
|
199
218
|
}
|
|
219
|
+
if (isNamespaceExport(node)) {
|
|
220
|
+
indexNamespaceExport(index, node);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
200
223
|
indexDeclarationNode(index, node);
|
|
201
224
|
});
|
|
202
225
|
declarationIndexes.set(sourceFile, index);
|
|
@@ -205,7 +228,7 @@ const declarationIndex = (sourceFile) => {
|
|
|
205
228
|
|
|
206
229
|
export const findDeclaration = (sourceFile, identity) => {
|
|
207
230
|
const anchor = declarationIndex(sourceFile).get(anchorKey(identity));
|
|
208
|
-
if (!anchor ||
|
|
231
|
+
if (!anchor || isExportAnchor(anchor)) return anchor;
|
|
209
232
|
return declarationNamespaces(anchor).has(identity.namespace) ? anchor : undefined;
|
|
210
233
|
};
|
|
211
234
|
|
package/src/semantic.mjs
CHANGED
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
declarationsForSymbol,
|
|
36
36
|
findDeclaration,
|
|
37
37
|
isDeclaration,
|
|
38
|
+
isExportAnchor,
|
|
38
39
|
isProjectSource,
|
|
39
40
|
nodeText,
|
|
40
41
|
ownerDeclaration,
|
|
@@ -156,6 +157,10 @@ const REASON_ACTIONS = new Map([
|
|
|
156
157
|
"blocking-diagnostics",
|
|
157
158
|
"Repair structural TypeScript diagnostics in every selected project and retry.",
|
|
158
159
|
],
|
|
160
|
+
[
|
|
161
|
+
"svelte-virtual-module-exports",
|
|
162
|
+
"Run svelte-check for framework diagnostics. See https://docs.fallow.tools/analysis/type-aware#svelte-virtual-module-exports for supported Svelte project setup.",
|
|
163
|
+
],
|
|
159
164
|
[
|
|
160
165
|
"unknown-entry-point",
|
|
161
166
|
"Refresh the package entry points or pass project-relative source entry points.",
|
|
@@ -165,21 +170,22 @@ const DEFAULT_REASON_ACTION =
|
|
|
165
170
|
"Narrow the query to a specific symbol, entry point, or healthy TypeScript project and retry.";
|
|
166
171
|
const REASON_PRIORITY = new Map([
|
|
167
172
|
["blocking-diagnostics", 0],
|
|
168
|
-
["
|
|
169
|
-
["
|
|
170
|
-
["
|
|
171
|
-
["
|
|
172
|
-
["unknown-
|
|
173
|
-
["
|
|
174
|
-
["
|
|
175
|
-
["
|
|
176
|
-
["
|
|
177
|
-
["
|
|
178
|
-
["
|
|
179
|
-
["
|
|
180
|
-
["
|
|
181
|
-
["
|
|
182
|
-
["
|
|
173
|
+
["svelte-virtual-module-exports", 1],
|
|
174
|
+
["incomplete-project-coverage", 2],
|
|
175
|
+
["framework-contract-provenance", 3],
|
|
176
|
+
["ambiguous-project", 4],
|
|
177
|
+
["unknown-symbol", 5],
|
|
178
|
+
["unknown-entry-point", 6],
|
|
179
|
+
["decorated-declaration", 7],
|
|
180
|
+
["optional-contract", 8],
|
|
181
|
+
["accessor-pair", 9],
|
|
182
|
+
["overload-set", 10],
|
|
183
|
+
["attached-comment", 11],
|
|
184
|
+
["abstract-declaration", 12],
|
|
185
|
+
["dynamic-member-access", 13],
|
|
186
|
+
["virtual-dispatch", 14],
|
|
187
|
+
["dynamic-behavior", 15],
|
|
188
|
+
["evidence-limit", 16],
|
|
183
189
|
]);
|
|
184
190
|
|
|
185
191
|
const actionForReason = (reasonCode) => REASON_ACTIONS.get(reasonCode) ?? DEFAULT_REASON_ACTION;
|
|
@@ -200,6 +206,22 @@ const combineOmissions = (omissions) => {
|
|
|
200
206
|
return [...counts].map(([reason_code, count]) => ({ reason_code, count }));
|
|
201
207
|
};
|
|
202
208
|
|
|
209
|
+
const projectStateReason = (state) => state.reason_code ?? "blocking-diagnostics";
|
|
210
|
+
|
|
211
|
+
const unavailableProjectOmissions = (states) =>
|
|
212
|
+
combineOmissions(
|
|
213
|
+
states
|
|
214
|
+
.filter((state) => state.status !== "complete")
|
|
215
|
+
.map((state) => ({ reason_code: projectStateReason(state), count: 1 })),
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
const unavailableProjectReason = (states) => {
|
|
219
|
+
if (states.length === 0) return "no-project";
|
|
220
|
+
return (
|
|
221
|
+
unavailableProjectOmissions(states).toSorted(compareOmissions)[0]?.reason_code ?? "no-project"
|
|
222
|
+
);
|
|
223
|
+
};
|
|
224
|
+
|
|
203
225
|
const resultStatus = (partial) => (partial ? "partial" : "complete");
|
|
204
226
|
const resultReason = (omissions) => (omissions.length > 0 ? omissions[0].reason_code : null);
|
|
205
227
|
const resultActions = (omissions) =>
|
|
@@ -680,8 +702,6 @@ const symbolResolutionError = (query, reasonCode, action) => ({
|
|
|
680
702
|
});
|
|
681
703
|
|
|
682
704
|
const isCompleteProjectState = (state) => state?.status === "complete";
|
|
683
|
-
const selectedProjectName = (state) => state?.config ?? "the selected project";
|
|
684
|
-
|
|
685
705
|
const owningProjectContexts = (statesByProject, absolutePath) =>
|
|
686
706
|
[...statesByProject.entries()]
|
|
687
707
|
.filter(([project]) => project.program.getSourceFile(absolutePath))
|
|
@@ -721,13 +741,16 @@ const selectSymbolContext = (
|
|
|
721
741
|
}
|
|
722
742
|
const completeOwners = owners.filter(({ state }) => isCompleteProjectState(state));
|
|
723
743
|
if (completeOwners.length === 0) {
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
"blocking-diagnostics",
|
|
729
|
-
`Repair structural diagnostics in ${selectedProjectName(owners[0].state)} and retry.`,
|
|
744
|
+
const selectedOwner = [...owners].toSorted((left, right) =>
|
|
745
|
+
compareOmissions(
|
|
746
|
+
{ reason_code: projectStateReason(left.state) },
|
|
747
|
+
{ reason_code: projectStateReason(right.state) },
|
|
730
748
|
),
|
|
749
|
+
)[0];
|
|
750
|
+
const reasonCode = projectStateReason(selectedOwner.state);
|
|
751
|
+
return {
|
|
752
|
+
...selectedOwner,
|
|
753
|
+
...symbolResolutionError(query, reasonCode, actionForReason(reasonCode)),
|
|
731
754
|
};
|
|
732
755
|
}
|
|
733
756
|
return { owners, completeOwners };
|
|
@@ -735,7 +758,7 @@ const selectSymbolContext = (
|
|
|
735
758
|
|
|
736
759
|
const resolvedAnchorTarget = (project, anchor, requestedSymbol) => {
|
|
737
760
|
if (!anchor) return undefined;
|
|
738
|
-
if (!
|
|
761
|
+
if (!isExportAnchor(anchor)) {
|
|
739
762
|
return declarationNamespaces(anchor).has(requestedSymbol.namespace)
|
|
740
763
|
? { declaration: anchor, namespace: requestedSymbol.namespace }
|
|
741
764
|
: undefined;
|
|
@@ -1376,6 +1399,13 @@ const signatureTypes = (project, type, anchor) =>
|
|
|
1376
1399
|
);
|
|
1377
1400
|
|
|
1378
1401
|
const checkerTypeChildren = (project, type, anchor, hasNamedDeclaration) => {
|
|
1402
|
+
if (type.isTypeParameter()) {
|
|
1403
|
+
const constraint = safeCheckerValue(
|
|
1404
|
+
() => project.checker.getConstraintOfTypeParameter(type),
|
|
1405
|
+
undefined,
|
|
1406
|
+
);
|
|
1407
|
+
return constraint ? [constraint] : [];
|
|
1408
|
+
}
|
|
1379
1409
|
const structural = [
|
|
1380
1410
|
...(type.getTypes() ?? []),
|
|
1381
1411
|
...safeCheckerValue(() => type.getAliasTypeArguments(), []),
|
|
@@ -1546,12 +1576,9 @@ const graphProjects = (snapshot, explicitProjects) =>
|
|
|
1546
1576
|
const readyProjectStates = (query, states) => {
|
|
1547
1577
|
const ready = states.filter((state) => state.status === "complete");
|
|
1548
1578
|
if (ready.length > 0) return { ready };
|
|
1579
|
+
const reasonCode = unavailableProjectReason(states);
|
|
1549
1580
|
return {
|
|
1550
|
-
error: unavailable(
|
|
1551
|
-
query,
|
|
1552
|
-
states.length === 0 ? "no-project" : "blocking-diagnostics",
|
|
1553
|
-
"Pass a healthy tsconfig containing the package entry points and retry.",
|
|
1554
|
-
),
|
|
1581
|
+
error: unavailable(query, reasonCode, actionForReason(reasonCode)),
|
|
1555
1582
|
};
|
|
1556
1583
|
};
|
|
1557
1584
|
|
|
@@ -1683,7 +1710,7 @@ const analyzeApiSurface = (root, query, states, evidenceLimit) => {
|
|
|
1683
1710
|
reason_code: "evidence-limit",
|
|
1684
1711
|
count: omissionCount,
|
|
1685
1712
|
},
|
|
1686
|
-
|
|
1713
|
+
...unavailableProjectOmissions(states),
|
|
1687
1714
|
{ reason_code: "unknown-entry-point", count: missingEntryPointCount },
|
|
1688
1715
|
],
|
|
1689
1716
|
});
|
|
@@ -1762,6 +1789,7 @@ const analyzeGraphSemanticQuery = (root, query, graphStates, evidenceLimit) => {
|
|
|
1762
1789
|
missingEntryPoints,
|
|
1763
1790
|
publicApiGraph,
|
|
1764
1791
|
readyProjectStates,
|
|
1792
|
+
unavailableProjectOmissions,
|
|
1765
1793
|
uniqueSorted,
|
|
1766
1794
|
},
|
|
1767
1795
|
);
|
|
@@ -1841,6 +1869,7 @@ const recordAbstainedProjectOutcome = (state, result) => {
|
|
|
1841
1869
|
"no-project",
|
|
1842
1870
|
"ambiguous-project",
|
|
1843
1871
|
"blocking-diagnostics",
|
|
1872
|
+
"svelte-virtual-module-exports",
|
|
1844
1873
|
"unknown-symbol",
|
|
1845
1874
|
"incomplete-project-coverage",
|
|
1846
1875
|
]);
|
package/src/type-coupling.mjs
CHANGED
|
@@ -118,7 +118,6 @@ export const analyzeTypeCoupling = ({ root, query, states, evidenceLimit }, serv
|
|
|
118
118
|
const highCouplingThreshold = percentile(degrees, 0.9);
|
|
119
119
|
const topContributors = topCouplingContributors(perFile);
|
|
120
120
|
const cycles = query.includeCycles ? findCycles(edges) : [];
|
|
121
|
-
const unavailableProjectCount = states.length - readiness.ready.length;
|
|
122
121
|
const missingEntryPointCount = services.missingEntryPoints(query, resolvedEntryPoints);
|
|
123
122
|
const nestedFileOmissionCount = nestedCouplingOmissionCount(perFile, evidenceLimit);
|
|
124
123
|
const boundFile = (entry) => boundCoupledFile(entry, evidenceLimit);
|
|
@@ -159,7 +158,7 @@ export const analyzeTypeCoupling = ({ root, query, states, evidenceLimit }, serv
|
|
|
159
158
|
Math.max(0, cycles.length - evidenceLimit) +
|
|
160
159
|
nestedFileOmissionCount,
|
|
161
160
|
},
|
|
162
|
-
|
|
161
|
+
...services.unavailableProjectOmissions(states),
|
|
163
162
|
{ reason_code: "unknown-entry-point", count: missingEntryPointCount },
|
|
164
163
|
],
|
|
165
164
|
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import childProcess from "node:child_process";
|
|
2
|
+
import { syncBuiltinESMExports } from "node:module";
|
|
3
|
+
|
|
4
|
+
const INSTALL_MARKER = Symbol.for("fallow.type-aware.windows-child-process-policy");
|
|
5
|
+
|
|
6
|
+
const hiddenOptions = (options) => ({ ...options, windowsHide: true });
|
|
7
|
+
|
|
8
|
+
/** Keep TypeScript-Go child processes hidden when the sidecar runs on Windows. */
|
|
9
|
+
export const installWindowsChildProcessPolicy = ({
|
|
10
|
+
childProcess: processApi = childProcess,
|
|
11
|
+
platform = process.platform,
|
|
12
|
+
syncBuiltinESMExports: syncExports = syncBuiltinESMExports,
|
|
13
|
+
} = {}) => {
|
|
14
|
+
if (platform !== "win32" || processApi[INSTALL_MARKER] === true) return;
|
|
15
|
+
|
|
16
|
+
const originalSpawn = processApi.spawn;
|
|
17
|
+
processApi.spawn = function spawnHidden(command, args, options) {
|
|
18
|
+
if (Array.isArray(args)) {
|
|
19
|
+
return originalSpawn.call(this, command, args, hiddenOptions(options));
|
|
20
|
+
}
|
|
21
|
+
return originalSpawn.call(this, command, hiddenOptions(args));
|
|
22
|
+
};
|
|
23
|
+
Object.defineProperty(processApi, INSTALL_MARKER, { value: true });
|
|
24
|
+
syncExports();
|
|
25
|
+
};
|