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