Source file src/go/types/commentMap_test.go

     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  package types_test
     6  
     7  import (
     8  	"fmt"
     9  	"go/scanner"
    10  	"go/token"
    11  	"regexp"
    12  	"strings"
    13  	"testing"
    14  )
    15  
    16  type comment struct {
    17  	line, col int    // comment position
    18  	text      string // comment text, excluding "//", "/*", or "*/"
    19  }
    20  
    21  // commentMap collects all comments in the given src with comment text
    22  // that matches the supplied regular expression rx and returns them as
    23  // []comment lists in a map indexed by line number. The comment text is
    24  // the comment with any comment markers ("//", "/*", or "*/") stripped.
    25  // The position for each comment is the position of the token immediately
    26  // preceding the comment, with all comments that are on the same line
    27  // collected in a slice, in source order. If there is no preceding token
    28  // (the matching comment appears at the beginning of the file), then the
    29  // recorded position is unknown (line, col = 0, 0).
    30  // If there are no matching comments, the result is nil.
    31  func commentMap(src []byte, rx *regexp.Regexp) (res map[int][]comment) {
    32  	fset := token.NewFileSet()
    33  	file := fset.AddFile("", -1, len(src))
    34  
    35  	var s scanner.Scanner
    36  	s.Init(file, src, nil, scanner.ScanComments)
    37  	var prev token.Pos // position of last non-comment, non-semicolon token
    38  
    39  	for {
    40  		pos, tok, lit := s.Scan()
    41  		switch tok {
    42  		case token.EOF:
    43  			return
    44  		case token.COMMENT:
    45  			if lit[1] == '*' {
    46  				lit = lit[:len(lit)-2] // strip trailing */
    47  			}
    48  			lit = lit[2:] // strip leading // or /*
    49  			if rx.MatchString(lit) {
    50  				p := fset.Position(prev)
    51  				err := comment{p.Line, p.Column, lit}
    52  				if res == nil {
    53  					res = make(map[int][]comment)
    54  				}
    55  				res[p.Line] = append(res[p.Line], err)
    56  			}
    57  		case token.SEMICOLON:
    58  			// ignore automatically inserted semicolon
    59  			if lit == "\n" {
    60  				continue
    61  			}
    62  			fallthrough
    63  		default:
    64  			prev = pos
    65  		}
    66  	}
    67  }
    68  
    69  func TestCommentMap(t *testing.T) {
    70  	const src = `/* ERROR "0:0" */ /* ERROR "0:0" */ // ERROR "0:0"
    71  // ERROR "0:0"
    72  x /* ERROR "3:1" */                // ignore automatically inserted semicolon here
    73  /* ERROR "3:1" */                  // position of x on previous line
    74     x /* ERROR "5:4" */ ;           // do not ignore this semicolon
    75  /* ERROR "5:24" */                 // position of ; on previous line
    76  	package /* ERROR "7:2" */  // indented with tab
    77          import  /* ERROR "8:9" */  // indented with blanks
    78  `
    79  	m := commentMap([]byte(src), regexp.MustCompile("^ ERROR "))
    80  	found := 0 // number of errors found
    81  	for line, errlist := range m {
    82  		for _, err := range errlist {
    83  			if err.line != line {
    84  				t.Errorf("%v: got map line %d; want %d", err, err.line, line)
    85  				continue
    86  			}
    87  			// err.line == line
    88  
    89  			got := strings.TrimSpace(err.text[len(" ERROR "):])
    90  			want := fmt.Sprintf(`"%d:%d"`, line, err.col)
    91  			if got != want {
    92  				t.Errorf("%v: got msg %q; want %q", err, got, want)
    93  				continue
    94  			}
    95  			found++
    96  		}
    97  	}
    98  
    99  	want := strings.Count(src, " ERROR ")
   100  	if found != want {
   101  		t.Errorf("commentMap got %d errors; want %d", found, want)
   102  	}
   103  }
   104  

View as plain text