markdownlint-cli2 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -93,6 +93,7 @@ Configuration via:
93
93
  - .markdownlint.jsonc or .markdownlint.json
94
94
  - .markdownlint.yaml or .markdownlint.yml
95
95
  - .markdownlint.cjs or .markdownlint.mjs
96
+ - package.json
96
97
 
97
98
  Cross-platform compatibility:
98
99
  - UNIX and Windows shells expand globs according to different rules; quoting arguments is recommended
@@ -138,7 +139,7 @@ markdownlint-cli2 --fix "**/*.md" "#node_modules"
138
139
 
139
140
  In cases where it is not convenient to store a configuration file in the root
140
141
  of a project, the `--config` argument can be used to provide a path to any
141
- supported configuration file:
142
+ supported configuration file (except `package.json`):
142
143
 
143
144
  ```bash
144
145
  markdownlint-cli2 --config "config/.markdownlint-cli2.jsonc" "**/*.md" "#node_modules"
@@ -166,7 +167,7 @@ A container image [`davidanson/markdownlint-cli2`][docker-hub-markdownlint-cli2]
166
167
  can also be used (e.g., as part of a CI pipeline):
167
168
 
168
169
  ```bash
169
- docker run -v $PWD:/workdir davidanson/markdownlint-cli2:v0.8.0 "**/*.md" "#node_modules"
170
+ docker run -v $PWD:/workdir davidanson/markdownlint-cli2:v0.9.0 "**/*.md" "#node_modules"
170
171
  ```
171
172
 
172
173
  Notes:
@@ -183,7 +184,7 @@ Notes:
183
184
  - A custom working directory can be specified with Docker's `-w` flag:
184
185
 
185
186
  ```bash
186
- docker run -w /myfolder -v $PWD:/myfolder davidanson/markdownlint-cli2:v0.8.0 "**/*.md" "#node_modules"
187
+ docker run -w /myfolder -v $PWD:/myfolder davidanson/markdownlint-cli2:v0.9.0 "**/*.md" "#node_modules"
187
188
  ```
188
189
 
189
190
  > **Deprecated**
@@ -192,7 +193,7 @@ Notes:
192
193
  > instead, use Docker's `--entrypoint` flag:
193
194
  >
194
195
  > ```bash
