comments.go 7.1 KB

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