1// Copyright 2019 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
5package cache
6
7import (
8	"context"
9	"fmt"
10	"go/token"
11
12	"golang.org/x/tools/internal/lsp/source"
13	"golang.org/x/tools/internal/memoize"
14)
15
16type tokenKey struct {
17	file source.FileIdentity
18}
19
20type tokenHandle struct {
21	handle *memoize.Handle
22	file   source.FileHandle
23}
24
25type tokenData struct {
26	memoize.NoCopy
27
28	tok *token.File
29	err error
30}
31
32func (c *cache) TokenHandle(fh source.FileHandle) source.TokenHandle {
33	key := tokenKey{
34		file: fh.Identity(),
35	}
36	h := c.store.Bind(key, func(ctx context.Context) interface{} {
37		data := &tokenData{}
38		data.tok, data.err = tokenFile(ctx, c, fh)
39		return data
40	})
41	return &tokenHandle{
42		handle: h,
43		file:   fh,
44	}
45}
46
47func (h *tokenHandle) File() source.FileHandle {
48	return h.file
49}
50
51func (h *tokenHandle) Token(ctx context.Context) (*token.File, error) {
52	v := h.handle.Get(ctx)
53	if v == nil {
54		return nil, ctx.Err()
55	}
56	data := v.(*tokenData)
57	return data.tok, data.err
58}
59
60func tokenFile(ctx context.Context, c *cache, fh source.FileHandle) (*token.File, error) {
61	// First, check if we already have a parsed AST for this file's handle.
62	for _, mode := range []source.ParseMode{
63		source.ParseHeader,
64		source.ParseExported,
65		source.ParseFull,
66	} {
67		pk := parseKey{
68			file: fh.Identity(),
69			mode: mode,
70		}
71		pd, ok := c.store.Cached(pk).(*parseGoData)
72		if !ok {
73			continue
74		}
75		if pd.ast == nil {
76			continue
77		}
78		if !pd.ast.Pos().IsValid() {
79			continue
80		}
81		return c.FileSet().File(pd.ast.Pos()), nil
82	}
83	// We have not yet parsed this file.
84	buf, _, err := fh.Read(ctx)
85	if err != nil {
86		return nil, err
87	}
88	tok := c.FileSet().AddFile(fh.Identity().URI.Filename(), -1, len(buf))
89	if tok == nil {
90		return nil, fmt.Errorf("no token.File for %s", fh.Identity().URI)
91	}
92	tok.SetLinesForContent(buf)
93	return tok, nil
94}
95