diff --git a/src/formatters.ts b/src/formatters.ts index d5af3c9..2b422ee 100644 --- a/src/formatters.ts +++ b/src/formatters.ts @@ -4,33 +4,40 @@ import { ToolCheckFunc, Tool, type FormattingProvider } from "./types.js"; import * as muon from "./tools/muon.js"; import * as meson from "./tools/meson.js"; -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 = { +const formatters: Record< + FormattingProvider, + FormatterDefinition | 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 +51,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 +72,7 @@ async function getBestAvailableFormatter(provider: FormattingProvider | "auto"): continue; } - return { provider: providerName, tool: checkResult.tool }; + return { provider: providerName, tool: checkResult.data }; } return errors; @@ -105,7 +112,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 9899051..c41fb87 100644 --- a/src/linters.ts +++ b/src/linters.ts @@ -3,11 +3,15 @@ import { extensionConfiguration, getOutputChannel } from "./utils.js"; import { ExtensionConfiguration, LinterConfiguration, ToolCheckFunc, Tool } from "./types.js"; import * as muon from "./tools/muon.js"; -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 f3b98f8..7f2a20a 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.js"; -import { Tool, ToolCheckResult } from "../types.js"; +import { Tool, CheckResult } from "../types.js"; import { getMesonVersion } from "../introspection.js"; import { Version } from "../version.js"; -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( + return 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 aa64d80..7290901 100644 --- a/src/tools/muon.ts +++ b/src/tools/muon.ts @@ -1,9 +1,13 @@ import * as vscode from "vscode"; import { ExecResult, exec, execFeed, extensionConfiguration, getOutputChannel } from "../utils.js"; -import { Tool, ToolCheckResult } from "../types.js"; +import { Tool, CheckResult } from "../types.js"; import { Version, type VersionArray } from "../version.js"; -export async function lint(muon: Tool, root: string, document: vscode.TextDocument): Promise { +export interface MuonOptions {} + +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 +55,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 +86,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 +95,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 +114,7 @@ export async function check(): Promise { return Number.parseInt(s); }) as VersionArray; - return ToolCheckResult.newTool({ path: muon_path, version: new Version(ver) }); + const options: MuonOptions = {}; + + return CheckResult.newData({ path: muon_path, version: new Version(ver), options }); } diff --git a/src/types.ts b/src/types.ts index aabd7a1..1307af1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,35 +3,35 @@ import type { Version } from "./version.js"; type Dict = { [x: string]: T }; -export type Tool = { path: string; version: Version }; +export type Tool = { path: string; version: Version; 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 +46,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 df1c5aa..c681032 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.js"; import { getMesonBuildOptions } from "./introspection.js"; import { extensionPath, workspaceState } from "./extension.js";