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.
@@ -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-dev.5+e96392f56",
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-dev.5+e96392f56",
30
- "@markuplint/file-resolver": "5.0.0-dev.5+e96392f56",
31
- "@markuplint/html-parser": "5.0.0-dev.5+e96392f56",
32
- "@markuplint/html-spec": "5.0.0-dev.5+e96392f56",
33
- "@markuplint/i18n": "5.0.0-dev.5+e96392f56",
34
- "@markuplint/ml-ast": "5.0.0-dev.5+e96392f56",
35
- "@markuplint/ml-config": "5.0.0-dev.5+e96392f56",
36
- "@markuplint/ml-core": "5.0.0-dev.5+e96392f56",
37
- "@markuplint/ml-spec": "5.0.0-dev.5+e96392f56",
38
- "@markuplint/rules": "5.0.0-dev.5+e96392f56",
39
- "@markuplint/shared": "5.0.0-dev.5+e96392f56",
40
- "@types/debug": "4.1.12",
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.4.4"
47
+ "type-fest": "5.5.0"
48
48
  },
49
- "gitHead": "e96392f56e4bc8165ba59622b41c822703a96372"
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
@@ -1,6 +0,0 @@
1
- /**
2
- * Legacy v1 entry point re-exporting the deprecated `exec` function.
3
- *
4
- * @deprecated Use `lint` or `MLEngine` from the current API instead.
5
- */
6
- export { lint_v1 as exec } from './api/v1.js';
package/lib/v1.js DELETED
@@ -1,6 +0,0 @@
1
- /**
2
- * Legacy v1 entry point re-exporting the deprecated `exec` function.
3
- *
4
- * @deprecated Use `lint` or `MLEngine` from the current API instead.
5
- */
6
- export { lint_v1 as exec } from './api/v1.js';