...

Source file src/cuelang.org/go/cue/format/import.go

Documentation: cuelang.org/go/cue/format

     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  }
   168  

View as plain text