1
0
mirror of https://github.com/golang/go synced 2024-11-18 16:04:44 -07:00

internal/lsp: support definitions and hover for builtins

This change adds support for definitions and hover for builtin types and
functions. It also includes some small (non-logic) changes to the import
spec definition function.

Additionally, there are some resulting changes in diagnostics to ignore
the builtin file but also use it for definitions (Ian, you were right
with your comment on my earlier review...).

Fixes golang/go#31696

Change-Id: I52d43d010a5ca8359b539c33e40782877eb730d0
Reviewed-on: https://go-review.googlesource.com/c/tools/+/177517
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Ian Cottrell <iancottrell@google.com>
This commit is contained in:
Rebecca Stambler 2019-05-15 17:58:16 -04:00
parent 1a8f2608bd
commit bffc5affc6
16 changed files with 208 additions and 147 deletions

View File

@ -174,12 +174,12 @@ func (f *goFile) GetActiveReverseDeps(ctx context.Context) []source.GoFile {
results := make(map[*goFile]struct{})
f.view.reverseDeps(ctx, seen, results, pkg.PkgPath())
files := make([]source.GoFile, 0, len(results))
var files []source.GoFile
for rd := range results {
if rd == nil {
continue
}
// Don't return any of the active file's in this package.
// Don't return any of the active files in this package.
if rd.pkg != nil && rd.pkg == pkg {
continue
}

View File

@ -63,6 +63,10 @@ func (s *session) NewView(name string, folder span.URI, config *packages.Config)
pcache: &packageCache{
packages: make(map[string]*entry),
},
ignoredURIs: make(map[span.URI]struct{}),
}
for filename := range v.builtinPkg.Files {
v.ignoredURIs[span.NewURI(filename)] = struct{}{}
}
s.views = append(s.views, v)
// we always need to drop the view map

View File

@ -6,7 +6,6 @@ package cache
import (
"context"
"fmt"
"go/ast"
"go/parser"
"go/token"
@ -67,6 +66,9 @@ type view struct {
// builtinPkg is the AST package used to resolve builtin types.
builtinPkg *ast.Package
// ignoredURIs is the set of URIs of files that we ignore.
ignoredURIs map[span.URI]struct{}
}
type metadataCache struct {
@ -290,18 +292,15 @@ func (v *view) GetFile(ctx context.Context, uri span.URI) (source.File, error) {
// getFile is the unlocked internal implementation of GetFile.
func (v *view) getFile(uri span.URI) (viewFile, error) {
filename, err := uri.Filename()
if err != nil {
return nil, err
}
if v.isIgnored(filename) {
return nil, fmt.Errorf("%s is ignored", filename)
}
if f, err := v.findFile(uri); err != nil {
return nil, err
} else if f != nil {
return f, nil
}
filename, err := uri.Filename()
if err != nil {
return nil, err
}
f := &goFile{
fileBase: fileBase{
view: v,
@ -312,18 +311,11 @@ func (v *view) getFile(uri span.URI) (viewFile, error) {
return f, nil
}
// isIgnored checks if the given filename is a file we ignore.
// Ignore checks if the given URI is a URI we ignore.
// As of right now, we only ignore files in the "builtin" package.
func (v *view) isIgnored(filename string) bool {
bpkg := v.BuiltinPackage()
if bpkg != nil {
for builtinFilename := range bpkg.Files {
if filename == builtinFilename {
return true
}
}
}
return false
func (v *view) Ignore(uri span.URI) bool {
_, ok := v.ignoredURIs[uri]
return ok
}
// findFile checks the cache for any file matching the given uri.

View File

@ -64,8 +64,8 @@ func TestDefinitionHelpExample(t *testing.T) {
func (r *runner) Definition(t *testing.T, data tests.Definitions) {
for _, d := range data {
if d.IsType {
// TODO: support type definition queries
if d.IsType || d.OnlyHover {
// TODO: support type definition, hover queries
continue
}
d.Src = span.New(d.Src.URI(), span.NewPoint(0, 0, d.Src.Start().Offset()), span.Point{})

View File

@ -312,7 +312,10 @@ func (r *runner) Format(t *testing.T, data tests.Formats) {
func (r *runner) Definition(t *testing.T, data tests.Definitions) {
for _, d := range data {
sm := r.mapper(d.Src.URI())
sm, err := r.mapper(d.Src.URI())
if err != nil {
t.Fatal(err)
}
loc, err := sm.Location(d.Src)
if err != nil {
t.Fatalf("failed for %v: %v", d.Src, err)
@ -338,13 +341,6 @@ func (r *runner) Definition(t *testing.T, data tests.Definitions) {
if len(locs) != 1 {
t.Errorf("got %d locations for definition, expected 1", len(locs))
}
locURI := span.NewURI(locs[0].URI)
lm := r.mapper(locURI)
if def, err := lm.Span(locs[0]); err != nil {
t.Fatalf("failed for %v: %v", locs[0], err)
} else if def != d.Def {
t.Errorf("for %v got %v want %v", d.Src, def, d.Def)
}
if hover != nil {
tag := fmt.Sprintf("%s-hover", d.Name)
filename, err := d.Src.URI().Filename()
@ -357,13 +353,29 @@ func (r *runner) Definition(t *testing.T, data tests.Definitions) {
if hover.Contents.Value != expectHover {
t.Errorf("for %v got %q want %q", d.Src, hover.Contents.Value, expectHover)
}
} else if !d.OnlyHover {
locURI := span.NewURI(locs[0].URI)
lm, err := r.mapper(locURI)
if err != nil {
t.Fatal(err)
}
if def, err := lm.Span(locs[0]); err != nil {
t.Fatalf("failed for %v: %v", locs[0], err)
} else if def != d.Def {
t.Errorf("for %v got %v want %v", d.Src, def, d.Def)
}
} else {
t.Errorf("no tests ran for %s", d.Src.URI())
}
}
}
func (r *runner) Highlight(t *testing.T, data tests.Highlights) {
for name, locations := range data {
m := r.mapper(locations[0].URI())
m, err := r.mapper(locations[0].URI())
if err != nil {
t.Fatal(err)
}
loc, err := m.Location(locations[0])
if err != nil {
t.Fatalf("failed for %v: %v", locations[0], err)
@ -405,16 +417,19 @@ func (r *runner) Symbol(t *testing.T, data tests.Symbols) {
t.Errorf("want %d top-level symbols in %v, got %d", len(expectedSymbols), uri, len(symbols))
continue
}
if diff := r.diffSymbols(uri, expectedSymbols, symbols); diff != "" {
if diff := r.diffSymbols(t, uri, expectedSymbols, symbols); diff != "" {
t.Error(diff)
}
}
}
func (r *runner) diffSymbols(uri span.URI, want []source.Symbol, got []protocol.DocumentSymbol) string {
func (r *runner) diffSymbols(t *testing.T, uri span.URI, want []source.Symbol, got []protocol.DocumentSymbol) string {
sort.Slice(want, func(i, j int) bool { return want[i].Name < want[j].Name })
sort.Slice(got, func(i, j int) bool { return got[i].Name < got[j].Name })
m := r.mapper(uri)
m, err := r.mapper(uri)
if err != nil {
t.Fatal(err)
}
if len(got) != len(want) {
return summarizeSymbols(-1, want, got, "different lengths got %v want %v", len(got), len(want))
}
@ -433,7 +448,7 @@ func (r *runner) diffSymbols(uri span.URI, want []source.Symbol, got []protocol.
if w.SelectionSpan != spn {
return summarizeSymbols(i, want, got, "incorrect span got %v want %v", spn, w.SelectionSpan)
}
if msg := r.diffSymbols(uri, w.Children, g.Children); msg != "" {
if msg := r.diffSymbols(t, uri, w.Children, g.Children); msg != "" {
return fmt.Sprintf("children of %s: %s", w.Name, msg)
}
}
@ -461,7 +476,10 @@ func summarizeSymbols(i int, want []source.Symbol, got []protocol.DocumentSymbol
func (r *runner) SignatureHelp(t *testing.T, data tests.Signatures) {
for spn, expectedSignatures := range data {
m := r.mapper(spn.URI())
m, err := r.mapper(spn.URI())
if err != nil {
t.Fatal(err)
}
loc, err := m.Location(spn)
if err != nil {
t.Fatalf("failed for %v: %v", loc, err)
@ -519,7 +537,10 @@ func diffSignatures(spn span.Span, want source.SignatureInformation, got *protoc
func (r *runner) Link(t *testing.T, data tests.Links) {
for uri, wantLinks := range data {
m := r.mapper(uri)
m, err := r.mapper(uri)
if err != nil {
t.Fatal(err)
}
gotLinks, err := r.server.DocumentLink(context.Background(), &protocol.DocumentLinkParams{
TextDocument: protocol.TextDocumentIdentifier{
URI: protocol.NewURI(uri),
@ -552,28 +573,28 @@ func (r *runner) Link(t *testing.T, data tests.Links) {
}
}
func (r *runner) mapper(uri span.URI) *protocol.ColumnMapper {
fname, err := uri.Filename()
func (r *runner) mapper(uri span.URI) (*protocol.ColumnMapper, error) {
filename, err := uri.Filename()
if err != nil {
return nil
return nil, err
}
fset := r.data.Exported.ExpectFileSet
var f *token.File
fset.Iterate(func(check *token.File) bool {
if check.Name() == fname {
if check.Name() == filename {
f = check
return false
}
return true
})
if f == nil {
return nil
return nil, fmt.Errorf("no token.File for %s", uri)
}
content, err := r.data.Exported.FileContents(f.Name())
if err != nil {
return nil
return nil, err
}
return protocol.NewColumnMapper(uri, fset, f, content)
return protocol.NewColumnMapper(uri, fset, f, content), nil
}
func TestBytesOffset(t *testing.T) {

View File

@ -107,8 +107,8 @@ func (c *completer) formatBuiltin(obj types.Object, score float64) CompletionIte
item.Kind = ConstantCompletionItem
case *types.Builtin:
item.Kind = FunctionCompletionItem
decl := lookupBuiltin(c.view, obj.Name())
if decl == nil {
decl, ok := lookupBuiltinDecl(c.view, obj.Name()).(*ast.FuncDecl)
if !ok {
break
}
params, _ := formatFieldList(c.ctx, c.view, decl.Type.Params)
@ -127,22 +127,6 @@ func (c *completer) formatBuiltin(obj types.Object, score float64) CompletionIte
return item
}
func lookupBuiltin(v View, name string) *ast.FuncDecl {
builtinPkg := v.BuiltinPackage()
if builtinPkg == nil || builtinPkg.Scope == nil {
return nil
}
fn := builtinPkg.Scope.Lookup(name)
if fn == nil {
return nil
}
decl, ok := fn.Decl.(*ast.FuncDecl)
if !ok {
return nil
}
return decl
}
var replacer = strings.NewReplacer(
`ComplexType`, `complex128`,
`FloatType`, `float64`,

View File

@ -66,17 +66,25 @@ func Diagnostics(ctx context.Context, v View, uri span.URI) (map[span.URI][]Diag
// Prepare the reports we will send for this package.
reports := make(map[span.URI][]Diagnostic)
for _, filename := range pkg.GetFilenames() {
reports[span.FileURI(filename)] = []Diagnostic{}
uri := span.FileURI(filename)
if v.Ignore(uri) {
continue
}
reports[uri] = []Diagnostic{}
}
// Run diagnostics for the package that this URI belongs to.
if !diagnostics(ctx, v, pkg, reports) {
// If we don't have any list, parse, or type errors, run analyses.
if err := analyses(ctx, v, pkg, reports); err != nil {
return singleDiagnostic(uri, "failed to run analyses for %s", uri), nil
return singleDiagnostic(uri, "failed to run analyses for %s: %v", uri, err), nil
}
}
// Updates to the diagnostics for this package may need to be propagated.
for _, f := range gof.GetActiveReverseDeps(ctx) {
if f == nil {
v.Session().Logger().Errorf(ctx, "nil file in reverse active dependencies for %s", f.URI())
continue
}
pkg := f.GetPackage(ctx)
if pkg == nil {
continue

View File

@ -38,9 +38,19 @@ func (i *IdentifierInfo) Hover(ctx context.Context, qf types.Qualifier, markdown
case *types.TypeName, *types.Var, *types.Const, *types.Func:
return formatGenDecl(node, obj, obj.Type(), f)
}
case *ast.TypeSpec:
if obj.Parent() == types.Universe {
if obj.Name() == "error" {
return f(node, nil)
}
return f(node.Name, nil) // comments not needed for builtins
}
case *ast.FuncDecl:
if _, ok := obj.(*types.Func); ok {
switch obj.(type) {
case *types.Func:
return f(obj, node.Doc)
case *types.Builtin:
return f(node.Type, node.Doc)
}
}
return f(obj, nil)

View File

@ -27,7 +27,7 @@ type IdentifierInfo struct {
}
Declaration struct {
Range span.Range
Node ast.Decl
Node ast.Node
Object types.Object
}
@ -64,15 +64,12 @@ func identifier(ctx context.Context, v View, f GoFile, pos token.Pos) (*Identifi
return nil, fmt.Errorf("can't find node enclosing position")
}
// Handle import specs first because they can contain *ast.Idents, and
// we don't want the default *ast.Ident behavior below.
if result, err := checkImportSpec(f, fAST, pkg, pos); result != nil || err != nil {
// Handle import specs separately, as there is no formal position for a package declaration.
if result, err := importSpec(f, fAST, pkg, pos); result != nil || err != nil {
return result, err
}
result := &IdentifierInfo{
File: f,
}
result := &IdentifierInfo{File: f}
switch node := path[0].(type) {
case *ast.Ident:
@ -95,6 +92,22 @@ func identifier(ctx context.Context, v View, f GoFile, pos token.Pos) (*Identifi
if result.Declaration.Object == nil {
return nil, fmt.Errorf("no object for ident %v", result.Name)
}
var err error
// Handle builtins separately.
if result.Declaration.Object.Parent() == types.Universe {
decl, ok := lookupBuiltinDecl(f.View(), result.Name).(ast.Node)
if !ok {
return nil, fmt.Errorf("no declaration for %s", result.Name)
}
result.Declaration.Node = decl
if result.Declaration.Range, err = posToRange(ctx, v.FileSet(), result.Name, decl.Pos()); err != nil {
return nil, err
}
return result, nil
}
if result.wasEmbeddedField {
// The original position was on the embedded field declaration, so we
// try to dig out the type and jump to that instead.
@ -104,8 +117,8 @@ func identifier(ctx context.Context, v View, f GoFile, pos token.Pos) (*Identifi
}
}
}
var err error
if result.Declaration.Range, err = objToRange(ctx, v, result.Declaration.Object); err != nil {
if result.Declaration.Range, err = objToRange(ctx, v.FileSet(), result.Declaration.Object); err != nil {
return nil, err
}
if result.Declaration.Node, err = objToNode(ctx, v, result.Declaration.Object, result.Declaration.Range); err != nil {
@ -118,68 +131,16 @@ func identifier(ctx context.Context, v View, f GoFile, pos token.Pos) (*Identifi
result.Type.Object = typeToObject(typ)
if result.Type.Object != nil {
// Identifiers with the type "error" are a special case with no position.
if types.IsInterface(result.Type.Object.Type()) && result.Type.Object.Pkg() == nil && result.Type.Object.Name() == "error" {
if hasErrorType(result.Type.Object) {
return result, nil
}
if result.Type.Range, err = objToRange(ctx, v, result.Type.Object); err != nil {
if result.Type.Range, err = objToRange(ctx, v.FileSet(), result.Type.Object); err != nil {
return nil, err
}
}
return result, nil
}
func checkImportSpec(f GoFile, fAST *ast.File, pkg Package, pos token.Pos) (*IdentifierInfo, error) {
// Check if pos is in an *ast.ImportSpec.
for _, imp := range fAST.Imports {
if imp.Pos() <= pos && pos < imp.End() {
pkgPath, err := strconv.Unquote(imp.Path.Value)
if err != nil {
return nil, fmt.Errorf("import path not quoted: %s (%v)", imp.Path.Value, err)
}
result := &IdentifierInfo{
File: f,
Name: pkgPath,
Range: span.NewRange(f.View().FileSet(), imp.Pos(), imp.End()),
}
// Consider the definition of an import spec to be the imported package.
result.Declaration.Range, err = importedPkg(f.View(), pkg, pkgPath)
if err != nil {
return nil, err
}
return result, nil
}
}
return nil, nil
}
func importedPkg(v View, pkg Package, importPath string) (span.Range, error) {
otherPkg := pkg.GetImport(importPath)
if otherPkg == nil {
return span.Range{}, fmt.Errorf("no import for %q", importPath)
}
if otherPkg.GetSyntax() == nil {
return span.Range{}, fmt.Errorf("no syntax for for %q", importPath)
}
// Heuristic: Jump to the longest file of the package, assuming it's the most "interesting."
// TODO: Consider alternative approaches, if necessary.
var longest *ast.File
for _, astFile := range otherPkg.GetSyntax() {
if longest == nil || astFile.End()-astFile.Pos() > longest.End()-longest.Pos() {
longest = astFile
}
}
if longest == nil {
return span.Range{}, fmt.Errorf("package %q has no files", importPath)
}
return span.NewRange(v.FileSet(), longest.Name.Pos(), longest.Name.End()), nil
}
func typeToObject(typ types.Type) types.Object {
switch typ := typ.(type) {
case *types.Named:
@ -191,12 +152,19 @@ func typeToObject(typ types.Type) types.Object {
}
}
func objToRange(ctx context.Context, v View, obj types.Object) (span.Range, error) {
p := obj.Pos()
if !p.IsValid() {
return span.Range{}, fmt.Errorf("invalid position for %v", obj.Name())
func hasErrorType(obj types.Object) bool {
return types.IsInterface(obj.Type()) && obj.Pkg() == nil && obj.Name() == "error"
}
func objToRange(ctx context.Context, fset *token.FileSet, obj types.Object) (span.Range, error) {
return posToRange(ctx, fset, obj.Name(), obj.Pos())
}
func posToRange(ctx context.Context, fset *token.FileSet, name string, pos token.Pos) (span.Range, error) {
if !pos.IsValid() {
return span.Range{}, fmt.Errorf("invalid position for %v", name)
}
return span.NewRange(v.FileSet(), p, p+token.Pos(len(obj.Name()))), nil
return span.NewRange(fset, pos, pos+token.Pos(len(name))), nil
}
func objToNode(ctx context.Context, v View, obj types.Object, rng span.Range) (ast.Decl, error) {
@ -234,3 +202,42 @@ func objToNode(ctx context.Context, v View, obj types.Object, rng span.Range) (a
}
return nil, nil // didn't find a node, but don't fail
}
// importSpec handles positions inside of an *ast.ImportSpec.
func importSpec(f GoFile, fAST *ast.File, pkg Package, pos token.Pos) (*IdentifierInfo, error) {
for _, imp := range fAST.Imports {
if !(imp.Pos() <= pos && pos < imp.End()) {
continue
}
importPath, err := strconv.Unquote(imp.Path.Value)
if err != nil {
return nil, fmt.Errorf("import path not quoted: %s (%v)", imp.Path.Value, err)
}
result := &IdentifierInfo{
File: f,
Name: importPath,
Range: span.NewRange(f.View().FileSet(), imp.Pos(), imp.End()),
}
// Consider the "declaration" of an import spec to be the imported package.
importedPkg := pkg.GetImport(importPath)
if importedPkg == nil {
return nil, fmt.Errorf("no import for %q", importPath)
}
if importedPkg.GetSyntax() == nil {
return nil, fmt.Errorf("no syntax for for %q", importPath)
}
// Heuristic: Jump to the longest (most "interesting") file of the package.
var dest *ast.File
for _, f := range importedPkg.GetSyntax() {
if dest == nil || f.End()-f.Pos() > dest.End()-dest.Pos() {
dest = f
}
}
if dest == nil {
return nil, fmt.Errorf("package %q has no files", importPath)
}
result.Declaration.Range = span.NewRange(f.View().FileSet(), dest.Name.Pos(), dest.Name.End())
return result, nil
}
return nil, nil
}

View File

@ -89,8 +89,8 @@ func SignatureHelp(ctx context.Context, f GoFile, pos token.Pos) (*SignatureInfo
}
func builtinSignature(ctx context.Context, v View, callExpr *ast.CallExpr, name string, pos token.Pos) (*SignatureInformation, error) {
decl := lookupBuiltin(v, name)
if decl == nil {
decl, ok := lookupBuiltinDecl(v, name).(*ast.FuncDecl)
if !ok {
return nil, fmt.Errorf("no function declaration for builtin: %s", name)
}
params, _ := formatFieldList(ctx, v, decl.Type.Params)

View File

@ -310,11 +310,6 @@ func (r *runner) Definition(t *testing.T, data tests.Definitions) {
rng = ident.Type.Range
hover = ""
}
if def, err := rng.Span(); err != nil {
t.Fatalf("failed for %v: %v", rng, err)
} else if def != d.Def {
t.Errorf("for %v got %v want %v", d.Src, def, d.Def)
}
if hover != "" {
tag := fmt.Sprintf("%s-hover", d.Name)
filename, err := d.Src.URI().Filename()
@ -327,6 +322,14 @@ func (r *runner) Definition(t *testing.T, data tests.Definitions) {
if hover != expectHover {
t.Errorf("for %v got %q want %q", d.Src, hover, expectHover)
}
} else if !d.OnlyHover {
if def, err := rng.Span(); err != nil {
t.Fatalf("failed for %v: %v", rng, err)
} else if def != d.Def {
t.Errorf("for %v got %v want %v", d.Src, def, d.Def)
}
} else {
t.Errorf("no tests ran for %s", d.Src.URI())
}
}
}

View File

@ -102,6 +102,18 @@ func resolveInvalid(obj types.Object, node ast.Node, info *types.Info) types.Obj
return formatResult(resultExpr)
}
func lookupBuiltinDecl(v View, name string) interface{} {
builtinPkg := v.BuiltinPackage()
if builtinPkg == nil || builtinPkg.Scope == nil {
return nil
}
obj := builtinPkg.Scope.Lookup(name)
if obj == nil {
return nil
}
return obj.Decl
}
func isPointer(T types.Type) bool {
_, ok := T.(*types.Pointer)
return ok

View File

@ -67,6 +67,7 @@ type View interface {
Config() packages.Config
SetEnv([]string)
Shutdown(ctx context.Context)
Ignore(span.URI) bool
}
// File represents a source file of any type.

View File

@ -13,4 +13,7 @@ func Stuff() { //@Stuff
var err error //@err
fmt.Printf("%v", err) //@godef("err", err)
var y string //@string,hover("string", string)
_ = make([]int, 0) //@make,hover("make", make)
}

View File

@ -67,3 +67,8 @@ godef/a/a.go:14:6-9: defined here as var err error
-- err-hover --
var err error
-- string-hover --
string
-- make-hover --
The make built-in function allocates and initializes an object of type slice, map, or chan (only).
func(t Type, size ...IntegerType) Type

View File

@ -32,7 +32,7 @@ const (
ExpectedCompletionSnippetCount = 13
ExpectedDiagnosticsCount = 17
ExpectedFormatCount = 5
ExpectedDefinitionsCount = 33
ExpectedDefinitionsCount = 35
ExpectedTypeDefinitionsCount = 2
ExpectedHighlightsCount = 2
ExpectedSymbolsCount = 1
@ -94,10 +94,11 @@ type Tests interface {
}
type Definition struct {
Name string
Src span.Span
IsType bool
Def span.Span
Name string
Src span.Span
IsType bool
OnlyHover bool
Def span.Span
}
type CompletionSnippet struct {
@ -203,6 +204,7 @@ func Load(t testing.TB, exporter packagestest.Exporter, dir string) *Data {
"format": data.collectFormats,
"godef": data.collectDefinitions,
"typdef": data.collectTypeDefinitions,
"hover": data.collectHoverDefinitions,
"highlight": data.collectHighlights,
"symbol": data.collectSymbols,
"signature": data.collectSignatures,
@ -217,9 +219,10 @@ func Load(t testing.TB, exporter packagestest.Exporter, dir string) *Data {
symbols[i].Children = children
}
}
// run a second pass to collect names for some entries.
// Collect names for the entries that require golden files.
if err := data.Exported.Expect(map[string]interface{}{
"godef": data.collectDefinitionNames,
"hover": data.collectDefinitionNames,
}); err != nil {
t.Fatal(err)
}
@ -420,6 +423,14 @@ func (data *Data) collectDefinitions(src, target span.Span) {
}
}
func (data *Data) collectHoverDefinitions(src, target span.Span) {
data.Definitions[src] = Definition{
Src: src,
Def: target,
OnlyHover: true,
}
}
func (data *Data) collectTypeDefinitions(src, target span.Span) {
data.Definitions[src] = Definition{
Src: src,