Skip to content

Commit

Permalink
feat: add proper version type
Browse files Browse the repository at this point in the history
this type allows pretty printing, version comparison, and runtime checks, to detect such errors as the one fixed in the previous commit

also use the proper compare methods, where we previously did it by hand

add class for ToolCheckResult: it is now easier to check if it's an error or not
  • Loading branch information
Totto16 authored and tristan957 committed Dec 12, 2024
1 parent 9a40eac commit 9fb8065
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 24 deletions.
8 changes: 4 additions & 4 deletions src/formatters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,16 @@ async function reloadFormatters(sourceRoot: string, context: vscode.ExtensionCon
const name = extensionConfiguration("formatting").provider;
const props = formatters[name];

const { tool, error } = await props.check();
if (error) {
getOutputChannel().appendLine(`Failed to enable formatter ${name}: ${error}`);
const checkResult = await props.check();
if (checkResult.isError()) {
getOutputChannel().appendLine(`Failed to enable formatter ${name}: ${checkResult.error}`);
getOutputChannel().show(true);
return disposables;
}

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(checkResult.tool, sourceRoot, document);
},
});

Expand Down
8 changes: 5 additions & 3 deletions src/introspection.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as path from "path";
import { exec, extensionConfiguration, parseJSONFileIfExists, getOutputChannel } from "./utils";
import { Targets, Dependencies, BuildOptions, Tests, ProjectInfo, Compilers } from "./types";
import { type VersionArray, Version } from "./version";

