| // 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 parsego |
| |
| import ( |
| "go/ast" |
| "go/parser" |
| "go/scanner" |
| "go/token" |
| "sync" |
| "unicode" |
| |
| "golang.org/x/tools/go/ast/inspector" |
| "golang.org/x/tools/gopls/internal/protocol" |
| "golang.org/x/tools/gopls/internal/util/bug" |
| "golang.org/x/tools/gopls/internal/util/safetoken" |
| ) |
| |
| // A File contains the results of parsing a Go file. |
| type File struct { |
| URI protocol.DocumentURI |
| Mode parser.Mode |
| |
| // File is the file resulting from parsing. It is always non-nil. |
| // |
| // Clients must not access the AST's legacy ast.Object-related |
| // fields without first ensuring that [File.Resolve] was |
| // already called. |
| File *ast.File |
| Tok *token.File |
| // Source code used to build the AST. It may be different from the |
| // actual content of the file if we have fixed the AST. |
| Src []byte |
| |
| Cursor inspector.Cursor // cursor of *ast.File, sans sibling files |
| |
| // fixedSrc and fixedAST report on "fixing" that occurred during parsing of |
| // this file. |
| // |
| // fixedSrc means Src holds file content that was modified to improve parsing. |
| // fixedAST means File was modified after parsing, so AST positions may not |
| // reflect the content of Src. |
| // |
| // TODO(rfindley): there are many places where we haphazardly use the Src or |
| // positions without checking these fields. Audit these places and guard |
| // accordingly. After doing so, we may find that we don't need to |
| // differentiate fixedSrc and fixedAST. |
| fixedSrc bool |
| fixedAST bool |
| Mapper *protocol.Mapper // may map fixed Src, not file content |
| ParseErr scanner.ErrorList |
| |
| // resolveOnce guards the lazy ast.Object resolution. See [File.Resolve]. |
| resolveOnce sync.Once |
| } |
| |
| func (pgf *File) String() string { return string(pgf.URI) } |
| |
| // Fixed reports whether p was "Fixed", meaning that its source or positions |
| // may not correlate with the original file. |
| func (pgf *File) Fixed() bool { |
| return pgf.fixedSrc || pgf.fixedAST |
| } |
| |
| // -- go/token domain convenience helpers -- |
| |
| // PositionPos returns the token.Pos of protocol position p within the file. |
| func (pgf *File) PositionPos(p protocol.Position) (token.Pos, error) { |
| offset, err := pgf.Mapper.PositionOffset(p) |
| if err != nil { |
| return token.NoPos, err |
| } |
| return safetoken.Pos(pgf.Tok, offset) |
| } |
| |
| // PosPosition returns a protocol Position for the token.Pos in this file. |
| func (pgf *File) PosPosition(pos token.Pos) (protocol.Position, error) { |
| return pgf.Mapper.PosPosition(pgf.Tok, pos) |
| } |
| |
| // PosRange returns a protocol Range for the token.Pos interval in this file. |
| func (pgf *File) PosRange(start, end token.Pos) (protocol.Range, error) { |
| return pgf.Mapper.PosRange(pgf.Tok, start, end) |
| } |
| |
| // PosLocation returns a protocol Location for the token.Pos interval in this file. |
| func (pgf *File) PosLocation(start, end token.Pos) (protocol.Location, error) { |
| return pgf.Mapper.PosLocation(pgf.Tok, start, end) |
| } |
| |
| // PosText returns the source text for the token.Pos interval in this file. |
| func (pgf *File) PosText(start, end token.Pos) ([]byte, error) { |
| return pgf.Mapper.PosText(pgf.Tok, start, end) |
| } |
| |
| // NodeRange returns a protocol Range for the ast.Node interval in this file. |
| func (pgf *File) NodeRange(node ast.Node) (protocol.Range, error) { |
| return pgf.Mapper.NodeRange(pgf.Tok, node) |
| } |
| |
| // NodeOffsets returns offsets for the ast.Node. |
| func (pgf *File) NodeOffsets(node ast.Node) (start int, end int, _ error) { |
| return safetoken.Offsets(pgf.Tok, node.Pos(), node.End()) |
| } |
| |
| // NodeLocation returns a protocol Location for the ast.Node interval in this file. |
| func (pgf *File) NodeLocation(node ast.Node) (protocol.Location, error) { |
| return pgf.Mapper.PosLocation(pgf.Tok, node.Pos(), node.End()) |
| } |
| |
| // NodeText returns the source text for the ast.Node interval in this file. |
| func (pgf *File) NodeText(node ast.Node) ([]byte, error) { |
| return pgf.Mapper.NodeText(pgf.Tok, node) |
| } |
| |
| // RangePos parses a protocol Range back into the go/token domain. |
| func (pgf *File) RangePos(r protocol.Range) (token.Pos, token.Pos, error) { |
| start, end, err := pgf.Mapper.RangeOffsets(r) |
| if err != nil { |
| return token.NoPos, token.NoPos, err |
| } |
| return pgf.Tok.Pos(start), pgf.Tok.Pos(end), nil |
| } |
| |
| // CheckNode asserts that the Node's positions are valid w.r.t. pgf.Tok. |
| func (pgf *File) CheckNode(node ast.Node) { |
| // Avoid safetoken.Offsets, and put each assertion on its own source line. |
| pgf.CheckPos(node.Pos()) |
| pgf.CheckPos(node.End()) |
| } |
| |
| // CheckPos asserts that the position is valid w.r.t. pgf.Tok. |
| func (pgf *File) CheckPos(pos token.Pos) { |
| if !pos.IsValid() { |
| bug.Report("invalid token.Pos") |
| } else if _, err := safetoken.Offset(pgf.Tok, pos); err != nil { |
| bug.Report("token.Pos out of range") |
| } |
| } |
| |
| // Resolve lazily resolves ast.Ident.Objects in the enclosed syntax tree. |
| // |
| // Resolve must be called before accessing any of: |
| // - pgf.File.Scope |
| // - pgf.File.Unresolved |
| // - Ident.Obj, for any Ident in pgf.File |
| func (pgf *File) Resolve() { |
| pgf.resolveOnce.Do(func() { |
| if pgf.File.Scope != nil { |
| return // already resolved by parsing without SkipObjectResolution. |
| } |
| defer func() { |
| // (panic handler duplicated from go/parser) |
| if e := recover(); e != nil { |
| // A bailout indicates the resolution stack has exceeded max depth. |
| if _, ok := e.(bailout); !ok { |
| panic(e) |
| } |
| } |
| }() |
| declErr := func(token.Pos, string) {} |
| resolveFile(pgf.File, pgf.Tok, declErr) |
| }) |
| } |
| |
| // Indentation returns the string of spaces representing the indentation |
| // of the line containing the specified position. |
| // This can be used to ensure that inserted code maintains consistent indentation |
| // and column alignment. |
| func (pgf *File) Indentation(pos token.Pos) (string, error) { |
| line := safetoken.Line(pgf.Tok, pos) |
| start, end, err := safetoken.Offsets(pgf.Tok, pgf.Tok.LineStart(line), pos) |
| if err != nil { |
| return "", err |
| } |
| |
| s := string(pgf.Src[start:end]) |
| for i, r := range s { |
| if !unicode.IsSpace(r) { |
| return s[:i], nil // prefix of spaces |
| } |
| } |
| return s, nil |
| } |