diff --git a/.prettierignore b/.prettierignore index 459fd3949..b0f5bcefa 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,4 +2,5 @@ server/out analysis/examples analysis/reanalyze/examples -tools/tests \ No newline at end of file +tools/tests +.history/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index d82622929..5edf1b478 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index 3071788f9..1227ab8ae 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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` | diff --git a/package-lock.json b/package-lock.json index 771876b5d..597dda11b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "semver": "^7.7.2" }, "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", @@ -41,10 +41,14 @@ } }, "node_modules/@types/node": { - "version": "14.14.41", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.41.tgz", - "integrity": "sha512-dueRKfaJL4RTtSa7bWeTK1M+VH+Gns73oCgzvYfHZywRCoPSd8EkXBL0mZ9unPTveBn+D9phZBaxuzpwjWkW0g==", - "dev": true + "version": "20.19.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.13.tgz", + "integrity": "sha512-yCAeZl7a0DxgNVteXFHt9+uyFbqXGy/ShC4BlcHkoE0AfGXYv/BUiplV72DjMYXHDBXFjhvr6DD1NiRVfB4j8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } }, "node_modules/@types/semver": { "version": "7.7.0", @@ -138,6 +142,13 @@ "engines": { "node": ">=14.17" } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" } } } diff --git a/package.json b/package.json index 82e929196..530cc76ea 100644 --- a/package.json +++ b/package.json @@ -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, @@ -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", @@ -268,5 +276,6 @@ }, "dependencies": { "semver": "^7.7.2" - } + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } diff --git a/scripts/find-runtime.ts b/scripts/find-runtime.ts new file mode 100644 index 000000000..5f6c13235 --- /dev/null +++ b/scripts/find-runtime.ts @@ -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 +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`); diff --git a/server/src/bsc-args/bsb.ts b/server/src/bsc-args/bsb.ts new file mode 100644 index 000000000..8566db7b1 --- /dev/null +++ b/server/src/bsc-args/bsb.ts @@ -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 { + 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(); + } +} diff --git a/server/src/bsc-args/rewatch.ts b/server/src/bsc-args/rewatch.ts new file mode 100644 index 000000000..5e39c4ed6 --- /dev/null +++ b/server/src/bsc-args/rewatch.ts @@ -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; + parser_args: Array; +}; + +async function getRuntimePath( + entry: IncrementallyCompiledFileInfo, +): Promise { + 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, + entry: IncrementallyCompiledFileInfo, +): Promise { + 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; + } +} diff --git a/server/src/config.ts b/server/src/config.ts index e8055e379..97b89985a 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -11,6 +11,7 @@ export interface extensionConfiguration { codeLens?: boolean; binaryPath?: string | null; platformPath?: string | null; + runtimePath?: string | null; signatureHelp?: { enabled?: boolean; forConstructorPayloads?: boolean; diff --git a/server/src/find-runtime.ts b/server/src/find-runtime.ts new file mode 100644 index 000000000..1611ec792 --- /dev/null +++ b/server/src/find-runtime.ts @@ -0,0 +1,140 @@ +import { readdir, stat as statAsync } from "fs/promises"; +import { join, resolve } from "path"; + +// Efficient parallel folder traversal to find node_modules directories +async function findNodeModulesDirs( + rootPath: string, + maxDepth = 12, +): Promise { + const nodeModulesDirs: string[] = []; + const stack: Array<{ dir: string; depth: number }> = [ + { dir: rootPath, depth: 0 }, + ]; + const visited = new Set(); + + while (stack.length) { + const { dir, depth } = stack.pop()!; + if (depth > maxDepth || visited.has(dir)) continue; + visited.add(dir); + + let entries: string[]; + try { + entries = await readdir(dir); + } catch { + continue; + } + + if (entries.includes("node_modules")) { + const nm = join(dir, "node_modules"); + try { + const st = await statAsync(nm); + if (st.isDirectory()) { + nodeModulesDirs.push(nm); + // Do NOT push deeper here to keep same behavior (stop at first node_modules in this branch) + continue; + } + } catch {} + } + + for (const entry of entries) { + if (entry === "node_modules" || entry.startsWith(".")) continue; + const full = join(dir, entry); + try { + const st = await statAsync(full); + if (st.isDirectory()) { + stack.push({ dir: full, depth: depth + 1 }); + } + } catch {} + } + } + + return nodeModulesDirs; +} + +// Custom function to find Deno vendorized @rescript/runtime directories +async function findDenoRescriptRuntime(nodeModulesPath: string) { + // We only care about the Deno vendorized layout: + // /.deno/@rescript+runtime@/node_modules/@rescript/runtime + const denoRoot = join(nodeModulesPath, ".deno"); + let entries: string[]; + try { + entries = await readdir(denoRoot); + } catch { + return []; + } + + // Collect all @rescript+runtime@ vendor dirs + const vendorDirs = entries.filter((e) => e.startsWith("@rescript+runtime@")); + if (vendorDirs.length === 0) return []; + + // Optionally pick β€œlatest” by version; for now we return all valid matches. + const results: string[] = []; + for (const dir of vendorDirs) { + const runtimePath = join( + denoRoot, + dir, + "node_modules", + "@rescript", + "runtime", + ); + try { + const st = await statAsync(runtimePath); + if (st.isDirectory()) results.push(runtimePath); + } catch { + // Ignore inaccessible / missing path + } + } + + return results; +} + +async function findRuntimePath(project: string) { + // Find all node_modules directories using efficient traversal + const node_modules = await findNodeModulesDirs(project); + + const rescriptRuntimeDirs = await Promise.all( + node_modules.map(async (nm) => { + const results = []; + + // Check for standard layout: @rescript/runtime + const standardPath = join(nm, "@rescript", "runtime"); + try { + const stat = await statAsync(standardPath); + if (stat.isDirectory()) { + results.push(standardPath); + // If we found standard layout, no need to search for Deno layouts + return results; + } + } catch (e) { + // Directory doesn't exist, continue + } + + // Only check for Deno vendorized layouts if standard layout wasn't found + const denoResults = await findDenoRescriptRuntime(nm); + results.push(...denoResults); + + return results; + }), + ).then((results) => results.flatMap((x) => x)); + + return rescriptRuntimeDirs.map((runtime) => resolve(runtime)); +} + +function findRuntimeCached() { + const cache = new Map(); + return async (project: string) => { + if (cache.has(project)) { + return cache.get(project)!; + } + const runtimes = await findRuntimePath(project); + cache.set(project, runtimes); + return runtimes; + }; +} + +/** + * Find all installed @rescript/runtime directories in the given project path. + * In a perfect world, there should be exactly one. + * This function is cached per project path. + */ +export const findRescriptRuntimesInProject = findRuntimeCached(); diff --git a/server/src/incrementalCompilation.ts b/server/src/incrementalCompilation.ts index 1f1a13ed1..849047e4c 100644 --- a/server/src/incrementalCompilation.ts +++ b/server/src/incrementalCompilation.ts @@ -12,8 +12,10 @@ import config, { send } from "./config"; import * as c from "./constants"; import { fileCodeActions } from "./codeActions"; import { projectsFiles } from "./projectFiles"; +import { getRewatchBscArgs, RewatchCompilerArgs } from "./bsc-args/rewatch"; +import { BsbCompilerArgs, getBsbBscArgs } from "./bsc-args/bsb"; -function debug() { +export function debug() { return ( config.extensionConfiguration.incrementalTypechecking?.debugLogging ?? false ); @@ -25,12 +27,7 @@ const INCREMENTAL_FILE_FOLDER_LOCATION = path.join( INCREMENTAL_FOLDER_NAME, ); -type RewatchCompilerArgs = { - compiler_args: Array; - parser_args: Array; -}; - -type IncrementallyCompiledFileInfo = { +export type IncrementallyCompiledFileInfo = { file: { /** File type. */ extension: ".res" | ".resi"; @@ -53,7 +50,7 @@ type IncrementallyCompiledFileInfo = { /** When build.ninja was last modified. Used as a cache key. */ fileMtime: number; /** The raw, extracted needed info from build.ninja. Needs processing. */ - rawExtracted: Array; + rawExtracted: BsbCompilerArgs; } | null; /** Cache for rewatch compiler args. */ buildRewatch: { @@ -181,202 +178,14 @@ export function cleanUpIncrementalFiles( ); }); } -function getBscArgs( - entry: IncrementallyCompiledFileInfo, -): Promise | RewatchCompilerArgs | null> { - const buildNinjaPath = path.resolve( - entry.project.rootPath, - c.buildNinjaPartialPath, - ); - const rewatchLockfile = path.resolve( - entry.project.workspaceRootPath, - c.rewatchLockPartialPath, - ); - const rescriptLockfile = path.resolve( - entry.project.workspaceRootPath, - c.rescriptLockPartialPath, - ); - let buildSystem: "bsb" | "rewatch" | null = null; - - let stat: fs.Stats | null = null; - try { - stat = fs.statSync(buildNinjaPath); - buildSystem = "bsb"; - } catch {} - try { - stat = fs.statSync(rewatchLockfile); - buildSystem = "rewatch"; - } catch {} - try { - stat = fs.statSync(rescriptLockfile); - buildSystem = "rewatch"; - } catch {} - if (buildSystem == null) { - console.log("Did not find build.ninja or rewatch.lock, cannot proceed.."); - return Promise.resolve(null); - } else if (debug()) { - console.log( - `Using build system: ${buildSystem} for ${entry.file.sourceFilePath}`, - ); - } - const bsbCacheEntry = entry.buildNinja; - const rewatchCacheEntry = entry.buildRewatch; - - if ( - buildSystem === "bsb" && - bsbCacheEntry != null && - stat != null && - bsbCacheEntry.fileMtime >= stat.mtimeMs - ) { - return Promise.resolve(bsbCacheEntry.rawExtracted); - } - if ( - buildSystem === "rewatch" && - rewatchCacheEntry != null && - rewatchCacheEntry.lastFile === entry.file.sourceFilePath - ) { - return Promise.resolve(rewatchCacheEntry.compilerArgs); - } - return new Promise(async (resolve, _reject) => { - function resolveResult(result: Array | RewatchCompilerArgs) { - if (stat != null && Array.isArray(result)) { - entry.buildSystem = "bsb"; - entry.buildNinja = { - fileMtime: stat.mtimeMs, - rawExtracted: result, - }; - } else if (!Array.isArray(result)) { - entry.buildSystem = "rewatch"; - entry.buildRewatch = { - lastFile: entry.file.sourceFilePath, - compilerArgs: result, - }; - } - resolve(result); - } - if (buildSystem === "bsb") { - const fileStream = fs.createReadStream(buildNinjaPath, { - encoding: "utf8", - }); - fileStream.on("error", (err) => { - console.error("File stream error:", err); - resolveResult([]); - }); - const rl = readline.createInterface({ - input: fileStream, - crlfDelay: Infinity, - }); - let captureNextLine = false; - let done = false; - let stopped = false; - const captured: Array = []; - rl.on("line", (line) => { - line = line.trim(); // Normalize line endings - if (stopped) { - return; - } - if (captureNextLine) { - captured.push(line); - captureNextLine = false; - } - if (done) { - // Not sure if fileStream.destroy is necessary, rl.close() will handle it gracefully. - // fileStream.destroy(); - rl.close(); - resolveResult(captured); - stopped = true; - return; - } - if (line.startsWith("rule astj")) { - captureNextLine = true; - } - if (line.startsWith("rule mij")) { - captureNextLine = true; - done = true; - } - }); - rl.on("error", (err) => { - console.error("Readline error:", err); - resolveResult([]); - }); - rl.on("close", () => { - resolveResult(captured); - }); - } else if (buildSystem === "rewatch") { - try { - const project = projectsFiles.get(entry.project.rootPath); - if (project?.rescriptVersion == null) return; - 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 = bscExe != null ? { RESCRIPT_BSC_EXE: bscExe } : undefined; - const compilerArgs = JSON.parse( - cp - .execFileSync(rewatchPath, rewatchArguments, { env }) - .toString() - .trim(), - ) as RewatchCompilerArgs; - resolveResult(compilerArgs); - } catch (e) { - console.error(e); - } - } - }); +export async function getBscArgs( + send: (msg: p.Message) => void, + entry: IncrementallyCompiledFileInfo, +): Promise { + return entry.buildSystem === "bsb" + ? await getBsbBscArgs(entry) + : await getRewatchBscArgs(send, projectsFiles, entry); } function argCouples(argList: string[]): string[][] { @@ -447,14 +256,18 @@ function triggerIncrementalCompilationOfFile( return; } - const projectRewatchLockfile = path.resolve( - projectRootPath, - c.rewatchLockPartialPath, - ); + const projectRewatchLockfiles = [ + path.resolve(projectRootPath, c.rewatchLockPartialPath), + path.resolve(projectRootPath, c.rescriptLockPartialPath), + ]; let foundRewatchLockfileInProjectRoot = false; - if (fs.existsSync(projectRewatchLockfile)) { + if (projectRewatchLockfiles.some((lockFile) => fs.existsSync(lockFile))) { foundRewatchLockfileInProjectRoot = true; + } else if (debug()) { + console.log( + `Did not find ${projectRewatchLockfiles.join(" or ")} in project root, assuming bsb`, + ); } // if we find a rewatch.lock in the project root, it's a compilation of a local package @@ -519,6 +332,7 @@ function triggerIncrementalCompilationOfFile( }; incrementalFileCacheEntry.project.callArgs = figureOutBscArgs( + send, incrementalFileCacheEntry, ); originalTypeFileToFilePath.set( @@ -559,7 +373,10 @@ function verifyTriggerToken(filePath: string, triggerToken: number): boolean { const isWindows = os.platform() === "win32"; -async function figureOutBscArgs(entry: IncrementallyCompiledFileInfo) { +async function figureOutBscArgs( + send: (msg: p.Message) => void, + entry: IncrementallyCompiledFileInfo, +) { const project = projectsFiles.get(entry.project.rootPath); if (project?.rescriptVersion == null) { if (debug()) { @@ -570,7 +387,7 @@ async function figureOutBscArgs(entry: IncrementallyCompiledFileInfo) { } return null; } - const res = await getBscArgs(entry); + const res = await getBscArgs(send, entry); if (res == null) return null; let astArgs: Array> = []; let buildArgs: Array> = []; @@ -671,7 +488,7 @@ async function compileContents( const triggerToken = entry.compilation?.triggerToken; let callArgs = await entry.project.callArgs; if (callArgs == null) { - const callArgsRetried = await figureOutBscArgs(entry); + const callArgsRetried = await figureOutBscArgs(send, entry); if (callArgsRetried != null) { callArgs = callArgsRetried; entry.project.callArgs = Promise.resolve(callArgsRetried); diff --git a/server/src/projectFiles.ts b/server/src/projectFiles.ts index 4a1787a0c..82582e123 100644 --- a/server/src/projectFiles.ts +++ b/server/src/projectFiles.ts @@ -5,7 +5,7 @@ export type filesDiagnostics = { [key: string]: p.Diagnostic[]; }; -interface projectFiles { +export interface projectFiles { openFiles: Set; filesWithDiagnostics: Set; filesDiagnostics: filesDiagnostics; diff --git a/tsconfig.json b/tsconfig.json index cae98cfda..0f5dff06a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,8 +1,8 @@ { "compilerOptions": { "module": "commonjs", - "target": "es2019", - "lib": ["ES2019"], + "target": "ES2020", + "lib": ["ES2020"], "outDir": "out", "rootDir": "src", "sourceMap": true