whitespace.go 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. package parser
  2. import (
  3. "regexp"
  4. "github.com/aymerick/raymond/ast"
  5. )
  6. // whitespaceVisitor walks through the AST to perform whitespace control
  7. //
  8. // The logic was shamelessly borrowed from:
  9. // https://github.com/wycats/handlebars.js/blob/master/lib/handlebars/compiler/whitespace-control.js
  10. type whitespaceVisitor struct {
  11. isRootSeen bool
  12. }
  13. var (
  14. rTrimLeft = regexp.MustCompile(`^[ \t]*\r?\n?`)
  15. rTrimLeftMultiple = regexp.MustCompile(`^\s+`)
  16. rTrimRight = regexp.MustCompile(`[ \t]+$`)
  17. rTrimRightMultiple = regexp.MustCompile(`\s+$`)
  18. rPrevWhitespace = regexp.MustCompile(`\r?\n\s*?$`)
  19. rPrevWhitespaceStart = regexp.MustCompile(`(^|\r?\n)\s*?$`)
  20. rNextWhitespace = regexp.MustCompile(`^\s*?\r?\n`)
  21. rNextWhitespaceEnd = regexp.MustCompile(`^\s*?(\r?\n|$)`)
  22. rPartialIndent = regexp.MustCompile(`([ \t]+$)`)
  23. )
  24. // newWhitespaceVisitor instanciates a new whitespaceVisitor
  25. func newWhitespaceVisitor() *whitespaceVisitor {
  26. return &whitespaceVisitor{}
  27. }
  28. // processWhitespaces performs whitespace control on given AST
  29. //
  30. // WARNING: It must be called only once on AST.
  31. func processWhitespaces(node ast.Node) {
  32. node.Accept(newWhitespaceVisitor())
  33. }
  34. func omitRightFirst(body []ast.Node, multiple bool) {
  35. omitRight(body, -1, multiple)
  36. }
  37. func omitRight(body []ast.Node, i int, multiple bool) {
  38. if i+1 >= len(body) {
  39. return
  40. }
  41. current := body[i+1]
  42. node, ok := current.(*ast.ContentStatement)
  43. if !ok {
  44. return
  45. }
  46. if !multiple && node.RightStripped {
  47. return
  48. }
  49. original := node.Value
  50. r := rTrimLeft
  51. if multiple {
  52. r = rTrimLeftMultiple
  53. }
  54. node.Value = r.ReplaceAllString(node.Value, "")
  55. node.RightStripped = (original != node.Value)
  56. }
  57. func omitLeftLast(body []ast.Node, multiple bool) {
  58. omitLeft(body, len(body), multiple)
  59. }
  60. func omitLeft(body []ast.Node, i int, multiple bool) bool {
  61. if i-1 < 0 {
  62. return false
  63. }
  64. current := body[i-1]
  65. node, ok := current.(*ast.ContentStatement)
  66. if !ok {
  67. return false
  68. }
  69. if !multiple && node.LeftStripped {
  70. return false
  71. }
  72. original := node.Value
  73. r := rTrimRight
  74. if multiple {
  75. r = rTrimRightMultiple
  76. }
  77. node.Value = r.ReplaceAllString(node.Value, "")
  78. node.LeftStripped = (original != node.Value)
  79. return node.LeftStripped
  80. }
  81. func isPrevWhitespace(body []ast.Node) bool {
  82. return isPrevWhitespaceProgram(body, len(body), false)
  83. }
  84. func isPrevWhitespaceProgram(body []ast.Node, i int, isRoot bool) bool {
  85. if i < 1 {
  86. return isRoot
  87. }
  88. prev := body[i-1]
  89. if node, ok := prev.(*ast.ContentStatement); ok {
  90. if (node.Value == "") && node.RightStripped {
  91. // already stripped, so it may be an empty string not catched by regexp
  92. return true
  93. }
  94. r := rPrevWhitespaceStart
  95. if (i > 1) || !isRoot {
  96. r = rPrevWhitespace
  97. }
  98. return r.MatchString(node.Value)
  99. }
  100. return false
  101. }
  102. func isNextWhitespace(body []ast.Node) bool {
  103. return isNextWhitespaceProgram(body, -1, false)
  104. }
  105. func isNextWhitespaceProgram(body []ast.Node, i int, isRoot bool) bool {
  106. if i+1 >= len(body) {
  107. return isRoot
  108. }
  109. next := body[i+1]
  110. if node, ok := next.(*ast.ContentStatement); ok {
  111. if (node.Value == "") && node.LeftStripped {
  112. // already stripped, so it may be an empty string not catched by regexp
  113. return true
  114. }
  115. r := rNextWhitespaceEnd
  116. if (i+2 > len(body)) || !isRoot {
  117. r = rNextWhitespace
  118. }
  119. return r.MatchString(node.Value)
  120. }
  121. return false
  122. }
  123. //
  124. // Visitor interface
  125. //
  126. func (v *whitespaceVisitor) VisitProgram(program *ast.Program) interface{} {
  127. isRoot := !v.isRootSeen
  128. v.isRootSeen = true
  129. body := program.Body
  130. for i, current := range body {
  131. strip, _ := current.Accept(v).(*ast.Strip)
  132. if strip == nil {
  133. continue
  134. }
  135. _isPrevWhitespace := isPrevWhitespaceProgram(body, i, isRoot)
  136. _isNextWhitespace := isNextWhitespaceProgram(body, i, isRoot)
  137. openStandalone := strip.OpenStandalone && _isPrevWhitespace
  138. closeStandalone := strip.CloseStandalone && _isNextWhitespace
  139. inlineStandalone := strip.InlineStandalone && _isPrevWhitespace && _isNextWhitespace
  140. if strip.Close {
  141. omitRight(body, i, true)
  142. }
  143. if strip.Open && (i > 0) {
  144. omitLeft(body, i, true)
  145. }
  146. if inlineStandalone {
  147. omitRight(body, i, false)
  148. if omitLeft(body, i, false) {
  149. // If we are on a standalone node, save the indent info for partials
  150. if partial, ok := current.(*ast.PartialStatement); ok {
  151. // Pull out the whitespace from the final line
  152. if i > 0 {
  153. if prevContent, ok := body[i-1].(*ast.ContentStatement); ok {
  154. partial.Indent = rPartialIndent.FindString(prevContent.Original)
  155. }
  156. }
  157. }
  158. }
  159. }
  160. if b, ok := current.(*ast.BlockStatement); ok {
  161. if openStandalone {
  162. prog := b.Program
  163. if prog == nil {
  164. prog = b.Inverse
  165. }
  166. omitRightFirst(prog.Body, false)
  167. // Strip out the previous content node if it's whitespace only
  168. omitLeft(body, i, false)
  169. }
  170. if closeStandalone {
  171. prog := b.Inverse
  172. if prog == nil {
  173. prog = b.Program
  174. }
  175. // Always strip the next node
  176. omitRight(body, i, false)
  177. omitLeftLast(prog.Body, false)
  178. }
  179. }
  180. }
  181. return nil
  182. }
  183. func (v *whitespaceVisitor) VisitBlock(block *ast.BlockStatement) interface{} {
  184. if block.Program != nil {
  185. block.Program.Accept(v)
  186. }
  187. if block.Inverse != nil {
  188. block.Inverse.Accept(v)
  189. }
  190. program := block.Program
  191. inverse := block.Inverse
  192. if program == nil {
  193. program = inverse
  194. inverse = nil
  195. }
  196. firstInverse := inverse
  197. lastInverse := inverse
  198. if (inverse != nil) && inverse.Chained {
  199. b, _ := inverse.Body[0].(*ast.BlockStatement)
  200. firstInverse = b.Program
  201. for lastInverse.Chained {
  202. b, _ := lastInverse.Body[len(lastInverse.Body)-1].(*ast.BlockStatement)
  203. lastInverse = b.Program
  204. }
  205. }
  206. closeProg := firstInverse
  207. if closeProg == nil {
  208. closeProg = program
  209. }
  210. strip := &ast.Strip{
  211. Open: (block.OpenStrip != nil) && block.OpenStrip.Open,
  212. Close: (block.CloseStrip != nil) && block.CloseStrip.Close,
  213. OpenStandalone: isNextWhitespace(program.Body),
  214. CloseStandalone: isPrevWhitespace(closeProg.Body),
  215. }
  216. if (block.OpenStrip != nil) && block.OpenStrip.Close {
  217. omitRightFirst(program.Body, true)
  218. }
  219. if inverse != nil {
  220. if block.InverseStrip != nil {
  221. inverseStrip := block.InverseStrip
  222. if inverseStrip.Open {
  223. omitLeftLast(program.Body, true)
  224. }
  225. if inverseStrip.Close {
  226. omitRightFirst(firstInverse.Body, true)
  227. }
  228. }
  229. if (block.CloseStrip != nil) && block.CloseStrip.Open {
  230. omitLeftLast(lastInverse.Body, true)
  231. }
  232. // Find standalone else statements
  233. if isPrevWhitespace(program.Body) && isNextWhitespace(firstInverse.Body) {
  234. omitLeftLast(program.Body, false)
  235. omitRightFirst(firstInverse.Body, false)
  236. }
  237. } else if (block.CloseStrip != nil) && block.CloseStrip.Open {
  238. omitLeftLast(program.Body, true)
  239. }
  240. return strip
  241. }
  242. func (v *whitespaceVisitor) VisitMustache(mustache *ast.MustacheStatement) interface{} {
  243. return mustache.Strip
  244. }
  245. func _inlineStandalone(strip *ast.Strip) interface{} {
  246. return &ast.Strip{
  247. Open: strip.Open,
  248. Close: strip.Close,
  249. InlineStandalone: true,
  250. }
  251. }
  252. func (v *whitespaceVisitor) VisitPartial(node *ast.PartialStatement) interface{} {
  253. strip := node.Strip
  254. if strip == nil {
  255. strip = &ast.Strip{}
  256. }
  257. return _inlineStandalone(strip)
  258. }
  259. func (v *whitespaceVisitor) VisitComment(node *ast.CommentStatement) interface{} {
  260. strip := node.Strip
  261. if strip == nil {
  262. strip = &ast.Strip{}
  263. }
  264. return _inlineStandalone(strip)
  265. }
  266. // NOOP
  267. func (v *whitespaceVisitor) VisitContent(node *ast.ContentStatement) interface{} { return nil }
  268. func (v *whitespaceVisitor) VisitExpression(node *ast.Expression) interface{} { return nil }
  269. func (v *whitespaceVisitor) VisitSubExpression(node *ast.SubExpression) interface{} { return nil }
  270. func (v *whitespaceVisitor) VisitPath(node *ast.PathExpression) interface{} { return nil }
  271. func (v *whitespaceVisitor) VisitString(node *ast.StringLiteral) interface{} { return nil }
  272. func (v *whitespaceVisitor) VisitBoolean(node *ast.BooleanLiteral) interface{} { return nil }
  273. func (v *whitespaceVisitor) VisitNumber(node *ast.NumberLiteral) interface{} { return nil }
  274. func (v *whitespaceVisitor) VisitHash(node *ast.Hash) interface{} { return nil }
  275. func (v *whitespaceVisitor) VisitHashPair(node *ast.HashPair) interface{} { return nil }