markuplint 5.0.0-rc.2 → 5.0.0-rc.5

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.
Files changed (53) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +3 -3
  3. package/lib/api/ml-engine.d.ts +16 -0
  4. package/lib/api/ml-engine.js +55 -8
  5. package/lib/cli/bootstrap.d.ts +2 -15
  6. package/lib/cli/bootstrap.js +3 -13
  7. package/lib/cli/command.d.ts +0 -12
  8. package/lib/cli/command.js +67 -28
  9. package/lib/cli/dry-run-output.d.ts +0 -8
  10. package/lib/cli/dry-run-output.js +0 -8
  11. package/lib/cli/index.d.ts +4 -3
  12. package/lib/cli/index.js +4 -3
  13. package/lib/cli/init/create-config.d.ts +0 -16
  14. package/lib/cli/init/create-config.js +0 -20
  15. package/lib/cli/init/get-default-rules.d.ts +0 -9
  16. package/lib/cli/init/get-default-rules.js +0 -9
  17. package/lib/cli/init/index.d.ts +0 -14
  18. package/lib/cli/init/index.js +18 -20
  19. package/lib/cli/init/select-modules.d.ts +0 -10
  20. package/lib/cli/init/select-modules.js +0 -10
  21. package/lib/cli/init/types.d.ts +1 -20
  22. package/lib/cli/output.d.ts +0 -21
  23. package/lib/cli/output.js +0 -21
  24. package/lib/cli/search/index.d.ts +0 -17
  25. package/lib/cli/search/index.js +0 -17
  26. package/lib/debug.d.ts +0 -9
  27. package/lib/debug.js +0 -9
  28. package/lib/get-json-module.d.ts +0 -10
  29. package/lib/get-json-module.js +0 -10
  30. package/lib/global-settings.d.ts +0 -15
  31. package/lib/global-settings.js +0 -12
  32. package/lib/reporter/github-reporter.d.ts +0 -9
  33. package/lib/reporter/github-reporter.js +0 -9
  34. package/lib/reporter/index.d.ts +0 -9
  35. package/lib/reporter/index.js +0 -9
  36. package/lib/reporter/simple-reporter.d.ts +0 -11
  37. package/lib/reporter/simple-reporter.js +0 -11
  38. package/lib/reporter/standard-reporter.d.ts +0 -12
  39. package/lib/reporter/standard-reporter.js +0 -12
  40. package/lib/suppressions/apply-suppressions.d.ts +9 -0
  41. package/lib/suppressions/apply-suppressions.js +9 -23
  42. package/lib/suppressions/compute-scope.d.ts +17 -16
  43. package/lib/suppressions/compute-scope.js +17 -24
  44. package/lib/suppressions/downgrade-severity.js +0 -3
  45. package/lib/suppressions/index.d.ts +8 -1
  46. package/lib/suppressions/index.js +8 -1
  47. package/lib/testing-tool/index.js +3 -0
  48. package/package.json +15 -15
  49. package/ARCHITECTURE.ja.md +0 -419
  50. package/ARCHITECTURE.md +0 -419
  51. package/SKILL.md +0 -110
  52. package/docs/maintenance.ja.md +0 -207
  53. package/docs/maintenance.md +0 -207