195
- > docker run -v $PWD:/workdir --entrypoint="markdownlint-cli2-fix" davidanson/markdownlint-cli2:v0.8.0 "**/*.md" "#node_modules"
196
+ > docker run -v $PWD:/workdir --entrypoint="markdownlint-cli2-fix" davidanson/markdownlint-cli2:v0.9.0 "**/*.md" "#node_modules"
196
197
  > ```
197
198
 
198
199
  For convenience, the container image
@@ -233,9 +234,24 @@ of the rules within.
233
234
  - There are two kinds of configuration file (both detailed below):
234
235
  - Configuration files like `.markdownlint-cli2.*` allow complete control of
235
236
  `markdownlint-cli2` behavior and are also used by `vscode-markdownlint`.
237
+ - If multiple of these files are present in the same directory, only one is
238
+ used according to the following precedence:
239
+ 1. `.markdownlint-cli2.jsonc`
240
+ 2. `.markdownlint-cli2.yaml`
241
+ 3. `.markdownlint-cli2.cjs`
242
+ 4. `.markdownlint-cli2.mjs`
243
+ 5. `package.json` (only supported in the current directory)
236
244
  - Configuration files like `.markdownlint.*` allow control over only the
237
245
  `markdownlint` `config` object and tend to be supported more broadly (such
238
246
  as by `markdownlint-cli`).
247
+ - If multiple of these files are present in the same directory, only one is
248
+ used according to the following precedence:
249
+ 1. `.markdownlint.jsonc`
250
+ 2. `.markdownlint.json`
251
+ 3. `.markdownlint.yaml`
252
+ 4. `.markdownlint.yml`
253
+ 5. `.markdownlint.cjs`
254
+ 6. `.markdownlint.mjs`
239
255
  - The VS Code extension `vscode-markdownlint` includes a schema definition for
240
256
  the `JSON(C)` configuration files described below. This adds auto-complete and
241
257
  can make it easier to define proper structure.
@@ -322,8 +338,6 @@ of the rules within.
322
338
  - The format of this file is a [YAML][yaml] object with the structure described
323
339
  above for `.markdownlint-cli2.jsonc`.
324
340
  - Other details are the same as for `.markdownlint-cli2.jsonc` described above.
325
- - If a `.markdownlint-cli2.jsonc` file is present in the same directory, it
326
- takes precedence.
327
341
  - For example: [`.markdownlint-cli2.yaml`][markdownlint-cli2-yaml] with all
328
342
  properties set
329
343
 
@@ -336,12 +350,18 @@ of the rules within.
336
350
  `customRules`, `markdownItPlugins`, and `outputFormatters`, the corresponding
337
351
  `Object` or `Function` can be provided directly.
338
352
  - Other details are the same as for `.markdownlint-cli2.jsonc` described above.
339
- - If a `.markdownlint-cli2.jsonc` or `.markdownlint-cli2.yaml` file is present
340
- in the same directory, it takes precedence; `.markdownlint-cli2.cjs` takes
341
- precedence over `.markdownlint-cli2.mjs`.
342
353
  - For example: [`.markdownlint-cli2.cjs`][markdownlint-cli2-cjs] or
343
354
  [`.markdownlint-cli2.mjs`][markdownlint-cli2-mjs]
344
355
 
356
+ ### `package.json`
357
+
358
+ - The format of this file is a standard [npm `package.json`][package-json] file
359
+ including a `markdownlint-cli2` property at the root and a value corresponding
360
+ to the object described above for `.markdownlint-cli2.jsonc`.
361
+ - `package.json` is only supported in the current directory.
362
+ - `package.json` is not supported by the `--config` argument.
363
+ - For example: [`package-json-sample`][package-json-sample]
364
+
345
365
  ### `.markdownlint.jsonc` or `.markdownlint.json`
346
366
 
347
367
  - The format of this file is a [JSONC][jsonc] or [JSON][json] object matching
@@ -349,8 +369,6 @@ of the rules within.
349
369
  - Settings in this file apply to the directory it is in and all subdirectories
350
370
  - Settings **override** those applied by any versions of this file in a parent
351
371
  directory (up to the current directory).
352
- - If `jsonc` and `json` files are present in the same directory, the `jsonc`
353
- version takes precedence.
354
372
  - To merge the settings of these files or share configuration, use the `extends`
355
373
  property (documented in the link above).
356
374
  - Both file types support comments in JSON.
@@ -361,10 +379,6 @@ of the rules within.
361
379
  - The format of this file is a [YAML][yaml] object representing the
362
380
  [`markdownlint` `config` object][markdownlint-config].
363
381
  - Other details are the same as for `jsonc`/`json` files described above.
364
- - If `yaml` and `yml` files are present in the same directory, the `yaml`
365
- version takes precedence.
366
- - If a `jsonc` or `json` file is present in the same directory, it takes
367
- precedence.
368
382
  - For example: [`.markdownlint.yaml`][markdownlint-yaml]
369
383
 
370
384
  ### `.markdownlint.cjs` or `.markdownlint.mjs`
@@ -373,9 +387,6 @@ of the rules within.
373
387
  [ECMAScript module][ecmascript-module] (`.mjs`) that exports the
374
388
  [`markdownlint` `config` object][markdownlint-config].
375
389
  - Other details are the same as for `jsonc`/`json` files described above.
376
- - If a `.markdownlint.jsonc`, `.json`, `.yaml`, or `.yml` file is present in the
377
- same directory, it takes precedence; `.markdownlint.cjs` takes precedence over
378
- `.markdownlint.mjs`.
379
390
  - For example: [`.markdownlint.cjs`][markdownlint-cjs] or
380
391
  [`.markdownlint.mjs`][markdownlint-mjs]
381
392
 
@@ -401,7 +412,7 @@ reference to the `repos` list in that project's `.pre-commit-config.yaml` like:
401
412
 
402
413
  ```yaml
403
414
  - repo: https://github.com/DavidAnson/markdownlint-cli2
404
- rev: v0.8.0
415
+ rev: v0.9.0
405
416
  hooks:
406
417
  - id: markdownlint-cli2
