typegraph-mcp 0.9.47 → 0.9.48
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 +12 -3
- package/benchmark.ts +12 -3
- package/biome-config.ts +76 -0
- package/check.ts +67 -83
- package/cli.ts +43 -62
- package/commands/check.md +1 -1
- package/dist/benchmark.js +56 -19
- package/dist/check.js +213 -153
- package/dist/cli.js +1310 -1219
- package/dist/module-graph.js +22 -9
- package/dist/server.js +82 -38
- package/dist/smoke-test.js +66 -25
- package/dist/tsserver-client.js +35 -10
- package/module-graph.ts +29 -9
- package/package.json +5 -4
- package/scripts/self-install.ts +78 -0
- package/server.ts +26 -19
- package/smoke-test.ts +15 -6
- package/tsserver-client.ts +49 -11
- package/engine-sync-test.ts +0 -262
- package/export-surface-test.ts +0 -202
- package/install-oxlint-test.ts +0 -116
- package/smoke-test-selection-test.ts +0 -67
package/export-surface-test.ts
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env npx tsx
|
|
2
|
-
|
|
3
|
-
import * as assert from "node:assert/strict";
|
|
4
|
-
import * as fs from "node:fs";
|
|
5
|
-
import * as os from "node:os";
|
|
6
|
-
import * as path from "node:path";
|
|
7
|
-
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
8
|
-
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
9
|
-
import { CallToolResultSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
10
|
-
|
|
11
|
-
type ModuleExportRecord = {
|
|
12
|
-
symbol: string;
|
|
13
|
-
kind: string;
|
|
14
|
-
line: number;
|
|
15
|
-
type: string | null;
|
|
16
|
-
exportKind: "value" | "type";
|
|
17
|
-
isTypeOnly: boolean;
|
|
18
|
-
isNamespace: boolean;
|
|
19
|
-
source: "local" | "re-export" | "star-re-export";
|
|
20
|
-
from: string | null;
|
|
21
|
-
definedIn: string;
|
|
22
|
-
definedLine: number | null;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
type ModuleExportsResult = {
|
|
26
|
-
file: string;
|
|
27
|
-
exports: ModuleExportRecord[];
|
|
28
|
-
count: number;
|
|
29
|
-
localCount: number;
|
|
30
|
-
reExportCount: number;
|
|
31
|
-
typeOnlyCount: number;
|
|
32
|
-
valueCount: number;
|
|
33
|
-
namespaceExportCount: number;
|
|
34
|
-
hasLocalRuntimeExports: boolean;
|
|
35
|
-
isPrimarilyBarrel: boolean;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
function copyDir(src: string, dest: string): void {
|
|
39
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
40
|
-
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
41
|
-
const srcPath = path.join(src, entry.name);
|
|
42
|
-
const destPath = path.join(dest, entry.name);
|
|
43
|
-
if (entry.isDirectory()) {
|
|
44
|
-
copyDir(srcPath, destPath);
|
|
45
|
-
} else {
|
|
46
|
-
fs.copyFileSync(srcPath, destPath);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function findExport(
|
|
52
|
-
result: ModuleExportsResult,
|
|
53
|
-
symbol: string,
|
|
54
|
-
exportKind: "value" | "type"
|
|
55
|
-
): ModuleExportRecord {
|
|
56
|
-
const found = result.exports.find(
|
|
57
|
-
(item) => item.symbol === symbol && item.exportKind === exportKind
|
|
58
|
-
);
|
|
59
|
-
assert.ok(found, `Expected ${symbol}:${exportKind} in ${result.file}`);
|
|
60
|
-
return found;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function assertProjectPath(actual: string | null, expected: string): void {
|
|
64
|
-
assert.ok(actual, `Expected path ${expected}`);
|
|
65
|
-
const normalized = actual.replaceAll("\\", "/");
|
|
66
|
-
assert.equal(normalized, expected, `Expected ${normalized} to equal ${expected}`);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async function main(): Promise<void> {
|
|
70
|
-
const repoRoot = import.meta.dirname;
|
|
71
|
-
const fixtureRoot = path.join(repoRoot, ".fixtures/export-surface");
|
|
72
|
-
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "typegraph-export-surface-"));
|
|
73
|
-
const projectRoot = path.join(tempRoot, "project");
|
|
74
|
-
|
|
75
|
-
copyDir(fixtureRoot, projectRoot);
|
|
76
|
-
fs.mkdirSync(path.join(projectRoot, "node_modules"), { recursive: true });
|
|
77
|
-
fs.symlinkSync(
|
|
78
|
-
path.join(repoRoot, "node_modules/typescript"),
|
|
79
|
-
path.join(projectRoot, "node_modules/typescript"),
|
|
80
|
-
"dir"
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
const client = new Client({ name: "export-surface-test", version: "1.0.0" });
|
|
84
|
-
const transport = new StdioClientTransport({
|
|
85
|
-
command: path.join(repoRoot, "node_modules/.bin/tsx"),
|
|
86
|
-
args: [path.join(repoRoot, "server.ts")],
|
|
87
|
-
cwd: projectRoot,
|
|
88
|
-
env: {
|
|
89
|
-
TYPEGRAPH_PROJECT_ROOT: projectRoot,
|
|
90
|
-
TYPEGRAPH_TSCONFIG: path.join(projectRoot, "tsconfig.json"),
|
|
91
|
-
},
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
try {
|
|
95
|
-
await client.connect(transport);
|
|
96
|
-
|
|
97
|
-
async function moduleExports(file: string): Promise<ModuleExportsResult> {
|
|
98
|
-
const result = await client.request(
|
|
99
|
-
{
|
|
100
|
-
method: "tools/call",
|
|
101
|
-
params: {
|
|
102
|
-
name: "ts_module_exports",
|
|
103
|
-
arguments: { file },
|
|
104
|
-
},
|
|
105
|
-
},
|
|
106
|
-
CallToolResultSchema
|
|
107
|
-
);
|
|
108
|
-
|
|
109
|
-
const content = result.content[0];
|
|
110
|
-
assert.ok(content?.type === "text", `Expected text response for ${file}`);
|
|
111
|
-
return JSON.parse(content.text) as ModuleExportsResult;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const source = await moduleExports("src/source.ts");
|
|
115
|
-
const defaultExport = findExport(source, "default", "value");
|
|
116
|
-
assert.equal(defaultExport.source, "local");
|
|
117
|
-
assert.equal(defaultExport.kind, "function");
|
|
118
|
-
assert.equal(defaultExport.definedIn, "src/source.ts");
|
|
119
|
-
assert.equal(defaultExport.definedLine, 1);
|
|
120
|
-
|
|
121
|
-
const defaultExpression = await moduleExports("src/default-expression.ts");
|
|
122
|
-
const expressionDefault = findExport(defaultExpression, "default", "value");
|
|
123
|
-
assert.equal(expressionDefault.source, "local");
|
|
124
|
-
assert.equal(expressionDefault.definedIn, "src/default-expression.ts");
|
|
125
|
-
|
|
126
|
-
const barrel = await moduleExports("src/barrel.ts");
|
|
127
|
-
const barrelValue = findExport(barrel, "value", "value");
|
|
128
|
-
const barrelUserShape = findExport(barrel, "UserShape", "type");
|
|
129
|
-
assert.equal(barrelValue.source, "star-re-export");
|
|
130
|
-
assert.equal(barrelUserShape.source, "star-re-export");
|
|
131
|
-
assert.equal(barrel.exports.some((item) => item.symbol === "default"), false);
|
|
132
|
-
assert.equal(barrel.exports.some((item) => item.symbol === "buildUser"), false);
|
|
133
|
-
assert.equal(barrelUserShape.isTypeOnly, true);
|
|
134
|
-
assert.equal(barrel.typeOnlyCount, 2);
|
|
135
|
-
assert.equal(barrel.isPrimarilyBarrel, true);
|
|
136
|
-
assert.equal(barrel.hasLocalRuntimeExports, false);
|
|
137
|
-
|
|
138
|
-
const typeReExport = await moduleExports("src/reexport-type.ts");
|
|
139
|
-
const userShape = findExport(typeReExport, "UserShape", "type");
|
|
140
|
-
assert.equal(userShape.source, "re-export");
|
|
141
|
-
assert.equal(userShape.isTypeOnly, true);
|
|
142
|
-
assert.equal(userShape.exportKind, "type");
|
|
143
|
-
assert.equal(typeReExport.typeOnlyCount, 1);
|
|
144
|
-
assert.equal(typeReExport.valueCount, 0);
|
|
145
|
-
|
|
146
|
-
const namedReExport = await moduleExports("src/named-reexport.ts");
|
|
147
|
-
const aliasedValue = findExport(namedReExport, "aliasedValue", "value");
|
|
148
|
-
assert.equal(aliasedValue.source, "re-export");
|
|
149
|
-
assertProjectPath(aliasedValue.from, "src/source.ts");
|
|
150
|
-
assert.equal(aliasedValue.definedIn, "src/source.ts");
|
|
151
|
-
assert.equal(namedReExport.namespaceExportCount, 0);
|
|
152
|
-
|
|
153
|
-
const namespaceReExport = await moduleExports("src/namespace-reexport.ts");
|
|
154
|
-
const models = findExport(namespaceReExport, "Models", "value");
|
|
155
|
-
assert.equal(models.source, "re-export");
|
|
156
|
-
assert.equal(models.isNamespace, true);
|
|
157
|
-
assert.equal(namespaceReExport.namespaceExportCount, 1);
|
|
158
|
-
|
|
159
|
-
const mixed = await moduleExports("src/mixed.ts");
|
|
160
|
-
const localValue = findExport(mixed, "SessionId", "value");
|
|
161
|
-
const localType = findExport(mixed, "SessionId", "type");
|
|
162
|
-
const externalValue = findExport(mixed, "externalValue", "value");
|
|
163
|
-
const externalUserShape = findExport(mixed, "ExternalUserShape", "type");
|
|
164
|
-
assert.equal(localValue.source, "local");
|
|
165
|
-
assert.equal(localType.source, "local");
|
|
166
|
-
assert.equal(localType.kind, "type");
|
|
167
|
-
assert.equal(localType.definedLine, 2);
|
|
168
|
-
assert.equal(externalValue.source, "re-export");
|
|
169
|
-
assert.equal(externalUserShape.source, "re-export");
|
|
170
|
-
assert.equal(externalUserShape.isTypeOnly, true);
|
|
171
|
-
assert.equal(mixed.localCount, 2);
|
|
172
|
-
assert.equal(mixed.reExportCount, 2);
|
|
173
|
-
assert.equal(mixed.typeOnlyCount, 2);
|
|
174
|
-
assert.equal(mixed.hasLocalRuntimeExports, true);
|
|
175
|
-
assert.equal(mixed.isPrimarilyBarrel, false);
|
|
176
|
-
|
|
177
|
-
const collision = await moduleExports("src/collision-barrel.ts");
|
|
178
|
-
assert.equal(collision.exports.some((item) => item.symbol === "dup"), false);
|
|
179
|
-
assert.equal(collision.count, 0);
|
|
180
|
-
|
|
181
|
-
console.log("");
|
|
182
|
-
console.log("typegraph-mcp Export Surface Test");
|
|
183
|
-
console.log("=================================");
|
|
184
|
-
console.log(" ✓ local default exports are reported as default");
|
|
185
|
-
console.log(" ✓ anonymous default exports stay visible");
|
|
186
|
-
console.log(" ✓ barrel star re-exports");
|
|
187
|
-
console.log(" ✓ barrel star re-exports exclude default exports");
|
|
188
|
-
console.log(" ✓ type-only named re-exports");
|
|
189
|
-
console.log(" ✓ named alias re-exports");
|
|
190
|
-
console.log(" ✓ namespace re-exports");
|
|
191
|
-
console.log(" ✓ mixed local + re-export modules");
|
|
192
|
-
console.log(" ✓ conflicting star re-exports stay hidden");
|
|
193
|
-
} finally {
|
|
194
|
-
await transport.close().catch(() => {});
|
|
195
|
-
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
main().catch((err) => {
|
|
200
|
-
console.error(err);
|
|
201
|
-
process.exit(1);
|
|
202
|
-
});
|
package/install-oxlint-test.ts
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env npx tsx
|
|
2
|
-
|
|
3
|
-
import * as assert from "node:assert/strict";
|
|
4
|
-
import { execFileSync } from "node:child_process";
|
|
5
|
-
import * as fs from "node:fs";
|
|
6
|
-
import * as os from "node:os";
|
|
7
|
-
import * as path from "node:path";
|
|
8
|
-
|
|
9
|
-
function copyDir(src: string, dest: string): void {
|
|
10
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
11
|
-
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
12
|
-
const srcPath = path.join(src, entry.name);
|
|
13
|
-
const destPath = path.join(dest, entry.name);
|
|
14
|
-
if (entry.isDirectory()) {
|
|
15
|
-
copyDir(srcPath, destPath);
|
|
16
|
-
} else {
|
|
17
|
-
fs.copyFileSync(srcPath, destPath);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function runTsx(
|
|
23
|
-
toolRoot: string,
|
|
24
|
-
args: string[],
|
|
25
|
-
cwd: string,
|
|
26
|
-
env: NodeJS.ProcessEnv = process.env
|
|
27
|
-
): string {
|
|
28
|
-
return execFileSync(path.join(toolRoot, "node_modules/.bin/tsx"), args, {
|
|
29
|
-
cwd,
|
|
30
|
-
encoding: "utf-8",
|
|
31
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
32
|
-
env,
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function assertIncludes(text: string, expected: string): void {
|
|
37
|
-
assert.ok(
|
|
38
|
-
text.includes(expected),
|
|
39
|
-
`Expected output to include:\n${expected}\n\nActual output:\n${text}`
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
async function main(): Promise<void> {
|
|
44
|
-
const repoRoot = import.meta.dirname;
|
|
45
|
-
const fixtureRoot = path.join(repoRoot, ".fixtures/install-oxlint");
|
|
46
|
-
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "typegraph-install-oxlint-"));
|
|
47
|
-
const projectRoot = path.join(tempRoot, "project");
|
|
48
|
-
const homeRoot = path.join(tempRoot, "home");
|
|
49
|
-
|
|
50
|
-
copyDir(fixtureRoot, projectRoot);
|
|
51
|
-
fs.mkdirSync(path.join(projectRoot, "node_modules"), { recursive: true });
|
|
52
|
-
fs.symlinkSync(
|
|
53
|
-
path.join(repoRoot, "node_modules/typescript"),
|
|
54
|
-
path.join(projectRoot, "node_modules/typescript"),
|
|
55
|
-
"dir"
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
try {
|
|
59
|
-
fs.mkdirSync(homeRoot, { recursive: true });
|
|
60
|
-
const testEnv = { ...process.env, HOME: homeRoot };
|
|
61
|
-
const setupOutput = runTsx(
|
|
62
|
-
repoRoot,
|
|
63
|
-
[path.join(repoRoot, "cli.ts"), "setup", "--yes"],
|
|
64
|
-
projectRoot,
|
|
65
|
-
testEnv
|
|
66
|
-
);
|
|
67
|
-
const pluginRoot = path.join(projectRoot, "plugins/typegraph-mcp");
|
|
68
|
-
|
|
69
|
-
const tsconfig = fs.readFileSync(path.join(projectRoot, "tsconfig.json"), "utf-8");
|
|
70
|
-
const oxlint = fs.readFileSync(path.join(projectRoot, ".oxlintrc.json"), "utf-8");
|
|
71
|
-
const eslint = fs.readFileSync(path.join(projectRoot, "eslint.config.js"), "utf-8");
|
|
72
|
-
|
|
73
|
-
assertIncludes(tsconfig, '"$schema": "http://json.schemastore.org/tsconfig"');
|
|
74
|
-
assertIncludes(tsconfig, '"exclude": ["plugins/**"]');
|
|
75
|
-
assertIncludes(oxlint, '"ignorePatterns": [');
|
|
76
|
-
assertIncludes(oxlint, '"plugins/**"');
|
|
77
|
-
assertIncludes(eslint, 'const config = [\n { ignores: ["plugins/**"] },');
|
|
78
|
-
assert.ok(fs.existsSync(path.join(pluginRoot, "cli.ts")), "Expected installed plugin CLI");
|
|
79
|
-
|
|
80
|
-
assertIncludes(setupOutput, 'Added "plugins/**" to tsconfig.json exclude');
|
|
81
|
-
assertIncludes(setupOutput, 'Added "plugins/**" to .oxlintrc.json ignorePatterns');
|
|
82
|
-
assertIncludes(setupOutput, 'Added "plugins/**" to eslint.config.js ignores');
|
|
83
|
-
assertIncludes(setupOutput, "Oxlint ignores plugins/ (.oxlintrc.json)");
|
|
84
|
-
assertIncludes(setupOutput, "ESLint ignores plugins/ (eslint.config.js)");
|
|
85
|
-
|
|
86
|
-
const checkOutput = runTsx(
|
|
87
|
-
pluginRoot,
|
|
88
|
-
[path.join(pluginRoot, "cli.ts"), "check"],
|
|
89
|
-
projectRoot,
|
|
90
|
-
testEnv
|
|
91
|
-
);
|
|
92
|
-
assertIncludes(checkOutput, "Oxlint ignores plugins/ (.oxlintrc.json)");
|
|
93
|
-
assertIncludes(checkOutput, "ESLint ignores plugins/ (eslint.config.js)");
|
|
94
|
-
assert.ok(
|
|
95
|
-
!checkOutput.includes("Lint config check (no ESLint or Oxlint config found)"),
|
|
96
|
-
`Did not expect lint config detection to be skipped:\n${checkOutput}`
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
console.log("");
|
|
100
|
-
console.log("typegraph-mcp Install Oxlint Test");
|
|
101
|
-
console.log("=================================");
|
|
102
|
-
console.log(" ✓ tsconfig schema URL preserved during exclude patch");
|
|
103
|
-
console.log(" ✓ tsconfig exclude patch ignores unrelated plugins text");
|
|
104
|
-
console.log(" ✓ .oxlintrc.json patched with plugins ignore");
|
|
105
|
-
console.log(" ✓ eslint.config.js named flat-config array patched with plugins ignore");
|
|
106
|
-
console.log(" ✓ installed plugin health check recognizes Oxlint config");
|
|
107
|
-
console.log(" ✓ installed plugin health check recognizes ESLint config");
|
|
108
|
-
} finally {
|
|
109
|
-
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
main().catch((err) => {
|
|
114
|
-
console.error(err);
|
|
115
|
-
process.exit(1);
|
|
116
|
-
});
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env npx tsx
|
|
2
|
-
|
|
3
|
-
import * as assert from "node:assert/strict";
|
|
4
|
-
import type { NavBarItem, QuickInfoResult } from "./tsserver-client.js";
|
|
5
|
-
import { selectQuickInfoSymbol } from "./smoke-test.js";
|
|
6
|
-
|
|
7
|
-
const syntheticCallback: NavBarItem = {
|
|
8
|
-
text: "Alpine.data('table') callback",
|
|
9
|
-
kind: "function",
|
|
10
|
-
kindModifiers: "",
|
|
11
|
-
spans: [
|
|
12
|
-
{
|
|
13
|
-
start: { line: 1, offset: 1 },
|
|
14
|
-
end: { line: 1, offset: 21 },
|
|
15
|
-
},
|
|
16
|
-
],
|
|
17
|
-
childItems: [],
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
const typedConst: NavBarItem = {
|
|
21
|
-
text: "booleanFilterFn",
|
|
22
|
-
kind: "const",
|
|
23
|
-
kindModifiers: "export",
|
|
24
|
-
spans: [
|
|
25
|
-
{
|
|
26
|
-
start: { line: 2, offset: 1 },
|
|
27
|
-
end: { line: 2, offset: 42 },
|
|
28
|
-
},
|
|
29
|
-
],
|
|
30
|
-
childItems: [],
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const quickInfo: QuickInfoResult = {
|
|
34
|
-
displayString: "const booleanFilterFn: FilterFn<any, any>",
|
|
35
|
-
documentation: "",
|
|
36
|
-
kind: "const",
|
|
37
|
-
kindModifiers: "export",
|
|
38
|
-
start: { line: 2, offset: 14 },
|
|
39
|
-
end: { line: 2, offset: 29 },
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
const calls: Array<{ line: number; offset: number }> = [];
|
|
43
|
-
const client = {
|
|
44
|
-
async quickinfo(_file: string, line: number, offset: number) {
|
|
45
|
-
calls.push({ line, offset });
|
|
46
|
-
return line === 2 && offset === 14 ? quickInfo : null;
|
|
47
|
-
},
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const selection = await selectQuickInfoSymbol(
|
|
51
|
-
client,
|
|
52
|
-
"src/main.ts",
|
|
53
|
-
[syntheticCallback, typedConst],
|
|
54
|
-
["Alpine.data('table', () => ({}))", "export const booleanFilterFn = () => true"]
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
assert.ok(selection);
|
|
58
|
-
assert.equal(selection.symbol.text, "booleanFilterFn");
|
|
59
|
-
assert.deepEqual(selection.position, { line: 2, offset: 14 });
|
|
60
|
-
assert.equal(selection.info, quickInfo);
|
|
61
|
-
assert.deepEqual(calls, [{ line: 2, offset: 14 }]);
|
|
62
|
-
|
|
63
|
-
console.log("");
|
|
64
|
-
console.log("typegraph-mcp Smoke Test Selection Test");
|
|
65
|
-
console.log("=======================================");
|
|
66
|
-
console.log(" ✓ synthetic callbacks are skipped for typed declarations");
|
|
67
|
-
console.log(" ✓ quickinfo probes the declaration name rather than the span keyword");
|