markuplint 5.0.0-dev.5 → 5.0.0-rc.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/ARCHITECTURE.ja.md +1 -1
- package/ARCHITECTURE.md +1 -1
- package/CHANGELOG.md +18 -0
- package/README.md +43 -3
- package/lib/api/ml-engine.js +13 -1
- package/lib/cli/bootstrap.d.ts +15 -1
- package/lib/cli/bootstrap.js +19 -0
- package/lib/cli/command.js +112 -5
- package/lib/suppressions/apply-suppressions.d.ts +45 -0
- package/lib/suppressions/apply-suppressions.js +221 -0
- package/lib/suppressions/compute-scope.d.ts +96 -0
- package/lib/suppressions/compute-scope.js +244 -0
- package/lib/suppressions/generate-suppressions.d.ts +32 -0
- package/lib/suppressions/generate-suppressions.js +55 -0
- package/lib/suppressions/index.d.ts +20 -0
- package/lib/suppressions/index.js +16 -0
- package/lib/suppressions/merge-suppressions.d.ts +11 -0
- package/lib/suppressions/merge-suppressions.js +31 -0
- package/lib/suppressions/prune-suppressions.d.ts +22 -0
- package/lib/suppressions/prune-suppressions.js +64 -0
- package/lib/suppressions/suppressions-file.d.ts +46 -0
- package/lib/suppressions/suppressions-file.js +108 -0
- package/lib/suppressions/types.d.ts +26 -0
- package/lib/suppressions/types.js +1 -0
- package/package.json +15 -15
- package/lib/api/v1.d.ts +0 -57
- package/lib/api/v1.js +0 -41
- package/lib/v1.d.ts +0 -6
- package/lib/v1.js +0 -6
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { isFatalError } from '@markuplint/shared';
|
|
4
|
+
const DEFAULT_FILE_NAME = 'markuplint-suppressions.json';
|
|
5
|
+
/**
|
|
6
|
+
* @experimental
|
|
7
|
+
* Resolves the absolute path to the suppressions file.
|
|
8
|
+
*
|
|
9
|
+
* @param customPath - Optional custom path (relative or absolute). Defaults to `markuplint-suppressions.json` in CWD.
|
|
10
|
+
* @returns The absolute file path.
|
|
11
|
+
*/
|
|
12
|
+
export function resolveSuppressionsPath(customPath) {
|
|
13
|
+
if (customPath) {
|
|
14
|
+
return path.isAbsolute(customPath) ? customPath : path.resolve(process.cwd(), customPath);
|
|
15
|
+
}
|
|
16
|
+
return path.resolve(process.cwd(), DEFAULT_FILE_NAME);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* @experimental
|
|
20
|
+
* Reads and parses a suppressions JSON file.
|
|
21
|
+
* Returns an empty object if the file does not exist.
|
|
22
|
+
*
|
|
23
|
+
* @param filePath - Absolute path to the suppressions file.
|
|
24
|
+
* @returns The parsed suppressions data, or an empty object if the file does not exist.
|
|
25
|
+
*/
|
|
26
|
+
export async function readSuppressionsFile(filePath) {
|
|
27
|
+
try {
|
|
28
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
29
|
+
return JSON.parse(content);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
if (error instanceof SyntaxError) {
|
|
36
|
+
// User may have manually edited and broken the JSON — Tier 2, not Fatal
|
|
37
|
+
throw new Error(`Failed to parse suppressions file "${filePath}": ${error.message}`, { cause: error });
|
|
38
|
+
}
|
|
39
|
+
if (isFatalError(error)) {
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* @experimental
|
|
47
|
+
* Writes suppressions data to a JSON file with sorted keys.
|
|
48
|
+
* Deletes the file if data is empty.
|
|
49
|
+
*
|
|
50
|
+
* @param filePath - Absolute path to the suppressions file.
|
|
51
|
+
* @param data - The suppressions data to write.
|
|
52
|
+
*/
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
54
|
+
export async function writeSuppressionsFile(filePath, data) {
|
|
55
|
+
// If empty, delete the file rather than leaving an empty JSON object.
|
|
56
|
+
// This avoids committing a useless file to the repository.
|
|
57
|
+
if (Object.keys(data).length === 0) {
|
|
58
|
+
try {
|
|
59
|
+
await fs.unlink(filePath);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (isFatalError(error)) {
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
// ENOENT is expected when the file doesn't already exist
|
|
66
|
+
}
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
// Sort top-level keys and nested keys
|
|
70
|
+
const sorted = {};
|
|
71
|
+
for (const fileKey of Object.keys(data).toSorted()) {
|
|
72
|
+
const rules = data[fileKey];
|
|
73
|
+
const sortedRules = {};
|
|
74
|
+
for (const ruleKey of Object.keys(rules).toSorted()) {
|
|
75
|
+
sortedRules[ruleKey] = rules[ruleKey];
|
|
76
|
+
}
|
|
77
|
+
sorted[fileKey] = sortedRules;
|
|
78
|
+
}
|
|
79
|
+
const content = JSON.stringify(sorted, null, '\t') + '\n';
|
|
80
|
+
await fs.writeFile(filePath, content, 'utf8');
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* @experimental
|
|
84
|
+
* Converts an absolute file path to a relative path based on the suppressions file location.
|
|
85
|
+
* Always uses POSIX separators for cross-platform consistency.
|
|
86
|
+
*
|
|
87
|
+
* @param absoluteFilePath - The absolute path to the linted file.
|
|
88
|
+
* @param suppressionsFilePath - The absolute path to the suppressions file.
|
|
89
|
+
* @returns The relative path using POSIX separators.
|
|
90
|
+
*/
|
|
91
|
+
export function toRelativePath(absoluteFilePath, suppressionsFilePath) {
|
|
92
|
+
const dir = path.dirname(suppressionsFilePath);
|
|
93
|
+
const rel = path.relative(dir, absoluteFilePath);
|
|
94
|
+
// Normalize to POSIX separators
|
|
95
|
+
return rel.split(path.sep).join('/');
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* @experimental
|
|
99
|
+
* Converts a relative path (from the suppressions file) back to an absolute path.
|
|
100
|
+
*
|
|
101
|
+
* @param relativeFilePath - The relative path stored in the suppressions file.
|
|
102
|
+
* @param suppressionsFilePath - The absolute path to the suppressions file.
|
|
103
|
+
* @returns The absolute file path.
|
|
104
|
+
*/
|
|
105
|
+
export function toAbsolutePath(relativeFilePath, suppressionsFilePath) {
|
|
106
|
+
const dir = path.dirname(suppressionsFilePath);
|
|
107
|
+
return path.resolve(dir, relativeFilePath);
|
|
108
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @experimental
|
|
3
|
+
* A single suppression entry representing the count of suppressed violations
|
|
4
|
+
* for a specific rule in a specific file.
|
|
5
|
+
*/
|
|
6
|
+
export type SuppressionEntry = {
|
|
7
|
+
readonly count: number;
|
|
8
|
+
/**
|
|
9
|
+
* @experimental
|
|
10
|
+
* CSS selector identifying the LCA (Lowest Common Ancestor) subtree
|
|
11
|
+
* that contains all violations for this rule. Uses id/class/attr for
|
|
12
|
+
* precise matching. When absent, suppression applies to the entire file.
|
|
13
|
+
*/
|
|
14
|
+
readonly scope?: string;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* @experimental
|
|
18
|
+
* The full suppressions data structure.
|
|
19
|
+
* Top-level keys are relative file paths, values are maps of ruleId to suppression entry.
|
|
20
|
+
*
|
|
21
|
+
* **Format note:** The flat structure `{ filePath: { ruleId: { count, scope? } } }`
|
|
22
|
+
* has no version envelope. The `scope` field is additive (Phase 2) — existing files
|
|
23
|
+
* without `scope` remain compatible as file-level suppressions. Should a breaking
|
|
24
|
+
* format change ever be needed, introduce a `{ version: N, ... }` wrapper and migrate.
|
|
25
|
+
*/
|
|
26
|
+
export type SuppressionsData = Record<string, Record<string, SuppressionEntry>>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "markuplint",
|
|
3
|
-
"version": "5.0.0-
|
|
3
|
+
"version": "5.0.0-rc.1",
|
|
4
4
|
"description": "An HTML linter for all markup developers",
|
|
5
5
|
"author": "Yusuke Hirao",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,25 +26,25 @@
|
|
|
26
26
|
"clean": "tsc --build --clean tsconfig.build.json"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@markuplint/cli-utils": "5.0.0-
|
|
30
|
-
"@markuplint/file-resolver": "5.0.0-
|
|
31
|
-
"@markuplint/html-parser": "5.0.0-
|
|
32
|
-
"@markuplint/html-spec": "5.0.0-
|
|
33
|
-
"@markuplint/i18n": "5.0.0-
|
|
34
|
-
"@markuplint/ml-ast": "5.0.0-
|
|
35
|
-
"@markuplint/ml-config": "5.0.0-
|
|
36
|
-
"@markuplint/ml-core": "5.0.0-
|
|
37
|
-
"@markuplint/ml-spec": "5.0.0-
|
|
38
|
-
"@markuplint/rules": "5.0.0-
|
|
39
|
-
"@markuplint/shared": "5.0.0-
|
|
40
|
-
"@types/debug": "4.1.
|
|
29
|
+
"@markuplint/cli-utils": "5.0.0-rc.1",
|
|
30
|
+
"@markuplint/file-resolver": "5.0.0-rc.1",
|
|
31
|
+
"@markuplint/html-parser": "5.0.0-rc.1",
|
|
32
|
+
"@markuplint/html-spec": "5.0.0-rc.1",
|
|
33
|
+
"@markuplint/i18n": "5.0.0-rc.1",
|
|
34
|
+
"@markuplint/ml-ast": "5.0.0-rc.1",
|
|
35
|
+
"@markuplint/ml-config": "5.0.0-rc.1",
|
|
36
|
+
"@markuplint/ml-core": "5.0.0-rc.1",
|
|
37
|
+
"@markuplint/ml-spec": "5.0.0-rc.1",
|
|
38
|
+
"@markuplint/rules": "5.0.0-rc.1",
|
|
39
|
+
"@markuplint/shared": "5.0.0-rc.1",
|
|
40
|
+
"@types/debug": "4.1.13",
|
|
41
41
|
"chokidar": "5.0.0",
|
|
42
42
|
"debug": "4.4.3",
|
|
43
43
|
"meow": "14.1.0",
|
|
44
44
|
"os-locale": "8.0.0",
|
|
45
45
|
"strict-event-emitter": "0.5.1",
|
|
46
46
|
"strip-ansi": "7.2.0",
|
|
47
|
-
"type-fest": "5.
|
|
47
|
+
"type-fest": "5.5.0"
|
|
48
48
|
},
|
|
49
|
-
"gitHead": "
|
|
49
|
+
"gitHead": "0d6b4324d9a7d6b9e1ba57d4a57e45d36975cba9"
|
|
50
50
|
}
|
package/lib/api/v1.d.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import type { MLResultInfo } from '../types.js';
|
|
2
|
-
import type { Config, PlainData, RuleConfigValue } from '@markuplint/ml-config';
|
|
3
|
-
import type { MLRule } from '@markuplint/ml-core';
|
|
4
|
-
/**
|
|
5
|
-
* Legacy v1 lint function provided for backward compatibility.
|
|
6
|
-
*
|
|
7
|
-
* Translates the v1 option shape into the current `lint` function's parameters
|
|
8
|
-
* and delegates execution to it.
|
|
9
|
-
*
|
|
10
|
-
* @deprecated Use the `lint` function or `MLEngine` class from the current API instead.
|
|
11
|
-
* @param options - The v1-style lint options including file paths, source codes, config, and rules.
|
|
12
|
-
* @returns An array of lint result information objects, one per evaluated file.
|
|
13
|
-
*/
|
|
14
|
-
export declare function lint_v1(options: {
|
|
15
|
-
/**
|
|
16
|
-
* Glob pattern
|
|
17
|
-
*/
|
|
18
|
-
readonly files?: string | readonly string[];
|
|
19
|
-
/**
|
|
20
|
-
* Target source code of evaluation
|
|
21
|
-
*/
|
|
22
|
-
readonly sourceCodes?: string | readonly string[];
|
|
23
|
-
/**
|
|
24
|
-
* File names when `sourceCodes`
|
|
25
|
-
*/
|
|
26
|
-
readonly names?: string | readonly string[];
|
|
27
|
-
/**
|
|
28
|
-
* Workspace path when `sourceCodes`
|
|
29
|
-
*/
|
|
30
|
-
readonly workspace?: string;
|
|
31
|
-
/**
|
|
32
|
-
* Configure file or object
|
|
33
|
-
*/
|
|
34
|
-
readonly config?: string | Config;
|
|
35
|
-
/**
|
|
36
|
-
* The config applied when not resolved from files or set it explicitly.
|
|
37
|
-
*/
|
|
38
|
-
readonly defaultConfig?: Config;
|
|
39
|
-
/**
|
|
40
|
-
* Rules (default: `@markuplint/rules`)
|
|
41
|
-
*/
|
|
42
|
-
readonly rules?: readonly Readonly<MLRule<RuleConfigValue, PlainData>>[];
|
|
43
|
-
/**
|
|
44
|
-
* Auto resolve rules
|
|
45
|
-
*
|
|
46
|
-
* Auto importing form *node_modules* when set `@markuplint/rule-{RULE_NAME}` or `markuplint-rule-{RULE_NAME}` in config rules
|
|
47
|
-
*/
|
|
48
|
-
readonly rulesAutoResolve?: boolean;
|
|
49
|
-
/**
|
|
50
|
-
* Auto fix
|
|
51
|
-
*/
|
|
52
|
-
readonly fix?: boolean;
|
|
53
|
-
/**
|
|
54
|
-
* Locale
|
|
55
|
-
*/
|
|
56
|
-
readonly locale?: string;
|
|
57
|
-
}): Promise<MLResultInfo[]>;
|
package/lib/api/v1.js
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { toNoEmptyStringArrayFromStringOrArray } from '@markuplint/shared';
|
|
2
|
-
import { lint } from './lint.js';
|
|
3
|
-
/**
|
|
4
|
-
* Legacy v1 lint function provided for backward compatibility.
|
|
5
|
-
*
|
|
6
|
-
* Translates the v1 option shape into the current `lint` function's parameters
|
|
7
|
-
* and delegates execution to it.
|
|
8
|
-
*
|
|
9
|
-
* @deprecated Use the `lint` function or `MLEngine` class from the current API instead.
|
|
10
|
-
* @param options - The v1-style lint options including file paths, source codes, config, and rules.
|
|
11
|
-
* @returns An array of lint result information objects, one per evaluated file.
|
|
12
|
-
*/
|
|
13
|
-
export async function lint_v1(options) {
|
|
14
|
-
const filePathList = toNoEmptyStringArrayFromStringOrArray(options.files);
|
|
15
|
-
const codes = toNoEmptyStringArrayFromStringOrArray(options.sourceCodes);
|
|
16
|
-
const files = [
|
|
17
|
-
...filePathList,
|
|
18
|
-
...codes.map((code, i) => ({
|
|
19
|
-
sourceCode: code,
|
|
20
|
-
name: Array.isArray(options.names) ? options.names?.[i] : options.names,
|
|
21
|
-
workspace: options.workspace?.[i],
|
|
22
|
-
})),
|
|
23
|
-
];
|
|
24
|
-
let config;
|
|
25
|
-
let configFile;
|
|
26
|
-
if (typeof options.config === 'string') {
|
|
27
|
-
configFile = options.config;
|
|
28
|
-
}
|
|
29
|
-
else if (options.config) {
|
|
30
|
-
config = options.config;
|
|
31
|
-
}
|
|
32
|
-
const result = await lint(files, {
|
|
33
|
-
config,
|
|
34
|
-
configFile,
|
|
35
|
-
noSearchConfig: filePathList.length === 0,
|
|
36
|
-
rules: options.rules,
|
|
37
|
-
autoLoad: options.rulesAutoResolve ?? true,
|
|
38
|
-
locale: options.locale,
|
|
39
|
-
});
|
|
40
|
-
return result;
|
|
41
|
-
}
|
package/lib/v1.d.ts
DELETED