407
418
  ```
@@ -413,8 +424,6 @@ reference to the `repos` list in that project's `.pre-commit-config.yaml` like:
413
424
 
414
425
  See [CHANGELOG.md](CHANGELOG.md).
415
426
 
416
- <!-- markdownlint-disable line-length -->
417
-
418
427
  [commonmark]: https://commonmark.org/
419
428
  [commonjs-module]: https://nodejs.org/api/modules.html#modules_modules_commonjs_modules
420
429
  [ecmascript-module]: https://nodejs.org/api/esm.html#modules-ecmascript-modules
@@ -465,6 +474,8 @@ See [CHANGELOG.md](CHANGELOG.md).
465
474
  [npm-image]: https://img.shields.io/npm/v/markdownlint-cli2.svg
466
475
  [npm-url]: https://www.npmjs.com/package/markdownlint-cli2
467
476
  [output-formatters]: doc/OutputFormatters.md
477
+ [package-json]: https://docs.npmjs.com/cli/v9/configuring-npm/package-json
478
+ [package-json-sample]: test/package-json/package.json
468
479
  [pre-commit]: https://pre-commit.com/
469
480
  [pre-commit-version]: https://pre-commit.com/#overriding-language-version
470
481
  [regexp]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp
@@ -10,7 +10,8 @@ const dynamicRequire = (typeof __non_webpack_require__ === "undefined") ? requir
10
10
  // Capture native require implementation for dynamic loading of modules
11
11
 
12
12
  // Requires
13
- const path = require("node:path");
13
+ const pathDefault = require("node:path");
14
+ const pathPosix = pathDefault.posix;
14
15
  const { pathToFileURL } = require("node:url");
15
16
  const markdownlintLibrary = require("markdownlint");
16
17
  const {
@@ -25,7 +26,7 @@ const resolveAndRequire = require("./resolve-and-require");
25
26
 
26
27
  // Variables
27
28
  const packageName = "markdownlint-cli2";
28
- const packageVersion = "0.8.0";
29
+ const packageVersion = "0.9.0";
29
30
  const libraryName = "markdownlint";
30
31
  const libraryVersion = markdownlintLibrary.getVersion();
31
32
  const dotOnlySubstitute = "*.{md,markdown}";
@@ -49,11 +50,11 @@ const yamlParse = (text) => require("yaml").parse(text);
49
50
  const negateGlob = (glob) => `!${glob}`;
50
51
 
51
52
  // Return a posix path (even on Windows)
52
- const posixPath = (p) => p.split(path.sep).join(path.posix.sep);
53
+ const posixPath = (p) => p.split(pathDefault.sep).join(pathPosix.sep);
53
54
 
54
55
  // Read a JSON(C) or YAML file and return the object
55
56
  const readConfig = (fs, dir, name, otherwise) => {
56
- const file = path.posix.join(dir, name);
57
+ const file = pathPosix.join(dir, name);
57
58
  return () => fs.promises.access(file).
58
59
  then(
59
60
  () => getJsoncParse().then(
@@ -80,7 +81,7 @@ const importOrRequireResolve = async (dir, id) => {
80
81
  }
81
82
  try {
82
83
  const fileUrlString =
83
- pathToFileURL(path.resolve(dir, expandId)).toString();
84
+ pathToFileURL(pathDefault.resolve(dir, expandId)).toString();
84
85
  // eslint-disable-next-line no-inline-comments
85
86
  const module = await import(/* webpackIgnore: true */ fileUrlString);
86
87
  return module.default;
@@ -114,22 +115,34 @@ const importOrRequireIdsAndParams = async (dir, idsAndParams, noRequire) => {
114
115
  };
115
116
 
116
117
  // Import or require a JavaScript file and return the exported object
117
- const importOrRequireConfig = (fs, dir, name, noRequire, otherwise) => (
118
- () => (noRequire
119
- // eslint-disable-next-line prefer-promise-reject-errors
120
- ? Promise.reject()
121
- : fs.promises.access(path.posix.join(dir, name))
122
- ).
118
+ const importOrRequireConfig = (fs, dir, name, noRequire, otherwise) => {
119
+ const id = pathPosix.join(dir, name);
120
+ return () => fs.promises.access(id).
123
121
  then(
124
- () => importOrRequireResolve(dir, `./${name}`),
122
+ () => (noRequire ? {} : importOrRequireResolve(dir, id)),
125
123
  otherwise
126
- )
127
- );
124
+ );
125
+ };
126
+
127
+ // Extend a config object if it has 'extends' property
128
+ const getExtendedConfig = async (config, configPath, fs) => {
129
+ if (config.extends) {
130
+ const jsoncParse = await getJsoncParse();
131
+ return markdownlintExtendConfig(
132
+ config,
133
+ configPath,
134
+ [ jsoncParse, yamlParse ],
135
+ fs
136
+ );
137
+ }
138
+
139
+ return config;
140
+ };
128
141
 
129
142
  // Read an options or config file in any format and return the object
130
143
  const readOptionsOrConfig = async (configPath, fs, noRequire) => {
131
- const basename = path.basename(configPath);
132
- const dirname = path.dirname(configPath);
144
+ const basename = pathPosix.basename(configPath);
145
+ const dirname = pathPosix.dirname(configPath);
133
146
  let options = null;
134
147
  let config = null;
135
148
  if (basename.endsWith(".markdownlint-cli2.jsonc")) {
@@ -167,16 +180,25 @@ const readOptionsOrConfig = async (configPath, fs, noRequire) => {
167
180
  "(e.g., \".markdownlint.json\" or \"example.markdownlint-cli2.jsonc\")."
168
181
  );
169
182
  }
170
- return options || { config };
183
+
184
+ if (options) {
185
+ if (options.config) {
186
+ options.config = await getExtendedConfig(options.config, configPath, fs);
187
+ }
188
+ return options;
189
+ }
190
+
191
+ config = await getExtendedConfig(config, configPath, fs);
192
+ return { config };
171
193
  };
172
194
 
173
195
  // Filter a list of files to ignore by glob
174
196
  const removeIgnoredFiles = (dir, files, ignores) => {
175
197
  const micromatch = require("micromatch");
176
198
  return micromatch(
177
- files.map((file) => path.posix.relative(dir, file)),
199
+ files.map((file) => pathPosix.relative(dir, file)),
178
200
  ignores
179
- ).map((file) => path.posix.join(dir, file));
201
+ ).map((file) => pathPosix.join(dir, file));
180
202
  };
181
203
 
182
204
  // Process/normalize command-line arguments and return glob patterns
@@ -234,6 +256,7 @@ Configuration via:
234
256
  - .markdownlint.jsonc or .markdownlint.json
235
257
  - .markdownlint.yaml or .markdownlint.yml
236
258
  - .markdownlint.cjs or .markdownlint.mjs
259
+ - package.json
237
260
 
238
261
  Cross-platform compatibility:
239
262
  - UNIX and Windows shells expand globs according to different rules; quoting arguments is recommended
@@ -250,7 +273,7 @@ $ markdownlint-cli2 "**/*.md" "#node_modules"`
250
273
 