export function getIntrospectionFile(buildDir: string, filename: string) {
return path.join(buildDir, path.join("meson-info", filename));
Expand All @@ -23,7 +24,8 @@ async function introspectMeson<T>(buildDir: string, filename: string, introspect
export async function getMesonTargets(buildDir: string) {
const parsed = await introspectMeson<Targets>(buildDir, "intro-targets.json", "--targets");

if ((await getMesonVersion())[1] < 50) {
// if (mesonVersion < 0.50.0)
if ((await getMesonVersion()).compare([0, 50, 0]) < 0) {
return parsed.map((t) => {
if (typeof t.filename === "string") t.filename = [t.filename]; // Old versions would directly pass a string with only 1 filename on the target
return t;
Expand Down Expand Up @@ -56,12 +58,12 @@ export async function getMesonBenchmarks(buildDir: string) {
return introspectMeson<Tests>(buildDir, "intro-benchmarks.json", "--benchmarks");
}

export async function getMesonVersion(): Promise<[number, number, number]> {
export async function getMesonVersion(): Promise<Version> {
const MESON_VERSION_REGEX = /^(\d+)\.(\d+)\.(\d+)/;

const { stdout } = await exec(extensionConfiguration("mesonPath"), ["--version"]);
const match = stdout.trim().match(MESON_VERSION_REGEX);
if (match && match.length >= 4) {
return match.slice(1, 4).map((s) => Number.parseInt(s)) as [number, number, number];
return new Version(match.slice(1, 4).map((s) => Number.parseInt(s)) as VersionArray);
} else throw new Error("Meson version doesn't match expected output: " + stdout.trim());
}
8 changes: 4 additions & 4 deletions src/linters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,14 @@ async function reloadLinters(
}

const props = linters[name];
const { tool, error } = await props.check();
if (error) {
getOutputChannel().appendLine(`Failed to enable linter ${name}: ${error}`);
const checkResult = await props.check();
if (checkResult.isError()) {
getOutputChannel().appendLine(`Failed to enable linter ${name}: ${checkResult.error}`);
getOutputChannel().show(true);
continue;
}

const linter = async (document: vscode.TextDocument) => await props.lint(tool!, sourceRoot, document);
const linter = async (document: vscode.TextDocument) => await props.lint(checkResult.tool, sourceRoot, document);
enabledLinters.push(linter);
}

Expand Down
2 changes: 1 addition & 1 deletion src/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export async function testDebugHandler(
relevantTests.some((test) => test.depends.some((dep) => dep == target.id)),
);

var args = ["compile", "-C", buildDir];
let args = ["compile", "-C", buildDir];
requiredTargets.forEach((target) => {
args.push(target.name);
});
Expand Down
21 changes: 12 additions & 9 deletions src/tools/muon.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as vscode from "vscode";
import { ExecResult, exec, execFeed, extensionConfiguration, getOutputChannel } from "../utils";
import { Tool } from "../types";
import { Tool, ToolCheckResult } from "../types";
import { Version, type VersionArray } from "../version";

export async function lint(muon: Tool, root: string, document: vscode.TextDocument): Promise<vscode.Diagnostic[]> {
const { stdout, stderr } = await execFeed(
Expand All @@ -10,8 +11,9 @@ export async function lint(muon: Tool, root: string, document: vscode.TextDocume
document.getText(),
);

var out;
if (muon.version[0] == 0 && muon.version[1] <= 3) {
let out: string;
// if (muonVersion < 0.4.0)
if (muon.version.compare([0, 4, 0]) < 0) {
out = stderr;
} else {
out = stdout;
Expand Down Expand Up @@ -54,7 +56,8 @@ export async function format(muon: Tool, root: string, document: vscode.TextDocu

let args = ["fmt"];

if (muon.version[0] == 0 && muon.version[1] == 0) {
// if (muonVersion < 0.1.0)
if (muon.version.compare([0, 1, 0]) < 0) {
args = ["fmt_unstable"];
}

Expand All @@ -79,7 +82,7 @@ export async function format(muon: Tool, root: string, document: vscode.TextDocu
return [new vscode.TextEdit(documentRange, stdout)];
}

export async function check(): Promise<{ tool?: Tool; error?: string }> {
export async function check(): Promise<ToolCheckResult> {
const muon_path = extensionConfiguration("muonPath");
let stdout: string, stderr: string;

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

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

const ver = line1[1]
Expand All @@ -105,7 +108,7 @@ export async function check(): Promise<{ tool?: Tool; error?: string }> {
}

return Number.parseInt(s);
}) as [number, number, number];
}) as VersionArray;

return { tool: { path: muon_path, version: ver } };
return ToolCheckResult.newTool({ path: muon_path, version: new Version(ver) });
}
56 changes: 54 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,60 @@
import * as vscode from "vscode";
import type { Version } from "./version";

type Dict<T> = { [x: string]: T };
export type Tool = { path: string; version: [number, number, number] };
export type ToolCheckFunc = () => Promise<{ tool?: Tool; error?: string }>;

export type Tool = { path: string; version: Version };

export type ToolCheckSuccessResult = {
tool: Tool;
error?: undefined;
};
export type ToolCheckErrorResult = {
tool?: undefined;
error: string;
};

type ResultImpl = ToolCheckSuccessResult | ToolCheckErrorResult;

export class ToolCheckResult {
private readonly result: ResultImpl;

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

static newError(error: string) {
return new ToolCheckResult({ error });
}

static newTool(tool: Tool) {
return new ToolCheckResult({ tool });
}

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

isError(): boolean {
return this.hasErrorImpl(this.result);
}

get error(): string {
if (!this.hasErrorImpl(this.result)) {
throw new Error("Wrong invocation of getter for 'error', check the state first");
}
return this.result.error;
}

get tool(): Tool {
if (this.hasErrorImpl(this.result)) {
throw new Error("Wrong invocation of getter for 'tool', check the state first");
}
return this.result.tool;
}
}

export type ToolCheckFunc = () => Promise<ToolCheckResult>;

export type LinterConfiguration = {
enabled: boolean;
Expand Down
9 changes: 8 additions & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@ import * as vscode from "vscode";
import * as which from "which";

import { createHash, BinaryLike } from "crypto";
import { ExtensionConfiguration, Target, SettingsKey, ModifiableExtension } from "./types";
import {
ExtensionConfiguration,
Target,
SettingsKey,
ModifiableExtension,
type ToolCheckResult,
type ToolCheckErrorResult,
} from "./types";
import { getMesonBuildOptions } from "./introspection";
import { extensionPath, workspaceState } from "./extension";

Expand Down
72 changes: 72 additions & 0 deletions src/version.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
export type VersionArray = [number, number, number];

const versionNames = ["major", "minor", "patch"] as const;

export class Version {
constructor(private readonly version: VersionArray) {
const isValid = Version.isValidVersion(this.version);

if (isValid !== true) {
throw isValid;
}
}

/** This checks if any type is a valid version array at runtime
*
* @param version the version to check
*/
private static isValidVersion(version: Version | any): true | Error {
if (!Array.isArray(version)) {
return new Error("Version object is not an Array");
}

if (version.length !== 3) {
return new Error(`Version array has ${version.length} entries, but expected 3`);
}

for (const index in version as VersionArray) {
const num = version[index];
if (!Number.isInteger(num)) {
const name = versionNames[index];
return new Error(`${name} version component is not a number: '${num}'`);
}
}

return true;
}

/** This compares two versions
* - if the first one is bigger, a value > 0 is returned
* - if they are the same, 0 is returned
* - if the first one is smaller, a value < 0 is returned
* @param version1
* @param version2
*/
private static compareImpl([major1, minor1, patch1]: VersionArray, [major2, minor2, patch2]: VersionArray): number {
if (major1 !== major2) {
return major1 - major2;
}

if (minor1 !== minor2) {
return minor1 - minor2;
}

return patch1 - patch2;
}

compareWithOther(otherVersion: Version): number {
return Version.compareImpl(this.version, otherVersion.version);
}

compare(otherVersion: VersionArray): number {
return Version.compareImpl(this.version, otherVersion);
}

private static versionToString([major, minor, patch]: VersionArray): string {
return `${major}.${minor}.${patch}`;
}

toString(): string {
return Version.versionToString(this.version);
}
}

0 comments on commit 9fb8065

Please sign in to comment.