| import * as vscode from 'vscode'; |
| import * as path from 'path'; |
| import * as fs from 'fs'; |
| |
| export enum FormatToolErrorCause { |
| ToolNotSpecified, ToolNotExecutable, FormatterError |
| } |
| |
| export class FormatToolError extends Error { |
| cause: FormatToolErrorCause; |
| formatterMessage: string | undefined; |
| |
| constructor(cause: FormatToolErrorCause) { |
| super(); |
| this.cause = cause; |
| } |
| } |
| |
| /** |
| * Finds the Zircon build output directory given a Fuchsia repository root. |
| * |
| * This function derives the correct filesystem path, but it doesn't check to |
| * ensure that the path exists. Callers are responsible for ensuring that the |
| * resulting path exists and has the expected contents. |
| * |
| * @param fuchsiaRoot the filesystem path to the root of the Fuchsia |
| * repository. |
| * @returns the filesystem path to the root of the Zircon build output |
| * directory. |
| */ |
| function deriveZirconOutPath(fuchsiaRoot: string): string | null { |
| const buildOutputPointer = path.join(fuchsiaRoot, '.fx-build-dir'); |
| try { |
| const outputRoot = fs.readFileSync(buildOutputPointer, { encoding: 'utf8' }).trim(); |
| return path.join(fuchsiaRoot, outputRoot + '.zircon'); |
| } catch (err) { |
| // TODO: proper error handling |
| return null; |
| } |
| } |
| |
| /** |
| * Finds the fidl-format tool given a Zircon output repository root. |
| * |
| * This function derives the correct filesystem path, but it doesn't check to |
| * ensure that the path exists. Callers are responsible for ensuring that the |
| * resulting path exists and has the expected contents. |
| * |
| * @param zirconRoot the filesystem path to the root of the Fuchsia |
| * repository. |
| * @returns the filesystem path to the fidl-format tool. |
| */ |
| function deriveFidlFormatPath(zirconRoot: string): string | null { |
| return path.join(zirconRoot, 'tools', 'fidl-format'); |
| } |
| |
| /** |
| * Attempts to find the fidl-format tool in the current directory, returning |
| * the path to the tool if it's present and executable by the current user. |
| * |
| * @param folderPath The path to the folder to search in. |
| * @returns The path to the fidl-format tool in folderPath or null if the tool can't be found or isn't executable. |
| */ |
| function findFidlFormatInFolder(folderPath: string): string | null { |
| const zirconRoot = deriveZirconOutPath(folderPath); |
| if (!zirconRoot) { |
| return null; |
| } |
| |
| const toolPath = deriveFidlFormatPath(zirconRoot); |
| if (!toolPath) { |
| return null; |
| } |
| |
| try { |
| fs.accessSync(toolPath, fs.constants.X_OK); |
| } catch (err) { |
| return null; |
| } |
| |
| return toolPath; |
| } |
| |
| /** |
| * Attempts to find the fidl-format tool by asking VS Code to find the root of |
| * the Fuchsia repository. |
| * |
| * @returns the absolute path to fidl-format or null if it couldn't be found. |
| */ |
| async function guessFidlFormatPathFromWorkspace(): Promise<string | null> { |
| const uris = await vscode.workspace.findFiles('.fx-build-dir', null, 1); |
| if (uris.length < 1) { |
| return null; |
| } |
| |
| for (const uri of uris) { |
| if (uri.scheme !== 'file') { |
| // If the repository isn't local, fidl-format won't be runnable |
| // even if present. |
| continue; |
| } |
| |
| const basePath = path.dirname(uri.fsPath); |
| const toolPath = findFidlFormatInFolder(basePath); |
| if (toolPath === null) { |
| continue; |
| } |
| |
| return toolPath; |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Attempts to find the fidl-format tool by traversing the directory tree up |
| * from the root of each workspace folder toward the root of the filesystem. |
| * |
| * @returns the absolute path to fidl-format or null if it couldn't be found. |
| */ |
| function guessFidlFormatPathFromAncestors(): string | null { |
| const folders = vscode.workspace.workspaceFolders; |
| if (!folders) { |
| return null; |
| } |
| |
| for (const folder of folders) { |
| if (folder.uri.scheme !== 'file') { |
| // If the repository isn't local, fidl-format won't be runnable |
| // even if present. |
| continue; |
| } |
| |
| let folderPath; |
| |
| // Traverse through parent repositories until the direct child of the |
| // root directory. |
| for (folderPath = folder.uri.fsPath; |
| path.dirname(folderPath) !== folderPath; |
| folderPath = path.dirname(folderPath)) { |
| |
| const toolPath = findFidlFormatInFolder(folderPath); |
| if (toolPath) { |
| return toolPath; |
| } |
| } |
| |
| // Also check the root directory. |
| const toolPath = findFidlFormatInFolder(folderPath); |
| if (toolPath) { |
| return toolPath; |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Gets the fidl-format tool path as configured in VS Code preferences. |
| * |
| * @returns the path to fidl-format as configured by the user, or `null` if the |
| * tool isn't set. |
| */ |
| export function getFidlFormatToolPathFromPreferences(): string | null { |
| let formatTool: string | undefined | null |
| = vscode.workspace.getConfiguration('fidl').get('formatTool'); |
| |
| if (formatTool) { |
| try { |
| fs.accessSync(formatTool, fs.constants.X_OK); |
| } catch (err) { |
| throw new FormatToolError(FormatToolErrorCause.ToolNotExecutable); |
| } |
| return formatTool; |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Checks whether the path to fidl-format has been filled out. If the default |
| * value of an empty string is found, an error is shown as a notification. |
| */ |
| export async function guessFidlFormatToolPath(): Promise<string> { |
| let formatTool: string | undefined | null |
| = getFidlFormatToolPathFromPreferences(); |
| |
| // Guessing by ancestor is faster than searching a large workspace for the |
| // Fuchsia root, so we try this approach first. |
| if (!formatTool) { |
| formatTool = guessFidlFormatPathFromAncestors(); |
| } |
| |
| if (!formatTool) { |
| formatTool = await guessFidlFormatPathFromWorkspace(); |
| } |
| |
| if (!formatTool) { |
| throw new FormatToolError(FormatToolErrorCause.ToolNotSpecified); |
| } |
| |
| try { |
| // Throws if tool is not present or not executable. |
| fs.accessSync(formatTool, fs.constants.X_OK); |
| } catch (err) { |
| throw new FormatToolError(FormatToolErrorCause.ToolNotExecutable); |
| } |
| |
| return formatTool; |
| } |