251
274
  // Get (creating if necessary) and process a directory's info object
252
275
  const getAndProcessDirInfo =
253
- (fs, tasks, dirToDirInfo, dir, relativeDir, noRequire, func) => {
276
+ (fs, tasks, dirToDirInfo, dir, relativeDir, noRequire, allowPackageJson) => {
254
277
  let dirInfo = dirToDirInfo[dir];
255
278
  if (!dirInfo) {
256
279
  dirInfo = {
@@ -265,9 +288,10 @@ const getAndProcessDirInfo =
265
288
 
266
289
  // Load markdownlint-cli2 object(s)
267
290
  const markdownlintCli2Jsonc =
268
- path.posix.join(dir, ".markdownlint-cli2.jsonc");
291
+ pathPosix.join(dir, ".markdownlint-cli2.jsonc");
269
292
  const markdownlintCli2Yaml =
270
- path.posix.join(dir, ".markdownlint-cli2.yaml");
293
+ pathPosix.join(dir, ".markdownlint-cli2.yaml");
294
+ const packageJson = pathPosix.join(dir, "package.json");
271
295
  tasks.push(
272
296
  fs.promises.access(markdownlintCli2Jsonc).
273
297
  then(
@@ -292,7 +316,21 @@ const getAndProcessDirInfo =
292
316
  dir,
293
317
  ".markdownlint-cli2.mjs",
294
318
  noRequire,
295
- noop
319
+ () => (allowPackageJson
320
+ ? fs.promises.access(packageJson)
321
+ // eslint-disable-next-line prefer-promise-reject-errors
322
+ : Promise.reject()
323
+ ).
324
+ then(
325
+ () => fs.promises.
326
+ readFile(packageJson, utf8).
327
+ then(
328
+ (content) => getJsoncParse().
329
+ then((jsoncParse) => jsoncParse(content)).
330
+ then((obj) => obj[packageName])
331
+ ),
332
+ noop
333
+ )
296
334
  )
297
335
  )
298
336
  )
@@ -301,17 +339,12 @@ const getAndProcessDirInfo =
301
339
  dirInfo.markdownlintOptions = options;
302
340
  return options &&
303
341
  options.config &&
304
- options.config.extends &&
305
- getJsoncParse().
306
- then(
307
- (jsoncParse) => markdownlintExtendConfig(
308
- options.config,
309
- // Just needs to identify a file in the right directory
310
- markdownlintCli2Jsonc,
311
- [ jsoncParse, yamlParse ],
312
- fs
313
- )
314
- ).
342
+ getExtendedConfig(
343
+ options.config,
344
+ // Just needs to identify a file in the right directory
345
+ markdownlintCli2Jsonc,
346
+ fs
347
+ ).
315
348
  then((config) => {
316
349
  options.config = config;
317
350
  });
@@ -360,9 +393,6 @@ const getAndProcessDirInfo =
360
393
  })
361
394
  );
