Skip to content

Commit 15be47c

Browse files
Fix open compiled command for monorepos (#592)
* Fix the open compiled command for monorepos where the package's gets superseded * Add build-schema interface * Make getSuffixAndPathFragmentFromBsconfig more typesafe * Strip down build-schema to what the extension actually uses * Add explanatory comment to getSuffixAndPathFragmentFromBsconfig * Refactor lookup methods from utils into own file * Fix partialFilePath parameter
1 parent d6a98f4 commit 15be47c

File tree

5 files changed

+223
-105
lines changed

5 files changed

+223
-105
lines changed

server/src/buildSchema.ts

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// This file has been generated from https://raw.githubusercontent.com/rescript-lang/rescript-compiler/master/docs/docson/build-schema.json
2+
// with https://app.quicktype.io/ and stripped down to what we actually need for the extension.
3+
4+
export interface BuildSchema {
5+
name: string;
6+
namespace?: boolean | string;
7+
"package-specs"?:
8+
| Array<ModuleFormat | ModuleFormatObject>
9+
| ModuleFormat
10+
| ModuleFormatObject;
11+
suffix?: SuffixSpec;
12+
}
13+
14+
export enum ModuleFormat {
15+
Commonjs = "commonjs",
16+
Es6 = "es6",
17+
Es6Global = "es6-global",
18+
}
19+
20+
export interface ModuleFormatObject {
21+
"in-source"?: boolean;
22+
module: ModuleFormat;
23+
suffix?: SuffixSpec;
24+
}
25+
26+
export enum SuffixSpec {
27+
BsCjs = ".bs.cjs",
28+
BsJS = ".bs.js",
29+
BsMjs = ".bs.mjs",
30+
Cjs = ".cjs",
31+
JS = ".js",
32+
Mjs = ".mjs",
33+
}

server/src/constants.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as path from "path";
2+
import { ModuleFormat } from "./buildSchema";
23

34
export let platformDir =
45
process.arch == "arm64" ? process.platform + process.arch : process.platform;
@@ -49,7 +50,7 @@ export let cmiExt = ".cmi";
4950
export let startBuildAction = "Start Build";
5051

5152
// bsconfig defaults according configuration schema (https://rescript-lang.org/docs/manual/latest/build-configuration-schema)
52-
export let bsconfigModuleDefault = "commonjs";
53+
export let bsconfigModuleDefault = ModuleFormat.Commonjs;
5354
export let bsconfigSuffixDefault = ".js";
5455

5556
export let configurationRequestId = "rescript_configuration_request";

server/src/lookup.ts

+150
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import * as fs from "fs";
2+
import * as path from "path";
3+
import * as p from "vscode-languageserver-protocol";
4+
5+
import { BuildSchema, ModuleFormat, ModuleFormatObject } from "./buildSchema";
6+
import * as c from "./constants";
7+
8+
const getCompiledFolderName = (moduleFormat: ModuleFormat): string => {
9+
switch (moduleFormat) {
10+
case "es6":
11+
return "es6";
12+
case "es6-global":
13+
return "es6_global";
14+
case "commonjs":
15+
default:
16+
return "js";
17+
}
18+
};
19+
20+
export const replaceFileExtension = (filePath: string, ext: string): string => {
21+
let name = path.basename(filePath, path.extname(filePath));
22+
return path.format({ dir: path.dirname(filePath), name, ext });
23+
};
24+
25+
// Check if filePartialPath exists at directory and return the joined path,
26+
// otherwise recursively check parent directories for it.
27+
export const findFilePathFromProjectRoot = (
28+
directory: p.DocumentUri | null, // This must be a directory and not a file!
29+
filePartialPath: string
30+
): null | p.DocumentUri => {
31+
if (directory == null) {
32+
return null;
33+
}
34+
35+
let filePath: p.DocumentUri = path.join(directory, filePartialPath);
36+
if (fs.existsSync(filePath)) {
37+
return filePath;
38+
}
39+
40+
let parentDir: p.DocumentUri = path.dirname(directory);
41+
if (parentDir === directory) {
42+
// reached the top
43+
return null;
44+
}
45+
46+
return findFilePathFromProjectRoot(parentDir, filePartialPath);
47+
};
48+
49+
export const readBsConfig = (projDir: p.DocumentUri): BuildSchema | null => {
50+
try {
51+
let bsconfigFile = fs.readFileSync(
52+
path.join(projDir, c.bsconfigPartialPath),
53+
{ encoding: "utf-8" }
54+
);
55+
56+
let result: BuildSchema = JSON.parse(bsconfigFile);
57+
return result;
58+
} catch (e) {
59+
return null;
60+
}
61+
};
62+
63+
// Collect data from bsconfig to be able to find out the correct path of
64+
// the compiled JS artifacts.
65+
export const getSuffixAndPathFragmentFromBsconfig = (bsconfig: BuildSchema) => {
66+
let pkgSpecs = bsconfig["package-specs"];
67+
let pathFragment = "";
68+
let module = c.bsconfigModuleDefault;
69+
let moduleFormatObj: ModuleFormatObject = { module: module };
70+
let suffix = c.bsconfigSuffixDefault;
71+
72+
if (pkgSpecs) {
73+
if (
74+
!Array.isArray(pkgSpecs) &&
75+
typeof pkgSpecs !== "string" &&
76+
pkgSpecs.module
77+
) {
78+
moduleFormatObj = pkgSpecs;
79+
} else if (typeof pkgSpecs === "string") {
80+
module = pkgSpecs;
81+
} else if (Array.isArray(pkgSpecs) && pkgSpecs[0]) {
82+
if (typeof pkgSpecs[0] === "string") {
83+
module = pkgSpecs[0];
84+
} else {
85+
moduleFormatObj = pkgSpecs[0];
86+
}
87+
}
88+
}
89+
90+
if (moduleFormatObj["module"]) {
91+
module = moduleFormatObj["module"];
92+
}
93+
94+
if (!moduleFormatObj["in-source"]) {
95+
pathFragment = "lib/" + getCompiledFolderName(module);
96+
}
97+
98+
if (moduleFormatObj.suffix) {
99+
suffix = moduleFormatObj.suffix;
100+
} else if (bsconfig.suffix) {
101+
suffix = bsconfig.suffix;
102+
}
103+
104+
return [suffix, pathFragment];
105+
};
106+
107+
export const getFilenameFromBsconfig = (
108+
projDir: string,
109+
partialFilePath: string
110+
): string | null => {
111+
let bsconfig = readBsConfig(projDir);
112+
113+
if (!bsconfig) {
114+
return null;
115+
}
116+
117+
let [suffix, pathFragment] = getSuffixAndPathFragmentFromBsconfig(bsconfig);
118+
119+
let compiledPartialPath = replaceFileExtension(partialFilePath, suffix);
120+
121+
return path.join(projDir, pathFragment, compiledPartialPath);
122+
};
123+
124+
// Monorepo helpers
125+
export const getFilenameFromRootBsconfig = (
126+
projDir: string,
127+
partialFilePath: string
128+
): string | null => {
129+
let rootBsConfigPath = findFilePathFromProjectRoot(
130+
path.join("..", projDir),
131+
c.bsconfigPartialPath
132+
);
133+
134+
if (!rootBsConfigPath) {
135+
return null;
136+
}
137+
138+
let rootBsconfig = readBsConfig(path.dirname(rootBsConfigPath));
139+
140+
if (!rootBsconfig) {
141+
return null;
142+
}
143+
144+
let [suffix, pathFragment] =
145+
getSuffixAndPathFragmentFromBsconfig(rootBsconfig);
146+
147+
let compiledPartialPath = replaceFileExtension(partialFilePath, suffix);
148+
149+
return path.join(projDir, pathFragment, compiledPartialPath);
150+
};

server/src/server.ts

+13-6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
CodeLensParams,
1616
SignatureHelpParams,
1717
} from "vscode-languageserver-protocol";
18+
import * as lookup from "./lookup";
1819
import * as utils from "./utils";
1920
import * as codeActions from "./codeActions";
2021
import * as c from "./constants";
@@ -102,20 +103,26 @@ let send: (msg: p.Message) => void = (_) => {};
102103

