markuplint 5.1.0 → 5.1.1
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/CHANGELOG.md +6 -0
- package/lib/api/ml-engine.js +110 -17
- package/package.json +13 -13
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
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.1.1](https://github.com/markuplint/markuplint/compare/v5.1.0...v5.1.1) (2026-10-09)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
- **markuplint:** fail a file whose config cannot be loaded instead of passing it ([4579d89](https://github.com/markuplint/markuplint/commit/4579d890e4b3e18f34fbe91f8b8d7f59ca886e92))
|
|
11
|
+
|
|
6
12
|
# [5.1.0](https://github.com/markuplint/markuplint/compare/v5.0.1...v5.1.0) (2026-10-08)
|
|
7
13
|
|
|
8
14
|
### Bug Fixes
|
package/lib/api/ml-engine.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { ConfigProvider, createPretenderResolver, disambiguatePretendersForFile, invalidatePretenderResolutionCaches, resolveFiles, resolveParser, resolveRules, resolveSpecs, } from '@markuplint/file-resolver';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { applyRuleAliasesToConfig, mergeConfig } from '@markuplint/ml-config';
|
|
4
|
-
import { MLCore, convertRuleset } from '@markuplint/ml-core';
|
|
4
|
+
import { CONFIG_ERROR_RULE_ID, MLCore, convertRuleset } from '@markuplint/ml-core';
|
|
5
5
|
import { ruleAliasTable } from '@markuplint/rules';
|
|
6
|
-
import { isFatalError } from '@markuplint/shared';
|
|
6
|
+
import { ConfigLoadError, isFatalError } from '@markuplint/shared';
|
|
7
7
|
import { Emitter } from 'strict-event-emitter';
|
|
8
8
|
import { log as coreLog, verbosely } from '../debug.js';
|
|
9
9
|
import { i18n } from '../i18n.js';
|
|
@@ -71,6 +71,15 @@ export class MLEngine extends Emitter {
|
|
|
71
71
|
return files[0];
|
|
72
72
|
}
|
|
73
73
|
#configProvider;
|
|
74
|
+
/**
|
|
75
|
+
* Why the latest config resolution left nothing to lint with: a config that
|
|
76
|
+
* cannot be loaded (Tier 2). `null` while the latest resolution succeeded.
|
|
77
|
+
*
|
|
78
|
+
* Set by `#provide` and reported by `#reportConfigFailure` as one
|
|
79
|
+
* error-severity `config-error` for the file. It is not folded into an empty
|
|
80
|
+
* config plus a warning: a file that no rule has looked at must not pass.
|
|
81
|
+
*/
|
|
82
|
+
#configFailure = null;
|
|
74
83
|
#core = null;
|
|
75
84
|
#file;
|
|
76
85
|
#options;
|
|
@@ -168,6 +177,10 @@ export class MLEngine extends Emitter {
|
|
|
168
177
|
log('exec: start');
|
|
169
178
|
const core = await this.#setup();
|
|
170
179
|
if (!core) {
|
|
180
|
+
if (this.#configFailure) {
|
|
181
|
+
log('exec: config cannot be loaded');
|
|
182
|
+
return this.#reportConfigFailure(this.#configFailure);
|
|
183
|
+
}
|
|
171
184
|
log('exec: cancel (unsetuped yet)');
|
|
172
185
|
return null;
|
|
173
186
|
}
|
|
@@ -394,14 +407,32 @@ export class MLEngine extends Emitter {
|
|
|
394
407
|
return;
|
|
395
408
|
}
|
|
396
409
|
const fabric = await this.#provide(false);
|
|
397
|
-
if (!fabric
|
|
410
|
+
if (!fabric) {
|
|
411
|
+
if (this.#configFailure) {
|
|
412
|
+
// The core holds the config that no longer loads. Dropping it makes the
|
|
413
|
+
// next `exec()` / `setCode()` resolve again instead of linting on it.
|
|
414
|
+
this.#core = null;
|
|
415
|
+
if (this.#options?.watch) {
|
|
416
|
+
await this.#reportConfigFailure(this.#configFailure);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
if (!this.#options?.watch) {
|
|
398
422
|
return;
|
|
399
423
|
}
|
|
400
424
|
if (fabric.configErrors) {
|
|
401
425
|
this.emit('config-errors', this.#file.path, fabric.configErrors);
|
|
402
426
|
}
|
|
403
427
|
this.emit('log', 'update:core', this.#file.path);
|
|
404
|
-
this.#core
|
|
428
|
+
if (this.#core) {
|
|
429
|
+
this.#core.update(fabric);
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
// No core yet — the config did not load until now. `#exec()` would
|
|
433
|
+
// resolve once more through `#setup()`, for the fabric just resolved.
|
|
434
|
+
await this.#createCore(fabric);
|
|
435
|
+
}
|
|
405
436
|
await this.#exec();
|
|
406
437
|
}
|
|
407
438
|
#enqueue(task) {
|
|
@@ -464,32 +495,42 @@ export class MLEngine extends Emitter {
|
|
|
464
495
|
this.#pretenderDependencies = dependencies;
|
|
465
496
|
await this.#syncWatch();
|
|
466
497
|
}
|
|
498
|
+
/**
|
|
499
|
+
* Resolves everything a lint of the file needs and returns it, or `null`
|
|
500
|
+
* when there is nothing to lint with: the file is missing, excluded, or
|
|
501
|
+
* unmatched by the extension — or its config cannot be loaded, which
|
|
502
|
+
* `#configFailure` records.
|
|
503
|
+
*
|
|
504
|
+
* A config that cannot be loaded is a Tier 2 failure (see the module JSDoc
|
|
505
|
+
* of `@markuplint/shared`'s `errors/index.ts`): any non-fatal error thrown by
|
|
506
|
+
* the resolution, or a `ConfigLoadError` collected into the config set's
|
|
507
|
+
* `errs`, fails the file. Tier 1 errors — markuplint's own bugs
|
|
508
|
+
* (`isFatalError()`) — are not caught here. The other `errs` (a rule or a
|
|
509
|
+
* plugin that is not found, an invalid selector, a circular `extends`)
|
|
510
|
+
* leave the rest of the config usable, so they stay `config-error` warnings
|
|
511
|
+
* that `MLCore.verify()` reports.
|
|
512
|
+
*/
|
|
467
513
|
async #provide(cache = true) {
|
|
514
|
+
this.#configFailure = null;
|
|
468
515
|
let configSet;
|
|
469
516
|
try {
|
|
470
517
|
configSet = await this.resolveConfig(cache);
|
|
471
518
|
}
|
|
472
519
|
catch (error) {
|
|
473
|
-
if (error instanceof Error) {
|
|
474
|
-
configSet = {
|
|
475
|
-
config: {},
|
|
476
|
-
plugins: [],
|
|
477
|
-
files: new Set(),
|
|
478
|
-
errs: [error],
|
|
479
|
-
ruleDeprecations: [],
|
|
480
|
-
};
|
|
481
|
-
}
|
|
482
|
-
else {
|
|
520
|
+
if (isFatalError(error) || !(error instanceof Error)) {
|
|
483
521
|
throw error;
|
|
484
522
|
}
|
|
523
|
+
return this.#failConfig(error);
|
|
485
524
|
}
|
|
486
525
|
fileLog('Fetched Config files: %O', configSet.files);
|
|
487
526
|
fileLog('Resolved Config: %O', configSet.config);
|
|
488
527
|
fileLog('Resolved Plugins: %O', configSet.plugins);
|
|
489
528
|
fileLog('Resolve Errors: %O', configSet.errs);
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
529
|
+
const loadError = configSet.errs.find(error => error instanceof ConfigLoadError);
|
|
530
|
+
if (loadError) {
|
|
531
|
+
return this.#failConfig(loadError);
|
|
532
|
+
}
|
|
533
|
+
if (!(await this.#isTargetFile())) {
|
|
493
534
|
return null;
|
|
494
535
|
}
|
|
495
536
|
// Exclude
|
|
@@ -698,6 +739,58 @@ export class MLEngine extends Emitter {
|
|
|
698
739
|
this.emit('schemas', this.#file.path, schemas);
|
|
699
740
|
return schemas;
|
|
700
741
|
}
|
|
742
|
+
async #isTargetFile() {
|
|
743
|
+
if (await this.#file.isFile()) {
|
|
744
|
+
return true;
|
|
745
|
+
}
|
|
746
|
+
this.emit('log', 'file-no-exists', `The file doesn't exist or it is not a file: ${this.#file.path}`);
|
|
747
|
+
fileLog("The file doesn't exist or it is not a file: %s", this.#file.path);
|
|
748
|
+
return false;
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Records that the config cannot be loaded, unless the file is missing too,
|
|
752
|
+
* which is the more basic reason to skip it. Always resolves to `null`: the
|
|
753
|
+
* "nothing to lint with" answer of `#provide`.
|
|
754
|
+
*/
|
|
755
|
+
async #failConfig(error) {
|
|
756
|
+
fileLog('Config cannot be loaded: %O', error);
|
|
757
|
+
if (await this.#isTargetFile()) {
|
|
758
|
+
this.#configFailure = error;
|
|
759
|
+
}
|
|
760
|
+
return null;
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* Reports a config that cannot be loaded as the file's only violation, at
|
|
764
|
+
* error severity, so the run fails instead of passing a file no rule has
|
|
765
|
+
* checked.
|
|
766
|
+
*
|
|
767
|
+
* Emits `lint` as well as `lint-error`: an editor shows its diagnostics from
|
|
768
|
+
* `lint`, and a config that stops loading while it is being edited must show
|
|
769
|
+
* up there. See `#exec` for the `lint-error` conversion of a failed
|
|
770
|
+
* `verify()`, which has no violations to publish.
|
|
771
|
+
*/
|
|
772
|
+
async #reportConfigFailure(error) {
|
|
773
|
+
const sourceCode = await this.#file.getCode();
|
|
774
|
+
const violations = [
|
|
775
|
+
{
|
|
776
|
+
ruleId: CONFIG_ERROR_RULE_ID,
|
|
777
|
+
severity: 'error',
|
|
778
|
+
message: error.message,
|
|
779
|
+
line: 1,
|
|
780
|
+
col: 1,
|
|
781
|
+
raw: '',
|
|
782
|
+
},
|
|
783
|
+
];
|
|
784
|
+
this.emit('lint-error', this.#file.path, sourceCode, error);
|
|
785
|
+
this.emit('lint', this.#file.path, sourceCode, violations, sourceCode, null, null);
|
|
786
|
+
return {
|
|
787
|
+
violations,
|
|
788
|
+
filePath: this.#file.path,
|
|
789
|
+
sourceCode,
|
|
790
|
+
fixedCode: sourceCode,
|
|
791
|
+
status: 'processed',
|
|
792
|
+
};
|
|
793
|
+
}
|
|
701
794
|
async #setup() {
|
|
702
795
|
if (this.#core) {
|
|
703
796
|
return this.#core;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "markuplint",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.1",
|
|
4
4
|
"description": "An HTML linter for all markup developers",
|
|
5
5
|
"author": "Yusuke Hirao",
|
|
6
6
|
"license": "MIT",
|
|
@@ -35,17 +35,17 @@
|
|
|
35
35
|
"clean": "tsc --build --clean tsconfig.build.json"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@markuplint/cli-utils": "5.1.
|
|
39
|
-
"@markuplint/file-resolver": "5.1.
|
|
40
|
-
"@markuplint/html-parser": "5.1.
|
|
41
|
-
"@markuplint/html-spec": "5.1.
|
|
42
|
-
"@markuplint/i18n": "5.1.
|
|
43
|
-
"@markuplint/ml-ast": "5.1.
|
|
44
|
-
"@markuplint/ml-config": "5.1.
|
|
45
|
-
"@markuplint/ml-core": "5.1.
|
|
46
|
-
"@markuplint/ml-spec": "5.1.
|
|
47
|
-
"@markuplint/rules": "5.1.
|
|
48
|
-
"@markuplint/shared": "5.1.
|
|
38
|
+
"@markuplint/cli-utils": "5.1.1",
|
|
39
|
+
"@markuplint/file-resolver": "5.1.1",
|
|
40
|
+
"@markuplint/html-parser": "5.1.1",
|
|
41
|
+
"@markuplint/html-spec": "5.1.1",
|
|
42
|
+
"@markuplint/i18n": "5.1.1",
|
|
43
|
+
"@markuplint/ml-ast": "5.1.1",
|
|
44
|
+
"@markuplint/ml-config": "5.1.1",
|
|
45
|
+
"@markuplint/ml-core": "5.1.1",
|
|
46
|
+
"@markuplint/ml-spec": "5.1.1",
|
|
47
|
+
"@markuplint/rules": "5.1.1",
|
|
48
|
+
"@markuplint/shared": "5.1.1",
|
|
49
49
|
"@types/debug": "4.1.13",
|
|
50
50
|
"chokidar": "5.0.0",
|
|
51
51
|
"debug": "4.4.3",
|
|
@@ -55,5 +55,5 @@
|
|
|
55
55
|
"strip-ansi": "7.2.0",
|
|
56
56
|
"type-fest": "5.6.0"
|
|
57
57
|
},
|
|
58
|
-
"gitHead": "
|
|
58
|
+
"gitHead": "a940c8526e971d590d97395ce924910221208820"
|
|
59
59
|
}
|