From 52ad8668ea3710919c85bbd90365f46f9d01b9c6 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Braun Date: Fri, 29 Jul 2022 16:41:59 +0200 Subject: [PATCH] Support import aliases It doesn't seem possible to get package aliases from cue's build instances. They only seem to be used internally in the adt and not exported in the public API. Therefore the patch relies on the ast to record package aliases by loaded file. When the selector of a definition isn't found we make some extra calls to see if it's a package alias in the current file and find the associated instance. Closes #79 --- file/file.go | 22 +++++++++--- parser/definition.go | 10 +++++- parser/definition_test.go | 6 ++-- plan/plan.go | 36 +++++++++++++++++-- plan/plan_test.go | 22 ++++++------ .../cue.mod/pkg/test.com/test2/test2.cue | 7 ++++ plan/testdata/with-cue-mod/main.cue | 3 +- 7 files changed, 85 insertions(+), 21 deletions(-) create mode 100644 plan/testdata/with-cue-mod/cue.mod/pkg/test.com/test2/test2.cue diff --git a/file/file.go b/file/file.go index 1527f6b..580d5a6 100644 --- a/file/file.go +++ b/file/file.go @@ -19,6 +19,10 @@ type File struct { // Definitions of the file defs *parser.Definitions + + // Package aliases of the file + // Maps alias to import path + importAliases map[string]string } // New create a File and analise CUE ast in it. @@ -29,15 +33,25 @@ func New(path string) (*File, error) { } defs := parser.Definitions{} - parser.ParseDefs(&defs, content) + importAliases := make(map[string]string) + parser.ParseDefs(&defs, importAliases, content) return &File{ - path: path, - content: content, - defs: &defs, + path: path, + content: content, + defs: &defs, + importAliases: importAliases, }, nil } +// AliasImportPath returns the import path of some package alias. +func (f *File) AliasImportPath(alias string) (string, bool) { + if importPath, ok := f.importAliases[alias]; ok { + return importPath, true + } + return "", false +} + func (f *File) String() string { return fmt.Sprintf("%s,%s", f.path, f.defs) } diff --git a/parser/definition.go b/parser/definition.go index c6d5492..e1faf57 100644 --- a/parser/definition.go +++ b/parser/definition.go @@ -3,6 +3,7 @@ package parser import ( "fmt" "sort" + "strconv" "cuelang.org/go/cue/ast" "cuelang.org/go/cue/token" @@ -54,7 +55,7 @@ func (def Definitions) Find(line int, column int) (string, error) { // - Ident: those are definitions from the package itself // - SelectorExpr: those are definitions from external package // they will be stored as . (E.g., foo.#Bar) -func ParseDefs(defs *Definitions, f *ast.File) { +func ParseDefs(defs *Definitions, importAliases map[string]string, f *ast.File) { ast.Walk(f, func(node ast.Node) bool { switch v := node.(type) { // case: #Def @@ -75,7 +76,14 @@ func ParseDefs(defs *Definitions, f *ast.File) { defs.AppendRange(definitionName, pkg.Pos(), v.Sel.End()) return false } + + case *ast.ImportSpec: + if v.Name != nil { + importPath, _ := strconv.Unquote(v.Path.Value) + importAliases[v.Name.String()] = importPath + } } + return true }, nil) } diff --git a/parser/definition_test.go b/parser/definition_test.go index cdf262d..593ab63 100644 --- a/parser/definition_test.go +++ b/parser/definition_test.go @@ -116,7 +116,8 @@ func TestDefinitionParsing(t *testing.T) { } defs := Definitions{} - ParseDefs(&defs, f) + importAliases := make(map[string]string) + ParseDefs(&defs, importAliases, f) output := defs.String() for _, o := range tc.output { require.Contains(t, output, o) @@ -177,7 +178,8 @@ func TestFindDefinition(t *testing.T) { } defs := Definitions{} - ParseDefs(&defs, f) + importAliases := make(map[string]string) + ParseDefs(&defs, importAliases, f) name, err := defs.Find(tc.input.line, tc.input.col) if err != nil { diff --git a/plan/plan.go b/plan/plan.go index 5e35196..6abc71a 100644 --- a/plan/plan.go +++ b/plan/plan.go @@ -1,6 +1,7 @@ package plan import ( + "errors" "fmt" "path/filepath" "sync" @@ -143,7 +144,9 @@ func (p *Plan) GetDefinition(path string, line, char int) (*loader.Value, error) } else { i, found := p.imports[def.Pkg()] if !found { - return nil, fmt.Errorf("imported package %s not registered in plan", def.Def()) + if i, err = p.findInstanceAlias(path, def.Pkg()); err != nil { + return nil, fmt.Errorf("imported package %s not registered in plan", def.Def()) + } } return i.GetDefinition(def.Def()) @@ -186,13 +189,38 @@ func (p *Plan) GetInstance(path string, line, char int) (*loader.Instance, error } else { i, found := p.imports[def.Pkg()] if !found { - return nil, fmt.Errorf("imported package %s not registered in plan", def.Def()) + if i, err = p.findInstanceAlias(path, def.Pkg()); err != nil { + return nil, fmt.Errorf("imported package %s not registered in plan", def.Def()) + } } return i, nil } } +// findInstanceAlias returns the instance of a package alias +func (p *Plan) findInstanceAlias(path string, pkgAlias string) (*loader.Instance, error) { + p.log.Debugf("Looking for pkg alias: %s", pkgAlias) + + p.muFiles.RLock() + defer p.muFiles.RUnlock() + + f, ok := p.files[path] + if !ok { + return nil, fmt.Errorf("file not registered") + } + + if importPath, ok := f.AliasImportPath(pkgAlias); ok { + for _, i := range p.imports { + if i.ImportPath == importPath { + return i, nil + } + } + return nil, fmt.Errorf("no instance found for %s", importPath) + } + return nil, errors.New("not an alias") +} + func (p *Plan) findDefInFile(path string, line, char int) (*internal.Definition, error) { p.log.Debugf("Looking for file: %s", path) @@ -243,7 +271,9 @@ func (p *Plan) GetDoc(path string, line, char int) (*loader.Value, error) { } else { i, found := p.imports[_def.Pkg()] if !found { - return nil, fmt.Errorf("imported package %s not registered in plan", _def.Def()) + if i, err = p.findInstanceAlias(path, _def.Pkg()); err != nil { + return nil, fmt.Errorf("imported package %s not registered in plan", _def.Def()) + } } return i.GetValue() diff --git a/plan/plan_test.go b/plan/plan_test.go index 12e00b1..47935b5 100644 --- a/plan/plan_test.go +++ b/plan/plan_test.go @@ -52,7 +52,8 @@ func TestNew(t *testing.T) { RootFilePath: "main.cue", Kind: File, imports: map[string]*loader.Instance{ - "test": nil, + "test": nil, + "test2": nil, }, }, }, @@ -176,22 +177,22 @@ func TestPlan_GetDefinition(t *testing.T) { defs: []Def{ { path: "_#TestName", - line: 7, + line: 8, char: 1, }, { path: "#Test", - line: 9, + line: 10, char: 9, }, { path: "_#TestName", - line: 15, + line: 16, char: 12, }, { path: "#Test", - line: 14, + line: 15, char: 15, }, }, @@ -365,19 +366,19 @@ func TestPlan_GetInstance(t *testing.T) { file: "main.cue", defs: []Def{ { - line: 7, + line: 8, char: 1, }, { - line: 9, + line: 10, char: 9, }, { - line: 15, + line: 16, char: 12, }, { - line: 14, + line: 15, char: 15, }, }, @@ -500,7 +501,8 @@ func TestPlan_Reload(t *testing.T) { RootFilePath: "main.cue", Kind: File, imports: map[string]*loader.Instance{ - "test": nil, + "test": nil, + "test2": nil, }, }, }, diff --git a/plan/testdata/with-cue-mod/cue.mod/pkg/test.com/test2/test2.cue b/plan/testdata/with-cue-mod/cue.mod/pkg/test.com/test2/test2.cue new file mode 100644 index 0000000..d79e393 --- /dev/null +++ b/plan/testdata/with-cue-mod/cue.mod/pkg/test.com/test2/test2.cue @@ -0,0 +1,7 @@ +package test2 + +#Test: { + name: string + + assert: string +} diff --git a/plan/testdata/with-cue-mod/main.cue b/plan/testdata/with-cue-mod/main.cue index a73d415..66314b5 100644 --- a/plan/testdata/with-cue-mod/main.cue +++ b/plan/testdata/with-cue-mod/main.cue @@ -2,6 +2,7 @@ package main import ( "test.com/test" + t "test.com/test2" ) _#TestName: =~"test" @@ -11,7 +12,7 @@ test1: test.#Test & { assert: "it's the first test" } -test2: test.#Test & { +test2: t.#Test & { name: _#TestName & "test 2" assert: "it's the second test" }