362
395
  }
363
- if (func) {
364
- func(dirInfo);
365
- }
366
396
  return dirInfo;
367
397
  };
368
398
 
@@ -385,7 +415,8 @@ const getBaseOptions = async (
385
415
  dirToDirInfo,
386
416
  baseDir,
387
417
  relativeDir,
388
- noRequire
418
+ noRequire,
419
+ true
389
420
  );
390
421
  await Promise.all(tasks);
391
422
  // eslint-disable-next-line no-multi-assign
@@ -437,7 +468,7 @@ const enumerateFiles =
437
468
  (globPattern) => {
438
469
  if (globPattern.startsWith(":")) {
439
470
  literalFiles.push(
440
- posixPath(path.resolve(baseDirSystem, globPattern.slice(1)))
471
+ posixPath(pathDefault.resolve(baseDirSystem, globPattern.slice(1)))
441
472
  );
442
473
  return false;
443
474
  }
@@ -459,13 +490,15 @@ const enumerateFiles =
459
490
  globPattern.startsWith("!")
460
491
  ? globPattern.slice(1)
461
492
  : globPattern;
462
- const globPath =
463
- (path.posix.isAbsolute(barePattern) || path.isAbsolute(barePattern))
464
- ? barePattern
465
- : path.posix.join(baseDir, barePattern);
493
+ const globPath = (
494
+ pathPosix.isAbsolute(barePattern) ||
495
+ pathDefault.isAbsolute(barePattern)
496
+ )
497
+ ? barePattern
498
+ : pathPosix.join(baseDir, barePattern);
466
499
  return fs.promises.stat(globPath).
467
500
  then((stats) => (stats.isDirectory()
468
- ? path.posix.join(globPattern, "**")
501
+ ? pathPosix.join(globPattern, "**")
469
502
  : globPattern)).
470
503
  catch(() => globPattern);
471
504
  })
@@ -478,18 +511,17 @@ const enumerateFiles =
478
511
  ...filteredLiteralFiles
479
512
  ];
480
513
  for (const file of files) {
481
- const dir = path.posix.dirname(file);
482
- getAndProcessDirInfo(
514
+ const dir = pathPosix.dirname(file);
515
+ const dirInfo = getAndProcessDirInfo(
483
516
  fs,
484
517
  tasks,
485
518
  dirToDirInfo,
486
519
  dir,
487
520
  null,
488
521
  noRequire,
489
- (dirInfo) => {
490
- dirInfo.files.push(file);
491
- }
522
+ false
492
523
  );
524
+ dirInfo.files.push(file);
493
525
  }
494
526
  await Promise.all(tasks);
495
527
  };
