parse_test.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. package css // import "github.com/tdewolff/parse/css"
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "testing"
  7. "github.com/tdewolff/parse"
  8. "github.com/tdewolff/test"
  9. )
  10. ////////////////////////////////////////////////////////////////
  11. func TestParse(t *testing.T) {
  12. var parseTests = []struct {
  13. inline bool
  14. css string
  15. expected string
  16. }{
  17. {true, " x : y ; ", "x:y;"},
  18. {true, "color: red;", "color:red;"},
  19. {true, "color : red;", "color:red;"},
  20. {true, "color: red; border: 0;", "color:red;border:0;"},
  21. {true, "color: red !important;", "color:red!important;"},
  22. {true, "color: red ! important;", "color:red!important;"},
  23. {true, "white-space: -moz-pre-wrap;", "white-space:-moz-pre-wrap;"},
  24. {true, "display: -moz-inline-stack;", "display:-moz-inline-stack;"},
  25. {true, "x: 10px / 1em;", "x:10px/1em;"},
  26. {true, "x: 1em/1.5em \"Times New Roman\", Times, serif;", "x:1em/1.5em \"Times New Roman\",Times,serif;"},
  27. {true, "x: hsla(100,50%, 75%, 0.5);", "x:hsla(100,50%,75%,0.5);"},
  28. {true, "x: hsl(100,50%, 75%);", "x:hsl(100,50%,75%);"},
  29. {true, "x: rgba(255, 238 , 221, 0.3);", "x:rgba(255,238,221,0.3);"},
  30. {true, "x: 50vmax;", "x:50vmax;"},
  31. {true, "color: linear-gradient(to right, black, white);", "color:linear-gradient(to right,black,white);"},
  32. {true, "color: calc(100%/2 - 1em);", "color:calc(100%/2 - 1em);"},
  33. {true, "color: calc(100%/2--1em);", "color:calc(100%/2--1em);"},
  34. {false, "<!-- @charset; -->", "<!--@charset;-->"},
  35. {false, "@media print, screen { }", "@media print,screen{}"},
  36. {false, "@media { @viewport ; }", "@media{@viewport;}"},
  37. {false, "@keyframes 'diagonal-slide' { from { left: 0; top: 0; } to { left: 100px; top: 100px; } }", "@keyframes 'diagonal-slide'{from{left:0;top:0;}to{left:100px;top:100px;}}"},
  38. {false, "@keyframes movingbox{0%{left:90%;}50%{left:10%;}100%{left:90%;}}", "@keyframes movingbox{0%{left:90%;}50%{left:10%;}100%{left:90%;}}"},
  39. {false, ".foo { color: #fff;}", ".foo{color:#fff;}"},
  40. {false, ".foo { ; _color: #fff;}", ".foo{_color:#fff;}"},
  41. {false, "a { color: red; border: 0; }", "a{color:red;border:0;}"},
  42. {false, "a { color: red; border: 0; } b { padding: 0; }", "a{color:red;border:0;}b{padding:0;}"},
  43. {false, "/* comment */", "/* comment */"},
  44. // extraordinary
  45. {true, "color: red;;", "color:red;"},
  46. {true, "color:#c0c0c0", "color:#c0c0c0;"},
  47. {true, "background:URL(x.png);", "background:URL(x.png);"},
  48. {true, "filter: progid : DXImageTransform.Microsoft.BasicImage(rotation=1);", "filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);"},
  49. {true, "/*a*/\n/*c*/\nkey: value;", "key:value;"},
  50. {true, "@-moz-charset;", "@-moz-charset;"},
  51. {true, "--custom-variable: (0;) ;", "--custom-variable: (0;) ;"},
  52. {false, "@import;@import;", "@import;@import;"},
  53. {false, ".a .b#c, .d<.e { x:y; }", ".a .b#c,.d<.e{x:y;}"},
  54. {false, ".a[b~=c]d { x:y; }", ".a[b~=c]d{x:y;}"},
  55. // {false, "{x:y;}", "{x:y;}"},
  56. {false, "a{}", "a{}"},
  57. {false, "a,.b/*comment*/ {x:y;}", "a,.b{x:y;}"},
  58. {false, "a,.b/*comment*/.c {x:y;}", "a,.b.c{x:y;}"},
  59. {false, "a{x:; z:q;}", "a{x:;z:q;}"},
  60. {false, "@font-face { x:y; }", "@font-face{x:y;}"},
  61. {false, "a:not([controls]){x:y;}", "a:not([controls]){x:y;}"},
  62. {false, "@document regexp('https:.*') { p { color: red; } }", "@document regexp('https:.*'){p{color:red;}}"},
  63. {false, "@media all and ( max-width:400px ) { }", "@media all and (max-width:400px){}"},
  64. {false, "@media (max-width:400px) { }", "@media(max-width:400px){}"},
  65. {false, "@media (max-width:400px)", "@media(max-width:400px);"},
  66. {false, "@font-face { ; font:x; }", "@font-face{font:x;}"},
  67. {false, "@-moz-font-face { ; font:x; }", "@-moz-font-face{font:x;}"},
  68. {false, "@unknown abc { {} lala }", "@unknown abc{{}lala}"},
  69. {false, "a[x={}]{x:y;}", "a[x={}]{x:y;}"},
  70. {false, "a[x=,]{x:y;}", "a[x=,]{x:y;}"},
  71. {false, "a[x=+]{x:y;}", "a[x=+]{x:y;}"},
  72. {false, ".cla .ss > #id { x:y; }", ".cla .ss>#id{x:y;}"},
  73. {false, ".cla /*a*/ /*b*/ .ss{}", ".cla .ss{}"},
  74. {false, "a{x:f(a(),b);}", "a{x:f(a(),b);}"},
  75. {false, "a{x:y!z;}", "a{x:y!z;}"},
  76. {false, "[class*=\"column\"]+[class*=\"column\"]:last-child{a:b;}", "[class*=\"column\"]+[class*=\"column\"]:last-child{a:b;}"},
  77. {false, "@media { @viewport }", "@media{@viewport;}"},
  78. {false, "table { @unknown }", "table{@unknown;}"},
  79. // early endings
  80. {false, "selector{", "selector{"},
  81. {false, "@media{selector{", "@media{selector{"},
  82. // bad grammar
  83. {true, "~color:red", "~color:red;"},
  84. {false, ".foo { *color: #fff;}", ".foo{*color:#fff;}"},
  85. {true, "*color: red; font-size: 12pt;", "*color:red;font-size:12pt;"},
  86. {true, "_color: red; font-size: 12pt;", "_color:red;font-size:12pt;"},
  87. // issues
  88. {false, "@media print {.class{width:5px;}}", "@media print{.class{width:5px;}}"}, // #6
  89. {false, ".class{width:calc((50% + 2em)/2 + 14px);}", ".class{width:calc((50% + 2em)/2 + 14px);}"}, // #7
  90. {false, ".class [c=y]{}", ".class [c=y]{}"}, // tdewolff/minify#16
  91. {false, "table{font-family:Verdana}", "table{font-family:Verdana;}"}, // tdewolff/minify#22
  92. // go-fuzz
  93. {false, "@-webkit-", "@-webkit-;"},
  94. }
  95. for _, tt := range parseTests {
  96. t.Run(tt.css, func(t *testing.T) {
  97. output := ""
  98. p := NewParser(bytes.NewBufferString(tt.css), tt.inline)
  99. for {
  100. grammar, _, data := p.Next()
  101. data = parse.Copy(data)
  102. if grammar == ErrorGrammar {
  103. if err := p.Err(); err != io.EOF {
  104. for _, val := range p.Values() {
  105. data = append(data, val.Data...)
  106. }
  107. if perr, ok := err.(*parse.Error); ok && perr.Message == "unexpected token in declaration" {
  108. data = append(data, ";"...)
  109. }
  110. } else {
  111. test.T(t, err, io.EOF)
  112. break
  113. }
  114. } else if grammar == AtRuleGrammar || grammar == BeginAtRuleGrammar || grammar == QualifiedRuleGrammar || grammar == BeginRulesetGrammar || grammar == DeclarationGrammar || grammar == CustomPropertyGrammar {
  115. if grammar == DeclarationGrammar || grammar == CustomPropertyGrammar {
  116. data = append(data, ":"...)
  117. }
  118. for _, val := range p.Values() {
  119. data = append(data, val.Data...)
  120. }
  121. if grammar == BeginAtRuleGrammar || grammar == BeginRulesetGrammar {
  122. data = append(data, "{"...)
  123. } else if grammar == AtRuleGrammar || grammar == DeclarationGrammar || grammar == CustomPropertyGrammar {
  124. data = append(data, ";"...)
  125. } else if grammar == QualifiedRuleGrammar {
  126. data = append(data, ","...)
  127. }
  128. }
  129. output += string(data)
  130. }
  131. test.String(t, output, tt.expected)
  132. })
  133. }
  134. test.T(t, ErrorGrammar.String(), "Error")
  135. test.T(t, AtRuleGrammar.String(), "AtRule")
  136. test.T(t, BeginAtRuleGrammar.String(), "BeginAtRule")
  137. test.T(t, EndAtRuleGrammar.String(), "EndAtRule")
  138. test.T(t, BeginRulesetGrammar.String(), "BeginRuleset")
  139. test.T(t, EndRulesetGrammar.String(), "EndRuleset")
  140. test.T(t, DeclarationGrammar.String(), "Declaration")
  141. test.T(t, TokenGrammar.String(), "Token")
  142. test.T(t, CommentGrammar.String(), "Comment")
  143. test.T(t, CustomPropertyGrammar.String(), "CustomProperty")
  144. test.T(t, GrammarType(100).String(), "Invalid(100)")
  145. }
  146. func TestParseError(t *testing.T) {
  147. var parseErrorTests = []struct {
  148. inline bool
  149. css string
  150. col int
  151. }{
  152. {false, "selector", 9},
  153. {true, "color 0", 8},
  154. {true, "--color 0", 10},
  155. {true, "--custom-variable:0", 0},
  156. }
  157. for _, tt := range parseErrorTests {
  158. t.Run(tt.css, func(t *testing.T) {
  159. p := NewParser(bytes.NewBufferString(tt.css), tt.inline)
  160. for {
  161. grammar, _, _ := p.Next()
  162. if grammar == ErrorGrammar {
  163. if tt.col == 0 {
  164. test.T(t, p.Err(), io.EOF)
  165. } else if perr, ok := p.Err().(*parse.Error); ok {
  166. _, col, _ := perr.Position()
  167. test.T(t, col, tt.col)
  168. } else {
  169. test.Fail(t, "bad error:", p.Err())
  170. }
  171. break
  172. }
  173. }
  174. })
  175. }
  176. }
  177. func TestReader(t *testing.T) {
  178. input := "x:a;"
  179. p := NewParser(test.NewPlainReader(bytes.NewBufferString(input)), true)
  180. for {
  181. grammar, _, _ := p.Next()
  182. if grammar == ErrorGrammar {
  183. break
  184. }
  185. }
  186. }
  187. ////////////////////////////////////////////////////////////////
  188. type Obj struct{}
  189. func (*Obj) F() {}
  190. var f1 func(*Obj)
  191. func BenchmarkFuncPtr(b *testing.B) {
  192. for i := 0; i < b.N; i++ {
  193. f1 = (*Obj).F
  194. }
  195. }
  196. var f2 func()
  197. func BenchmarkMemFuncPtr(b *testing.B) {
  198. obj := &Obj{}
  199. for i := 0; i < b.N; i++ {
  200. f2 = obj.F
  201. }
  202. }
  203. func ExampleNewParser() {
  204. p := NewParser(bytes.NewBufferString("color: red;"), true) // false because this is the content of an inline style attribute
  205. out := ""
  206. for {
  207. gt, _, data := p.Next()
  208. if gt == ErrorGrammar {
  209. break
  210. } else if gt == AtRuleGrammar || gt == BeginAtRuleGrammar || gt == BeginRulesetGrammar || gt == DeclarationGrammar {
  211. out += string(data)
  212. if gt == DeclarationGrammar {
  213. out += ":"
  214. }
  215. for _, val := range p.Values() {
  216. out += string(val.Data)
  217. }
  218. if gt == BeginAtRuleGrammar || gt == BeginRulesetGrammar {
  219. out += "{"
  220. } else if gt == AtRuleGrammar || gt == DeclarationGrammar {
  221. out += ";"
  222. }
  223. } else {
  224. out += string(data)
  225. }
  226. }
  227. fmt.Println(out)
  228. // Output: color:red;
  229. }