...

Source file src/golang.org/x/tools/go/expect/expect.go

Documentation: golang.org/x/tools/go/expect

     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  /*
     6  Package expect provides support for interpreting structured comments in Go
     7  source code as test expectations.
     8  
     9  This is primarily intended for writing tests of things that process Go source
    10  files, although it does not directly depend on the testing package.
    11  
    12  Collect notes with the Extract or Parse functions, and use the
    13  MatchBefore function to find matches within the lines the comments were on.
    14  
    15  The interpretation of the notes depends on the application.
    16  For example, the test suite for a static checking tool might
    17  use a @diag note to indicate an expected diagnostic:
    18  
    19  	fmt.Printf("%s", 1) //@ diag("%s wants a string, got int")
    20  
    21  By contrast, the test suite for a source code navigation tool
    22  might use notes to indicate the positions of features of
    23  interest, the actions to be performed by the test,
    24  and their expected outcomes:
    25  
    26  	var x = 1 //@ x_decl
    27  	...
    28  	print(x) //@ definition("x", x_decl)
    29  	print(x) //@ typeof("x", "int")
    30  
    31  # Note comment syntax
    32  
    33  Note comments always start with the special marker @, which must be the
    34  very first character after the comment opening pair, so //@ or /*@ with no
    35  spaces.
    36  
    37  This is followed by a comma separated list of notes.
    38  
    39  A note always starts with an identifier, which is optionally followed by an
    40  argument list. The argument list is surrounded with parentheses and contains a
    41  comma-separated list of arguments.
    42  The empty parameter list and the missing parameter list are distinguishable if
    43  needed; they result in a nil or an empty list in the Args parameter respectively.
    44  
    45  Arguments are either identifiers or literals.
    46  The literals supported are the basic value literals, of string, float, integer
    47  true, false or nil. All the literals match the standard go conventions, with
    48  all bases of integers, and both quote and backtick strings.
    49  There is one extra literal type, which is a string literal preceded by the
    50  identifier "re" which is compiled to a regular expression.
    51  */
    52  package expect
    53  
    54  import (
    55  	"bytes"
    56  	"fmt"
    57  	"go/token"
    58  	"regexp"
    59  )
    60  
    61  // Note is a parsed note from an expect comment.
    62  // It knows the position of the start of the comment, and the name and
    63  // arguments that make up the note.
    64  type Note struct {
    65  	Pos  token.Pos     // The position at which the note identifier appears
    66  	Name string        // the name associated with the note
    67  	Args []interface{} // the arguments for the note
    68  }
    69  
    70  // ReadFile is the type of a function that can provide file contents for a
    71  // given filename.
    72  // This is used in MatchBefore to look up the content of the file in order to
    73  // find the line to match the pattern against.
    74  type ReadFile func(filename string) ([]byte, error)
    75  
    76  // MatchBefore attempts to match a pattern in the line before the supplied pos.
    77  // It uses the FileSet and the ReadFile to work out the contents of the line
    78  // that end is part of, and then matches the pattern against the content of the
    79  // start of that line up to the supplied position.
    80  // The pattern may be either a simple string, []byte or a *regexp.Regexp.
    81  // MatchBefore returns the range of the line that matched the pattern, and
    82  // invalid positions if there was no match, or an error if the line could not be
    83  // found.
    84  func MatchBefore(fset *token.FileSet, readFile ReadFile, end token.Pos, pattern interface{}) (token.Pos, token.Pos, error) {
    85  	f := fset.File(end)
    86  	content, err := readFile(f.Name())
    87  	if err != nil {
    88  		return token.NoPos, token.NoPos, fmt.Errorf("invalid file: %v", err)
    89  	}
    90  	position := f.Position(end)
    91  	startOffset := f.Offset(f.LineStart(position.Line))
    92  	endOffset := f.Offset(end)
    93  	line := content[startOffset:endOffset]
    94  	matchStart, matchEnd := -1, -1
    95  	switch pattern := pattern.(type) {
    96  	case string:
    97  		bytePattern := []byte(pattern)
    98  		matchStart = bytes.Index(line, bytePattern)
    99  		if matchStart >= 0 {
   100  			matchEnd = matchStart + len(bytePattern)
   101  		}
   102  	case []byte:
   103  		matchStart = bytes.Index(line, pattern)
   104  		if matchStart >= 0 {
   105  			matchEnd = matchStart + len(pattern)
   106  		}
   107  	case *regexp.Regexp:
   108  		match := pattern.FindIndex(line)
   109  		if len(match) > 0 {
   110  			matchStart = match[0]
   111  			matchEnd = match[1]
   112  		}
   113  	}
   114  	if matchStart < 0 {
   115  		return token.NoPos, token.NoPos, nil
   116  	}
   117  	return f.Pos(startOffset + matchStart), f.Pos(startOffset + matchEnd), nil
   118  }
   119  
   120  func lineEnd(f *token.File, line int) token.Pos {
   121  	if line >= f.LineCount() {
   122  		return token.Pos(f.Base() + f.Size())
   123  	}
   124  	return f.LineStart(line + 1)
   125  }
   126  

View as plain text