comments.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. package ast
  2. import (
  3. "fmt"
  4. "github.com/robertkrimen/otto/file"
  5. )
  6. // CommentPosition determines where the comment is in a given context.
  7. type CommentPosition int
  8. // Available comment positions.
  9. const (
  10. _ CommentPosition = iota
  11. // LEADING is before the pertinent expression.
  12. LEADING
  13. // TRAILING is after the pertinent expression.
  14. TRAILING
  15. // KEY is before a key in an object.
  16. KEY
  17. // COLON is after a colon in a field declaration.
  18. COLON
  19. // FINAL is the final comments in a block, not belonging to a specific expression or the comment after a trailing , in an array or object literal.
  20. FINAL
  21. // IF is after an if keyword.
  22. IF
  23. // WHILE is after a while keyword.
  24. WHILE
  25. // DO is after do keyword.
  26. DO
  27. // FOR is after a for keyword.
  28. FOR
  29. // WITH is after a with keyword.
  30. WITH
  31. // TBD is unknown.
  32. TBD
  33. )
  34. // Comment contains the data of the comment.
  35. type Comment struct {
  36. Text string
  37. Begin file.Idx
  38. Position CommentPosition
  39. }
  40. // NewComment creates a new comment.
  41. func NewComment(text string, idx file.Idx) *Comment {
  42. comment := &Comment{
  43. Begin: idx,
  44. Text: text,
  45. Position: TBD,
  46. }
  47. return comment
  48. }
  49. // String returns a stringified version of the position.
  50. func (cp CommentPosition) String() string {
  51. switch cp {
  52. case LEADING:
  53. return "Leading"
  54. case TRAILING:
  55. return "Trailing"
  56. case KEY:
  57. return "Key"
  58. case COLON:
  59. return "Colon"
  60. case FINAL:
  61. return "Final"
  62. case IF:
  63. return "If"
  64. case WHILE:
  65. return "While"
  66. case DO:
  67. return "Do"
  68. case FOR:
  69. return "For"
  70. case WITH:
  71. return "With"
  72. default:
  73. return "???"
  74. }
  75. }
  76. // String returns a stringified version of the comment.
  77. func (c Comment) String() string {
  78. return fmt.Sprintf("Comment: %v", c.Text)
  79. }
  80. // Comments defines the current view of comments from the parser.
  81. type Comments struct {
  82. // CommentMap is a reference to the parser comment map
  83. CommentMap CommentMap
  84. // Comments lists the comments scanned, not linked to a node yet
  85. Comments []*Comment
  86. // Current is node for which comments are linked to
  87. Current Expression
  88. // future lists the comments after a line break during a sequence of comments
  89. future []*Comment
  90. // wasLineBreak determines if a line break occurred while scanning for comments
  91. wasLineBreak bool
  92. // primary determines whether or not processing a primary expression
  93. primary bool
  94. // afterBlock determines whether or not being after a block statement
  95. afterBlock bool
  96. }
  97. // NewComments returns a new Comments.
  98. func NewComments() *Comments {
  99. comments := &Comments{
  100. CommentMap: CommentMap{},
  101. }
  102. return comments
  103. }
  104. func (c *Comments) String() string {
  105. return fmt.Sprintf("NODE: %v, Comments: %v, Future: %v(LINEBREAK:%v)", c.Current, len(c.Comments), len(c.future), c.wasLineBreak)
  106. }
  107. // FetchAll returns all the currently scanned comments,
  108. // including those from the next line.
  109. func (c *Comments) FetchAll() []*Comment {
  110. defer func() {
  111. c.Comments = nil
  112. c.future = nil
  113. }()
  114. return append(c.Comments, c.future...)
  115. }
  116. // Fetch returns all the currently scanned comments.
  117. func (c *Comments) Fetch() []*Comment {
  118. defer func() {
  119. c.Comments = nil
  120. }()
  121. return c.Comments
  122. }
  123. // ResetLineBreak marks the beginning of a new statement.
  124. func (c *Comments) ResetLineBreak() {
  125. c.wasLineBreak = false
  126. }
  127. // MarkPrimary will mark the context as processing a primary expression.
  128. func (c *Comments) MarkPrimary() {
  129. c.primary = true
  130. c.wasLineBreak = false
  131. }
  132. // AfterBlock will mark the context as being after a block.
  133. func (c *Comments) AfterBlock() {
  134. c.afterBlock = true
  135. }
  136. // AddComment adds a comment to the view.
  137. // Depending on the context, comments are added normally or as post line break.
  138. func (c *Comments) AddComment(comment *Comment) {
  139. if c.primary {
  140. if !c.wasLineBreak {
  141. c.Comments = append(c.Comments, comment)
  142. } else {
  143. c.future = append(c.future, comment)
  144. }
  145. } else {
  146. if !c.wasLineBreak || (c.Current == nil && !c.afterBlock) {
  147. c.Comments = append(c.Comments, comment)
  148. } else {
  149. c.future = append(c.future, comment)
  150. }
  151. }
  152. }
  153. // MarkComments will mark the found comments as the given position.
  154. func (c *Comments) MarkComments(position CommentPosition) {
  155. for _, comment := range c.Comments {
  156. if comment.Position == TBD {
  157. comment.Position = position
  158. }
  159. }
  160. for _, c := range c.future {
  161. if c.Position == TBD {
  162. c.Position = position
  163. }
  164. }
  165. }
  166. // Unset the current node and apply the comments to the current expression.
  167. // Resets context variables.
  168. func (c *Comments) Unset() {
  169. if c.Current != nil {
  170. c.applyComments(c.Current, c.Current, TRAILING)
  171. c.Current = nil
  172. }
  173. c.wasLineBreak = false
  174. c.primary = false
  175. c.afterBlock = false
  176. }
  177. // SetExpression sets the current expression.
  178. // It is applied the found comments, unless the previous expression has not been unset.
  179. // It is skipped if the node is already set or if it is a part of the previous node.
  180. func (c *Comments) SetExpression(node Expression) {
  181. // Skipping same node
  182. if c.Current == node {
  183. return
  184. }
  185. if c.Current != nil && c.Current.Idx1() == node.Idx1() {
  186. c.Current = node
  187. return
  188. }
  189. previous := c.Current
  190. c.Current = node
  191. // Apply the found comments and futures to the node and the previous.
  192. c.applyComments(node, previous, TRAILING)
  193. }
  194. // PostProcessNode applies all found comments to the given node.
  195. func (c *Comments) PostProcessNode(node Node) {
  196. c.applyComments(node, nil, TRAILING)
  197. }
  198. // applyComments applies both the comments and the future comments to the given node and the previous one,
  199. // based on the context.
  200. func (c *Comments) applyComments(node, previous Node, position CommentPosition) {
  201. if previous != nil {
  202. c.CommentMap.AddComments(previous, c.Comments, position)
  203. c.Comments = nil
  204. } else {
  205. c.CommentMap.AddComments(node, c.Comments, position)
  206. c.Comments = nil
  207. }
  208. // Only apply the future comments to the node if the previous is set.
  209. // This is for detecting end of line comments and which node comments on the following lines belongs to
  210. if previous != nil {
  211. c.CommentMap.AddComments(node, c.future, position)
  212. c.future = nil
  213. }
  214. }
  215. // AtLineBreak will mark a line break.
  216. func (c *Comments) AtLineBreak() {
  217. c.wasLineBreak = true
  218. }
  219. // CommentMap is the data structure where all found comments are stored.
  220. type CommentMap map[Node][]*Comment
  221. // AddComment adds a single comment to the map.
  222. func (cm CommentMap) AddComment(node Node, comment *Comment) {
  223. list := cm[node]
  224. list = append(list, comment)
  225. cm[node] = list
  226. }
  227. // AddComments adds a slice of comments, given a node and an updated position.
  228. func (cm CommentMap) AddComments(node Node, comments []*Comment, position CommentPosition) {
  229. for _, comment := range comments {
  230. if comment.Position == TBD {
  231. comment.Position = position
  232. }
  233. cm.AddComment(node, comment)
  234. }
  235. }
  236. // Size returns the size of the map.
  237. func (cm CommentMap) Size() int {
  238. size := 0
  239. for _, comments := range cm {
  240. size += len(comments)
  241. }
  242. return size
  243. }
  244. // MoveComments moves comments with a given position from a node to another.
  245. func (cm CommentMap) MoveComments(from, to Node, position CommentPosition) {
  246. for i, c := range cm[from] {
  247. if c.Position == position {
  248. cm.AddComment(to, c)
  249. // Remove the comment from the "from" slice
  250. cm[from][i] = cm[from][len(cm[from])-1]
  251. cm[from][len(cm[from])-1] = nil
  252. cm[from] = cm[from][:len(cm[from])-1]
  253. }
  254. }
  255. }