...

Source file src/pkg/cmd/go/internal/modfile/rule.go

     1	// Copyright 2018 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	package modfile
     6	
     7	import (
     8		"bytes"
     9		"errors"
    10		"fmt"
    11		"internal/lazyregexp"
    12		"path/filepath"
    13		"sort"
    14		"strconv"
    15		"strings"
    16		"unicode"
    17	
    18		"cmd/go/internal/module"
    19	)
    20	
    21	// A File is the parsed, interpreted form of a go.mod file.
    22	type File struct {
    23		Module  *Module
    24		Go      *Go
    25		Require []*Require
    26		Exclude []*Exclude
    27		Replace []*Replace
    28	
    29		Syntax *FileSyntax
    30	}
    31	
    32	// A Module is the module statement.
    33	type Module struct {
    34		Mod    module.Version
    35		Syntax *Line
    36	}
    37	
    38	// A Go is the go statement.
    39	type Go struct {
    40		Version string // "1.23"
    41		Syntax  *Line
    42	}
    43	
    44	// A Require is a single require statement.
    45	type Require struct {
    46		Mod      module.Version
    47		Indirect bool // has "// indirect" comment
    48		Syntax   *Line
    49	}
    50	
    51	// An Exclude is a single exclude statement.
    52	type Exclude struct {
    53		Mod    module.Version
    54		Syntax *Line
    55	}
    56	
    57	// A Replace is a single replace statement.
    58	type Replace struct {
    59		Old    module.Version
    60		New    module.Version
    61		Syntax *Line
    62	}
    63	
    64	func (f *File) AddModuleStmt(path string) error {
    65		if f.Syntax == nil {
    66			f.Syntax = new(FileSyntax)
    67		}
    68		if f.Module == nil {
    69			f.Module = &Module{
    70				Mod:    module.Version{Path: path},
    71				Syntax: f.Syntax.addLine(nil, "module", AutoQuote(path)),
    72			}
    73		} else {
    74			f.Module.Mod.Path = path
    75			f.Syntax.updateLine(f.Module.Syntax, "module", AutoQuote(path))
    76		}
    77		return nil
    78	}
    79	
    80	func (f *File) AddComment(text string) {
    81		if f.Syntax == nil {
    82			f.Syntax = new(FileSyntax)
    83		}
    84		f.Syntax.Stmt = append(f.Syntax.Stmt, &CommentBlock{
    85			Comments: Comments{
    86				Before: []Comment{
    87					{
    88						Token: text,
    89					},
    90				},
    91			},
    92		})
    93	}
    94	
    95	type VersionFixer func(path, version string) (string, error)
    96	
    97	// Parse parses the data, reported in errors as being from file,
    98	// into a File struct. It applies fix, if non-nil, to canonicalize all module versions found.
    99	func Parse(file string, data []byte, fix VersionFixer) (*File, error) {
   100		return parseToFile(file, data, fix, true)
   101	}
   102	
   103	// ParseLax is like Parse but ignores unknown statements.
   104	// It is used when parsing go.mod files other than the main module,
   105	// under the theory that most statement types we add in the future will
   106	// only apply in the main module, like exclude and replace,
   107	// and so we get better gradual deployments if old go commands
   108	// simply ignore those statements when found in go.mod files
   109	// in dependencies.
   110	func ParseLax(file string, data []byte, fix VersionFixer) (*File, error) {
   111		return parseToFile(file, data, fix, false)
   112	}
   113	
   114	func parseToFile(file string, data []byte, fix VersionFixer, strict bool) (*File, error) {
   115		fs, err := parse(file, data)
   116		if err != nil {
   117			return nil, err
   118		}
   119		f := &File{
   120			Syntax: fs,
   121		}
   122	
   123		var errs bytes.Buffer
   124		for _, x := range fs.Stmt {
   125			switch x := x.(type) {
   126			case *Line:
   127				f.add(&errs, x, x.Token[0], x.Token[1:], fix, strict)
   128	
   129			case *LineBlock:
   130				if len(x.Token) > 1 {
   131					if strict {
   132						fmt.Fprintf(&errs, "%s:%d: unknown block type: %s\n", file, x.Start.Line, strings.Join(x.Token, " "))
   133					}
   134					continue
   135				}
   136				switch x.Token[0] {
   137				default:
   138					if strict {
   139						fmt.Fprintf(&errs, "%s:%d: unknown block type: %s\n", file, x.Start.Line, strings.Join(x.Token, " "))
   140					}
   141					continue
   142				case "module", "require", "exclude", "replace":
   143					for _, l := range x.Line {
   144						f.add(&errs, l, x.Token[0], l.Token, fix, strict)
   145					}
   146				}
   147			}
   148		}
   149	
   150		if errs.Len() > 0 {
   151			return nil, errors.New(strings.TrimRight(errs.String(), "\n"))
   152		}
   153		return f, nil
   154	}
   155	
   156	var GoVersionRE = lazyregexp.New(`([1-9][0-9]*)\.(0|[1-9][0-9]*)`)
   157	
   158	func (f *File) add(errs *bytes.Buffer, line *Line, verb string, args []string, fix VersionFixer, strict bool) {
   159		// If strict is false, this module is a dependency.
   160		// We ignore all unknown directives as well as main-module-only
   161		// directives like replace and exclude. It will work better for
   162		// forward compatibility if we can depend on modules that have unknown
   163		// statements (presumed relevant only when acting as the main module)
   164		// and simply ignore those statements.
   165		if !strict {
   166			switch verb {
   167			case "module", "require", "go":
   168				// want these even for dependency go.mods
   169			default:
   170				return
   171			}
   172		}
   173	
   174		switch verb {
   175		default:
   176			fmt.Fprintf(errs, "%s:%d: unknown directive: %s\n", f.Syntax.Name, line.Start.Line, verb)
   177	
   178		case "go":
   179			if f.Go != nil {
   180				fmt.Fprintf(errs, "%s:%d: repeated go statement\n", f.Syntax.Name, line.Start.Line)
   181				return
   182			}
   183			if len(args) != 1 || !GoVersionRE.MatchString(args[0]) {
   184				fmt.Fprintf(errs, "%s:%d: usage: go 1.23\n", f.Syntax.Name, line.Start.Line)
   185				return
   186			}
   187			f.Go = &Go{Syntax: line}
   188			f.Go.Version = args[0]
   189		case "module":
   190			if f.Module != nil {
   191				fmt.Fprintf(errs, "%s:%d: repeated module statement\n", f.Syntax.Name, line.Start.Line)
   192				return
   193			}
   194			f.Module = &Module{Syntax: line}
   195			if len(args) != 1 {
   196	
   197				fmt.Fprintf(errs, "%s:%d: usage: module module/path\n", f.Syntax.Name, line.Start.Line)
   198				return
   199			}
   200			s, err := parseString(&args[0])
   201			if err != nil {
   202				fmt.Fprintf(errs, "%s:%d: invalid quoted string: %v\n", f.Syntax.Name, line.Start.Line, err)
   203				return
   204			}
   205			f.Module.Mod = module.Version{Path: s}
   206		case "require", "exclude":
   207			if len(args) != 2 {
   208				fmt.Fprintf(errs, "%s:%d: usage: %s module/path v1.2.3\n", f.Syntax.Name, line.Start.Line, verb)
   209				return
   210			}
   211			s, err := parseString(&args[0])
   212			if err != nil {
   213				fmt.Fprintf(errs, "%s:%d: invalid quoted string: %v\n", f.Syntax.Name, line.Start.Line, err)
   214				return
   215			}
   216			v, err := parseVersion(verb, s, &args[1], fix)
   217			if err != nil {
   218				fmt.Fprintf(errs, "%s:%d: %v\n", f.Syntax.Name, line.Start.Line, err)
   219				return
   220			}
   221			pathMajor, err := modulePathMajor(s)
   222			if err != nil {
   223				fmt.Fprintf(errs, "%s:%d: %v\n", f.Syntax.Name, line.Start.Line, err)
   224				return
   225			}
   226			if err := module.MatchPathMajor(v, pathMajor); err != nil {
   227				fmt.Fprintf(errs, "%s:%d: %v\n", f.Syntax.Name, line.Start.Line, &Error{Verb: verb, ModPath: s, Err: err})
   228				return
   229			}
   230			if verb == "require" {
   231				f.Require = append(f.Require, &Require{
   232					Mod:      module.Version{Path: s, Version: v},
   233					Syntax:   line,
   234					Indirect: isIndirect(line),
   235				})
   236			} else {
   237				f.Exclude = append(f.Exclude, &Exclude{
   238					Mod:    module.Version{Path: s, Version: v},
   239					Syntax: line,
   240				})
   241			}
   242		case "replace":
   243			arrow := 2
   244			if len(args) >= 2 && args[1] == "=>" {
   245				arrow = 1
   246			}
   247			if len(args) < arrow+2 || len(args) > arrow+3 || args[arrow] != "=>" {
   248				fmt.Fprintf(errs, "%s:%d: usage: %s module/path [v1.2.3] => other/module v1.4\n\t or %s module/path [v1.2.3] => ../local/directory\n", f.Syntax.Name, line.Start.Line, verb, verb)
   249				return
   250			}
   251			s, err := parseString(&args[0])
   252			if err != nil {
   253				fmt.Fprintf(errs, "%s:%d: invalid quoted string: %v\n", f.Syntax.Name, line.Start.Line, err)
   254				return
   255			}
   256			pathMajor, err := modulePathMajor(s)
   257			if err != nil {
   258				fmt.Fprintf(errs, "%s:%d: %v\n", f.Syntax.Name, line.Start.Line, err)
   259				return
   260			}
   261			var v string
   262			if arrow == 2 {
   263				v, err = parseVersion(verb, s, &args[1], fix)
   264				if err != nil {
   265					fmt.Fprintf(errs, "%s:%d: %v\n", f.Syntax.Name, line.Start.Line, err)
   266					return
   267				}
   268				if err := module.MatchPathMajor(v, pathMajor); err != nil {
   269					fmt.Fprintf(errs, "%s:%d: %v\n", f.Syntax.Name, line.Start.Line, &Error{Verb: verb, ModPath: s, Err: err})
   270					return
   271				}
   272			}
   273			ns, err := parseString(&args[arrow+1])
   274			if err != nil {
   275				fmt.Fprintf(errs, "%s:%d: invalid quoted string: %v\n", f.Syntax.Name, line.Start.Line, err)
   276				return
   277			}
   278			nv := ""
   279			if len(args) == arrow+2 {
   280				if !IsDirectoryPath(ns) {
   281					fmt.Fprintf(errs, "%s:%d: replacement module without version must be directory path (rooted or starting with ./ or ../)\n", f.Syntax.Name, line.Start.Line)
   282					return
   283				}
   284				if filepath.Separator == '/' && strings.Contains(ns, `\`) {
   285					fmt.Fprintf(errs, "%s:%d: replacement directory appears to be Windows path (on a non-windows system)\n", f.Syntax.Name, line.Start.Line)
   286					return
   287				}
   288			}
   289			if len(args) == arrow+3 {
   290				nv, err = parseVersion(verb, ns, &args[arrow+2], fix)
   291				if err != nil {
   292					fmt.Fprintf(errs, "%s:%d: %v\n", f.Syntax.Name, line.Start.Line, err)
   293					return
   294				}
   295				if IsDirectoryPath(ns) {
   296					fmt.Fprintf(errs, "%s:%d: replacement module directory path %q cannot have version\n", f.Syntax.Name, line.Start.Line, ns)
   297					return
   298				}
   299			}
   300			f.Replace = append(f.Replace, &Replace{
   301				Old:    module.Version{Path: s, Version: v},
   302				New:    module.Version{Path: ns, Version: nv},
   303				Syntax: line,
   304			})
   305		}
   306	}
   307	
   308	// isIndirect reports whether line has a "// indirect" comment,
   309	// meaning it is in go.mod only for its effect on indirect dependencies,
   310	// so that it can be dropped entirely once the effective version of the
   311	// indirect dependency reaches the given minimum version.
   312	func isIndirect(line *Line) bool {
   313		if len(line.Suffix) == 0 {
   314			return false
   315		}
   316		f := strings.Fields(line.Suffix[0].Token)
   317		return (len(f) == 2 && f[1] == "indirect" || len(f) > 2 && f[1] == "indirect;") && f[0] == "//"
   318	}
   319	
   320	// setIndirect sets line to have (or not have) a "// indirect" comment.
   321	func setIndirect(line *Line, indirect bool) {
   322		if isIndirect(line) == indirect {
   323			return
   324		}
   325		if indirect {
   326			// Adding comment.
   327			if len(line.Suffix) == 0 {
   328				// New comment.
   329				line.Suffix = []Comment{{Token: "// indirect", Suffix: true}}
   330				return
   331			}
   332			// Insert at beginning of existing comment.
   333			com := &line.Suffix[0]
   334			space := " "
   335			if len(com.Token) > 2 && com.Token[2] == ' ' || com.Token[2] == '\t' {
   336				space = ""
   337			}
   338			com.Token = "// indirect;" + space + com.Token[2:]
   339			return
   340		}
   341	
   342		// Removing comment.
   343		f := strings.Fields(line.Suffix[0].Token)
   344		if len(f) == 2 {
   345			// Remove whole comment.
   346			line.Suffix = nil
   347			return
   348		}
   349	
   350		// Remove comment prefix.
   351		com := &line.Suffix[0]
   352		i := strings.Index(com.Token, "indirect;")
   353		com.Token = "//" + com.Token[i+len("indirect;"):]
   354	}
   355	
   356	// IsDirectoryPath reports whether the given path should be interpreted
   357	// as a directory path. Just like on the go command line, relative paths
   358	// and rooted paths are directory paths; the rest are module paths.
   359	func IsDirectoryPath(ns string) bool {
   360		// Because go.mod files can move from one system to another,
   361		// we check all known path syntaxes, both Unix and Windows.
   362		return strings.HasPrefix(ns, "./") || strings.HasPrefix(ns, "../") || strings.HasPrefix(ns, "/") ||
   363			strings.HasPrefix(ns, `.\`) || strings.HasPrefix(ns, `..\`) || strings.HasPrefix(ns, `\`) ||
   364			len(ns) >= 2 && ('A' <= ns[0] && ns[0] <= 'Z' || 'a' <= ns[0] && ns[0] <= 'z') && ns[1] == ':'
   365	}
   366	
   367	// MustQuote reports whether s must be quoted in order to appear as
   368	// a single token in a go.mod line.
   369	func MustQuote(s string) bool {
   370		for _, r := range s {
   371			if !unicode.IsPrint(r) || r == ' ' || r == '"' || r == '\'' || r == '`' {
   372				return true
   373			}
   374		}
   375		return s == "" || strings.Contains(s, "//") || strings.Contains(s, "/*")
   376	}
   377	
   378	// AutoQuote returns s or, if quoting is required for s to appear in a go.mod,
   379	// the quotation of s.
   380	func AutoQuote(s string) string {
   381		if MustQuote(s) {
   382			return strconv.Quote(s)
   383		}
   384		return s
   385	}
   386	
   387	func parseString(s *string) (string, error) {
   388		t := *s
   389		if strings.HasPrefix(t, `"`) {
   390			var err error
   391			if t, err = strconv.Unquote(t); err != nil {
   392				return "", err
   393			}
   394		} else if strings.ContainsAny(t, "\"'`") {
   395			// Other quotes are reserved both for possible future expansion
   396			// and to avoid confusion. For example if someone types 'x'
   397			// we want that to be a syntax error and not a literal x in literal quotation marks.
   398			return "", fmt.Errorf("unquoted string cannot contain quote")
   399		}
   400		*s = AutoQuote(t)
   401		return t, nil
   402	}
   403	
   404	type Error struct {
   405		Verb    string
   406		ModPath string
   407		Err     error
   408	}
   409	
   410	func (e *Error) Error() string {
   411		return fmt.Sprintf("%s %s: %v", e.Verb, e.ModPath, e.Err)
   412	}
   413	
   414	func (e *Error) Unwrap() error { return e.Err }
   415	
   416	func parseVersion(verb string, path string, s *string, fix VersionFixer) (string, error) {
   417		t, err := parseString(s)
   418		if err != nil {
   419			return "", &Error{
   420				Verb:    verb,
   421				ModPath: path,
   422				Err: &module.InvalidVersionError{
   423					Version: *s,
   424					Err:     err,
   425				},
   426			}
   427		}
   428		if fix != nil {
   429			var err error
   430			t, err = fix(path, t)
   431			if err != nil {
   432				if err, ok := err.(*module.ModuleError); ok {
   433					return "", &Error{
   434						Verb:    verb,
   435						ModPath: path,
   436						Err:     err.Err,
   437					}
   438				}
   439				return "", err
   440			}
   441		}
   442		if v := module.CanonicalVersion(t); v != "" {
   443			*s = v
   444			return *s, nil
   445		}
   446		return "", &Error{
   447			Verb:    verb,
   448			ModPath: path,
   449			Err: &module.InvalidVersionError{
   450				Version: t,
   451				Err:     errors.New("must be of the form v1.2.3"),
   452			},
   453		}
   454	}
   455	
   456	func modulePathMajor(path string) (string, error) {
   457		_, major, ok := module.SplitPathVersion(path)
   458		if !ok {
   459			return "", fmt.Errorf("invalid module path")
   460		}
   461		return major, nil
   462	}
   463	
   464	func (f *File) Format() ([]byte, error) {
   465		return Format(f.Syntax), nil
   466	}
   467	
   468	// Cleanup cleans up the file f after any edit operations.
   469	// To avoid quadratic behavior, modifications like DropRequire
   470	// clear the entry but do not remove it from the slice.
   471	// Cleanup cleans out all the cleared entries.
   472	func (f *File) Cleanup() {
   473		w := 0
   474		for _, r := range f.Require {
   475			if r.Mod.Path != "" {
   476				f.Require[w] = r
   477				w++
   478			}
   479		}
   480		f.Require = f.Require[:w]
   481	
   482		w = 0
   483		for _, x := range f.Exclude {
   484			if x.Mod.Path != "" {
   485				f.Exclude[w] = x
   486				w++
   487			}
   488		}
   489		f.Exclude = f.Exclude[:w]
   490	
   491		w = 0
   492		for _, r := range f.Replace {
   493			if r.Old.Path != "" {
   494				f.Replace[w] = r
   495				w++
   496			}
   497		}
   498		f.Replace = f.Replace[:w]
   499	
   500		f.Syntax.Cleanup()
   501	}
   502	
   503	func (f *File) AddGoStmt(version string) error {
   504		if !GoVersionRE.MatchString(version) {
   505			return fmt.Errorf("invalid language version string %q", version)
   506		}
   507		if f.Go == nil {
   508			f.Go = &Go{
   509				Version: version,
   510				Syntax:  f.Syntax.addLine(nil, "go", version),
   511			}
   512		} else {
   513			f.Go.Version = version
   514			f.Syntax.updateLine(f.Go.Syntax, "go", version)
   515		}
   516		return nil
   517	}
   518	
   519	func (f *File) AddRequire(path, vers string) error {
   520		need := true
   521		for _, r := range f.Require {
   522			if r.Mod.Path == path {
   523				if need {
   524					r.Mod.Version = vers
   525					f.Syntax.updateLine(r.Syntax, "require", AutoQuote(path), vers)
   526					need = false
   527				} else {
   528					f.Syntax.removeLine(r.Syntax)
   529					*r = Require{}
   530				}
   531			}
   532		}
   533	
   534		if need {
   535			f.AddNewRequire(path, vers, false)
   536		}
   537		return nil
   538	}
   539	
   540	func (f *File) AddNewRequire(path, vers string, indirect bool) {
   541		line := f.Syntax.addLine(nil, "require", AutoQuote(path), vers)
   542		setIndirect(line, indirect)
   543		f.Require = append(f.Require, &Require{module.Version{Path: path, Version: vers}, indirect, line})
   544	}
   545	
   546	func (f *File) SetRequire(req []*Require) {
   547		need := make(map[string]string)
   548		indirect := make(map[string]bool)
   549		for _, r := range req {
   550			need[r.Mod.Path] = r.Mod.Version
   551			indirect[r.Mod.Path] = r.Indirect
   552		}
   553	
   554		for _, r := range f.Require {
   555			if v, ok := need[r.Mod.Path]; ok {
   556				r.Mod.Version = v
   557				r.Indirect = indirect[r.Mod.Path]
   558			}
   559		}
   560	
   561		var newStmts []Expr
   562		for _, stmt := range f.Syntax.Stmt {
   563			switch stmt := stmt.(type) {
   564			case *LineBlock:
   565				if len(stmt.Token) > 0 && stmt.Token[0] == "require" {
   566					var newLines []*Line
   567					for _, line := range stmt.Line {
   568						if p, err := parseString(&line.Token[0]); err == nil && need[p] != "" {
   569							line.Token[1] = need[p]
   570							delete(need, p)
   571							setIndirect(line, indirect[p])
   572							newLines = append(newLines, line)
   573						}
   574					}
   575					if len(newLines) == 0 {
   576						continue // drop stmt
   577					}
   578					stmt.Line = newLines
   579				}
   580	
   581			case *Line:
   582				if len(stmt.Token) > 0 && stmt.Token[0] == "require" {
   583					if p, err := parseString(&stmt.Token[1]); err == nil && need[p] != "" {
   584						stmt.Token[2] = need[p]
   585						delete(need, p)
   586						setIndirect(stmt, indirect[p])
   587					} else {
   588						continue // drop stmt
   589					}
   590				}
   591			}
   592			newStmts = append(newStmts, stmt)
   593		}
   594		f.Syntax.Stmt = newStmts
   595	
   596		for path, vers := range need {
   597			f.AddNewRequire(path, vers, indirect[path])
   598		}
   599		f.SortBlocks()
   600	}
   601	
   602	func (f *File) DropRequire(path string) error {
   603		for _, r := range f.Require {
   604			if r.Mod.Path == path {
   605				f.Syntax.removeLine(r.Syntax)
   606				*r = Require{}
   607			}
   608		}
   609		return nil
   610	}
   611	
   612	func (f *File) AddExclude(path, vers string) error {
   613		var hint *Line
   614		for _, x := range f.Exclude {
   615			if x.Mod.Path == path && x.Mod.Version == vers {
   616				return nil
   617			}
   618			if x.Mod.Path == path {
   619				hint = x.Syntax
   620			}
   621		}
   622	
   623		f.Exclude = append(f.Exclude, &Exclude{Mod: module.Version{Path: path, Version: vers}, Syntax: f.Syntax.addLine(hint, "exclude", AutoQuote(path), vers)})
   624		return nil
   625	}
   626	
   627	func (f *File) DropExclude(path, vers string) error {
   628		for _, x := range f.Exclude {
   629			if x.Mod.Path == path && x.Mod.Version == vers {
   630				f.Syntax.removeLine(x.Syntax)
   631				*x = Exclude{}
   632			}
   633		}
   634		return nil
   635	}
   636	
   637	func (f *File) AddReplace(oldPath, oldVers, newPath, newVers string) error {
   638		need := true
   639		old := module.Version{Path: oldPath, Version: oldVers}
   640		new := module.Version{Path: newPath, Version: newVers}
   641		tokens := []string{"replace", AutoQuote(oldPath)}
   642		if oldVers != "" {
   643			tokens = append(tokens, oldVers)
   644		}
   645		tokens = append(tokens, "=>", AutoQuote(newPath))
   646		if newVers != "" {
   647			tokens = append(tokens, newVers)
   648		}
   649	
   650		var hint *Line
   651		for _, r := range f.Replace {
   652			if r.Old.Path == oldPath && (oldVers == "" || r.Old.Version == oldVers) {
   653				if need {
   654					// Found replacement for old; update to use new.
   655					r.New = new
   656					f.Syntax.updateLine(r.Syntax, tokens...)
   657					need = false
   658					continue
   659				}
   660				// Already added; delete other replacements for same.
   661				f.Syntax.removeLine(r.Syntax)
   662				*r = Replace{}
   663			}
   664			if r.Old.Path == oldPath {
   665				hint = r.Syntax
   666			}
   667		}
   668		if need {
   669			f.Replace = append(f.Replace, &Replace{Old: old, New: new, Syntax: f.Syntax.addLine(hint, tokens...)})
   670		}
   671		return nil
   672	}
   673	
   674	func (f *File) DropReplace(oldPath, oldVers string) error {
   675		for _, r := range f.Replace {
   676			if r.Old.Path == oldPath && r.Old.Version == oldVers {
   677				f.Syntax.removeLine(r.Syntax)
   678				*r = Replace{}
   679			}
   680		}
   681		return nil
   682	}
   683	
   684	func (f *File) SortBlocks() {
   685		f.removeDups() // otherwise sorting is unsafe
   686	
   687		for _, stmt := range f.Syntax.Stmt {
   688			block, ok := stmt.(*LineBlock)
   689			if !ok {
   690				continue
   691			}
   692			sort.Slice(block.Line, func(i, j int) bool {
   693				li := block.Line[i]
   694				lj := block.Line[j]
   695				for k := 0; k < len(li.Token) && k < len(lj.Token); k++ {
   696					if li.Token[k] != lj.Token[k] {
   697						return li.Token[k] < lj.Token[k]
   698					}
   699				}
   700				return len(li.Token) < len(lj.Token)
   701			})
   702		}
   703	}
   704	
   705	func (f *File) removeDups() {
   706		have := make(map[module.Version]bool)
   707		kill := make(map[*Line]bool)
   708		for _, x := range f.Exclude {
   709			if have[x.Mod] {
   710				kill[x.Syntax] = true
   711				continue
   712			}
   713			have[x.Mod] = true
   714		}
   715		var excl []*Exclude
   716		for _, x := range f.Exclude {
   717			if !kill[x.Syntax] {
   718				excl = append(excl, x)
   719			}
   720		}
   721		f.Exclude = excl
   722	
   723		have = make(map[module.Version]bool)
   724		// Later replacements take priority over earlier ones.
   725		for i := len(f.Replace) - 1; i >= 0; i-- {
   726			x := f.Replace[i]
   727			if have[x.Old] {
   728				kill[x.Syntax] = true
   729				continue
   730			}
   731			have[x.Old] = true
   732		}
   733		var repl []*Replace
   734		for _, x := range f.Replace {
   735			if !kill[x.Syntax] {
   736				repl = append(repl, x)
   737			}
   738		}
   739		f.Replace = repl
   740	
   741		var stmts []Expr
   742		for _, stmt := range f.Syntax.Stmt {
   743			switch stmt := stmt.(type) {
   744			case *Line:
   745				if kill[stmt] {
   746					continue
   747				}
   748			case *LineBlock:
   749				var lines []*Line
   750				for _, line := range stmt.Line {
   751					if !kill[line] {
   752						lines = append(lines, line)
   753					}
   754				}
   755				stmt.Line = lines
   756				if len(lines) == 0 {
   757					continue
   758				}
   759			}
   760			stmts = append(stmts, stmt)
   761		}
   762		f.Syntax.Stmt = stmts
   763	}
   764	

View as plain text