cuelang.org/go@v0.10.1/cue/format/import.go (about) 1 // Copyright 2018 The CUE Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package format 16 17 import ( 18 "sort" 19 "strconv" 20 21 "cuelang.org/go/cue/ast" 22 "cuelang.org/go/cue/token" 23 ) 24 25 // sortImports sorts runs of consecutive import lines in import blocks in f. 26 // It also removes duplicate imports when it is possible to do so without data 27 // loss. 28 func sortImports(d *ast.ImportDecl) { 29 if !d.Lparen.IsValid() || len(d.Specs) == 0 { 30 // Not a block: sorted by default. 31 return 32 } 33 34 // Identify and sort runs of specs on successive lines. 35 i := 0 36 specs := d.Specs[:0] 37 for j, s := range d.Specs { 38 if j > i && (s.Pos().RelPos() >= token.NewSection || hasDoc(s)) { 39 setRelativePos(s, token.Newline) 40 // j begins a new run. End this one. 41 block := sortSpecs(d.Specs[i:j]) 42 specs = append(specs, block...) 43 i = j 44 } 45 } 46 specs = append(specs, sortSpecs(d.Specs[i:])...) 47 setRelativePos(specs[0], token.Newline) 48 d.Specs = specs 49 } 50 51 func setRelativePos(s *ast.ImportSpec, r token.RelPos) { 52 if hasDoc(s) { 53 return 54 } 55 pos := s.Pos().WithRel(r) 56 if s.Name != nil { 57 s.Name.NamePos = pos 58 } else { 59 s.Path.ValuePos = pos 60 } 61 } 62 63 func hasDoc(s *ast.ImportSpec) bool { 64 for _, doc := range s.Comments() { 65 if doc.Doc { 66 return true 67 } 68 } 69 return false 70 } 71 72 func importPath(s *ast.ImportSpec) string { 73 t, err := strconv.Unquote(s.Path.Value) 74 if err == nil { 75 return t 76 } 77 return "" 78 } 79 80 func importName(s *ast.ImportSpec) string { 81 n := s.Name 82 if n == nil { 83 return "" 84 } 85 return n.Name 86 } 87 88 func importComment(s *ast.ImportSpec) string { 89 for _, c := range s.Comments() { 90 if c.Line { 91 return c.Text() 92 } 93 } 94 return "" 95 } 96 97 // collapse indicates whether prev may be removed, leaving only next. 98 func collapse(prev, next *ast.ImportSpec) bool { 99 if importPath(next) != importPath(prev) || importName(next) != importName(prev) { 100 return false 101 } 102 for _, c := range prev.Comments() { 103 if !c.Doc { 104 return false 105 } 106 } 107 return true 108 } 109 110 type posSpan struct { 111 Start token.Pos 112 End token.Pos 113 } 114 115 func sortSpecs(specs []*ast.ImportSpec) []*ast.ImportSpec { 116 // Can't short-circuit here even if specs are already sorted, 117 // since they might yet need deduplication. 118 // A lone import, however, may be safely ignored. 119 if len(specs) <= 1 { 120 setRelativePos(specs[0], token.NewSection) 121 return specs 122 } 123 124 // Record positions for specs. 125 pos := make([]posSpan, len(specs)) 126 for i, s := range specs { 127 pos[i] = posSpan{s.Pos(), s.End()} 128 } 129 130 // Sort the import specs by import path. 131 // Remove duplicates, when possible without data loss. 132 // Reassign the import paths to have the same position sequence. 133 // Reassign each comment to abut the end of its spec. 134 // Sort the comments by new position. 135 sort.Sort(byImportSpec(specs)) 136 137 // Dedup. Thanks to our sorting, we can just consider 138 // adjacent pairs of imports. 139 deduped := specs[:0] 140 for i, s := range specs { 141 if i == len(specs)-1 || !collapse(s, specs[i+1]) { 142 deduped = append(deduped, s) 143 } 144 } 145 specs = deduped 146 147 setRelativePos(specs[0], token.NewSection) 148 return specs 149 } 150 151 type byImportSpec []*ast.ImportSpec 152 153 func (x byImportSpec) Len() int { return len(x) } 154 func (x byImportSpec) Swap(i, j int) { x[i], x[j] = x[j], x[i] } 155 func (x byImportSpec) Less(i, j int) bool { 156 ipath := importPath(x[i]) 157 jpath := importPath(x[j]) 158 if ipath != jpath { 159 return ipath < jpath 160 } 161 iname := importName(x[i]) 162 jname := importName(x[j]) 163 if iname != jname { 164 return iname < jname 165 } 166 return importComment(x[i]) < importComment(x[j]) 167 }