Skip to content

Commit

Permalink
Add --configPointer argument to allow embedding the configuration obj…
Browse files Browse the repository at this point in the history
…ect in files like package.json and pyproject.toml (fixes #113, fixes #458).
  • Loading branch information
DavidAnson committed Apr 30, 2024
1 parent 899e6a8 commit 3acb04e
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 20 deletions.
33 changes: 19 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,21 @@ markdownlint --help
MarkdownLint Command Line Interface

Options:
-V, --version output the version number
-c, --config [configFile] configuration file (JSON, JSONC, JS, YAML, or TOML)
-d, --dot include files/folders with a dot (for example `.github`)
-f, --fix fix basic errors (does not work with STDIN)
-i, --ignore [file|directory|glob] file(s) to ignore/exclude (default: [])
-j, --json write issues in json format
-o, --output [outputFile] write issues to file (no console)
-p, --ignore-path [file] path to file with ignore pattern(s)
-q, --quiet do not write issues to STDOUT
-r, --rules [file|directory|glob|package] include custom rule files (default: [])
-s, --stdin read from STDIN (does not work with files)
--enable [rules...] Enable certain rules, e.g. --enable MD013 MD041 --
--disable [rules...] Disable certain rules, e.g. --disable MD013 MD041 --
-h, --help display help for command
-V, --version output the version number
-c, --config <configFile> configuration file (JSON, JSONC, JS, YAML, or TOML)
--configPointer <pointer> JSON Pointer to object within configuration file (default: "")
-d, --dot include files/folders with a dot (for example `.github`)
-f, --fix fix basic errors (does not work with STDIN)
-i, --ignore <file|directory|glob> file(s) to ignore/exclude (default: [])
-j, --json write issues in json format
-o, --output <outputFile> write issues to file (no console)
-p, --ignore-path <file> path to file with ignore pattern(s)
-q, --quiet do not write issues to STDOUT
-r, --rules <file|directory|glob|package> include custom rule files (default: [])
-s, --stdin read from STDIN (does not work with files)
--enable <rules...> Enable certain rules, e.g. --enable MD013 MD041 --
--disable <rules...> Disable certain rules, e.g. --disable MD013 MD041 --
-h, --help display help for command
```

Or run using [Docker](https://www.docker.com) and [GitHub Packages](https://github.com/features/packages):
Expand Down Expand Up @@ -113,6 +114,9 @@ JS configuration files contain JavaScript code, must have the `.js` or `.cjs` fi
If your workspace _(project)_ is [ESM-only] _(`"type": "module"` set in the root `package.json` file)_, then the configuration file **should end with `.cjs` file extension**.
A JS configuration file may internally `require` one or more npm packages as a way of reusing configuration across projects.

The `--configPointer` argument allows the use of [JSON Pointer][json-pointer] syntax to identify a sub-object within the configuration object (per above).
This argument can be used with any configuration file type and makes it possible to nest a configuration object within another file like `package.json` or `pyproject.toml` (e.g., via `/key` or `/key/subkey`).

`--enable` and `--disable` override configuration files; if a configuration file disables `MD123` and you pass `--enable MD123`, it will be enabled.
If a rule is passed to both `--enable` and `--disable`, it will be disabled.

Expand Down Expand Up @@ -156,6 +160,7 @@ MIT © Igor Shubovych
[actions-badge]: https://github.com/igorshubovych/markdownlint-cli/workflows/CI/badge.svg?branch=master
[actions-url]: https://github.com/igorshubovych/markdownlint-cli/actions?query=workflow%3ACI
[commander-variadic]: https://github.com/tj/commander.js#variadic-option
[json-pointer]: https://datatracker.ietf.org/doc/html/rfc6901
[markdownlint]: https://github.com/DavidAnson/markdownlint
[markdownlint-cli2]: https://github.com/DavidAnson/markdownlint-cli2
[markdownlint-jsonc]: https://github.com/DavidAnson/markdownlint/blob/main/schema/.markdownlint.jsonc
Expand Down
14 changes: 8 additions & 6 deletions markdownlint.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const glob = require('glob');
const markdownlint = require('markdownlint');
const rc = require('run-con');
const minimatch = require('minimatch');
const jsonpointer = require('jsonpointer');
const pkg = require('./package.json');

const options = program.opts();
Expand Down Expand Up @@ -55,8 +56,6 @@ const fsOptions = {encoding: 'utf8'};
const processCwd = process.cwd();

function readConfiguration(userConfigFile) {
const jsConfigFile = /\.c?js$/i.test(userConfigFile);

// Load from well-known config files
let config = rc('markdownlint', {});
for (const projectConfigFile of projectConfigFiles) {
Expand All @@ -71,9 +70,10 @@ function readConfiguration(userConfigFile) {
}

// Normally parsing this file is not needed, because it is already parsed by rc package.
// However I have to do it to overwrite configuration from .markdownlint.{json,yaml,yml}.
// However I have to do it to overwrite configuration from .markdownlint.{jsonc,json,yaml,yml}.
if (userConfigFile) {
try {
const jsConfigFile = /\.c?js$/i.test(userConfigFile);
const userConfig = jsConfigFile ? require(path.resolve(processCwd, userConfigFile)) : markdownlint.readConfigSync(userConfigFile, configParsers);
config = require('deep-extend')(config, userConfig);
} catch (error) {
Expand Down Expand Up @@ -199,15 +199,16 @@ program
.version(pkg.version)
.description(pkg.description)
.usage('[options] <files|directories|globs>')
.option('-c, --config <configFile>', 'configuration file (JSON, JSONC, JS, or YAML)')
.option('-c, --config <configFile>', 'configuration file (JSON, JSONC, JS, YAML, or TOML)')
.option('--configPointer <pointer>', 'JSON Pointer to object within configuration file', '')
.option('-d, --dot', 'include files/folders with a dot (for example `.github`)')
.option('-f, --fix', 'fix basic errors (does not work with STDIN)')
.option('-i, --ignore <file|directory|glob>', 'file(s) to ignore/exclude', concatArray, [])
.option('-j, --json', 'write issues in json format')
.option('-o, --output <outputFile>', 'write issues to file (no console)')
.option('-p, --ignore-path <file>', 'path to file with ignore pattern(s)')
.option('-q, --quiet', 'do not write issues to STDOUT')
.option('-r, --rules <file|directory|glob|package>', 'include custom rule files', concatArray, [])
.option('-r, --rules <file|directory|glob|package>', 'include custom rule files', concatArray, [])
.option('-s, --stdin', 'read from STDIN (does not work with files)')
.option('--enable <rules...>', 'Enable certain rules, e.g. --enable MD013 MD041 --')
.option('--disable <rules...>', 'Disable certain rules, e.g. --disable MD013 MD041 --');
Expand Down Expand Up @@ -276,7 +277,8 @@ const diff = files.filter(file => !ignores.some(ignore => ignore.absolute === fi

function lintAndPrint(stdin, files) {
files ||= [];
const config = readConfiguration(options.config);
const configuration = readConfiguration(options.config);
const config = jsonpointer.get(configuration, options.configPointer) || {};

for (const rule of options.enable || []) {
// Leave default values in place if rule is an object
Expand Down
14 changes: 14 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"ignore": "~5.3.1",
"js-yaml": "^4.1.0",
"jsonc-parser": "~3.2.1",
"jsonpointer": "5.0.1",
"markdownlint": "~0.34.0",
"minimatch": "~9.0.4",
"run-con": "~1.3.2",
Expand Down
8 changes: 8 additions & 0 deletions test/nested-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "unused",

"key": {
"blanks-around-headings": false,
"commands-show-output": false
}
}
7 changes: 7 additions & 0 deletions test/nested-config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
name = "unused"

[key]
other-name = "unused"

[key.subkey]
commands-show-output = false
44 changes: 44 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,50 @@ test('.markdownlint.yaml in cwd is used instead of .markdownlint.yml', getCwdCon

test('.markdownlint.json with JavaScript-style comments is handled', getCwdConfigFileTest('json-c'));

test('invalid JSON Pointer', async t => {
try {
await execa('../markdownlint.js', ['--config', 'nested-config.json', '--configPointer', 'INVALID', '**/*.md'], {stripFinalNewline: false});
t.fail();
} catch (error) {
t.is(error.stdout, '');
t.regex(error.stderr, /Invalid JSON pointer\./);
t.is(error.exitCode, 4);
}
});

test('empty JSON Pointer', async t => {
try {
await execa('../markdownlint.js', ['--config', 'nested-config.json', '--configPointer', '/EMPTY', 'incorrect.md'], {stripFinalNewline: false});
t.fail();
} catch (error) {
t.is(error.stdout, '');
t.is(error.stderr.match(errorPattern).length, 7);
t.is(error.exitCode, 1);
}
});

test('valid JSON Pointer with JSON configuration', async t => {
try {
await execa('../markdownlint.js', ['--config', 'nested-config.json', '--configPointer', '/key', 'incorrect.md'], {stripFinalNewline: false});
t.fail();
} catch (error) {
t.is(error.stdout, '');
t.is(error.stderr.match(errorPattern).length, 1);
t.is(error.exitCode, 1);
}
});

test('valid JSON Pointer with TOML configuration', async t => {
try {
await execa('../markdownlint.js', ['--config', 'nested-config.toml', '--configPointer', '/key/subkey', 'incorrect.md'], {stripFinalNewline: false});
t.fail();
} catch (error) {
t.is(error.stdout, '');
t.is(error.stderr.match(errorPattern).length, 3);
t.is(error.exitCode, 1);
}
});

test('Custom rule from single file loaded', async t => {
try {
const input = '# Input\n';
Expand Down

0 comments on commit 3acb04e

Please sign in to comment.