@parcel/codeframe 2.0.0-nightly.135 → 2.0.0-nightly.1351
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 +36032 -131
- package/lib/codeframe.js.map +1 -0
- package/package.json +20 -5
- package/src/codeframe.js +174 -94
- package/test/codeframe.test.js +222 -11
- package/test/fixtures/a.js +13 -0
package/package.json
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parcel/codeframe",
|
|
3
|
-
"version": "2.0.0-nightly.
|
|
3
|
+
"version": "2.0.0-nightly.1351+88e6db442",
|
|
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,22 @@
|
|
|
13
17
|
"main": "lib/codeframe.js",
|
|
14
18
|
"source": "src/codeframe.js",
|
|
15
19
|
"engines": {
|
|
16
|
-
"node": ">=
|
|
20
|
+
"node": ">= 12.0.0"
|
|
21
|
+
},
|
|
22
|
+
"targets": {
|
|
23
|
+
"main": {
|
|
24
|
+
"includeNodeModules": {
|
|
25
|
+
"chalk": false
|
|
26
|
+
}
|
|
27
|
+
}
|
|
17
28
|
},
|
|
18
29
|
"dependencies": {
|
|
19
|
-
"chalk": "^
|
|
20
|
-
|
|
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"
|
|
21
36
|
},
|
|
22
|
-
"gitHead": "
|
|
37
|
+
"gitHead": "88e6db442542f56c21899658dda69c652a85349a"
|
|
23
38
|
}
|
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
|
|
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
|
-
|
|
74
|
+
lineNumberLength: number,
|
|
75
75
|
isHighlighted: boolean,
|
|
76
76
|
|}) => {
|
|
77
|
-
let {lineNumber,
|
|
77
|
+
let {lineNumber, lineNumberLength, isHighlighted} = params;
|
|
78
78
|
|
|
79
79
|
return `${isHighlighted ? highlighter('>') : ' '} ${
|
|
80
80
|
lineNumber
|
|
81
|
-
? lineNumber.
|
|
82
|
-
: ' '.repeat(
|
|
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
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
|
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 = (
|
|
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
|
-
|
|
131
|
-
|
|
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
|
-
|
|
134
|
-
|
|
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: (
|
|
140
|
-
|
|
141
|
-
isHighlighted:
|
|
199
|
+
lineNumber: (currentLineIndex + 1).toString(10),
|
|
200
|
+
lineNumberLength,
|
|
201
|
+
isHighlighted: lineHighlights.length > 0,
|
|
142
202
|
}) + syntaxHighlightedLine,
|
|
143
203
|
);
|
|
144
204
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
152
|
-
|
|
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
|
-
|
|
156
|
-
//
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
191
|
-
if (endCol
|
|
192
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
let sortedByEnd = sortedColumns.sort(
|
|
203
|
-
(a, b) => a.end.column - b.end.column,
|
|
204
|
-
);
|
|
248
|
+
highlightHasEnded = true;
|
|
249
|
+
}
|
|
205
250
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
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
|
|
298
|
+
return result;
|
|
219
299
|
}
|