@parcel/codeframe 2.0.0-nightly.97 → 2.0.0-nightly.970

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/lib/codeframe.js CHANGED
@@ -5,58 +5,98 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.default = codeFrame;
7
7
 
8
- var _chalk = _interopRequireDefault(require("chalk"));
8
+ function _chalk() {
9
+ const data = _interopRequireDefault(require("chalk"));
9
10
 
10
- var _emphasize = _interopRequireDefault(require("emphasize"));
11
+ _chalk = function () {
12
+ return data;
13
+ };
14
+
15
+ return data;
16
+ }
17
+
18
+ function _emphasize() {
19
+ const data = _interopRequireDefault(require("emphasize"));
20
+
21
+ _emphasize = function () {
22
+ return data;
23
+ };
24
+
25
+ return data;
26
+ }
27
+
28
+ function _stringWidth() {
29
+ const data = _interopRequireDefault(require("string-width"));
30
+
31
+ _stringWidth = function () {
32
+ return data;
33
+ };
34
+
35
+ return data;
36
+ }
37
+
38
+ function _sliceAnsi() {
39
+ const data = _interopRequireDefault(require("slice-ansi"));
40
+
41
+ _sliceAnsi = function () {
42
+ return data;
43
+ };
44
+
45
+ return data;
46
+ }
11
47
 
12
48
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
13
49
 
14
50
  const NEWLINE = /\r\n|[\n\r\u2028\u2029]/;
15
51
  const TAB_REPLACE_REGEX = /\t/g;
16
52
  const TAB_REPLACEMENT = ' ';
53
+ const DEFAULT_TERMINAL_WIDTH = 80;
17
54
 
18
55
  const highlightSyntax = (txt, lang) => {
19
56
  if (lang) {
20
57
  try {
21
- return _emphasize.default.highlight(lang, txt).value;
58
+ return _emphasize().default.highlight(lang, txt).value;
22
59
  } catch (e) {// fallback for unknown languages...
23
60
  }
24
61
  }
25
62
 
26
- return _emphasize.default.highlightAuto(txt).value;
63
+ return _emphasize().default.highlightAuto(txt).value;
27
64
  };
28
65
 
29
- function codeFrame(code, highlights, // $FlowFixMe
30
- inputOpts = {}) {
66
+ function codeFrame(code, highlights, inputOpts = {}) {
67
+ var _inputOpts$maxLines;
68
+
31
69
  if (highlights.length < 1) return '';
32
70
  let opts = {
33
71
  useColor: !!inputOpts.useColor,
34
72
  syntaxHighlighting: !!inputOpts.syntaxHighlighting,
35
73
  language: inputOpts.language,
36
- maxLines: inputOpts.maxLines !== undefined ? inputOpts.maxLines : 12,
74
+ maxLines: (_inputOpts$maxLines = inputOpts.maxLines) !== null && _inputOpts$maxLines !== void 0 ? _inputOpts$maxLines : 12,
75
+ terminalWidth: inputOpts.terminalWidth || DEFAULT_TERMINAL_WIDTH,
37
76
  padding: inputOpts.padding || {
38
77
  before: 1,
39
78
  after: 2
40
79
  }
41
- };
80
+ }; // Highlights messages and prefixes when colors are enabled
42
81
 
43
82
  const highlighter = (s, bold) => {
44
83
  if (opts.useColor) {
45
- let redString = _chalk.default.red(s);
84
+ let redString = _chalk().default.red(s);
46
85
 
47
- return bold ? _chalk.default.bold(redString) : redString;
86
+ return bold ? _chalk().default.bold(redString) : redString;
48
87
  }
49
88
 
50
89
  return s;
51
- };
90
+ }; // Prefix lines with the line number
91
+
52
92
 
53
93
  const lineNumberPrefixer = params => {
54
94
  let {
55
95
  lineNumber,
56
- endLine,
96
+ lineNumberLength,
57
97
  isHighlighted
58
98
  } = params;
59
- return `${isHighlighted ? highlighter('>') : ' '} ${lineNumber ? lineNumber.padEnd(endLine.length, ' ') : ' '.repeat(endLine.length)} | `;
99
+ return `${isHighlighted ? highlighter('>') : ' '} ${lineNumber ? lineNumber.padStart(lineNumberLength, ' ') : ' '.repeat(lineNumberLength)} | `;
60
100
  }; // Make columns/lines start at 1
61
101
 
62
102
 
@@ -72,75 +112,127 @@ inputOpts = {}) {
72
112
  },
73
113
  message: h.message
74
114
  };
75
- });
115
+ }); // Find first and last highlight
116
+
76
117
  let firstHighlight = highlights.length > 1 ? highlights.sort((a, b) => a.start.line - b.start.line)[0] : highlights[0];
77
- let lastHighlight = highlights.length > 1 ? highlights.sort((a, b) => b.end.line - a.end.line)[0] : highlights[0];
118
+ let lastHighlight = highlights.length > 1 ? highlights.sort((a, b) => b.end.line - a.end.line)[0] : highlights[0]; // Calculate first and last line index of codeframe
119
+
78
120
  let startLine = firstHighlight.start.line - opts.padding.before;
79
121
  startLine = startLine < 0 ? 0 : startLine;
