@vmz/diagnostic 0.3.13 → 0.3.14

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/dist/pretty.js CHANGED
@@ -1,4 +1,4 @@
1
- import { scalarColumnToIndex, utf8LineAt } from './utf8.js';
1
+ import { displayWidth, expandTabs, utf8ByteSlice, utf8LineAt } from './utf8.js';
2
2
  const ANSI = {
3
3
  reset: '\x1b[0m',
4
4
  bold: '\x1b[1m',
@@ -26,21 +26,20 @@ function severityStyle(severity) {
26
26
  function severityLabel(severity) {
27
27
  return severity === 'warning' ? 'warning' : severity;
28
28
  }
29
- function formatLocation(path, line, column) {
29
+ function formatLocation(path, line, column, gutterWidth = 1) {
30
30
  const where = path ? `${path}:${line}:${column}` : `${line}:${column}`;
31
- return paint(` --> ${where}`, ANSI.bold, ANSI.blue);
31
+ return paint(`${' '.repeat(gutterWidth + 1)}--> ${where}`, ANSI.bold, ANSI.blue);
32
32
  }
33
33
  function canRenderSnippet(d, opts) {
34
- return (opts.style ?? 'pretty') === 'pretty' && Boolean(opts.sourceText && opts.position && d.span && d.span.end > d.span.start);
34
+ return ((opts.style ?? 'pretty') === 'pretty' &&
35
+ Boolean(opts.sourceText && opts.position && d.span && d.span.end >= d.span.start));
35
36
  }
36
- function renderSnippetLine(out, gutterWidth, lineNum, lineText, colStart, colEnd, severity) {
37
+ function renderSnippetLine(out, gutterWidth, lineNum, lineText, startCell, endCell, severity) {
37
38
  const gutter = paint(`${String(lineNum).padStart(gutterWidth)} | `, ANSI.dim);
38
- out.push(`${gutter}${lineText}`);
39
- const startIdx = scalarColumnToIndex(lineText, colStart);
40
- const endIdx = Math.max(startIdx + 1, scalarColumnToIndex(lineText, colEnd + 1));
39
+ out.push(`${gutter}${expandTabs(lineText)}`);
41
40
  const marker = paint(`${' '.repeat(gutterWidth)} | `, ANSI.dim) +
42
- ' '.repeat(startIdx) +
43
- paint('^'.repeat(endIdx - startIdx), ...severityStyle(severity));
41
+ ' '.repeat(startCell) +
42
+ paint('^'.repeat(Math.max(1, endCell - startCell)), ...severityStyle(severity));
44
43
  out.push(marker);
45
44
  }
46
45
  export function formatDiagnosticPretty(d, message, opts) {
@@ -62,19 +61,19 @@ export function formatDiagnosticPretty(d, message, opts) {
62
61
  const span = d.span;
63
62
  const path = d.path || '';
64
63
  const startPos = position.lineCol(span.start);
65
- const endPos = position.lineCol(Math.max(span.start, span.end - 1));
66
- lines.push(formatLocation(path, startPos.line, startPos.column));
67
64
  const startLine = utf8LineAt(source, span.start);
68
65
  const endLine = utf8LineAt(source, Math.max(span.start, span.end - 1));
69
66
  const gutterWidth = String(endLine.line).length;
67
+ lines.push(formatLocation(path, startPos.line, startPos.column, gutterWidth));
70
68
  if (startLine.line === endLine.line) {
71
- renderSnippetLine(lines, gutterWidth, startLine.line, startLine.text, startPos.column, endPos.column, d.severity);
69
+ renderSnippetLine(lines, gutterWidth, startLine.line, startLine.text, displayWidth(utf8ByteSlice(source, startLine.lineStart, span.start)), displayWidth(utf8ByteSlice(source, startLine.lineStart, Math.min(span.end, startLine.lineEnd))), d.severity);
72
70
  }
73
71
  else {
74
- renderSnippetLine(lines, gutterWidth, startLine.line, startLine.text, startPos.column, startLine.text.length + 1, d.severity);
72
+ renderSnippetLine(lines, gutterWidth, startLine.line, startLine.text, displayWidth(utf8ByteSlice(source, startLine.lineStart, span.start)), displayWidth(startLine.text), d.severity);
75
73
  if (endLine.line > startLine.line + 1) {
76
74
  lines.push(paint(`${' '.repeat(gutterWidth + 3)}...`, ANSI.dim));
77
75
  }
76
+ renderSnippetLine(lines, gutterWidth, endLine.line, endLine.text, 0, displayWidth(utf8ByteSlice(source, endLine.lineStart, Math.min(span.end, endLine.lineEnd))), d.severity);
78
77
  }
79
78
  return lines.join('\n');
80
79
  }
package/dist/utf8.d.ts CHANGED
@@ -11,5 +11,16 @@ export type Utf8LineSlice = {
11
11
  export declare function utf8LineAt(source: string, offset: number): Utf8LineSlice;
12
12
  /** 1-based Unicode scalar column within a line slice (matches compiler OffsetIndex). */
13
13
  export declare function unicodeScalarColumn(lineText: string, byteOffsetInLine: number): number;
14
- /** Map 1-based scalar column to JS string index for underline rendering. */
15
- export declare function scalarColumnToIndex(lineText: string, column: number): number;
14
+ /** Terminal cells occupied by one Unicode scalar (wide CJK/emoji render as two, CR as zero). */
15
+ export declare function scalarDisplayWidth(ch: string): number;
16
+ /** Tab stops used when rendering source snippets, matching rustc and miette defaults. */
17
+ export declare const SNIPPET_TAB_WIDTH = 4;
18
+ /** Display width of a line fragment in terminal cells (tabs advance to the next stop). */
19
+ export declare function displayWidth(text: string): number;
20
+ /**
21
+ * Expand tabs to spaces so the printed source line and the caret row agree in
22
+ * any terminal, regardless of that terminal's own tab-stop configuration.
23
+ * Carriage returns from CRLF sources are dropped because they terminate the
24
+ * line rather than occupy a cell.
25
+ */
26
+ export declare function expandTabs(text: string): string;
package/dist/utf8.js CHANGED
@@ -40,17 +40,75 @@ export function unicodeScalarColumn(lineText, byteOffsetInLine) {
40
40
  return 1;
41
41
  return buf.subarray(0, end).toString('utf8').length > 0 ? [...buf.subarray(0, end).toString('utf8')].length : 1;
42
42
  }
43
- /** Map 1-based scalar column to JS string index for underline rendering. */
44
- export function scalarColumnToIndex(lineText, column) {
45
- if (column <= 1)
43
+ const ZERO_WIDTH_PATTERN = /\p{Mn}|\p{Me}|\p{Cf}/u;
44
+ function isWideCodePoint(cp) {
45
+ return ((cp >= 0x1100 && cp <= 0x115f) ||
46
+ cp === 0x2329 ||
47
+ cp === 0x232a ||
48
+ (cp >= 0x2e80 && cp <= 0x303e) ||
49
+ (cp >= 0x3041 && cp <= 0x33ff) ||
50
+ (cp >= 0x3400 && cp <= 0x4dbf) ||
51
+ (cp >= 0x4e00 && cp <= 0x9fff) ||
52
+ (cp >= 0xa000 && cp <= 0xa4cf) ||
53
+ (cp >= 0xa960 && cp <= 0xa97f) ||
54
+ (cp >= 0xac00 && cp <= 0xd7a3) ||
55
+ (cp >= 0xf900 && cp <= 0xfaff) ||
56
+ (cp >= 0xfe10 && cp <= 0xfe19) ||
57
+ (cp >= 0xfe30 && cp <= 0xfe6f) ||
58
+ (cp >= 0xff00 && cp <= 0xff60) ||
59
+ (cp >= 0xffe0 && cp <= 0xffe6) ||
60
+ (cp >= 0x1b000 && cp <= 0x1b2ff) ||
61
+ (cp >= 0x1f000 && cp <= 0x1f02f) ||
62
+ (cp >= 0x1f0a0 && cp <= 0x1f0ff) ||
63
+ (cp >= 0x1f100 && cp <= 0x1f1ad) ||
64
+ (cp >= 0x1f300 && cp <= 0x1f64f) ||
65
+ (cp >= 0x1f680 && cp <= 0x1f6ff) ||
66
+ (cp >= 0x1f900 && cp <= 0x1f9ff) ||
67
+ (cp >= 0x1fa70 && cp <= 0x1faff) ||
68
+ (cp >= 0x20000 && cp <= 0x3fffd));
69
+ }
70
+ /** Terminal cells occupied by one Unicode scalar (wide CJK/emoji render as two, CR as zero). */
71
+ export function scalarDisplayWidth(ch) {
72
+ const cp = ch.codePointAt(0) ?? 0;
73
+ if (cp === 0 || cp === 0x0d || ZERO_WIDTH_PATTERN.test(ch))
46
74
  return 0;
47
- let pos = 1;
48
- let index = 0;
49
- for (const ch of lineText) {
50
- if (pos >= column)
51
- break;
52
- index += ch.length;
53
- pos += 1;
75
+ return isWideCodePoint(cp) ? 2 : 1;
76
+ }
77
+ /** Tab stops used when rendering source snippets, matching rustc and miette defaults. */
78
+ export const SNIPPET_TAB_WIDTH = 4;
79
+ /** Display width of a line fragment in terminal cells (tabs advance to the next stop). */
80
+ export function displayWidth(text) {
81
+ let cells = 0;
82
+ for (const ch of text) {
83
+ if (ch === '\t') {
84
+ cells += SNIPPET_TAB_WIDTH - (cells % SNIPPET_TAB_WIDTH);
85
+ }
86
+ else {
87
+ cells += scalarDisplayWidth(ch);
88
+ }
89
+ }
90
+ return cells;
91
+ }
92
+ /**
93
+ * Expand tabs to spaces so the printed source line and the caret row agree in
94
+ * any terminal, regardless of that terminal's own tab-stop configuration.
95
+ * Carriage returns from CRLF sources are dropped because they terminate the
96
+ * line rather than occupy a cell.
97
+ */
98
+ export function expandTabs(text) {
99
+ let cells = 0;
100
+ let out = '';
101
+ for (const ch of text) {
102
+ if (ch === '\r')
103
+ continue;
104
+ if (ch === '\t') {
105
+ const pad = SNIPPET_TAB_WIDTH - (cells % SNIPPET_TAB_WIDTH);
106
+ out += ' '.repeat(pad);
107
+ cells += pad;
108
+ continue;
109
+ }
110
+ out += ch;
111
+ cells += scalarDisplayWidth(ch);
54
112
  }
55
- return index;
113
+ return out;
56
114
  }
package/package.json CHANGED
@@ -39,5 +39,5 @@
39
39
  },
40
40
  "type": "module",
41
41
  "types": "./dist/index.d.ts",
42
- "version": "0.3.13"
42
+ "version": "0.3.14"
43
43
  }