...

Source file src/github.com/protocolbuffers/txtpbfmt/ast/ast.go

Documentation: github.com/protocolbuffers/txtpbfmt/ast

     1  // Package ast provides data structure representing textproto syntax tree.
     2  package ast
     3  
     4  import (
     5  	"fmt"
     6  	"sort"
     7  	"strconv"
     8  	"strings"
     9  )
    10  
    11  // Position describes a position of a token in the input.
    12  // Both byte-based and line/column-based positions are maintained
    13  // because different downstream consumers need different formats
    14  // and we don't want to keep the entire input in memory to be able
    15  // to convert between the two.
    16  // Fields Byte, Line and Column should be interpreted as
    17  // ByteRange.start_byte, TextRange.start_line, and TextRange.start_column
    18  // of devtools.api.Range proto.
    19  type Position struct {
    20  	Byte   uint32
    21  	Line   int32
    22  	Column int32
    23  }
    24  
    25  // Node represents a field with a value in a proto message, or a comment unattached to a field.
    26  type Node struct {
    27  	// Start describes the start position of the node.
    28  	// For nodes that span entire lines, this is the first character
    29  	// of the first line attributed to the node; possible a whitespace if the node is indented.
    30  	// For nodes that are members of one-line message literals,
    31  	// this is the first non-whitespace character encountered.
    32  	Start Position
    33  	// Lines of comments appearing before the field.
    34  	// Each non-empty line starts with a # and does not contain the trailing newline.
    35  	PreComments []string
    36  	// Name of proto field (eg 'presubmit'). Will be an empty string for comment-only
    37  	// nodes and unqualified messages, e.g.
    38  	//     { name: "first_msg" }
    39  	//     { name: "second_msg" }
    40  	Name string
    41  	// Values, for nodes that don't have children.
    42  	Values []*Value
    43  	// Children for nodes that have children.
    44  	Children []*Node
    45  	// Whether or not this node was deleted by edits.
    46  	Deleted bool
    47  	// Should the colon after the field name be omitted?
    48  	// (e.g. "presubmit: {" vs "presubmit {")
    49  	SkipColon bool
    50  	// Whether or not all children are in the same line.
    51  	// (eg "base { id: "id" }")
    52  	ChildrenSameLine bool
    53  	// Comment in the same line as the "}".
    54  	ClosingBraceComment string
    55  	// End holds the position suitable for inserting new items.
    56  	// For multi-line nodes, this is the first character on the line with the closing brace.
    57  	// For single-line nodes, this is the first character after the last item (usually a space).
    58  	// For non-message nodes, this is Position zero value.
    59  	End Position
    60  	// Keep values in list (e.g "list: [1, 2]").
    61  	ValuesAsList bool
    62  	// Keep children in list (e.g "list: [ { value: 1 }, { value: 2 } ]").
    63  	ChildrenAsList bool
    64  	// Lines of comments appearing after last value inside list.
    65  	// Each non-empty line starts with a # and does not contain the trailing newline.
    66  	// e.g
    67  	// field: [
    68  	//   value
    69  	//   # Comment
    70  	// ]
    71  	PostValuesComments []string
    72  	// Whether the braces used for the children of this node are curly braces or angle brackets.
    73  	IsAngleBracket bool
    74  }
    75  
    76  // NodeLess is a sorting function that compares two *Nodes, possibly using the parent Node
    77  // for context, returning whether a is less than b.
    78  type NodeLess func(parent, a, b *Node, isWholeSlice bool) bool
    79  
    80  // ChainNodeLess combines two NodeLess functions that returns the first comparison if values are
    81  // not equal, else returns the second.
    82  func ChainNodeLess(first, second NodeLess) NodeLess {
    83  	if first == nil {
    84  		return second
    85  	}
    86  	if second == nil {
    87  		return first
    88  	}
    89  	return func(parent, a, b *Node, isWholeSlice bool) bool {
    90  		if isALess := first(parent, a, b, isWholeSlice); isALess {
    91  			return true
    92  		}
    93  		if isBLess := first(parent, b, a, isWholeSlice); isBLess {
    94  			return false
    95  		}
    96  		return second(parent, a, b, isWholeSlice)
    97  	}
    98  }
    99  
   100  // SortNodes sorts nodes by the given less function.
   101  func SortNodes(parent *Node, ns []*Node, less NodeLess) {
   102  	sort.Stable(sortableNodes(parent, ns, less, true /* isWholeSlice */))
   103  	end := 0
   104  	for begin := 0; begin < len(ns); begin = end {
   105  		for end = begin + 1; end < len(ns) && ns[begin].Name == ns[end].Name; end++ {
   106  		}
   107  		sort.Stable(sortableNodes(parent, ns[begin:end], less, false /* isWholeSlice */))
   108  	}
   109  }
   110  
   111  // sortableNodes returns a sort.Interface that sorts nodes by the given less function.
   112  func sortableNodes(parent *Node, ns []*Node, less NodeLess, isWholeSlice bool) sort.Interface {
   113  	return sortable{parent, ns, less, isWholeSlice}
   114  }
   115  
   116  type sortable struct {
   117  	parent       *Node
   118  	ns           []*Node
   119  	less         NodeLess
   120  	isWholeSlice bool
   121  }
   122  
   123  func (s sortable) Len() int {
   124  	return len(s.ns)
   125  }
   126  
   127  func (s sortable) Swap(i, j int) {
   128  	s.ns[i], s.ns[j] = s.ns[j], s.ns[i]
   129  }
   130  
   131  func (s sortable) Less(i, j int) bool {
   132  	if s.less == nil {
   133  		return false
   134  	}
   135  	return s.less(s.parent, s.ns[i], s.ns[j], s.isWholeSlice)
   136  }
   137  
   138  // ByFieldName is a NodeLess function that orders nodes by their field name.
   139  func ByFieldName(_, ni, nj *Node, isWholeSlice bool) bool {
   140  	return ni.Name < nj.Name
   141  }
   142  
   143  func getFieldValueForByFieldValue(n *Node) *Value {
   144  	if len(n.Values) != 1 {
   145  		return nil
   146  	}
   147  	return n.Values[0]
   148  }
   149  
   150  // ByFieldValue is a NodeLess function that orders adjacent scalar nodes with the same name by
   151  // their scalar value.
   152  func ByFieldValue(_, ni, nj *Node, isWholeSlice bool) bool {
   153  	if isWholeSlice {
   154  		return false
   155  	}
   156  	vi := getFieldValueForByFieldValue(ni)
   157  	vj := getFieldValueForByFieldValue(nj)
   158  	if vi == nil {
   159  		return vj != nil
   160  	}
   161  	if vj == nil {
   162  		return false
   163  	}
   164  	return vi.Value < vj.Value
   165  }
   166  
   167  func getChildValueByFieldSubfield(field, subfield string, n *Node) *Value {
   168  	if field != "" {
   169  		if n.Name != field {
   170  			return nil
   171  		}
   172  	}
   173  	return n.getChildValue(subfield)
   174  }
   175  
   176  // ByFieldSubfield returns a NodeLess function that orders adjacent message nodes with the given
   177  // field name by the given subfield name value. If no field name is provided, it compares the
   178  // subfields of any adjacent nodes with matching names.
   179  func ByFieldSubfield(field, subfield string) NodeLess {
   180  	return func(_, ni, nj *Node, isWholeSlice bool) bool {
   181  		if isWholeSlice {
   182  			return false
   183  		}
   184  		vi := getChildValueByFieldSubfield(field, subfield, ni)
   185  		vj := getChildValueByFieldSubfield(field, subfield, nj)
   186  		if vi == nil {
   187  			return vj != nil
   188  		}
   189  		if vj == nil {
   190  			return false
   191  		}
   192  		return vi.Value < vj.Value
   193  	}
   194  }
   195  
   196  // getChildValue returns the Value of the child with the given field name,
   197  // or nil if no single such child exists.
   198  func (n *Node) getChildValue(field string) *Value {
   199  	for _, c := range n.Children {
   200  		if c.Name == field {
   201  			if len(c.Values) != 1 {
   202  				return nil
   203  			}
   204  			return c.Values[0]
   205  		}
   206  	}
   207  	return nil
   208  }
   209  
   210  // IsCommentOnly returns true if this is a comment-only node.
   211  func (n *Node) IsCommentOnly() bool {
   212  	return n.Name == "" && n.Children == nil
   213  }
   214  
   215  type fixData struct {
   216  	inline bool
   217  }
   218  
   219  // Fix fixes inconsistencies that may arise after manipulation.
   220  //
   221  // For example if a node is ChildrenSameLine but has non-inline children, or
   222  // children with comments ChildrenSameLine will be set to false.
   223  func (n *Node) Fix() {
   224  	n.fix()
   225  }
   226  
   227  func isRealPosition(p Position) bool {
   228  	return p.Byte != 0 || p.Line != 0 || p.Column != 0
   229  }
   230  
   231  func (n *Node) fix() fixData {
   232  	isEmptyAndWasOriginallyInline := !(isRealPosition(n.Start) && isRealPosition(n.End) && n.End.Line-n.Start.Line > 0)
   233  	d := fixData{
   234  		// ChildrenSameLine may be false for cases with no children such as a
   235  		// value `foo: false`. We don't want these to trigger expansion.
   236  		inline: n.ChildrenSameLine || (len(n.Children) == 0 && isEmptyAndWasOriginallyInline && len(n.Values) <= 1),
   237  	}
   238  
   239  	for _, c := range n.Children {
   240  		if c.Deleted {
   241  			continue
   242  		}
   243  
   244  		cd := c.fix()
   245  		if !cd.inline {
   246  			d.inline = false
   247  		}
   248  	}
   249  
   250  	for _, v := range n.Values {
   251  		vd := v.fix()
   252  		if !vd.inline {
   253  			d.inline = false
   254  		}
   255  	}
   256  
   257  	n.ChildrenSameLine = d.inline
   258  
   259  	// textproto comments go until the end of the line, so we must force parents
   260  	// to be multiline otherwise we will partially comment them out.
   261  	if len(n.PreComments) > 0 || len(n.ClosingBraceComment) > 0 {
   262  		d.inline = false
   263  	}
   264  
   265  	return d
   266  }
   267  
   268  // StringNode is a helper for constructing simple string nodes.
   269  func StringNode(name, unquoted string) *Node {
   270  	return &Node{Name: name, Values: []*Value{{Value: strconv.Quote(unquoted)}}}
   271  }
   272  
   273  // Value represents a field value in a proto message.
   274  type Value struct {
   275  	// Lines of comments appearing before the value (for multi-line strings).
   276  	// Each non-empty line starts with a # and does not contain the trailing newline.
   277  	PreComments []string
   278  	// Node value (eg 'ERROR').
   279  	Value string
   280  	// Comment in the same line as the value.
   281  	InlineComment string
   282  }
   283  
   284  func (v *Value) String() string {
   285  	return fmt.Sprintf("{Value: %q, PreComments: %q, InlineComment: %q}", v.Value, strings.Join(v.PreComments, "\n"), v.InlineComment)
   286  }
   287  
   288  func (v *Value) fix() fixData {
   289  	return fixData{
   290  		inline: len(v.PreComments) == 0 && v.InlineComment == "",
   291  	}
   292  }
   293  
   294  // GetFromPath returns all nodes with a given string path in the parse tree. See ast_test.go for examples.
   295  func GetFromPath(nodes []*Node, path []string) []*Node {
   296  	if len(path) == 0 {
   297  		return nil
   298  	}
   299  	res := []*Node{}
   300  	for _, node := range nodes {
   301  		if node.Name == path[0] {
   302  			if len(path) == 1 {
   303  				res = append(res, node)
   304  			} else {
   305  				res = append(res, GetFromPath(node.Children, path[1:])...)
   306  			}
   307  		}
   308  	}
   309  	return res
   310  }
   311  

View as plain text