Skip to content
Merged
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
3 changes: 2 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
server/out
analysis/examples
analysis/reanalyze/examples
tools/tests
tools/tests
.history/
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@

- Add status bar item tracking compilation state. https://github.com/rescript-lang/rescript-vscode/pull/1119

#### :house: Internal

- Find `@rescript/runtime` for Rewatch compiler-args call. https://github.com/rescript-lang/rescript-vscode/pull/1125

## 1.64.0

#### :rocket: New Feature
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,20 @@

## Contents

- [Contents](#contents)
- [📝 Prerequisite](#-prerequisite)
- [🌈 Supported Themes](#-supported-themes)
- [💡 Features](#-features)
- [📥 Installation](#-installation)
- [Pre-release channel](#pre-release-channel)
- [📦 Commands](#-commands)
- [🔨 Settings](#-settings)
- [🚀 Code Analyzer](#-code-analyzer)
- [Configuring the Code Analyzer](#configuring-the-code-analyzer)
- [Usage](#usage)
- [Caveats](#caveats)
- [🪄 Tips & Tricks](#-tips--tricks)
- [🪄 Tips \& Tricks](#-tips--tricks)
- [Hide generated files](#hide-generated-files)
- [⌨️ Use with Other Editors](#️-use-with-other-editors)
- [📰 Changelog](#-changelog)
- [👏 How to Contribute](#-how-to-contribute)
- [📄 License](#-license)
Expand Down Expand Up @@ -102,6 +103,7 @@ You'll find all ReScript specific settings under the scope `rescript.settings`.
| Prompt to Start Build | If there's no ReScript build running already in the opened project, the extension will prompt you and ask if you want to start a build automatically. You can turn off this automatic prompt via the setting `rescript.settings.askToStartBuild`. |
| ReScript Binary Path | The extension will look for the existence of a `node_modules/.bin/rescript` file and use its directory as the `binaryPath`. If it does not find it at the project root (which is where the nearest `rescript.json` resides), it goes up folders in the filesystem recursively until it either finds it (often the case in monorepos) or hits the top level. To override this lookup process, the path can be configured explicitly using the setting `rescript.settings.binaryPath` |
| ReScript Platform Path | The extension will look for the existence of a `node_modules/rescript` directory and use the subdirectory corresponding to the current platform as the `platformPath`. If it does not find it at the project root (which is where the nearest `rescript.json` resides), it goes up folders in the filesystem recursively until it either finds it (often the case in monorepos) or hits the top level. To override this lookup process, the path can be configured explicitly using the setting `rescript.settings.platformPath` |
| ReScript Runtime Path | The extension will look for the existence of a `node_modules/@rescript/runtime` directory (ReScript v12 beta 11+). To override this lookup process, the path can be configured explicitly using the setting `rescript.settings.runtimePath`. |
| Inlay Hints (experimental) | This allows an editor to place annotations inline with text to display type hints. Enable using `rescript.settings.inlayHints.enable: true` |
| Code Lens (experimental) | This tells the editor to add code lenses to function definitions, showing its full type above the definition. Enable using `rescript.settings.codeLens: true` |
| Signature Help | This tells the editor to show signature help when you're writing function calls. Enable using `rescript.settings.signatureHelp.enabled: true` |
Expand Down
21 changes: 16 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 11 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,14 @@
"default": null,
"description": "Path to the directory where platform-specific ReScript binaries are. You can use it if you haven't or don't want to use the installed ReScript from node_modules in your project."
},
"rescript.settings.runtimePath": {
"type": [
"string",
"null"
],
"default": null,
"description": "Optional path to the directory containing the @rescript/runtime package. Set this if your tooling is unable to automatically locate the package in your project."
},
"rescript.settings.compileStatus.enable": {
"type": "boolean",
"default": true,
Expand Down Expand Up @@ -259,7 +267,7 @@
"bundle": "npm run bundle-server && npm run bundle-client"
},
"devDependencies": {
"@types/node": "^14.14.41",
"@types/node": "^20.19.13",
"@types/semver": "^7.7.0",
"@types/vscode": "1.68.0",
"esbuild": "^0.20.1",
Expand All @@ -268,5 +276,6 @@
},
"dependencies": {
"semver": "^7.7.2"
}
},
"packageManager": "[email protected]+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
32 changes: 32 additions & 0 deletions scripts/find-runtime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// benchmark
const start = process.hrtime.bigint();

// start code
const args = process.argv.slice(2);

if (args.length === 0) {
console.log(`
Usage: node find-runtime.mjs <project-folder>
Find @rescript/runtime directories in a project's node_modules.
Arguments:
project-folder Path to the project directory to search
Examples:
node find-runtime.mjs /path/to/project
node find-runtime.mjs .
`);
process.exit(1);
}

const project = args[args.length - 1];

import { findRescriptRuntimesInProject } from "../server/src/find-runtime.ts";

const runtimes = await findRescriptRuntimesInProject(project);

console.log("Found @rescript/runtime directories:", runtimes);

// end code
const end = process.hrtime.bigint();
const durationMs = Number(end - start) / 1e6; // convert ns → ms

console.log(`Script took ${durationMs.toFixed(3)}ms`);
59 changes: 59 additions & 0 deletions server/src/bsc-args/bsb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import * as path from "path";
import fs from "fs";
import { IncrementallyCompiledFileInfo } from "../incrementalCompilation";
import { buildNinjaPartialPath } from "../constants";

export type BsbCompilerArgs = string[];

export async function getBsbBscArgs(
entry: IncrementallyCompiledFileInfo,
): Promise<BsbCompilerArgs | null> {
const buildNinjaPath = path.resolve(
entry.project.rootPath,
buildNinjaPartialPath,
);

let stat: fs.Stats;
try {
stat = await fs.promises.stat(buildNinjaPath);
} catch {
return null;
}

const cache = entry.buildNinja;
if (cache && cache.fileMtime >= stat.mtimeMs) {
return cache.rawExtracted;
}

const fh = await fs.promises.open(buildNinjaPath, "r");
try {
let captureNext = false;
let haveAst = false;
const captured: string[] = [];

for await (const rawLine of fh.readLines()) {
const line = String(rawLine).trim();
if (captureNext) {
captured.push(line);
captureNext = false;
if (haveAst && captured.length === 2) break; // got ast + mij
}
if (line.startsWith("rule astj")) {
captureNext = true;
haveAst = true;
} else if (line.startsWith("rule mij")) {
captureNext = true;
}
}

if (captured.length !== 2) return null;

entry.buildNinja = {
fileMtime: stat.mtimeMs,
rawExtracted: captured,
};
return captured;
} finally {
await fh.close();
}
}
178 changes: 178 additions & 0 deletions server/src/bsc-args/rewatch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import * as path from "path";
import * as utils from "../utils";
import * as cp from "node:child_process";
import * as p from "vscode-languageserver-protocol";
import semver from "semver";
import {
debug,
IncrementallyCompiledFileInfo,
} from "../incrementalCompilation";
import type { projectFiles } from "../projectFiles";
import config from "../config";
import { findRescriptRuntimesInProject } from "../find-runtime";
import { jsonrpcVersion } from "../constants";

export type RewatchCompilerArgs = {
compiler_args: Array<string>;
parser_args: Array<string>;
};

async function getRuntimePath(
entry: IncrementallyCompiledFileInfo,
): Promise<string | null> {
let rescriptRuntime: string | null =
config.extensionConfiguration.runtimePath ?? null;

if (rescriptRuntime !== null) {
if (debug()) {
console.log(
`Using configured runtime path as RESCRIPT_RUNTIME: ${rescriptRuntime}`,
);
}
return rescriptRuntime;
}

const rescriptRuntimes = await findRescriptRuntimesInProject(
entry.project.workspaceRootPath,
);

if (debug()) {
if (rescriptRuntimes.length === 0) {
console.log(
`Did not find @rescript/runtime directory for ${entry.project.workspaceRootPath}`,
);
} else if (rescriptRuntimes.length > 1) {
console.warn(
`Found multiple @rescript/runtime directories, using the first one as RESCRIPT_RUNTIME: ${rescriptRuntimes.join(", ")}`,
);
} else {
console.log(
`Found @rescript/runtime directory: ${rescriptRuntimes.join(", ")}`,
);
}
}

return rescriptRuntimes.at(0) ?? null;
}

export async function getRewatchBscArgs(
send: (msg: p.Message) => void,
projectsFiles: Map<string, projectFiles>,
entry: IncrementallyCompiledFileInfo,
): Promise<RewatchCompilerArgs | null> {
const rewatchCacheEntry = entry.buildRewatch;

if (
rewatchCacheEntry != null &&
rewatchCacheEntry.lastFile === entry.file.sourceFilePath
) {
return Promise.resolve(rewatchCacheEntry.compilerArgs);
}

try {
const project = projectsFiles.get(entry.project.rootPath);
if (project?.rescriptVersion == null) return null;
let rewatchPath = path.resolve(
entry.project.workspaceRootPath,
"node_modules/@rolandpeelen/rewatch/rewatch",
);
let rescriptRewatchPath = null;
if (
semver.valid(project.rescriptVersion) &&
semver.satisfies(project.rescriptVersion as string, ">11", {
includePrerelease: true,
})
) {
rescriptRewatchPath = await utils.findRewatchBinary(
entry.project.workspaceRootPath,
);
}

if (
semver.valid(project.rescriptVersion) &&
semver.satisfies(project.rescriptVersion as string, ">=12.0.0-beta.1", {
includePrerelease: true,
})
) {
rescriptRewatchPath = await utils.findRescriptExeBinary(
entry.project.workspaceRootPath,
);
}

if (rescriptRewatchPath != null) {
rewatchPath = rescriptRewatchPath;
if (debug()) {
console.log(
`Found rewatch binary bundled with v12: ${rescriptRewatchPath}`,
);
}
} else {
if (debug()) {
console.log("Did not find rewatch binary bundled with v12");
}
}

const rewatchArguments = semver.satisfies(
project.rescriptVersion,
">=12.0.0-beta.2",
{ includePrerelease: true },
)
? ["compiler-args", entry.file.sourceFilePath]
: [
"--rescript-version",
project.rescriptVersion,
"--compiler-args",
entry.file.sourceFilePath,
];
const bscExe = await utils.findBscExeBinary(
entry.project.workspaceRootPath,
);
const env: NodeJS.ProcessEnv = {};
if (bscExe != null) {
env["RESCRIPT_BSC_EXE"] = bscExe;
}

// For ReScript >= 12.0.0-beta.11 we need to set RESCRIPT_RUNTIME
if (
semver.satisfies(project.rescriptVersion, ">=12.0.0-beta.11", {
includePrerelease: true,
})
) {
let rescriptRuntime: string | null = await getRuntimePath(entry);

if (rescriptRuntime !== null) {
env["RESCRIPT_RUNTIME"] = rescriptRuntime;
} else {
// If no runtime was found, we should let the user know.
let params: p.ShowMessageParams = {
type: p.MessageType.Error,
message:
`[Incremental type checking] The @rescript/runtime package was not found in your project. ` +
`It is normally included with ReScript, but either it's missing or could not be detected. ` +
`Check that it exists in your dependencies, or configure 'rescript.settings.runtimePath' to point to it. ` +
`Without this package, incremental type checking may not work as expected.`,
};
let message: p.NotificationMessage = {
jsonrpc: jsonrpcVersion,
method: "window/showMessage",
params: params,
};
send(message);
}
}

const compilerArgs = JSON.parse(
cp.execFileSync(rewatchPath, rewatchArguments, { env }).toString().trim(),
) as RewatchCompilerArgs;

entry.buildRewatch = {
lastFile: entry.file.sourceFilePath,
compilerArgs: compilerArgs,
};

return compilerArgs;
} catch (e) {
console.error(e);
return null;
}
}
Loading
Loading