From f95c7c48d97a0d9c0a4b9eb866caf972f900c460 Mon Sep 17 00:00:00 2001 From: Nicolas Hedger Date: Fri, 4 Oct 2024 19:26:51 +0200 Subject: [PATCH] refactor constants and add comments --- src/binary-finder.ts | 33 +++++---- src/constants.ts | 113 +++++++++++++++++++++++++++++ src/downloader.ts | 17 +++-- src/project.ts | 7 +- src/session.ts | 18 ++--- src/utils.ts | 168 ++++++++++++++++++++++--------------------- 6 files changed, 239 insertions(+), 117 deletions(-) create mode 100644 src/constants.ts diff --git a/src/binary-finder.ts b/src/binary-finder.ts index dcc429ff..08a09c08 100644 --- a/src/binary-finder.ts +++ b/src/binary-finder.ts @@ -3,16 +3,14 @@ import { dirname, join } from "node:path"; import { delimiter } from "node:path"; import { Uri, window } from "vscode"; import { config, getLspBin } from "./config"; +import { + platformIdentifier, + platformSpecificBinaryName, + platformSpecificNodePackageName, +} from "./constants"; import { downloadBiome, getDownloadedVersion } from "./downloader"; import { debug, info } from "./logger"; -import { - binaryName, - fileExists, - getPackageName, - hasNodeDependencies, - packageName, - platform, -} from "./utils"; +import { fileExists, hasNodeDependencies } from "./utils"; export type LocatorStrategy = { /** @@ -94,8 +92,8 @@ const vsCodeSettingsStrategy: LocatorStrategy = { const findPlatformSpecificBinary = async ( bin: Record, ): Promise => { - if (platform in bin) { - return findBinary(bin[platform]); + if (platformIdentifier in bin) { + return findBinary(bin[platformIdentifier]); } return undefined; @@ -147,10 +145,14 @@ const nodeModulesStrategy: LocatorStrategy = { // We need to resolve the package.json file here because the // platform-specific packages do not have an entry point. const binPackage = dirname( - biomePackage.resolve(`${getPackageName()}/package.json`), + biomePackage.resolve( + `${platformSpecificNodePackageName}/package.json`, + ), ); - const binPath = Uri.file(join(binPackage, binaryName())); + const binPath = Uri.file( + join(binPackage, platformSpecificBinaryName), + ); if (!(await fileExists(binPath))) { return undefined; @@ -193,7 +195,7 @@ const yarnPnpStrategy: LocatorStrategy = { } return yarnPnpApi.resolveRequest( - `${packageName}/${binaryName()}`, + `${platformSpecificNodePackageName}/${platformSpecificBinaryName}`, biomePackage, ); } catch { @@ -223,7 +225,10 @@ const pathEnvironmentVariableStrategy: LocatorStrategy = { } for (const dir of pathEnv.split(delimiter)) { - const biome = Uri.joinPath(Uri.file(dir), binaryName()); + const biome = Uri.joinPath( + Uri.file(dir), + platformSpecificBinaryName, + ); if (await fileExists(biome)) { return biome; diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 00000000..c1f6e0f0 --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,113 @@ +import { version, workspace } from "vscode"; + +/** + * Platform identifier + * + * This constant contains the identifier of the current platform. + * + * @example "linux-x64-musl" + * @example "darwin-arm64" + * @example "win32-x64" + */ +export const platformIdentifier = (() => { + // On Linux, we always use the `musl` flavor because it has the advantage of + // having been built statically. This is meant to improve the compatibility + // with various systems such as NixOS, which handle dynamically linked + // binaries differently. + const flavor = process.platform === "linux" ? "-musl" : ""; + + return `${process.platform}-${process.arch}${flavor}`; +})(); + +/** + * Platform-specific binary name + * + * This constant contains the name of the Biome CLI binary for the current + * platform. + * + * @example "biome" (on Linux, macOS, and other Unix-like systems) + * @example "biome.exe" (on Windows) + */ +export const platformSpecificBinaryName = (() => { + return `biome${process.platform === "win32" ? ".exe" : ""}`; +})(); + +/** + * Platform-specific package name + * + * This constant contains the name of the Biome CLI asset for the current + * platform. + * + * @example "cli-linux-x64" + * @example "cli-darwin-x64" + * @example "cli-win32-x64" + */ +export const platformSpecificPackageName = (() => { + return `cli-${platformIdentifier}`; +})(); + +/** + * Platform-specific node package name + * + * This constant contains the name of the Biome CLI node package for the current + * platform. + * + * @example "@biomejs/cli-linux-x64" + * @example "@biomejs/cli-darwin-x64" + * @example "@biomejs/cli-win32-x64" + */ +export const platformSpecificNodePackageName = (() => { + return `@biomejs/${platformSpecificPackageName}`; +})(); + +/** + * Identifiers of the languages supported by the extension + * + * This constant contains a list of identifiers of the languages supported by the + * extension. These identifiers are used determine whether LSP sessions should be + * taking a given file into account or not. + */ +export const supportedLanguageIdentifiers: string[] = [ + "astro", + "css", + "graphql", + "javascript", + "javascriptreact", + "json", + "jsonc", + "svelte", + "typescript", + "typescriptreact", + "vue", +]; + +export type OperatingMode = "single-file" | "single-root" | "multi-root"; + +/** + * Operating mode of the extension + * + * This constant contains the operating mode of the extension. The operating + * mode determines whether the extension is operating in single-file, + * single-root, or multi-root mode, which impacts how the extension behaves and + * how LSP sessions are created. + * + * This can be a constant because whenever the operating mode changes, VS Code + * reloads the window, which causes the extension to be destroyed and + * recreated. + */ +export const operatingMode: OperatingMode = (() => { + // If there aren't any workspace folders, we assume to be operating in + // single-file mode. + if (workspace.workspaceFolders === undefined) { + return "single-file"; + } + + // If more than one workspace folder is present, we assume to be operating + // in multi-root mode. + if (workspace.workspaceFolders.length > 1) { + return "multi-root"; + } + + // Otherwise, we assume to be operating in single-root mode. + return "single-root"; +})(); diff --git a/src/downloader.ts b/src/downloader.ts index 958a39b5..400b35e2 100644 --- a/src/downloader.ts +++ b/src/downloader.ts @@ -8,14 +8,13 @@ import { window, workspace, } from "vscode"; +import { + platformSpecificBinaryName, + platformSpecificPackageName, +} from "./constants"; import { error, info } from "./logger"; import { state } from "./state"; -import { - binaryExtension, - binaryName, - fileExists, - platformPackageName, -} from "./utils"; +import { fileExists } from "./utils"; export const downloadBiome = async (): Promise => { const version = await promptVersionToDownload(); @@ -47,7 +46,7 @@ const downloadBiomeVersion = async ( .json(); const asset = releases.assets.find((asset) => { - return asset.name === platformPackageName; + return asset.name === platformSpecificPackageName; }); if (!asset) { @@ -69,7 +68,7 @@ const downloadBiomeVersion = async ( const binPath = Uri.joinPath( state.context.globalStorageUri, "global-bin", - `biome${binaryExtension}`, + platformSpecificBinaryName, ); try { @@ -93,7 +92,7 @@ export const getDownloadedVersion = async (): Promise< const binPath = Uri.joinPath( state.context.globalStorageUri, "global-bin", - binaryName("biome"), + platformSpecificBinaryName, ); if (!(await fileExists(binPath))) { diff --git a/src/project.ts b/src/project.ts index 79473d78..48e86a8c 100644 --- a/src/project.ts +++ b/src/project.ts @@ -12,9 +12,10 @@ import { isEnabled, workspaceFolderRequiresConfigFile, } from "./config"; +import { operatingMode, supportedLanguageIdentifiers } from "./constants"; import { error, warn } from "./logger"; import { state } from "./state"; -import { directoryExists, fileExists, mode, supportedLanguages } from "./utils"; +import { directoryExists, fileExists } from "./utils"; export type Project = { folder?: WorkspaceFolder; @@ -30,7 +31,7 @@ export type ProjectDefinition = { }; export const createProjects = async () => { - if (mode === "single-file") { + if (operatingMode === "single-file") { const project = await createSingleFileProject(); return project ? [project] : []; } @@ -260,7 +261,7 @@ export const updateActiveProject = (editor: TextEditor) => { state.hidden = editor?.document === undefined || - !supportedLanguages.includes(editor.document.languageId); + !supportedLanguageIdentifiers.includes(editor.document.languageId); state.activeProject = project; }; diff --git a/src/session.ts b/src/session.ts index b24de305..8316cb7e 100644 --- a/src/session.ts +++ b/src/session.ts @@ -16,20 +16,18 @@ import { import { displayName } from "../package.json"; import { findBiomeGlobally, findBiomeLocally } from "./binary-finder"; import { isEnabledGlobally } from "./config"; +import { operatingMode, supportedLanguageIdentifiers } from "./constants"; import { debug, error, info, error as logError, warn } from "./logger"; import { type Project, createProjects } from "./project"; import { state } from "./state"; import { - binaryName, - directoryExists, fileExists, fileIsExecutable, + generatePlatformSpecificVersionedBinaryName, hasUntitledDocuments, hasVSCodeUserDataDocuments, - mode, shortURI, subtractURI, - supportedLanguages, } from "./utils"; export type Session = { @@ -104,7 +102,7 @@ const copyBinaryToTemporaryLocation = async ( const location = Uri.joinPath( state.context.globalStorageUri, "tmp-bin", - binaryName(`biome-${version}`), + generatePlatformSpecificVersionedBinaryName(version), ); try { @@ -305,7 +303,8 @@ const createLspLogger = (project?: Project): LogOutputChannel => { // In this case, we display the name of the project and the relative path to // the project root in the logger name. Additionally, when in a multi-root // workspace, we prefix the path with the name of the workspace folder. - const prefix = mode === "multi-root" ? `${project.folder.name}::` : ""; + const prefix = + operatingMode === "multi-root" ? `${project.folder.name}::` : ""; const path = subtractURI(project.path, project.folder.uri).fsPath; return window.createOutputChannel(`${displayName} LSP (${prefix}${path})`, { @@ -333,7 +332,8 @@ const createLspTraceLogger = (project?: Project): LogOutputChannel => { // In this case, we display the name of the project and the relative path to // the project root in the logger name. Additionally, when in a multi-root // workspace, we prefix the path with the name of the workspace folder. - const prefix = mode === "multi-root" ? `${project.folder.name}::` : ""; + const prefix = + operatingMode === "multi-root" ? `${project.folder.name}::` : ""; const path = subtractURI(project.path, project.folder.uri).fsPath; return window.createOutputChannel( @@ -354,7 +354,7 @@ const createLspTraceLogger = (project?: Project): LogOutputChannel => { */ const createDocumentSelector = (project?: Project): DocumentFilter[] => { if (project) { - return supportedLanguages.map((language) => ({ + return supportedLanguageIdentifiers.map((language) => ({ language, scheme: "file", pattern: Uri.joinPath(project.path, "**", "*").fsPath.replace( @@ -364,7 +364,7 @@ const createDocumentSelector = (project?: Project): DocumentFilter[] => { })); } - return supportedLanguages.flatMap((language) => { + return supportedLanguageIdentifiers.flatMap((language) => { return ["untitled", "vscode-userdata"].map((scheme) => ({ language, scheme, diff --git a/src/utils.ts b/src/utils.ts index 16401bc1..cd3d2849 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,26 +1,25 @@ import { constants, accessSync } from "node:fs"; import { FileType, RelativePattern, Uri, workspace } from "vscode"; +import { operatingMode } from "./constants"; import { debug } from "./logger"; import type { Project } from "./project"; import { state } from "./state"; /** - * Returns the platform-specific NPM package name of the Biome CLI + * Generates a platform-specific versioned binary name * - * This function computes and returns the platform-specific NPM package name of - * the Biome CLI. The package name is computed based on the current platform and - * architecture, and whether the system's C library is musl. + * This function generates a platform-specific versioned binary name for the + * current platform and given version of Biome. * - * @example "@biomejs/cli-linux-x64-musl" - * @example "@biomejs/cli-darwin-x64" - * @example "@biomejs/cli-win32-x64" + * @param version The version of Biome * - * @returns The platform-specific NPM package name of the Biome CLI + * @example "biome-1.0.0" (on Linux, macOS, and other Unix-like systems) + * @example "biome.exe-1.0.0" (on Windows) */ -export const getPackageName = (): string => { - const flavor = process.platform === "linux" ? "-musl" : ""; - - return `@biomejs/cli-${process.platform}-${process.arch}${flavor}`; +export const generatePlatformSpecificVersionedBinaryName = ( + version: string, +) => { + return `biome-${version}${process.platform === "win32" ? ".exe" : ""}`; }; /** @@ -80,49 +79,6 @@ export const anyFileExists = async (uris: Uri[]): Promise => { return false; }; -/** - * Supported languages for the extension - * - * This array contains the supported languages for the extension. These are - * language identifiers, not file extensions. - */ -export const supportedLanguages: string[] = [ - "astro", - "css", - "graphql", - "javascript", - "javascriptreact", - "json", - "jsonc", - "svelte", - "typescript", - "typescriptreact", - "vue", -]; - -/** - * The name of the Biome executable - * - * This constant contains the name of the Biome executable. The name is suffixed - * with ".exe" on Windows, and is returned as-is on other platforms. - */ - -export const binaryName = (name = "biome") => `${name}${binaryExtension}`; - -export const binaryExtension = process.platform === "win32" ? ".exe" : ""; - -/** - * Name of the Biome CLI NPM package - */ -export const packageName = getPackageName(); - -/** - * The platform identifier - */ -export const platform = `${process.platform}-${process.arch}${ - process.platform === "linux" ? "-musl" : "" -}`; - /** * Substracts the second string from the first string */ @@ -137,42 +93,68 @@ export const subtractURI = (original: Uri, subtract: Uri): Uri | undefined => { return Uri.parse(result); }; +/** + * Generates a short URI for display purposes + * + * This function generates a short URI for display purposes. It takes into + * account the operating mode of the extension and the project folder name. + * + * This is primarily used for naming logging channels. + * + * @param project The project for which the short URI is generated + * + * @example "/hello-world" (in single-root mode) + * @example "workspace-folder-1::/hello-world" (in multi-root mode) + */ export const shortURI = (project: Project) => { - const prefix = mode === "multi-root" ? `${project.folder.name}::` : ""; + const prefix = + operatingMode === "multi-root" ? `${project.folder.name}::` : ""; return `${prefix}${subtractURI(project.path, project.folder.uri).fsPath}`; }; -export const determineMode = (): - | "single-file" - | "single-root" - | "multi-root" => { - if (workspace.workspaceFolders === undefined) { - return "single-file"; - } - - if (workspace.workspaceFolders.length > 1) { - return "multi-root"; - } - - return "single-root"; +/** + * Checks if there are any open untitled documents in the workspace + * + * This function verifies the presence of open documents within the workspace + * that have not yet been saved to disk. It will return true if any such + * documents are identified, otherwise, it returns false if none are found. + * + * This is typically used to determine if the user is working with an untitled + * document in the workspace. + */ +export const hasUntitledDocuments = (): boolean => { + return ( + workspace.textDocuments.find( + (document) => document.isUntitled === true, + ) !== undefined + ); }; -export const mode = determineMode(); - /** - * Indicates whether there are any untitled documents currently open in the - * workspace + * Checks if there are any open VS Code User Data documents in the workspace + * + * This function verifies the presence of open documents within the workspace + * that utilize the vscode-userdata scheme. It will return true if any such + * documents are identified, otherwise, it returns false if none are found. + * + * This is typically used to determine if the user's settings.json file is open + * in the workspace. */ -export const hasUntitledDocuments = (): boolean => - workspace.textDocuments.find((doc) => doc.isUntitled) !== undefined; - -export const hasVSCodeUserDataDocuments = (): boolean => - workspace.textDocuments.find( - (doc) => doc.uri.scheme === "vscode-userdata", - ) !== undefined; - -export const platformPackageName = `biome-${platform}`; +export const hasVSCodeUserDataDocuments = (): boolean => { + return ( + workspace.textDocuments.find( + ({ uri }) => uri.scheme === "vscode-userdata", + ) !== undefined + ); +}; +/** + * Debounces a function + * + * This function debounces a function by waiting for a specified delay before + * executing it. It returns a new function that wraps the original function and + * only executes it after the specified delay has passed. + */ export const debounce = ( fn: (...args: TArgs) => void, delay = 300, @@ -184,6 +166,14 @@ export const debounce = ( }; }; +/** + * Checks if a file is executable + * + * This function checks if a file is executable using Node's `accessSync` function. + * It returns true if the file is executable, otherwise it returns false. + * + * This is used to ensure that downloaded Biome binaries are executable. + */ export const fileIsExecutable = (uri: Uri): boolean => { try { accessSync(uri.fsPath, constants.X_OK); @@ -193,6 +183,13 @@ export const fileIsExecutable = (uri: Uri): boolean => { } }; +/** + * Checks if a directory contains any node dependencies + * + * This function checks if a directory contains any node dependencies by + * searching for any `package.json` files within the directory. It returns true + * if any `package.json` files are found, otherwise it returns false. + */ export const hasNodeDependencies = async (path: Uri) => { const results = await workspace.findFiles( new RelativePattern(path, "**/package.json"), @@ -201,6 +198,13 @@ export const hasNodeDependencies = async (path: Uri) => { return results.length > 0; }; +/** + * Clears temporary binaries + * + * This function clears any temporary binaries that may have been created by + * the extension. It deletes the `tmp-bin` directory within the global storage + * directory. + */ export const clearTemporaryBinaries = async () => { const binDirPath = Uri.joinPath(state.context.globalStorageUri, "tmp-bin"); if (await directoryExists(binDirPath)) {