@parcel/codeframe 2.0.0-nightly.122 → 2.0.0-nightly.1222

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/package.json CHANGED
@@ -1,11 +1,15 @@
1
1
  {
2
2
  "name": "@parcel/codeframe",
3
- "version": "2.0.0-nightly.122+04f2e662",
3
+ "version": "2.0.0-nightly.1222+da6cbb363",
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,23 @@
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"
21
+ },
22
+ "targets": {
23
+ "main": {
24
+ "includeNodeModules": {
25
+ "chalk": false
26
+ }
27
+ }
17
28
  },
18
29
  "dependencies": {
19
- "chalk": "^2.4.2",
20
- "emphasize": "^2.1.0"
30
+ "chalk": "^4.1.0"
31
+ },
32
+ "devDependencies": {
33
+ "emphasize": "^4.2.0",
34
+ "slice-ansi": "^4.0.0",
35
+ "string-width": "^4.2.0",
36
+ "strip-ansi": "^6.0.0"
21
37
  },
22
- "gitHead": "04f2e662b571a9caba58363aa0ce3303a282e281"
38
+ "gitHead": "da6cbb3633d2c0e9fedb8e2af560c82504d05ddc"
23
39
  }
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,21 +68,23 @@ 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
 
86
86
  // Make columns/lines start at 1
