Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ci(lint): add checks for variables #522

Merged
merged 14 commits into from
Feb 24, 2024
Merged
5 changes: 5 additions & 0 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion scripts/lint/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const prettyPrint = (
color.dim(String(startLine + 1)),
color.dim(lines[startLine]),
),
sprintf("%*s╰─► %s", pad, "", error),
sprintf("%*s╰─► %s", pad, "", error.split('\n').map((str, i) => i === 0 ? str : " ".repeat(pad + 4) + str).join('\n')),
uncenter marked this conversation as resolved.
Show resolved Hide resolved
undefined,
].join("\n"),
);
Expand Down
173 changes: 151 additions & 22 deletions scripts/lint/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { relative } from "std/path/mod.ts";
import { REPO_ROOT } from "@/deps.ts";
import { log } from "@/lint/logger.ts";
import { formatListOfItems, getUserstylesData } from "@/utils.ts";
import stringify from "npm:json-oneline-stringify";
uncenter marked this conversation as resolved.
Show resolved Hide resolved

export const verifyMetadata = async (
entry: WalkEntry,
Expand All @@ -32,19 +33,22 @@ export const verifyMetadata = async (
log(e.message, { file, startLine, content });
});

for (const [key, value] of Object.entries(assert)) {
const defacto = metadata[key];
if (defacto !== value) {
for (const [key, expected] of Object.entries(assert)) {
const current = metadata[key];

if (current !== expected) {
const line = content
.split("\n")
.findIndex((line) => line.includes(key)) + 1;

const message = sprintf(
'Metadata `%s` should be "%s" but is "%s"',
color.bold(key),
color.green(value),
color.red(String(defacto)),
);
const message = current === undefined
? sprintf("Metadata `%s` should not be undefined", color.bold(key))
: sprintf(
'Metadata `%s` should be "%s" but is "%s"',
color.bold(key),
color.green(expected),
color.red(String(current)),
);

log(message, {
file,
Expand All @@ -54,6 +58,73 @@ export const verifyMetadata = async (
}
}

for (const [variable, expected] of Object.entries(vars)) {
let current = metadata.vars[variable];

if (current === undefined) {
// This variable is undefined so there isn't a line for it, so we just put it at the bottom of the variables section.
const line = content
.split("\n")
.findLastIndex((line: string) =>
line.includes('==/UserStyle== */')
) + 1;

log(
sprintf(
"Metadata variable `%s` should not be undefined",
color.bold(variable),
),
{
file,
startLine: line !== 0 ? line : undefined,
content,
},
"warning",
);
} else {
for (const [key, value] of Object.entries(expected)) {
// If this property is an array (such as `options`) and there is a difference in the stringified representation of the values...
if ((Array.isArray(value) && Array.isArray(current[key])) ? (new Set([
...value.map(stringify),
...current[key].map(stringify),
]).size !== value.length) :
(current[key] !== value)) {
const line = content
.split("\n")
.findIndex((line) =>
line.includes(`@var ${expected.type} ${variable}`)
) + 1;

const message =
(Array.isArray(value) && Array.isArray(current[key]))
? sprintf(
'Found mismatch in array elements of property "%s" of metadata variable `%s`:\n' +
value.map((el, i) =>
stringify(el) === stringify(current[key][i])
? ""
: color.green(`+ Expected: ${stringify(el)}\n`) +
color.red(`- Received: ${stringify(current[key][i])}`)
).join(''),
color.bold(key),
color.bold(variable),
)
: sprintf(
'Property "%s" of metadata variable `%s` should be %s but is %s',
color.bold(key),
color.bold(variable),
color.green(stringify(value)),
color.red(stringify(current[key])),
);
log(message, {
file,
startLine: line !== 0 ? line : undefined,
content,
}, "warning");
}
}
}
}

// Parse the UserCSS variables to LESS global variables, e.g.
// `@var select lightFlavor "Light Flavor" ["latte:Latte*", "frappe:Frappé", "macchiato:Macchiato", "mocha:Mocha"]`
// gets parsed as
Expand Down Expand Up @@ -85,22 +156,80 @@ const assertions = async (userstyle: string) => {
}

return {
name: `${
Array.isArray(userstyles[userstyle].name)
? (userstyles[userstyle].name as string[]).join("/")
: userstyles[userstyle].name
} Catppuccin`,
name: `${Array.isArray(userstyles[userstyle].name)
? (userstyles[userstyle].name as string[]).join("/")
: userstyles[userstyle].name
} Catppuccin`,
namespace: `github.com/catppuccin/userstyles/styles/${userstyle}`,
author: "Catppuccin",
description: `Soothing pastel theme for ${
Array.isArray(userstyles[userstyle].name)
? formatListOfItems(userstyles[userstyle].name as string[])
: userstyles[userstyle].name
}`,
license: "MIT",
preprocessor: "less",
homepageURL: `${prefix}/tree/main/styles/${userstyle}`,
description: `Soothing pastel theme for ${Array.isArray(userstyles[userstyle].name)
? formatListOfItems(userstyles[userstyle].name as string[])
: userstyles[userstyle].name
}`,
author: "Catppuccin",
updateURL: `${prefix}/raw/main/styles/${userstyle}/catppuccin.user.css`,
supportURL: `${prefix}/issues?q=is%3Aopen+is%3Aissue+label%3A${userstyle}`,
license: "MIT",
preprocessor: "less",
};
};

const vars = {
lightFlavor: {
type: "select",
label: "Light Flavor",
name: "lightFlavor",
value: null,
default: "latte",
options: [
{ name: "latte", label: "Latte", value: "latte" },
{ name: "frappe", label: "Frappé", value: "frappe" },
{ name: "macchiato", label: "Macchiato", value: "macchiato" },
{ name: "mocha", label: "Mocha", value: "mocha" },
],
},
darkFlavor: {
type: "select",
label: "Dark Flavor",
name: "darkFlavor",
value: null,
default: "mocha",
options: [
{ name: "latte", label: "Latte", value: "latte" },
{ name: "frappe", label: "Frappé", value: "frappe" },
{ name: "macchiato", label: "Macchiato", value: "macchiato" },
{ name: "mocha", label: "Mocha", value: "mocha" },
],
},
accentColor: {
type: "select",
label: "Accent",
name: "accentColor",
value: null,
default: "sapphire",
options: [
{ name: "rosewater", label: "Rosewater", value: "rosewater" },
{ name: "flamingo", label: "Flamingo", value: "flamingo" },
{ name: "pink", label: "Pink", value: "pink" },
{ name: "mauve", label: "Mauve", value: "mauve" },
{ name: "red", label: "Red", value: "red" },
{ name: "maroon", label: "Maroon", value: "maroon" },
{ name: "peach", label: "Peach", value: "peach" },
{ name: "yellow", label: "Yellow", value: "yellow" },
{ name: "green", label: "Green", value: "green" },
{ name: "teal", label: "Teal", value: "teal" },
{ name: "blue", label: "Blue", value: "blue" },
{ name: "sapphire", label: "Sapphire", value: "sapphire" },
{ name: "sky", label: "Sky", value: "sky" },
{ name: "lavender", label: "Lavender", value: "lavender" },
{ name: "subtext0", label: "Gray", value: "subtext0" },
],
},
} as Record<string, {
type: string;
label: string;
name: string;
value: null | string;
default: string;
options: { name: string; label: string; value: string }[];
}>;
Loading