// Copyright 2023 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package astutil provides various AST utility functions for gopls. package astutil import ( "bytes" "go/scanner" "go/token" ) // PurgeFuncBodies returns a copy of src in which the contents of each // outermost {...} region have been deleted, except for struct and // interface type bodies and the bodies of length-elided array // literals ([...]T), whose element count is part of the type. It // includes function bodies, function-literal bodies, and the bodies // of slice, map, and explicitly-sized array composite literals (whose // contents don't affect the type of the enclosing declaration). This // reduces the amount of work required to parse the top-level // declarations. // // PurgeFuncBodies does not preserve newlines or position information. // Also, if the input is invalid, parsing the output of // PurgeFuncBodies may result in a different tree due to its effects // on parser error recovery. func PurgeFuncBodies(src []byte) []byte { // Destroy the content of any {...}-bracketed regions that are // not immediately preceded by a "struct" or "interface" token, // and that are not the body of a length-elided array literal. // That includes function bodies, switch/select bodies, and most // composite literals; this will lead to non-void functions that // don't have return statements, which of course is a type error, // but that's ok. var out bytes.Buffer file := token.NewFileSet().AddFile("", -1, len(src)) var sc scanner.Scanner sc.Init(file, src, nil, 0) var prev token.Token var cursor int // last consumed src offset var braces []token.Pos // stack of unclosed braces, or -1 for a region we preserve var ellipsis bool // saw "[...]" not yet consumed by a literal-body "{" for { pos, tok, _ := sc.Scan() if tok == token.EOF { break } switch tok { case token.COMMENT: // TODO(adonovan): opt: skip, to save an estimated 20% of time. case token.SEMICOLON: ellipsis = false case token.RBRACK: // "...]" occurs only in the array-type prefix of a // composite literal; variadic "..." is followed by // a type or ")", never "]". if prev == token.ELLIPSIS { ellipsis = true } case token.LBRACE: if prev == token.STRUCT || prev == token.INTERFACE { pos = -1 // type body: preserve (don't consume ellipsis) } else if ellipsis { pos = -1 // [...]T literal body: preserve ellipsis = false } braces = append(braces, pos) case token.RBRACE: if last := len(braces) - 1; last >= 0 { top := braces[last] braces = braces[:last] if top < 0 { // preserve } else if len(braces) == 0 { // toplevel only // Delete {...} body. start := file.Offset(top) end := file.Offset(pos) out.Write(src[cursor : start+len("{")]) cursor = end } } } prev = tok } out.Write(src[cursor:]) return out.Bytes() }