package/CHANGELOG.md CHANGED
@@ -3,6 +3,57 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [5.0.0-rc.5](https://github.com/markuplint/markuplint/compare/v5.0.0-rc.4...v5.0.0-rc.5) (2026-08-28)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **markuplint:** report post-fix violations for exit code and suppressions ([03855d6](https://github.com/markuplint/markuplint/commit/03855d6641ae736b65994fa6123e91779d2eb41e)), closes [#3890](https://github.com/markuplint/markuplint/issues/3890)
11
+ - **markuplint:** stop repeating config-error messages once per file ([#4007](https://github.com/markuplint/markuplint/issues/4007)) ([466193d](https://github.com/markuplint/markuplint/commit/466193d286628f69e0d99fc8b2f66028523025aa)), closes [#4006](https://github.com/markuplint/markuplint/issues/4006)
12
+ - **pretenders:** resolve same-named components via imports, not scan order ([#3957](https://github.com/markuplint/markuplint/issues/3957)) ([d46a514](https://github.com/markuplint/markuplint/commit/d46a5148c4d7afb156962f4ed795f40a9324e6c5)), closes [#3951](https://github.com/markuplint/markuplint/issues/3951) [#3951](https://github.com/markuplint/markuplint/issues/3951) [#3951](https://github.com/markuplint/markuplint/issues/3951)
13
+ - **rules:** surface disallowed-element reason via reasonOnly (close [#3815](https://github.com/markuplint/markuplint/issues/3815)) ([#3986](https://github.com/markuplint/markuplint/issues/3986)) ([0142cec](https://github.com/markuplint/markuplint/commit/0142cec667f70fee086f2a6e06d7a26e66bda380))
14
+ - **types): strict charset=utf-8; feat(rules:** usemap-references-map ([#3969](https://github.com/markuplint/markuplint/issues/3969)) ([c63070e](https://github.com/markuplint/markuplint/commit/c63070e29ccb283da7468b2fc67db372ebfcf42a)), closes [#3945](https://github.com/markuplint/markuplint/issues/3945) [#3966](https://github.com/markuplint/markuplint/issues/3966) [#3966](https://github.com/markuplint/markuplint/issues/3966) [#3928](https://github.com/markuplint/markuplint/issues/3928)
15
+
16
+ ### Code Refactoring
17
+
18
+ - **rules:** redesign v5 rule system — naming, splits, specConformance ([#3989](https://github.com/markuplint/markuplint/issues/3989)) ([e925565](https://github.com/markuplint/markuplint/commit/e925565ce537848d7d1573369723cbce724a841b)), closes [#4](https://github.com/markuplint/markuplint/issues/4) [#aside-conditional-role-mapping-aria-13](https://github.com/markuplint/markuplint/issues/aside-conditional-role-mapping-aria-13)
19
+
20
+ - feat(markuplint)!: stop forcing severity.parseError default in CLI ([79ff00b](https://github.com/markuplint/markuplint/commit/79ff00b5d068802f4d7e0d8d30ee63e55b8bc7f1)), closes [#3844](https://github.com/markuplint/markuplint/issues/3844)
21
+
22
+ ### Features
23
+
24
+ - add `pretenders.auto` for on-demand import-graph resolution ([#3962](https://github.com/markuplint/markuplint/issues/3962)) ([5870671](https://github.com/markuplint/markuplint/commit/58706711a20c12cff080d49359f3f6443345eca3)), closes [#3951](https://github.com/markuplint/markuplint/issues/3951) [#3957](https://github.com/markuplint/markuplint/issues/3957) [#3951](https://github.com/markuplint/markuplint/issues/3951) [#3957](https://github.com/markuplint/markuplint/issues/3957) [#3951](https://github.com/markuplint/markuplint/issues/3951) [#3957](https://github.com/markuplint/markuplint/issues/3957) [#3951](https://github.com/markuplint/markuplint/issues/3951) [#3957](https://github.com/markuplint/markuplint/issues/3957) [#3951](https://github.com/markuplint/markuplint/issues/3951) [#3957](https://github.com/markuplint/markuplint/issues/3957) [#3959](https://github.com/markuplint/markuplint/issues/3959) [#3951](https://github.com/markuplint/markuplint/issues/3951) [#3957](https://github.com/markuplint/markuplint/issues/3957) [#3951](https://github.com/markuplint/markuplint/issues/3951) [#3957](https://github.com/markuplint/markuplint/issues/3957) [#3959](https://github.com/markuplint/markuplint/issues/3959) [#3951](https://github.com/markuplint/markuplint/issues/3951) [#3951](https://github.com/markuplint/markuplint/issues/3951)
25
+ - **config-presets:** forbid <base> after <link> or <script> in <head> ([#3925](https://github.com/markuplint/markuplint/issues/3925)) ([ceb892d](https://github.com/markuplint/markuplint/commit/ceb892d64772d459a6bd9564684218e3afbdec2e))
26
+ - **rules:** add form-attr-references-form rule ([6b541f0](https://github.com/markuplint/markuplint/commit/6b541f032b76c3712c99ea35596b6b0aa79b6137))
27
+ - **rules:** add input-button-non-empty-value rule ([2cc73dd](https://github.com/markuplint/markuplint/commit/2cc73ddd0f874fae9faf005f498d73fa364b682c))
28
+ - **rules:** add input-file-empty-value rule ([228cbd7](https://github.com/markuplint/markuplint/commit/228cbd752956d3df8f525e4f19f9278a44d87160))
29
+ - **rules:** add input-list-references-datalist rule ([#3931](https://github.com/markuplint/markuplint/issues/3931)) ([bf4ef54](https://github.com/markuplint/markuplint/commit/bf4ef54a1b2937ecbe05fbe5121ddfe199781a95))
30
+ - **rules:** add label-for-references-labelable rule ([#3932](https://github.com/markuplint/markuplint/issues/3932)) ([3713e6b](https://github.com/markuplint/markuplint/commit/3713e6b435a76fe03a941a36a5e33c0ab06c9a80)), closes [#3918](https://github.com/markuplint/markuplint/issues/3918)
31
+ - **rules:** add label-no-multiple-controls rule ([5da3f85](https://github.com/markuplint/markuplint/commit/5da3f8523dd6ffd9f44ea73d9012952aad85d821))
32
+ - **rules:** add map-id-name-match rule ([1472daf](https://github.com/markuplint/markuplint/commit/1472daf62470bf56c4cb326ab47dc43ba87a8cb3))
33
+ - **rules:** add no-extra-selected-options rule ([3ea75ac](https://github.com/markuplint/markuplint/commit/3ea75ac0b6850c36d5419924b18c1002dfb864a9))
34
+ - **rules:** add progress-value-bounds rule ([#3926](https://github.com/markuplint/markuplint/issues/3926)) ([1e259ec](https://github.com/markuplint/markuplint/commit/1e259ec9929ceb3c7ac5864ce2807420646e9602))
35
+ - **rules:** add wai-aria-tab-requires-tabpanel rule ([#3955](https://github.com/markuplint/markuplint/issues/3955)) ([eac9abe](https://github.com/markuplint/markuplint/commit/eac9abef20ef304c3da2114849686b9cf0733942))
36
+ - **rules:** surface parse5-silent HTML LS parse errors (close nu-only umbrella [#3943](https://github.com/markuplint/markuplint/issues/3943)) ([#3980](https://github.com/markuplint/markuplint/issues/3980)) ([89951fa](https://github.com/markuplint/markuplint/commit/89951fa274007d56370510cb0cf11aead808ce13))
37
+ - wire script-content into preset, bench, and default-rules ([dd0507a](https://github.com/markuplint/markuplint/commit/dd0507a9f54fcff25dba666a1c8fbc082489bdc8))
38
+
39
+ ### BREAKING CHANGES
40
+
41
+ - **rules:** with no alias coverage.
42
+ - CLI invocations that previously relied on the implicit
43
+ \`severity.parseError: 'error'\` default for _fatal_ parser errors are
44
+ unaffected — fatal errors continue to emit at \`'error'\` regardless. But
45
+ projects that lint malformed HTML and expected non-fatal parse5 events to
46
+ show up by default must now pass \`--severity-parse-error error\` (or set
47
+ \`severity.parseError\` in their config).
48
+
49
+ # [5.0.0-rc.4](https://github.com/markuplint/markuplint/compare/v5.0.0-rc.3...v5.0.0-rc.4) (2026-04-19)
50
+
51
+ **Note:** Version bump only for package markuplint
52
+
53
+ # [5.0.0-rc.3](https://github.com/markuplint/markuplint/compare/v5.0.0-rc.2...v5.0.0-rc.3) (2026-04-19)
54
+
55
+ **Note:** Version bump only for package markuplint
56
+
6
57
  # [5.0.0-rc.2](https://github.com/markuplint/markuplint/compare/v5.0.0-rc.1...v5.0.0-rc.2) (2026-04-15)
7
58
 
8
59
  ### Bug Fixes
package/README.md CHANGED
@@ -25,7 +25,7 @@
25
25
  $ npx markuplint target.html
26
26
  ```
27
27
 
28
- Supported for _Node.js_ `v22.0.0` or later.
28
+ Supported for _Node.js_ `v24.0.0` or later.
29
29
 
30
30
  ## Usage
31
31
 
@@ -138,7 +138,7 @@ $ markuplint --prune-suppressions "src/**/*.html"
138
138
  ```json
139
139
  {
140
140
  "src/index.html": {
141
- "attr-duplication": { "count": 3, "scope": "#main-nav > ul" }
141
+ "no-duplicate-attr": { "count": 3, "scope": "#main-nav > ul" }
142
142
  }
143
143
  }
144
144
  ```
@@ -158,7 +158,7 @@ See [ESLint's Bulk Suppressions](https://eslint.org/docs/latest/use/suppressions
158
158
 
159
159
  ## Editor Extensions
160
160
 
161
- - [Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=yusukehirao.vscode-markuplint)
161
+ - [Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=markuplint.vscode-markuplint)
162
162
 
163
163
  ## License
164
164
 
@@ -70,6 +70,22 @@ export declare class MLEngine extends Emitter<MLEngineEventMap> {
70
70
  * @param enable - Whether to enable watch mode
71
71
  */
72
72
  watchMode(enable: boolean): void;
73
+ /**
74
+ * Resolves the configuration set for the target file.
75
+ *
76
+ * Public — unlike the other resolution steps, which are private —
77
+ * because the CLI's `--show-config` needs the computed configuration
78
+ * without running a lint.
79
+ *
80
+ * Precedence contract (highest first): the inline `config` option,
81
+ * then the explicit `configFile` path, then auto-discovered config files
82
+ * (search is skipped when `noSearchConfig` or `configFile` is set),
83
+ * then `defaultConfig`. `markuplint:recommended` applies only when none
84
+ * of these are provided.
85
+ *
86
+ * @param cache - Whether to reuse previously loaded config files
87
+ * @returns The resolved configuration set
88
+ */
73
89
  resolveConfig(cache: boolean): Promise<ConfigSet>;
74
90
  }
75
91
  export {};
@@ -1,6 +1,7 @@
1
- import { ConfigProvider, resolveFiles, resolveParser, resolvePretenders, resolveRules, resolveSpecs, } from '@markuplint/file-resolver';
2
- import { mergeConfig } from '@markuplint/ml-config';
1
+ import { ConfigProvider, disambiguatePretendersForFile, invalidatePretenderResolutionCaches, resolveFiles, resolveParser, resolvePretenders, resolveRules, resolveSpecs, } from '@markuplint/file-resolver';
2
+ import { applyRuleAliasesToConfig, mergeConfig } from '@markuplint/ml-config';
3
3
  import { MLCore, convertRuleset } from '@markuplint/ml-core';
4
+ import { ruleAliasTable } from '@markuplint/rules';
4
5
  import { isFatalError } from '@markuplint/shared';
5
6
  import { FSWatcher } from 'chokidar';
6
7
  import { Emitter } from 'strict-event-emitter';
@@ -256,7 +257,7 @@ export class MLEngine extends Emitter {
256
257
  ...configSet.config.severity,
257
258
  ...this.#options?.severity,
258
259
  };
259
- const pretenders = await this.#resolvePretenders(configSet);
260
+ const pretenders = await this.#resolvePretenders(configSet, cache);
260
261
  fileLog('Resolved pretenders: %O', pretenders);
261
262
  const ruleset = this.#resolveRuleset(configSet);
262
263
  fileLog('Resolved ruleset: %O', ruleset);
@@ -293,6 +294,22 @@ export class MLEngine extends Emitter {
293
294
  configErrors: configSet.errs,
294
295
  };
295
296
  }
297
+ /**
298
+ * Resolves the configuration set for the target file.
299
+ *
300
+ * Public — unlike the other resolution steps, which are private —
301
+ * because the CLI's `--show-config` needs the computed configuration
302
+ * without running a lint.
303
+ *
304
+ * Precedence contract (highest first): the inline `config` option,
305
+ * then the explicit `configFile` path, then auto-discovered config files
306
+ * (search is skipped when `noSearchConfig` or `configFile` is set),
307
+ * then `defaultConfig`. `markuplint:recommended` applies only when none
308
+ * of these are provided.
309
+ *
310
+ * @param cache - Whether to reuse previously loaded config files
311
+ * @returns The resolved configuration set
312
+ */
296
313
  async resolveConfig(cache) {
297
314
  this.emit('log', 'resolveConfig', JSON.stringify(this.#configProvider, null, 2));
298
315
  configLog('configProvider: %s', this.#configProvider);
@@ -317,7 +334,25 @@ export class MLEngine extends Emitter {
317
334
  }
318
335
  configLog('defaultRecommended: %s', defaultRecommended ?? 'N/A');
319
336
  this.emit('log', 'defaultRecommended', defaultRecommended ?? 'N/A');
320
- const configSet = await this.#configProvider.resolve(this.#file, [configFilePathsFromTarget, this.#options?.configFile, configKey, defaultRecommended], cache);
337
+ const resolvedConfigSet = await this.#configProvider.resolve(this.#file, [configFilePathsFromTarget, this.#options?.configFile, configKey, defaultRecommended], cache);
338
+ // Rewrite deprecated rule names (v5 rule-system redesign, #3989) to
339
+ // their current replacement(s) so old configurations keep working.
340
+ // Applied once, here, after `extends` is fully merged — everything
341
+ // downstream (Ruleset, rule resolution, `--show-config`) sees only
342
+ // current rule names. Covers all three places a rule name can appear:
343
+ // the top-level `rules` map, and each `nodeRules`/`childNodeRules`
344
+ // entry's own `rules`.
345
+ const { config: aliasedConfig, warnings: ruleAliasWarnings } = applyRuleAliasesToConfig(resolvedConfigSet.config, ruleAliasTable);
346
+ const configSet = ruleAliasWarnings.length === 0
347
+ ? resolvedConfigSet
348
+ : {
349
+ ...resolvedConfigSet,
350
+ config: aliasedConfig,
351
+ errs: [
352
+ ...resolvedConfigSet.errs,
353
+ ...ruleAliasWarnings.map(({ deprecatedName, replacedBy }) => new Error(`Rule "${deprecatedName}" is deprecated and will be removed in v6. Use ${replacedBy.join(', ')} instead.`)),
354
+ ],
355
+ };
321
356
  this.emit('config', this.#file.path, configSet);
322
357
  if (this.#options?.watch) {
323
358
  // It doesn't watch the main HTML file because it may is watched and managed by a language server or text editor or more.
@@ -335,10 +370,22 @@ export class MLEngine extends Emitter {
335
370
  }
336
371
  async #resolvePretenders(
337
372
  // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
338
- configSet) {
339
- const pretenders = await resolvePretenders(configSet.config.pretenders);
340
- fileLog('Resolved pretenders: %O', pretenders);
341
- return pretenders;
373
+ configSet, cache) {
374
+ if (!cache) {
375
+ // A cache-busting re-resolve (e.g. watch mode after a file change) must also
376
+ // invalidate `@markuplint/pretenders`' own module-level resolution caches —
377
+ // otherwise a renamed export or a newly valid tsconfig `paths` alias keeps
378
+ // resolving as it did before the change for the rest of the process's lifetime.
379
+ await invalidatePretenderResolutionCaches();
380
+ }
381
+ const sourceCode = await this.#file.getCode();
382
+ const pretenders = await resolvePretenders(configSet.config.pretenders, {
383
+ filePath: this.#file.path,
384
+ sourceCode,
385
+ });
386
+ const disambiguated = await disambiguatePretendersForFile(this.#file.path, sourceCode, pretenders);
387
+ fileLog('Resolved pretenders: %O', disambiguated);
388
+ return disambiguated;
342
389
  }
343
390
  async #resolveRules(plugins, ruleset) {
344
391
  const rules = await resolveRules(plugins, ruleset, this.#options?.importPresetRules ?? true);
@@ -1,13 +1,5 @@
1
1
  import type { ReadonlyDeep } from 'type-fest';
2
- /**
3
- * Help text displayed when the CLI is invoked with `--help` or without arguments.
4
- * Documents all available options, flags, and usage examples.
5
- */
6
- export declare const help = "\nUsage\n\t$ markuplint <HTML file paths (glob format)>\n\t$ <stdout> | markuplint\n\nOptions\n\t--config, -c FILE_PATH A configuration file path.\n\t--fix, Fix HTML.\n\t--fix-dry-run Show what --fix would change without writing files.\n\t--format, -f FORMAT Output format. Support \"JSON\", \"Simple\", \"GitHub\" and \"Standard\". Default: \"Standard\".\n\t--no-search-config No search a configure file automatically.\n\t--ignore-ext Evaluate files that are received even though the type of extension.\n\t--no-import-preset-rules No import preset rules.\n\t--locale Locale of the message of violation. Default is an OS setting.\n\t--no-color, Output no color.\n\t--problem-only, -p Output only problems, without passeds.\n\t--no-allow-warnings Return status code 1 even if there are warnings.\n\t--allow-empty-input Return status code 1 even if there are no input files.\n\t--show-config Output computed configuration of the target file. Supports \"details\" and empty. Default: empty.\n\t--verbose Output with detailed information.\n\t--include-node-modules Include files in node_modules directory. Default: false.\n\t--severity-parse-error Specifies the severity level of parse errors. Supports \"error\", \"warning\", and \"off\". Default: \"error\".\n\t--max-count Limit the number of violations shown. Default: 0 (no limit).\n\t--max-warnings Number of warnings to trigger nonzero exit code. Default: -1 (no limit).\n\t--progressive-output Output results immediately after processing each file. Default: false.\n\n\t--suppress [Experimental] Generate/update suppressions file for all current errors.\n\t--suppress-rule RULE_ID [Experimental] Suppress only the specified rule.\n\t--prune-suppressions [Experimental] Remove stale entries from the suppressions file.\n\t--suppressions-location PATH [Experimental] Custom path for the suppressions file. Default: \"markuplint-suppressions.json\".\n\n\t--init Initialize settings interactively.\n\t--search Search lines of codes that include the target element by selectors.\n\n\t--help, -h Show help.\n\t--version, -v Show version.\n\nExamples\n\t$ markuplint verifyee.html --config path/to/.markuplintrc\n\t$ cat verifyee.html | markuplint\n";
7
- /**
8
- * The parsed CLI instance created by `meow`, providing access to
9
- * positional arguments (`cli.input`) and parsed flags (`cli.flags`).
10
- */
2
+ export declare const help = "\nUsage\n\t$ markuplint <HTML file paths (glob format)>\n\t$ <stdout> | markuplint\n\nOptions\n\t--config, -c FILE_PATH A configuration file path.\n\t--fix, Fix HTML.\n\t--fix-dry-run Show what --fix would change without writing files.\n\t--format, -f FORMAT Output format. Support \"JSON\", \"Simple\", \"GitHub\" and \"Standard\". Default: \"Standard\".\n\t--no-search-config No search a configure file automatically.\n\t--ignore-ext Evaluate files that are received even though the type of extension.\n\t--no-import-preset-rules No import preset rules.\n\t--locale Locale of the message of violation. Default is an OS setting.\n\t--no-color, Output no color.\n\t--problem-only, -p Output only problems, without passeds.\n\t--no-allow-warnings Return status code 1 even if there are warnings.\n\t--allow-empty-input Return status code 1 even if there are no input files.\n\t--show-config Output computed configuration of the target file. Supports \"details\" and empty. Default: empty.\n\t--verbose Output with detailed information.\n\t--include-node-modules Include files in node_modules directory. Default: false.\n\t--severity-parse-error Severity for the built-in parse-error channel. Supports \"error\", \"warning\", and \"off\". Unset by default: fatal ParserErrors emit at \"error\" and non-fatal parse5 events are off (opt-in per code via config).\n\t--max-count Limit the number of violations shown. Default: 0 (no limit).\n\t--max-warnings Number of warnings to trigger nonzero exit code. Default: -1 (no limit).\n\t--no-progressive-output Wait until every file is processed before outputting results. Default: false (output progressively).\n\n\t--suppress [Experimental] Generate/update suppressions file for all current errors.\n\t--suppress-rule RULE_ID [Experimental] Suppress only the specified rule.\n\t--prune-suppressions [Experimental] Remove stale entries from the suppressions file.\n\t--suppressions-location PATH [Experimental] Custom path for the suppressions file. Default: \"markuplint-suppressions.json\".\n\n\t--init Initialize settings interactively.\n\t--search Search lines of codes that include the target element by selectors.\n\n\t--help, -h Show help.\n\t--version, -v Show version.\n\nExamples\n\t$ markuplint verifyee.html --config path/to/.markuplintrc\n\t$ cat verifyee.html | markuplint\n";
11
3
  export declare const cli: import("meow").Result<{
12
4
  config: {
13
5
  type: "string";
@@ -81,7 +73,6 @@ export declare const cli: import("meow").Result<{
81
73
  };
82
74
  severityParseError: {
83
75
  type: "string";
84
- default: string;
85
76
  };
86
77
  maxCount: {
87
78
  type: "number";
@@ -93,7 +84,7 @@ export declare const cli: import("meow").Result<{
93
84
  };
94
85
  progressiveOutput: {
95
86
  type: "boolean";
96
- default: false;
87
+ default: true;
97
88
  };
98
89
  suppress: {
99
90
  type: "boolean";
@@ -110,8 +101,4 @@ export declare const cli: import("meow").Result<{
110
101
  type: "string";
111
102
  };
112
103
  }>;
113
- /**
114
- * Deeply read-only type representing the parsed CLI flags.
115
- * Derived from the `meow` flag definitions in {@link cli}.
116
- */
117
104
  export type CLIOptions = ReadonlyDeep<typeof cli.flags>;
@@ -1,8 +1,4 @@
1
1
  import meow from 'meow';
2
- /**
3
- * Help text displayed when the CLI is invoked with `--help` or without arguments.
4
- * Documents all available options, flags, and usage examples.
5
- */
6
2
  export const help = `
7
3
  Usage
8
4
  $ markuplint <HTML file paths (glob format)>
@@ -24,10 +20,10 @@ Options
24
20
  --show-config Output computed configuration of the target file. Supports "details" and empty. Default: empty.
25
21
  --verbose Output with detailed information.
26
22
  --include-node-modules Include files in node_modules directory. Default: false.
27
- --severity-parse-error Specifies the severity level of parse errors. Supports "error", "warning", and "off". Default: "error".
23
+ --severity-parse-error Severity for the built-in parse-error channel. Supports "error", "warning", and "off". Unset by default: fatal ParserErrors emit at "error" and non-fatal parse5 events are off (opt-in per code via config).
28
24
  --max-count Limit the number of violations shown. Default: 0 (no limit).
29
25
  --max-warnings Number of warnings to trigger nonzero exit code. Default: -1 (no limit).
30
- --progressive-output Output results immediately after processing each file. Default: false.
26
+ --no-progressive-output Wait until every file is processed before outputting results. Default: false (output progressively).
31
27
 
32
28
  --suppress [Experimental] Generate/update suppressions file for all current errors.
33
29
  --suppress-rule RULE_ID [Experimental] Suppress only the specified rule.
@@ -44,10 +40,6 @@ Examples
44
40
  $ markuplint verifyee.html --config path/to/.markuplintrc
45
41
  $ cat verifyee.html | markuplint
46
42
  `;
47
- /**
48
- * The parsed CLI instance created by `meow`, providing access to
49
- * positional arguments (`cli.input`) and parsed flags (`cli.flags`).
50
- */
51
43
  export const cli = meow(help, {
52
44
  importMeta: import.meta,
53
45
  flags: {
@@ -123,7 +115,6 @@ export const cli = meow(help, {
123
115
  },
124
116
  severityParseError: {
125
117
  type: 'string',
126
- default: 'error',
127
118
  },
128
119
  maxCount: {
129
120
  type: 'number',
@@ -135,8 +126,7 @@ export const cli = meow(help, {
135
126
  },
136
127
  progressiveOutput: {
137
128
  type: 'boolean',
138
- // TODO: It will be changed to `true` in the next major version.
139
- default: false,
129
+ default: true,
140
130
  },
141
131
  suppress: {
142
132
  type: 'boolean',
@@ -1,16 +1,4 @@
1
1
  import type { CLIOptions } from './bootstrap.js';
2
2
  import type { APIOptions } from '../api/types.js';
3
3
  import type { Target } from '@markuplint/file-resolver';
4
- /**
5
- * Executes the markuplint linting command against the given files.
6
- *
7
- * Resolves file targets, creates an {@link MLEngine} for each file, collects
8
- * violations, and outputs results in the requested format. When the `--fix`
9
- * flag is set, overwrites files with their auto-fixed content.
10
- *
11
- * @param files - The list of file targets (paths or inline source code) to lint.
12
- * @param options - CLI options controlling output format, fix mode, locale, and other behaviors.
13
- * @param apiOptions - Optional overrides for the underlying API (e.g., custom rules or config).
14
- * @returns `true` if any errors were found (or warnings exceeded the limit), `false` otherwise.
15
- */
16
4
  export declare function command(files: readonly Readonly<Target>[], options: CLIOptions, apiOptions?: APIOptions): Promise<boolean>;
@@ -8,18 +8,6 @@ import { log } from '../debug.js';
8
8
  import { applySuppressions, generateSuppressions, mergeSuppressions, pruneSuppressions, readSuppressionsFile, resolveSuppressionsPath, writeSuppressionsFile, } from '../suppressions/index.js';
9
9
  import { outputDryRunDiff } from './dry-run-output.js';
10
10
  import { output, outputSummary } from './output.js';
11
- /**
12
- * Executes the markuplint linting command against the given files.
13
- *
14
- * Resolves file targets, creates an {@link MLEngine} for each file, collects
15
- * violations, and outputs results in the requested format. When the `--fix`
16
- * flag is set, overwrites files with their auto-fixed content.
17
- *
18
- * @param files - The list of file targets (paths or inline source code) to lint.
19
- * @param options - CLI options controlling output format, fix mode, locale, and other behaviors.
20
- * @param apiOptions - Optional overrides for the underlying API (e.g., custom rules or config).
21
- * @returns `true` if any errors were found (or warnings exceeded the limit), `false` otherwise.
22
- */
23
11
  export async function command(files, options, apiOptions) {
24
12
  const fixDryRun = options.fixDryRun;
25
13
  if (options.fix && fixDryRun) {
@@ -30,9 +18,6 @@ export async function command(files, options, apiOptions) {
30
18
  // Mutual exclusion checks for suppressions flags
31
19
  const isSuppressMode = options.suppress || options.suppressRule != null;
32
20
  const isPruneMode = options.pruneSuppressions;
33
- if (isSuppressMode && fix) {
34
- process.stderr.write('Warning: --suppress counts violations from the original code, not from the fixed result. Consider running --fix first, then --suppress.\n');
35
- }
36
21
  if (isSuppressMode && isPruneMode) {
37
22
  process.stderr.write('Error: --suppress/--suppress-rule and --prune-suppressions cannot be used together.\n');
38
23
  return true;
@@ -62,14 +47,43 @@ export async function command(files, options, apiOptions) {
62
47
  const collector = new ViolationCollector(options.maxCount);
63
48
  const processedFiles = [];
64
49
  const skippedFiles = [];
50
+ // `config-error` violations (deprecated rule names, unresolved rule
51
+ // references, ...) come from resolving this run's config, so the same
52
+ // message is identical across every file. Track messages already
53
+ // reported once so a run over many files doesn't repeat the same lines
54
+ // per file — the config is one thing, not N things.
55
+ const seenConfigMessages = new Set();
65
56
  const filesContent = new Map();
66
57
  const engines = new Map();
67
- const severityParseError = options.severityParseError.toLowerCase();
68
- const severity = {
69
- parseError: ['error', 'warning', 'off'].includes(severityParseError)
70
- ? severityParseError
71
- : true,
72
- };
58
+ const severityParseError = options.severityParseError?.toLowerCase();
59
+ const severity = severityParseError != null && ['error', 'warning', 'off'].includes(severityParseError)
60
+ ? { parseError: severityParseError }
61
+ : {};
62
+ // Progressive output prints each file's own violations as soon as that
63
+ // file is processed, ahead of the two whole-run passes that batch output
64
+ // waits for: suppressions (applied once at the end, below, via
65
+ // `applySuppressions`, since scope resolution and the "unused entry"
66
+ // report need the complete result set) and `--max-count` truncation
67
+ // (enforced by `collector.pushWithFile`'s running total across files,
68
+ // including which file the limit is hit within and which later files are
69
+ // skipped entirely). Printing per file ahead of either pass would show
70
+ // violations, or omit a skipped-file notice, that the run's own summary
71
+ // and exit code then contradict. Rather than duplicate that truncation
72
+ // and suppression logic in the progressive branch, fall back to batch
73
+ // output for the whole run whenever either is in play; --suppress and
74
+ // --prune-suppressions manage the suppressions file directly and are
75
+ // unaffected.
76
+ let progressiveOutput = options.progressiveOutput;
77
+ if (progressiveOutput && options.maxCount > 0) {
78
+ progressiveOutput = false;
79
+ }
80
+ if (progressiveOutput && !isSuppressMode && !isPruneMode) {
81
+ const suppressionsFilePathForCheck = resolveSuppressionsPath(options.suppressionsLocation);
82
+ const existingSuppressions = await readSuppressionsFile(suppressionsFilePathForCheck);
83
+ if (Object.keys(existingSuppressions).length > 0) {
84
+ progressiveOutput = false;
85
+ }
86
+ }
73
87
  for (const file of fileList) {
74
88
  // Check if collector is already locked (max-count reached)
75
89
  if (collector.isLocked()) {
@@ -121,11 +135,33 @@ export async function command(files, options, apiOptions) {
121
135
  });
122
136
  // Store engine for scope computation in suppressions
123
137
  engines.set(result.filePath, engine);
138
+ // In fix mode, report the violations remaining in the FIXED code
139
+ // (re-verified by ml-core) instead of the pre-fix violations, so that
140
+ // the exit code and suppressions reflect the written output.
141
+ // With --fix-dry-run the file is NOT modified, so keep the first-pass
142
+ // violations, which match the file on disk.
143
+ const violationsBeforeDedupe = fixDryRun
144
+ ? result.violations
145
+ : (result.fixSummary?.finalPassViolations ?? result.violations);
146
+ // `config-error` is regenerated fresh per file (config resolution is
147
+ // not shared across a run), so an identical message would otherwise
148
+ // repeat once per file. Keep only the first occurrence across this run.
149
+ const reportedViolations = violationsBeforeDedupe.filter(violation => {
150
+ if (violation.ruleId !== 'config-error') {
151
+ return true;
152
+ }
153
+ const key = `${violation.ruleId} ${violation.message}`;
154
+ if (seenConfigMessages.has(key)) {
155
+ return false;
156
+ }
157
+ seenConfigMessages.add(key);
158
+ return true;
159
+ });
124
160
  // Progressive出力が有効でJSON形式でない場合
125
- if (options.progressiveOutput && format !== 'json') {
161
+ if (progressiveOutput && format !== 'json') {
126
162
  // 即座に出力
127
163
  output({
128
- violations: result.violations,
164
+ violations: reportedViolations,
129
165
  filePath: result.filePath,
130
166
  sourceCode: result.sourceCode,
131
167
  fixedCode: result.fixedCode,
@@ -133,9 +169,9 @@ export async function command(files, options, apiOptions) {
133
169
  }, options);
134
170
  }
135
171
  // Add violations to collector
136
- collector.pushWithFile(result.filePath, ...result.violations);
137
- const errorCount = result.violations.filter(v => v.severity === 'error').length;
138
- const warningCount = result.violations.filter(v => v.severity === 'warning').length;
172
+ collector.pushWithFile(result.filePath, ...reportedViolations);
173
+ const errorCount = reportedViolations.filter(v => v.severity === 'error').length;
174
+ const warningCount = reportedViolations.filter(v => v.severity === 'warning').length;
139
175
  // Track total warning count across all files
140
176
  totalWarningCount += warningCount;
141
177
  if (!hasError && (errorCount > 0 || (warningCount > 0 && !options.allowWarnings))) {
@@ -250,7 +286,7 @@ export async function command(files, options, apiOptions) {
250
286
  return false;
251
287
  }
252
288
  // Progressive出力が無効の場合のみループ後に出力
253
- if (!options.progressiveOutput) {
289
+ if (!progressiveOutput) {
254
290
  // Output per file - include processed files without violations
255
291
  for (const filePath of processedFiles) {
256
292
  const violations = outputViolationsByFile.get(filePath) || [];
@@ -290,7 +326,10 @@ export async function command(files, options, apiOptions) {
290
326
  let failedFileCount = 0;
291
327
  for (const filePath of processedFiles) {
292
328
  const violations = outputViolationsByFile.get(filePath) || [];
293
- if (violations.length > 0) {
329
+ // A file whose only violations are `config-error` didn't fail on its
330
+ // own content — the config issue is reported once for the whole run
331
+ // (see the dedupe above), not attributable to this particular file.
332
+ if (violations.some(violation => violation.ruleId !== 'config-error')) {
294
333
  failedFileCount++;
295
334
  }
296
335
  for (const violation of violations) {
@@ -1,9 +1 @@
1
- /**
2
- * Outputs a unified diff showing what --fix would change.
3
- * Writes the diff to stdout if there are differences.
4
- *
5
- * @param filePath - File path for the diff header
6
- * @param original - Original source code
7
- * @param fixed - Fixed source code after applying fixes
8
- */
9
1
  export declare function outputDryRunDiff(filePath: string, original: string, fixed: string): void;
@@ -1,12 +1,4 @@
1
1
  import { unifiedDiff } from '@markuplint/cli-utils';
2
- /**
3
- * Outputs a unified diff showing what --fix would change.
4
- * Writes the diff to stdout if there are differences.
5
- *
6
- * @param filePath - File path for the diff header
7
- * @param original - Original source code
8
- * @param fixed - Fixed source code after applying fixes
9
- */
10
2
  export function outputDryRunDiff(filePath, original, fixed) {
11
3
  const diff = unifiedDiff(filePath, original, fixed);
12
4
  if (diff) {
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * @module cli
3
3
  *
4
- * CLI entry point for markuplint.
5
- * Parses command-line arguments, dispatches to the appropriate handler
6
- * (lint, init, search, or help), and manages the process exit code.
4
+ * Convention: all error reporting in the CLI layer must go through
5
+ * `process.stderr.write()` — never `console.warn`/`console.error`/
6
+ * `console.log` — so stdout stays reserved for lint results
7
+ * (e.g. `--format JSON` output that may be piped to other tools).
7
8
  */
8
9
  export {};
package/lib/cli/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * @module cli
3
3
  *
4
- * CLI entry point for markuplint.
5
- * Parses command-line arguments, dispatches to the appropriate handler
6
- * (lint, init, search, or help), and manages the process exit code.
4
+ * Convention: all error reporting in the CLI layer must go through
5
+ * `process.stderr.write()` — never `console.warn`/`console.error`/
6
+ * `console.log` — so stdout stays reserved for lint results
7
+ * (e.g. `--format JSON` output that may be piped to other tools).
7
8
  */
8
9
  import { text } from 'node:stream/consumers';
9
10
  import { verbosely } from '../debug.js';
@@ -1,20 +1,4 @@
1
1
  import type { DefaultRules, Langs, RuleSettingMode } from './types.js';
2
2
  import type { Config } from '@markuplint/ml-config';
3
- /**
4
- * Human-readable display names for each supported template language/framework,
5
- * shown in the interactive init wizard prompts.
6
- */
7
3
  export declare const langs: Record<Langs, string>;
8
- /**
9
- * Builds a markuplint configuration object based on the user's init wizard selections.
10
- *
11
- * Configures parsers and spec modules for the selected template languages,
12
- * and populates rules based on the chosen rule-setting mode (custom categories,
13
- * recommended preset, or all defaults).
14
- *
15
- * @param langs - The template languages/frameworks selected by the user.
16
- * @param mode - The rule selection mode: an array of categories, `'recommended'`, or `'none'`.
17
- * @param defaultRules - The full set of available default rules with their categories and values.
18
- * @returns A complete markuplint `Config` object ready to be serialized to a file.
19
- */
20
4
  export declare function createConfig(langs: readonly Langs[], mode: RuleSettingMode, defaultRules: DefaultRules): Config;
@@ -1,7 +1,3 @@
1
- /**
2
- * Maps each supported language/framework to a file-extension regular expression
3
- * used in the generated configuration's `parser` field.
4
- */
5
1
  const extRExp = {
6
2
  jsx: '\\.[jt]sx?$',
7
3
  vue: '\\.vue$',
@@ -18,10 +14,6 @@ const extRExp = {
18
14
  nunjucks: '\\.nunjucks$',
19
15
  liquid: '\\.liquid$',
20
16
  };
21
- /**
22
- * Human-readable display names for each supported template language/framework,
23
- * shown in the interactive init wizard prompts.
24
- */
25
17
  export const langs = {
26
18
  jsx: 'React (JSX)',
27
19
  vue: 'Vue',
@@ -38,18 +30,6 @@ export const langs = {
38
30
  nunjucks: 'Nunjucks',
39
31
  liquid: 'liquid (Shopify)',
40
32
  };
41
- /**
42
- * Builds a markuplint configuration object based on the user's init wizard selections.
43
- *
44
- * Configures parsers and spec modules for the selected template languages,
45
- * and populates rules based on the chosen rule-setting mode (custom categories,
46
- * recommended preset, or all defaults).
47
- *
48
- * @param langs - The template languages/frameworks selected by the user.
49
- * @param mode - The rule selection mode: an array of categories, `'recommended'`, or `'none'`.
50
- * @param defaultRules - The full set of available default rules with their categories and values.
51
- * @returns A complete markuplint `Config` object ready to be serialized to a file.
52
- */
53
33
  export function createConfig(langs, mode, defaultRules) {
54
34
  let config = {};
55
35
  const parser = { ...config.parser };
@@ -1,12 +1,3 @@
1
- /**
2
- * Collects all built-in rules that have a defined category and returns them
3
- * as a record of rule name to rule metadata.
4
- *
5
- * Rules with `'warning'` default severity are disabled by default (`false`),
6
- * while all others use their defined default value or `true`.
7
- *
8
- * @returns A read-only mapping from rule names to their category and default configuration value.
9
- */
10
1
  export declare function getDefaultRules(): {
11
2
  [x: string]: import("./types.js").Rule;
12
3
  };
@@ -1,13 +1,4 @@
1
1
  import builtinRules from '@markuplint/rules';
2
- /**
3
- * Collects all built-in rules that have a defined category and returns them
4
- * as a record of rule name to rule metadata.
5
- *
6
- * Rules with `'warning'` default severity are disabled by default (`false`),
7
- * while all others use their defined default value or `true`.
8
- *
9
- * @returns A read-only mapping from rule names to their category and default configuration value.
10
- */
11
2
  export function getDefaultRules() {
12
3
  const rules = {};
13
4
  for (const [ruleName, rule] of Object.entries(builtinRules)) {
@@ -1,15 +1 @@
1
- /**
2
- * @module cli/init
3
- *
4
- * Interactive initialization wizard for markuplint.
5
- * Guides the user through selecting template engines, rule categories,
6
- * and dependency installation, then writes a `.markuplintrc` config file.
7
- */
8
- /**
9
- * Runs the interactive initialization flow.
10
- *
11
- * Prompts the user to select template engines, choose rule categories or the
12
- * recommended preset, generates a `.markuplintrc` configuration file in the
13
- * current working directory, and optionally installs the required npm packages.
14
- */
15
1
  export declare function initialize(): Promise<void>;