| 43 | } |
| 44 | |
| 45 | func processFile(path string) error { |
| 46 | fset := token.NewFileSet() |
| 47 | f, err := parser.ParseFile(fset, path, nil, parser.ParseComments) |
| 48 | if err != nil { |
| 49 | return fmt.Errorf("parse %s: %w", path, err) |
| 50 | } |
| 51 | |
| 52 | // Collect line numbers where we need to insert t.Parallel(). |
| 53 | // Each entry is the line of the opening '{' of the test function body. |
| 54 | type insertion struct { |
| 55 | line int // line number of the '{' opening the function body |
| 56 | paramName string // name of the *testing.T parameter |
| 57 | } |
| 58 | var insertions []insertion |
| 59 | |
| 60 | for _, decl := range f.Decls { |
| 61 | fn, ok := decl.(*ast.FuncDecl) |
| 62 | if !ok { |
| 63 | continue |
| 64 | } |
| 65 | if !isTestFunc(fn) { |
| 66 | continue |
| 67 | } |
| 68 | paramName := testingTParamName(fn) |
| 69 | if paramName == "" { |
| 70 | continue |
| 71 | } |
| 72 | if hasParallelCall(fn.Body, paramName) { |
| 73 | continue |
| 74 | } |
| 75 | if hasNoLintComment(fn) { |
| 76 | continue |
| 77 | } |
| 78 | bodyLine := fset.Position(fn.Body.Lbrace).Line |
| 79 | insertions = append(insertions, insertion{line: bodyLine, paramName: paramName}) |
| 80 | } |
| 81 | |
| 82 | if len(insertions) == 0 { |
| 83 | return nil |
| 84 | } |
| 85 | |
| 86 | // Sort by line descending so insertions don't shift line numbers of subsequent insertions. |
| 87 | sort.Slice(insertions, func(i, j int) bool { |
| 88 | return insertions[i].line > insertions[j].line |
| 89 | }) |
| 90 | |
| 91 | fi, err := os.Stat(path) |
| 92 | if err != nil { |
| 93 | return fmt.Errorf("stat %s: %w", path, err) |
| 94 | } |
| 95 | |
| 96 | src, err := os.ReadFile(path) |
| 97 | if err != nil { |
| 98 | return fmt.Errorf("read %s: %w", path, err) |
| 99 | } |
| 100 | |
| 101 | lines := strings.Split(string(src), "\n") |
| 102 | for _, ins := range insertions { |