src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

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 }