80
- let endLine = lastHighlight.end.line + opts.padding.after;
81
- endLine = endLine - startLine > opts.maxLines ? startLine + opts.maxLines - 1 : endLine;
82
- let endLineString = endLine.toString(10);
83
- let resultLines = [];
122
+ let endLineIndex = lastHighlight.end.line + opts.padding.after;
123
+ endLineIndex = endLineIndex - startLine > opts.maxLines ? startLine + opts.maxLines - 1 : endLineIndex;
124
+ let lineNumberLength = (endLineIndex + 1).toString(10).length; // Split input into lines and highlight syntax
125
+
84
126
  let lines = code.split(NEWLINE);
85
- let syntaxHighlightedLines = (opts.syntaxHighlighting ? highlightSyntax(code, opts.language) : code).replace(TAB_REPLACE_REGEX, TAB_REPLACEMENT).split(NEWLINE);
127
+ let syntaxHighlightedLines = (opts.syntaxHighlighting ? highlightSyntax(code, opts.language) : code).replace(TAB_REPLACE_REGEX, TAB_REPLACEMENT).split(NEWLINE); // Loop over all lines and create codeframe
128
+
129
+ let resultLines = [];
130
+
131
+ for (let currentLineIndex = startLine; currentLineIndex < syntaxHighlightedLines.length; currentLineIndex++) {
132
+ if (currentLineIndex > endLineIndex) break;
133
+ if (currentLineIndex > syntaxHighlightedLines.length - 1) break; // Find highlights that need to get rendered on the current line
134
+
135
+ let lineHighlights = highlights.filter(highlight => highlight.start.line <= currentLineIndex && highlight.end.line >= currentLineIndex).sort((a, b) => (a.start.line < currentLineIndex ? 0 : a.start.column) - (b.start.line < currentLineIndex ? 0 : b.start.column)); // Check if this line has a full line highlight
136
+
137
+ let isWholeLine = lineHighlights.length && !!lineHighlights.find(h => h.start.line < currentLineIndex && h.end.line > currentLineIndex);
138
+ let lineLengthLimit = opts.terminalWidth > lineNumberLength + 7 ? opts.terminalWidth - (lineNumberLength + 5) : 10; // Split the line into line parts that will fit the provided terminal width
139
+
140
+ let colOffset = 0;
141
+ let lineEndCol = lineLengthLimit;
142
+ let syntaxHighlightedLine = syntaxHighlightedLines[currentLineIndex];
143
+
144
+ if ((0, _stringWidth().default)(syntaxHighlightedLine) > lineLengthLimit) {
145
+ if (lineHighlights.length > 0) {
146
+ if (lineHighlights[0].start.line === currentLineIndex) {
147
+ colOffset = lineHighlights[0].start.column - 5;
148
+ } else if (lineHighlights[0].end.line === currentLineIndex) {
149
+ colOffset = lineHighlights[0].end.column - 5;
150
+ }
151
+ }
152
+
153
+ colOffset = colOffset > 0 ? colOffset : 0;
154
+ lineEndCol = colOffset + lineLengthLimit;
155
+ syntaxHighlightedLine = (0, _sliceAnsi().default)(syntaxHighlightedLine, colOffset, lineEndCol);
156
+ } // Write the syntax highlighted line part
157
+
86
158
 
87
- for (let i = startLine; i < lines.length; i++) {
88
- if (i > endLine) break;
89
- let originalLine = lines[i];
90
- let syntaxHighlightedLine = syntaxHighlightedLines[i];
91
- let foundHighlights = highlights.filter(highlight => highlight.start.line <= i && highlight.end.line >= i);
92
159
  resultLines.push(lineNumberPrefixer({
93
- lineNumber: (i + 1).toString(10),
94
- endLine: endLineString,
95
- isHighlighted: foundHighlights.length > 0
160
+ lineNumber: (currentLineIndex + 1).toString(10),
161
+ lineNumberLength,
162
+ isHighlighted: lineHighlights.length > 0
96
163
  }) + syntaxHighlightedLine);
164
+ let lineWidth = (0, _stringWidth().default)(syntaxHighlightedLine);
165
+ let highlightLine = '';
97
166
 
98
- if (foundHighlights.length > 0) {
99
- let highlightLine = lineNumberPrefixer({
100
- endLine: endLineString,
101
- isHighlighted: true
102
- });
103
- let isWholeLine = !!foundHighlights.find(h => h.start.line < i && h.end.line > i);
104
-
105
- if (isWholeLine) {
106
- // If there's a whole line highlight
107
- // don't even bother creating seperate highlight
108
- highlightLine += highlighter('^'.repeat(originalLine.replace(TAB_REPLACE_REGEX, TAB_REPLACEMENT).length));
109
- } else {
110
- let sortedColumns = foundHighlights.length > 1 ? foundHighlights.sort((a, b) => (a.start.line < i ? 0 : a.start.column) - (b.start.line < i ? 0 : b.start.column)) : foundHighlights;
111
- let lastCol = 0;
112
-
113
- for (let col of sortedColumns) {
114
- let startCol = col.start.line === i ? col.start.column : 0;
115
- let endCol = (col.end.line === i ? col.end.column : originalLine.length - (lastCol || 1)) + 1;
116
-
117
- if (startCol - lastCol > 0) {
118
- let whitespace = originalLine.substring(lastCol, startCol).replace(TAB_REPLACE_REGEX, TAB_REPLACEMENT).replace(/\S/g, ' ');
119
- highlightLine += whitespace;
120
- }
167
+ if (isWholeLine) {
168
+ highlightLine = highlighter('^'.repeat(lineWidth));
169
+ } else if (lineHighlights.length > 0) {
170
+ let lastCol = 0;
171
+ let highlight = null;
172
+ let highlightHasEnded = false;
121
173
 
122
- let highlightStartColumn = lastCol >= startCol ? lastCol : startCol;
174
+ for (let highlightIndex = 0; highlightIndex < lineHighlights.length; highlightIndex++) {
175
+ // Set highlight to current highlight
176
+ highlight = lineHighlights[highlightIndex];
177
+ highlightHasEnded = false; // Calculate the startColumn and get the real width by doing a substring of the original
178
+ // line and replacing tabs with our tab replacement to support tab handling
179
+
180
+ let startCol = 0;
181
+
182
+ if (highlight.start.line === currentLineIndex && highlight.start.column > colOffset) {
183
+ startCol = lines[currentLineIndex].substring(colOffset, highlight.start.column).replace(TAB_REPLACE_REGEX, TAB_REPLACEMENT).length;
184
+ } // Calculate the endColumn and get the real width by doing a substring of the original
185
+ // line and replacing tabs with our tab replacement to support tab handling
123
186
 
124
- if (endCol - highlightStartColumn > 0) {
125
- let renderedHighlightLength = originalLine.substring(highlightStartColumn, endCol).replace(TAB_REPLACE_REGEX, TAB_REPLACEMENT).length;
126
- highlightLine += highlighter('^'.repeat(renderedHighlightLength));
127
- lastCol = endCol;
128
- }
129
- }
130
187
 
131
- let endsWithFullLine = !!sortedColumns.find(h => h.end.line > i);
188
+ let endCol = lineWidth - 1;
132
189
 
133
- if (!endsWithFullLine) {
134
- let sortedByEnd = sortedColumns.sort((a, b) => a.end.column - b.end.column);
135
- let lastHighlightForColumn = sortedByEnd[sortedByEnd.length - 1];
190
+ if (highlight.end.line === currentLineIndex) {
191
+ endCol = lines[currentLineIndex].substring(colOffset, highlight.end.column).replace(TAB_REPLACE_REGEX, TAB_REPLACEMENT).length; // If the endCol is too big for this line part, trim it so we can handle it in the next one
136
192
 
137
- if (lastHighlightForColumn.message) {
138
- highlightLine += ' ' + highlighter(lastHighlightForColumn.message, true);
193
+ if (endCol > lineWidth) {
194
+ endCol = lineWidth - 1;
139
195
  }
196
+
197
+ highlightHasEnded = true;
198
+ } // If endcol is smaller than lastCol it overlaps with another highlight and is no longer visible, we can skip those
199
+
200
+
201
+ if (endCol >= lastCol) {
202
+ let characters = endCol - startCol + 1;
203
+
204
+ if (startCol > lastCol) {
205
+ // startCol is before lastCol, so add spaces as padding before the highlight indicators
206
+ highlightLine += ' '.repeat(startCol - lastCol);
207
+ } else if (lastCol > startCol) {
208
+ // If last column is larger than the start, there's overlap in highlights
209
+ // This line adjusts the characters count to ensure we don't add too many characters
210
+ characters += startCol - lastCol;
211
+ } // Append the highlight indicators
212
+
213
+
214
+ highlightLine += highlighter('^'.repeat(characters)); // Set the lastCol equal to character count between start of line part and highlight end-column
215
+
216
+ lastCol = endCol + 1;
217
+ } // There's no point in processing more highlights if we reached the end of the line
218
+
219
+
220
+ if (endCol >= lineEndCol - 1) {
221
+ break;
140
222
  }
223
+ } // Append the highlight message if the current highlights ends on this line part
224
+
225
+
226
+ if (highlight && highlight.message && highlightHasEnded) {
227
+ highlightLine += ' ' + highlighter(highlight.message, true);
141
228
  }
229
+ }
142
230
 
143
- resultLines.push(highlightLine);
231
+ if (highlightLine) {
232
+ resultLines.push(lineNumberPrefixer({
233
+ lineNumberLength,
234
+ isHighlighted: true
235
+ }) + highlightLine);
144
236
  }
145
237
  }
146
238
 
package/package.json CHANGED
@@ -1,11 +1,15 @@
1
1
  {
2
2
  "name": "@parcel/codeframe",
3
- "version": "2.0.0-nightly.97+a63f3fc9",
3
+ "version": "2.0.0-nightly.970+5a80fbe4",
4
4
  "description": "Blazing fast, zero configuration web application bundler",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
7
7
  "access": "public"
8
8
  },
9
+ "funding": {
10
+ "type": "opencollective",
11
+ "url": "https://opencollective.com/parcel"
12
+ },
9
13
  "repository": {
10
14
  "type": "git",
11
15
  "url": "https://github.com/parcel-bundler/parcel.git"
@@ -13,11 +17,16 @@
13
17
  "main": "lib/codeframe.js",
14
18
  "source": "src/codeframe.js",
15
19
  "engines": {
16
- "node": ">= 10.0.0"
20
+ "node": ">= 12.0.0"
17
21
  },
18
22
  "dependencies": {
19
- "chalk": "^2.4.2",
20
- "emphasize": "^2.1.0"
23
+ "chalk": "^4.1.0",
24
+ "emphasize": "^4.2.0",
25
+ "slice-ansi": "^4.0.0",
26
+ "string-width": "^4.2.0"
27
+ },
28
+ "devDependencies": {
29
+ "strip-ansi": "^6.0.0"
21
30
  },
22
- "gitHead": "a63f3fc9726483219412920faeb255e035f90747"
31
+ "gitHead": "5a80fbe4af8797d0aab248a66f5254f42f00e83e"
23
32
  }
package/src/codeframe.js CHANGED
@@ -3,31 +3,29 @@ import type {DiagnosticCodeHighlight} from '@parcel/diagnostic';
3
3
 
4
4
  import chalk from 'chalk';
5
5
  import emphasize from 'emphasize';
6
+ import stringWidth from 'string-width';
7
+ import sliceAnsi from 'slice-ansi';
6
8
 
7
9
  type CodeFramePadding = {|
8
10
  before: number,
9
11
  after: number,
10
12
  |};
11
13
 
12
- type CodeFrameOptionsInput = {|
13
- useColor?: boolean,
14
- maxLines?: number,
15
- padding?: CodeFramePadding,
16
- syntaxHighlighting?: boolean,
17
- language?: string,
18
- |};
14
+ type CodeFrameOptionsInput = $Shape<CodeFrameOptions>;
19
15
 
20
16
  type CodeFrameOptions = {|
21
17
  useColor: boolean,
22
18
  syntaxHighlighting: boolean,
23
19
  maxLines: number,
24
20
  padding: CodeFramePadding,
21
+ terminalWidth: number,
25
22
  language?: string,
26
23
  |};
27
24
 
28
25
  const NEWLINE = /\r\n|[\n\r\u2028\u2029]/;
29
26
  const TAB_REPLACE_REGEX = /\t/g;
30
27
  const TAB_REPLACEMENT = ' ';
28
+ const DEFAULT_TERMINAL_WIDTH = 80;
31
29
 
32
30
  const highlightSyntax = (txt: string, lang?: string): string => {
33
31
  if (lang) {
@@ -44,7 +42,6 @@ const highlightSyntax = (txt: string, lang?: string): string => {
44
42
  export default function codeFrame(
45
43
  code: string,
46
44
  highlights: Array<DiagnosticCodeHighlight>,
47
- // $FlowFixMe
48
45
  inputOpts: CodeFrameOptionsInput = {},
49
46
  ): string {
50
47
  if (highlights.length < 1) return '';
@@ -53,13 +50,15 @@ export default function codeFrame(
53
50
  useColor: !!inputOpts.useColor,
54
51
  syntaxHighlighting: !!inputOpts.syntaxHighlighting,
55
52
  language: inputOpts.language,
56
- maxLines: inputOpts.maxLines !== undefined ? inputOpts.maxLines : 12,
53
+ maxLines: inputOpts.maxLines ?? 12,
54
+ terminalWidth: inputOpts.terminalWidth || DEFAULT_TERMINAL_WIDTH,
57
55
  padding: inputOpts.padding || {
58
56
  before: 1,
59
57
  after: 2,
60
58
  },
61
59
  };
62
60
 
61
+ // Highlights messages and prefixes when colors are enabled
63
62
  const highlighter = (s: string, bold?: boolean) => {
64
63
  if (opts.useColor) {
65
64
  let redString = chalk.red(s);
@@ -69,17 +68,18 @@ export default function codeFrame(
69
68
  return s;
70
69
  };
71
70
 
71
+ // Prefix lines with the line number
72
72
  const lineNumberPrefixer = (params: {|
73
73
  lineNumber?: string,
74
- endLine: string,
74
+ lineNumberLength: number,
75
75
  isHighlighted: boolean,
76
76
  |}) => {
77
- let {lineNumber, endLine, isHighlighted} = params;
77
+ let {lineNumber, lineNumberLength, isHighlighted} = params;
78
78
 
79
79
  return `${isHighlighted ? highlighter('>') : ' '} ${
80
80
  lineNumber
81
- ? lineNumber.padEnd(endLine.length, ' ')
82
- : ' '.repeat(endLine.length)
81
+ ? lineNumber.padStart(lineNumberLength, ' ')
82
+ : ' '.repeat(lineNumberLength)
83
83
  } | `;
84
84
  };
85
85
 
@@ -98,6 +98,7 @@ export default function codeFrame(
98
98
  };
99
99
  });
100
100
 
101
+ // Find first and last highlight
101
102
  let firstHighlight =
102
103
  highlights.length > 1
103
104
  ? highlights.sort((a, b) => a.start.line - b.start.line)[0]
@@ -107,111 +108,176 @@ export default function codeFrame(
107
108
  ? highlights.sort((a, b) => b.end.line - a.end.line)[0]
108
109
  : highlights[0];
109
110
 
111
+ // Calculate first and last line index of codeframe
110
112
  let startLine = firstHighlight.start.line - opts.padding.before;
111
113
  startLine = startLine < 0 ? 0 : startLine;
112
- let endLine = lastHighlight.end.line + opts.padding.after;
113
- endLine =
114
- endLine - startLine > opts.maxLines
114
+ let endLineIndex = lastHighlight.end.line + opts.padding.after;
115
+ endLineIndex =
116
+ endLineIndex - startLine > opts.maxLines
115
117
  ? startLine + opts.maxLines - 1
116
- : endLine;
117
- let endLineString = endLine.toString(10);
118
+ : endLineIndex;
118
119
 
119
- let resultLines = [];
120
+ let lineNumberLength = (endLineIndex + 1).toString(10).length;
121
+
122
+ // Split input into lines and highlight syntax
120
123
  let lines = code.split(NEWLINE);
121
- let syntaxHighlightedLines = (opts.syntaxHighlighting
122
- ? highlightSyntax(code, opts.language)
123
- : code
124
+ let syntaxHighlightedLines = (
125
+ opts.syntaxHighlighting ? highlightSyntax(code, opts.language) : code
124
126
  )
125
127
  .replace(TAB_REPLACE_REGEX, TAB_REPLACEMENT)
126
128
  .split(NEWLINE);
127
- for (let i = startLine; i < lines.length; i++) {
128
- if (i > endLine) break;
129
129
 
130
- let originalLine: string = lines[i];
131
- let syntaxHighlightedLine = syntaxHighlightedLines[i];
130
+ // Loop over all lines and create codeframe
131
+ let resultLines = [];
132
+ for (
133
+ let currentLineIndex = startLine;
134
+ currentLineIndex < syntaxHighlightedLines.length;
135
+ currentLineIndex++
136
+ ) {
137
+ if (currentLineIndex > endLineIndex) break;
138
+ if (currentLineIndex > syntaxHighlightedLines.length - 1) break;
132
139
 
133
- let foundHighlights = highlights.filter(
134
- highlight => highlight.start.line <= i && highlight.end.line >= i,
135
- );
140
+ // Find highlights that need to get rendered on the current line
141
+ let lineHighlights = highlights
142
+ .filter(
143
+ highlight =>
144
+ highlight.start.line <= currentLineIndex &&
145
+ highlight.end.line >= currentLineIndex,
146
+ )
147
+ .sort(
148
+ (a, b) =>
149
+ (a.start.line < currentLineIndex ? 0 : a.start.column) -
150
+ (b.start.line < currentLineIndex ? 0 : b.start.column),
151
+ );
152
+
153
+ // Check if this line has a full line highlight
154
+ let isWholeLine =
155
+ lineHighlights.length &&
156
+ !!lineHighlights.find(
157
+ h => h.start.line < currentLineIndex && h.end.line > currentLineIndex,
158
+ );
159
+
160
+ let lineLengthLimit =
161
+ opts.terminalWidth > lineNumberLength + 7
162
+ ? opts.terminalWidth - (lineNumberLength + 5)
163
+ : 10;
164
+
165
+ // Split the line into line parts that will fit the provided terminal width
166
+ let colOffset = 0;
167
+ let lineEndCol = lineLengthLimit;
168
+ let syntaxHighlightedLine = syntaxHighlightedLines[currentLineIndex];
169
+ if (stringWidth(syntaxHighlightedLine) > lineLengthLimit) {
170
+ if (lineHighlights.length > 0) {
171
+ if (lineHighlights[0].start.line === currentLineIndex) {
172
+ colOffset = lineHighlights[0].start.column - 5;
173
+ } else if (lineHighlights[0].end.line === currentLineIndex) {
174
+ colOffset = lineHighlights[0].end.column - 5;
175
+ }
176
+ }
177
+
178
+ colOffset = colOffset > 0 ? colOffset : 0;
179
+ lineEndCol = colOffset + lineLengthLimit;
136
180
 
181
+ syntaxHighlightedLine = sliceAnsi(
182
+ syntaxHighlightedLine,
183
+ colOffset,
184
+ lineEndCol,
185
+ );
186
+ }
187
+
188
+ // Write the syntax highlighted line part
137
189
  resultLines.push(
138
190
  lineNumberPrefixer({
139
- lineNumber: (i + 1).toString(10),
140
- endLine: endLineString,
141
- isHighlighted: foundHighlights.length > 0,
191
+ lineNumber: (currentLineIndex + 1).toString(10),
192
+ lineNumberLength,
193
+ isHighlighted: lineHighlights.length > 0,
142
194
  }) + syntaxHighlightedLine,
143
195
  );
144
196
 
145
- if (foundHighlights.length > 0) {
146
- let highlightLine = lineNumberPrefixer({
147
- endLine: endLineString,
148
- isHighlighted: true,
149
- });
197
+ let lineWidth = stringWidth(syntaxHighlightedLine);
198
+ let highlightLine = '';
199
+ if (isWholeLine) {
200
+ highlightLine = highlighter('^'.repeat(lineWidth));
201
+ } else if (lineHighlights.length > 0) {
202
+ let lastCol = 0;
203
+ let highlight = null;
204
+ let highlightHasEnded = false;
150
205
 
151
- let isWholeLine = !!foundHighlights.find(
152
- h => h.start.line < i && h.end.line > i,
153
- );
206
+ for (
207
+ let highlightIndex = 0;
208
+ highlightIndex < lineHighlights.length;
209
+ highlightIndex++
210
+ ) {
211
+ // Set highlight to current highlight
212
+ highlight = lineHighlights[highlightIndex];
213
+ highlightHasEnded = false;
154
214
 
155
- if (isWholeLine) {
156
- // If there's a whole line highlight
157
- // don't even bother creating seperate highlight
158
- highlightLine += highlighter(
159
- '^'.repeat(
160
- originalLine.replace(TAB_REPLACE_REGEX, TAB_REPLACEMENT).length,
161
- ),
162
- );
163
- } else {
164
- let sortedColumns =
165
- foundHighlights.length > 1
166
- ? foundHighlights.sort(
167
- (a, b) =>
168
- (a.start.line < i ? 0 : a.start.column) -
169
- (b.start.line < i ? 0 : b.start.column),
170
- )
171
- : foundHighlights;
172
-
173
- let lastCol = 0;
174
- for (let col of sortedColumns) {
175
- let startCol = col.start.line === i ? col.start.column : 0;
176
- let endCol =
177
- (col.end.line === i
178
- ? col.end.column
179
- : originalLine.length - (lastCol || 1)) + 1;
180
-
181
- if (startCol - lastCol > 0) {
182
- let whitespace = originalLine
183
- .substring(lastCol, startCol)
184
- .replace(TAB_REPLACE_REGEX, TAB_REPLACEMENT)
185
- .replace(/\S/g, ' ');
186
-
187
- highlightLine += whitespace;
188
- }
215
+ // Calculate the startColumn and get the real width by doing a substring of the original
216
+ // line and replacing tabs with our tab replacement to support tab handling
217
+ let startCol = 0;
218
+ if (
219
+ highlight.start.line === currentLineIndex &&
220
+ highlight.start.column > colOffset
221
+ ) {
222
+ startCol = lines[currentLineIndex]
223
+ .substring(colOffset, highlight.start.column)
224
+ .replace(TAB_REPLACE_REGEX, TAB_REPLACEMENT).length;
225
+ }
189
226
 
190
- let highlightStartColumn = lastCol >= startCol ? lastCol : startCol;
191
- if (endCol - highlightStartColumn > 0) {
192
- let renderedHighlightLength = originalLine
193
- .substring(highlightStartColumn, endCol)
194
- .replace(TAB_REPLACE_REGEX, TAB_REPLACEMENT).length;
195
- highlightLine += highlighter('^'.repeat(renderedHighlightLength));
196
- lastCol = endCol;
227
+ // Calculate the endColumn and get the real width by doing a substring of the original
228
+ // line and replacing tabs with our tab replacement to support tab handling
229
+ let endCol = lineWidth - 1;
230
+ if (highlight.end.line === currentLineIndex) {
231
+ endCol = lines[currentLineIndex]
232
+ .substring(colOffset, highlight.end.column)
233
+ .replace(TAB_REPLACE_REGEX, TAB_REPLACEMENT).length;
234
+
235
+ // If the endCol is too big for this line part, trim it so we can handle it in the next one
236
+ if (endCol > lineWidth) {
237
+ endCol = lineWidth - 1;
197
238
  }
198
- }
199
239
 
200
- let endsWithFullLine = !!sortedColumns.find(h => h.end.line > i);
201
- if (!endsWithFullLine) {
202
- let sortedByEnd = sortedColumns.sort(
203
- (a, b) => a.end.column - b.end.column,
204
- );
240
+ highlightHasEnded = true;
241
+ }
205
242
 
206
- let lastHighlightForColumn = sortedByEnd[sortedByEnd.length - 1];
207
- if (lastHighlightForColumn.message) {
208
- highlightLine +=
209
- ' ' + highlighter(lastHighlightForColumn.message, true);
243
+ // If endcol is smaller than lastCol it overlaps with another highlight and is no longer visible, we can skip those
244
+ if (endCol >= lastCol) {
245
+ let characters = endCol - startCol + 1;
246
+ if (startCol > lastCol) {
247
+ // startCol is before lastCol, so add spaces as padding before the highlight indicators
248
+ highlightLine += ' '.repeat(startCol - lastCol);
249
+ } else if (lastCol > startCol) {
250
+ // If last column is larger than the start, there's overlap in highlights
251
+ // This line adjusts the characters count to ensure we don't add too many characters
252
+ characters += startCol - lastCol;
210
253
  }
254
+
255
+ // Append the highlight indicators
256
+ highlightLine += highlighter('^'.repeat(characters));
257
+
258
+ // Set the lastCol equal to character count between start of line part and highlight end-column
259
+ lastCol = endCol + 1;
260
+ }
261
+
262
+ // There's no point in processing more highlights if we reached the end of the line
263
+ if (endCol >= lineEndCol - 1) {
264
+ break;
211
265
  }
212
266
  }
213
267
 
214
- resultLines.push(highlightLine);
268
+ // Append the highlight message if the current highlights ends on this line part
269
+ if (highlight && highlight.message && highlightHasEnded) {
270
+ highlightLine += ' ' + highlighter(highlight.message, true);
271
+ }
272
+ }
273
+
274
+ if (highlightLine) {
275
+ resultLines.push(
276
+ lineNumberPrefixer({
277
+ lineNumberLength,
278
+ isHighlighted: true,
279
+ }) + highlightLine,
280
+ );
215
281
  }
216
282
  }
217
283
 
@@ -1,4 +1,6 @@
1
1
  import assert from 'assert';
2
+ import {readFileSync} from 'fs';
3
+ import {join as joinPath} from 'path';
2
4
 
3
5
  import codeframe from '../src/codeframe';
4
6
 
@@ -393,7 +395,7 @@ describe('codeframe', () => {
393
395
  assert.equal(lines[7], ' 9 | test');
394
396
  });
395
397
 
396
- it('should properly pad numbers', () => {
398
+ it('should properly pad numbers for large files', () => {
397
399
  let codeframeString = codeframe('test\n'.repeat(1000), [
398
400
  {
399
401
  start: {
@@ -415,17 +417,22 @@ describe('codeframe', () => {
415
417
  column: 2,
416
418
  line: 100,
417
419
  },
418
- message: 'test',
420
+ message: 'test 2',
419
421
  },
420
422
  ]);
421
423
 
422
424
  let lines = codeframeString.split(LINE_END);
423
425
  assert.equal(lines.length, 7);
424
- assert.equal(lines[0], ' 98 | test');
426
+ assert.equal(lines[0], ' 98 | test');
427
+ assert.equal(lines[1], '> 99 | test');
428
+ assert.equal(lines[2], '> | ^ test');
429
+ assert.equal(lines[3], '> 100 | test');
430
+ assert.equal(lines[4], '> | ^ test 2');
431
+ assert.equal(lines[5], ' 101 | test');
425
432
  assert.equal(lines[6], ' 102 | test');
426
433
  });
427
434
 
428
- it('should properly pad numbers', () => {
435
+ it('should properly pad numbers for short files', () => {
429
436
  let codeframeString = codeframe('test\n'.repeat(1000), [
430
437
  {
431
438
  start: {
@@ -453,13 +460,17 @@ describe('codeframe', () => {
453
460
 
454
461
  let lines = codeframeString.split(LINE_END);
455
462
  assert.equal(lines.length, 11);
456
- assert.equal(lines[0], ' 6 | test');
463
+ assert.equal(lines[0], ' 6 | test');
464
+ assert.equal(lines[4], ' 9 | test');
465
+ assert.equal(lines[5], ' 10 | test');
466
+ assert.equal(lines[6], ' 11 | test');
457
467
  assert.equal(lines[10], ' 14 | test');
458
468
  });
459
469
 
460
470
  it('should properly use maxLines', () => {
471
+ let line = 'test '.repeat(100);
461
472
  let codeframeString = codeframe(
462
- 'test\n'.repeat(100),
473
+ `${line}\n`.repeat(100),
463
474
  [
464
475
  {
465
476
  start: {
@@ -487,14 +498,16 @@ describe('codeframe', () => {
487
498
  {
488
499
  useColor: false,
489
500
  maxLines: 10,
501
+ terminalWidth: 5,
490
502
  },
491
503
  );
492
504
 
493
505
  let lines = codeframeString.split(LINE_END);
494
506
  assert.equal(lines.length, 13);
495
- assert.equal(lines[0], ' 4 | test');
496
- assert.equal(lines[11], '> 13 | test');
497
- assert.equal(lines[12], '> | ^^^^');
507
+ assert.equal(lines[0], ' 4 | test test ');
508
+ assert.equal(lines[7], ' 10 | test test ');
509
+ assert.equal(lines[11], '> 13 | test test ');
510
+ assert.equal(lines[12], '> | ^^^^^^^^^^');
498
511
  });
499
512
 
500
513
  it('should be able to handle tabs', () => {
@@ -507,7 +520,7 @@ describe('codeframe', () => {
507
520
  line: 1,
508
521
  },
509
522
  end: {
510
- column: 7,
523
+ column: 8,
511
524
  line: 1,
512
525
  },
513
526
  message: 'test',
@@ -518,7 +531,7 @@ describe('codeframe', () => {
518
531
 
519
532
  let lines = codeframeString.split(LINE_END);
520
533
  assert.equal(lines[0], '> 1 | hel lo wor ld');
521
- assert.equal(lines[1], '> | ^^^ test');
534
+ assert.equal(lines[1], '> | ^^^^ test');
522
535
  assert.equal(lines[2], ' 2 | Enjoy thi s nice cod eframe');
523
536
  });
524
537
 
@@ -585,4 +598,202 @@ describe('codeframe', () => {
585
598
  assert.equal(lines[4], '> 3 | test');
586
599
  assert.equal(lines[5], '> | ^^ test');
587
600
  });
601
+
602
+ it('Should truncate long lines and print message', () => {
603
+ let originalLine = 'hello world '.repeat(1000);
604
+ let codeframeString = codeframe(
605
+ originalLine,
606
+ [
607
+ {
608
+ start: {
609
+ column: 1000,
610
+ line: 1,
611
+ },
612
+ end: {
613
+ column: 1200,
614
+ line: 1,
615
+ },
616
+ message: 'This is a message',
617
+ },
618
+ ],
619
+ {useColor: false, terminalWidth: 25},
620
+ );
621
+
622
+ let lines = codeframeString.split(LINE_END);
623
+ assert.equal(lines.length, 2);
624
+ assert.equal(lines[0], '> 1 | d hello world hello');
625
+ assert.equal(lines[1], '> | ^^^^^^^^^^^^^^ This is a message');
626
+ });
627
+
628
+ it('Truncation across multiple lines', () => {
629
+ let originalLine =
630
+ 'hello world '.repeat(100) + '\n' + 'new line '.repeat(100);
631
+ let codeframeString = codeframe(
632
+ originalLine,
633
+ [
634
+ {
635
+ start: {
636
+ column: 15,
637
+ line: 1,
638
+ },
639
+ end: {
640
+ column: 400,
641
+ line: 1,
642
+ },
643
+ message: 'This is the first line',
644
+ },
645
+ {
646
+ start: {
647
+ column: 2,
648
+ line: 2,
649
+ },
650
+ end: {
651
+ column: 100,
652
+ line: 2,
653
+ },
654
+ message: 'This is the second line',
655
+ },
656
+ ],
657
+ {useColor: false, terminalWidth: 25},
658
+ );
659
+
660
+ let lines = codeframeString.split(LINE_END);
661
+ assert.equal(lines.length, 4);
662
+ assert.equal(lines[0], '> 1 | ld hello world hell');
663
+ assert.equal(lines[1], '> | ^^^^^^^^^^^^^^ This is the first line');
664
+ assert.equal(lines[2], '> 2 | new line new line n');
665
+ assert.equal(lines[3], '> | ^^^^^^^^^^^^^^^^^^ This is the second line');
666
+ });
667
+
668
+ it('Truncation across various types and positions of highlights', () => {
669
+ let originalLine =
670
+ 'hello world '.repeat(100) + '\n' + 'new line '.repeat(100);
671
+ let codeframeString = codeframe(
672
+ originalLine,
673
+ [
674
+ {
675
+ start: {
676
+ column: 2,
677
+ line: 1,
678
+ },
679
+ end: {
680
+ column: 5,
681
+ line: 1,
682
+ },
683
+ },
684
+ {
685
+ start: {
686
+ column: 6,
687
+ line: 1,
688
+ },
689
+ end: {
690
+ column: 10,
691
+ line: 1,
692
+ },
693
+ message: 'I have a message',
694
+ },
695
+ {
696
+ start: {
697
+ column: 15,
698
+ line: 1,
699
+ },
700
+ end: {
701
+ column: 25,
702
+ line: 1,
703
+ },
704
+ message: 'I also have a message',
705
+ },
706
+ {
707
+ start: {
708
+ column: 2,
709
+ line: 2,
710
+ },
711
+ end: {
712
+ column: 5,
713
+ line: 2,
714
+ },
715
+ message: 'This is the second line',
716
+ },
717
+ ],
718
+ {useColor: false, terminalWidth: 25},
719
+ );
720
+
721
+ let lines = codeframeString.split(LINE_END);
722
+ assert.equal(lines.length, 4);
723
+ assert.equal(lines[0], '> 1 | hello world hello w');
724
+ assert.equal(lines[1], '> | ^^^^^^^^^ ^^^^^ I also have a message');
725
+ assert.equal(lines[2], '> 2 | new line new line n');
726
+ assert.equal(lines[3], '> | ^^^^ This is the second line');
727
+ });
728
+
729
+ it('Multi-line highlight w/ truncation', () => {
730
+ let originalLine =
731
+ 'hello world '.repeat(100) + '\n' + 'new line '.repeat(100);
732
+ let codeframeString = codeframe(
733
+ originalLine,
734
+ [
735
+ {
736
+ start: {
737
+ column: 2,
738
+ line: 1,
739
+ },
740
+ end: {
741
+ column: 151,
742
+ line: 2,
743
+ },
744
+ message: 'I have a message',
745
+ },
746
+ ],
747
+ {useColor: false, terminalWidth: 25},
748
+ );
749
+
750
+ let lines = codeframeString.split(LINE_END);
751
+ assert.equal(lines.length, 4);
752
+ assert.equal(lines[0], '> 1 | hello world hello w');
753
+ assert.equal(lines[1], '> | ^^^^^^^^^^^^^^^^^^');
754
+ assert.equal(lines[2], '> 2 | ew line new line ne');
755
+ assert.equal(lines[3], '> | ^^^^^^ I have a message');
756
+ });
757
+
758
+ it('Should pad properly, T-650', () => {
759
+ let fileContent = readFileSync(
760
+ joinPath(__dirname, './fixtures/a.js'),
761
+ 'utf8',
762
+ );
763
+ let codeframeString = codeframe(
764
+ fileContent,
765
+ [
766
+ {
767
+ start: {
768
+ line: 8,
769
+ column: 10,
770
+ },
771
+ end: {
772
+ line: 8,
773
+ column: 48,
774
+ },
775
+ },
776
+ ],
777
+ {
778
+ useColor: false,
779
+ syntaxHighlighting: false,
780
+ language: 'js',
781
+ terminalWidth: 100,
782
+ },
783
+ );
784
+
785
+ let lines = codeframeString.split(LINE_END);
786
+ assert.equal(lines.length, 5);
787
+ assert.equal(lines[0], ` 7 | import Tooltip from '../tooltip';`);
788
+ assert.equal(
789
+ lines[1],
790
+ `> 8 | import VisuallyHidden from '../visually-hidden';`,
791
+ );
792
+ assert.equal(
793
+ lines[2],
794
+ '> | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^',
795
+ );
796
+ assert.equal(lines[3], ' 9 | ');
797
+ assert.equal(lines[4], ' 10 | /**');
798
+ });
588
799
  });
@@ -0,0 +1,13 @@
1
+ import test from 'test';
2
+ import component from './component';
3
+
4
+ /**
5
+ * This is a comment
6
+ */
7
+ import Tooltip from '../tooltip';
8
+ import VisuallyHidden from '../visually-hidden';
9
+
10
+ /**
11
+ * This is another comment
12
+ */
13
+ import {Label} from './label';