Skip to content
Open
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
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@

#### :bug: Bug fix

- Fix Code Analyzer binary lookup for ReScript v12+ projects.
- Fix Code Analyzer cwd/binary lookup in monorepos (run from workspace root).
- Fix monorepo build detection by only watching the workspace root `.compiler.log`.
- Fix Start Build for ReScript v12+ projects by preferring `rescript.exe`.
- 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
2 changes: 0 additions & 2 deletions client/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,12 @@ export { pasteAsRescriptJson } from "./commands/paste_as_rescript_json";
export { pasteAsRescriptJsx } from "./commands/paste_as_rescript_jsx";

export const codeAnalysisWithReanalyze = (
targetDir: string | null,
diagnosticsCollection: DiagnosticCollection,
diagnosticsResultCodeActions: DiagnosticsResultCodeActionsMap,
outputChannel: OutputChannel,
codeAnalysisRunningStatusBarItem: StatusBarItem,
) => {
runCodeAnalysisWithReanalyze(
targetDir,
diagnosticsCollection,
diagnosticsResultCodeActions,
outputChannel,
Expand Down
53 changes: 21 additions & 32 deletions client/src/commands/code_analysis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ import {
OutputChannel,
StatusBarItem,
} from "vscode";
import { getBinaryPath, NormalizedPath, normalizePath } from "../utils";
import {
findProjectRootOfFileInDir,
getBinaryPath,
NormalizedPath,
} from "../utils";
findBinary,
findBinary as findSharedBinary,
} from "../../../shared/src/findBinary";
import { findProjectRootOfFile } from "../../../shared/src/projectRoots";

export let statusBarItem = {
setToStopText: (codeAnalysisRunningStatusBarItem: StatusBarItem) => {
Expand Down Expand Up @@ -203,53 +204,41 @@ let resultsToDiagnostics = (
};
};

export const runCodeAnalysisWithReanalyze = (
targetDir: string | null,
export const runCodeAnalysisWithReanalyze = async (
diagnosticsCollection: DiagnosticCollection,
diagnosticsResultCodeActions: DiagnosticsResultCodeActionsMap,
outputChannel: OutputChannel,
codeAnalysisRunningStatusBarItem: StatusBarItem,
) => {
let currentDocument = window.activeTextEditor.document;
let cwd = targetDir ?? path.dirname(currentDocument.uri.fsPath);

let projectRootPath: NormalizedPath | null = findProjectRootOfFileInDir(
currentDocument.uri.fsPath,
let projectRootPath: NormalizedPath | null = normalizePath(
findProjectRootOfFile(currentDocument.uri.fsPath),
);

// Try v12+ path first: @rescript/{platform}-{arch}/bin/rescript-tools.exe
// Then fall back to legacy paths via getBinaryPath
let binaryPath: string | null = null;
if (projectRootPath != null) {
const v12Path = path.join(
projectRootPath,
"node_modules",
"@rescript",
`${process.platform}-${process.arch}`,
"bin",
"rescript-tools.exe",
);
if (fs.existsSync(v12Path)) {
binaryPath = v12Path;
}
}
let binaryPath: string | null = await findBinary({
projectRootPath,
binary: "rescript-tools.exe",
});
if (binaryPath == null) {
binaryPath =
getBinaryPath("rescript-tools.exe", projectRootPath) ??
getBinaryPath("rescript-editor-analysis.exe", projectRootPath);
binaryPath = await findBinary({
projectRootPath,
binary: "rescript-editor-analysis.exe",
});
}

if (binaryPath === null) {
window.showErrorMessage("Binary executable not found.");
return;
}

// Strip everything after the outermost node_modules segment to get the project root.
let cwd =
binaryPath.match(/^(.*?)[\\/]+node_modules([\\/]+|$)/)?.[1] ?? binaryPath;

statusBarItem.setToRunningText(codeAnalysisRunningStatusBarItem);

let opts = ["reanalyze", "-json"];
let p = cp.spawn(binaryPath, opts, {
cwd,
});
let p = cp.spawn(binaryPath, opts, { cwd });

if (p.stdout == null) {
statusBarItem.setToFailed(codeAnalysisRunningStatusBarItem);
Expand Down
13 changes: 1 addition & 12 deletions client/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,6 @@ export function activate(context: ExtensionContext) {
client.onNotification("rescript/compilationFinished", () => {
if (inCodeAnalysisState.active === true) {
customCommands.codeAnalysisWithReanalyze(
inCodeAnalysisState.activatedFromDirectory,
diagnosticsCollection,
diagnosticsResultCodeActions,
outputChannel,
Expand Down Expand Up @@ -308,8 +307,7 @@ export function activate(context: ExtensionContext) {

let inCodeAnalysisState: {
active: boolean;
activatedFromDirectory: string | null;
} = { active: false, activatedFromDirectory: null };
} = { active: false };

// This code actions provider yields the code actions potentially extracted
// from the code analysis to the editor.
Expand Down Expand Up @@ -442,20 +440,12 @@ export function activate(context: ExtensionContext) {

inCodeAnalysisState.active = true;

// Pointing reanalyze to the dir of the current file path is fine, because
// reanalyze will walk upwards looking for a bsconfig.json in order to find
// the correct project root.
inCodeAnalysisState.activatedFromDirectory = path.dirname(
currentDocument.uri.fsPath,
);

codeAnalysisRunningStatusBarItem.command =
"rescript-vscode.stop_code_analysis";
codeAnalysisRunningStatusBarItem.show();
statusBarItem.setToStopText(codeAnalysisRunningStatusBarItem);

customCommands.codeAnalysisWithReanalyze(
inCodeAnalysisState.activatedFromDirectory,
diagnosticsCollection,
diagnosticsResultCodeActions,
outputChannel,
Expand All @@ -465,7 +455,6 @@ export function activate(context: ExtensionContext) {

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

diagnosticsCollection.clear();
diagnosticsResultCodeActions.clear();
Expand Down
31 changes: 9 additions & 22 deletions client/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import * as path from "path";
import * as fs from "fs";
import * as os from "os";
import { DocumentUri } from "vscode-languageclient";
import { findBinary, type BinaryName } from "../../shared/src/findBinary";
import {
findProjectRootOfFileInDir as findProjectRootOfFileInDirShared,
normalizePath as normalizePathShared,
} from "../../shared/src/projectRoots";

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

type binaryName = "rescript-editor-analysis.exe" | "rescript-tools.exe";
Expand Down Expand Up @@ -83,25 +88,7 @@ export const createFileInTempDir = (prefix = "", extension = "") => {
export let findProjectRootOfFileInDir = (
source: string,
): NormalizedPath | null => {
const normalizedSource = normalizePath(source);
if (normalizedSource == null) {
return null;
}
const dir = normalizePath(path.dirname(normalizedSource));
if (dir == null) {
return null;
}
if (
fs.existsSync(path.join(dir, "rescript.json")) ||
fs.existsSync(path.join(dir, "bsconfig.json"))
) {
return dir;
} else {
if (dir === normalizedSource) {
// reached top
return null;
} else {
return findProjectRootOfFileInDir(dir);
}
}
return normalizePath(findProjectRootOfFileInDirShared(source));
};

export { findBinary, BinaryName };
4 changes: 2 additions & 2 deletions client/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
"target": "es2019",
"lib": ["ES2019"],
"outDir": "out",
"rootDir": "src",
"rootDirs": ["src", "../shared/src"],
"sourceMap": true
},
"include": ["src"],
"include": ["src", "../shared/src"],
"exclude": ["node_modules"]
}
9 changes: 4 additions & 5 deletions server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1388,11 +1388,10 @@ async function onMessage(msg: p.Message) {
const watchers = Array.from(workspaceFolders).flatMap(
(projectRootPath) => [
{
globPattern: path.join(
projectRootPath,
"**",
c.compilerLogPartialPath,
),
// Only watch the root compiler log for each workspace folder.
// In monorepos, `**/lib/bs/.compiler.log` matches every package and dependency,
// causing a burst of events per save.
globPattern: path.join(projectRootPath, c.compilerLogPartialPath),
kind: p.WatchKind.Change | p.WatchKind.Create | p.WatchKind.Delete,
},
{
Expand Down
119 changes: 12 additions & 107 deletions server/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ import * as os from "os";
import semver from "semver";
import { fileURLToPath, pathToFileURL } from "url";

import {
findBinary as findSharedBinary,
type BinaryName,
} from "../../shared/src/findBinary";
import { findProjectRootOfFileInDir as findProjectRootOfFileInDirShared } from "../../shared/src/projectRoots";
import * as codeActions from "./codeActions";
import * as c from "./constants";
import * as lookup from "./lookup";
Expand Down Expand Up @@ -85,23 +90,7 @@ export let createFileInTempDir = (extension = ""): NormalizedPath => {
function findProjectRootOfFileInDir(
source: NormalizedPath,
): NormalizedPath | null {
const dir = normalizePath(path.dirname(source));
if (dir == null) {
return null;
}
if (
fs.existsSync(path.join(dir, c.rescriptJsonPartialPath)) ||
fs.existsSync(path.join(dir, c.bsconfigPartialPath))
) {
return dir;
} else {
if (dir === source) {
// reached top
return null;
} else {
return findProjectRootOfFileInDir(dir);
}
}
return normalizePath(findProjectRootOfFileInDirShared(source));
}

/**
Expand Down Expand Up @@ -216,98 +205,14 @@ export let getProjectFile = (
// We won't know which version is in the project root until we read and parse `{project_root}/node_modules/rescript/package.json`
let findBinary = async (
projectRootPath: NormalizedPath | null,
binary:
| "bsc.exe"
| "rescript-editor-analysis.exe"
| "rescript"
| "rewatch.exe"
| "rescript.exe",
binary: BinaryName,
): Promise<NormalizedPath | null> => {
if (config.extensionConfiguration.platformPath != null) {
const result = path.join(
config.extensionConfiguration.platformPath,
binary,
);
return normalizePath(result);
}

if (projectRootPath !== null) {
try {
const compilerInfo = path.resolve(
projectRootPath,
c.compilerInfoPartialPath,
);
const contents = await fsAsync.readFile(compilerInfo, "utf8");
const compileInfo = JSON.parse(contents);
if (compileInfo && compileInfo.bsc_path) {
const bsc_path = compileInfo.bsc_path;
if (binary === "bsc.exe") {
return normalizePath(bsc_path);
} else {
const binary_path = path.join(path.dirname(bsc_path), binary);
return normalizePath(binary_path);
}
}
} catch {}
}

const rescriptDir = lookup.findFilePathFromProjectRoot(
const result = await findSharedBinary({
projectRootPath,
path.join("node_modules", "rescript"),
);
if (rescriptDir == null) {
return null;
}

let rescriptVersion = null;
let rescriptJSWrapperPath = null;
try {
const rescriptPackageJSONPath = path.join(rescriptDir, "package.json");
const rescriptPackageJSON = JSON.parse(
await fsAsync.readFile(rescriptPackageJSONPath, "utf-8"),
);
rescriptVersion = rescriptPackageJSON.version;
rescriptJSWrapperPath = rescriptPackageJSON.bin.rescript;
} catch (error) {
return null;
}

let binaryPath: string | null = null;
if (binary == "rescript") {
// Can't use the native bsb/rescript since we might need the watcher -w
// flag, which is only in the JS wrapper
binaryPath = path.join(rescriptDir, rescriptJSWrapperPath);
} else if (semver.gte(rescriptVersion, "12.0.0-alpha.13")) {
// TODO: export `binPaths` from `rescript` package so that we don't need to
// copy the logic for figuring out `target`.
const target = `${process.platform}-${process.arch}`;
// Use realpathSync to resolve symlinks, which is necessary for package
// managers like Deno and pnpm that use symlinked node_modules structures.
const targetPackagePath = path.join(
fs.realpathSync(rescriptDir),
"..",
`@rescript/${target}/bin.js`,
);
const { binPaths } = await import(targetPackagePath);

if (binary == "bsc.exe") {
binaryPath = binPaths.bsc_exe;
} else if (binary == "rescript-editor-analysis.exe") {
binaryPath = binPaths.rescript_editor_analysis_exe;
} else if (binary == "rewatch.exe") {
binaryPath = binPaths.rewatch_exe;
} else if (binary == "rescript.exe") {
binaryPath = binPaths.rescript_exe;
}
} else {
binaryPath = path.join(rescriptDir, c.platformDir, binary);
}

if (binaryPath != null && fs.existsSync(binaryPath)) {
return normalizePath(binaryPath);
} else {
return null;
}
binary,
platformPath: config.extensionConfiguration.platformPath ?? null,
});
return normalizePath(result);
};

export let findRescriptBinary = (projectRootPath: NormalizedPath | null) =>
Expand Down
4 changes: 2 additions & 2 deletions server/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
"sourceMap": true,
"strict": true,
"outDir": "out",
"rootDir": "src",
"rootDirs": ["src", "../shared/src"],
"esModuleInterop": true
},
"include": ["src"],
"include": ["src", "../shared/src"],
"exclude": ["node_modules"]
}
Loading