github.com/nya3jp/tast@v0.0.0-20230601000426-85c8e4d83a9b/src/go.chromium.org/tast/core/cmd/tast-lint/internal/check/check_informational.go (about)

     1  // Copyright 2019 The ChromiumOS Authors
     2  // Use of this source code is governed by a BSD-style license that can be
     3  // found in the LICENSE file.
     4  
     5  package check
     6  
     7  import (
     8  	"fmt"
     9  	"go/ast"
    10  	"go/token"
    11  )
    12  
    13  const (
    14  	shouldBeInformationalMsg = `Newly added tests should be marked as 'informational'.`
    15  )
    16  
    17  // VerifyInformationalAttr checks if a newly added test has 'informational' attribute.
    18  func VerifyInformationalAttr(fs *token.FileSet, f *ast.File) []*Issue {
    19  	var issues []*Issue
    20  
    21  	for _, decl := range f.Decls {
    22  		fn, ok := decl.(*ast.FuncDecl)
    23  		if !ok || fn.Recv != nil || fn.Name.Name != "init" {
    24  			// Not an init() function declaration. Skip.
    25  			continue
    26  		}
    27  		for _, stmt := range fn.Body.List {
    28  			estmt, ok := stmt.(*ast.ExprStmt)
    29  			if !ok || !isTestingAddTestCall(estmt.X) {
    30  				continue
    31  			}
    32  			call := estmt.X.(*ast.CallExpr)
    33  			if len(call.Args) != 1 {
    34  				// This should be checked by a compiler, so skipped.
    35  				continue
    36  			}
    37  			// Verify the argument is "&testing.Test{...}"
    38  			arg, ok := call.Args[0].(*ast.UnaryExpr)
    39  			if !ok || arg.Op != token.AND {
    40  				continue
    41  			}
    42  			comp, ok := arg.X.(*ast.CompositeLit)
    43  			if !ok {
    44  				continue
    45  			}
    46  
    47  			var attrs []string
    48  			var attrPos token.Pos
    49  			var paramNode ast.Node
    50  			for _, el := range comp.Elts {
    51  				identName, value, pos, err := decomposeKVNode(el)
    52  				if err != nil {
    53  					continue
    54  				}
    55  				if identName == "Attr" {
    56  					attrs = makeSliceAttrs(value)
    57  					attrPos = pos
    58  				}
    59  				if identName == "Params" {
    60  					paramNode = value
    61  				}
    62  			}
    63  
    64  			if paramNode != nil {
    65  				comp, ok := paramNode.(*ast.CompositeLit)
    66  				if !ok {
    67  					// This should be checked by another lint, so skipped.
    68  					continue
    69  				}
    70  				for _, el := range comp.Elts {
    71  					comp, ok := el.(*ast.CompositeLit)
    72  					if !ok {
    73  						continue
    74  					}
    75  					var exAttrs []string
    76  					var exAttrPos token.Pos
    77  					for _, el := range comp.Elts {
    78  						identName, value, pos, err := decomposeKVNode(el)
    79  						if err != nil {
    80  							continue
    81  						}
    82  						if identName == "ExtraAttr" {
    83  							exAttrs = makeSliceAttrs(value)
    84  							exAttrPos = pos
    85  						}
    86  					}
    87  					exAttrs = append(exAttrs, attrs...)
    88  					if isCriticalTest(exAttrs) {
    89  						issues = append(issues, &Issue{
    90  							Pos:  fs.Position(exAttrPos),
    91  							Msg:  shouldBeInformationalMsg,
    92  							Link: testRegistrationURL,
    93  						})
    94  					}
    95  				}
    96  			} else {
    97  				if isCriticalTest(attrs) {
    98  					issues = append(issues, &Issue{
    99  						Pos:  fs.Position(attrPos),
   100  						Msg:  shouldBeInformationalMsg,
   101  						Link: testRegistrationURL,
   102  					})
   103  				}
   104  			}
   105  		}
   106  	}
   107  	return issues
   108  }
   109  
   110  // decomposeKVNode returns the name of identifier, node of keyvalue and its position.
   111  func decomposeKVNode(el ast.Node) (string, ast.Node, token.Pos, error) {
   112  	kv, ok := el.(*ast.KeyValueExpr)
   113  	if !ok {
   114  		return "", nil, token.NoPos, fmt.Errorf("unexpected input")
   115  	}
   116  	ident, ok := kv.Key.(*ast.Ident)
   117  	if !ok {
   118  		return "", kv.Value, kv.Pos(), fmt.Errorf("unexpected input")
   119  	}
   120  	return ident.Name, kv.Value, kv.Pos(), nil
   121  }
   122  
   123  // makeSliceAttrs make a string slice of elements in attribute.
   124  // We do not check the non-string-literal elements.
   125  func makeSliceAttrs(node ast.Node) []string {
   126  	var attrs []string
   127  	comp, ok := node.(*ast.CompositeLit)
   128  	if !ok {
   129  		return attrs
   130  	}
   131  	for _, el := range comp.Elts {
   132  		s, ok := toString(el)
   133  		if !ok {
   134  			continue
   135  		}
   136  		attrs = append(attrs, s)
   137  	}
   138  	return attrs
   139  }
   140  
   141  // isCriticalTest returns true if there are no 'informational' attribute
   142  // in existing attributes and it is mainline test.
   143  func isCriticalTest(attrs []string) bool {
   144  	isMainlineTest := false
   145  	isInformational := false
   146  	for _, attr := range attrs {
   147  		if attr == "informational" {
   148  			isInformational = true
   149  		} else if attr == "group:mainline" {
   150  			isMainlineTest = true
   151  		}
   152  	}
   153  	return isMainlineTest && !isInformational
   154  }