decode and re-encode css strings

This commit is contained in:
Evan Wallace
2020-09-28 21:31:44 -07:00
parent 13a21ba2f1
commit d10cb700b4
8 changed files with 191 additions and 55 deletions

View File

@@ -1,6 +1,7 @@
package css_printer
import (
"fmt"
"strings"
"github.com/evanw/esbuild/internal/ast"
@@ -35,9 +36,11 @@ func (p *printer) printRule(rule css_ast.R, indent int, omitTrailingSemicolon bo
}
switch r := rule.(type) {
case *css_ast.RAtCharset:
// Note: It's not valid to remove the space in between these two tokens
// It's not valid to remove the space in between these two tokens
p.print("@charset ")
p.print(css_lexer.QuoteForStringToken(r.Encoding))
// It's not valid to print the string with single quotes
p.printQuotedWithQuote(r.Encoding, '"')
p.print(";")
case *css_ast.RAtNamespace:
@@ -50,7 +53,7 @@ func (p *printer) printRule(rule css_ast.R, indent int, omitTrailingSemicolon bo
if !p.RemoveWhitespace {
p.print(" ")
}
p.print(css_lexer.QuoteForStringToken(r.Path))
p.printQuoted(r.Path)
p.print(";")
case *css_ast.RAtImport:
@@ -59,7 +62,7 @@ func (p *printer) printRule(rule css_ast.R, indent int, omitTrailingSemicolon bo
} else {
p.print("@import ")
}
p.print(css_lexer.QuoteForStringToken(p.importRecords[r.ImportRecordIndex].Path.Text))
p.printQuoted(p.importRecords[r.ImportRecordIndex].Path.Text)
p.print(";")
case *css_ast.RAtKeyframes:
@@ -283,6 +286,53 @@ func (p *printer) print(text string) {
p.sb.WriteString(text)
}
func bestQuoteCharForString(text string) rune {
singleCost := 0
doubleCost := 0
for _, c := range text {
switch c {
case '\'':
singleCost++
case '"':
doubleCost++
}
}
if singleCost < doubleCost {
return '\''
}
return '"'
}
func (p *printer) printQuoted(text string) {
p.printQuotedWithQuote(text, bestQuoteCharForString(text))
}
func (p *printer) printQuotedWithQuote(text string, quote rune) {
p.sb.WriteRune(quote)
for i, c := range text {
switch c {
case 0, '\\', '\r', '\n', '\f', quote:
p.sb.WriteString(fmt.Sprintf("\\%x", c))
// Make sure the next character is not interpreted as part of the escape sequence
if next := i + 1; next < len(text) {
c = rune(text[next])
if c == ' ' || c == '\t' || (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') {
p.sb.WriteRune(' ')
}
}
default:
p.sb.WriteRune(c)
}
}
p.sb.WriteRune(quote)
}
func (p *printer) printIndent(indent int) {
for i := 0; i < indent; i++ {
p.sb.WriteString(" ")
@@ -291,7 +341,13 @@ func (p *printer) printIndent(indent int) {
func (p *printer) printTokens(tokens []css_ast.Token) {
for i, t := range tokens {
p.print(t.Text)
switch t.Kind {
case css_lexer.TString:
p.printQuoted(t.Text)
default:
p.print(t.Text)
}
if t.Children != nil {
children := *t.Children

View File

@@ -45,6 +45,39 @@ func expectPrintedMinify(t *testing.T, contents string, expected string) {
})
}
func expectPrintedString(t *testing.T, stringValue string, expected string) {
t.Helper()
t.Run(stringValue, func(t *testing.T) {
t.Helper()
p := printer{}
p.printQuoted(stringValue)
assertEqual(t, p.sb.String(), expected)
})
}
func TestStringQuote(t *testing.T) {
expectPrintedString(t, "", "\"\"")
expectPrintedString(t, "foo", "\"foo\"")
expectPrintedString(t, "f\"o", "'f\"o'")
expectPrintedString(t, "f'\"'o", "\"f'\\22'o\"")
expectPrintedString(t, "f\\o", "\"f\\5co\"")
expectPrintedString(t, "f\ro", "\"f\\do\"")
expectPrintedString(t, "f\no", "\"f\\ao\"")
expectPrintedString(t, "f\fo", "\"f\\co\"")
expectPrintedString(t, "f\r\no", "\"f\\d\\ao\"")
expectPrintedString(t, "f\r0", "\"f\\d 0\"")
expectPrintedString(t, "f\n0", "\"f\\a 0\"")
expectPrintedString(t, "f\n ", "\"f\\a \"")
expectPrintedString(t, "f\n\t", "\"f\\a \t\"")
expectPrintedString(t, "f\nf", "\"f\\a f\"")
expectPrintedString(t, "f\nF", "\"f\\a F\"")
expectPrintedString(t, "f\ng", "\"f\\ag\"")
expectPrintedString(t, "f\nG", "\"f\\aG\"")
expectPrintedString(t, "f\x00o", "\"f\\0o\"")
expectPrintedString(t, "f\x01o", "\"f\x01o\"")
expectPrintedString(t, "f\to", "\"f\to\"")
}
func TestImportant(t *testing.T) {
expectPrinted(t, "a { b: c!important }", "a {\n b: c !important;\n}\n")
expectPrinted(t, "a { b: c!important; }", "a {\n b: c !important;\n}\n")
@@ -60,19 +93,19 @@ func TestImportant(t *testing.T) {
func TestSelector(t *testing.T) {
expectPrintedMinify(t, "a + b c > d ~ e{}", "a+b c>d~e{}")
expectPrinted(t, ":unknown( x (a+b), 'c' ) {}", ":unknown(x (a+b), 'c') {\n}\n")
expectPrinted(t, ":unknown( x (a-b), 'c' ) {}", ":unknown(x (a-b), 'c') {\n}\n")
expectPrinted(t, ":unknown( x (a,b), 'c' ) {}", ":unknown(x (a, b), 'c') {\n}\n")
expectPrinted(t, ":unknown( x ( a + b ), 'c' ) {}", ":unknown(x (a + b), 'c') {\n}\n")
expectPrinted(t, ":unknown( x ( a - b ), 'c' ) {}", ":unknown(x (a - b), 'c') {\n}\n")
expectPrinted(t, ":unknown( x ( a , b ), 'c' ) {}", ":unknown(x (a, b), 'c') {\n}\n")
expectPrinted(t, ":unknown( x (a+b), 'c' ) {}", ":unknown(x (a+b), \"c\") {\n}\n")
expectPrinted(t, ":unknown( x (a-b), 'c' ) {}", ":unknown(x (a-b), \"c\") {\n}\n")
expectPrinted(t, ":unknown( x (a,b), 'c' ) {}", ":unknown(x (a, b), \"c\") {\n}\n")
expectPrinted(t, ":unknown( x ( a + b ), 'c' ) {}", ":unknown(x (a + b), \"c\") {\n}\n")
expectPrinted(t, ":unknown( x ( a - b ), 'c' ) {}", ":unknown(x (a - b), \"c\") {\n}\n")
expectPrinted(t, ":unknown( x ( a , b ), 'c' ) {}", ":unknown(x (a, b), \"c\") {\n}\n")
expectPrintedMinify(t, ":unknown( x (a+b), 'c' ) {}", ":unknown(x (a+b),'c'){}")
expectPrintedMinify(t, ":unknown( x (a-b), 'c' ) {}", ":unknown(x (a-b),'c'){}")
expectPrintedMinify(t, ":unknown( x (a,b), 'c' ) {}", ":unknown(x (a,b),'c'){}")
expectPrintedMinify(t, ":unknown( x ( a + b ), 'c' ) {}", ":unknown(x (a + b),'c'){}")
expectPrintedMinify(t, ":unknown( x ( a - b ), 'c' ) {}", ":unknown(x (a - b),'c'){}")
expectPrintedMinify(t, ":unknown( x ( a , b ), 'c' ) {}", ":unknown(x (a,b),'c'){}")
expectPrintedMinify(t, ":unknown( x (a+b), 'c' ) {}", ":unknown(x (a+b),\"c\"){}")
expectPrintedMinify(t, ":unknown( x (a-b), 'c' ) {}", ":unknown(x (a-b),\"c\"){}")
expectPrintedMinify(t, ":unknown( x (a,b), 'c' ) {}", ":unknown(x (a,b),\"c\"){}")
expectPrintedMinify(t, ":unknown( x ( a + b ), 'c' ) {}", ":unknown(x (a + b),\"c\"){}")
expectPrintedMinify(t, ":unknown( x ( a - b ), 'c' ) {}", ":unknown(x (a - b),\"c\"){}")
expectPrintedMinify(t, ":unknown( x ( a , b ), 'c' ) {}", ":unknown(x (a,b),\"c\"){}")
}
func TestNestedSelector(t *testing.T) {