Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 26 additions & 21 deletions src/formatters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<vscode.TextEdit[]>;

type FormatterDefinition = {
format: FormatterFunc;
check: ToolCheckFunc;
type FormatterFunc<Options> = (
tool: Tool<Options>,
root: string,
document: vscode.TextDocument,
) => Promise<vscode.TextEdit[]>;

type FormatterDefinition<Options> = {
format: FormatterFunc<Options>;
check: ToolCheckFunc<Options>;
priority: number;
};

//NOTE: the highest priority number means it is tested first, the lowest is tested last
const formatters: Record<FormattingProvider, FormatterDefinition> = {
muon: {
format: muon.format,
check: muon.check,
priority: 0,
},
meson: {
format: meson.format,
check: meson.check,
priority: 1,
},
};
const formatters: Record<FormattingProvider, FormatterDefinition<undefined> | FormatterDefinition<meson.MesonOptions>> =
{
muon: {
format: muon.format,
check: muon.check,
priority: 0,
} as FormatterDefinition<undefined>,
meson: {
format: meson.format,
check: meson.check,
priority: 1,
} as FormatterDefinition<meson.MesonOptions>,
};

type FormatterError = { provider: FormattingProvider; error: string };

type BestTool = {
provider: FormattingProvider;
tool: Tool;
tool: Tool<undefined> | Tool<meson.MesonOptions>;
};

type BestFormatterResult = BestTool | FormatterError[];
Expand All @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -105,7 +110,7 @@ async function reloadFormatters(sourceRoot: string, context: vscode.ExtensionCon

const sub = vscode.languages.registerDocumentFormattingEditProvider("meson", {
async provideDocumentFormattingEdits(document: vscode.TextDocument): Promise<vscode.TextEdit[]> {
return await props.format(tool, sourceRoot, document);
return await (props.format as FormatterFunc<unknown>)(tool as Tool<unknown>, sourceRoot, document);
},
});

Expand Down
10 changes: 7 additions & 3 deletions src/linters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<vscode.Diagnostic[]>;
type LinterFunc = (
tool: Tool<undefined>,
sourceRoot: string,
document: vscode.TextDocument,
) => Promise<vscode.Diagnostic[]>;

type LinterDefinition = {
lint: LinterFunc;
check: ToolCheckFunc;
check: ToolCheckFunc<undefined>;
};

const linters: Record<keyof ExtensionConfiguration["linter"], LinterDefinition> = {
Expand Down Expand Up @@ -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);
}

Expand Down
35 changes: 28 additions & 7 deletions src/tools/meson.ts
Original file line number Diff line number Diff line change
@@ -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<vscode.TextEdit[]> {
export interface MesonOptions {
supportsFileNameArgument: boolean;
}

export type MesonTool = Tool<MesonOptions>;

export async function format(
meson: MesonTool,
root: string,
document: vscode.TextDocument,
): Promise<vscode.TextEdit[]> {
const originalDocumentText = document.getText();

let args = ["format"];
Expand All @@ -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 :(
Expand All @@ -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<ToolCheckResult> {
export async function check(): Promise<CheckResult<MesonTool>> {
const meson_path = mesonProgram();

let mesonVersion;
Expand All @@ -45,23 +60,29 @@ export async function check(): Promise<ToolCheckResult> {
} catch (e) {
const error = e as Error;
console.log(error);
return ToolCheckResult.newError(error.message);
return CheckResult.newError<MesonTool>(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<MesonTool>(
`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<MesonTool>(
`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<MesonTool>({ path: meson_path, version: mesonVersion, options });
}
16 changes: 9 additions & 7 deletions src/tools/muon.ts
Original file line number Diff line number Diff line change
@@ -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<vscode.Diagnostic[]> {
export type MuonTool = Tool<undefined>;

export async function lint(muon: MuonTool, root: string, document: vscode.TextDocument): Promise<vscode.Diagnostic[]> {
const { stdout, stderr } = await execFeed(
muon.path,
["analyze", "-l", "-O", document.uri.fsPath],
Expand Down Expand Up @@ -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<vscode.TextEdit[]> {
export async function format(muon: MuonTool, root: string, document: vscode.TextDocument): Promise<vscode.TextEdit[]> {
const originalDocumentText = document.getText();

let args = ["fmt"];
Expand Down Expand Up @@ -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<ToolCheckResult> {
export async function check(): Promise<CheckResult<MuonTool>> {
const muon_path = extensionConfiguration("muonPath");
let stdout: string, stderr: string;

Expand All @@ -91,12 +93,12 @@ export async function check(): Promise<ToolCheckResult> {
} catch (e) {
const { error, stdout, stderr } = e as ExecResult;
console.log(error);
return ToolCheckResult.newError(error!.message);
return CheckResult.newError<MuonTool>(error!.message);
}

const line1 = stdout.split("\n")[0].split(" ");
if (line1.length !== 2) {
return ToolCheckResult.newError(`Invalid version string: ${line1}`);
return CheckResult.newError<MuonTool>(`Invalid version string: ${line1}`);
}

const ver = line1[1]
Expand All @@ -110,5 +112,5 @@ export async function check(): Promise<ToolCheckResult> {
return Number.parseInt(s);
}) as VersionArray;

return ToolCheckResult.newTool({ path: muon_path, version: new Version(ver) });
return CheckResult.newData<MuonTool>({ path: muon_path, version: new Version(ver) });
}
38 changes: 20 additions & 18 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,37 @@ import type { Version } from "./version";

type Dict<T> = { [x: string]: T };

export type Tool = { path: string; version: Version };
export type Tool<Options> = { path: string; version: Version } & (Options extends undefined
? {}
: { options: Options });

export type ToolCheckSuccessResult = {
tool: Tool;
export type CheckSuccessResult<Data> = {
data: Data;
error?: undefined;
};
export type ToolCheckErrorResult = {
tool?: undefined;
export type CheckErrorResult = {
data?: undefined;
error: string;
};

type ResultImpl = ToolCheckSuccessResult | ToolCheckErrorResult;
type ResultImpl<Data> = CheckSuccessResult<Data> | CheckErrorResult;

export class ToolCheckResult {
private readonly result: ResultImpl;
export class CheckResult<Data> {
private readonly result: ResultImpl<Data>;

private constructor(result: ResultImpl) {
private constructor(result: ResultImpl<Data>) {
this.result = result;
}

static newError(error: string) {
return new ToolCheckResult({ error });
static newError<D>(error: string): CheckResult<D> {
return new CheckResult<D>({ error });
}

static newTool(tool: Tool) {
return new ToolCheckResult({ tool });
static newData<D>(data: D): CheckResult<D> {
return new CheckResult<D>({ data });
}

private hasErrorImpl(result: ResultImpl): result is ToolCheckErrorResult {
private hasErrorImpl(result: ResultImpl<Data>): result is CheckErrorResult {
return !!result.error;
}

Expand All @@ -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<ToolCheckResult>;
export type ToolCheckFunc<D> = () => Promise<CheckResult<Tool<D>>>;

export type LinterConfiguration = {
enabled: boolean;
Expand Down
4 changes: 2 additions & 2 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Loading