@@ -4,56 +4,96 @@ import StreamReader from '@emmetio/stream-reader';
44import Stylesheet from './lib/stylesheet' ;
55import createRule from './lib/rule' ;
66import createProperty from './lib/property' ;
7- import token , { unknown , Token } from './lib/tokens/index' ;
7+ import { Token } from './lib/tokens/index' ;
88
9- export default function ( source ) {
9+ import atKeyword from './lib/tokens/at-keyword' ;
10+ import string from './lib/tokens/string' ;
11+ import separator from './lib/tokens/separator' ;
12+ import comment , { multiLineComment } from './lib/tokens/comment' ;
13+ import whitespace from './lib/tokens/whitespace' ;
14+
15+ const LBRACE = 40 ; // (
16+ const RBRACE = 41 ; // )
17+
18+ export default function parseStylesheet ( source ) {
1019 const stream = typeof source === 'string' ? new StreamReader ( source ) : source ;
1120 const root = new Stylesheet ( ) ;
21+ let ctx = root , child , accum ;
22+ let token , start ;
1223 let tokens = [ ] ;
13- let ctx = root , child , t ;
1424
1525 while ( ! stream . eof ( ) ) {
16- t = token ( stream ) ;
17-
18- if ( ! t ) {
19- // unable to identify following character, consume it as unknown token
20- stream . start = stream . pos ;
21- stream . next ( ) ;
22- tokens . push ( unknown ( stream ) ) ;
23- } else if ( t . propertyTerminator ) {
24- // Tokens consumed before are CSS property
25- tokens . push ( t ) ;
26- ctx . addChild ( createProperty ( stream , tokens , t ) ) ;
27- tokens = [ ] ;
28- } else if ( t . ruleStart ) {
29- // Tokens consumed before are CSS rule
30- child = createRule ( stream , tokens , t ) ;
31- if ( child ) {
26+ token = atKeyword ( stream ) || separator ( stream ) || whitespace ( stream ) || comment ( stream ) ;
27+ if ( token ) {
28+ if ( accum ) {
29+ tokens . push ( accum ) ;
30+ accum = null ;
31+ }
32+
33+ if ( token . propertyTerminator ) {
34+ ctx . addChild ( createProperty ( stream , tokens , token ) ) ;
35+ tokens = [ ] ;
36+ } else if ( token . ruleStart ) {
37+ child = createRule ( stream , tokens , token ) ;
3238 ctx . addChild ( child ) ;
3339 ctx = child ;
40+ tokens = [ ] ;
41+ } else if ( token . ruleEnd ) {
42+ // Finalize context section
43+ ctx . addChild ( createProperty ( stream , tokens ) ) ;
44+
45+ if ( ctx . type !== 'root' ) {
46+ // In case of invalid stylesheet with redundant ` }`,
47+ // don’t modify root section.
48+ ctx . contentRange . end = token . end ;
49+ ctx = ctx . parent ;
50+ }
51+
52+ tokens = [ ] ;
53+ } else {
54+ tokens . push ( token ) ;
3455 }
35- tokens = [ ] ;
36- } else if ( t . ruleEnd ) {
37- // Finalize previously consumed tokens as CSS property
38- ctx . addChild ( createProperty ( stream , tokens ) ) ;
39- tokens = [ ] ;
40-
41- // In case of invalid stylesheet with redundant ` }`,
42- // don’t modify root section.
43- if ( ctx . type !== 'root' ) {
44- ctx . contentEnd = t ;
45- }
4656
47- ctx = ctx . parent || ctx ;
57+ continue ;
58+ }
59+
60+ start = stream . pos ;
61+ if ( braces ( stream ) || string ( stream ) || ! isNaN ( stream . next ( ) ) ) {
62+ if ( ! accum ) {
63+ accum = new Token ( stream , start ) ;
64+ } else {
65+ accum . end = stream . pos ;
66+ }
4867 } else {
49- tokens . push ( t ) ;
68+ throw new Error ( `Unexpected end-of-stream at ${ stream . pos } ` ) ;
5069 }
5170 }
5271
53- // save unterminated tokens as property
72+ // Finalize all the rest properties
5473 ctx . addChild ( createProperty ( stream , tokens ) ) ;
5574
5675 return root ;
5776}
5877
59- export { token , createProperty , createRule , Token }
78+ function braces ( stream ) {
79+ if ( stream . eat ( LBRACE ) ) {
80+ let stack = 1 ;
81+
82+ // Handle edge case: do not consume single-line comment inside braces
83+ // since most likely it’s an unquoted url like `http://example.com`
84+ while ( ! stream . eof ( ) ) {
85+ if ( stream . eat ( RBRACE ) ) {
86+ stack -- ;
87+ if ( ! stack ) {
88+ break ;
89+ }
90+ } else if ( ! string ( stream ) && ! multiLineComment ( stream ) ) {
91+ stream . next ( ) ;
92+ }
93+ }
94+
95+ return true ;
96+ }
97+
98+ return false ;
99+ }
0 commit comments