1
16
17
18
19 package build
20
21 import (
22 "path"
23 "path/filepath"
24 "regexp"
25 "sort"
26 "strings"
27
28 "github.com/bazelbuild/buildtools/tables"
29 )
30
31
32 var DisableRewrites []string
33
34
35 func disabled(name string) bool {
36 for _, x := range DisableRewrites {
37 if name == x {
38 return true
39 }
40 }
41 return false
42 }
43
44
45 var AllowSort []string
46
47
48 func allowedSort(name string) bool {
49 for _, x := range AllowSort {
50 if name == x {
51 return true
52 }
53 }
54 return false
55 }
56
57
58
59
60
61
62 type Rewriter struct {
63 RewriteSet []string
64 IsLabelArg map[string]bool
65 LabelDenyList map[string]bool
66 IsSortableListArg map[string]bool
67 SortableDenylist map[string]bool
68 SortableAllowlist map[string]bool
69 NamePriority map[string]int
70 StripLabelLeadingSlashes bool
71 ShortenAbsoluteLabelsToRelative bool
72 }
73
74
75 func Rewrite(f *File) {
76 var rewriter = &Rewriter{
77 IsLabelArg: tables.IsLabelArg,
78 LabelDenyList: tables.LabelDenylist,
79 IsSortableListArg: tables.IsSortableListArg,
80 SortableDenylist: tables.SortableDenylist,
81 SortableAllowlist: tables.SortableAllowlist,
82 NamePriority: tables.NamePriority,
83 StripLabelLeadingSlashes: tables.StripLabelLeadingSlashes,
84 ShortenAbsoluteLabelsToRelative: tables.ShortenAbsoluteLabelsToRelative,
85 }
86 rewriter.Rewrite(f)
87 }
88
89
90 func (w *Rewriter) Rewrite(f *File) {
91 for _, r := range rewrites {
92
93
94
95 if (!disabled(r.name) && (f.Type&r.scope != 0) && w.RewriteSet == nil) || (w.RewriteSet != nil && rewriteSetContains(w, r.name)) {
96 r.fn(f, w)
97 }
98 }
99 }
100
101 func rewriteSetContains(w *Rewriter, name string) bool {
102 for _, value := range w.RewriteSet {
103 if value == name {
104 return true
105 }
106 }
107 return false
108 }
109
110
111
112 const (
113 scopeDefault = TypeDefault | TypeBzl
114 scopeBuild = TypeBuild | TypeWorkspace | TypeModule
115 scopeBoth = scopeDefault | scopeBuild
116 )
117
118
119
120
121 var rewrites = []struct {
122 name string
123 fn func(*File, *Rewriter)
124 scope FileType
125 }{
126 {"removeParens", removeParens, scopeBuild},
127 {"callsort", sortCallArgs, scopeBuild},
128 {"label", fixLabels, scopeBuild},
129 {"listsort", sortStringLists, scopeBoth},
130 {"multiplus", fixMultilinePlus, scopeBuild},
131 {"loadTop", moveLoadOnTop, scopeBoth},
132 {"sameOriginLoad", compressSameOriginLoads, scopeBoth},
133 {"sortLoadStatements", sortLoadStatements, scopeBoth},
134 {"loadsort", sortAllLoadArgs, scopeBoth},
135 {"useRepoPositionalsSort", sortUseRepoPositionals, TypeModule},
136 {"formatdocstrings", formatDocstrings, scopeBoth},
137 {"reorderarguments", reorderArguments, scopeBoth},
138 {"editoctal", editOctals, scopeBoth},
139 }
140
141
142
143 func leaveAlone(stk []Expr, final Expr) bool {
144 for _, x := range stk {
145 if leaveAlone1(x) {
146 return true
147 }
148 }
149 if final != nil && leaveAlone1(final) {
150 return true
151 }
152 return false
153 }
154
155
156
157 func hasComment(x Expr, text string) bool {
158 if x == nil {
159 return false
160 }
161 for _, com := range x.Comment().Before {
162 if strings.Contains(strings.ToLower(com.Token), text) {
163 return true
164 }
165 }
166 for _, com := range x.Comment().After {
167 if strings.Contains(strings.ToLower(com.Token), text) {
168 return true
169 }
170 }
171 for _, com := range x.Comment().Suffix {
172 if strings.Contains(strings.ToLower(com.Token), text) {
173 return true
174 }
175 }
176 return false
177 }
178
179
180
181 func isCommentAnywhere(f *File, text string) bool {
182 commentExists := false
183 WalkInterruptable(f, func(node Expr, stack []Expr) (err error) {
184 if hasComment(node, text) {
185 commentExists = true
186 return &StopTraversalError{}
187 }
188 return nil
189 })
190 return commentExists
191 }
192
193
194
195 func leaveAlone1(x Expr) bool {
196 return hasComment(x, "buildifier: leave-alone")
197 }
198
199
200
201 func doNotSort(x Expr) bool {
202 return hasComment(x, "do not sort")
203 }
204
205
206
207 func keepSorted(x Expr) bool {
208 return hasComment(x, "keep sorted")
209 }
210
211
212
213
214
215
216
217
218
219 func fixLabels(f *File, w *Rewriter) {
220 joinLabel := func(p *Expr) {
221 add, ok := (*p).(*BinaryExpr)
222 if !ok || add.Op != "+" {
223 return
224 }
225 str1, ok := add.X.(*StringExpr)
226 if !ok || !strings.HasPrefix(str1.Value, "//") || strings.Contains(str1.Value, " ") {
227 return
228 }
229 str2, ok := add.Y.(*StringExpr)
230 if !ok || strings.Contains(str2.Value, " ") {
231 return
232 }
233 str1.Value += str2.Value
234
235
236
237 com1 := add.Comment()
238 com2 := str1.Comment()
239 com3 := str2.Comment()
240 com1.Before = append(com1.Before, com2.Before...)
241 com1.Before = append(com1.Before, com3.Before...)
242 com1.Suffix = append(com1.Suffix, com2.Suffix...)
243 com1.Suffix = append(com1.Suffix, com3.Suffix...)
244 *str1.Comment() = *com1
245
246 *p = str1
247 }
248
249 labelPrefix := "//"
250 if w.StripLabelLeadingSlashes {
251 labelPrefix = ""
252 }
253
254
255 labelRE := regexp.MustCompile(`^(((?:@(\w+))?//|` + labelPrefix + `)(?:.+/)?([^:]*))(?::([^:]+))?$`)
256
257 shortenLabel := func(v Expr) {
258 str, ok := v.(*StringExpr)
259 if !ok {
260 return
261 }
262
263 if w.StripLabelLeadingSlashes && strings.HasPrefix(str.Value, "//") {
264 if filepath.Dir(f.Path) == "." || !strings.HasPrefix(str.Value, "//:") {
265 str.Value = str.Value[2:]
266 }
267 }
268 if w.ShortenAbsoluteLabelsToRelative {
269 thisPackage := labelPrefix + filepath.Dir(f.Path)
270
271 if filepath.Separator != '/' {
272 thisPackage = strings.Replace(thisPackage, string(filepath.Separator), "/", -1)
273 }
274
275 if str.Value == thisPackage {
276 str.Value = ":" + path.Base(str.Value)
277 } else if strings.HasPrefix(str.Value, thisPackage+":") {
278 str.Value = str.Value[len(thisPackage):]
279 }
280 }
281
282 m := labelRE.FindStringSubmatch(str.Value)
283 if m == nil {
284 return
285 }
286 if m[4] != "" && m[4] == m[5] {
287 str.Value = m[1]
288 } else if m[3] != "" && m[4] == "" && m[3] == m[5] {
289 str.Value = "@" + m[3]
290 }
291 }
292
293
294
295
296 fixLabelsWithinAContainer := func(e *Expr) {
297 if list, ok := (*e).(*ListExpr); ok {
298 for i := range list.List {
299 if leaveAlone1(list.List[i]) {
300 continue
301 }
302 joinLabel(&list.List[i])
303 shortenLabel(list.List[i])
304 }
305 }
306 if set, ok := (*e).(*SetExpr); ok {
307 for i := range set.List {
308 if leaveAlone1(set.List[i]) {
309 continue
310 }
311 joinLabel(&set.List[i])
312 shortenLabel(set.List[i])
313 }
314 } else {
315 joinLabel(e)
316 shortenLabel(*e)
317 }
318 }
319
320 Walk(f, func(v Expr, stk []Expr) {
321 switch v := v.(type) {
322 case *CallExpr:
323 if leaveAlone(stk, v) {
324 return
325 }
326 for i := range v.List {
327 if leaveAlone1(v.List[i]) {
328 continue
329 }
330 as, ok := v.List[i].(*AssignExpr)
331 if !ok {
332 continue
333 }
334 key, ok := as.LHS.(*Ident)
335 if !ok || !w.IsLabelArg[key.Name] || w.LabelDenyList[callName(v)+"."+key.Name] {
336 continue
337 }
338 if leaveAlone1(as.RHS) {
339 continue
340 }
341
342 findAndModifyStrings(&as.RHS, fixLabelsWithinAContainer)
343 }
344 }
345 })
346 }
347
348
349
350
351 func callName(call *CallExpr) string {
352 return (&Rule{call, ""}).Kind()
353 }
354
355
356 func sortCallArgs(f *File, w *Rewriter) {
357
358 Walk(f, func(v Expr, stk []Expr) {
359 call, ok := v.(*CallExpr)
360 if !ok {
361 return
362 }
363 if leaveAlone(stk, call) {
364 return
365 }
366 rule := callName(call)
367 if rule == "" {
368 rule = "<complex rule kind>"
369 }
370
371
372 start := len(call.List)
373 for start > 0 && argName(call.List[start-1]) != "" {
374 start--
375 }
376
377
378 var args namedArgs
379 for i, x := range call.List[start:] {
380 name := argName(x)
381 args = append(args, namedArg{ruleNamePriority(w, rule, name), name, i, x})
382 }
383
384
385 if sort.IsSorted(args) {
386 return
387 }
388 sort.Sort(args)
389 for i, x := range args {
390 call.List[start+i] = x.expr
391 }
392 })
393 }
394
395
396
397
398 func ruleNamePriority(w *Rewriter, rule, arg string) int {
399 ruleArg := rule + "." + arg
400 if val, ok := w.NamePriority[ruleArg]; ok {
401 return val
402 }
403 return w.NamePriority[arg]
404
405
417 }
418
419
420
421 func argName(x Expr) string {
422 if as, ok := x.(*AssignExpr); ok {
423 if id, ok := as.LHS.(*Ident); ok {
424 return id.Name
425 }
426 }
427 return ""
428 }
429
430
431
432 type namedArg struct {
433 priority int
434 name string
435 index int
436 expr Expr
437 }
438
439
440 type namedArgs []namedArg
441
442 func (x namedArgs) Len() int { return len(x) }
443 func (x namedArgs) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
444
445 func (x namedArgs) Less(i, j int) bool {
446 p := x[i]
447 q := x[j]
448 if p.priority != q.priority {
449 return p.priority < q.priority
450 }
451 if p.name != q.name {
452 return p.name < q.name
453 }
454 return p.index < q.index
455 }
456
457
458 func sortStringLists(f *File, w *Rewriter) {
459 sortStringList := func(x *Expr) {
460 SortStringList(*x)
461 }
462
463 Walk(f, func(e Expr, stk []Expr) {
464 switch v := e.(type) {
465 case *CallExpr:
466 if f.Type == TypeDefault || f.Type == TypeBzl {
467
468 return
469 }
470 if leaveAlone(stk, v) {
471 return
472 }
473 rule := callName(v)
474 for _, arg := range v.List {
475 if leaveAlone1(arg) {
476 continue
477 }
478 as, ok := arg.(*AssignExpr)
479 if !ok || leaveAlone1(as) {
480 continue
481 }
482 key, ok := as.LHS.(*Ident)
483 if !ok {
484 continue
485 }
486 context := rule + "." + key.Name
487 if w.SortableDenylist[context] {
488 continue
489 }
490 if w.IsSortableListArg[key.Name] ||
491 w.SortableAllowlist[context] ||
492 (!disabled("unsafesort") && allowedSort(context)) {
493 if doNotSort(as) {
494 deduplicateStringList(as.RHS)
495 } else {
496 findAndModifyStrings(&as.RHS, sortStringList)
497 }
498 }
499 }
500 case *AssignExpr:
501 if disabled("unsafesort") {
502 return
503 }
504
505 if keepSorted(v) {
506 findAndModifyStrings(&v.RHS, sortStringList)
507 }
508 case *KeyValueExpr:
509 if disabled("unsafesort") {
510 return
511 }
512
513 if keepSorted(v) {
514 findAndModifyStrings(&v.Value, sortStringList)
515 }
516 case *ListExpr:
517 if disabled("unsafesort") {
518 return
519 }
520
521 if len(v.List) > 0 && (keepSorted(v) || keepSorted(v.List[0])) {
522 findAndModifyStrings(&e, sortStringList)
523 }
524 }
525 })
526 }
527
528
529
530
531 func deduplicateStringList(x Expr) {
532 list, ok := x.(*ListExpr)
533 if !ok {
534 return
535 }
536
537 list.List = deduplicateStringExprs(list.List)
538 }
539
540
541
542
543 func deduplicateStringExprs(list []Expr) []Expr {
544 var comments []Comment
545 alreadySeen := make(map[string]bool)
546 var deduplicated []Expr
547 for _, value := range list {
548 str, ok := value.(*StringExpr)
549 if !ok {
550 deduplicated = append(deduplicated, value)
551 continue
552 }
553 strVal := str.Value
554 if _, ok := alreadySeen[strVal]; ok {
555
556
557 comments = append(comments, str.Comment().Before...)
558 comments = append(comments, str.Comment().After...)
559 continue
560 }
561 alreadySeen[strVal] = true
562 if len(comments) > 0 {
563 comments = append(comments, value.Comment().Before...)
564 value.Comment().Before = comments
565 comments = nil
566 }
567 deduplicated = append(deduplicated, value)
568 }
569 return deduplicated
570 }
571
572
573
574
575 func SortStringList(x Expr) {
576 list, ok := x.(*ListExpr)
577 if !ok || len(list.List) < 2 {
578 return
579 }
580
581 if doNotSort(list.List[0]) {
582 list.List = deduplicateStringExprs(list.List)
583 return
584 }
585
586 forceSort := keepSorted(list) || keepSorted(list.List[0])
587
588
589
590
591
592
593 if !forceSort {
594 if line, _ := hasComments(list); line {
595 deduplicateStringList(list)
596 return
597 }
598 }
599
600 list.List = sortStringExprs(list.List)
601 }
602
603
604
605
606
607
608
609
610 func findAndModifyStrings(x *Expr, callback func(*Expr)) {
611 callback(x)
612 switch x := (*x).(type) {
613 case *BinaryExpr:
614 if x.Op != "+" {
615 return
616 }
617 findAndModifyStrings(&x.X, callback)
618 findAndModifyStrings(&x.Y, callback)
619 case *CallExpr:
620 if ident, ok := x.X.(*Ident); !ok || ident.Name != "select" {
621 return
622 }
623 if len(x.List) == 0 {
624 return
625 }
626 dict, ok := x.List[0].(*DictExpr)
627 if !ok {
628 return
629 }
630 for _, kv := range dict.List {
631 findAndModifyStrings(&kv.Value, callback)
632 }
633 }
634 }
635
636 func sortStringExprs(list []Expr) []Expr {
637 if len(list) < 2 {
638 return list
639 }
640
641
642 for i := 0; i < len(list); {
643 if _, ok := list[i].(*StringExpr); !ok {
644 i++
645 continue
646 }
647
648 j := i + 1
649 for ; j < len(list); j++ {
650 if str, ok := list[j].(*StringExpr); !ok || len(str.Before) > 0 {
651 break
652 }
653 }
654
655 var chunk []stringSortKey
656 for index, x := range list[i:j] {
657 chunk = append(chunk, makeSortKey(index, x.(*StringExpr)))
658 }
659 if !sort.IsSorted(byStringExpr(chunk)) || !isUniq(chunk) {
660 before := chunk[0].x.Comment().Before
661 chunk[0].x.Comment().Before = nil
662
663 sort.Sort(byStringExpr(chunk))
664 chunk = uniq(chunk)
665
666 chunk[0].x.Comment().Before = before
667 for offset, key := range chunk {
668 list[i+offset] = key.x
669 }
670 list = append(list[:(i+len(chunk))], list[j:]...)
671 }
672
673 i = j
674 }
675
676 return list
677 }
678
679
680
681 func uniq(sortedList []stringSortKey) []stringSortKey {
682 out := sortedList[:0]
683 for _, sk := range sortedList {
684 if len(out) == 0 || sk.value != out[len(out)-1].value {
685 out = append(out, sk)
686 }
687 }
688 return out
689 }
690
691
692 func isUniq(list []stringSortKey) bool {
693 for i := range list {
694 if i+1 < len(list) && list[i].value == list[i+1].value {
695 return false
696 }
697 }
698 return true
699 }
700
701
702
703 func callArgName(stk []Expr) string {
704 n := len(stk)
705 if n < 2 {
706 return ""
707 }
708 arg := argName(stk[n-1])
709 if arg == "" {
710 return ""
711 }
712 call, ok := stk[n-2].(*CallExpr)
713 if !ok {
714 return ""
715 }
716 rule, ok := call.X.(*Ident)
717 if !ok {
718 return ""
719 }
720 return rule.Name + "." + arg
721 }
722
723
724
725
726
727
728
729 type stringSortKey struct {
730 phase int
731 split []string
732 value string
733 original int
734 x Expr
735 }
736
737 func makeSortKey(index int, x *StringExpr) stringSortKey {
738 key := stringSortKey{
739 value: x.Value,
740 original: index,
741 x: x,
742 }
743
744 switch {
745 case strings.HasPrefix(x.Value, ":"):
746 key.phase = 1
747 case strings.HasPrefix(x.Value, "//") || (tables.StripLabelLeadingSlashes && !strings.HasPrefix(x.Value, "@")):
748 key.phase = 2
749 case strings.HasPrefix(x.Value, "@"):
750 key.phase = 3
751 }
752
753 key.split = strings.Split(strings.Replace(x.Value, ":", ".", -1), ".")
754 return key
755 }
756
757
758 type byStringExpr []stringSortKey
759
760 func (x byStringExpr) Len() int { return len(x) }
761 func (x byStringExpr) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
762
763 func (x byStringExpr) Less(i, j int) bool {
764 xi := x[i]
765 xj := x[j]
766
767 if xi.phase != xj.phase {
768 return xi.phase < xj.phase
769 }
770 for k := 0; k < len(xi.split) && k < len(xj.split); k++ {
771 if xi.split[k] != xj.split[k] {
772 return xi.split[k] < xj.split[k]
773 }
774 }
775 if len(xi.split) != len(xj.split) {
776 return len(xi.split) < len(xj.split)
777 }
778 if xi.value != xj.value {
779 return xi.value < xj.value
780 }
781 return xi.original < xj.original
782 }
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803 func fixMultilinePlus(f *File, _ *Rewriter) {
804
805
806
807
808
809
810 var isList func(x Expr) bool
811 isList = func(x Expr) bool {
812 switch x := x.(type) {
813 case *ListExpr:
814 return true
815 case *CallExpr:
816 if len(x.List) == 1 {
817 return isList(x.List[0])
818 }
819 }
820 return false
821 }
822
823
824 var isMultiLine func(Expr) bool
825 isMultiLine = func(x Expr) bool {
826 switch x := x.(type) {
827 case *ListExpr:
828 return x.ForceMultiLine || len(x.List) > 1
829 case *CallExpr:
830 if x.ForceMultiLine || len(x.List) > 1 && !x.ForceCompact {
831 return true
832 }
833 if len(x.List) == 1 {
834 return isMultiLine(x.List[0])
835 }
836 }
837 return false
838 }
839
840
841
842 var forceMultiLine func(Expr) bool
843 forceMultiLine = func(x Expr) bool {
844 switch x := x.(type) {
845 case *ListExpr:
846
847 if x.ForceMultiLine {
848 return true
849 }
850
851
852 if len(x.List) == 1 && forceMultiLine(x.List[0]) {
853 return true
854 }
855 x.ForceMultiLine = true
856 return true
857
858 case *CallExpr:
859 if len(x.List) == 1 {
860 return forceMultiLine(x.List[0])
861 }
862 }
863 return false
864 }
865
866 skip := map[Expr]bool{}
867 Walk(f, func(v Expr, stk []Expr) {
868 if skip[v] {
869 return
870 }
871 bin, ok := v.(*BinaryExpr)
872 if !ok || bin.Op != "+" {
873 return
874 }
875
876
877
878
879
880
881
882
883 var all []Expr
884 for {
885 all = append(all, bin.Y, bin)
886 bin1, ok := bin.X.(*BinaryExpr)
887 if !ok || bin1.Op != "+" {
888 break
889 }
890 bin = bin1
891 skip[bin] = true
892 }
893 all = append(all, bin.X)
894
895
896
897 for i, j := 0, len(all)-1; i < j; i, j = i+1, j-1 {
898 all[i], all[j] = all[j], all[i]
899 }
900
901
902
903
904 haveList := false
905 for i := 0; i < len(all); i += 2 {
906 if isList(all[i]) {
907 haveList = true
908 break
909 }
910 }
911 if !haveList {
912 return
913 }
914
915
916
917 for i := 1; i < len(all); i += 2 {
918 bin := all[i].(*BinaryExpr)
919 if !bin.LineBreak {
920 continue
921 }
922
923
924
925
926 if forceMultiLine(all[i+1]) {
927 bin.LineBreak = false
928 continue
929 }
930
931
932
933
934 if isMultiLine(all[i-1]) {
935 bin.LineBreak = false
936 continue
937 }
938 }
939 })
940 }
941
942
943 func isFunctionCall(expr Expr, name string) bool {
944 call, ok := expr.(*CallExpr)
945 if !ok {
946 return false
947 }
948 if ident, ok := call.X.(*Ident); ok && ident.Name == name {
949 return true
950 }
951 return false
952 }
953
954
955 func moveLoadOnTop(f *File, _ *Rewriter) {
956 if f.Type == TypeWorkspace {
957
958 return
959 }
960 if isCommentAnywhere(f, "disable=load-on-top") {
961
962
963
964
965
966 return
967 }
968
969
970 misplacedLoads := make(map[int]*LoadStmt)
971 firstStmtIndex := -1
972 for i := 0; i < len(f.Stmt); i++ {
973 stmt := f.Stmt[i]
974 _, isString := stmt.(*StringExpr)
975 _, isComment := stmt.(*CommentBlock)
976 isWorkspace := isFunctionCall(stmt, "workspace")
977 if isString || isComment || isWorkspace || stmt == nil {
978 continue
979 }
980 load, ok := stmt.(*LoadStmt)
981 if !ok {
982 if firstStmtIndex == -1 {
983 firstStmtIndex = i
984 }
985 continue
986 }
987 if firstStmtIndex == -1 {
988 continue
989 }
990 misplacedLoads[i] = load
991 }
992
993
994 if firstStmtIndex == -1 {
995 firstStmtIndex = 0
996 }
997 offset := len(misplacedLoads)
998 if offset == 0 {
999 return
1000 }
1001 stmtCopy := append([]Expr{}, f.Stmt...)
1002 for i := range f.Stmt {
1003 if i < firstStmtIndex {
1004
1005 continue
1006 } else if _, ok := misplacedLoads[i]; ok {
1007
1008 f.Stmt[firstStmtIndex] = stmtCopy[i]
1009 firstStmtIndex++
1010 offset--
1011 if offset == 0 {
1012
1013 break
1014 }
1015 } else {
1016
1017
1018 f.Stmt[i+offset] = stmtCopy[i]
1019 }
1020 }
1021 }
1022
1023
1024 func compressSameOriginLoads(f *File, _ *Rewriter) {
1025
1026 loads := make(map[string]*LoadStmt)
1027
1028 for stmtIndex := 0; stmtIndex < len(f.Stmt); stmtIndex++ {
1029 load, ok := f.Stmt[stmtIndex].(*LoadStmt)
1030 if !ok {
1031 continue
1032 }
1033
1034 previousLoad, ok := loads[load.Module.Value]
1035 if !ok {
1036 loads[load.Module.Value] = load
1037 continue
1038 }
1039 if hasComment(previousLoad, "disable=same-origin-load") ||
1040 hasComment(load, "disable=same-origin-load") {
1041 continue
1042 }
1043
1044
1045 previousLoad.From = append(previousLoad.From, load.From...)
1046 previousLoad.To = append(previousLoad.To, load.To...)
1047
1048 f.Stmt[stmtIndex] = nil
1049 }
1050 }
1051
1052
1053
1054 func comparePaths(path1, path2 string) bool {
1055 if path1 == path2 {
1056 return false
1057 }
1058
1059 chunks1 := strings.Split(path1, "/")
1060 chunks2 := strings.Split(path2, "/")
1061
1062 for i, chunk1 := range chunks1 {
1063 if i >= len(chunks2) {
1064 return false
1065 }
1066 chunk2 := chunks2[i]
1067
1068 chunk1Lower := strings.ToLower(chunk1)
1069 chunk2Lower := strings.ToLower(chunk2)
1070 if chunk1Lower != chunk2Lower {
1071 return chunk1Lower < chunk2Lower
1072 }
1073 }
1074
1075
1076
1077 return path1 <= path2
1078 }
1079
1080
1081
1082
1083
1084 func compareLoadLabels(load1Label, load2Label string) bool {
1085
1086
1087 isExplicitRepo1 := strings.HasPrefix(load1Label, "@")
1088 isExplicitRepo2 := strings.HasPrefix(load2Label, "@")
1089
1090 if isExplicitRepo1 != isExplicitRepo2 {
1091
1092 return isExplicitRepo1
1093 }
1094
1095
1096
1097 module1Parts := strings.SplitN(load1Label, ":", 2)
1098 package1, filename1 := "", module1Parts[0]
1099 if len(module1Parts) == 2 {
1100 package1, filename1 = module1Parts[0], module1Parts[1]
1101 }
1102 module2Parts := strings.SplitN(load2Label, ":", 2)
1103 package2, filename2 := "", module2Parts[0]
1104 if len(module2Parts) == 2 {
1105 package2, filename2 = module2Parts[0], module2Parts[1]
1106 }
1107
1108
1109 if package1 == package2 {
1110 return comparePaths(filename1, filename2)
1111 }
1112
1113
1114 if len(package1) == 0 || len(package2) == 0 {
1115 return len(package1) > 0
1116 }
1117
1118
1119 return comparePaths(package1, package2)
1120 }
1121
1122
1123
1124 func sortLoadStatements(f *File, _ *Rewriter) {
1125 if isCommentAnywhere(f, "disable=out-of-order-load") {
1126
1127
1128
1129
1130
1131 return
1132 }
1133
1134
1135 var loadsChunks [][]*LoadStmt
1136 newChunk := true
1137
1138 for _, stmt := range f.Stmt {
1139 if stmt == nil {
1140
1141
1142 continue
1143 }
1144 load, ok := stmt.(*LoadStmt)
1145 if !ok {
1146 newChunk = true
1147 continue
1148 }
1149 if newChunk {
1150
1151 loadsChunks = append(loadsChunks, []*LoadStmt{})
1152 newChunk = false
1153 }
1154 loadsChunks[len(loadsChunks)-1] = append(loadsChunks[len(loadsChunks)-1], load)
1155 }
1156
1157
1158 var sortedLoads []*LoadStmt
1159 for _, chunk := range loadsChunks {
1160 sortedChunk := append([]*LoadStmt{}, chunk...)
1161
1162 sort.SliceStable(sortedChunk, func(i, j int) bool {
1163 load1Label := (sortedChunk)[i].Module.Value
1164 load2Label := (sortedChunk)[j].Module.Value
1165 return compareLoadLabels(load1Label, load2Label)
1166 })
1167 sortedLoads = append(sortedLoads, sortedChunk...)
1168 }
1169
1170
1171 loadIndex := 0
1172 for stmtIndex := range f.Stmt {
1173 if _, ok := f.Stmt[stmtIndex].(*LoadStmt); !ok {
1174 continue
1175 }
1176 f.Stmt[stmtIndex] = sortedLoads[loadIndex]
1177 loadIndex++
1178 }
1179 }
1180
1181
1182 func sortAllLoadArgs(f *File, _ *Rewriter) {
1183 Walk(f, func(v Expr, stk []Expr) {
1184 if load, ok := v.(*LoadStmt); ok {
1185 SortLoadArgs(load)
1186 }
1187 })
1188 }
1189
1190 func sortUseRepoPositionals(f *File, _ *Rewriter) {
1191 Walk(f, func(v Expr, stk []Expr) {
1192 if call, ok := v.(*CallExpr); ok {
1193
1194
1195 if ident, ok := call.X.(*Ident); !ok || ident.Name != "use_repo" || len(call.List) < 3 {
1196 return
1197 }
1198
1199
1200 if doNotSort(call) || doNotSort(call.List[0]) || doNotSort(call.List[1]) {
1201 call.List = deduplicateStringExprs(call.List)
1202 } else {
1203
1204
1205 call.List = sortStringExprs(call.List)
1206 }
1207 }
1208 })
1209 }
1210
1211
1212
1213 func hasComments(list *ListExpr) (line, suffix bool) {
1214 com := list.Comment()
1215 if len(com.Before) > 0 || len(com.After) > 0 || len(list.End.Before) > 0 {
1216 line = true
1217 }
1218 if len(com.Suffix) > 0 {
1219 suffix = true
1220 }
1221 for _, elem := range list.List {
1222 com := elem.Comment()
1223 if len(com.Before) > 0 {
1224 line = true
1225 }
1226 if len(com.Suffix) > 0 {
1227 suffix = true
1228 }
1229 }
1230 return
1231 }
1232
1233
1234
1235
1236
1237 type loadArgs struct {
1238 From []*Ident
1239 To []*Ident
1240 modified bool
1241 }
1242
1243 func (args *loadArgs) Len() int {
1244 return len(args.From)
1245 }
1246
1247 func (args *loadArgs) Swap(i, j int) {
1248 args.From[i], args.From[j] = args.From[j], args.From[i]
1249 args.To[i], args.To[j] = args.To[j], args.To[i]
1250 args.modified = true
1251 }
1252
1253 func (args *loadArgs) Less(i, j int) bool {
1254
1255 equalI := args.From[i].Name == args.To[i].Name
1256 equalJ := args.From[j].Name == args.To[j].Name
1257 if equalI != equalJ {
1258
1259
1260 return equalI
1261 }
1262 return args.To[i].Name < args.To[j].Name
1263 }
1264
1265
1266
1267 func (args *loadArgs) deduplicate() {
1268 var from, to []*Ident
1269 for i := range args.To {
1270 if len(to) > 0 && to[len(to)-1].Name == args.To[i].Name {
1271
1272 from[len(from)-1] = args.From[i]
1273 to[len(to)-1] = args.To[i]
1274 args.modified = true
1275 } else {
1276
1277 from = append(from, args.From[i])
1278 to = append(to, args.To[i])
1279 }
1280 }
1281 args.From = from
1282 args.To = to
1283 }
1284
1285
1286 func SortLoadArgs(load *LoadStmt) bool {
1287 args := loadArgs{From: load.From, To: load.To}
1288 sort.Sort(&args)
1289 args.deduplicate()
1290 load.From = args.From
1291 load.To = args.To
1292 return args.modified
1293 }
1294
1295
1296 func formatDocstrings(f *File, _ *Rewriter) {
1297 Walk(f, func(v Expr, stk []Expr) {
1298 def, ok := v.(*DefStmt)
1299 if !ok || len(def.Body) == 0 {
1300 return
1301 }
1302 docstring, ok := def.Body[0].(*StringExpr)
1303 if !ok || !docstring.TripleQuote {
1304 return
1305 }
1306
1307 oldIndentation := docstring.Start.LineRune - 1
1308 newIndentation := nestedIndentation * len(stk)
1309
1310
1311
1312 updatedToken := formatString(docstring.Token, oldIndentation, newIndentation)
1313 if updatedToken != docstring.Token {
1314 docstring.Token = updatedToken
1315
1316 docstring.Value, _, _ = Unquote(updatedToken)
1317 }
1318 })
1319 }
1320
1321
1322
1323 func formatString(value string, oldIndentation, newIndentation int) string {
1324 difference := newIndentation - oldIndentation
1325 lines := strings.Split(value, "\n")
1326 for i, line := range lines {
1327 if i == 0 {
1328
1329 continue
1330 }
1331 if difference > 0 {
1332 line = strings.Repeat(" ", difference) + line
1333 } else {
1334 for i, rune := range line {
1335 if i == -difference || rune != ' ' {
1336 line = line[i:]
1337 break
1338 }
1339 }
1340 }
1341 if i != len(lines)-1 {
1342
1343
1344 line = strings.TrimRight(line, " ")
1345 }
1346 lines[i] = line
1347 }
1348 return strings.Join(lines, "\n")
1349 }
1350
1351
1352
1353 func argumentType(expr Expr) int {
1354 switch expr := expr.(type) {
1355 case *UnaryExpr:
1356 switch expr.Op {
1357 case "**":
1358 return 4
1359 case "*":
1360 return 3
1361 }
1362 case *AssignExpr:
1363 return 2
1364 }
1365 return 1
1366 }
1367
1368
1369
1370 func reorderArguments(f *File, _ *Rewriter) {
1371 Walk(f, func(expr Expr, stack []Expr) {
1372 call, ok := expr.(*CallExpr)
1373 if !ok {
1374 return
1375 }
1376 compare := func(i, j int) bool {
1377
1378
1379 if call.List[i] == nil || call.List[j] == nil {
1380 return false
1381 }
1382 return argumentType(call.List[i]) < argumentType(call.List[j])
1383 }
1384 if !sort.SliceIsSorted(call.List, compare) {
1385 sort.SliceStable(call.List, compare)
1386 }
1387 })
1388 }
1389
1390
1391
1392 func editOctals(f *File, _ *Rewriter) {
1393 Walk(f, func(expr Expr, stack []Expr) {
1394 l, ok := expr.(*LiteralExpr)
1395 if !ok {
1396 return
1397 }
1398 if len(l.Token) > 1 && l.Token[0] == '0' && l.Token[1] >= '0' && l.Token[1] <= '9' {
1399 l.Token = "0o" + l.Token[1:]
1400 }
1401 })
1402 }
1403
1404
1405 func removeParens(f *File, _ *Rewriter) {
1406 var simplify func(expr Expr, stack []Expr) Expr
1407 simplify = func(expr Expr, stack []Expr) Expr {
1408
1409
1410 pa, ok := expr.(*ParenExpr)
1411 if !ok || pa.ForceMultiLine {
1412 return expr
1413 }
1414 if len(pa.Comment().Before) > 0 || len(pa.Comment().After) > 0 || len(pa.Comment().Suffix) > 0 {
1415 return expr
1416 }
1417
1418 switch x := pa.X.(type) {
1419 case *Comprehension, *DictExpr, *Ident, *ListExpr, *LiteralExpr, *ParenExpr, *SetExpr, *StringExpr:
1420
1421 return Edit(x, simplify)
1422 case *CallExpr:
1423
1424 start, end := x.X.Span()
1425 if start.Line == end.Line {
1426 return Edit(x, simplify)
1427 }
1428 }
1429 return expr
1430 }
1431
1432 Edit(f, simplify)
1433 }
1434
View as plain text