Track locations when printing

This commit is contained in:
Jordan Pittman 2025-04-16 17:48:26 -04:00
parent fcd4e2d2bc
commit 6febfd966a
3 changed files with 168 additions and 2 deletions

View file

@ -0,0 +1,28 @@
import { bench } from 'vitest'
import { toCss } from './ast'
import * as CSS from './css-parser'
const css = String.raw
const input = css`
@theme {
--color-primary: #333;
}
@tailwind utilities;
.foo {
color: red;
/* comment */
&:hover {
color: blue;
@apply font-bold;
}
}
`
const ast = CSS.parse(input)
bench('toCss', () => {
toCss(ast)
})
bench('toCss with source maps', () => {
toCss(ast, true)
})

View file

@ -714,7 +714,14 @@ export function optimizeAst(
return newAst
}
export function toCss(ast: AstNode[]) {
export function toCss(ast: AstNode[], track?: boolean) {
let pos = 0
let source: Source = {
file: null,
code: '',
}
function stringify(node: AstNode, depth = 0): string {
let css = ''
let indent = ' '.repeat(depth)
@ -722,15 +729,70 @@ export function toCss(ast: AstNode[]) {
// Declaration
if (node.kind === 'declaration') {
css += `${indent}${node.property}: ${node.value}${node.important ? ' !important' : ''};\n`
if (track) {
// indent
pos += indent.length
// node.property
let start = pos
pos += node.property.length
// `: `
pos += 2
// node.value
pos += node.value?.length ?? 0
// !important
if (node.important) {
pos += 11
}
let end = pos
// `;\n`
pos += 2
node.dst = [source, start, end]
}
}
// Rule
else if (node.kind === 'rule') {
css += `${indent}${node.selector} {\n`
if (track) {
// indent
pos += indent.length
// node.selector
let start = pos
pos += node.selector.length
// ` `
pos += 1
let end = pos
node.dst = [source, start, end]
// `{\n`
pos += 2
}
for (let child of node.nodes) {
css += stringify(child, depth + 1)
}
css += `${indent}}\n`
if (track) {
// indent
pos += indent.length
// `}\n`
pos += 2
}
}
// AtRule
@ -744,19 +806,93 @@ export function toCss(ast: AstNode[]) {
// ```
if (node.nodes.length === 0) {
let css = `${indent}${node.name} ${node.params};\n`
if (track) {
// indent
pos += indent.length
// node.name
let start = pos
pos += node.name.length
// ` `
pos += 1
// node.params
pos += node.params.length
let end = pos
// `;\n`
pos += 2
node.dst = [source, start, end]
}
return css
}
css += `${indent}${node.name}${node.params ? ` ${node.params} ` : ' '}{\n`
if (track) {
// indent
pos += indent.length
// node.name
let start = pos
pos += node.name.length
if (node.params) {
// ` `
pos += 1
// node.params
pos += node.params.length
}
// ` `
pos += 1
let end = pos
node.dst = [source, start, end]
// `{\n`
pos += 2
}
for (let child of node.nodes) {
css += stringify(child, depth + 1)
}
css += `${indent}}\n`
if (track) {
// indent
pos += indent.length
// `}\n`
pos += 2
}
}
// Comment
else if (node.kind === 'comment') {
css += `${indent}/*${node.value}*/\n`
if (track) {
// indent
pos += indent.length
// The comment itself. We do this instead of just the inside because
// it seems more useful to have the entire comment span tracked.
let start = pos
pos += 2 + node.value.length + 2
let end = pos
node.dst = [source, start, end]
// `\n`
pos += 1
}
}
// These should've been handled already by `optimizeAst` which
@ -780,6 +916,8 @@ export function toCss(ast: AstNode[]) {
css += stringify(node, 0)
}
source.code = css
return css
}

View file

@ -814,7 +814,7 @@ export async function compile(
return compiledCss
}
compiledCss = toCss(newAst)
compiledCss = toCss(newAst, !!opts.from)
compiledAst = newAst
return compiledCss