Compare commits
2 Commits
e3c84fcf60
...
94ee7e4880
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94ee7e4880 | ||
|
|
ce9b68200e |
@@ -723,6 +723,82 @@ func suggestFieldValues(container *index.ProjectNode, field string, path string)
|
||||
if field == "Functions" {
|
||||
return suggestObjects(root, "GAM")
|
||||
}
|
||||
if field == "Type" {
|
||||
return suggestSignalTypes()
|
||||
}
|
||||
|
||||
if list := suggestCUEEnums(container, field); list != nil {
|
||||
return list
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func suggestSignalTypes() *CompletionList {
|
||||
types := []string{
|
||||
"uint8", "int8", "uint16", "int16", "uint32", "int32", "uint64", "int64",
|
||||
"float32", "float64", "string", "bool", "char8",
|
||||
}
|
||||
var items []CompletionItem
|
||||
for _, t := range types {
|
||||
items = append(items, CompletionItem{
|
||||
Label: t,
|
||||
Kind: 13, // EnumMember
|
||||
Detail: "Signal Type",
|
||||
})
|
||||
}
|
||||
return &CompletionList{Items: items}
|
||||
}
|
||||
|
||||
func suggestCUEEnums(container *index.ProjectNode, field string) *CompletionList {
|
||||
if GlobalSchema == nil {
|
||||
return nil
|
||||
}
|
||||
cls := container.Metadata["Class"]
|
||||
if cls == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
classPath := cue.ParsePath(fmt.Sprintf("#Classes.%s.%s", cls, field))
|
||||
val := GlobalSchema.Value.LookupPath(classPath)
|
||||
if val.Err() != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
op, args := val.Expr()
|
||||
var values []cue.Value
|
||||
if op == cue.OrOp {
|
||||
values = args
|
||||
} else {
|
||||
values = []cue.Value{val}
|
||||
}
|
||||
|
||||
var items []CompletionItem
|
||||
for _, v := range values {
|
||||
if !v.IsConcrete() {
|
||||
continue
|
||||
}
|
||||
|
||||
str, err := v.String() // Returns quoted string for string values?
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure strings are quoted
|
||||
if v.Kind() == cue.StringKind && !strings.HasPrefix(str, "\"") {
|
||||
str = fmt.Sprintf("\"%s\"", str)
|
||||
}
|
||||
|
||||
items = append(items, CompletionItem{
|
||||
Label: str,
|
||||
Kind: 13, // EnumMember
|
||||
Detail: "Enum Value",
|
||||
})
|
||||
}
|
||||
|
||||
if len(items) > 0 {
|
||||
return &CompletionList{Items: items}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -208,7 +208,7 @@ func (p *Parser) parseSubnode() (Subnode, bool) {
|
||||
if t.Type == TokenEOF {
|
||||
p.addError(t.Position, "unexpected EOF, expected }")
|
||||
sub.EndPosition = t.Position
|
||||
return sub, false
|
||||
return sub, true
|
||||
}
|
||||
def, ok := p.parseDefinition()
|
||||
if ok {
|
||||
|
||||
@@ -228,4 +228,93 @@ $App = {
|
||||
t.Error("Expected ProjectDS in project file suggestions")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Suggest Signal Types", func(t *testing.T) {
|
||||
setup()
|
||||
content := `
|
||||
+DS = {
|
||||
Class = FileReader
|
||||
Signals = {
|
||||
S1 = { Type = }
|
||||
}
|
||||
}
|
||||
`
|
||||
lsp.Documents[uri] = content
|
||||
p := parser.NewParser(content)
|
||||
cfg, _ := p.Parse()
|
||||
lsp.Tree.AddFile(path, cfg)
|
||||
|
||||
params := lsp.CompletionParams{
|
||||
TextDocument: lsp.TextDocumentIdentifier{URI: uri},
|
||||
Position: lsp.Position{Line: 4, Character: strings.Index(content, "Type = ") + len("Type = ") + 1},
|
||||
}
|
||||
|
||||
list := lsp.HandleCompletion(params)
|
||||
if list == nil {
|
||||
t.Fatal("Expected signal type suggestions")
|
||||
}
|
||||
|
||||
foundUint32 := false
|
||||
for _, item := range list.Items {
|
||||
if item.Label == "uint32" {
|
||||
foundUint32 = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundUint32 {
|
||||
t.Error("Expected uint32 in suggestions")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Suggest CUE Enums", func(t *testing.T) {
|
||||
setup()
|
||||
// Inject custom schema with enum
|
||||
custom := []byte(`
|
||||
package schema
|
||||
#Classes: {
|
||||
TestEnumClass: {
|
||||
Mode: "Auto" | "Manual"
|
||||
}
|
||||
}
|
||||
`)
|
||||
val := lsp.GlobalSchema.Context.CompileBytes(custom)
|
||||
lsp.GlobalSchema.Value = lsp.GlobalSchema.Value.Unify(val)
|
||||
|
||||
content := `
|
||||
+Obj = {
|
||||
Class = TestEnumClass
|
||||
Mode =
|
||||
}
|
||||
`
|
||||
lsp.Documents[uri] = content
|
||||
p := parser.NewParser(content)
|
||||
cfg, _ := p.Parse()
|
||||
lsp.Tree.AddFile(path, cfg)
|
||||
|
||||
params := lsp.CompletionParams{
|
||||
TextDocument: lsp.TextDocumentIdentifier{URI: uri},
|
||||
Position: lsp.Position{Line: 3, Character: strings.Index(content, "Mode = ") + len("Mode = ") + 1},
|
||||
}
|
||||
|
||||
list := lsp.HandleCompletion(params)
|
||||
if list == nil {
|
||||
t.Fatal("Expected enum suggestions")
|
||||
}
|
||||
|
||||
foundAuto := false
|
||||
for _, item := range list.Items {
|
||||
if item.Label == "\"Auto\"" { // CUE string value includes quotes
|
||||
foundAuto = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundAuto {
|
||||
// Check if it returned without quotes?
|
||||
// v.String() returns quoted for string.
|
||||
t.Error("Expected \"Auto\" in suggestions")
|
||||
for _, item := range list.Items {
|
||||
t.Logf("Suggestion: %s", item.Label)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
85
test/validator_gam_direction_test.go
Normal file
85
test/validator_gam_direction_test.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/marte-community/marte-dev-tools/internal/index"
|
||||
"github.com/marte-community/marte-dev-tools/internal/parser"
|
||||
"github.com/marte-community/marte-dev-tools/internal/validator"
|
||||
)
|
||||
|
||||
func TestGAMSignalDirectionality(t *testing.T) {
|
||||
content := `
|
||||
$App = {
|
||||
$Data = {
|
||||
+InDS = { Class = FileReader Filename="f" +Signals = { S1 = { Type = uint32 } } }
|
||||
+OutDS = { Class = FileWriter Filename="f" +Signals = { S1 = { Type = uint32 } } }
|
||||
+InOutDS = { Class = FileDataSource Filename="f" +Signals = { S1 = { Type = uint32 } } }
|
||||
}
|
||||
+ValidGAM = {
|
||||
Class = IOGAM
|
||||
InputSignals = {
|
||||
S1 = { DataSource = InDS }
|
||||
S2 = { DataSource = InOutDS Alias = S1 }
|
||||
}
|
||||
OutputSignals = {
|
||||
S3 = { DataSource = OutDS Alias = S1 }
|
||||
S4 = { DataSource = InOutDS Alias = S1 }
|
||||
}
|
||||
}
|
||||
+InvalidGAM = {
|
||||
Class = IOGAM
|
||||
InputSignals = {
|
||||
BadIn = { DataSource = OutDS Alias = S1 }
|
||||
}
|
||||
OutputSignals = {
|
||||
BadOut = { DataSource = InDS Alias = S1 }
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
p := parser.NewParser(content)
|
||||
config, err := p.Parse()
|
||||
if err != nil {
|
||||
t.Fatalf("Parse failed: %v", err)
|
||||
}
|
||||
|
||||
idx := index.NewProjectTree()
|
||||
idx.AddFile("dir.marte", config)
|
||||
idx.ResolveReferences()
|
||||
|
||||
v := validator.NewValidator(idx, ".")
|
||||
v.ValidateProject()
|
||||
|
||||
// Check ValidGAM has NO directionality errors
|
||||
for _, d := range v.Diagnostics {
|
||||
if strings.Contains(d.Message, "is Output-only but referenced in InputSignals") ||
|
||||
strings.Contains(d.Message, "is Input-only but referenced in OutputSignals") {
|
||||
if strings.Contains(d.Message, "ValidGAM") {
|
||||
t.Errorf("Unexpected direction error for ValidGAM: %s", d.Message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check InvalidGAM HAS errors
|
||||
foundBadIn := false
|
||||
foundBadOut := false
|
||||
for _, d := range v.Diagnostics {
|
||||
if strings.Contains(d.Message, "InvalidGAM") {
|
||||
if strings.Contains(d.Message, "is Output-only but referenced in InputSignals") {
|
||||
foundBadIn = true
|
||||
}
|
||||
if strings.Contains(d.Message, "is Input-only but referenced in OutputSignals") {
|
||||
foundBadOut = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !foundBadIn {
|
||||
t.Error("Expected error for OutDS in InputSignals of InvalidGAM")
|
||||
}
|
||||
if !foundBadOut {
|
||||
t.Error("Expected error for InDS in OutputSignals of InvalidGAM")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user