From 2f76dffa6200e2fea0d9a9757c0c6998ddeb8d77 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Mon, 26 Sep 2022 13:53:40 +0200 Subject: [PATCH 1/7] Fix the open compiled command for monorepos where the package's gets superseded --- server/src/utils.ts | 59 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 14 deletions(-) diff --git a/server/src/utils.ts b/server/src/utils.ts index 5360e10ec..c37832327 100644 --- a/server/src/utils.ts +++ b/server/src/utils.ts @@ -219,7 +219,7 @@ export const toCamelCase = (text: string): string => { .replace(/(\s|-)+/g, ""); }; -const readBsConfig = (projDir: p.DocumentUri) => { +const readBsConfig = (projDir: p.DocumentUri): any | null => { try { let bsconfigFile = fs.readFileSync( path.join(projDir, c.bsconfigPartialPath), @@ -270,19 +270,7 @@ let getCompiledFolderName = (moduleFormat: string): string => { } }; -export let getCompiledFilePath = ( - filePath: string, - projDir: string -): execResult => { - let bsconfig = readBsConfig(projDir); - - if (!bsconfig) { - return { - kind: "error", - error: "Could not read bsconfig", - }; - } - +let getSuffixAndPathFragmentFromBsconfig = (bsconfig: any) => { let pkgSpecs = bsconfig["package-specs"]; let pathFragment = ""; let moduleFormatObj: any = {}; @@ -318,10 +306,53 @@ export let getCompiledFilePath = ( suffix = bsconfig.suffix; } + return [suffix, pathFragment]; +}; + +export let getCompiledFilePath = ( + filePath: string, + projDir: string +): execResult => { + let bsconfig = readBsConfig(projDir); + let error: execResult = { + kind: "error", + error: "Could not read bsconfig", + }; + + if (!bsconfig) { + return error; + } + + let [suffix, pathFragment] = getSuffixAndPathFragmentFromBsconfig(bsconfig); let partialFilePath = filePath.split(projDir)[1]; let compiledPartialPath = replaceFileExtension(partialFilePath, suffix); let result = path.join(projDir, pathFragment, compiledPartialPath); + // If the file is not found, lookup a possible root bsconfig that may contain + // info about the possible location of the file. + if (!fs.existsSync(result)) { + let rootBsConfigPath = findFilePathFromProjectRoot( + path.join("..", projDir), + c.bsconfigPartialPath + ); + + if (!rootBsConfigPath) { + return error; + } + + let rootBsconfig = readBsConfig(path.dirname(rootBsConfigPath)); + + if (!rootBsconfig) { + return error; + } + + let [rootSuffix, rootPathFragment] = + getSuffixAndPathFragmentFromBsconfig(rootBsconfig); + + let compiledPartialPath = replaceFileExtension(partialFilePath, rootSuffix); + result = path.join(projDir, rootPathFragment, compiledPartialPath); + } + return { kind: "success", result, From 323b70f59a1833d97d8d36aef3c42e8782b27817 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Mon, 10 Oct 2022 15:12:40 +0200 Subject: [PATCH 2/7] Add build-schema interface --- server/src/buildSchema.ts | 392 ++++++++++++++++++++++++++++++++++++++ server/src/utils.ts | 6 +- 2 files changed, 396 insertions(+), 2 deletions(-) create mode 100644 server/src/buildSchema.ts diff --git a/server/src/buildSchema.ts b/server/src/buildSchema.ts new file mode 100644 index 000000000..99b3f9dd5 --- /dev/null +++ b/server/src/buildSchema.ts @@ -0,0 +1,392 @@ +// This file has been generated from https://raw.githubusercontent.com/rescript-lang/rescript-compiler/master/docs/docson/build-schema.json +// with https://app.quicktype.io/ + +// To parse this data: +// +// import { Convert, BuildSchema } from "./file"; +// +// const buildSchema = Convert.toBuildSchema(json); + +/** + * All paths are required to be in **Unix format** (foo/bar), the build system normalizes + * them for other platforms internally + */ +export interface BuildSchema { + /** + * ReScript dependencies of the library, like in package.json. Currently searches in + * `node_modules` + */ + "bs-dependencies"?: string[]; + /** + * ReScript dev dependencies of the library, like in package.json. Currently searches in + * `node_modules` + */ + "bs-dev-dependencies"?: string[]; + /** + * (Not needed usually) external include directories, which will be applied `-I` to all + * compilation units + */ + "bs-external-includes"?: string[]; + /** + * Flags passed to bsc.exe + */ + "bsc-flags"?: string[] | BscFlagsObject; + /** + * Ignore generators, cut the dependency on generator tools + */ + "cut-generators"?: boolean; + /** + * (internal) Used by bsb to build to different targets: native (ocamlopt), bytecode + * (ocamlc) or JS (bsc) + */ + entries?: TargetItems[]; + /** + * Use the external stdlib library instead of the one shipped with the compiler package + */ + "external-stdlib"?: string; + /** + * Whether to generate the `.merlin` file for [Merlin](https://github.com/ocaml/merlin). + * Default: true + */ + "generate-merlin"?: boolean; + /** + * (WIP) Pre defined rules + */ + generators?: RuleGenerator[]; + /** + * gentype config, see cristianoc/genType for more details + */ + gentypeconfig?: GentypeSpecs; + /** + * a list of directories that bsb will not look into + */ + "ignored-dirs"?: string[]; + /** + * (Experimental) post-processing hook. bsb will invoke `cmd ${file}` whenever a `${file}` + * is changed + */ + "js-post-build"?: JSPostBuild; + /** + * Configuration for the JSX transformation. + */ + jsx?: JsxSpecs; + /** + * Package name + */ + name: string; + /** + * can be true/false or a customized name + */ + namespace?: boolean | string; + /** + * ReScript can currently output to [Commonjs](https://en.wikipedia.org/wiki/CommonJS), and + * [ES6 + * modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) + */ + "package-specs"?: + | Array + | ModuleFormat + | ModuleFormatObject; + /** + * Those dependencies are pinned (since version 8.4) + */ + "pinned-dependencies"?: string[]; + /** + * preprocessors to pass to compiler. The syntax is package_name/binary, for example: + * `pp/syntax.exe`. Currenly searches in `node_modules` + */ + "pp-flags"?: string; + /** + * PPX macros to pass to compiler. The syntax is package_name/binary, for example: + * `reason/reactjs_jsx_ppx_3.native`. Currenly searches in `node_modules` + */ + "ppx-flags"?: Array; + /** + * Configure reanalyze, a static code analysis tool for ReScript. + */ + reanalyze?: Reanalyze; + /** + * ReScript comes with [Reason](http://reasonml.github.io/) by default. Specific + * configurations here. + */ + reason?: ReasonSpecs; + /** + * Source code location + */ + sources: Array | SourcesObject | string; + suffix?: SuffixSpec; + /** + * (Experimental) whether to use the OCaml standard library. Default: true + */ + "use-stdlib"?: boolean; + /** + * The semantic version of the ReScript library + */ + version?: string; + /** + * warning numbers and whether to turn it into error or not + */ + warnings?: Warnings; +} + +/** + * (Not implemented yet) + */ +export interface BscFlagsObject { + flags?: string[]; + kind?: BscFlagsKind; +} + +export enum BscFlagsKind { + Append = "append", + Prefix = "prefix", + Reset = "reset", +} + +/** + * (internal) Used by bsb to build to different targets: native (ocamlopt), bytecode + * (ocamlc) or JS (bsc) + * + * A list of buildable targets + */ +export interface TargetItems { + /** + * The compiler to use for the target + */ + kind?: EntryKind; + /** + * Name of the main module used as entry point for this target. 'entry-point' isn't used + * when this project is built as a dependency. + */ + main?: string; +} + +/** + * The compiler to use for the target + */ +export enum EntryKind { + Bytecode = "bytecode", + JS = "js", + Native = "native", +} + +/** + * The shell command is running in *dev* time, and you generated could should be checked in, + * the depedency is tracked properly during dev time,example: `{ "name" : "ocamllex", + * "command" : "ocamllex.opt $in -o $out"}` + */ +export interface RuleGenerator { + command?: string; + name?: string; +} + +/** + * gentype config, see cristianoc/genType for more details + * + * path to gentype, path resolution is similar to ReScript + */ +export interface GentypeSpecs { + path?: string; +} + +/** + * (Experimental) post-processing hook. bsb will invoke `cmd ${file}` whenever a `${file}` + * is changed + */ +export interface JSPostBuild { + cmd?: string; +} + +/** + * Configuration for the JSX transformation. + */ +export interface JsxSpecs { + /** + * JSX transformation mode + */ + mode?: Mode; + /** + * JSX module, currently only support the React. + */ + module?: Module; + /** + * Build the given dependencies in JSX V3 compatibility mode. + */ + "v3-dependencies"?: string[]; + /** + * Whether to apply the specific version of JSX PPX transformation + */ + version: number; +} + +/** + * JSX transformation mode + */ +export enum Mode { + Automatic = "automatic", + Classic = "classic", +} + +/** + * JSX module, currently only support the React. + */ +export enum Module { + React = "react", +} + +/** + * es6-global generate relative `require` paths instead of relying on NodeJS' module + * resolution. Default: commonjs. + */ +export enum ModuleFormat { + Commonjs = "commonjs", + Es6 = "es6", + Es6Global = "es6-global", +} + +export interface ModuleFormatObject { + /** + * Default: false. + */ + "in-source"?: boolean; + module: ModuleFormat; + suffix?: SuffixSpec; +} + +/** + * suffix of generated js files, default to [.js] + */ +export enum SuffixSpec { + BsCjs = ".bs.cjs", + BsJS = ".bs.js", + BsMjs = ".bs.mjs", + Cjs = ".cjs", + JS = ".js", + Mjs = ".mjs", +} + +/** + * Configure reanalyze, a static code analysis tool for ReScript. + */ +export interface Reanalyze { + /** + * The types of analysis to activate. `dce` means dead code analysis, `exception` means + * exception analysis, and `termination` is to check for infinite loops. + */ + analysis?: Analysis[]; + /** + * Paths for any folders you'd like to exclude from analysis. Useful for bindings and + * similar. Example: `["src/bindings"]`. + */ + suppress?: string[]; + /** + * Any specific paths inside suppressed folders that you want to unsuppress. Example: + * ["src/bindings/SomeBinding.res"]. + */ + unsuppress?: string[]; +} + +export enum Analysis { + Dce = "dce", + Exception = "exception", + Termination = "termination", +} + +/** + * ReScript comes with [Reason](http://reasonml.github.io/) by default. Specific + * configurations here. + */ +export interface ReasonSpecs { + /** + * Whether to apply the + * [RescriptReact](https://github.com/rescript-lang/rescript-react)-specific JSX PPX + * transformation. + */ + "react-jsx"?: number; +} + +export interface SourcesObject { + /** + * name of the directory + */ + dir: string; + files?: string[] | FilesObject; + /** + * (WIP) Files generated in dev time + */ + generators?: BuildGenerator[]; + /** + * Not implemented yet + */ + group?: GroupObject | string; + "internal-depends"?: string[]; + /** + * Default: export all modules. It is recommended for library developers to hide some + * files/interfaces + */ + public?: string[] | PublicEnum; + resources?: string[]; + subdirs?: Array | boolean | SourcesObject | string; + type?: Type; +} + +export interface FilesObject { + /** + * Files to be excluded + */ + excludes?: string[]; + /** + * Regex to glob the patterns, syntax is documented + * [here](http://caml.inria.fr/pub/docs/manual-ocaml/libref/Str.html), for better + * incremental build performance, we'd suggest listing files explicitly + */ + "slow-re"?: string; +} + +/** + * Note that we will add the directory path accordingly + */ +export interface BuildGenerator { + edge?: string[]; + name?: string; +} + +export interface GroupObject { + /** + * When true, all subdirs are considered as a whole as dependency + */ + hierachy?: boolean; + name?: string; +} + +export enum PublicEnum { + All = "all", +} + +export enum Type { + Dev = "dev", +} + +/** + * warning numbers and whether to turn it into error or not + */ +export interface Warnings { + error?: boolean | string; + /** + * Default: -40+6+7+27+32..39+44+45 + * [Here](https://caml.inria.fr/pub/docs/manual-ocaml/comp.html#sec270) for the meanings of + * the warning flags + */ + number?: string; +} + +// Converts JSON strings to/from your types +export class Convert { + public static toBuildSchema(json: string): BuildSchema { + return JSON.parse(json); + } + + public static buildSchemaToJson(value: BuildSchema): string { + return JSON.stringify(value); + } +} diff --git a/server/src/utils.ts b/server/src/utils.ts index c37832327..69573bfe0 100644 --- a/server/src/utils.ts +++ b/server/src/utils.ts @@ -11,6 +11,8 @@ import { import fs from "fs"; import * as os from "os"; +import { BuildSchema } from "./buildSchema"; + let tempFilePrefix = "rescript_format_file_" + process.pid + "_"; let tempFileId = 0; @@ -219,14 +221,14 @@ export const toCamelCase = (text: string): string => { .replace(/(\s|-)+/g, ""); }; -const readBsConfig = (projDir: p.DocumentUri): any | null => { +const readBsConfig = (projDir: p.DocumentUri): BuildSchema | null => { try { let bsconfigFile = fs.readFileSync( path.join(projDir, c.bsconfigPartialPath), { encoding: "utf-8" } ); - let result = JSON.parse(bsconfigFile); + let result: BuildSchema = JSON.parse(bsconfigFile); return result; } catch (e) { return null; From 2710b34eed144f68c6f098a0477c7037062ecd46 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Mon, 10 Oct 2022 16:10:30 +0200 Subject: [PATCH 3/7] Make getSuffixAndPathFragmentFromBsconfig more typesafe --- server/src/constants.ts | 3 ++- server/src/utils.ts | 17 ++++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/server/src/constants.ts b/server/src/constants.ts index 1bc0bd55b..0709089ab 100644 --- a/server/src/constants.ts +++ b/server/src/constants.ts @@ -1,4 +1,5 @@ import * as path from "path"; +import { ModuleFormat } from "./buildSchema"; export let platformDir = process.arch == "arm64" ? process.platform + process.arch : process.platform; @@ -49,7 +50,7 @@ export let cmiExt = ".cmi"; export let startBuildAction = "Start Build"; // bsconfig defaults according configuration schema (https://rescript-lang.org/docs/manual/latest/build-configuration-schema) -export let bsconfigModuleDefault = "commonjs"; +export let bsconfigModuleDefault = ModuleFormat.Commonjs; export let bsconfigSuffixDefault = ".js"; export let configurationRequestId = "rescript_configuration_request"; diff --git a/server/src/utils.ts b/server/src/utils.ts index 69573bfe0..3733fc5b3 100644 --- a/server/src/utils.ts +++ b/server/src/utils.ts @@ -11,7 +11,7 @@ import { import fs from "fs"; import * as os from "os"; -import { BuildSchema } from "./buildSchema"; +import { BuildSchema, ModuleFormat, ModuleFormatObject } from "./buildSchema"; let tempFilePrefix = "rescript_format_file_" + process.pid + "_"; let tempFileId = 0; @@ -260,7 +260,7 @@ export const getNamespaceNameFromBsConfig = ( }; }; -let getCompiledFolderName = (moduleFormat: string): string => { +let getCompiledFolderName = (moduleFormat: ModuleFormat): string => { switch (moduleFormat) { case "es6": return "es6"; @@ -272,20 +272,23 @@ let getCompiledFolderName = (moduleFormat: string): string => { } }; -let getSuffixAndPathFragmentFromBsconfig = (bsconfig: any) => { +let getSuffixAndPathFragmentFromBsconfig = (bsconfig: BuildSchema) => { let pkgSpecs = bsconfig["package-specs"]; let pathFragment = ""; - let moduleFormatObj: any = {}; - let module = c.bsconfigModuleDefault; + let moduleFormatObj: ModuleFormatObject = { module: module }; let suffix = c.bsconfigSuffixDefault; if (pkgSpecs) { - if (pkgSpecs.module) { + if ( + !Array.isArray(pkgSpecs) && + typeof pkgSpecs !== "string" && + pkgSpecs.module + ) { moduleFormatObj = pkgSpecs; } else if (typeof pkgSpecs === "string") { module = pkgSpecs; - } else if (pkgSpecs[0]) { + } else if (Array.isArray(pkgSpecs) && pkgSpecs[0]) { if (typeof pkgSpecs[0] === "string") { module = pkgSpecs[0]; } else { From d76f8aac078296a9977688c971cf3037669d94e1 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Mon, 10 Oct 2022 16:33:59 +0200 Subject: [PATCH 4/7] Strip down build-schema to what the extension actually uses --- server/src/buildSchema.ts | 361 +------------------------------------- 1 file changed, 1 insertion(+), 360 deletions(-) diff --git a/server/src/buildSchema.ts b/server/src/buildSchema.ts index 99b3f9dd5..99c7cfe4f 100644 --- a/server/src/buildSchema.ts +++ b/server/src/buildSchema.ts @@ -1,243 +1,16 @@ // This file has been generated from https://raw.githubusercontent.com/rescript-lang/rescript-compiler/master/docs/docson/build-schema.json -// with https://app.quicktype.io/ +// with https://app.quicktype.io/ and stripped down to what we actually need for the extension. -// To parse this data: -// -// import { Convert, BuildSchema } from "./file"; -// -// const buildSchema = Convert.toBuildSchema(json); - -/** - * All paths are required to be in **Unix format** (foo/bar), the build system normalizes - * them for other platforms internally - */ export interface BuildSchema { - /** - * ReScript dependencies of the library, like in package.json. Currently searches in - * `node_modules` - */ - "bs-dependencies"?: string[]; - /** - * ReScript dev dependencies of the library, like in package.json. Currently searches in - * `node_modules` - */ - "bs-dev-dependencies"?: string[]; - /** - * (Not needed usually) external include directories, which will be applied `-I` to all - * compilation units - */ - "bs-external-includes"?: string[]; - /** - * Flags passed to bsc.exe - */ - "bsc-flags"?: string[] | BscFlagsObject; - /** - * Ignore generators, cut the dependency on generator tools - */ - "cut-generators"?: boolean; - /** - * (internal) Used by bsb to build to different targets: native (ocamlopt), bytecode - * (ocamlc) or JS (bsc) - */ - entries?: TargetItems[]; - /** - * Use the external stdlib library instead of the one shipped with the compiler package - */ - "external-stdlib"?: string; - /** - * Whether to generate the `.merlin` file for [Merlin](https://github.com/ocaml/merlin). - * Default: true - */ - "generate-merlin"?: boolean; - /** - * (WIP) Pre defined rules - */ - generators?: RuleGenerator[]; - /** - * gentype config, see cristianoc/genType for more details - */ - gentypeconfig?: GentypeSpecs; - /** - * a list of directories that bsb will not look into - */ - "ignored-dirs"?: string[]; - /** - * (Experimental) post-processing hook. bsb will invoke `cmd ${file}` whenever a `${file}` - * is changed - */ - "js-post-build"?: JSPostBuild; - /** - * Configuration for the JSX transformation. - */ - jsx?: JsxSpecs; - /** - * Package name - */ name: string; - /** - * can be true/false or a customized name - */ namespace?: boolean | string; - /** - * ReScript can currently output to [Commonjs](https://en.wikipedia.org/wiki/CommonJS), and - * [ES6 - * modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) - */ "package-specs"?: | Array | ModuleFormat | ModuleFormatObject; - /** - * Those dependencies are pinned (since version 8.4) - */ - "pinned-dependencies"?: string[]; - /** - * preprocessors to pass to compiler. The syntax is package_name/binary, for example: - * `pp/syntax.exe`. Currenly searches in `node_modules` - */ - "pp-flags"?: string; - /** - * PPX macros to pass to compiler. The syntax is package_name/binary, for example: - * `reason/reactjs_jsx_ppx_3.native`. Currenly searches in `node_modules` - */ - "ppx-flags"?: Array; - /** - * Configure reanalyze, a static code analysis tool for ReScript. - */ - reanalyze?: Reanalyze; - /** - * ReScript comes with [Reason](http://reasonml.github.io/) by default. Specific - * configurations here. - */ - reason?: ReasonSpecs; - /** - * Source code location - */ - sources: Array | SourcesObject | string; suffix?: SuffixSpec; - /** - * (Experimental) whether to use the OCaml standard library. Default: true - */ - "use-stdlib"?: boolean; - /** - * The semantic version of the ReScript library - */ - version?: string; - /** - * warning numbers and whether to turn it into error or not - */ - warnings?: Warnings; -} - -/** - * (Not implemented yet) - */ -export interface BscFlagsObject { - flags?: string[]; - kind?: BscFlagsKind; -} - -export enum BscFlagsKind { - Append = "append", - Prefix = "prefix", - Reset = "reset", -} - -/** - * (internal) Used by bsb to build to different targets: native (ocamlopt), bytecode - * (ocamlc) or JS (bsc) - * - * A list of buildable targets - */ -export interface TargetItems { - /** - * The compiler to use for the target - */ - kind?: EntryKind; - /** - * Name of the main module used as entry point for this target. 'entry-point' isn't used - * when this project is built as a dependency. - */ - main?: string; -} - -/** - * The compiler to use for the target - */ -export enum EntryKind { - Bytecode = "bytecode", - JS = "js", - Native = "native", -} - -/** - * The shell command is running in *dev* time, and you generated could should be checked in, - * the depedency is tracked properly during dev time,example: `{ "name" : "ocamllex", - * "command" : "ocamllex.opt $in -o $out"}` - */ -export interface RuleGenerator { - command?: string; - name?: string; } -/** - * gentype config, see cristianoc/genType for more details - * - * path to gentype, path resolution is similar to ReScript - */ -export interface GentypeSpecs { - path?: string; -} - -/** - * (Experimental) post-processing hook. bsb will invoke `cmd ${file}` whenever a `${file}` - * is changed - */ -export interface JSPostBuild { - cmd?: string; -} - -/** - * Configuration for the JSX transformation. - */ -export interface JsxSpecs { - /** - * JSX transformation mode - */ - mode?: Mode; - /** - * JSX module, currently only support the React. - */ - module?: Module; - /** - * Build the given dependencies in JSX V3 compatibility mode. - */ - "v3-dependencies"?: string[]; - /** - * Whether to apply the specific version of JSX PPX transformation - */ - version: number; -} - -/** - * JSX transformation mode - */ -export enum Mode { - Automatic = "automatic", - Classic = "classic", -} - -/** - * JSX module, currently only support the React. - */ -export enum Module { - React = "react", -} - -/** - * es6-global generate relative `require` paths instead of relying on NodeJS' module - * resolution. Default: commonjs. - */ export enum ModuleFormat { Commonjs = "commonjs", Es6 = "es6", @@ -245,17 +18,11 @@ export enum ModuleFormat { } export interface ModuleFormatObject { - /** - * Default: false. - */ "in-source"?: boolean; module: ModuleFormat; suffix?: SuffixSpec; } -/** - * suffix of generated js files, default to [.js] - */ export enum SuffixSpec { BsCjs = ".bs.cjs", BsJS = ".bs.js", @@ -264,129 +31,3 @@ export enum SuffixSpec { JS = ".js", Mjs = ".mjs", } - -/** - * Configure reanalyze, a static code analysis tool for ReScript. - */ -export interface Reanalyze { - /** - * The types of analysis to activate. `dce` means dead code analysis, `exception` means - * exception analysis, and `termination` is to check for infinite loops. - */ - analysis?: Analysis[]; - /** - * Paths for any folders you'd like to exclude from analysis. Useful for bindings and - * similar. Example: `["src/bindings"]`. - */ - suppress?: string[]; - /** - * Any specific paths inside suppressed folders that you want to unsuppress. Example: - * ["src/bindings/SomeBinding.res"]. - */ - unsuppress?: string[]; -} - -export enum Analysis { - Dce = "dce", - Exception = "exception", - Termination = "termination", -} - -/** - * ReScript comes with [Reason](http://reasonml.github.io/) by default. Specific - * configurations here. - */ -export interface ReasonSpecs { - /** - * Whether to apply the - * [RescriptReact](https://github.com/rescript-lang/rescript-react)-specific JSX PPX - * transformation. - */ - "react-jsx"?: number; -} - -export interface SourcesObject { - /** - * name of the directory - */ - dir: string; - files?: string[] | FilesObject; - /** - * (WIP) Files generated in dev time - */ - generators?: BuildGenerator[]; - /** - * Not implemented yet - */ - group?: GroupObject | string; - "internal-depends"?: string[]; - /** - * Default: export all modules. It is recommended for library developers to hide some - * files/interfaces - */ - public?: string[] | PublicEnum; - resources?: string[]; - subdirs?: Array | boolean | SourcesObject | string; - type?: Type; -} - -export interface FilesObject { - /** - * Files to be excluded - */ - excludes?: string[]; - /** - * Regex to glob the patterns, syntax is documented - * [here](http://caml.inria.fr/pub/docs/manual-ocaml/libref/Str.html), for better - * incremental build performance, we'd suggest listing files explicitly - */ - "slow-re"?: string; -} - -/** - * Note that we will add the directory path accordingly - */ -export interface BuildGenerator { - edge?: string[]; - name?: string; -} - -export interface GroupObject { - /** - * When true, all subdirs are considered as a whole as dependency - */ - hierachy?: boolean; - name?: string; -} - -export enum PublicEnum { - All = "all", -} - -export enum Type { - Dev = "dev", -} - -/** - * warning numbers and whether to turn it into error or not - */ -export interface Warnings { - error?: boolean | string; - /** - * Default: -40+6+7+27+32..39+44+45 - * [Here](https://caml.inria.fr/pub/docs/manual-ocaml/comp.html#sec270) for the meanings of - * the warning flags - */ - number?: string; -} - -// Converts JSON strings to/from your types -export class Convert { - public static toBuildSchema(json: string): BuildSchema { - return JSON.parse(json); - } - - public static buildSchemaToJson(value: BuildSchema): string { - return JSON.stringify(value); - } -} From b995fade6ce1edf85e3d5f77a510e9d309080410 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Tue, 11 Oct 2022 13:11:29 +0200 Subject: [PATCH 5/7] Add explanatory comment to getSuffixAndPathFragmentFromBsconfig --- server/src/utils.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/src/utils.ts b/server/src/utils.ts index 3733fc5b3..a37873f24 100644 --- a/server/src/utils.ts +++ b/server/src/utils.ts @@ -272,6 +272,8 @@ let getCompiledFolderName = (moduleFormat: ModuleFormat): string => { } }; +// Collect data from bsconfig to be able to find out the correct path of +// the compiled JS artifacts. let getSuffixAndPathFragmentFromBsconfig = (bsconfig: BuildSchema) => { let pkgSpecs = bsconfig["package-specs"]; let pathFragment = ""; From 6830b292941d8d07b495d908ca89ba43be2b13f0 Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Tue, 11 Oct 2022 15:57:41 +0200 Subject: [PATCH 6/7] Refactor lookup methods from utils into own file --- server/src/lookup.ts | 152 +++++++++++++++++++++++++++++++++++++++++++ server/src/server.ts | 19 ++++-- server/src/utils.ts | 136 ++++---------------------------------- 3 files changed, 176 insertions(+), 131 deletions(-) create mode 100644 server/src/lookup.ts diff --git a/server/src/lookup.ts b/server/src/lookup.ts new file mode 100644 index 000000000..c9dd28b2a --- /dev/null +++ b/server/src/lookup.ts @@ -0,0 +1,152 @@ +import * as fs from "fs"; +import * as path from "path"; +import * as p from "vscode-languageserver-protocol"; + +import { BuildSchema, ModuleFormat, ModuleFormatObject } from "./buildSchema"; +import * as c from "./constants"; + +const getCompiledFolderName = (moduleFormat: ModuleFormat): string => { + switch (moduleFormat) { + case "es6": + return "es6"; + case "es6-global": + return "es6_global"; + case "commonjs": + default: + return "js"; + } +}; + +export const replaceFileExtension = (filePath: string, ext: string): string => { + let name = path.basename(filePath, path.extname(filePath)); + return path.format({ dir: path.dirname(filePath), name, ext }); +}; + +// Check if filePartialPath exists at directory and return the joined path, +// otherwise recursively check parent directories for it. +export const findFilePathFromProjectRoot = ( + directory: p.DocumentUri | null, // This must be a directory and not a file! + filePartialPath: string +): null | p.DocumentUri => { + if (directory == null) { + return null; + } + + let filePath: p.DocumentUri = path.join(directory, filePartialPath); + if (fs.existsSync(filePath)) { + return filePath; + } + + let parentDir: p.DocumentUri = path.dirname(directory); + if (parentDir === directory) { + // reached the top + return null; + } + + return findFilePathFromProjectRoot(parentDir, filePartialPath); +}; + +export const readBsConfig = (projDir: p.DocumentUri): BuildSchema | null => { + try { + let bsconfigFile = fs.readFileSync( + path.join(projDir, c.bsconfigPartialPath), + { encoding: "utf-8" } + ); + + let result: BuildSchema = JSON.parse(bsconfigFile); + return result; + } catch (e) { + return null; + } +}; + +// Collect data from bsconfig to be able to find out the correct path of +// the compiled JS artifacts. +export const getSuffixAndPathFragmentFromBsconfig = (bsconfig: BuildSchema) => { + let pkgSpecs = bsconfig["package-specs"]; + let pathFragment = ""; + let module = c.bsconfigModuleDefault; + let moduleFormatObj: ModuleFormatObject = { module: module }; + let suffix = c.bsconfigSuffixDefault; + + if (pkgSpecs) { + if ( + !Array.isArray(pkgSpecs) && + typeof pkgSpecs !== "string" && + pkgSpecs.module + ) { + moduleFormatObj = pkgSpecs; + } else if (typeof pkgSpecs === "string") { + module = pkgSpecs; + } else if (Array.isArray(pkgSpecs) && pkgSpecs[0]) { + if (typeof pkgSpecs[0] === "string") { + module = pkgSpecs[0]; + } else { + moduleFormatObj = pkgSpecs[0]; + } + } + } + + if (moduleFormatObj["module"]) { + module = moduleFormatObj["module"]; + } + + if (!moduleFormatObj["in-source"]) { + pathFragment = "lib/" + getCompiledFolderName(module); + } + + if (moduleFormatObj.suffix) { + suffix = moduleFormatObj.suffix; + } else if (bsconfig.suffix) { + suffix = bsconfig.suffix; + } + + return [suffix, pathFragment]; +}; + +export const getFilenameFromBsconfig = ( + projDir: string, + filePath: string +): string | null => { + let bsconfig = readBsConfig(projDir); + + let partialFilePath = filePath.split(projDir)[1]; + + if (!bsconfig) { + return null; + } + + let [suffix, pathFragment] = getSuffixAndPathFragmentFromBsconfig(bsconfig); + + let compiledPartialPath = replaceFileExtension(partialFilePath, suffix); + + return path.join(projDir, pathFragment, compiledPartialPath); +}; + +// Monorepo helpers +export const getFilenameFromRootBsconfig = ( + projDir: string, + partialFilePath: string +): string | null => { + let rootBsConfigPath = findFilePathFromProjectRoot( + path.join("..", projDir), + c.bsconfigPartialPath + ); + + if (!rootBsConfigPath) { + return null; + } + + let rootBsconfig = readBsConfig(path.dirname(rootBsConfigPath)); + + if (!rootBsconfig) { + return null; + } + + let [suffix, pathFragment] = + getSuffixAndPathFragmentFromBsconfig(rootBsconfig); + + let compiledPartialPath = replaceFileExtension(partialFilePath, suffix); + + return path.join(projDir, pathFragment, compiledPartialPath); +}; diff --git a/server/src/server.ts b/server/src/server.ts index 7a1e374ea..2fb305f59 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -15,6 +15,7 @@ import { CodeLensParams, SignatureHelpParams, } from "vscode-languageserver-protocol"; +import * as lookup from "./lookup"; import * as utils from "./utils"; import * as codeActions from "./codeActions"; import * as c from "./constants"; @@ -102,7 +103,10 @@ let send: (msg: p.Message) => void = (_) => {}; let findRescriptBinary = (projectRootPath: p.DocumentUri | null) => extensionConfiguration.binaryPath == null - ? utils.findFilePathFromProjectRoot(projectRootPath, path.join(c.nodeModulesBinDir, c.rescriptBinName)) + ? lookup.findFilePathFromProjectRoot( + projectRootPath, + path.join(c.nodeModulesBinDir, c.rescriptBinName) + ) : utils.findBinary(extensionConfiguration.binaryPath, c.rescriptBinName); let findPlatformPath = (projectRootPath: p.DocumentUri | null) => { @@ -110,12 +114,15 @@ let findPlatformPath = (projectRootPath: p.DocumentUri | null) => { return extensionConfiguration.platformPath; } - let rescriptDir = utils.findFilePathFromProjectRoot(projectRootPath, path.join("node_modules", "rescript")); + let rescriptDir = lookup.findFilePathFromProjectRoot( + projectRootPath, + path.join("node_modules", "rescript") + ); if (rescriptDir == null) { return null; } - let platformPath = path.join(rescriptDir, c.platformDir) + let platformPath = path.join(rescriptDir, c.platformDir); // Workaround for darwinarm64 which has no folder yet in ReScript <= 9.1.4 if ( @@ -127,10 +134,10 @@ let findPlatformPath = (projectRootPath: p.DocumentUri | null) => { } return platformPath; -} +}; let findBscExeBinary = (projectRootPath: p.DocumentUri | null) => - utils.findBinary(findPlatformPath(projectRootPath), c.bscExeName) + utils.findBinary(findPlatformPath(projectRootPath), c.bscExeName); interface CreateInterfaceRequestParams { uri: string; @@ -941,7 +948,7 @@ function createInterface(msg: p.RequestMessage): p.Message { let result = typeof response.result === "string" ? response.result : ""; try { - let resiPath = utils.replaceFileExtension(filePath, c.resiExt); + let resiPath = lookup.replaceFileExtension(filePath, c.resiExt); fs.writeFileSync(resiPath, result, { encoding: "utf-8" }); let response: p.ResponseMessage = { jsonrpc: c.jsonrpcVersion, diff --git a/server/src/utils.ts b/server/src/utils.ts index a37873f24..6319dadd0 100644 --- a/server/src/utils.ts +++ b/server/src/utils.ts @@ -1,5 +1,3 @@ -import * as c from "./constants"; -import * as codeActions from "./codeActions"; import * as childProcess from "child_process"; import * as p from "vscode-languageserver-protocol"; import * as path from "path"; @@ -11,7 +9,9 @@ import { import fs from "fs"; import * as os from "os"; -import { BuildSchema, ModuleFormat, ModuleFormatObject } from "./buildSchema"; +import * as codeActions from "./codeActions"; +import * as c from "./constants"; +import * as lookup from "./lookup"; let tempFilePrefix = "rescript_format_file_" + process.pid + "_"; let tempFileId = 0; @@ -40,30 +40,6 @@ export let findProjectRootOfFile = ( } }; -// Check if filePartialPath exists at directory and return the joined path, -// otherwise recursively check parent directories for it. -export let findFilePathFromProjectRoot = ( - directory: p.DocumentUri | null, // This must be a directory and not a file! - filePartialPath: string -): null | p.DocumentUri => { - if (directory == null) { - return null; - } - - let filePath: p.DocumentUri = path.join(directory, filePartialPath); - if (fs.existsSync(filePath)) { - return filePath; - } - - let parentDir: p.DocumentUri = path.dirname(directory); - if (parentDir === directory) { - // reached the top - return null; - } - - return findFilePathFromProjectRoot(parentDir, filePartialPath); -}; - // Check if binaryName exists inside binaryDirPath and return the joined path. export let findBinary = ( binaryDirPath: p.DocumentUri | null, @@ -210,35 +186,16 @@ export let getReferencesForPosition = ( position.character, ]); -export let replaceFileExtension = (filePath: string, ext: string): string => { - let name = path.basename(filePath, path.extname(filePath)); - return path.format({ dir: path.dirname(filePath), name, ext }); -}; - export const toCamelCase = (text: string): string => { return text .replace(/(?:^\w|[A-Z]|\b\w)/g, (s: string) => s.toUpperCase()) .replace(/(\s|-)+/g, ""); }; -const readBsConfig = (projDir: p.DocumentUri): BuildSchema | null => { - try { - let bsconfigFile = fs.readFileSync( - path.join(projDir, c.bsconfigPartialPath), - { encoding: "utf-8" } - ); - - let result: BuildSchema = JSON.parse(bsconfigFile); - return result; - } catch (e) { - return null; - } -}; - export const getNamespaceNameFromBsConfig = ( projDir: p.DocumentUri ): execResult => { - let bsconfig = readBsConfig(projDir); + let bsconfig = lookup.readBsConfig(projDir); let result = ""; if (!bsconfig) { @@ -260,104 +217,33 @@ export const getNamespaceNameFromBsConfig = ( }; }; -let getCompiledFolderName = (moduleFormat: ModuleFormat): string => { - switch (moduleFormat) { - case "es6": - return "es6"; - case "es6-global": - return "es6_global"; - case "commonjs": - default: - return "js"; - } -}; - -// Collect data from bsconfig to be able to find out the correct path of -// the compiled JS artifacts. -let getSuffixAndPathFragmentFromBsconfig = (bsconfig: BuildSchema) => { - let pkgSpecs = bsconfig["package-specs"]; - let pathFragment = ""; - let module = c.bsconfigModuleDefault; - let moduleFormatObj: ModuleFormatObject = { module: module }; - let suffix = c.bsconfigSuffixDefault; - - if (pkgSpecs) { - if ( - !Array.isArray(pkgSpecs) && - typeof pkgSpecs !== "string" && - pkgSpecs.module - ) { - moduleFormatObj = pkgSpecs; - } else if (typeof pkgSpecs === "string") { - module = pkgSpecs; - } else if (Array.isArray(pkgSpecs) && pkgSpecs[0]) { - if (typeof pkgSpecs[0] === "string") { - module = pkgSpecs[0]; - } else { - moduleFormatObj = pkgSpecs[0]; - } - } - } - - if (moduleFormatObj["module"]) { - module = moduleFormatObj["module"]; - } - - if (!moduleFormatObj["in-source"]) { - pathFragment = "lib/" + getCompiledFolderName(module); - } - - if (moduleFormatObj.suffix) { - suffix = moduleFormatObj.suffix; - } else if (bsconfig.suffix) { - suffix = bsconfig.suffix; - } - - return [suffix, pathFragment]; -}; - export let getCompiledFilePath = ( filePath: string, projDir: string ): execResult => { - let bsconfig = readBsConfig(projDir); let error: execResult = { kind: "error", error: "Could not read bsconfig", }; - if (!bsconfig) { + let compiledPath = lookup.getFilenameFromBsconfig(projDir, filePath); + + if (!compiledPath) { return error; } - let [suffix, pathFragment] = getSuffixAndPathFragmentFromBsconfig(bsconfig); - let partialFilePath = filePath.split(projDir)[1]; - let compiledPartialPath = replaceFileExtension(partialFilePath, suffix); - let result = path.join(projDir, pathFragment, compiledPartialPath); + let result = compiledPath; // If the file is not found, lookup a possible root bsconfig that may contain // info about the possible location of the file. if (!fs.existsSync(result)) { - let rootBsConfigPath = findFilePathFromProjectRoot( - path.join("..", projDir), - c.bsconfigPartialPath - ); - - if (!rootBsConfigPath) { - return error; - } - - let rootBsconfig = readBsConfig(path.dirname(rootBsConfigPath)); + let compiledPath = lookup.getFilenameFromRootBsconfig(projDir, filePath); - if (!rootBsconfig) { + if (!compiledPath) { return error; } - let [rootSuffix, rootPathFragment] = - getSuffixAndPathFragmentFromBsconfig(rootBsconfig); - - let compiledPartialPath = replaceFileExtension(partialFilePath, rootSuffix); - result = path.join(projDir, rootPathFragment, compiledPartialPath); + result = compiledPath; } return { From 3d8b4d52b36a7f92f4813336853e2e6e44cbf10b Mon Sep 17 00:00:00 2001 From: Florian Hammerschmidt Date: Tue, 11 Oct 2022 17:13:21 +0200 Subject: [PATCH 7/7] Fix partialFilePath parameter --- server/src/lookup.ts | 4 +--- server/src/utils.ts | 9 ++++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/server/src/lookup.ts b/server/src/lookup.ts index c9dd28b2a..62c0535ec 100644 --- a/server/src/lookup.ts +++ b/server/src/lookup.ts @@ -106,12 +106,10 @@ export const getSuffixAndPathFragmentFromBsconfig = (bsconfig: BuildSchema) => { export const getFilenameFromBsconfig = ( projDir: string, - filePath: string + partialFilePath: string ): string | null => { let bsconfig = readBsConfig(projDir); - let partialFilePath = filePath.split(projDir)[1]; - if (!bsconfig) { return null; } diff --git a/server/src/utils.ts b/server/src/utils.ts index 6319dadd0..1535c7050 100644 --- a/server/src/utils.ts +++ b/server/src/utils.ts @@ -225,8 +225,8 @@ export let getCompiledFilePath = ( kind: "error", error: "Could not read bsconfig", }; - - let compiledPath = lookup.getFilenameFromBsconfig(projDir, filePath); + let partialFilePath = filePath.split(projDir)[1]; + let compiledPath = lookup.getFilenameFromBsconfig(projDir, partialFilePath); if (!compiledPath) { return error; @@ -237,7 +237,10 @@ export let getCompiledFilePath = ( // If the file is not found, lookup a possible root bsconfig that may contain // info about the possible location of the file. if (!fs.existsSync(result)) { - let compiledPath = lookup.getFilenameFromRootBsconfig(projDir, filePath); + let compiledPath = lookup.getFilenameFromRootBsconfig( + projDir, + partialFilePath + ); if (!compiledPath) { return error;