87
+ let originalHighlights = highlights;
87
88
  highlights = highlights.map(h => {
88
89
  return {
89
90
  start: {
@@ -98,6 +99,7 @@ export default function codeFrame(
98
99
  };
99
100
  });
100
101
 
102
+ // Find first and last highlight
101
103
  let firstHighlight =
102
104
  highlights.length > 1
103
105
  ? highlights.sort((a, b) => a.start.line - b.start.line)[0]
@@ -107,113 +109,191 @@ export default function codeFrame(
107
109
  ? highlights.sort((a, b) => b.end.line - a.end.line)[0]
108
110
  : highlights[0];
109
111
 
112
+ // Calculate first and last line index of codeframe
110
113
  let startLine = firstHighlight.start.line - opts.padding.before;
111
114
  startLine = startLine < 0 ? 0 : startLine;
112
- let endLine = lastHighlight.end.line + opts.padding.after;
113
- endLine =
114
- endLine - startLine > opts.maxLines
115
- ? startLine + opts.maxLines - 1
116
- : endLine;
117
- let endLineString = endLine.toString(10);
115
+ let endLineIndex = lastHighlight.end.line + opts.padding.after;
116
+ let tail;
117
+ if (endLineIndex - startLine > opts.maxLines) {
118
+ let maxLine = startLine + opts.maxLines - 1;
119
+ highlights = highlights.filter(h => h.start.line < maxLine);
120
+ lastHighlight = highlights[0];
121
+ endLineIndex = Math.min(
122
+ maxLine,
123
+ lastHighlight.end.line + opts.padding.after,
124
+ );
125
+ tail = originalHighlights.filter(h => h.start.line > endLineIndex);
126
+ }
118
127
 
119
- let resultLines = [];
128
+ let lineNumberLength = (endLineIndex + 1).toString(10).length;
129
+
130
+ // Split input into lines and highlight syntax
120
131
  let lines = code.split(NEWLINE);
121
- let syntaxHighlightedLines = (opts.syntaxHighlighting
122
- ? highlightSyntax(code, opts.language)
123
- : code
132
+ let syntaxHighlightedLines = (
133
+ opts.syntaxHighlighting ? highlightSyntax(code, opts.language) : code
124
134
  )
125
135
  .replace(TAB_REPLACE_REGEX, TAB_REPLACEMENT)
126
136
  .split(NEWLINE);
127
- for (let i = startLine; i < lines.length; i++) {
128
- if (i > endLine) break;
129
137
 
130
- let originalLine: string = lines[i];
131
- let syntaxHighlightedLine = syntaxHighlightedLines[i];
138
+ // Loop over all lines and create codeframe
139
+ let resultLines = [];
140
+ for (
141
+ let currentLineIndex = startLine;
142
+ currentLineIndex < syntaxHighlightedLines.length;
143
+ currentLineIndex++
144
+ ) {
145
+ if (currentLineIndex > endLineIndex) break;
146
+ if (currentLineIndex > syntaxHighlightedLines.length - 1) break;
132
147
 
133
- let foundHighlights = highlights.filter(
134
- highlight => highlight.start.line <= i && highlight.end.line >= i,
135
- );
148
+ // Find highlights that need to get rendered on the current line
149
+ let lineHighlights = highlights
150
+ .filter(
151
+ highlight =>
152
+ highlight.start.line <= currentLineIndex &&
153
+ highlight.end.line >= currentLineIndex,
154
+ )
155
+ .sort(
156
+ (a, b) =>
157
+ (a.start.line < currentLineIndex ? 0 : a.start.column) -
158
+ (b.start.line < currentLineIndex ? 0 : b.start.column),
159
+ );
160
+
161
+ // Check if this line has a full line highlight
162
+ let isWholeLine =
163
+ lineHighlights.length &&
164
+ !!lineHighlights.find(
165
+ h => h.start.line < currentLineIndex && h.end.line > currentLineIndex,
166
+ );
167
+
168
+ let lineLengthLimit =
169
+ opts.terminalWidth > lineNumberLength + 7
170
+ ? opts.terminalWidth - (lineNumberLength + 5)
171
+ : 10;
172
+
173
+ // Split the line into line parts that will fit the provided terminal width
174
+ let colOffset = 0;
175
+ let lineEndCol = lineLengthLimit;
176
+ let syntaxHighlightedLine = syntaxHighlightedLines[currentLineIndex];
177
+ if (stringWidth(syntaxHighlightedLine) > lineLengthLimit) {
178
+ if (lineHighlights.length > 0) {
179
+ if (lineHighlights[0].start.line === currentLineIndex) {
180
+ colOffset = lineHighlights[0].start.column - 5;
181
+ } else if (lineHighlights[0].end.line === currentLineIndex) {
182
+ colOffset = lineHighlights[0].end.column - 5;
183
+ }
184
+ }
185
+
186
+ colOffset = colOffset > 0 ? colOffset : 0;
187
+ lineEndCol = colOffset + lineLengthLimit;
188
+
189
+ syntaxHighlightedLine = sliceAnsi(
190
+ syntaxHighlightedLine,
191
+ colOffset,
192
+ lineEndCol,
193
+ );
194
+ }
136
195
 
196
+ // Write the syntax highlighted line part
137
197
  resultLines.push(
138
198
  lineNumberPrefixer({
139
- lineNumber: (i + 1).toString(10),
140
- endLine: endLineString,
141
- isHighlighted: foundHighlights.length > 0,
199
+ lineNumber: (currentLineIndex + 1).toString(10),
200
+ lineNumberLength,
201
+ isHighlighted: lineHighlights.length > 0,
142
202
  }) + syntaxHighlightedLine,
143
203
  );
144
204
 
145
- if (foundHighlights.length > 0) {
146
- let highlightLine = lineNumberPrefixer({
147
- endLine: endLineString,
148
- isHighlighted: true,
149
- });
205
+ let lineWidth = stringWidth(syntaxHighlightedLine);
206
+ let highlightLine = '';
207
+ if (isWholeLine) {
208
+ highlightLine = highlighter('^'.repeat(lineWidth));
209
+ } else if (lineHighlights.length > 0) {
210
+ let lastCol = 0;
211
+ let highlight = null;
212
+ let highlightHasEnded = false;
150
213
 
151
- let isWholeLine = !!foundHighlights.find(
152
- h => h.start.line < i && h.end.line > i,
153
- );
214
+ for (
215
+ let highlightIndex = 0;
216
+ highlightIndex < lineHighlights.length;
217
+ highlightIndex++
218
+ ) {
219
+ // Set highlight to current highlight
220
+ highlight = lineHighlights[highlightIndex];
221
+ highlightHasEnded = false;
154
222
 
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
- }
223
+ // Calculate the startColumn and get the real width by doing a substring of the original
224
+ // line and replacing tabs with our tab replacement to support tab handling
225
+ let startCol = 0;
226
+ if (
227
+ highlight.start.line === currentLineIndex &&
228
+ highlight.start.column > colOffset
229
+ ) {
230
+ startCol = lines[currentLineIndex]
231
+ .substring(colOffset, highlight.start.column)
232
+ .replace(TAB_REPLACE_REGEX, TAB_REPLACEMENT).length;
233
+ }
234
+
235
+ // Calculate the endColumn and get the real width by doing a substring of the original
236
+ // line and replacing tabs with our tab replacement to support tab handling
237
+ let endCol = lineWidth - 1;
238
+ if (highlight.end.line === currentLineIndex) {
239
+ endCol = lines[currentLineIndex]
240
+ .substring(colOffset, highlight.end.column)
241
+ .replace(TAB_REPLACE_REGEX, TAB_REPLACEMENT).length;
189
242
 
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;
243
+ // If the endCol is too big for this line part, trim it so we can handle it in the next one
244
+ if (endCol > lineWidth) {
245
+ endCol = lineWidth - 1;
197
246
  }
198
- }
199
247
 
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
- );
248
+ highlightHasEnded = true;
249
+ }
205
250
 
206
- let lastHighlightForColumn = sortedByEnd[sortedByEnd.length - 1];
207
- if (lastHighlightForColumn.message) {
208
- highlightLine +=
209
- ' ' + highlighter(lastHighlightForColumn.message, true);
251
+ // If endcol is smaller than lastCol it overlaps with another highlight and is no longer visible, we can skip those
252
+ if (endCol >= lastCol) {
253
+ let characters = endCol - startCol + 1;
254
+ if (startCol > lastCol) {
255
+ // startCol is before lastCol, so add spaces as padding before the highlight indicators
256
+ highlightLine += ' '.repeat(startCol - lastCol);
257
+ } else if (lastCol > startCol) {
258
+ // If last column is larger than the start, there's overlap in highlights
259
+ // This line adjusts the characters count to ensure we don't add too many characters
260
+ characters += startCol - lastCol;
210
261
  }
262
+
263
+ // Append the highlight indicators
264
+ highlightLine += highlighter('^'.repeat(characters));
265
+
266
+ // Set the lastCol equal to character count between start of line part and highlight end-column
267
+ lastCol = endCol + 1;
268
+ }
269
+
270
+ // There's no point in processing more highlights if we reached the end of the line
271
+ if (endCol >= lineEndCol - 1) {
272
+ break;
211
273
  }
212
274
  }
213
275
 
214
- resultLines.push(highlightLine);
276
+ // Append the highlight message if the current highlights ends on this line part
277
+ if (highlight && highlight.message && highlightHasEnded) {
278
+ highlightLine += ' ' + highlighter(highlight.message, true);
279
+ }
215
280
  }
281
+
282
+ if (highlightLine) {
283
+ resultLines.push(
284
+ lineNumberPrefixer({
285
+ lineNumberLength,
286
+ isHighlighted: true,
287
+ }) + highlightLine,
288
+ );
289
+ }
290
+ }
291
+
292
+ let result = resultLines.join('\n');
293
+
294
+ if (tail && tail.length > 0) {
295
+ result += '\n\n' + codeFrame(code, tail, inputOpts);
216
296
  }
217
297
 
218
- return resultLines.join('\n');
298
+ return result;
219
299
  }