@markuplint/cli-utils 5.0.0-alpha.1 → 5.0.0-alpha.3

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 CHANGED
@@ -3,6 +3,20 @@
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-alpha.3](https://github.com/markuplint/markuplint/compare/v5.0.0-alpha.2...v5.0.0-alpha.3) (2026-02-26)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **cli-utils:** return empty string from unifiedDiff for identical inputs ([a738284](https://github.com/markuplint/markuplint/commit/a738284e439724da17d3444ac176ccc3d9bd2008))
11
+
12
+ ### Features
13
+
14
+ - **cli-utils:** add specConformance to messageToString ([fda8660](https://github.com/markuplint/markuplint/commit/fda866061dbd8a989ca3c393487ea95be89b5a67))
15
+
16
+ # [5.0.0-alpha.2](https://github.com/markuplint/markuplint/compare/v5.0.0-alpha.1...v5.0.0-alpha.2) (2026-02-23)
17
+
18
+ **Note:** Version bump only for package @markuplint/cli-utils
19
+
6
20
  # [5.0.0-alpha.1](https://github.com/markuplint/markuplint/compare/v5.0.0-alpha.0...v5.0.0-alpha.1) (2026-02-22)
7
21
 
8
22
  **Note:** Version bump only for package @markuplint/cli-utils
package/lib/index.d.ts CHANGED
@@ -17,3 +17,4 @@ export { messageToString } from './message-to-string.js';
17
17
  export { name } from './name.js';
18
18
  export { pad } from './pad.js';
19
19
  export { space } from './space.js';
20
+ export { unifiedDiff } from './unified-diff.js';
package/lib/index.js CHANGED
@@ -16,3 +16,4 @@ export { messageToString } from './message-to-string.js';
16
16
  export { name } from './name.js';
17
17
  export { pad } from './pad.js';
18
18
  export { space } from './space.js';
19
+ export { unifiedDiff } from './unified-diff.js';
@@ -1,9 +1,10 @@
1
1
  /**
2
- * Combines a lint violation message with an optional reason string.
3
- * When a reason is provided, it is appended after a " / " separator.
2
+ * Combines a lint violation message with an optional specConformance tag
3
+ * and reason string.
4
4
  *
5
5
  * @param message - The primary violation message
6
+ * @param specConformance - An optional conformance level (e.g. "normative")
6
7
  * @param reason - An optional supplementary reason or detail to append
7
8
  * @returns The combined message string
8
9
  */
9
- export declare function messageToString(message: string, reason?: string): string;
10
+ export declare function messageToString(message: string, specConformance?: string, reason?: string): string;
@@ -1,14 +1,19 @@
1
1
  /**
2
- * Combines a lint violation message with an optional reason string.
3
- * When a reason is provided, it is appended after a " / " separator.
2
+ * Combines a lint violation message with an optional specConformance tag
3
+ * and reason string.
4
4
  *
5
5
  * @param message - The primary violation message
6
+ * @param specConformance - An optional conformance level (e.g. "normative")
6
7
  * @param reason - An optional supplementary reason or detail to append
7
8
  * @returns The combined message string
8
9
  */
9
- export function messageToString(message, reason) {
10
- if (!reason) {
11
- return message;
10
+ export function messageToString(message, specConformance, reason) {
11
+ let result = message;
12
+ if (specConformance) {
13
+ result += ` [${specConformance}]`;
12
14
  }
13
- return `${message} / ${reason}`;
15
+ if (reason) {
16
+ result += ` / ${reason}`;
17
+ }
18
+ return result;
14
19
  }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Generates a unified-style diff string between two texts.
3
+ * Uses ANSI colors via picocolors when color output is supported.
4
+ *
5
+ * @param filePath - File path for the diff header
6
+ * @param original - Original text
7
+ * @param fixed - Fixed text
8
+ * @returns Formatted unified diff string
9
+ */
10
+ export declare function unifiedDiff(filePath: string, original: string, fixed: string): string;
@@ -0,0 +1,144 @@
1
+ import c from 'picocolors';
2
+ /**
3
+ * Generates a unified-style diff string between two texts.
4
+ * Uses ANSI colors via picocolors when color output is supported.
5
+ *
6
+ * @param filePath - File path for the diff header
7
+ * @param original - Original text
8
+ * @param fixed - Fixed text
9
+ * @returns Formatted unified diff string
10
+ */
11
+ export function unifiedDiff(filePath, original, fixed) {
12
+ const originalLines = original.split('\n');
13
+ const fixedLines = fixed.split('\n');
14
+ const edits = computeLineEdits(originalLines, fixedLines);
15
+ const CONTEXT = 3;
16
+ const hunks = groupIntoHunks(edits, CONTEXT);
17
+ if (hunks.length === 0) {
18
+ return '';
19
+ }
20
+ const lines = [c.red(`--- a/${filePath}`), c.green(`+++ b/${filePath}`)];
21
+ for (const hunk of hunks) {
22
+ let origCount = 0;
23
+ let fixCount = 0;
24
+ const hunkLines = [];
25
+ for (const edit of hunk.edits) {
26
+ switch (edit.type) {
27
+ case 'equal': {
28
+ hunkLines.push(` ${edit.line}`);
29
+ origCount++;
30
+ fixCount++;
31
+ break;
32
+ }
33
+ case 'delete': {
34
+ hunkLines.push(c.red(`-${edit.line}`));
35
+ origCount++;
36
+ break;
37
+ }
38
+ case 'insert': {
39
+ hunkLines.push(c.green(`+${edit.line}`));
40
+ fixCount++;
41
+ break;
42
+ }
43
+ }
44
+ }
45
+ lines.push(c.cyan(`@@ -${hunk.origStart + 1},${origCount} +${hunk.fixStart + 1},${fixCount} @@`), ...hunkLines);
46
+ }
47
+ return lines.join('\n');
48
+ }
49
+ // O(n*m) LCS — acceptable for typical HTML files; consider Myers' algorithm if performance issues arise.
50
+ function computeLineEdits(a, b) {
51
+ const n = a.length;
52
+ const m = b.length;
53
+ // Build LCS table — dp[i][j] = LCS length of a[0..i-1] and b[0..j-1]
54
+ const dp = [];
55
+ for (let i = 0; i <= n; i++) {
56
+ const row = [];
57
+ for (let j = 0; j <= m; j++) {
58
+ row.push(0);
59
+ }
60
+ dp.push(row);
61
+ }
62
+ for (let i = 1; i <= n; i++) {
63
+ for (let j = 1; j <= m; j++) {
64
+ if (a[i - 1] === b[j - 1]) {
65
+ dp[i][j] = dp[i - 1][j - 1] + 1;
66
+ }
67
+ else {
68
+ dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
69
+ }
70
+ }
71
+ }
72
+ // Backtrack to produce edit script
73
+ const edits = [];
74
+ let i = n;
75
+ let j = m;
76
+ while (i > 0 || j > 0) {
77
+ if (i > 0 && j > 0 && a[i - 1] === b[j - 1]) {
78
+ edits.push({ type: 'equal', line: a[i - 1] });
79
+ i--;
80
+ j--;
81
+ }
82
+ else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
83
+ edits.push({ type: 'insert', line: b[j - 1] });
84
+ j--;
85
+ }
86
+ else {
87
+ edits.push({ type: 'delete', line: a[i - 1] });
88
+ i--;
89
+ }
90
+ }
91
+ edits.reverse();
92
+ return edits;
93
+ }
94
+ function groupIntoHunks(edits, context) {
95
+ const changedIndices = [];
96
+ for (const [i, edit] of edits.entries()) {
97
+ if (edit.type !== 'equal') {
98
+ changedIndices.push(i);
99
+ }
100
+ }
101
+ if (changedIndices.length === 0) {
102
+ return [];
103
+ }
104
+ const firstIdx = changedIndices[0];
105
+ const hunks = [];
106
+ let rangeStart = Math.max(0, firstIdx - context);
107
+ let rangeEnd = Math.min(edits.length - 1, firstIdx + context);
108
+ for (let k = 1; k < changedIndices.length; k++) {
109
+ const idx = changedIndices[k];
110
+ const newStart = Math.max(0, idx - context);
111
+ const newEnd = Math.min(edits.length - 1, idx + context);
112
+ if (newStart <= rangeEnd + 1) {
113
+ rangeEnd = newEnd;
114
+ }
115
+ else {
116
+ hunks.push(buildHunk(edits, rangeStart, rangeEnd));
117
+ rangeStart = newStart;
118
+ rangeEnd = newEnd;
119
+ }
120
+ }
121
+ hunks.push(buildHunk(edits, rangeStart, rangeEnd));
122
+ return hunks;
123
+ }
124
+ function buildHunk(edits, start, end) {
125
+ let origLine = 0;
126
+ let fixLine = 0;
127
+ for (let i = 0; i < start; i++) {
128
+ const edit = edits[i];
129
+ if (!edit) {
130
+ continue;
131
+ }
132
+ if (edit.type === 'equal' || edit.type === 'delete') {
133
+ origLine++;
134
+ }
135
+ if (edit.type === 'equal' || edit.type === 'insert') {
136
+ fixLine++;
137
+ }
138
+ }
139
+ return {
140
+ origStart: origLine,
141
+ fixStart: fixLine,
142
+ edits: edits.slice(start, end + 1),
143
+ };
144
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markuplint/cli-utils",
3
- "version": "5.0.0-alpha.1",
3
+ "version": "5.0.0-alpha.3",
4
4
  "description": "Utilities for CLI of Markuplint",
5
5
  "repository": "git@github.com:markuplint/markuplint.git",
6
6
  "author": "Yusuke Hirao <yusukehirao@me.com>",
@@ -31,5 +31,5 @@
31
31
  "picocolors": "1.1.1",
32
32
  "strip-ansi": "7.1.2"
33
33
  },
34
- "gitHead": "78a295e73a097a1ce09c777c06fa21ab68136387"
34
+ "gitHead": "2fbdf26daa3d021ac628ccc2f59f0eeae6ddd53d"
35
35
  }