Source file src/cmd/vendor/golang.org/x/tools/internal/astutil/util.go

     1  // Copyright 2025 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
     6  
     7  import (
     8  	"fmt"
     9  	"go/ast"
    10  	"go/printer"
    11  	"go/token"
    12  	"strings"
    13  
    14  	"golang.org/x/tools/go/ast/inspector"
    15  	"golang.org/x/tools/internal/moreiters"
    16  )
    17  
    18  // NodeContains reports whether the Pos/End range of node n encloses
    19  // the given range.
    20  //
    21  // It is inclusive of both end points, to allow hovering (etc) when
    22  // the cursor is immediately after a node.
    23  //
    24  // Like [NodeRange], it treats the range of an [ast.File] as the
    25  // file's complete extent.
    26  //
    27  // Precondition: n must not be nil.
    28  func NodeContains(n ast.Node, rng Range) bool {
    29  	return NodeRange(n).Contains(rng)
    30  }
    31  
    32  // NodeContainsPos reports whether the Pos/End range of node n encloses
    33  // the given pos.
    34  //
    35  // Like [NodeRange], it treats the range of an [ast.File] as the
    36  // file's complete extent.
    37  func NodeContainsPos(n ast.Node, pos token.Pos) bool {
    38  	return NodeRange(n).ContainsPos(pos)
    39  }
    40  
    41  // EnclosingFile returns the syntax tree for the file enclosing c.
    42  //
    43  // TODO(adonovan): promote this to a method of Cursor.
    44  func EnclosingFile(c inspector.Cursor) *ast.File {
    45  	c, _ = moreiters.First(c.Enclosing((*ast.File)(nil)))
    46  	return c.Node().(*ast.File)
    47  }
    48  
    49  // DocComment returns the doc comment for a node, if any.
    50  func DocComment(n ast.Node) *ast.CommentGroup {
    51  	switch n := n.(type) {
    52  	case *ast.FuncDecl:
    53  		return n.Doc
    54  	case *ast.GenDecl:
    55  		return n.Doc
    56  	case *ast.ValueSpec:
    57  		return n.Doc
    58  	case *ast.TypeSpec:
    59  		return n.Doc
    60  	case *ast.File:
    61  		return n.Doc
    62  	case *ast.ImportSpec:
    63  		return n.Doc
    64  	case *ast.Field:
    65  		return n.Doc
    66  	}
    67  	return nil
    68  }
    69  
    70  // Format returns a string representation of the node n.
    71  func Format(fset *token.FileSet, n ast.Node) string {
    72  	var buf strings.Builder
    73  	printer.Fprint(&buf, fset, n) // ignore errors
    74  	return buf.String()
    75  }
    76  
    77  // -- Range --
    78  
    79  // Range is a Pos interval.
    80  // It implements [analysis.Range] and [ast.Node].
    81  type Range struct{ Start, EndPos token.Pos }
    82  
    83  // RangeOf constructs a Range.
    84  //
    85  // RangeOf exists to pacify the "unkeyed literal" (composites) vet
    86  // check. It would be nice if there were a way for a type to add
    87  // itself to the allowlist.
    88  func RangeOf(start, end token.Pos) Range { return Range{start, end} }
    89  
    90  // NodeRange returns the extent of node n as a Range.
    91  //
    92  // For unfortunate historical reasons, the Pos/End extent of an
    93  // ast.File runs from the start of its package declaration---excluding
    94  // copyright comments, build tags, and package documentation---to the
    95  // end of its last declaration, excluding any trailing comments. So,
    96  // as a special case, if n is an [ast.File], NodeContains uses
    97  // n.FileStart <= pos && pos <= n.FileEnd to report whether the
    98  // position lies anywhere within the file.
    99  func NodeRange(n ast.Node) Range {
   100  	if file, ok := n.(*ast.File); ok {
   101  		return Range{file.FileStart, file.FileEnd} // entire file
   102  	}
   103  	return Range{n.Pos(), n.End()}
   104  }
   105  
   106  func (r Range) Pos() token.Pos { return r.Start }
   107  func (r Range) End() token.Pos { return r.EndPos }
   108  
   109  // ContainsPos reports whether the range (inclusive of both end points)
   110  // includes the specified position.
   111  func (r Range) ContainsPos(pos token.Pos) bool {
   112  	return r.Contains(RangeOf(pos, pos))
   113  }
   114  
   115  // Contains reports whether the range (inclusive of both end points)
   116  // includes the specified range.
   117  func (r Range) Contains(rng Range) bool {
   118  	return r.Start <= rng.Start && rng.EndPos <= r.EndPos
   119  }
   120  
   121  // IsValid reports whether the range is valid.
   122  func (r Range) IsValid() bool { return r.Start.IsValid() && r.Start <= r.EndPos }
   123  
   124  // --
   125  
   126  // Select returns the syntax nodes identified by a user's text
   127  // selection. It returns three nodes: the innermost node that wholly
   128  // encloses the selection; and the first and last nodes that are
   129  // wholly enclosed by the selection.
   130  //
   131  // For example, given this selection:
   132  //
   133  //	{ f(); g(); /* comment */ }
   134  //	  ~~~~~~~~~~~
   135  //
   136  // Select returns the enclosing BlockStmt, the f() CallExpr, and the g() CallExpr.
   137  //
   138  // If the selection does not wholly enclose any nodes, Select returns an error
   139  // and invalid start/end nodes, but it may return a valid enclosing node.
   140  //
   141  // Callers that require exactly one syntax tree (e.g. just f() or just
   142  // g()) should check that the returned start and end nodes are
   143  // identical.
   144  //
   145  // This function is intended to be called early in the handling of a
   146  // user's request, since it is tolerant of sloppy selection including
   147  // extraneous whitespace and comments. Use it in new code instead of
   148  // PathEnclosingInterval. When the exact extent of a node is known,
   149  // use [Cursor.FindByPos] instead.
   150  //
   151  // TODO(hxjiang): Consider refactoring the function signature. It is currently
   152  // confusing that an error is returned even when a valid enclosing node is
   153  // successfully found. Consider grouping all cursors into one struct.
   154  func Select(curFile inspector.Cursor, start, end token.Pos) (_enclosing, _start, _end inspector.Cursor, _ error) {
   155  	curEnclosing, ok := curFile.FindByPos(start, end)
   156  	if !ok {
   157  		return noCursor, noCursor, noCursor, fmt.Errorf("invalid selection")
   158  	}
   159  
   160  	// Find the first and last node wholly within the (start, end) range.
   161  	// We'll narrow the effective selection to them, to exclude whitespace.
   162  	// (This matches the functionality of PathEnclosingInterval.)
   163  	var curStart, curEnd inspector.Cursor
   164  	rng := RangeOf(start, end)
   165  	for cur := range curEnclosing.Preorder() {
   166  		if rng.Contains(NodeRange(cur.Node())) {
   167  			// The start node has the least Pos.
   168  			if !curStart.Valid() {
   169  				curStart = cur
   170  			}
   171  			// The end node has the greatest End.
   172  			// End positions do not change monotonically,
   173  			// so we must compute the max.
   174  			if !curEnd.Valid() ||
   175  				cur.Node().End() > curEnd.Node().End() {
   176  				curEnd = cur
   177  			}
   178  		}
   179  	}
   180  	if !curStart.Valid() {
   181  		// The selection is valid (inside curEnclosing) but contains no
   182  		// complete nodes. This happens for point selections (start == end),
   183  		// or selections covering only only spaces, comments, and punctuation
   184  		// tokens.
   185  		// Return the enclosing node so the caller can still use the context.
   186  		return curEnclosing, noCursor, noCursor, fmt.Errorf("invalid selection")
   187  	}
   188  	return curEnclosing, curStart, curEnd, nil
   189  }
   190  
   191  var noCursor inspector.Cursor
   192  
   193  // MaybeParenthesize returns new, possibly wrapped in parens if needed
   194  // to preserve operator precedence when it replaces old, whose parent
   195  // is parentNode.
   196  //
   197  // (This would be more naturally written in terms of Cursor, but one of
   198  // the callers--the inliner--does not have cursors handy.)
   199  func MaybeParenthesize(parentNode ast.Node, old, new ast.Expr) ast.Expr {
   200  	if needsParens(parentNode, old, new) {
   201  		new = &ast.ParenExpr{X: new}
   202  	}
   203  	return new
   204  }
   205  
   206  func needsParens(parentNode ast.Node, old, new ast.Expr) bool {
   207  	// An expression beneath a non-expression
   208  	// has no precedence ambiguity.
   209  	parent, ok := parentNode.(ast.Expr)
   210  	if !ok {
   211  		return false
   212  	}
   213  
   214  	precedence := func(n ast.Node) int {
   215  		switch n := n.(type) {
   216  		case *ast.UnaryExpr, *ast.StarExpr:
   217  			return token.UnaryPrec
   218  		case *ast.BinaryExpr:
   219  			return n.Op.Precedence()
   220  		}
   221  		return -1
   222  	}
   223  
   224  	// Parens are not required if the new node
   225  	// is not unary or binary.
   226  	newprec := precedence(new)
   227  	if newprec < 0 {
   228  		return false
   229  	}
   230  
   231  	// Parens are required if parent and child are both
   232  	// unary or binary and the parent has higher precedence.
   233  	if precedence(parent) > newprec {
   234  		return true
   235  	}
   236  
   237  	// Was the old node the operand of a postfix operator?
   238  	//  f().sel
   239  	//  f()[i:j]
   240  	//  f()[i]
   241  	//  f().(T)
   242  	//  f()(x)
   243  	switch parent := parent.(type) {
   244  	case *ast.SelectorExpr:
   245  		return parent.X == old
   246  	case *ast.IndexExpr:
   247  		return parent.X == old
   248  	case *ast.SliceExpr:
   249  		return parent.X == old
   250  	case *ast.TypeAssertExpr:
   251  		return parent.X == old
   252  	case *ast.CallExpr:
   253  		return parent.Fun == old
   254  	}
   255  	return false
   256  }
   257  
   258  func is[T any](n any) bool {
   259  	_, ok := n.(T)
   260  	return ok
   261  }
   262  

View as plain text