forked from golang/hotime
280 lines
7.1 KiB
Go
280 lines
7.1 KiB
Go
|
package ast
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
|
||
|
"github.com/robertkrimen/otto/file"
|
||
|
)
|
||
|
|
||
|
// CommentPosition determines where the comment is in a given context
|
||
|
type CommentPosition int
|
||
|
|
||
|
const (
|
||
|
_ CommentPosition = iota
|
||
|
LEADING // Before the pertinent expression
|
||
|
TRAILING // After the pertinent expression
|
||
|
KEY // Before a key in an object
|
||
|
COLON // After a colon in a field declaration
|
||
|
FINAL // Final comments in a block, not belonging to a specific expression or the comment after a trailing , in an array or object literal
|
||
|
IF // After an if keyword
|
||
|
WHILE // After a while keyword
|
||
|
DO // After do keyword
|
||
|
FOR // After a for keyword
|
||
|
WITH // After a with keyword
|
||
|
TBD
|
||
|
)
|
||
|
|
||
|
// Comment contains the data of the comment
|
||
|
type Comment struct {
|
||
|
Begin file.Idx
|
||
|
Text string
|
||
|
Position CommentPosition
|
||
|
}
|
||
|
|
||
|
// NewComment creates a new comment
|
||
|
func NewComment(text string, idx file.Idx) *Comment {
|
||
|
comment := &Comment{
|
||
|
Begin: idx,
|
||
|
Text: text,
|
||
|
Position: TBD,
|
||
|
}
|
||
|
|
||
|
return comment
|
||
|
}
|
||
|
|
||
|
// String returns a stringified version of the position
|
||
|
func (cp CommentPosition) String() string {
|
||
|
switch cp {
|
||
|
case LEADING:
|
||
|
return "Leading"
|
||
|
case TRAILING:
|
||
|
return "Trailing"
|
||
|
case KEY:
|
||
|
return "Key"
|
||
|
case COLON:
|
||
|
return "Colon"
|
||
|
case FINAL:
|
||
|
return "Final"
|
||
|
case IF:
|
||
|
return "If"
|
||
|
case WHILE:
|
||
|
return "While"
|
||
|
case DO:
|
||
|
return "Do"
|
||
|
case FOR:
|
||
|
return "For"
|
||
|
case WITH:
|
||
|
return "With"
|
||
|
default:
|
||
|
return "???"
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// String returns a stringified version of the comment
|
||
|
func (c Comment) String() string {
|
||
|
return fmt.Sprintf("Comment: %v", c.Text)
|
||
|
}
|
||
|
|
||
|
// Comments defines the current view of comments from the parser
|
||
|
type Comments struct {
|
||
|
// CommentMap is a reference to the parser comment map
|
||
|
CommentMap CommentMap
|
||
|
// Comments lists the comments scanned, not linked to a node yet
|
||
|
Comments []*Comment
|
||
|
// future lists the comments after a line break during a sequence of comments
|
||
|
future []*Comment
|
||
|
// Current is node for which comments are linked to
|
||
|
Current Expression
|
||
|
|
||
|
// wasLineBreak determines if a line break occured while scanning for comments
|
||
|
wasLineBreak bool
|
||
|
// primary determines whether or not processing a primary expression
|
||
|
primary bool
|
||
|
// afterBlock determines whether or not being after a block statement
|
||
|
afterBlock bool
|
||
|
}
|
||
|
|
||
|
func NewComments() *Comments {
|
||
|
comments := &Comments{
|
||
|
CommentMap: CommentMap{},
|
||
|
}
|
||
|
|
||
|
return comments
|
||
|
}
|
||
|
|
||
|
func (c *Comments) String() string {
|
||
|
return fmt.Sprintf("NODE: %v, Comments: %v, Future: %v(LINEBREAK:%v)", c.Current, len(c.Comments), len(c.future), c.wasLineBreak)
|
||
|
}
|
||
|
|
||
|
// FetchAll returns all the currently scanned comments,
|
||
|
// including those from the next line
|
||
|
func (c *Comments) FetchAll() []*Comment {
|
||
|
defer func() {
|
||
|
c.Comments = nil
|
||
|
c.future = nil
|
||
|
}()
|
||
|
|
||
|
return append(c.Comments, c.future...)
|
||
|
}
|
||
|
|
||
|
// Fetch returns all the currently scanned comments
|
||
|
func (c *Comments) Fetch() []*Comment {
|
||
|
defer func() {
|
||
|
c.Comments = nil
|
||
|
}()
|
||
|
|
||
|
return c.Comments
|
||
|
}
|
||
|
|
||
|
// ResetLineBreak marks the beginning of a new statement
|
||
|
func (c *Comments) ResetLineBreak() {
|
||
|
c.wasLineBreak = false
|
||
|
}
|
||
|
|
||
|
// MarkPrimary will mark the context as processing a primary expression
|
||
|
func (c *Comments) MarkPrimary() {
|
||
|
c.primary = true
|
||
|
c.wasLineBreak = false
|
||
|
}
|
||
|
|
||
|
// AfterBlock will mark the context as being after a block.
|
||
|
func (c *Comments) AfterBlock() {
|
||
|
c.afterBlock = true
|
||
|
}
|
||
|
|
||
|
// AddComment adds a comment to the view.
|
||
|
// Depending on the context, comments are added normally or as post line break.
|
||
|
func (c *Comments) AddComment(comment *Comment) {
|
||
|
if c.primary {
|
||
|
if !c.wasLineBreak {
|
||
|
c.Comments = append(c.Comments, comment)
|
||
|
} else {
|
||
|
c.future = append(c.future, comment)
|
||
|
}
|
||
|
} else {
|
||
|
if !c.wasLineBreak || (c.Current == nil && !c.afterBlock) {
|
||
|
c.Comments = append(c.Comments, comment)
|
||
|
} else {
|
||
|
c.future = append(c.future, comment)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// MarkComments will mark the found comments as the given position.
|
||
|
func (c *Comments) MarkComments(position CommentPosition) {
|
||
|
for _, comment := range c.Comments {
|
||
|
if comment.Position == TBD {
|
||
|
comment.Position = position
|
||
|
}
|
||
|
}
|
||
|
for _, c := range c.future {
|
||
|
if c.Position == TBD {
|
||
|
c.Position = position
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Unset the current node and apply the comments to the current expression.
|
||
|
// Resets context variables.
|
||
|
func (c *Comments) Unset() {
|
||
|
if c.Current != nil {
|
||
|
c.applyComments(c.Current, c.Current, TRAILING)
|
||
|
c.Current = nil
|
||
|
}
|
||
|
c.wasLineBreak = false
|
||
|
c.primary = false
|
||
|
c.afterBlock = false
|
||
|
}
|
||
|
|
||
|
// SetExpression sets the current expression.
|
||
|
// It is applied the found comments, unless the previous expression has not been unset.
|
||
|
// It is skipped if the node is already set or if it is a part of the previous node.
|
||
|
func (c *Comments) SetExpression(node Expression) {
|
||
|
// Skipping same node
|
||
|
if c.Current == node {
|
||
|
return
|
||
|
}
|
||
|
if c.Current != nil && c.Current.Idx1() == node.Idx1() {
|
||
|
c.Current = node
|
||
|
return
|
||
|
}
|
||
|
previous := c.Current
|
||
|
c.Current = node
|
||
|
|
||
|
// Apply the found comments and futures to the node and the previous.
|
||
|
c.applyComments(node, previous, TRAILING)
|
||
|
}
|
||
|
|
||
|
// PostProcessNode applies all found comments to the given node
|
||
|
func (c *Comments) PostProcessNode(node Node) {
|
||
|
c.applyComments(node, nil, TRAILING)
|
||
|
}
|
||
|
|
||
|
// applyComments applies both the comments and the future comments to the given node and the previous one,
|
||
|
// based on the context.
|
||
|
func (c *Comments) applyComments(node, previous Node, position CommentPosition) {
|
||
|
if previous != nil {
|
||
|
c.CommentMap.AddComments(previous, c.Comments, position)
|
||
|
c.Comments = nil
|
||
|
} else {
|
||
|
c.CommentMap.AddComments(node, c.Comments, position)
|
||
|
c.Comments = nil
|
||
|
}
|
||
|
// Only apply the future comments to the node if the previous is set.
|
||
|
// This is for detecting end of line comments and which node comments on the following lines belongs to
|
||
|
if previous != nil {
|
||
|
c.CommentMap.AddComments(node, c.future, position)
|
||
|
c.future = nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// AtLineBreak will mark a line break
|
||
|
func (c *Comments) AtLineBreak() {
|
||
|
c.wasLineBreak = true
|
||
|
}
|
||
|
|
||
|
// CommentMap is the data structure where all found comments are stored
|
||
|
type CommentMap map[Node][]*Comment
|
||
|
|
||
|
// AddComment adds a single comment to the map
|
||
|
func (cm CommentMap) AddComment(node Node, comment *Comment) {
|
||
|
list := cm[node]
|
||
|
list = append(list, comment)
|
||
|
|
||
|
cm[node] = list
|
||
|
}
|
||
|
|
||
|
// AddComments adds a slice of comments, given a node and an updated position
|
||
|
func (cm CommentMap) AddComments(node Node, comments []*Comment, position CommentPosition) {
|
||
|
for _, comment := range comments {
|
||
|
if comment.Position == TBD {
|
||
|
comment.Position = position
|
||
|
}
|
||
|
cm.AddComment(node, comment)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Size returns the size of the map
|
||
|
func (cm CommentMap) Size() int {
|
||
|
size := 0
|
||
|
for _, comments := range cm {
|
||
|
size += len(comments)
|
||
|
}
|
||
|
|
||
|
return size
|
||
|
}
|
||
|
|
||
|
// MoveComments moves comments with a given position from a node to another
|
||
|
func (cm CommentMap) MoveComments(from, to Node, position CommentPosition) {
|
||
|
for i, c := range cm[from] {
|
||
|
if c.Position == position {
|
||
|
cm.AddComment(to, c)
|
||
|
|
||
|
// Remove the comment from the "from" slice
|
||
|
cm[from][i] = cm[from][len(cm[from])-1]
|
||
|
cm[from][len(cm[from])-1] = nil
|
||
|
cm[from] = cm[from][:len(cm[from])-1]
|
||
|
}
|
||
|
}
|
||
|
}
|