Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

#### :bug: Bug fix

- Fix binary path resolution for ReScript v12+ projects. The extension now correctly locates binaries via `@rescript/{platform}/bin.js`.
- Take namespace into account for incremental cleanup. https://github.com/rescript-lang/rescript-vscode/pull/1164
- Potential race condition in incremental compilation. https://github.com/rescript-lang/rescript-vscode/pull/1167
- Fix extension crash triggered by incremental compilation. https://github.com/rescript-lang/rescript-vscode/pull/1169
Expand Down
22 changes: 16 additions & 6 deletions client/src/commands/code_analysis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ import {
OutputChannel,
StatusBarItem,
} from "vscode";
import * as semver from "semver";
import {
findProjectRootOfFileInDir,
getBinaryPath,
NormalizedPath,
findReScriptVersion,
} from "../utils";

export let statusBarItem = {
Expand Down Expand Up @@ -216,12 +218,20 @@ export const runCodeAnalysisWithReanalyze = (
currentDocument.uri.fsPath,
);

// This little weird lookup is because in the legacy setup reanalyze needs to be
// run from the analysis binary, whereas in the new setup it's run from the tools
// binary.
let binaryPath =
getBinaryPath("rescript-tools.exe", projectRootPath) ??
getBinaryPath("rescript-editor-analysis.exe");
// v12+: reanalyze is run from rescript-tools.exe
// Legacy: reanalyze is run from rescript-editor-analysis.exe
const rescriptVersion = findReScriptVersion(projectRootPath);
const isReScript12OrHigher =
rescriptVersion != null &&
semver.valid(rescriptVersion) &&
semver.gte(rescriptVersion, "12.0.0");

let binaryPath: string | null;
if (isReScript12OrHigher) {
binaryPath = getBinaryPath("rescript-tools.exe", projectRootPath);
} else {
binaryPath = getBinaryPath("rescript-editor-analysis.exe", projectRootPath);
}

if (binaryPath === null) {
window.showErrorMessage("Binary executable not found.");
Expand Down
59 changes: 59 additions & 0 deletions client/src/utils-legacy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* Legacy binary finding for ReScript < 12.
* This code is kept separate to avoid polluting the main utils with pre-v12 complexity.
*/

import * as fs from "fs";
import * as path from "path";
import { NormalizedPath } from "./utils";

type binaryName = "rescript-editor-analysis.exe" | "rescript-tools.exe";

// Legacy format: no hyphen (e.g., "darwinarm64")
const platformDir =
process.arch === "arm64" ? process.platform + process.arch : process.platform;

const getLegacyBinaryDevPath = (b: binaryName) =>
path.join(path.dirname(__dirname), "..", "analysis", b);

export const getLegacyBinaryProdPath = (b: binaryName) =>
path.join(
path.dirname(__dirname),
"..",
"server",
"analysis_binaries",
platformDir,
b,
);

/**
* Finds binaries for ReScript < 12 using old path structure.
* Tries project binary first, then falls back to builtin binaries.
*/
export const getBinaryPathLegacy = (
projectRootPath: NormalizedPath | null,
binaryName: binaryName,
): string | null => {
// Try project binary first
if (projectRootPath != null) {
const binaryFromCompilerPackage = path.join(
projectRootPath,
"node_modules",
"rescript",
platformDir,
binaryName,
);
if (fs.existsSync(binaryFromCompilerPackage)) {
return binaryFromCompilerPackage;
}
}

// Fall back to builtin binaries
if (fs.existsSync(getLegacyBinaryDevPath(binaryName))) {
return getLegacyBinaryDevPath(binaryName);
} else if (fs.existsSync(getLegacyBinaryProdPath(binaryName))) {
return getLegacyBinaryProdPath(binaryName);
}

return null;
};
122 changes: 96 additions & 26 deletions client/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import * as path from "path";
import * as fs from "fs";
import * as os from "os";
import { DocumentUri } from "vscode-languageclient";
import * as semver from "semver";
import { getBinaryPathLegacy } from "./utils-legacy";

/*
* Much of the code in here is duplicated from the server code.
Expand Down Expand Up @@ -32,42 +34,110 @@ export function normalizePath(filePath: string | null): NormalizedPath | null {

type binaryName = "rescript-editor-analysis.exe" | "rescript-tools.exe";

const platformDir =
process.arch === "arm64" ? process.platform + process.arch : process.platform;
// v12+ format: with hyphen (e.g., "darwin-arm64")
const platformTarget = `${process.platform}-${process.arch}`;

const getLegacyBinaryDevPath = (b: binaryName) =>
path.join(path.dirname(__dirname), "..", "analysis", b);
// ============================================================================
// Version Detection
// ============================================================================

export const getLegacyBinaryProdPath = (b: binaryName) =>
path.join(
path.dirname(__dirname),
"..",
"server",
"analysis_binaries",
platformDir,
b,
/**
* Finds the ReScript version from package.json in the project.
*/
export const findReScriptVersion = (
projectRootPath: NormalizedPath | null,
): string | null => {
if (projectRootPath == null) {
return null;
}
try {
const packageJsonPath = path.join(
projectRootPath,
"node_modules",
"rescript",
"package.json",
);
if (!fs.existsSync(packageJsonPath)) {
return null;
}
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
return packageJson.version ?? null;
} catch {
return null;
}
};

// ============================================================================
// ReScript 12+ Binary Finding (Clean, self-contained)
// ============================================================================

/**
* Finds binaries for ReScript 12+ using @rescript/${target}/bin.js structure.
* This is the single source of truth for binary locations in v12+.
* Returns null if binary not found, throws on critical errors.
*/
const getBinaryPathReScript12 = (
projectRootPath: NormalizedPath,
binaryName: binaryName,
): string | null => {
const binJsPath = path.join(
projectRootPath,
"node_modules",
"@rescript",
platformTarget,
"bin.js",
);

if (!fs.existsSync(binJsPath)) {
return null;
}

// Read bin.js and extract the binary path
// bin.js exports binPaths object with paths to binaries
const binDir = path.join(
projectRootPath,
"node_modules",
"@rescript",
platformTarget,
"bin",
);

let binaryPath: string | null = null;
if (binaryName === "rescript-tools.exe") {
binaryPath = path.join(binDir, "rescript-tools.exe");
} else if (binaryName === "rescript-editor-analysis.exe") {
binaryPath = path.join(binDir, "rescript-editor-analysis.exe");
}

if (binaryPath != null && fs.existsSync(binaryPath)) {
return binaryPath;
}
return null;
};

// ============================================================================
// Main Binary Finding Function (Routes to v12 or legacy)
// ============================================================================

/**
* Finds a ReScript binary, routing to v12+ or legacy implementation.
* Top-level if separates the two code paths completely.
*/
export const getBinaryPath = (
binaryName: "rescript-editor-analysis.exe" | "rescript-tools.exe",
projectRootPath: NormalizedPath | null = null,
): string | null => {
const binaryFromCompilerPackage = path.join(
projectRootPath ?? "",
"node_modules",
"rescript",
platformDir,
binaryName,
);
const rescriptVersion = findReScriptVersion(projectRootPath);
const isReScript12OrHigher =
rescriptVersion != null &&
semver.valid(rescriptVersion) &&
semver.gte(rescriptVersion, "12.0.0");

if (projectRootPath != null && fs.existsSync(binaryFromCompilerPackage)) {
return binaryFromCompilerPackage;
} else if (fs.existsSync(getLegacyBinaryDevPath(binaryName))) {
return getLegacyBinaryDevPath(binaryName);
} else if (fs.existsSync(getLegacyBinaryProdPath(binaryName))) {
return getLegacyBinaryProdPath(binaryName);
// Top-level separation: v12+ or legacy
if (isReScript12OrHigher && projectRootPath != null) {
return getBinaryPathReScript12(projectRootPath, binaryName);
} else {
return null;
return getBinaryPathLegacy(projectRootPath, binaryName);
}
};

Expand Down
Loading