golang.org/x/tools/gopls@v0.15.3/internal/hooks/gofumpt_120.go (about)

     1  // Copyright 2022 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build go1.20
     6  // +build go1.20
     7  
     8  package hooks
     9  
    10  import (
    11  	"context"
    12  	"fmt"
    13  
    14  	"golang.org/x/tools/gopls/internal/settings"
    15  	"mvdan.cc/gofumpt/format"
    16  )
    17  
    18  func updateGofumpt(options *settings.Options) {
    19  	options.GofumptFormat = func(ctx context.Context, langVersion, modulePath string, src []byte) ([]byte, error) {
    20  		fixedVersion, err := fixLangVersion(langVersion)
    21  		if err != nil {
    22  			return nil, err
    23  		}
    24  		return format.Source(src, format.Options{
    25  			LangVersion: fixedVersion,
    26  			ModulePath:  modulePath,
    27  		})
    28  	}
    29  }
    30  
    31  // fixLangVersion function cleans the input so that gofumpt doesn't panic. It is
    32  // rather permissive, and accepts version strings that aren't technically valid
    33  // in a go.mod file.
    34  //
    35  // More specifically, it looks for an optional 'v' followed by 1-3
    36  // '.'-separated numbers. The resulting string is stripped of any suffix beyond
    37  // this expected version number pattern.
    38  //
    39  // See also golang/go#61692: gofumpt does not accept the new language versions
    40  // appearing in go.mod files (e.g. go1.21rc3).
    41  func fixLangVersion(input string) (string, error) {
    42  	bad := func() (string, error) {
    43  		return "", fmt.Errorf("invalid language version syntax %q", input)
    44  	}
    45  	if input == "" {
    46  		return input, nil
    47  	}
    48  	i := 0
    49  	if input[0] == 'v' { // be flexible about 'v'
    50  		i++
    51  	}
    52  	// takeDigits consumes ascii numerals 0-9 and reports if at least one was
    53  	// consumed.
    54  	takeDigits := func() bool {
    55  		found := false
    56  		for ; i < len(input) && '0' <= input[i] && input[i] <= '9'; i++ {
    57  			found = true
    58  		}
    59  		return found
    60  	}
    61  	if !takeDigits() { // versions must start with at least one number
    62  		return bad()
    63  	}
    64  
    65  	// Accept optional minor and patch versions.
    66  	for n := 0; n < 2; n++ {
    67  		if i < len(input) && input[i] == '.' {
    68  			// Look for minor/patch version.
    69  			i++
    70  			if !takeDigits() {
    71  				i--
    72  				break
    73  			}
    74  		}
    75  	}
    76  	// Accept any suffix.
    77  	return input[:i], nil
    78  }