Skip to content

Commit 7e40a46

Browse files
Internal change
PiperOrigin-RevId: 429669999
1 parent 39736dd commit 7e40a46

3 files changed

Lines changed: 288 additions & 83 deletions

File tree

ast/ast.go

Lines changed: 0 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package ast
33

44
import (
55
"fmt"
6-
"sort"
76
"strconv"
87
"strings"
98
)
@@ -73,67 +72,6 @@ type Node struct {
7372
IsAngleBracket bool
7473
}
7574

76-
func sortableNodes(ns []*Node) sortable {
77-
return sortable(ns)
78-
}
79-
80-
type sortable []*Node
81-
82-
func (ns sortable) Len() int {
83-
return len(ns)
84-
}
85-
86-
func (ns sortable) Swap(i, j int) {
87-
ns[i], ns[j] = ns[j], ns[i]
88-
}
89-
90-
// ByFieldName constructs a sort.Interface that sorts nodes by their field name.
91-
func ByFieldName(ns []*Node) sort.Interface {
92-
return byFieldName{sortableNodes(ns)}
93-
}
94-
95-
type byFieldName struct{ sortable }
96-
97-
func (ns byFieldName) Less(i, j int) bool {
98-
ni, nj := ns.sortable[i], ns.sortable[j]
99-
return ni.Name < nj.Name
100-
}
101-
102-
// ByFieldValue constructs a sort.Interface that sorts adjacent scalar nodes with the same name by
103-
// their value.
104-
func ByFieldValue(ns []*Node) sort.Interface {
105-
return byFieldValue{sortableNodes(ns)}
106-
}
107-
108-
type byFieldValue struct{ sortable }
109-
110-
func (ns byFieldValue) Less(i, j int) bool {
111-
ni, nj := ns.sortable[i], ns.sortable[j]
112-
if ni.Name != nj.Name || len(ni.Values) != 1 || len(nj.Values) != 1 {
113-
return false
114-
}
115-
return ni.Values[0].Value < nj.Values[0].Value
116-
}
117-
118-
// ByFieldNameAndValue constructs a sort.Interface that sorts nodes by their field name and scalar
119-
// value.
120-
func ByFieldNameAndValue(ns []*Node) sort.Interface {
121-
return byFieldNameAndValue{sortableNodes(ns)}
122-
}
123-
124-
type byFieldNameAndValue struct{ sortable }
125-
126-
func (ns byFieldNameAndValue) Less(i, j int) bool {
127-
ni, nj := ns.sortable[i], ns.sortable[j]
128-
if ni.Name != nj.Name {
129-
return ni.Name < nj.Name
130-
}
131-
if len(ni.Values) != 1 || len(nj.Values) != 1 {
132-
return false
133-
}
134-
return ni.Values[0].Value < nj.Values[0].Value
135-
}
136-
13775
// IsCommentOnly returns true if this is a comment-only node.
13876
func (n *Node) IsCommentOnly() bool {
13977
return n.Name == "" && n.Children == nil

parser/parser.go

Lines changed: 148 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,16 @@ type Config struct {
4040
// Sort adjacent scalar fields of the same field name by their contents.
4141
SortRepeatedFieldsByContent bool
4242

43+
// Map from Node.Name to the order of all fields within that node. See AddFieldSortOrder().
44+
fieldSortOrder map[string]map[string]int
45+
46+
// RequireFieldSortOrderToMatchAllFieldsInNode will cause parsing to fail if a node was added via
47+
// AddFieldSortOrder() but 1+ fields under that node in the textproto aren't specified in the
48+
// field order. This won't fail for nodes that don't have a field order specified at all. Use this
49+
// to strictly enforce that your field order config always orders ALL the fields, and you're
50+
// willing for new fields in the textproto to break parsing in order to enforce it.
51+
RequireFieldSortOrderToMatchAllFieldsInNode bool
52+
4353
// Remove lines that have the same field name and scalar value as another.
4454
RemoveDuplicateValuesForRepeatedFields bool
4555

@@ -62,6 +72,45 @@ type Config struct {
6272
SmartQuotes bool
6373
}
6474

75+
// AddFieldSortOrder adds a config rule for the given Node.Name, so that all contained field names
76+
// are output in the provided order.
77+
func (c *Config) AddFieldSortOrder(nodeName string, fieldOrder ...string) {
78+
if c.fieldSortOrder == nil {
79+
c.fieldSortOrder = make(map[string]map[string]int)
80+
}
81+
c.fieldSortOrder[nodeName] = makePriorities(fieldOrder...)
82+
}
83+
84+
// UnsortedFieldsError will be returned by ParseWithConfig if
85+
// Config.RequireFieldSortOrderToMatchAllFieldsInNode is set, and an unrecognized field is found
86+
// while parsing.
87+
type UnsortedFieldsError struct {
88+
UnsortedFields []UnsortedField
89+
}
90+
91+
// UnsortedField records details about a single unsorted field.
92+
type UnsortedField struct {
93+
Line int32
94+
ParentFieldName string
95+
FieldName string
96+
}
97+
98+
func (e *UnsortedFieldsError) Error() string {
99+
var errs []string
100+
for _, us := range e.UnsortedFields {
101+
errs = append(errs, fmt.Sprintf(" line: %d, parent field: %q, unsorted field: %q", us.Line, us.ParentFieldName, us.FieldName))
102+
}
103+
return fmt.Sprintf("fields parsed that were not specified in the parser.AddFieldSortOrder() call:\n%s", strings.Join(errs, "\n"))
104+
}
105+
106+
func makePriorities(fieldOrder ...string) map[string]int {
107+
priorities := make(map[string]int)
108+
for i, fieldName := range fieldOrder {
109+
priorities[fieldName] = i + 1
110+
}
111+
return priorities
112+
}
113+
65114
type parser struct {
66115
in []byte
67116
index int
@@ -309,7 +358,7 @@ func parseWithConfig(in []byte, c Config, metaComments map[string]bool) ([]*ast.
309358
return nil, fmt.Errorf("parser didn't consume all input. Stopped at %s", p.errorContext())
310359
}
311360
wrapStrings(nodes, 0, c)
312-
sortAndFilterNodes(nodes, nodeSortFunction(c.SortFieldsByFieldName, c.SortRepeatedFieldsByContent), c.RemoveDuplicateValuesForRepeatedFields)
361+
err = sortAndFilterNodes("", nodes, c)
313362
return nodes, err
314363
}
315364

@@ -914,15 +963,15 @@ func (p *parser) readTemplate() string {
914963
return p.advance(i)
915964
}
916965

917-
func sortAndFilterNodes(nodes []*ast.Node, sortFunction func([]*ast.Node), removeDuplicates bool) {
966+
func sortAndFilterNodes(parentNodeName string, nodes []*ast.Node, c Config) error {
918967
if len(nodes) == 0 {
919-
return
968+
return nil
920969
}
921970
type nameAndValue struct {
922971
name, value string
923972
}
924973
var seen map[nameAndValue]bool
925-
if removeDuplicates {
974+
if c.RemoveDuplicateValuesForRepeatedFields {
926975
seen = make(map[nameAndValue]bool)
927976
}
928977
for _, nd := range nodes {
@@ -935,11 +984,23 @@ func sortAndFilterNodes(nodes []*ast.Node, sortFunction func([]*ast.Node), remov
935984
seen[key] = true
936985
}
937986
}
938-
sortAndFilterNodes(nd.Children, sortFunction, removeDuplicates)
987+
err := sortAndFilterNodes(nd.Name, nd.Children, c)
988+
if err != nil {
989+
return err
990+
}
939991
}
940-
if sortFunction != nil {
941-
sortFunction(nodes)
992+
993+
s := sortable{
994+
config: c,
995+
nodes: nodes,
996+
}
997+
sortableByNameAndValue{sortable: s}.Sort()
998+
fieldOrderSorter := sortableByFieldOrder{sortable: s, parentNodeName: parentNodeName}
999+
fieldOrderSorter.Sort()
1000+
if c.RequireFieldSortOrderToMatchAllFieldsInNode && len(fieldOrderSorter.unsortedFields) > 0 {
1001+
return &UnsortedFieldsError{fieldOrderSorter.unsortedFields}
9421002
}
1003+
return nil
9431004
}
9441005

9451006
func wrapStrings(nodes []*ast.Node, depth int, c Config) {
@@ -1125,17 +1186,87 @@ func out(nodes []*ast.Node) []byte {
11251186
return result.Bytes()
11261187
}
11271188

1128-
func nodeSortFunction(sortByFieldName, sortByFieldValue bool) func([]*ast.Node) {
1129-
switch {
1130-
case sortByFieldName && sortByFieldValue:
1131-
return func(ns []*ast.Node) { sort.Stable(ast.ByFieldNameAndValue(ns)) }
1132-
case sortByFieldName:
1133-
return func(ns []*ast.Node) { sort.Stable(ast.ByFieldName(ns)) }
1134-
case sortByFieldValue:
1135-
return func(ns []*ast.Node) { sort.Stable(ast.ByFieldValue(ns)) }
1136-
default:
1137-
return nil
1189+
type sortable struct {
1190+
config Config
1191+
nodes []*ast.Node
1192+
}
1193+
1194+
func (s sortable) Len() int {
1195+
return len(s.nodes)
1196+
}
1197+
1198+
func (s sortable) Swap(i, j int) {
1199+
s.nodes[i], s.nodes[j] = s.nodes[j], s.nodes[i]
1200+
}
1201+
1202+
type sortableByNameAndValue struct {
1203+
sortable
1204+
}
1205+
1206+
func (s sortableByNameAndValue) Sort() {
1207+
if s.config.SortFieldsByFieldName || s.config.SortRepeatedFieldsByContent {
1208+
sort.Stable(s)
1209+
}
1210+
}
1211+
1212+
func (s sortableByNameAndValue) Less(i, j int) bool {
1213+
ni, nj := s.nodes[i], s.nodes[j]
1214+
if s.config.SortFieldsByFieldName {
1215+
if ni.Name != nj.Name {
1216+
return ni.Name < nj.Name
1217+
}
1218+
}
1219+
if s.config.SortRepeatedFieldsByContent {
1220+
if ni.Name != nj.Name || len(ni.Values) != 1 || len(nj.Values) != 1 {
1221+
return false
1222+
}
1223+
return ni.Values[0].Value < nj.Values[0].Value
1224+
}
1225+
return false
1226+
}
1227+
1228+
type sortableByFieldOrder struct {
1229+
sortable
1230+
parentNodeName string
1231+
1232+
// If a Config.fieldOrder exists for a parent field, but fields in the textproto are found that
1233+
// are not in the config, the unsorted field info will be added to this slice.
1234+
unsortedFields []UnsortedField
1235+
}
1236+
1237+
func (s *sortableByFieldOrder) Sort() {
1238+
// Only sort if we have field orders for this parent node.
1239+
if s.config.fieldSortOrder[s.parentNodeName] != nil {
1240+
sort.Stable(s)
1241+
}
1242+
}
1243+
1244+
func (s *sortableByFieldOrder) Less(i, j int) bool {
1245+
order := s.config.fieldSortOrder[s.parentNodeName]
1246+
if order == nil {
1247+
return false
1248+
}
1249+
1250+
// CommentOnly nodes don't set priority below, and default to 9999, which keeps them at the bottom
1251+
lprio := 9999
1252+
rprio := 9999
1253+
1254+
// Unknown fields will get the int nil value of 0 from the order map, and bubble to the top.
1255+
if !s.nodes[i].IsCommentOnly() {
1256+
node := s.nodes[i]
1257+
lprio = order[node.Name]
1258+
if lprio == 0 {
1259+
s.unsortedFields = append(s.unsortedFields, UnsortedField{node.Start.Line, s.parentNodeName, node.Name})
1260+
}
1261+
}
1262+
if !s.nodes[j].IsCommentOnly() {
1263+
node := s.nodes[j]
1264+
rprio = order[node.Name]
1265+
if rprio == 0 {
1266+
s.unsortedFields = append(s.unsortedFields, UnsortedField{node.Start.Line, s.parentNodeName, node.Name})
1267+
}
11381268
}
1269+
return lprio < rprio
11391270
}
11401271

11411272
// stringWriter abstracts over bytes.Buffer and strings.Builder

0 commit comments

Comments
 (0)