diff --git a/packages/vite/src/node/__tests__/__snapshots__/utils.spec.ts.snap b/packages/vite/src/node/__tests__/__snapshots__/utils.spec.ts.snap new file mode 100644 index 00000000000000..69d8fe2143be4c --- /dev/null +++ b/packages/vite/src/node/__tests__/__snapshots__/utils.spec.ts.snap @@ -0,0 +1,93 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`generateCodeFrames > end 1`] = ` +" +1 | import foo from './foo' + | ^ +2 | foo() +" +`; + +exports[`generateCodeFrames > end 2`] = ` +" +1 | import foo from './foo' + | ^^^^^^^^^^^^^^^^^^^^^^^ +2 | foo() +" +`; + +exports[`generateCodeFrames > end 3`] = ` +" +1 | import foo from './foo' + | ^^^^^^^^^^^^^^^^^^^^^^^ +2 | foo() + | ^^^^^ +" +`; + +exports[`generateCodeFrames > range 1`] = ` +" +1 | +2 | import foo from './foo' +3 | + | ^ +4 | foo() +5 | +" +`; + +exports[`generateCodeFrames > start with number 1`] = ` +" +1 | import foo from './foo' + | ^ +2 | foo() +" +`; + +exports[`generateCodeFrames > start with number 2`] = ` +" +1 | import foo from './foo' + | ^ +2 | foo() +" +`; + +exports[`generateCodeFrames > start with number 3`] = ` +" +1 | import foo from './foo' +2 | foo() + | ^ +" +`; + +exports[`generateCodeFrames > start with postion 1`] = ` +" +1 | import foo from './foo' + | ^ +2 | foo() +" +`; + +exports[`generateCodeFrames > start with postion 2`] = ` +" +1 | import foo from './foo' + | ^ +2 | foo() +" +`; + +exports[`generateCodeFrames > start with postion 3`] = ` +" +1 | import foo from './foo' +2 | foo() + | ^ +" +`; + +exports[`generateCodeFrames > works with CRLF 1`] = ` +" +1 | import foo from './foo' +2 | foo() + | ^ +" +`; diff --git a/packages/vite/src/node/__tests__/utils.spec.ts b/packages/vite/src/node/__tests__/utils.spec.ts index 18d55b9f18b6fc..05bb953e239651 100644 --- a/packages/vite/src/node/__tests__/utils.spec.ts +++ b/packages/vite/src/node/__tests__/utils.spec.ts @@ -5,6 +5,7 @@ import { asyncFlatten, bareImportRE, flattenId, + generateCodeFrame, getHash, getLocalhostAddressIfDiffersFromDNS, injectQuery, @@ -176,6 +177,59 @@ describe('posToNumber', () => { }) }) +describe('generateCodeFrames', () => { + const source = ` +import foo from './foo' +foo() +`.trim() + const sourceCrLf = source.replace(/\n/, '\r\n') + const longSource = ` +import foo from './foo' + +foo() + +// bar +// baz + ` + + const expectSnapshot = (value: string) => { + try { + // add new line to make snapshot easier to read + expect('\n' + value + '\n').toMatchSnapshot() + } catch (e) { + // don't include this function in stacktrace + Error.captureStackTrace(e, expectSnapshot) + throw e + } + } + + test('start with number', () => { + expectSnapshot(generateCodeFrame(source, 0)) + expectSnapshot(generateCodeFrame(source, 1)) + expectSnapshot(generateCodeFrame(source, 24)) + }) + + test('start with postion', () => { + expectSnapshot(generateCodeFrame(source, { line: 1, column: 0 })) + expectSnapshot(generateCodeFrame(source, { line: 1, column: 1 })) + expectSnapshot(generateCodeFrame(source, { line: 2, column: 0 })) + }) + + test('works with CRLF', () => { + expectSnapshot(generateCodeFrame(sourceCrLf, { line: 2, column: 0 })) + }) + + test('end', () => { + expectSnapshot(generateCodeFrame(source, 0, 0)) + expectSnapshot(generateCodeFrame(source, 0, 23)) + expectSnapshot(generateCodeFrame(source, 0, 29)) + }) + + test('range', () => { + expectSnapshot(generateCodeFrame(longSource, { line: 3, column: 0 })) + }) +}) + describe('getHash', () => { test('8-digit hex', () => { const hash = getHash(Buffer.alloc(0)) diff --git a/packages/vite/src/node/plugins/css.ts b/packages/vite/src/node/plugins/css.ts index c7c5bd4bf9cacd..1fdc0e204e4793 100644 --- a/packages/vite/src/node/plugins/css.ts +++ b/packages/vite/src/node/plugins/css.ts @@ -1185,13 +1185,21 @@ async function compileCSS( deps.add(files[i]) } } else if (message.type === 'warning') { - let msg = `[vite:css] ${message.text}` - if (message.line && message.column) { - msg += `\n${generateCodeFrame(code, { - line: message.line, - column: message.column, - })}` - } + const warning = message as PostCSS.Warning + let msg = `[vite:css] ${warning.text}` + msg += `\n${generateCodeFrame( + code, + { + line: warning.line, + column: warning.column, + }, + warning.endLine !== undefined && warning.endColumn !== undefined + ? { + line: warning.endLine, + column: warning.endColumn, + } + : undefined, + )}` config.logger.warn(colors.yellow(msg)) } } diff --git a/packages/vite/src/node/plugins/esbuild.ts b/packages/vite/src/node/plugins/esbuild.ts index 65d173f5b15266..6f37877e6b07aa 100644 --- a/packages/vite/src/node/plugins/esbuild.ts +++ b/packages/vite/src/node/plugins/esbuild.ts @@ -433,15 +433,7 @@ export function resolveEsbuildTranspileOptions( function prettifyMessage(m: Message, code: string): string { let res = colors.yellow(m.text) if (m.location) { - const lines = code.split(/\r?\n/g) - const line = Number(m.location.line) - const column = Number(m.location.column) - const offset = - lines - .slice(0, line - 1) - .map((l) => l.length) - .reduce((total, l) => total + l + 1, 0) + column - res += `\n` + generateCodeFrame(code, offset, offset + 1) + res += `\n` + generateCodeFrame(code, m.location) } return res + `\n` } diff --git a/packages/vite/src/node/plugins/html.ts b/packages/vite/src/node/plugins/html.ts index f12531bdc2c698..1912143b70fdde 100644 --- a/packages/vite/src/node/plugins/html.ts +++ b/packages/vite/src/node/plugins/html.ts @@ -244,7 +244,11 @@ function formatParseError(parserError: ParserError, id: string, html: string) { const formattedError = { code: parserError.code, message: `parse5 error code ${parserError.code}`, - frame: generateCodeFrame(html, parserError.startOffset), + frame: generateCodeFrame( + html, + parserError.startOffset, + parserError.endOffset, + ), loc: { file: id, line: parserError.startLine, diff --git a/packages/vite/src/node/plugins/importAnalysis.ts b/packages/vite/src/node/plugins/importAnalysis.ts index 18dbebf0376e39..528b68a221dcc5 100644 --- a/packages/vite/src/node/plugins/importAnalysis.ts +++ b/packages/vite/src/node/plugins/importAnalysis.ts @@ -640,7 +640,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { `\n` + colors.cyan(importerModule.file) + `\n` + - colors.reset(generateCodeFrame(source, start)) + + colors.reset(generateCodeFrame(source, start, end)) + colors.yellow( `\nThe above dynamic import cannot be analyzed by Vite.\n` + `See ${colors.blue( diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index 1197693d7a5cc1..64526eb3ebd3ea 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -460,10 +460,14 @@ export function pad(source: string, n = 2): string { return lines.map((l) => ` `.repeat(n) + l).join(`\n`) } -export function posToNumber( - source: string, - pos: number | { line: number; column: number }, -): number { +type Pos = { + /** 1-based */ + line: number + /** 0-based */ + column: number +} + +export function posToNumber(source: string, pos: number | Pos): number { if (typeof pos === 'number') return pos const lines = source.split(splitRE) const { line, column } = pos @@ -474,10 +478,7 @@ export function posToNumber( return start + column } -export function numberToPos( - source: string, - offset: number | { line: number; column: number }, -): { line: number; column: number } { +export function numberToPos(source: string, offset: number | Pos): Pos { if (typeof offset !== 'number') return offset if (offset > source.length) { throw new Error( @@ -501,16 +502,16 @@ export function numberToPos( export function generateCodeFrame( source: string, - start: number | { line: number; column: number } = 0, - end?: number, + start: number | Pos = 0, + end?: number | Pos, ): string { start = posToNumber(source, start) - end = end || start + end = end !== undefined ? posToNumber(source, end) : start const lines = source.split(splitRE) let count = 0 const res: string[] = [] for (let i = 0; i < lines.length; i++) { - count += lines[i].length + 1 + count += lines[i].length if (count >= start) { for (let j = i - range; j <= i + range || end > count; j++) { if (j < 0 || j >= lines.length) continue @@ -523,7 +524,7 @@ export function generateCodeFrame( const lineLength = lines[j].length if (j === i) { // push underline - const pad = Math.max(start - (count - lineLength) + 1, 0) + const pad = Math.max(start - (count - lineLength), 0) const length = Math.max( 1, end > count ? lineLength - pad : end - start, @@ -539,6 +540,7 @@ export function generateCodeFrame( } break } + count++ } return res.join('\n') }