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
  • Loading branch information
Totto16 committed Dec 9, 2024
1 parent 8a42451 commit 5e9c9e9
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 24 deletions.
10 changes: 5 additions & 5 deletions src/formatters.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as vscode from "vscode";
import { extensionConfiguration, getOutputChannel } from "./utils";
import { checkHasError, extensionConfiguration, getOutputChannel } from "./utils";
import { ToolCheckFunc, Tool } from "./types";
import * as muon from "./tools/muon";

Expand Down 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 (checkHasError(checkResult)) {
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) {
return match.slice(1, 3).map((s) => Number.parseInt(s)) as [number, number, number];
return new Version(match.slice(1, 3).map((s) => Number.parseInt(s)) as VersionArray);
} else throw new Error("Meson version doesn't match expected output: " + stdout.trim());
}
10 changes: 5 additions & 5 deletions src/linters.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as vscode from "vscode";
import { extensionConfiguration, getOutputChannel } from "./utils";
import { checkHasError, extensionConfiguration, getOutputChannel } from "./utils";
import { ExtensionConfiguration, LinterConfiguration, ToolCheckFunc, Tool } from "./types";
import * as muon from "./tools/muon";

Expand Down 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 (checkHasError(checkResult)) {
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
17 changes: 10 additions & 7 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, type 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 @@ -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 { tool: { path: muon_path, version: new Version(ver) } };
}
17 changes: 15 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
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 interface ToolCheckSuccessResult {
tool: Tool;
error?: undefined;
}
export interface ToolCheckErrorResult {
tool?: undefined;
error: string;
}

export type ToolCheckResult = ToolCheckSuccessResult | ToolCheckErrorResult;
export type ToolCheckFunc = () => Promise<ToolCheckResult>;

export type LinterConfiguration = {
enabled: boolean;
Expand Down
13 changes: 12 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 Expand Up @@ -209,3 +216,7 @@ export function whenFileExists(ctx: vscode.ExtensionContext, file: string, liste
export function mesonProgram(): string {
return which.sync(extensionConfiguration("mesonPath"));
}

export function checkHasError(result: ToolCheckResult): result is ToolCheckErrorResult {
return !!result.error;
}
74 changes: 74 additions & 0 deletions src/version.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
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 "object" at runtime
*
* @param version the version toc heck
*/
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) {
if (Array.isArray(version)) {
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 5e9c9e9

Please sign in to comment.