...

Source file src/cmd/vendor/golang.org/x/mod/modfile/read.go

Documentation: cmd/vendor/golang.org/x/mod/modfile

     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  	"os"
    12  	"slices"
    13  	"strconv"
    14  	"strings"
    15  	"unicode"
    16  	"unicode/utf8"
    17  )
    18  
    19  // A Position describes an arbitrary source position in a file, including the
    20  // file, line, column, and byte offset.
    21  type Position struct {
    22  	Line     int // line in input (starting at 1)
    23  	LineRune int // rune in line (starting at 1)
    24  	Byte     int // byte in input (starting at 0)
    25  }
    26  
    27  // add returns the position at the end of s, assuming it starts at p.
    28  func (p Position) add(s string) Position {
    29  	p.Byte += len(s)
    30  	if n := strings.Count(s, "\n"); n > 0 {
    31  		p.Line += n
    32  		s = s[strings.LastIndex(s, "\n")+1:]
    33  		p.LineRune = 1
    34  	}
    35  	p.LineRune += utf8.RuneCountInString(s)
    36  	return p
    37  }
    38  
    39  // An Expr represents an input element.
    40  type Expr interface {
    41  	// Span returns the start and end position of the expression,
    42  	// excluding leading or trailing comments.
    43  	Span() (start, end Position)
    44  
    45  	// Comment returns the comments attached to the expression.
    46  	// This method would normally be named 'Comments' but that
    47  	// would interfere with embedding a type of the same name.
    48  	Comment() *Comments
    49  }
    50  
    51  // A Comment represents a single // comment.
    52  type Comment struct {
    53  	Start  Position
    54  	Token  string // without trailing newline
    55  	Suffix bool   // an end of line (not whole line) comment
    56  }
    57  
    58  // Comments collects the comments associated with an expression.
    59  type Comments struct {
    60  	Before []Comment // whole-line comments before this expression
    61  	Suffix []Comment // end-of-line comments after this expression
    62  
    63  	// For top-level expressions only, After lists whole-line
    64  	// comments following the expression.
    65  	After []Comment
    66  }
    67  
    68  // Comment returns the receiver. This isn't useful by itself, but
    69  // a [Comments] struct is embedded into all the expression
    70  // implementation types, and this gives each of those a Comment
    71  // method to satisfy the Expr interface.
    72  func (c *Comments) Comment() *Comments {
    73  	return c
    74  }
    75  
    76  // A FileSyntax represents an entire go.mod file.
    77  type FileSyntax struct {
    78  	Name string // file path
    79  	Comments
    80  	Stmt []Expr
    81  }
    82  
    83  func (x *FileSyntax) Span() (start, end Position) {
    84  	if len(x.Stmt) == 0 {
    85  		return
    86  	}
    87  	start, _ = x.Stmt[0].Span()
    88  	_, end = x.Stmt[len(x.Stmt)-1].Span()
    89  	return start, end
    90  }
    91  
    92  // addLine adds a line containing the given tokens to the file.
    93  //
    94  // If the first token of the hint matches the first token of the
    95  // line, the new line is added at the end of the block containing hint,
    96  // extracting hint into a new block if it is not yet in one.
    97  //
    98  // If the hint is non-nil but its first token does not match,
    99  // the new line is added after the block containing hint
   100  // (or hint itself, if not in a block).
   101  //
   102  // If no hint is provided, addLine appends the line to the end of
   103  // the last block with a matching first token,
   104  // or to the end of the file if no such block exists.
   105  func (x *FileSyntax) addLine(hint Expr, tokens ...string) *Line {
   106  	if hint == nil {
   107  		// If no hint given, add to the last statement of the given type.
   108  	Loop:
   109  		for _, stmt := range slices.Backward(x.Stmt) {
   110  			switch stmt := stmt.(type) {
   111  			case *Line:
   112  				if stmt.Token != nil && stmt.Token[0] == tokens[0] {
   113  					hint = stmt
   114  					break Loop
   115  				}
   116  			case *LineBlock:
   117  				if stmt.Token[0] == tokens[0] {
   118  					hint = stmt
   119  					break Loop
   120  				}
   121  			}
   122  		}
   123  	}
   124  
   125  	newLineAfter := func(i int) *Line {
   126  		new := &Line{Token: tokens}
   127  		if i == len(x.Stmt) {
   128  			x.Stmt = append(x.Stmt, new)
   129  		} else {
   130  			x.Stmt = append(x.Stmt, nil)
   131  			copy(x.Stmt[i+2:], x.Stmt[i+1:])
   132  			x.Stmt[i+1] = new
   133  		}
   134  		return new
   135  	}
   136  
   137  	if hint != nil {
   138  		for i, stmt := range x.Stmt {
   139  			switch stmt := stmt.(type) {
   140  			case *Line:
   141  				if stmt == hint {
   142  					if stmt.Token == nil || stmt.Token[0] != tokens[0] {
   143  						return newLineAfter(i)
   144  					}
   145  
   146  					// Convert line to line block.
   147  					stmt.InBlock = true
   148  					block := &LineBlock{Token: stmt.Token[:1], Line: []*Line{stmt}}
   149  					stmt.Token = stmt.Token[1:]
   150  					x.Stmt[i] = block
   151  					new := &Line{Token: tokens[1:], InBlock: true}
   152  					block.Line = append(block.Line, new)
   153  					return new
   154  				}
   155  
   156  			case *LineBlock:
   157  				if stmt == hint {
   158  					if stmt.Token[0] != tokens[0] {
   159  						return newLineAfter(i)
   160  					}
   161  
   162  					new := &Line{Token: tokens[1:], InBlock: true}
   163  					stmt.Line = append(stmt.Line, new)
   164  					return new
   165  				}
   166  
   167  				for j, line := range stmt.Line {
   168  					if line == hint {
   169  						if stmt.Token[0] != tokens[0] {
   170  							return newLineAfter(i)
   171  						}
   172  
   173  						// Add new line after hint within the block.
   174  						stmt.Line = append(stmt.Line, nil)
   175  						copy(stmt.Line[j+2:], stmt.Line[j+1:])
   176  						new := &Line{Token: tokens[1:], InBlock: true}
   177  						stmt.Line[j+1] = new
   178  						return new
   179  					}
   180  				}
   181  			}
   182  		}
   183  	}
   184  
   185  	new := &Line{Token: tokens}
   186  	x.Stmt = append(x.Stmt, new)
   187  	return new
   188  }
   189  
   190  func (x *FileSyntax) updateLine(line *Line, tokens ...string) {
   191  	if line.InBlock {
   192  		tokens = tokens[1:]
   193  	}
   194  	line.Token = tokens
   195  }
   196  
   197  // markRemoved modifies line so that it (and its end-of-line comment, if any)
   198  // will be dropped by (*FileSyntax).Cleanup.
   199  func (line *Line) markRemoved() {
   200  	line.Token = nil
   201  	line.Comments.Suffix = nil
   202  }
   203  
   204  // Cleanup cleans up the file syntax x after any edit operations.
   205  // To avoid quadratic behavior, (*Line).markRemoved marks the line as dead
   206  // by setting line.Token = nil but does not remove it from the slice
   207  // in which it appears. After edits have all been indicated,
   208  // calling Cleanup cleans out the dead lines.
   209  func (x *FileSyntax) Cleanup() {
   210  	w := 0
   211  	for _, stmt := range x.Stmt {
   212  		switch stmt := stmt.(type) {
   213  		case *Line:
   214  			if stmt.Token == nil {
   215  				continue
   216  			}
   217  		case *LineBlock:
   218  			ww := 0
   219  			for _, line := range stmt.Line {
   220  				if line.Token != nil {
   221  					stmt.Line[ww] = line
   222  					ww++
   223  				}
   224  			}
   225  			if ww == 0 {
   226  				continue
   227  			}
   228  			if ww == 1 && len(stmt.RParen.Comments.Before) == 0 {
   229  				// Collapse block into single line but keep the Line reference used by the
   230  				// parsed File structure.
   231  				*stmt.Line[0] = Line{
   232  					Comments: Comments{
   233  						Before: commentsAdd(stmt.Before, stmt.Line[0].Before),
   234  						Suffix: commentsAdd(stmt.Line[0].Suffix, stmt.Suffix),
   235  						After:  commentsAdd(stmt.Line[0].After, stmt.After),
   236  					},
   237  					Token: stringsAdd(stmt.Token, stmt.Line[0].Token),
   238  				}
   239  				x.Stmt[w] = stmt.Line[0]
   240  				w++
   241  				continue
   242  			}
   243  			stmt.Line = stmt.Line[:ww]
   244  		}
   245  		x.Stmt[w] = stmt
   246  		w++
   247  	}
   248  	x.Stmt = x.Stmt[:w]
   249  }
   250  
   251  func commentsAdd(x, y []Comment) []Comment {
   252  	return append(x[:len(x):len(x)], y...)
   253  }
   254  
   255  func stringsAdd(x, y []string) []string {
   256  	return append(x[:len(x):len(x)], y...)
   257  }
   258  
   259  // A CommentBlock represents a top-level block of comments separate
   260  // from any rule.
   261  type CommentBlock struct {
   262  	Comments
   263  	Start Position
   264  }
   265  
   266  func (x *CommentBlock) Span() (start, end Position) {
   267  	return x.Start, x.Start
   268  }
   269  
   270  // A Line is a single line of tokens.
   271  type Line struct {
   272  	Comments
   273  	Start   Position
   274  	Token   []string
   275  	InBlock bool
   276  	End     Position
   277  }
   278  
   279  func (x *Line) Span() (start, end Position) {
   280  	return x.Start, x.End
   281  }
   282  
   283  // A LineBlock is a factored block of lines, like
   284  //
   285  //	require (
   286  //		"x"
   287  //		"y"
   288  //	)
   289  type LineBlock struct {
   290  	Comments
   291  	Start  Position
   292  	LParen LParen
   293  	Token  []string
   294  	Line   []*Line
   295  	RParen RParen
   296  }
   297  
   298  func (x *LineBlock) Span() (start, end Position) {
   299  	return x.Start, x.RParen.Pos.add(")")
   300  }
   301  
   302  // An LParen represents the beginning of a parenthesized line block.
   303  // It is a place to store suffix comments.
   304  type LParen struct {
   305  	Comments
   306  	Pos Position
   307  }
   308  
   309  func (x *LParen) Span() (start, end Position) {
   310  	return x.Pos, x.Pos.add(")")
   311  }
   312  
   313  // An RParen represents the end of a parenthesized line block.
   314  // It is a place to store whole-line (before) comments.
   315  type RParen struct {
   316  	Comments
   317  	Pos Position
   318  }
   319  
   320  func (x *RParen) Span() (start, end Position) {
   321  	return x.Pos, x.Pos.add(")")
   322  }
   323  
   324  // An input represents a single input file being parsed.
   325  type input struct {
   326  	// Lexing state.
   327  	filename   string    // name of input file, for errors
   328  	complete   []byte    // entire input
   329  	remaining  []byte    // remaining input
   330  	tokenStart []byte    // token being scanned to end of input
   331  	token      token     // next token to be returned by lex, peek
   332  	pos        Position  // current input position
   333  	comments   []Comment // accumulated comments
   334  
   335  	// Parser state.
   336  	file        *FileSyntax // returned top-level syntax tree
   337  	parseErrors ErrorList   // errors encountered during parsing
   338  
   339  	// Comment assignment state.
   340  	pre  []Expr // all expressions, in preorder traversal
   341  	post []Expr // all expressions, in postorder traversal
   342  }
   343  
   344  func newInput(filename string, data []byte) *input {
   345  	return &input{
   346  		filename:  filename,
   347  		complete:  data,
   348  		remaining: data,
   349  		pos:       Position{Line: 1, LineRune: 1, Byte: 0},
   350  	}
   351  }
   352  
   353  // parse parses the input file.
   354  func parse(file string, data []byte) (f *FileSyntax, err error) {
   355  	// The parser panics for both routine errors like syntax errors
   356  	// and for programmer bugs like array index errors.
   357  	// Turn both into error returns. Catching bug panics is
   358  	// especially important when processing many files.
   359  	in := newInput(file, data)
   360  	defer func() {
   361  		if e := recover(); e != nil && e != &in.parseErrors {
   362  			in.parseErrors = append(in.parseErrors, Error{
   363  				Filename: in.filename,
   364  				Pos:      in.pos,
   365  				Err:      fmt.Errorf("internal error: %v", e),
   366  			})
   367  		}
   368  		if err == nil && len(in.parseErrors) > 0 {
   369  			err = in.parseErrors
   370  		}
   371  	}()
   372  
   373  	// Prime the lexer by reading in the first token. It will be available
   374  	// in the next peek() or lex() call.
   375  	in.readToken()
   376  
   377  	// Invoke the parser.
   378  	in.parseFile()
   379  	if len(in.parseErrors) > 0 {
   380  		return nil, in.parseErrors
   381  	}
   382  	in.file.Name = in.filename
   383  
   384  	// Assign comments to nearby syntax.
   385  	in.assignComments()
   386  
   387  	return in.file, nil
   388  }
   389  
   390  // Error is called to report an error.
   391  // Error does not return: it panics.
   392  func (in *input) Error(s string) {
   393  	in.parseErrors = append(in.parseErrors, Error{
   394  		Filename: in.filename,
   395  		Pos:      in.pos,
   396  		Err:      errors.New(s),
   397  	})
   398  	panic(&in.parseErrors)
   399  }
   400  
   401  // eof reports whether the input has reached end of file.
   402  func (in *input) eof() bool {
   403  	return len(in.remaining) == 0
   404  }
   405  
   406  // peekRune returns the next rune in the input without consuming it.
   407  func (in *input) peekRune() int {
   408  	if len(in.remaining) == 0 {
   409  		return 0
   410  	}
   411  	r, _ := utf8.DecodeRune(in.remaining)
   412  	return int(r)
   413  }
   414  
   415  // peekPrefix reports whether the remaining input begins with the given prefix.
   416  func (in *input) peekPrefix(prefix string) bool {
   417  	// This is like bytes.HasPrefix(in.remaining, []byte(prefix))
   418  	// but without the allocation of the []byte copy of prefix.
   419  	for i := 0; i < len(prefix); i++ {
   420  		if i >= len(in.remaining) || in.remaining[i] != prefix[i] {
   421  			return false
   422  		}
   423  	}
   424  	return true
   425  }
   426  
   427  // readRune consumes and returns the next rune in the input.
   428  func (in *input) readRune() int {
   429  	if len(in.remaining) == 0 {
   430  		in.Error("internal lexer error: readRune at EOF")
   431  	}
   432  	r, size := utf8.DecodeRune(in.remaining)
   433  	in.remaining = in.remaining[size:]
   434  	if r == '\n' {
   435  		in.pos.Line++
   436  		in.pos.LineRune = 1
   437  	} else {
   438  		in.pos.LineRune++
   439  	}
   440  	in.pos.Byte += size
   441  	return int(r)
   442  }
   443  
   444  type token struct {
   445  	kind   tokenKind
   446  	pos    Position
   447  	endPos Position
   448  	text   string
   449  }
   450  
   451  type tokenKind int
   452  
   453  const (
   454  	_EOF tokenKind = -(iota + 1)
   455  	_EOLCOMMENT
   456  	_IDENT
   457  	_STRING
   458  	_COMMENT
   459  
   460  	// newlines and punctuation tokens are allowed as ASCII codes.
   461  )
   462  
   463  func (k tokenKind) isComment() bool {
   464  	return k == _COMMENT || k == _EOLCOMMENT
   465  }
   466  
   467  // isEOL returns whether a token terminates a line.
   468  func (k tokenKind) isEOL() bool {
   469  	return k == _EOF || k == _EOLCOMMENT || k == '\n'
   470  }
   471  
   472  // startToken marks the beginning of the next input token.
   473  // It must be followed by a call to endToken, once the token's text has
   474  // been consumed using readRune.
   475  func (in *input) startToken() {
   476  	in.tokenStart = in.remaining
   477  	in.token.text = ""
   478  	in.token.pos = in.pos
   479  }
   480  
   481  // endToken marks the end of an input token.
   482  // It records the actual token string in tok.text.
   483  // A single trailing newline (LF or CRLF) will be removed from comment tokens.
   484  func (in *input) endToken(kind tokenKind) {
   485  	in.token.kind = kind
   486  	text := string(in.tokenStart[:len(in.tokenStart)-len(in.remaining)])
   487  	if kind.isComment() {
   488  		if strings.HasSuffix(text, "\r\n") {
   489  			text = text[:len(text)-2]
   490  		} else {
   491  			text = strings.TrimSuffix(text, "\n")
   492  		}
   493  	}
   494  	in.token.text = text
   495  	in.token.endPos = in.pos
   496  }
   497  
   498  // peek returns the kind of the next token returned by lex.
   499  func (in *input) peek() tokenKind {
   500  	return in.token.kind
   501  }
   502  
   503  // lex is called from the parser to obtain the next input token.
   504  func (in *input) lex() token {
   505  	tok := in.token
   506  	in.readToken()
   507  	return tok
   508  }
   509  
   510  // readToken lexes the next token from the text and stores it in in.token.
   511  func (in *input) readToken() {
   512  	// Skip past spaces, stopping at non-space or EOF.
   513  	for !in.eof() {
   514  		c := in.peekRune()
   515  		if c == ' ' || c == '\t' || c == '\r' {
   516  			in.readRune()
   517  			continue
   518  		}
   519  
   520  		// Comment runs to end of line.
   521  		if in.peekPrefix("//") {
   522  			in.startToken()
   523  
   524  			// Is this comment the only thing on its line?
   525  			// Find the last \n before this // and see if it's all
   526  			// spaces from there to here.
   527  			i := bytes.LastIndex(in.complete[:in.pos.Byte], []byte("\n"))
   528  			suffix := len(bytes.TrimSpace(in.complete[i+1:in.pos.Byte])) > 0
   529  			in.readRune()
   530  			in.readRune()
   531  
   532  			// Consume comment.
   533  			for len(in.remaining) > 0 && in.readRune() != '\n' {
   534  			}
   535  
   536  			// If we are at top level (not in a statement), hand the comment to
   537  			// the parser as a _COMMENT token. The grammar is written
   538  			// to handle top-level comments itself.
   539  			if !suffix {
   540  				in.endToken(_COMMENT)
   541  				return
   542  			}
   543  
   544  			// Otherwise, save comment for later attachment to syntax tree.
   545  			in.endToken(_EOLCOMMENT)
   546  			in.comments = append(in.comments, Comment{in.token.pos, in.token.text, suffix})
   547  			return
   548  		}
   549  
   550  		if in.peekPrefix("/*") {
   551  			in.Error("mod files must use // comments (not /* */ comments)")
   552  		}
   553  
   554  		// Found non-space non-comment.
   555  		break
   556  	}
   557  
   558  	// Found the beginning of the next token.
   559  	in.startToken()
   560  
   561  	// End of file.
   562  	if in.eof() {
   563  		in.endToken(_EOF)
   564  		return
   565  	}
   566  
   567  	// Punctuation tokens.
   568  	switch c := in.peekRune(); c {
   569  	case '\n', '(', ')', '[', ']', '{', '}', ',':
   570  		in.readRune()
   571  		in.endToken(tokenKind(c))
   572  		return
   573  
   574  	case '"', '`': // quoted string
   575  		quote := c
   576  		in.readRune()
   577  		for {
   578  			if in.eof() {
   579  				in.pos = in.token.pos
   580  				in.Error("unexpected EOF in string")
   581  			}
   582  			if in.peekRune() == '\n' {
   583  				in.Error("unexpected newline in string")
   584  			}
   585  			c := in.readRune()
   586  			if c == quote {
   587  				break
   588  			}
   589  			if c == '\\' && quote != '`' {
   590  				if in.eof() {
   591  					in.pos = in.token.pos
   592  					in.Error("unexpected EOF in string")
   593  				}
   594  				in.readRune()
   595  			}
   596  		}
   597  		in.endToken(_STRING)
   598  		return
   599  	}
   600  
   601  	// Checked all punctuation. Must be identifier token.
   602  	if c := in.peekRune(); !isIdent(c) {
   603  		in.Error(fmt.Sprintf("unexpected input character %#q", rune(c)))
   604  	}
   605  
   606  	// Scan over identifier.
   607  	for isIdent(in.peekRune()) {
   608  		if in.peekPrefix("//") {
   609  			break
   610  		}
   611  		if in.peekPrefix("/*") {
   612  			in.Error("mod files must use // comments (not /* */ comments)")
   613  		}
   614  		in.readRune()
   615  	}
   616  	in.endToken(_IDENT)
   617  }
   618  
   619  // isIdent reports whether c is an identifier rune.
   620  // We treat most printable runes as identifier runes, except for a handful of
   621  // ASCII punctuation characters.
   622  func isIdent(c int) bool {
   623  	switch r := rune(c); r {
   624  	case ' ', '(', ')', '[', ']', '{', '}', ',':
   625  		return false
   626  	default:
   627  		return !unicode.IsSpace(r) && unicode.IsPrint(r)
   628  	}
   629  }
   630  
   631  // Comment assignment.
   632  // We build two lists of all subexpressions, preorder and postorder.
   633  // The preorder list is ordered by start location, with outer expressions first.
   634  // The postorder list is ordered by end location, with outer expressions last.
   635  // We use the preorder list to assign each whole-line comment to the syntax
   636  // immediately following it, and we use the postorder list to assign each
   637  // end-of-line comment to the syntax immediately preceding it.
   638  
   639  // order walks the expression adding it and its subexpressions to the
   640  // preorder and postorder lists.
   641  func (in *input) order(x Expr) {
   642  	if x != nil {
   643  		in.pre = append(in.pre, x)
   644  	}
   645  	switch x := x.(type) {
   646  	default:
   647  		panic(fmt.Errorf("order: unexpected type %T", x))
   648  	case nil:
   649  		// nothing
   650  	case *LParen, *RParen:
   651  		// nothing
   652  	case *CommentBlock:
   653  		// nothing
   654  	case *Line:
   655  		// nothing
   656  	case *FileSyntax:
   657  		for _, stmt := range x.Stmt {
   658  			in.order(stmt)
   659  		}
   660  	case *LineBlock:
   661  		in.order(&x.LParen)
   662  		for _, l := range x.Line {
   663  			in.order(l)
   664  		}
   665  		in.order(&x.RParen)
   666  	}
   667  	if x != nil {
   668  		in.post = append(in.post, x)
   669  	}
   670  }
   671  
   672  // assignComments attaches comments to nearby syntax.
   673  func (in *input) assignComments() {
   674  	const debug = false
   675  
   676  	// Generate preorder and postorder lists.
   677  	in.order(in.file)
   678  
   679  	// Split into whole-line comments and suffix comments.
   680  	var line, suffix []Comment
   681  	for _, com := range in.comments {
   682  		if com.Suffix {
   683  			suffix = append(suffix, com)
   684  		} else {
   685  			line = append(line, com)
   686  		}
   687  	}
   688  
   689  	if debug {
   690  		for _, c := range line {
   691  			fmt.Fprintf(os.Stderr, "LINE %q :%d:%d #%d\n", c.Token, c.Start.Line, c.Start.LineRune, c.Start.Byte)
   692  		}
   693  	}
   694  
   695  	// Assign line comments to syntax immediately following.
   696  	for _, x := range in.pre {
   697  		start, _ := x.Span()
   698  		if debug {
   699  			fmt.Fprintf(os.Stderr, "pre %T :%d:%d #%d\n", x, start.Line, start.LineRune, start.Byte)
   700  		}
   701  		xcom := x.Comment()
   702  		for len(line) > 0 && start.Byte >= line[0].Start.Byte {
   703  			if debug {
   704  				fmt.Fprintf(os.Stderr, "ASSIGN LINE %q #%d\n", line[0].Token, line[0].Start.Byte)
   705  			}
   706  			xcom.Before = append(xcom.Before, line[0])
   707  			line = line[1:]
   708  		}
   709  	}
   710  
   711  	// Remaining line comments go at end of file.
   712  	in.file.After = append(in.file.After, line...)
   713  
   714  	if debug {
   715  		for _, c := range suffix {
   716  			fmt.Fprintf(os.Stderr, "SUFFIX %q :%d:%d #%d\n", c.Token, c.Start.Line, c.Start.LineRune, c.Start.Byte)
   717  		}
   718  	}
   719  
   720  	// Assign suffix comments to syntax immediately before.
   721  	for _, x := range slices.Backward(in.post) {
   722  		start, end := x.Span()
   723  		if debug {
   724  			fmt.Fprintf(os.Stderr, "post %T :%d:%d #%d :%d:%d #%d\n", x, start.Line, start.LineRune, start.Byte, end.Line, end.LineRune, end.Byte)
   725  		}
   726  
   727  		// Do not assign suffix comments to end of line block or whole file.
   728  		// Instead assign them to the last element inside.
   729  		switch x.(type) {
   730  		case *FileSyntax:
   731  			continue
   732  		}
   733  
   734  		// Do not assign suffix comments to something that starts
   735  		// on an earlier line, so that in
   736  		//
   737  		//	x ( y
   738  		//		z ) // comment
   739  		//
   740  		// we assign the comment to z and not to x ( ... ).
   741  		if start.Line != end.Line {
   742  			continue
   743  		}
   744  		xcom := x.Comment()
   745  		for len(suffix) > 0 && end.Byte <= suffix[len(suffix)-1].Start.Byte {
   746  			if debug {
   747  				fmt.Fprintf(os.Stderr, "ASSIGN SUFFIX %q #%d\n", suffix[len(suffix)-1].Token, suffix[len(suffix)-1].Start.Byte)
   748  			}
   749  			xcom.Suffix = append(xcom.Suffix, suffix[len(suffix)-1])
   750  			suffix = suffix[:len(suffix)-1]
   751  		}
   752  	}
   753  
   754  	// We assigned suffix comments in reverse.
   755  	// If multiple suffix comments were appended to the same
   756  	// expression node, they are now in reverse. Fix that.
   757  	for _, x := range in.post {
   758  		reverseComments(x.Comment().Suffix)
   759  	}
   760  
   761  	// Remaining suffix comments go at beginning of file.
   762  	in.file.Before = append(in.file.Before, suffix...)
   763  }
   764  
   765  // reverseComments reverses the []Comment list.
   766  func reverseComments(list []Comment) {
   767  	for i, j := 0, len(list)-1; i < j; i, j = i+1, j-1 {
   768  		list[i], list[j] = list[j], list[i]
   769  	}
   770  }
   771  
   772  func (in *input) parseFile() {
   773  	in.file = new(FileSyntax)
   774  	var cb *CommentBlock
   775  	for {
   776  		switch in.peek() {
   777  		case '\n':
   778  			in.lex()
   779  			if cb != nil {
   780  				in.file.Stmt = append(in.file.Stmt, cb)
   781  				cb = nil
   782  			}
   783  		case _COMMENT:
   784  			tok := in.lex()
   785  			if cb == nil {
   786  				cb = &CommentBlock{Start: tok.pos}
   787  			}
   788  			com := cb.Comment()
   789  			com.Before = append(com.Before, Comment{Start: tok.pos, Token: tok.text})
   790  		case _EOF:
   791  			if cb != nil {
   792  				in.file.Stmt = append(in.file.Stmt, cb)
   793  			}
   794  			return
   795  		default:
   796  			in.parseStmt()
   797  			if cb != nil {
   798  				in.file.Stmt[len(in.file.Stmt)-1].Comment().Before = cb.Before
   799  				cb = nil
   800  			}
   801  		}
   802  	}
   803  }
   804  
   805  func (in *input) parseStmt() {
   806  	tok := in.lex()
   807  	start := tok.pos
   808  	end := tok.endPos
   809  	tokens := []string{tok.text}
   810  	for {
   811  		tok := in.lex()
   812  		switch {
   813  		case tok.kind.isEOL():
   814  			in.file.Stmt = append(in.file.Stmt, &Line{
   815  				Start: start,
   816  				Token: tokens,
   817  				End:   end,
   818  			})
   819  			return
   820  
   821  		case tok.kind == '(':
   822  			if next := in.peek(); next.isEOL() {
   823  				// Start of block: no more tokens on this line.
   824  				in.file.Stmt = append(in.file.Stmt, in.parseLineBlock(start, tokens, tok))
   825  				return
   826  			} else if next == ')' {
   827  				rparen := in.lex()
   828  				if in.peek().isEOL() {
   829  					// Empty block.
   830  					in.lex()
   831  					in.file.Stmt = append(in.file.Stmt, &LineBlock{
   832  						Start:  start,
   833  						Token:  tokens,
   834  						LParen: LParen{Pos: tok.pos},
   835  						RParen: RParen{Pos: rparen.pos},
   836  					})
   837  					return
   838  				}
   839  				// '( )' in the middle of the line, not a block.
   840  				tokens = append(tokens, tok.text, rparen.text)
   841  			} else {
   842  				// '(' in the middle of the line, not a block.
   843  				tokens = append(tokens, tok.text)
   844  			}
   845  
   846  		default:
   847  			tokens = append(tokens, tok.text)
   848  			end = tok.endPos
   849  		}
   850  	}
   851  }
   852  
   853  func (in *input) parseLineBlock(start Position, token []string, lparen token) *LineBlock {
   854  	x := &LineBlock{
   855  		Start:  start,
   856  		Token:  token,
   857  		LParen: LParen{Pos: lparen.pos},
   858  	}
   859  	var comments []Comment
   860  	for {
   861  		switch in.peek() {
   862  		case _EOLCOMMENT:
   863  			// Suffix comment, will be attached later by assignComments.
   864  			in.lex()
   865  		case '\n':
   866  			// Blank line. Add an empty comment to preserve it.
   867  			in.lex()
   868  			if len(comments) == 0 && len(x.Line) > 0 || len(comments) > 0 && comments[len(comments)-1].Token != "" {
   869  				comments = append(comments, Comment{})
   870  			}
   871  		case _COMMENT:
   872  			tok := in.lex()
   873  			comments = append(comments, Comment{Start: tok.pos, Token: tok.text})
   874  		case _EOF:
   875  			in.Error(fmt.Sprintf("syntax error (unterminated block started at %s:%d:%d)", in.filename, x.Start.Line, x.Start.LineRune))
   876  		case ')':
   877  			rparen := in.lex()
   878  			// Don't preserve blank lines (denoted by a single empty comment, added above)
   879  			// at the end of the block.
   880  			if len(comments) == 1 && comments[0] == (Comment{}) {
   881  				comments = nil
   882  			}
   883  			x.RParen.Before = comments
   884  			x.RParen.Pos = rparen.pos
   885  			if !in.peek().isEOL() {
   886  				in.Error("syntax error (expected newline after closing paren)")
   887  			}
   888  			in.lex()
   889  			return x
   890  		default:
   891  			l := in.parseLine()
   892  			x.Line = append(x.Line, l)
   893  			l.Comment().Before = comments
   894  			comments = nil
   895  		}
   896  	}
   897  }
   898  
   899  func (in *input) parseLine() *Line {
   900  	tok := in.lex()
   901  	if tok.kind.isEOL() {
   902  		in.Error("internal parse error: parseLine at end of line")
   903  	}
   904  	start := tok.pos
   905  	end := tok.endPos
   906  	tokens := []string{tok.text}
   907  	for {
   908  		tok := in.lex()
   909  		if tok.kind.isEOL() {
   910  			return &Line{
   911  				Start:   start,
   912  				Token:   tokens,
   913  				End:     end,
   914  				InBlock: true,
   915  			}
   916  		}
   917  		tokens = append(tokens, tok.text)
   918  		end = tok.endPos
   919  	}
   920  }
   921  
   922  var (
   923  	slashSlash = []byte("//")
   924  	moduleStr  = []byte("module")
   925  )
   926  
   927  // ModulePath returns the module path from the gomod file text.
   928  // If it cannot find a module path, it returns an empty string.
   929  // It is tolerant of unrelated problems in the go.mod file.
   930  func ModulePath(mod []byte) string {
   931  	for len(mod) > 0 {
   932  		line := mod
   933  		mod = nil
   934  		if i := bytes.IndexByte(line, '\n'); i >= 0 {
   935  			line, mod = line[:i], line[i+1:]
   936  		}
   937  		if i := bytes.Index(line, slashSlash); i >= 0 {
   938  			line = line[:i]
   939  		}
   940  		line = bytes.TrimSpace(line)
   941  		if !bytes.HasPrefix(line, moduleStr) {
   942  			continue
   943  		}
   944  		line = line[len(moduleStr):]
   945  		n := len(line)
   946  		line = bytes.TrimSpace(line)
   947  		if len(line) == n || len(line) == 0 {
   948  			continue
   949  		}
   950  
   951  		if line[0] == '"' || line[0] == '`' {
   952  			p, err := strconv.Unquote(string(line))
   953  			if err != nil {
   954  				return "" // malformed quoted string or multiline module path
   955  			}
   956  			return p
   957  		}
   958  
   959  		return string(line)
   960  	}
   961  	return "" // missing module path
   962  }
   963  

View as plain text