Skip to content

Commit bcbe65a

Browse files
committed
Logic for finding binary: use shared logic between client and server.
1 parent adbe7c6 commit bcbe65a

File tree

12 files changed

+240
-200
lines changed

12 files changed

+240
-200
lines changed

client/src/commands.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,12 @@ export { pasteAsRescriptJson } from "./commands/paste_as_rescript_json";
1313
export { pasteAsRescriptJsx } from "./commands/paste_as_rescript_jsx";
1414

1515
export const codeAnalysisWithReanalyze = (
16-
targetDir: string | null,
1716
diagnosticsCollection: DiagnosticCollection,
1817
diagnosticsResultCodeActions: DiagnosticsResultCodeActionsMap,
1918
outputChannel: OutputChannel,
2019
codeAnalysisRunningStatusBarItem: StatusBarItem,
2120
) => {
2221
runCodeAnalysisWithReanalyze(
23-
targetDir,
2422
diagnosticsCollection,
2523
diagnosticsResultCodeActions,
2624
outputChannel,

client/src/commands/code_analysis.ts

Lines changed: 21 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@ import {
1515
OutputChannel,
1616
StatusBarItem,
1717
} from "vscode";
18+
import { getBinaryPath, NormalizedPath, normalizePath } from "../utils";
1819
import {
19-
findProjectRootOfFileInDir,
20-
getBinaryPath,
21-
NormalizedPath,
22-
} from "../utils";
20+
findBinary,
21+
findBinary as findSharedBinary,
22+
} from "../../../shared/src/findBinary";
23+
import { findProjectRootOfFile } from "../../../shared/src/projectRoots";
2324

2425
export let statusBarItem = {
2526
setToStopText: (codeAnalysisRunningStatusBarItem: StatusBarItem) => {
@@ -203,58 +204,41 @@ let resultsToDiagnostics = (
203204
};
204205
};
205206

206-
export const runCodeAnalysisWithReanalyze = (
207-
targetDir: string | null,
207+
export const runCodeAnalysisWithReanalyze = async (
208208
diagnosticsCollection: DiagnosticCollection,
209209
diagnosticsResultCodeActions: DiagnosticsResultCodeActionsMap,
210210
outputChannel: OutputChannel,
211211
codeAnalysisRunningStatusBarItem: StatusBarItem,
212212
) => {
213213
let currentDocument = window.activeTextEditor.document;
214-
let cwd = targetDir ?? path.dirname(currentDocument.uri.fsPath);
215214

216-
// Resolve the project root from `cwd` (which is the workspace root when code analysis is started),
217-
// rather than from the currently-open file (which may be in a subpackage).
218-
let projectRootPath: NormalizedPath | null = findProjectRootOfFileInDir(
219-
path.join(cwd, "bsconfig.json"),
215+
let projectRootPath: NormalizedPath | null = normalizePath(
216+
findProjectRootOfFile(currentDocument.uri.fsPath),
220217
);
221-
if (projectRootPath == null) {
222-
projectRootPath = findProjectRootOfFileInDir(currentDocument.uri.fsPath);
223-
}
224-
225-
// Try v12+ path first: @rescript/{platform}-{arch}/bin/rescript-tools.exe
226-
// Then fall back to legacy paths via getBinaryPath
227-
let binaryPath: string | null = null;
228-
if (projectRootPath != null) {
229-
const v12Path = path.join(
230-
projectRootPath,
231-
"node_modules",
232-
"@rescript",
233-
`${process.platform}-${process.arch}`,
234-
"bin",
235-
"rescript-tools.exe",
236-
);
237-
if (fs.existsSync(v12Path)) {
238-
binaryPath = v12Path;
239-
}
240-
}
218+
let binaryPath: string | null = await findBinary({
219+
projectRootPath,
220+
binary: "rescript-tools.exe",
221+
});
241222
if (binaryPath == null) {
242-
binaryPath =
243-
getBinaryPath("rescript-tools.exe", projectRootPath) ??
244-
getBinaryPath("rescript-editor-analysis.exe", projectRootPath);
223+
binaryPath = await findBinary({
224+
projectRootPath,
225+
binary: "rescript-editor-analysis.exe",
226+
});
245227
}
246228

247229
if (binaryPath === null) {
248230
window.showErrorMessage("Binary executable not found.");
249231
return;
250232
}
251233

234+
// Strip everything after the outermost node_modules segment to get the project root.
235+
let cwd =
236+
binaryPath.match(/^(.*?)[\\/]+node_modules([\\/]+|$)/)?.[1] ?? binaryPath;
237+
252238
statusBarItem.setToRunningText(codeAnalysisRunningStatusBarItem);
253239

254240
let opts = ["reanalyze", "-json"];
255-
let p = cp.spawn(binaryPath, opts, {
256-
cwd,
257-
});
241+
let p = cp.spawn(binaryPath, opts, { cwd });
258242

259243
if (p.stdout == null) {
260244
statusBarItem.setToFailed(codeAnalysisRunningStatusBarItem);

client/src/extension.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,6 @@ export function activate(context: ExtensionContext) {
155155
client.onNotification("rescript/compilationFinished", () => {
156156
if (inCodeAnalysisState.active === true) {
157157
customCommands.codeAnalysisWithReanalyze(
158-
inCodeAnalysisState.activatedFromDirectory,
159158
diagnosticsCollection,
160159
diagnosticsResultCodeActions,
161160
outputChannel,
@@ -308,8 +307,7 @@ export function activate(context: ExtensionContext) {
308307

309308
let inCodeAnalysisState: {
310309
active: boolean;
311-
activatedFromDirectory: string | null;
312-
} = { active: false, activatedFromDirectory: null };
310+
} = { active: false };
313311

314312
// This code actions provider yields the code actions potentially extracted
315313
// from the code analysis to the editor.
@@ -442,19 +440,12 @@ export function activate(context: ExtensionContext) {
442440

443441
inCodeAnalysisState.active = true;
444442

445-
// Run reanalyze from the workspace root (so monorepos consistently analyze the root project),
446-
// instead of from whatever file happened to be active when analysis was started.
447-
const wsFolder = workspace.getWorkspaceFolder(currentDocument.uri);
448-
inCodeAnalysisState.activatedFromDirectory =
449-
wsFolder?.uri.fsPath ?? path.dirname(currentDocument.uri.fsPath);
450-
451443
codeAnalysisRunningStatusBarItem.command =
452444
"rescript-vscode.stop_code_analysis";
453445
codeAnalysisRunningStatusBarItem.show();
454446
statusBarItem.setToStopText(codeAnalysisRunningStatusBarItem);
455447

456448
customCommands.codeAnalysisWithReanalyze(
457-
inCodeAnalysisState.activatedFromDirectory,
458449
diagnosticsCollection,
459450
diagnosticsResultCodeActions,
460451
outputChannel,
@@ -464,7 +455,6 @@ export function activate(context: ExtensionContext) {
464455

465456
commands.registerCommand("rescript-vscode.stop_code_analysis", () => {
466457
inCodeAnalysisState.active = false;
467-
inCodeAnalysisState.activatedFromDirectory = null;
468458

469459
diagnosticsCollection.clear();
470460
diagnosticsResultCodeActions.clear();

client/src/utils.ts

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ import * as path from "path";
22
import * as fs from "fs";
33
import * as os from "os";
44
import { DocumentUri } from "vscode-languageclient";
5+
import { findBinary, type BinaryName } from "../../shared/src/findBinary";
6+
import {
7+
findProjectRootOfFileInDir as findProjectRootOfFileInDirShared,
8+
normalizePath as normalizePathShared,
9+
} from "../../shared/src/projectRoots";
510

611
/*
712
* Much of the code in here is duplicated from the server code.
@@ -27,7 +32,7 @@ export type NormalizedPath = string & { __brand: "NormalizedPath" };
2732
*/
2833
export function normalizePath(filePath: string | null): NormalizedPath | null {
2934
// `path.normalize` ensures we can assume string is now NormalizedPath
30-
return filePath != null ? (path.normalize(filePath) as NormalizedPath) : null;
35+
return normalizePathShared(filePath) as NormalizedPath | null;
3136
}
3237

3338
type binaryName = "rescript-editor-analysis.exe" | "rescript-tools.exe";
@@ -83,25 +88,7 @@ export const createFileInTempDir = (prefix = "", extension = "") => {
8388
export let findProjectRootOfFileInDir = (
8489
source: string,
8590
): NormalizedPath | null => {
86-
const normalizedSource = normalizePath(source);
87-
if (normalizedSource == null) {
88-
return null;
89-
}
90-
const dir = normalizePath(path.dirname(normalizedSource));
91-
if (dir == null) {
92-
return null;
93-
}
94-
if (
95-
fs.existsSync(path.join(dir, "rescript.json")) ||
96-
fs.existsSync(path.join(dir, "bsconfig.json"))
97-
) {
98-
return dir;
99-
} else {
100-
if (dir === normalizedSource) {
101-
// reached top
102-
return null;
103-
} else {
104-
return findProjectRootOfFileInDir(dir);
105-
}
106-
}
91+
return normalizePath(findProjectRootOfFileInDirShared(source));
10792
};
93+
94+
export { findBinary, BinaryName };

client/tsconfig.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44
"target": "es2019",
55
"lib": ["ES2019"],
66
"outDir": "out",
7-
"rootDir": "src",
7+
"rootDirs": ["src", "../shared/src"],
88
"sourceMap": true
99
},
10-
"include": ["src"],
10+
"include": ["src", "../shared/src"],
1111
"exclude": ["node_modules"]
1212
}

server/src/server.ts

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -118,28 +118,16 @@ let findRescriptBinary = async (
118118
): Promise<utils.NormalizedPath | null> => {
119119
if (
120120
config.extensionConfiguration.binaryPath != null &&
121-
(fs.existsSync(
122-
path.join(config.extensionConfiguration.binaryPath, "rescript.exe"),
123-
) ||
124-
fs.existsSync(
125-
path.join(config.extensionConfiguration.binaryPath, "rescript"),
126-
))
121+
fs.existsSync(
122+
path.join(config.extensionConfiguration.binaryPath, "rescript"),
123+
)
127124
) {
128125
return utils.normalizePath(
129-
fs.existsSync(
130-
path.join(config.extensionConfiguration.binaryPath, "rescript.exe"),
131-
)
132-
? path.join(config.extensionConfiguration.binaryPath, "rescript.exe")
133-
: path.join(config.extensionConfiguration.binaryPath, "rescript"),
126+
path.join(config.extensionConfiguration.binaryPath, "rescript"),
134127
);
135128
}
136129

137-
// Prefer the native rescript.exe (v12+) for spawning `build -w`.
138-
// Fall back to the legacy/JS wrapper `rescript` path if needed.
139-
return (
140-
(await utils.findRescriptExeBinary(projectRootPath)) ??
141-
(await utils.findRescriptBinary(projectRootPath))
142-
);
130+
return utils.findRescriptBinary(projectRootPath);
143131
};
144132

145133
let createInterfaceRequest = new v.RequestType<

server/src/utils.ts

Lines changed: 12 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ import * as os from "os";
1212
import semver from "semver";
1313
import { fileURLToPath, pathToFileURL } from "url";
1414

15+
import {
16+
findBinary as findSharedBinary,
17+
type BinaryName,
18+
} from "../../shared/src/findBinary";
19+
import { findProjectRootOfFileInDir as findProjectRootOfFileInDirShared } from "../../shared/src/projectRoots";
1520
import * as codeActions from "./codeActions";
1621
import * as c from "./constants";
1722
import * as lookup from "./lookup";
@@ -85,23 +90,7 @@ export let createFileInTempDir = (extension = ""): NormalizedPath => {
8590
function findProjectRootOfFileInDir(
8691
source: NormalizedPath,
8792
): NormalizedPath | null {
88-
const dir = normalizePath(path.dirname(source));
89-
if (dir == null) {
90-
return null;
91-
}
92-
if (
93-
fs.existsSync(path.join(dir, c.rescriptJsonPartialPath)) ||
94-
fs.existsSync(path.join(dir, c.bsconfigPartialPath))
95-
) {
96-
return dir;
97-
} else {
98-
if (dir === source) {
99-
// reached top
100-
return null;
101-
} else {
102-
return findProjectRootOfFileInDir(dir);
103-
}
104-
}
93+
return normalizePath(findProjectRootOfFileInDirShared(source));
10594
}
10695

10796
/**
@@ -216,98 +205,14 @@ export let getProjectFile = (
216205
// We won't know which version is in the project root until we read and parse `{project_root}/node_modules/rescript/package.json`
217206
let findBinary = async (
218207
projectRootPath: NormalizedPath | null,
219-
binary:
220-
| "bsc.exe"
221-
| "rescript-editor-analysis.exe"
222-
| "rescript"
223-
| "rewatch.exe"
224-
| "rescript.exe",
208+
binary: BinaryName,
225209
): Promise<NormalizedPath | null> => {
226-
if (config.extensionConfiguration.platformPath != null) {
227-
const result = path.join(
228-
config.extensionConfiguration.platformPath,
229-
binary,
230-
);
231-
return normalizePath(result);
232-
}
233-
234-
if (projectRootPath !== null) {
235-
try {
236-
const compilerInfo = path.resolve(
237-
projectRootPath,
238-
c.compilerInfoPartialPath,
239-
);
240-
const contents = await fsAsync.readFile(compilerInfo, "utf8");
241-
const compileInfo = JSON.parse(contents);
242-
if (compileInfo && compileInfo.bsc_path) {
243-
const bsc_path = compileInfo.bsc_path;
244-
if (binary === "bsc.exe") {
245-
return normalizePath(bsc_path);
246-
} else {
247-
const binary_path = path.join(path.dirname(bsc_path), binary);
248-
return normalizePath(binary_path);
249-
}
250-
}
251-
} catch {}
252-
}
253-
254-
const rescriptDir = lookup.findFilePathFromProjectRoot(
210+
const result = await findSharedBinary({
255211
projectRootPath,
256-
path.join("node_modules", "rescript"),
257-
);
258-
if (rescriptDir == null) {
259-
return null;
260-
}
261-
262-
let rescriptVersion = null;
263-
let rescriptJSWrapperPath = null;
264-
try {
265-
const rescriptPackageJSONPath = path.join(rescriptDir, "package.json");
266-
const rescriptPackageJSON = JSON.parse(
267-
await fsAsync.readFile(rescriptPackageJSONPath, "utf-8"),
268-
);
269-
rescriptVersion = rescriptPackageJSON.version;
270-
rescriptJSWrapperPath = rescriptPackageJSON.bin.rescript;
271-
} catch (error) {
272-
return null;
273-
}
274-
275-
let binaryPath: string | null = null;
276-
if (binary == "rescript") {
277-
// Can't use the native bsb/rescript since we might need the watcher -w
278-
// flag, which is only in the JS wrapper
279-
binaryPath = path.join(rescriptDir, rescriptJSWrapperPath);
280-
} else if (semver.gte(rescriptVersion, "12.0.0-alpha.13")) {
281-
// TODO: export `binPaths` from `rescript` package so that we don't need to
282-
// copy the logic for figuring out `target`.
283-
const target = `${process.platform}-${process.arch}`;
284-
// Use realpathSync to resolve symlinks, which is necessary for package
285-
// managers like Deno and pnpm that use symlinked node_modules structures.
286-
const targetPackagePath = path.join(
287-
fs.realpathSync(rescriptDir),
288-
"..",
289-
`@rescript/${target}/bin.js`,
290-
);
291-
const { binPaths } = await import(targetPackagePath);
292-
293-
if (binary == "bsc.exe") {
294-
binaryPath = binPaths.bsc_exe;
295-
} else if (binary == "rescript-editor-analysis.exe") {
296-
binaryPath = binPaths.rescript_editor_analysis_exe;
297-
} else if (binary == "rewatch.exe") {
298-
binaryPath = binPaths.rewatch_exe;
299-
} else if (binary == "rescript.exe") {
300-
binaryPath = binPaths.rescript_exe;
301-
}
302-
} else {
303-
binaryPath = path.join(rescriptDir, c.platformDir, binary);
304-
}
305-
306-
if (binaryPath != null && fs.existsSync(binaryPath)) {
307-
return normalizePath(binaryPath);
308-
} else {
309-
return null;
310-
}
212+
binary,
213+
platformPath: config.extensionConfiguration.platformPath ?? null,
214+
});
215+
return normalizePath(result);
311216
};
312217

313218
export let findRescriptBinary = (projectRootPath: NormalizedPath | null) =>

0 commit comments

Comments
 (0)