Skip to content

Commit a1f9a15

Browse files
committedMay 3, 2020
Add support for JS configuration via --config (fixes #85).
1 parent 794167a commit a1f9a15

File tree

5 files changed

+58
-16
lines changed

5 files changed

+58
-16
lines changed
 

‎README.md

+9-5
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ markdownlint --help
2424
-f, --fix fix basic errors (does not work with STDIN)
2525
-s, --stdin read from STDIN (does not work with files)
2626
-o, --output [outputFile] write issues to file (no console)
27-
-c, --config [configFile] configuration file (JSON, JSONC, or YAML)
27+
-c, --config [configFile] configuration file (JSON, JSONC, JS, or YAML)
2828
-i, --ignore [file|directory|glob] file(s) to ignore/exclude
2929
-p, --ignore-path [file] path to file with ignore pattern(s)
3030
-r, --rules [file|directory|glob|package] custom rule files
@@ -83,10 +83,14 @@ The example of configuration file:
8383

8484
See [test configuration file][test-config] or [style folder][style-folder] for more examples.
8585

86-
CLI argument `--config` is not mandatory.
87-
If it is not provided, `markdownlint-cli` looks for file `.markdownlint.json`/`.markdownlint.yaml`/`.markdownlint.yml` in current folder, or for file `.markdownlintrc` in current or all upper folders.
88-
The algorithm is described in details on [rc package page][rc-standards].
89-
If `--config` argument is provided, the file must be valid JSON, JSONC, or YAML.
86+
The CLI argument `--config` is not required.
87+
If it is not provided, `markdownlint-cli` looks for the file `.markdownlint.json`/`.markdownlint.yaml`/`.markdownlint.yml` in current folder, or for the file `.markdownlintrc` in the current or all parent folders.
88+
The algorithm is described in detail on the [`rc` package page][rc-standards].
89+
If the `--config` argument is provided, the file must be valid JSON, JSONC, JS, or YAML.
90+
JS configuration files contain JavaScript code, must have the `.js` extension, and must export (via `module.exports = ...`) a configuration object of the form shown above.
91+
A JS configuration file may internally `require` one or more npm packages as a way of reusing configuration across projects.
92+
93+
> JS configuration files must be provided via the `--config` argument; they are not automatically loaded because running untrusted code is a security concern.
9094
9195
## Exit codes
9296

‎markdownlint.js

+24-10
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const markdownlint = require('markdownlint');
1212
const rc = require('rc');
1313
const glob = require('glob');
1414
const minimatch = require('minimatch');
15+
const minimist = require('minimist');
1516
const pkg = require('./package');
1617

1718
function jsoncParse(text) {
@@ -29,10 +30,19 @@ const projectConfigFiles = [
2930
];
3031
const configFileParsers = [jsoncParse, jsYamlSafeLoad];
3132
const fsOptions = {encoding: 'utf8'};
33+
const processCwd = process.cwd();
3234

3335
function readConfiguration(args) {
34-
let config = rc('markdownlint', {});
3536
const userConfigFile = args.config;
37+
const jsConfigFile = /\.js$/i.test(userConfigFile);
38+
const rcArgv = minimist(process.argv.slice(2));
39+
if (jsConfigFile) {
40+
// Prevent rc package from parsing .js config file as INI
41+
delete rcArgv.config;
42+
}
43+
44+
// Load from well-known config files
45+
let config = rc('markdownlint', {}, rcArgv);
3646
for (const projectConfigFile of projectConfigFiles) {
3747
try {
3848
fs.accessSync(projectConfigFile, fs.R_OK);
@@ -43,17 +53,21 @@ function readConfiguration(args) {
4353
// Ignore failure
4454
}
4555
}
56+
4657
// Normally parsing this file is not needed,
4758
// because it is already parsed by rc package.
4859
// However I have to do it to overwrite configuration
4960
// from .markdownlint.{json,yaml,yml}.
50-
5161
if (userConfigFile) {
5262
try {
53-
const userConfig = markdownlint.readConfigSync(userConfigFile, configFileParsers);
63+
const userConfig = jsConfigFile ?
64+
// Evaluate .js configuration file as code
65+
require(path.resolve(processCwd, userConfigFile)) :
66+
// Load JSON/YAML configuration as data
67+
markdownlint.readConfigSync(userConfigFile, configFileParsers);
5468
config = require('deep-extend')(config, userConfig);
5569
} catch (error) {
56-
console.warn('Cannot read or parse config file ' + args.config + ': ' + error.message);
70+
console.warn('Cannot read or parse config file ' + userConfigFile + ': ' + error.message);
Has conversations. Original line has conversations.
5771
}
5872
}
5973

@@ -78,7 +92,7 @@ function prepareFileList(files, fileExtensions, previousResults) {
7892
// Directory (file falls through to below)
7993
if (previousResults) {
8094
const matcher = new minimatch.Minimatch(
81-
path.resolve(process.cwd(), path.join(file, '**', extensionGlobPart)), globOptions);
95+
path.resolve(processCwd, path.join(file, '**', extensionGlobPart)), globOptions);
8296
return previousResults.filter(function (fileInfo) {
8397
return matcher.match(fileInfo.absolute);
8498
}).map(function (fileInfo) {
@@ -91,7 +105,7 @@ function prepareFileList(files, fileExtensions, previousResults) {
91105
} catch (_) {
92106
// Not a directory, not a file, may be a glob
93107
if (previousResults) {
94-
const matcher = new minimatch.Minimatch(path.resolve(process.cwd(), file), globOptions);
108+
const matcher = new minimatch.Minimatch(path.resolve(processCwd, file), globOptions);
95109
return previousResults.filter(function (fileInfo) {
96110
return matcher.match(fileInfo.absolute);
97111
}).map(function (fileInfo) {
@@ -108,7 +122,7 @@ function prepareFileList(files, fileExtensions, previousResults) {
108122
return flatten(files).map(function (file) {
109123
return {
110124
original: file,
111-
relative: path.relative(process.cwd(), file),
125+
relative: path.relative(processCwd, file),
112126
absolute: path.resolve(file)
113127
};
114128
});
@@ -171,7 +185,7 @@ program
171185
.option('-f, --fix', 'fix basic errors (does not work with STDIN)')
172186
.option('-s, --stdin', 'read from STDIN (does not work with files)')
173187
.option('-o, --output [outputFile]', 'write issues to file (no console)')
174-
.option('-c, --config [configFile]', 'configuration file (JSON, JSONC, or YAML)')
188+
.option('-c, --config [configFile]', 'configuration file (JSON, JSONC, JS, or YAML)')
175189
.option('-i, --ignore [file|directory|glob]', 'file(s) to ignore/exclude', concatArray, [])
176190
.option('-p, --ignore-path [file]', 'path to file with ignore pattern(s)')
177191
.option('-r, --rules [file|directory|glob|package]', 'custom rule files', concatArray, []);
@@ -183,7 +197,7 @@ function tryResolvePath(filepath) {
183197
if (path.basename(filepath) === filepath && path.extname(filepath) === '') {
184198
// Looks like a package name, resolve it relative to cwd
185199
// Get list of directories, where requested module can be.
186-
let paths = Module._nodeModulePaths(process.cwd());
200+
let paths = Module._nodeModulePaths(processCwd);
187201
paths = paths.concat(Module.globalPaths);
188202
if (require.resolve.paths) {
189203
// Node >= 8.9.0
@@ -194,7 +208,7 @@ function tryResolvePath(filepath) {
194208
}
195209

196210
// Maybe it is a path to package installed locally
197-
return require.resolve(path.join(process.cwd(), filepath));
211+
return require.resolve(path.join(processCwd, filepath));
198212
} catch (_) {
199213
return filepath;
200214
}

‎package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"markdownlint": "~0.20.2",
5050
"markdownlint-rule-helpers": "~0.9.0",
5151
"minimatch": "~3.0.4",
52+
"minimist": "~1.2.5",
5253
"rc": "~1.2.7"
5354
},
5455
"devDependencies": {
@@ -72,7 +73,8 @@
7273
"ava": {
7374
"files": [
7475
"test/**/*.js",
75-
"!test/custom-rules/**/*.js"
76+
"!test/custom-rules/**/*.js",
77+
"!test/md043-config.js"
7678
],
7779
"failFast": true
7880
}

‎test/md043-config.js

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
'use strict';
2+
3+
// Export config object directly (as below)
4+
// -OR-
5+
// via require('some-npm-module-that-exports-config')
6+
module.exports = {
7+
MD043: {
8+
headers: [
9+
'# First',
10+
'## Second',
11+
'### Third'
12+
]
13+
}
14+
};

‎test/test.js

+8
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,14 @@ test('configuration file can be YAML', async t => {
365365
t.is(result.stderr, '');
366366
});
367367

368+
test('configuration file can be JavaScript', async t => {
369+
const result = await execa('../markdownlint.js',
370+
['--config', 'md043-config.js', 'md043-config.md'],
371+
{stripFinalNewline: false});
372+
t.is(result.stdout, '');
373+
t.is(result.stderr, '');
374+
});
375+
368376
function getCwdConfigFileTest(extension) {
369377
return async t => {
370378
try {

0 commit comments

Comments
 (0)
Please sign in to comment.