purge.go (3030B)
1 // Copyright 2023 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 astutil provides various AST utility functions for gopls. 6 package astutil 7 8 import ( 9 "bytes" 10 "go/scanner" 11 "go/token" 12 ) 13 14 // PurgeFuncBodies returns a copy of src in which the contents of each 15 // outermost {...} region have been deleted, except for struct and 16 // interface type bodies and the bodies of length-elided array 17 // literals ([...]T), whose element count is part of the type. It 18 // includes function bodies, function-literal bodies, and the bodies 19 // of slice, map, and explicitly-sized array composite literals (whose 20 // contents don't affect the type of the enclosing declaration). This 21 // reduces the amount of work required to parse the top-level 22 // declarations. 23 // 24 // PurgeFuncBodies does not preserve newlines or position information. 25 // Also, if the input is invalid, parsing the output of 26 // PurgeFuncBodies may result in a different tree due to its effects 27 // on parser error recovery. 28 func PurgeFuncBodies(src []byte) []byte { 29 // Destroy the content of any {...}-bracketed regions that are 30 // not immediately preceded by a "struct" or "interface" token, 31 // and that are not the body of a length-elided array literal. 32 // That includes function bodies, switch/select bodies, and most 33 // composite literals; this will lead to non-void functions that 34 // don't have return statements, which of course is a type error, 35 // but that's ok. 36 37 var out bytes.Buffer 38 file := token.NewFileSet().AddFile("", -1, len(src)) 39 var sc scanner.Scanner 40 sc.Init(file, src, nil, 0) 41 var prev token.Token 42 var cursor int // last consumed src offset 43 var braces []token.Pos // stack of unclosed braces, or -1 for a region we preserve 44 var ellipsis bool // saw "[...]" not yet consumed by a literal-body "{" 45 for { 46 pos, tok, _ := sc.Scan() 47 if tok == token.EOF { 48 break 49 } 50 switch tok { 51 case token.COMMENT: 52 // TODO(adonovan): opt: skip, to save an estimated 20% of time. 53 54 case token.SEMICOLON: 55 ellipsis = false 56 57 case token.RBRACK: 58 // "...]" occurs only in the array-type prefix of a 59 // composite literal; variadic "..." is followed by 60 // a type or ")", never "]". 61 if prev == token.ELLIPSIS { 62 ellipsis = true 63 } 64 65 case token.LBRACE: 66 if prev == token.STRUCT || prev == token.INTERFACE { 67 pos = -1 // type body: preserve (don't consume ellipsis) 68 } else if ellipsis { 69 pos = -1 // [...]T literal body: preserve 70 ellipsis = false 71 } 72 braces = append(braces, pos) 73 74 case token.RBRACE: 75 if last := len(braces) - 1; last >= 0 { 76 top := braces[last] 77 braces = braces[:last] 78 if top < 0 { 79 // preserve 80 } else if len(braces) == 0 { // toplevel only 81 // Delete {...} body. 82 start := file.Offset(top) 83 end := file.Offset(pos) 84 out.Write(src[cursor : start+len("{")]) 85 cursor = end 86 } 87 } 88 } 89 prev = tok 90 } 91 out.Write(src[cursor:]) 92 return out.Bytes() 93 }