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