Skip to content

Commit

Permalink
Add support for input from stdin via "-" glob (fixes #414).
Browse files Browse the repository at this point in the history
  • Loading branch information
DavidAnson committed Nov 7, 2024
1 parent 212db1f commit d3265d7
Show file tree
Hide file tree
Showing 16 changed files with 237 additions and 23 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ Glob expressions (from the globby library):
- {} allows for a comma-separated list of "or" expressions
- ! or # at the beginning of a pattern negate the match
- : at the beginning identifies a literal file path
- - as a glob represents standard input (stdin)
Dot-only glob:
- The command "markdownlint-cli2 ." would lint every file in the current directory tree which is probably not intended
Expand Down
33 changes: 24 additions & 9 deletions markdownlint-cli2.js
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ Glob expressions (from the globby library):
- {} allows for a comma-separated list of "or" expressions
- ! or # at the beginning of a pattern negate the match
- : at the beginning identifies a literal file path
- - as a glob represents standard input (stdin)
Dot-only glob:
- The command "markdownlint-cli2 ." would lint every file in the current directory tree which is probably not intended
Expand Down Expand Up @@ -807,7 +808,8 @@ const lintFiles = (fs, dirInfos, fileContents) => {
task = task.then((results) => {
options.files = [];
const subTasks = [];
const errorFiles = Object.keys(results);
const errorFiles = Object.keys(results).
filter((result) => filteredFiles.includes(result));
for (const fileName of errorFiles) {
const errorInfos = results[fileName].
filter((errorInfo) => errorInfo.fixInfo);
Expand Down Expand Up @@ -909,11 +911,12 @@ const main = async (params) => {
optionsDefault,
optionsOverride,
fileContents,
nonFileContents,
noRequire
noRequire,
allowStdin
} = params;
let {
noGlobs
noGlobs,
nonFileContents
} = params;
const logMessage = params.logMessage || noop;
const logError = params.logError || noop;
Expand All @@ -926,13 +929,16 @@ const main = async (params) => {
let fixDefault = false;
// eslint-disable-next-line unicorn/no-useless-undefined
let configPath = undefined;
let useStdin = false;
let sawDashDash = false;
let shouldShowHelp = false;
const argvFiltered = (argv || []).filter((arg) => {
if (sawDashDash) {
return true;
} else if (configPath === null) {
configPath = arg;
} else if ((arg === "-") && allowStdin) {
useStdin = true;
// eslint-disable-next-line unicorn/prefer-switch
} else if (arg === "--") {
sawDashDash = true;
Expand Down Expand Up @@ -983,22 +989,30 @@ const main = async (params) => {
}
}
if (
((globPatterns.length === 0) && !nonFileContents) ||
((globPatterns.length === 0) && !useStdin && !nonFileContents) ||
(configPath === null)
) {
return showHelp(logMessage, false);
}
// Add stdin as a non-file input if necessary
if (useStdin) {
const key = pathPosix.join(baseDir, "stdin");
const { text } = require("node:stream/consumers");
nonFileContents = {
...nonFileContents,
[key]: await text(process.stdin)
};
}
// Include any file overrides or non-file content
const { baseMarkdownlintOptions, dirToDirInfo } = baseOptions;
const resolvedFileContents = {};
for (const file in fileContents) {
const resolvedFile = posixPath(pathDefault.resolve(baseDirSystem, file));
resolvedFileContents[resolvedFile] =
fileContents[file];
resolvedFileContents[resolvedFile] = fileContents[file];
}
for (const nonFile in nonFileContents) {
resolvedFileContents[nonFile] = nonFileContents[nonFile];
}
const { baseMarkdownlintOptions, dirToDirInfo } = baseOptions;
appendToArray(
dirToDirInfo[baseDir].files,
Object.keys(nonFileContents || {})
Expand Down Expand Up @@ -1079,7 +1093,8 @@ const run = (overrides, args) => {
const defaultParams = {
"argv": argsAndArgv,
"logMessage": console.log,
"logError": console.error
"logError": console.error,
"allowStdin": true
};
const params = {
...defaultParams,
Expand Down
156 changes: 153 additions & 3 deletions test/markdownlint-cli2-test-exec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@

const fs = require("node:fs/promises");
const path = require("node:path");
const test = require("ava").default;
const testCases = require("./markdownlint-cli2-test-cases");

const absolute = (rootDir, file) => path.join(rootDir, file);
const repositoryPath = (name) => path.join(__dirname, "..", name);

const invoke = (directory, args, noRequire, env, script) => async () => {
await fs.access(directory);
const { "default": spawn } = await import("nano-spawn");
return spawn(
"node",
[
path.join(__dirname, "..", script || "markdownlint-cli2.js"),
repositoryPath(script || "markdownlint-cli2.js"),
...args
],
{
Expand All @@ -27,8 +31,6 @@ const invoke = (directory, args, noRequire, env, script) => async () => {
catch((error) => error);
};

const absolute = (rootDir, file) => path.join(rootDir, file);

testCases({
"host": "exec",
invoke,
Expand All @@ -39,3 +41,151 @@ testCases({
"includeRequire": true,
"includeAbsolute": true
});

const invokeStdin = async (args, stdin, cwd) => {
const { "default": spawn } = await import("nano-spawn");
return spawn(
"node",
[
repositoryPath("markdownlint-cli2.js"),
...args
],
{
cwd,
"stdin": { "string": stdin }
}
);
};

const validInput = "# Heading\n\nText\n";
const invalidInput = "# Heading\n\nText";

test("- parameter with empty input from stdin", (t) => {
t.plan(1);
return invokeStdin(
[ "-" ],
""
).
then(() => t.pass()).
catch(() => t.fail());
});

test("- parameter with valid input from stdin", (t) => {
t.plan(1);
return invokeStdin(
[ "-" ],
validInput
).
then(() => t.pass()).
catch(() => t.fail());
});

test("- parameter with invalid input from stdin", (t) => {
t.plan(2);
return invokeStdin(
[ "-" ],
invalidInput
).
then(() => t.fail()).
catch((error) => {
t.is(error.exitCode, 1);
t.is("", error.stderr.replace(/^stdin:1:3 MD019\/.*$[\n\r]+^stdin:3:4 MD047\/.*$/mu, ""));
});
});

test("- parameter with invalid input from stdin and --fix", (t) => {
t.plan(2);
return invokeStdin(
[ "-", "--fix" ],
invalidInput
).
then(() => t.fail()).
catch((error) => {
t.is(error.exitCode, 1);
t.is("", error.stderr.replace(/^stdin:1:3 MD019\/.*$[\n\r]+^stdin:3:4 MD047\/.*$/mu, ""));
});
});

test("- parameter multiple times with invalid input", (t) => {
t.plan(2);
return invokeStdin(
[ "-", "-" ],
invalidInput
).
then(() => t.fail()).
catch((error) => {
t.is(error.exitCode, 1);
t.is("", error.stderr.replace(/^stdin:1:3 MD019\/.*$[\n\r]+^stdin:3:4 MD047\/.*$/mu, ""));
});
});

test("- parameter with valid input combined with valid globs", (t) => {
t.plan(1);
return invokeStdin(
[ repositoryPath("CONTRIBUTING.md"), "-", repositoryPath("README.md") ],
validInput
).
then(() => t.pass()).
catch(() => t.fail());
});

test("- parameter with invalid input combined with valid globs", (t) => {
t.plan(2);
return invokeStdin(
[ repositoryPath("CONTRIBUTING.md"), repositoryPath("README.md"), "-" ],
invalidInput
).
then(() => t.fail()).
catch((error) => {
t.is(error.exitCode, 1);
t.is("", error.stderr.replace(/^stdin:1:3 MD019\/.*$[\n\r]+^stdin:3:4 MD047\/.*$/mu, ""));
});
});

test("- parameter with invalid input combined with invalid glob", (t) => {
t.plan(2);
return invokeStdin(
[ "-", repositoryPath("LICENSE") ],
invalidInput
).
then(() => t.fail()).
catch((error) => {
t.is(error.exitCode, 1);
t.is("", error.stderr.replace(/^LICENSE:1 MD041\/.*$[\n\r]+^stdin:1:3 MD019\/.*$[\n\r]+^stdin:3:4 MD047\/.*$/mu, ""));
});
});

test("- parameter uses base directory configuration", (t) => {
t.plan(2);
return invokeStdin(
[ "-" ],
invalidInput,
path.join(__dirname, "stdin")
).
then(() => t.fail()).
catch((error) => {
t.is(error.exitCode, 1);
t.is("", error.stderr.replace(/^stdin:1:3 MD019\/.*$/mu, ""));
});
});

test("- parameter not treated as stdin in configuration file globs", (t) => {
t.plan(1);
return invokeStdin(
[],
invalidInput,
path.join(__dirname, "stdin-globs")
).
then(() => t.pass()).
catch(() => t.fail());
});

test("- parameter ignored after --", (t) => {
t.plan(1);
return invokeStdin(
[ "--", "-" ],
invalidInput
).
then(() => t.pass()).
catch(() => t.fail());
});
17 changes: 15 additions & 2 deletions test/markdownlint-cli2-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ test("README files", (t) => {
});

test("validateMarkdownlintConfigSchema", async (t) => {
t.plan(26);
t.plan(27);

// Validate schema
// @ts-ignore
Expand Down Expand Up @@ -111,7 +111,7 @@ test("validateMarkdownlintConfigSchema", async (t) => {
});

test("validateMarkdownlintCli2ConfigSchema", async (t) => {
t.plan(90);
t.plan(91);

// Validate schema
// @ts-ignore
Expand Down Expand Up @@ -696,3 +696,16 @@ test("-- stops matching parameters per POSIX Utility Conventions 12.2 Guideline
files.push([ "/--", "# Title" ]);
await scenario([ "--", "--" ], 1);
});

test ("- not supported by main entry point", (t) => {
t.plan(2);
return markdownlintCli2({
"argv": [ "-" ],
"optionsOverride": {
"outputFormatters": [ [ outputFormatterLengthIs(t, 0) ] ]
}
}).
then((exitCode) => {
t.is(exitCode, 0);
});
});
5 changes: 5 additions & 0 deletions test/snapshots/markdownlint-cli2-test-exec.js.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Generated by [AVA](https://avajs.dev).
- {} allows for a comma-separated list of "or" expressions␊
- ! or # at the beginning of a pattern negate the match␊
- : at the beginning identifies a literal file path␊
- - as a glob represents standard input (stdin)␊
Dot-only glob:␊
- The command "markdownlint-cli2 ." would lint every file in the current directory tree which is probably not intended␊
Expand Down Expand Up @@ -83,6 +84,7 @@ Generated by [AVA](https://avajs.dev).
- {} allows for a comma-separated list of "or" expressions␊
- ! or # at the beginning of a pattern negate the match␊
- : at the beginning identifies a literal file path␊
- - as a glob represents standard input (stdin)␊
Dot-only glob:␊
- The command "markdownlint-cli2 ." would lint every file in the current directory tree which is probably not intended␊
Expand Down Expand Up @@ -139,6 +141,7 @@ Generated by [AVA](https://avajs.dev).
- {} allows for a comma-separated list of "or" expressions␊
- ! or # at the beginning of a pattern negate the match␊
- : at the beginning identifies a literal file path␊
- - as a glob represents standard input (stdin)␊
Dot-only glob:␊
- The command "markdownlint-cli2 ." would lint every file in the current directory tree which is probably not intended␊
Expand Down Expand Up @@ -195,6 +198,7 @@ Generated by [AVA](https://avajs.dev).
- {} allows for a comma-separated list of "or" expressions␊
- ! or # at the beginning of a pattern negate the match␊
- : at the beginning identifies a literal file path␊
- - as a glob represents standard input (stdin)␊
Dot-only glob:␊
- The command "markdownlint-cli2 ." would lint every file in the current directory tree which is probably not intended␊
Expand Down Expand Up @@ -681,6 +685,7 @@ Generated by [AVA](https://avajs.dev).
- {} allows for a comma-separated list of "or" expressions␊
- ! or # at the beginning of a pattern negate the match␊
- : at the beginning identifies a literal file path␊
- - as a glob represents standard input (stdin)␊
Dot-only glob:␊
- The command "markdownlint-cli2 ." would lint every file in the current directory tree which is probably not intended␊
Expand Down
Binary file modified test/snapshots/markdownlint-cli2-test-exec.js.snap
Binary file not shown.
Loading

0 comments on commit d3265d7

Please sign in to comment.