diff --git a/src/formatters.ts b/src/formatters.ts index 1d5c63e..490b63f 100644 --- a/src/formatters.ts +++ b/src/formatters.ts @@ -4,33 +4,38 @@ import { ToolCheckFunc, Tool, type FormattingProvider } from "./types"; import * as muon from "./tools/muon"; import * as meson from "./tools/meson"; -type FormatterFunc = (tool: Tool, root: string, document: vscode.TextDocument) => Promise; - -type FormatterDefinition = { - format: FormatterFunc; - check: ToolCheckFunc; +type FormatterFunc = ( + tool: Tool, + root: string, + document: vscode.TextDocument, +) => Promise; + +type FormatterDefinition = { + format: FormatterFunc; + check: ToolCheckFunc; priority: number; }; //NOTE: the highest priority number means it is tested first, the lowest is tested last -const formatters: Record = { - muon: { - format: muon.format, - check: muon.check, - priority: 0, - }, - meson: { - format: meson.format, - check: meson.check, - priority: 1, - }, -}; +const formatters: Record | FormatterDefinition> = + { + muon: { + format: muon.format, + check: muon.check, + priority: 0, + } as FormatterDefinition, + meson: { + format: meson.format, + check: meson.check, + priority: 1, + } as FormatterDefinition, + }; type FormatterError = { provider: FormattingProvider; error: string }; type BestTool = { provider: FormattingProvider; - tool: Tool; + tool: Tool | Tool; }; type BestFormatterResult = BestTool | FormatterError[]; @@ -44,7 +49,7 @@ async function getBestAvailableFormatter(provider: FormattingProvider | "auto"): return [{ provider, error: checkResult.error }]; } - return { provider, tool: checkResult.tool }; + return { provider, tool: checkResult.data }; } // sort the available providers by priority @@ -65,7 +70,7 @@ async function getBestAvailableFormatter(provider: FormattingProvider | "auto"): continue; } - return { provider: providerName, tool: checkResult.tool }; + return { provider: providerName, tool: checkResult.data }; } return errors; @@ -105,7 +110,7 @@ async function reloadFormatters(sourceRoot: string, context: vscode.ExtensionCon const sub = vscode.languages.registerDocumentFormattingEditProvider("meson", { async provideDocumentFormattingEdits(document: vscode.TextDocument): Promise { - return await props.format(tool, sourceRoot, document); + return await (props.format as FormatterFunc)(tool as Tool, sourceRoot, document); }, }); diff --git a/src/linters.ts b/src/linters.ts index 5df7a29..1489ab0 100644 --- a/src/linters.ts +++ b/src/linters.ts @@ -3,11 +3,15 @@ import { extensionConfiguration, getOutputChannel } from "./utils"; import { ExtensionConfiguration, LinterConfiguration, ToolCheckFunc, Tool } from "./types"; import * as muon from "./tools/muon"; -type LinterFunc = (tool: Tool, sourceRoot: string, document: vscode.TextDocument) => Promise; +type LinterFunc = ( + tool: Tool, + sourceRoot: string, + document: vscode.TextDocument, +) => Promise; type LinterDefinition = { lint: LinterFunc; - check: ToolCheckFunc; + check: ToolCheckFunc; }; const linters: Record = { @@ -45,7 +49,7 @@ async function reloadLinters( continue; } - const linter = async (document: vscode.TextDocument) => await props.lint(checkResult.tool, sourceRoot, document); + const linter = async (document: vscode.TextDocument) => await props.lint(checkResult.data, sourceRoot, document); enabledLinters.push(linter); } diff --git a/src/tools/meson.ts b/src/tools/meson.ts index 9ab9154..e4897fb 100644 --- a/src/tools/meson.ts +++ b/src/tools/meson.ts @@ -1,10 +1,20 @@ import * as vscode from "vscode"; import { execFeed, extensionConfiguration, getOutputChannel, mesonProgram } from "../utils"; -import { Tool, ToolCheckResult } from "../types"; +import { Tool, CheckResult } from "../types"; import { getMesonVersion } from "../introspection"; import { Version } from "../version"; -export async function format(meson: Tool, root: string, document: vscode.TextDocument): Promise { +export interface MesonOptions { + supportsFileNameArgument: boolean; +} + +export type MesonTool = Tool; + +export async function format( + meson: MesonTool, + root: string, + document: vscode.TextDocument, +): Promise { const originalDocumentText = document.getText(); let args = ["format"]; @@ -15,6 +25,10 @@ export async function format(meson: Tool, root: string, document: vscode.TextDoc } args.push("-"); + if (meson.options.supportsFileNameArgument) { + args.push("--source-file-path", document.fileName); + } + const { stdout, stderr, error } = await execFeed(meson.path, args, { cwd: root }, originalDocumentText); if (error) { //TODO: file a bug report, meson prints some errors on stdout :( @@ -35,8 +49,9 @@ export async function format(meson: Tool, root: string, document: vscode.TextDoc const formattingSupportedSinceVersion = new Version([1, 5, 0]); const formattingWithStdinSupportedSinceVersion = new Version([1, 7, 0]); +const formattingWithFileNameArgumentSinceVersion = new Version([1, 9, 0]); -export async function check(): Promise { +export async function check(): Promise> { const meson_path = mesonProgram(); let mesonVersion; @@ -45,23 +60,29 @@ export async function check(): Promise { } catch (e) { const error = e as Error; console.log(error); - return ToolCheckResult.newError(error.message); + return CheckResult.newError(error.message); } // meson format was introduced in 1.5.0 // see https://mesonbuild.com/Commands.html#format if (mesonVersion.compareWithOther(formattingSupportedSinceVersion) < 0) { - ToolCheckResult.newError( + CheckResult.newError( `Meson supports formatting only since version ${formattingSupportedSinceVersion}, but you have version ${mesonVersion}`, ); } // using "-" as stdin is only supported since 1.7.0 (see https://github.com/mesonbuild/meson/pull/13793) if (mesonVersion.compareWithOther(formattingWithStdinSupportedSinceVersion) < 0) { - return ToolCheckResult.newError( + return CheckResult.newError( `Meson supports formatting from stdin only since version ${formattingWithStdinSupportedSinceVersion}, but you have version ${mesonVersion}`, ); } - return ToolCheckResult.newTool({ path: meson_path, version: mesonVersion }); + const supportsFileNameArgument = mesonVersion.compareWithOther(formattingWithFileNameArgumentSinceVersion) >= 0; + + const options: MesonOptions = { + supportsFileNameArgument, + }; + + return CheckResult.newData({ path: meson_path, version: mesonVersion, options }); } diff --git a/src/tools/muon.ts b/src/tools/muon.ts index 619401b..a8b7039 100644 --- a/src/tools/muon.ts +++ b/src/tools/muon.ts @@ -1,9 +1,11 @@ import * as vscode from "vscode"; import { ExecResult, exec, execFeed, extensionConfiguration, getOutputChannel } from "../utils"; -import { Tool, ToolCheckResult } from "../types"; +import { Tool, CheckResult } from "../types"; import { Version, type VersionArray } from "../version"; -export async function lint(muon: Tool, root: string, document: vscode.TextDocument): Promise { +export type MuonTool = Tool; + +export async function lint(muon: MuonTool, root: string, document: vscode.TextDocument): Promise { const { stdout, stderr } = await execFeed( muon.path, ["analyze", "-l", "-O", document.uri.fsPath], @@ -51,7 +53,7 @@ export async function lint(muon: Tool, root: string, document: vscode.TextDocume return diagnostics; } -export async function format(muon: Tool, root: string, document: vscode.TextDocument): Promise { +export async function format(muon: MuonTool, root: string, document: vscode.TextDocument): Promise { const originalDocumentText = document.getText(); let args = ["fmt"]; @@ -82,7 +84,7 @@ export async function format(muon: Tool, root: string, document: vscode.TextDocu return [new vscode.TextEdit(documentRange, stdout)]; } -export async function check(): Promise { +export async function check(): Promise> { const muon_path = extensionConfiguration("muonPath"); let stdout: string, stderr: string; @@ -91,12 +93,12 @@ export async function check(): Promise { } catch (e) { const { error, stdout, stderr } = e as ExecResult; console.log(error); - return ToolCheckResult.newError(error!.message); + return CheckResult.newError(error!.message); } const line1 = stdout.split("\n")[0].split(" "); if (line1.length !== 2) { - return ToolCheckResult.newError(`Invalid version string: ${line1}`); + return CheckResult.newError(`Invalid version string: ${line1}`); } const ver = line1[1] @@ -110,5 +112,5 @@ export async function check(): Promise { return Number.parseInt(s); }) as VersionArray; - return ToolCheckResult.newTool({ path: muon_path, version: new Version(ver) }); + return CheckResult.newData({ path: muon_path, version: new Version(ver) }); } diff --git a/src/types.ts b/src/types.ts index dd340e5..ddc1eeb 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,35 +3,37 @@ import type { Version } from "./version"; type Dict = { [x: string]: T }; -export type Tool = { path: string; version: Version }; +export type Tool = { path: string; version: Version } & (Options extends undefined + ? {} + : { options: Options }); -export type ToolCheckSuccessResult = { - tool: Tool; +export type CheckSuccessResult = { + data: Data; error?: undefined; }; -export type ToolCheckErrorResult = { - tool?: undefined; +export type CheckErrorResult = { + data?: undefined; error: string; }; -type ResultImpl = ToolCheckSuccessResult | ToolCheckErrorResult; +type ResultImpl = CheckSuccessResult | CheckErrorResult; -export class ToolCheckResult { - private readonly result: ResultImpl; +export class CheckResult { + private readonly result: ResultImpl; - private constructor(result: ResultImpl) { + private constructor(result: ResultImpl) { this.result = result; } - static newError(error: string) { - return new ToolCheckResult({ error }); + static newError(error: string): CheckResult { + return new CheckResult({ error }); } - static newTool(tool: Tool) { - return new ToolCheckResult({ tool }); + static newData(data: D): CheckResult { + return new CheckResult({ data }); } - private hasErrorImpl(result: ResultImpl): result is ToolCheckErrorResult { + private hasErrorImpl(result: ResultImpl): result is CheckErrorResult { return !!result.error; } @@ -46,15 +48,15 @@ export class ToolCheckResult { return this.result.error; } - get tool(): Tool { + get data(): Data { if (this.hasErrorImpl(this.result)) { - throw new Error("Wrong invocation of getter for 'tool', check the state first"); + throw new Error("Wrong invocation of getter for 'data', check the state first"); } - return this.result.tool; + return this.result.data; } } -export type ToolCheckFunc = () => Promise; +export type ToolCheckFunc = () => Promise>>; export type LinterConfiguration = { enabled: boolean; diff --git a/src/utils.ts b/src/utils.ts index ab831d1..4e9943c 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -10,8 +10,8 @@ import { Target, SettingsKey, ModifiableExtension, - type ToolCheckResult, - type ToolCheckErrorResult, + type CheckResult, + type CheckErrorResult, } from "./types"; import { getMesonBuildOptions } from "./introspection"; import { extensionPath, workspaceState } from "./extension";