Skip to content

Commit

Permalink
added CLI test for the --lint option (closes #126)
Browse files Browse the repository at this point in the history
  • Loading branch information
Oaphi committed Jul 3, 2022
1 parent 7123f3e commit 3849bcf
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 28 deletions.
16 changes: 15 additions & 1 deletion dist/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,18 @@ const sharedOpts = {
description: "Generates @grant headers, can be repeated",
type: "array",
},
l: {
alias: "lint",
describe: "Lints the generated headers with ESLint",
default: false,
type: "boolean",
},
lf: {
alias: "lint-fix",
describe: "Fixes lint issues found with ESLint (implies --lint)",
default: false,
type: "boolean",
},
m: {
alias: "match",
description: "Generates valid @match headers (repeatable)",
Expand Down Expand Up @@ -119,15 +131,17 @@ const sharedOpts = {
description: "Prettifies outputted headers where possible",
},
};
names.forEach((name) => cli.command(name, `generates ${scase(name)} headers`, sharedOpts, ({ c, ch = [], d, du, e, h, g = [], i, m = [], n, nf, q = [], o, p, r = "start", s, pretty, u, w = [], x = [] }) => void generate(name, {
names.forEach((name) => cli.command(name, `generates ${scase(name)} headers`, sharedOpts, ({ c, ch = [], d, du, e, h, g = [], i, l, lf, m = [], n, nf, q = [], o, p, r = "start", s, pretty, u, w = [], x = [] }) => void generate(name, {
collapse: c,
custom: ch,
direct: !!d,
downloadURL: du,
eol: e,
excludes: x.map(String),
fix: !!lf,
homepage: h,
inject: i,
lint: !!l,
matches: m.map(String),
noframes: !!nf,
requires: q.map(String),
Expand Down
10 changes: 10 additions & 0 deletions dist/generate.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ export declare type GeneratorOptions<T extends GrantOptions> = CommonGeneratorOp
downloadURL?: string;
eol?: string;
excludes?: string[];
fix?: boolean;
grants?: T[];
homepage?: string;
inject?: string;
lint?: boolean;
matches?: string[];
output: string;
packagePath: string;
Expand All @@ -21,4 +23,12 @@ export declare type GeneratorOptions<T extends GrantOptions> = CommonGeneratorOp
updateURL?: string;
whitelist?: Array<"self" | "localhost" | "*"> | string[];
};
export declare type WriteHeadersOptions = {
cli: boolean;
direct: boolean;
eol?: string;
output: string;
};
export declare const managersSupportingHomepage: Set<UserScriptManagerName>;
export declare const writeHeaders: (content: string, options: WriteHeadersOptions) => Promise<string>;
export declare const generate: <T extends GrantOptions>(type: UserScriptManagerName, options: GeneratorOptions<T>, cli?: boolean) => Promise<string>;
66 changes: 41 additions & 25 deletions dist/generate.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,32 @@ import { appendFile } from "fs/promises";
import { generateGreasemonkeyHeaders } from "./generators/greasemonkey/index.js";
import { generateTampermonkeyHeaders } from "./generators/tampermonkey/index.js";
import { generateViolentmonkeyHeaders } from "./generators/violentmonkey/index.js";
import { lintHeaders } from "./linters/index.js";
import { replaceFileContent } from "./utils/filesystem.js";
import { getPackage } from "./utils/package.js";
import { getExistingHeadersOffset, validateConnectHeaders, validateExcludeHeaders, validateMatchHeaders, validateOptionalHeaders, validateRequiredHeaders } from "./utils/validators.js";
export const managersSupportingHomepage = new Set(["tampermonkey", "violentmonkey"]);
export const writeHeaders = async (content, options) => {
const { cli, direct, eol, output } = options;
if (!direct) {
if (!existsSync(output)) {
await appendFile(output, content, { encoding: "utf-8", flag: "w+" });
return content;
}
const [openOffset, closeOffset] = await getExistingHeadersOffset(output, eol);
if (openOffset > -1 && closeOffset > -1) {
await replaceFileContent(output, openOffset, closeOffset, content);
return content;
}
await replaceFileContent(output, 0, 0, `${content}${eol}`);
return content;
}
if (cli)
process.stdout.write(content);
return content;
};
export const generate = async (type, options, cli = false) => {
const { packagePath, output, spaces = 4, eol, collapse = true, direct = false, excludes = [], matches = [], whitelist = [], ...rest } = options;
const { packagePath, output, spaces = 4, eol, fix = false, collapse = true, direct = false, excludes = [], lint = false, matches = [], whitelist = [], ...rest } = options;
const managerTypeMap = {
greasemonkey: generateGreasemonkeyHeaders,
tampermonkey: generateTampermonkeyHeaders,
Expand All @@ -17,34 +38,34 @@ export const generate = async (type, options, cli = false) => {
try {
const parsedPackage = await getPackage(packagePath);
if (!parsedPackage) {
console.log(chulk.bgRed `missing or corrupted package`);
console.error(chulk.bgRed `missing or corrupted package`);
return "";
}
const { invalid: matchInvalid, status: matchStatus, valid: validMatches } = validateMatchHeaders(matches);
if (!matchStatus) {
console.log(chulk.bgRed `Invalid @match headers:\n` + matchInvalid.join("\n"));
console.error(chulk.bgRed `Invalid @match headers:\n` + matchInvalid.join("\n"));
}
const { invalid: excludeInvalid, status: excludeStatus, valid: validExcludes } = validateExcludeHeaders(excludes);
if (!excludeStatus) {
console.log(chulk.bgRed `Invalid @exclude headers:\n` + excludeInvalid.join("\n"));
console.error(chulk.bgRed `Invalid @exclude headers:\n` + excludeInvalid.join("\n"));
}
const { invalid: connectInvalid, status: connectStatus, valid: validConnects } = validateConnectHeaders(whitelist);
if (!connectStatus) {
console.log(chulk.bgRed `Invalid @connect headers:\n` + connectInvalid.join("\n"));
console.error(chulk.bgRed `Invalid @connect headers:\n` + connectInvalid.join("\n"));
}
const { status: reqStatus, isValidHomepage, isValidVersion, missing, } = validateRequiredHeaders(parsedPackage);
if (!isValidHomepage) {
console.log(chulk.bgRed `Invalid homepage URL:\n` + parsedPackage.homepage);
console.error(chulk.bgRed `Invalid homepage URL:\n` + parsedPackage.homepage);
}
const { isValidDownloadURL } = validateOptionalHeaders(options);
if (!isValidDownloadURL) {
console.log(chulk.bgRed `Invalid @downloadURL:\n` + options.downloadURL);
console.error(chulk.bgRed `Invalid @downloadURL:\n` + options.downloadURL);
}
if (!isValidVersion) {
console.log(chulk.bgRed `Invalid version:\n` + parsedPackage.version);
console.error(chulk.bgRed `Invalid version:\n` + parsedPackage.version);
}
if (missing.length) {
console.log(chulk.bgRed `Missing required fields:\n` + missing.join("\n"));
console.error(chulk.bgRed `Missing required fields:\n` + missing.join("\n"));
}
if (!reqStatus)
return "";
Expand All @@ -59,22 +80,17 @@ export const generate = async (type, options, cli = false) => {
packagePath,
output,
});
if (!direct) {
if (!existsSync(output)) {
await appendFile(output, content, { encoding: "utf-8", flag: "w+" });
return content;
}
const [openOffset, closeOffset] = await getExistingHeadersOffset(output, eol);
if (openOffset > -1 && closeOffset > -1) {
await replaceFileContent(output, openOffset, closeOffset, content);
return content;
}
await replaceFileContent(output, 0, 0, `${content}${eol}`);
return content;
if (lint || fix) {
const { error, headers } = await lintHeaders(content, {
fix,
spaces,
isHomepageAllowed: managersSupportingHomepage.has(type),
});
if (error)
console.error(error);
return writeHeaders(headers, { cli, direct, eol, output });
}
if (cli)
process.stdout.write(content);
return content;
return writeHeaders(content, { cli, direct, eol, output });
}
catch (error) {
const exceptionObject = error;
Expand All @@ -86,7 +102,7 @@ export const generate = async (type, options, cli = false) => {
};
const handler = errMap[code || "default"] || errMap.default;
const [postfix, message] = handler(exceptionObject);
console.log(chulk.bgRed `[${name}] ${postfix}` + `\n\n${message}`);
console.error(chulk.bgRed `[${name}] ${postfix}` + `\n\n${message}`);
return "";
}
};
2 changes: 1 addition & 1 deletion dist/generators/common/monkey.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const makeMonkeyHeader = (header) => {
};
export const finalizeMonkeyHeaders = (headers, spaces) => {
const [openTag, closeTag] = makeMonkeyTags();
const longest = getLongest(headers.map(([key]) => key)) + spaces;
const longest = getLongest(headers.map(([key]) => key)) + spaces - 1;
const sortedHeaders = headers.sort(([a], [b]) => a === "name" ? -1 : a < b ? -1 : 1);
const indentedHeaders = sortedHeaders.map(([key, val]) => [
key.padEnd(longest),
Expand Down
11 changes: 11 additions & 0 deletions dist/linters/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/// <reference path="../../src/linters/eslint-plugin-userscripts.d.ts" />
export declare type LintOptions = {
fix?: boolean;
isHomepageAllowed?: boolean;
spaces?: number;
};
export declare type LintResult = {
error: string;
headers: string;
};
export declare const lintHeaders: (metadataBlock: string, options?: LintOptions) => Promise<LintResult>;
24 changes: 24 additions & 0 deletions dist/linters/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ESLint } from "eslint";
import { configs } from "eslint-plugin-userscripts";
export const lintHeaders = async (metadataBlock, options = {}) => {
const { fix = false, isHomepageAllowed = false, spaces = 4 } = options;
const eslint = new ESLint({
baseConfig: {
plugins: ["userscripts"],
rules: {
...configs.recommended.rules,
"userscripts/align-attributes": ["error", spaces],
"userscripts/use-homepage-and-url": isHomepageAllowed ? "error" : "off",
},
},
useEslintrc: false,
fix,
});
const results = await eslint.lintText(metadataBlock);
const [{ output }] = results;
const formatter = await eslint.loadFormatter("stylish");
return {
error: await formatter.format(results),
headers: fix && output ? output : metadataBlock
};
};
7 changes: 6 additions & 1 deletion test/cli.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ describe("CLI Options", function () {

const runs = await Promise.all([
aexec(`${cliPfx} tampermonkey -p ${pkg} -d --du ${requires[1]} -u ${requires[1]} -n testing -h ${requires[1]} --nf --ch "name1 value1" --ch name2 -r menu -m all -c --pretty`),
aexec(`${cliPfx} violentmonkey -i "content" -p ${pkg} -o ${output} -d -g all ${xOpts} -r end`),
aexec(`${cliPfx} violentmonkey -i "content" -p ${pkg} -o ${output} -d -g all ${xOpts} -r end --lint`),
aexec(`${cliPfx} tampermonkey -i "page" -p ${pkg} -o ${output} -d ${gOpts} ${mOpts} ${rOpts} ${xOpts} -s ${spaces}`),
aexec(`${cliPfx} greasemonkey -p ${pkg} -d -r idle`),
]);
Expand Down Expand Up @@ -81,6 +81,11 @@ describe("CLI Options", function () {
expect(stdout).to.match(new RegExp(`^\\/\\/ @downloadURL\\s+${requires[1]}$`, "m"));
});

it("-l option should output linting errors to stderr", () => {
const { stderr } = cliRuns[1];
expect(stderr).to.include("userscripts/use-homepage-and-url");
})

it('-n option should override @namespace header', () => {
const { stdout } = cliRuns[0];
expect(stdout).to.match(new RegExp(`^\\/\\/ @namespace\\s+testing$`, "m"));
Expand Down

0 comments on commit 3849bcf

Please sign in to comment.