103104
let findRescriptBinary = (projectRootPath: p.DocumentUri | null) =>
104105
extensionConfiguration.binaryPath == null
105-
? utils.findFilePathFromProjectRoot(projectRootPath, path.join(c.nodeModulesBinDir, c.rescriptBinName))
106+
? lookup.findFilePathFromProjectRoot(
107+
projectRootPath,
108+
path.join(c.nodeModulesBinDir, c.rescriptBinName)
109+
)
106110
: utils.findBinary(extensionConfiguration.binaryPath, c.rescriptBinName);
107111

108112
let findPlatformPath = (projectRootPath: p.DocumentUri | null) => {
109113
if (extensionConfiguration.platformPath != null) {
110114
return extensionConfiguration.platformPath;
111115
}
112116

113-
let rescriptDir = utils.findFilePathFromProjectRoot(projectRootPath, path.join("node_modules", "rescript"));
117+
let rescriptDir = lookup.findFilePathFromProjectRoot(
118+
projectRootPath,
119+
path.join("node_modules", "rescript")
120+
);
114121
if (rescriptDir == null) {
115122
return null;
116123
}
117124

118-
let platformPath = path.join(rescriptDir, c.platformDir)
125+
let platformPath = path.join(rescriptDir, c.platformDir);
119126

120127
// Workaround for darwinarm64 which has no folder yet in ReScript <= 9.1.4
121128
if (
@@ -127,10 +134,10 @@ let findPlatformPath = (projectRootPath: p.DocumentUri | null) => {
127134
}
128135

129136
return platformPath;
130-
}
137+
};
131138

132139
let findBscExeBinary = (projectRootPath: p.DocumentUri | null) =>
133-
utils.findBinary(findPlatformPath(projectRootPath), c.bscExeName)
140+
utils.findBinary(findPlatformPath(projectRootPath), c.bscExeName);
134141

135142
interface CreateInterfaceRequestParams {
136143
uri: string;
@@ -941,7 +948,7 @@ function createInterface(msg: p.RequestMessage): p.Message {
941948
let result = typeof response.result === "string" ? response.result : "";
942949

943950
try {
944-
let resiPath = utils.replaceFileExtension(filePath, c.resiExt);
951+
let resiPath = lookup.replaceFileExtension(filePath, c.resiExt);
945952
fs.writeFileSync(resiPath, result, { encoding: "utf-8" });
946953
let response: p.ResponseMessage = {
947954
jsonrpc: c.jsonrpcVersion,

0 commit comments

Comments
 (0)