Files
Alexander KireyevandGitHub 64117f22bc Fix Input#origin() mixing null and undefined for unmapped end position (#2106)
When the requested end position has no mapping in the source map,
originalPositionFor() returns an object with line/column set to null
rather than omitting them. endLine picked that null up correctly, but
endColumn was computed as `to && to.column + 1`, which due to operator
precedence is `to && (to.column + 1)`, turning null into 1. So callers
could get back { endColumn: 1, endLine: null }, which doesn't match
how every other unmapped case in this function behaves (undefined for
both). Looks like the +1 fix in #2036 covered the mapped case but not
this one.

Guard `to` the same way `from` already is, so an unmapped end position
just stays undefined.
2026-06-30 23:14:26 +02:00

137 lines
3.8 KiB
TypeScript

import { join } from 'node:path'
import { pathToFileURL } from 'node:url'
import { SourceNode } from 'source-map-js'
import { test } from 'uvu'
import { equal, is } from 'uvu/assert'
import { Input } from '../lib/postcss.js'
function urlOf(file: string): string {
return pathToFileURL(join(__dirname, file)).toString()
}
test('fromLineAndColumn() returns offset', () => {
let input = new Input('a {\n}')
is(input.fromLineAndColumn(1, 1), 0)
is(input.fromLineAndColumn(1, 3), 2)
is(input.fromLineAndColumn(2, 1), 4)
is(input.fromLineAndColumn(2, 2), 5)
})
test('fromOffset() returns line and column', () => {
let input = new Input('a {\n}')
equal(input.fromOffset(0), { col: 1, line: 1 })
equal(input.fromOffset(2), { col: 3, line: 1 })
equal(input.fromOffset(4), { col: 1, line: 2 })
equal(input.fromOffset(5), { col: 2, line: 2 })
})
test('origin() returns false without source map', () => {
let input = new Input('a {\n}')
is(input.origin(1, 1), false)
})
test('origin() returns source position with source map', () => {
// @ts-expect-error source-map-js accepts null, but it's not in the types
let node = new SourceNode(null, null, null, [
new SourceNode(1, 0, 'a.css', 'a'),
new SourceNode(1, 1, 'a.css', ' '),
new SourceNode(1, 2, 'a.css', '{'),
new SourceNode(1, 3, 'a.css', '}'),
'\n',
new SourceNode(1, 0, 'b.css', 'b'),
new SourceNode(1, 1, 'b.css', ' '),
new SourceNode(1, 2, 'b.css', '{'),
new SourceNode(1, 3, 'b.css', '}'),
new SourceNode(1, 4, 'b.css', '\n'),
new SourceNode(2, 0, 'b.css', 'c'),
new SourceNode(2, 1, 'b.css', ' '),
new SourceNode(2, 2, 'b.css', '{'),
new SourceNode(2, 3, 'b.css', '}')
])
let from = join(__dirname, 'all.css')
let codeWithSourceMap = node.toStringWithSourceMap({ file: from })
let input = new Input(codeWithSourceMap.code, {
from,
map: { prev: codeWithSourceMap.map }
})
equal(input.origin(1, 1), {
column: 1,
endColumn: undefined,
endLine: undefined,
file: join(__dirname, 'a.css'),
line: 1,
url: urlOf('a.css')
})
equal(input.origin(1, 4), {
column: 4,
endColumn: undefined,
endLine: undefined,
file: join(__dirname, 'a.css'),
line: 1,
url: urlOf('a.css')
})
equal(input.origin(2, 1), {
column: 1,
endColumn: undefined,
endLine: undefined,
file: join(__dirname, 'b.css'),
line: 1,
url: urlOf('b.css')
})
equal(input.origin(2, 4), {
column: 4,
endColumn: undefined,
endLine: undefined,
file: join(__dirname, 'b.css'),
line: 1,
url: urlOf('b.css')
})
equal(input.origin(3, 1), {
column: 1,
endColumn: undefined,
endLine: undefined,
file: join(__dirname, 'b.css'),
line: 2,
url: urlOf('b.css')
})
equal(input.origin(2, 1, 2, 4), {
column: 1,
endColumn: 4,
endLine: 1,
file: join(__dirname, 'b.css'),
line: 1,
url: urlOf('b.css')
})
})
test('origin() does not mix undefined and null when end position is unmapped', () => {
// @ts-expect-error source-map-js accepts null, but it's not in the types
let node = new SourceNode(null, null, null, [
new SourceNode(1, 0, 'a.css', 'a'),
new SourceNode(1, 1, 'a.css', ' '),
new SourceNode(1, 2, 'a.css', '{'),
new SourceNode(1, 3, 'a.css', '}')
])
let from = join(__dirname, 'all.css')
let codeWithSourceMap = node.toStringWithSourceMap({ file: from })
let input = new Input(codeWithSourceMap.code, {
from,
map: { prev: codeWithSourceMap.map }
})
// The start position (1, 1) is mapped, but the requested end position
// (99, 1) is far beyond anything the source map covers, so
// `originalPositionFor()` cannot resolve it.
equal(input.origin(1, 1, 99, 1), {
column: 1,
endColumn: undefined,
endLine: undefined,
file: join(__dirname, 'a.css'),
line: 1,
url: urlOf('a.css')
})
})
test.run()