Skip to content

Commit 9d4e8de

Browse files
committed
internal/*: add support for @font-face rules
1 parent 4a66af3 commit 9d4e8de

File tree

5 files changed

+187
-62
lines changed

5 files changed

+187
-62
lines changed

internal/integration_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ func TestIntegration(t *testing.T) {
1515
"testdata/bootstrap.css",
1616
"testdata/comments.css",
1717
"testdata/bem.css",
18+
"testdata/font-face.css",
1819
} {
1920
t.Run(c, func(t *testing.T) {
2021

internal/parser/parser.go

Lines changed: 85 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -90,80 +90,85 @@ func (p *parser) parseQualifiedRule(isKeyframes bool) *ast.QualifiedRule {
9090
p.lexer.Errorf("unexpected EOF")
9191

9292
case lexer.LCurly:
93-
block := &ast.DeclarationBlock{
94-
Span: p.lexer.TokenSpan(),
93+
r.Block = p.parseDeclarationBlock()
94+
r.End = r.Block.Location().End
95+
return r
96+
97+
default:
98+
if isKeyframes {
99+
r.Prelude = p.parseKeyframeSelectorList()
100+
continue
95101
}
96102

97-
r.Block = block
98-
p.lexer.Next()
103+
r.Prelude = p.parseSelectorList()
104+
}
105+
}
106+
}
107+
108+
// parseDeclarationBlock parses a {} block with declarations, e.g.
109+
// { width: 1px; }.
110+
func (p *parser) parseDeclarationBlock() *ast.DeclarationBlock {
111+
block := &ast.DeclarationBlock{
112+
Span: p.lexer.TokenSpan(),
113+
}
114+
p.lexer.Next()
99115

100-
for p.lexer.Current != lexer.RCurly {
101-
decl := &ast.Declaration{
102-
Span: p.lexer.TokenSpan(),
103-
Property: p.lexer.CurrentString,
116+
for p.lexer.Current != lexer.RCurly {
117+
decl := &ast.Declaration{
118+
Span: p.lexer.TokenSpan(),
119+
Property: p.lexer.CurrentString,
120+
}
121+
p.lexer.Expect(lexer.Ident)
122+
p.lexer.Expect(lexer.Colon)
123+
values:
124+
for {
125+
switch p.lexer.Current {
126+
case lexer.EOF:
127+
p.lexer.Errorf("unexpected EOF")
128+
129+
case lexer.Delim:
130+
if p.lexer.CurrentString != "!" {
131+
p.lexer.Errorf("unexpected token: %s", p.lexer.CurrentString)
104132
}
105-
p.lexer.Expect(lexer.Ident)
106-
p.lexer.Expect(lexer.Colon)
107-
values:
108-
for {
109-
switch p.lexer.Current {
110-
case lexer.EOF:
111-
p.lexer.Errorf("unexpected EOF")
112-
113-
case lexer.Delim:
114-
if p.lexer.CurrentString != "!" {
115-
p.lexer.Errorf("unexpected token: %s", p.lexer.CurrentString)
116-
}
117-
p.lexer.Next()
118-
119-
if !isImportantString(p.lexer.CurrentString) {
120-
p.lexer.Errorf("expected !important, unexpected token: %s", p.lexer.CurrentString)
121-
}
122-
decl.End = p.lexer.TokenEnd()
123-
p.lexer.Next()
124-
decl.Important = true
125-
126-
case lexer.Comma:
127-
decl.Values = append(decl.Values, &ast.Comma{Span: p.lexer.TokenSpan()})
128-
p.lexer.Next()
129-
130-
default:
131-
val := p.parseValue()
132-
if val == nil {
133-
if len(decl.Values) == 0 {
134-
p.lexer.Errorf("declaration must have a value")
135-
}
136-
if lastValueEnd := decl.Values[len(decl.Values)-1].Location().End; lastValueEnd > decl.End {
137-
decl.End = lastValueEnd
138-
}
139-
140-
block.Declarations = append(block.Declarations, decl)
141-
142-
break values
143-
}
144-
145-
decl.Values = append(decl.Values, val)
146-
}
133+
p.lexer.Next()
134+
135+
if !isImportantString(p.lexer.CurrentString) {
136+
p.lexer.Errorf("expected !important, unexpected token: %s", p.lexer.CurrentString)
147137
}
138+
decl.End = p.lexer.TokenEnd()
139+
p.lexer.Next()
140+
decl.Important = true
148141

149-
if p.lexer.Current == lexer.Semicolon {
150-
p.lexer.Next()
142+
case lexer.Comma:
143+
decl.Values = append(decl.Values, &ast.Comma{Span: p.lexer.TokenSpan()})
144+
p.lexer.Next()
145+
146+
default:
147+
val := p.parseValue()
148+
if val == nil {
149+
if len(decl.Values) == 0 {
150+
p.lexer.Errorf("declaration must have a value")
151+
}
152+
if lastValueEnd := decl.Values[len(decl.Values)-1].Location().End; lastValueEnd > decl.End {
153+
decl.End = lastValueEnd
154+
}
155+
156+
block.Declarations = append(block.Declarations, decl)
157+
158+
break values
151159
}
152-
}
153-
block.End = p.lexer.TokenEnd()
154-
r.End = block.End
155-
p.lexer.Next()
156-
return r
157160

158-
default:
159-
if isKeyframes {
160-
r.Prelude = p.parseKeyframeSelectorList()
161-
continue
161+
decl.Values = append(decl.Values, val)
162162
}
163+
}
163164

164-
r.Prelude = p.parseSelectorList()
165+
if p.lexer.Current == lexer.Semicolon {
166+
p.lexer.Next()
165167
}
166168
}
169+
block.End = p.lexer.TokenEnd()
170+
p.lexer.Next()
171+
return block
167172
}
168173

169174
func (p *parser) parseKeyframeSelectorList() *ast.KeyframeSelectorList {
@@ -363,6 +368,9 @@ func (p *parser) parseAtRule() {
363368
case "custom-media":
364369
p.parseCustomMediaAtRule()
365370

371+
case "font-face":
372+
p.parseFontFace()
373+
366374
default:
367375
p.lexer.Errorf("unsupported at rule: %s", p.lexer.CurrentString)
368376
}
@@ -725,3 +733,18 @@ func (p *parser) parseCustomMediaAtRule() {
725733

726734
p.ss.Nodes = append(p.ss.Nodes, r)
727735
}
736+
737+
// parseFontFace parses an @font-face rule.
738+
// See: https://www.w3.org/TR/css-fonts-4/#font-face-rule
739+
func (p *parser) parseFontFace() {
740+
r := &ast.AtRule{
741+
Span: p.lexer.TokenSpan(),
742+
Name: p.lexer.CurrentString,
743+
}
744+
p.lexer.Next()
745+
746+
r.Block = p.parseDeclarationBlock()
747+
r.End = r.Block.Location().End
748+
749+
p.ss.Nodes = append(p.ss.Nodes, r)
750+
}

internal/parser/span_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ func TestSpans(t *testing.T) {
5555
100% { opacity: 100%; }
5656
}
5757
58+
@font-face {
59+
font-family: "whatever";
60+
src: url(http://github.com/"/what.eot?") format("eot"),
61+
url(http://github.com/"./what.woff") format("woff"),
62+
url(http://github.com/"./what.ttf") format("truetype");
63+
}
5864
`,
5965
}
6066

internal/parser/testdata/spans.txt

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,3 +514,79 @@
514514
100% { opacity: 100%; }
515515
~~~~
516516

517+
*ast.AtRule:31:1
518+
@font-face {
519+
~~~~~~~~~~~~>
520+
521+
*ast.DeclarationBlock:31:12
522+
@font-face {
523+
~>
524+
525+
*ast.Declaration:32:2
526+
font-family: "whatever";
527+
~~~~~~~~~~~~~~~~~~~~~~~
528+
529+
*ast.String:32:15
530+
font-family: "whatever";
531+
~~~~~~~~~~
532+
533+
*ast.Declaration:33:3
534+
src: url(http://github.com/"/what.eot?") format("eot"),
535+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~>
536+
537+
*ast.Function:33:8
538+
src: url(http://github.com/"/what.eot?") format("eot"),
539+
~~~~~~~~~~~~~~~~~
540+
541+
*ast.String:33:12
542+
src: url(http://github.com/"/what.eot?") format("eot"),
543+
~~~~~~~~~~~~
544+
545+
*ast.Function:33:26
546+
src: url(http://github.com/"/what.eot?") format("eot"),
547+
~~~~~~~~~~~~~
548+
549+
*ast.String:33:33
550+
src: url(http://github.com/"/what.eot?") format("eot"),
551+
~~~~~
552+
553+
*ast.Comma:33:39
554+
src: url(http://github.com/"/what.eot?") format("eot"),
555+
~
556+
557+
*ast.Function:34:5
558+
url(http://github.com/"./what.woff") format("woff"),
559+
~~~~~~~~~~~~~~~~~~
560+
561+
*ast.String:34:9
562+
url(http://github.com/"./what.woff") format("woff"),
563+
~~~~~~~~~~~~~
564+
565+
*ast.Function:34:24
566+
url(http://github.com/"./what.woff") format("woff"),
567+
~~~~~~~~~~~~~~
568+
569+
*ast.String:34:31
570+
url(http://github.com/"./what.woff") format("woff"),
571+
~~~~~~
572+
573+
*ast.Comma:34:38
574+
url(http://github.com/"./what.woff") format("woff"),
575+
~
576+
577+
*ast.Function:35:3
578+
url(http://github.com/"./what.ttf") format("truetype");
579+
~~~~~~~~~~~~~~~~~
580+
581+
*ast.String:35:7
582+
url(http://github.com/"./what.ttf") format("truetype");
583+
~~~~~~~~~~~~
584+
585+
*ast.Function:35:21
586+
url(http://github.com/"./what.ttf") format("truetype");
587+
~~~~~~~~~~~~~~~~~~
588+
589+
*ast.String:35:28
590+
url(http://github.com/"./what.ttf") format("truetype");
591+
~~~~~~~~~~
592+

internal/testdata/font-face.css

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
@font-face {
2+
font-family: "what";
3+
src: url("/what.eot?") format("eot"),
4+
url("./what.woff") format("woff"),
5+
url("./what.ttf") format("truetype");
6+
7+
font-weight: normal;
8+
font-style: normal;
9+
}
10+
11+
@font-face {
12+
font-family: "what";
13+
src: url("./what-bold.eot?") format("eot"),
14+
url("./what-bold.woff") format("woff"),
15+
url("./what-bold.ttf") format("truetype");
16+
17+
font-weight: bold;
18+
font-style: normal;
19+
}

0 commit comments

Comments
 (0)