diff --git a/packages/vite/src/node/__tests__/__snapshots__/utils.spec.ts.snap b/packages/vite/src/node/__tests__/__snapshots__/utils.spec.ts.snap index ccf43c253a01b3..0f108ecdedd244 100644 --- a/packages/vite/src/node/__tests__/__snapshots__/utils.spec.ts.snap +++ b/packages/vite/src/node/__tests__/__snapshots__/utils.spec.ts.snap @@ -213,3 +213,16 @@ exports[`generateCodeFrames > works with CRLF 1`] = ` | ^ " `; + +exports[`generateCodeFrames > works with CRLF given a range 1`] = ` +" +1 | import foo from './foo' +2 | +3 | foo() + | ^^^^^ +4 | // 1 + | ^^^^ +5 | // 2 + | ^^ +" +`; diff --git a/packages/vite/src/node/__tests__/utils.spec.ts b/packages/vite/src/node/__tests__/utils.spec.ts index 9f845e0c210e0f..f36e49521e94a5 100644 --- a/packages/vite/src/node/__tests__/utils.spec.ts +++ b/packages/vite/src/node/__tests__/utils.spec.ts @@ -279,6 +279,10 @@ describe('posToNumber', () => { const actual = posToNumber('a\n\nb', { line: 3, column: 0 }) expect(actual).toBe(3) }) + test('crlf', () => { + const actual = posToNumber('a\r\nb', { line: 2, column: 0 }) + expect(actual).toBe(3) + }) test('out of range', () => { const actual = posToNumber('a\nb', { line: 4, column: 0 }) expect(actual).toBe(4) @@ -361,6 +365,25 @@ foo() expectSnapshot(generateCodeFrame(sourceCrLf, { line: 2, column: 0 })) }) + test('works with CRLF given an offset', () => { + const longSourceCrLf = longSource.replaceAll('\n', '\r\n') + // the frame should point to the same location regardless of the line endings + expect( + generateCodeFrame(longSourceCrLf, longSourceCrLf.indexOf('// 3')), + ).toBe(generateCodeFrame(longSource, longSource.indexOf('// 3'))) + }) + + test('works with CRLF given a range', () => { + const longSourceCrLf = longSource.replaceAll('\n', '\r\n') + expectSnapshot( + generateCodeFrame( + longSourceCrLf, + longSourceCrLf.indexOf('foo()'), + longSourceCrLf.indexOf('// 2'), + ), + ) + }) + test('end', () => { expectSnapshot(generateCodeFrame(source, 0, 0)) expectSnapshot(generateCodeFrame(source, 0, 23)) diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index 779020aaa0fda9..7c2c4ba830be0b 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -481,6 +481,14 @@ export const splitRE: RegExp = /\r?\n/g const range: number = 2 +/** + * `splitRE` treats both LF and CRLF as a line terminator, so the terminator + * following a line is 2 characters long when the source uses CRLF. + */ +function lineTerminatorLengthAt(source: string, index: number): number { + return source[index] === '\r' ? 2 : 1 +} + export function pad(source: string, n = 2): string { const lines = source.split(splitRE) return lines.map((l) => ` `.repeat(n) + l).join(`\n`) @@ -499,7 +507,8 @@ export function posToNumber(source: string, pos: number | Pos): number { const { line, column } = pos let start = 0 for (let i = 0; i < line - 1 && i < lines.length; i++) { - start += lines[i].length + 1 + start += lines[i].length + start += lineTerminatorLengthAt(source, start) } return start + column } @@ -592,12 +601,12 @@ export function generateCodeFrame( const underline = '^'.repeat(Math.min(length, MAX_DISPLAY_LEN)) res.push(`${' '.repeat(lineNumberWidth)}| ` + underline) } - count += lineLength + 1 + count += lineTerminatorLengthAt(source, count) + lineLength } } break } - count++ + count += lineTerminatorLengthAt(source, count) } return res.join('\n') }