@@ -503,7 +535,7 @@ const enumerateParents = async (fs, baseDir, dirToDirInfo, noRequire) => {
503
535
  let baseDirParent = baseDir;
504
536
  do {
505
537
  baseDirParents[baseDirParent] = true;
506
- baseDirParent = path.posix.dirname(baseDirParent);
538
+ baseDirParent = pathPosix.dirname(baseDirParent);
507
539
  } while (!baseDirParents[baseDirParent]);
508
540
 
509
541
  // Visit parents of each dirInfo
@@ -512,11 +544,11 @@ const enumerateParents = async (fs, baseDir, dirToDirInfo, noRequire) => {
512
544
  let lastDir = dir;
513
545
  while (
514
546
  !baseDirParents[dir] &&
515
- (dir = path.posix.dirname(dir)) &&
547
+ (dir = pathPosix.dirname(dir)) &&
516
548
  (dir !== lastDir)
517
549
  ) {
518
550
  lastDir = dir;
519
- lastDirInfo =
551
+ const dirInfo =
520
552
  getAndProcessDirInfo(
521
553
  fs,
522
554
  tasks,
@@ -524,11 +556,10 @@ const enumerateParents = async (fs, baseDir, dirToDirInfo, noRequire) => {
524
556
  dir,
525
557
  null,
526
558
  noRequire,
527
- // eslint-disable-next-line no-loop-func
528
- (dirInfo) => {
529
- lastDirInfo.parent = dirInfo;
530
- }
559
+ false
531
560
  );
561
+ lastDirInfo.parent = dirInfo;
562
+ lastDirInfo = dirInfo;
532
563
  }
533
564
 
534
565
  // If dir not under baseDir, inject it as parent for configuration
@@ -761,7 +792,7 @@ const createSummary = (baseDir, taskResults) => {
761
792
  for (const fileName in results) {
762
793
  const errorInfos = results[fileName];
763
794
  for (const errorInfo of errorInfos) {
764
- const fileNameRelative = path.posix.relative(baseDir, fileName);
795
+ const fileNameRelative = pathPosix.relative(baseDir, fileName);
765
796
  summary.push({
766
797
  "fileName": fileNameRelative,
767
798
  ...errorInfo,
@@ -831,7 +862,7 @@ const main = async (params) => {
831
862
  const logError = params.logError || noop;
832
863
  const fs = params.fs || require("node:fs");
833
864
  const baseDirSystem =
834
- (directory && path.resolve(directory)) ||
865
+ (directory && pathDefault.resolve(directory)) ||
835
866
  process.cwd();
836
867
  const baseDir = posixPath(baseDirSystem);
837
868
  // Output banner
@@ -860,9 +891,11 @@ const main = async (params) => {
860
891
  let optionsArgv = null;
861
892
  let relativeDir = null;
862
893
  if (configPath) {
894
+ const resolvedConfigPath =
895
+ posixPath(pathDefault.resolve(baseDirSystem, configPath));
863
896
  optionsArgv =
864
- await readOptionsOrConfig(configPath, fs, noRequire);
865
- relativeDir = path.dirname(configPath);
897
+ await readOptionsOrConfig(resolvedConfigPath, fs, noRequire);
898
+ relativeDir = pathPosix.dirname(resolvedConfigPath);
866
899
  }
867
900
  // Process arguments and get base options
868
901
  const globPatterns = processArgv(argvFiltered);
@@ -877,14 +910,17 @@ const main = async (params) => {
877
910
  noGlobs,
878
911
  noRequire
879
912
  );
880
- if ((globPatterns.length === 0) && !nonFileContents) {
913
+ if (
914
+ ((globPatterns.length === 0) && !nonFileContents) ||
915
+ (configPath === null)
916
+ ) {
881
917
  showHelp(logMessage);
882
918
  return 2;
883
919
  }
884
920
  // Include any file overrides or non-file content
885
921
  const resolvedFileContents = {};
886
922
  for (const file in fileContents) {
887
- const resolvedFile = posixPath(path.resolve(baseDirSystem, file));
923
+ const resolvedFile = posixPath(pathDefault.resolve(baseDirSystem, file));
888
924
  resolvedFileContents[resolvedFile] =
889
925
  fileContents[file];
890
926
  }