Improved scoping
This commit is contained in:
@@ -427,11 +427,8 @@ func (pt *ProjectTree) ResolveReferences() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if isoNode, ok := pt.IsolatedFiles[ref.File]; ok {
|
container := pt.GetNodeContaining(ref.File, ref.Position)
|
||||||
ref.Target = pt.FindNode(isoNode, ref.Name, nil)
|
ref.Target = pt.resolveScopedName(container, ref.Name)
|
||||||
} else {
|
|
||||||
ref.Target = pt.FindNode(pt.Root, ref.Name, nil)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -612,3 +609,45 @@ func (pt *ProjectTree) findNodeContaining(node *ProjectNode, file string, pos pa
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pt *ProjectTree) resolveScopedName(ctx *ProjectNode, name string) *ProjectNode {
|
||||||
|
if ctx == nil {
|
||||||
|
return pt.FindNode(pt.Root, name, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(name, ".")
|
||||||
|
first := parts[0]
|
||||||
|
normFirst := NormalizeName(first)
|
||||||
|
|
||||||
|
var startNode *ProjectNode
|
||||||
|
curr := ctx
|
||||||
|
|
||||||
|
for curr != nil {
|
||||||
|
if child, ok := curr.Children[normFirst]; ok {
|
||||||
|
startNode = child
|
||||||
|
break
|
||||||
|
}
|
||||||
|
curr = curr.Parent
|
||||||
|
}
|
||||||
|
|
||||||
|
if startNode == nil && ctx != pt.Root {
|
||||||
|
if child, ok := pt.Root.Children[normFirst]; ok {
|
||||||
|
startNode = child
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if startNode == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
curr = startNode
|
||||||
|
for i := 1; i < len(parts); i++ {
|
||||||
|
norm := NormalizeName(parts[i])
|
||||||
|
if child, ok := curr.Children[norm]; ok {
|
||||||
|
curr = child
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return curr
|
||||||
|
}
|
||||||
|
|||||||
@@ -221,6 +221,14 @@ func (v *Validator) valueToInterface(val parser.Value) interface{} {
|
|||||||
return t.Value
|
return t.Value
|
||||||
case *parser.ReferenceValue:
|
case *parser.ReferenceValue:
|
||||||
return t.Value
|
return t.Value
|
||||||
|
case *parser.VariableReferenceValue:
|
||||||
|
name := strings.TrimPrefix(t.Name, "$")
|
||||||
|
if info, ok := v.Tree.Variables[name]; ok {
|
||||||
|
if info.Def.DefaultValue != nil {
|
||||||
|
return v.valueToInterface(info.Def.DefaultValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
case *parser.ArrayValue:
|
case *parser.ArrayValue:
|
||||||
var arr []interface{}
|
var arr []interface{}
|
||||||
for _, e := range t.Elements {
|
for _, e := range t.Elements {
|
||||||
|
|||||||
65
test/scoping_test.go
Normal file
65
test/scoping_test.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/marte-community/marte-dev-tools/internal/index"
|
||||||
|
"github.com/marte-community/marte-dev-tools/internal/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNameScoping(t *testing.T) {
|
||||||
|
// App1 = { A = { Data = 10 } B = { Ref = A } }
|
||||||
|
// App2 = { C = { Data = 10 } A = { Data = 12 } D = { Ref = A } }
|
||||||
|
|
||||||
|
content := `
|
||||||
|
+App1 = {
|
||||||
|
Class = App
|
||||||
|
+A = { Class = Node Data = 10 }
|
||||||
|
+B = { Class = Node Ref = A }
|
||||||
|
}
|
||||||
|
+App2 = {
|
||||||
|
Class = App
|
||||||
|
+C = { Class = Node Data = 10 }
|
||||||
|
+A = { Class = Node Data = 12 }
|
||||||
|
+D = { Class = Node Ref = A }
|
||||||
|
}
|
||||||
|
`
|
||||||
|
pt := index.NewProjectTree()
|
||||||
|
p := parser.NewParser(content)
|
||||||
|
cfg, err := p.Parse()
|
||||||
|
if err != nil { t.Fatal(err) }
|
||||||
|
pt.AddFile("main.marte", cfg)
|
||||||
|
|
||||||
|
pt.ResolveReferences()
|
||||||
|
|
||||||
|
// Helper to find ref target
|
||||||
|
findRefTarget := func(refName string, containerName string) *index.ProjectNode {
|
||||||
|
for _, ref := range pt.References {
|
||||||
|
if ref.Name == refName {
|
||||||
|
container := pt.GetNodeContaining(ref.File, ref.Position)
|
||||||
|
if container != nil && container.RealName == containerName {
|
||||||
|
return ref.Target
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
targetB := findRefTarget("A", "+B")
|
||||||
|
if targetB == nil {
|
||||||
|
t.Fatal("Could not find reference A in +B")
|
||||||
|
}
|
||||||
|
// Check if targetB is App1.A
|
||||||
|
if targetB.Parent == nil || targetB.Parent.RealName != "+App1" {
|
||||||
|
t.Errorf("App1.B.Ref resolved to wrong target: %v (Parent %v)", targetB.RealName, targetB.Parent.RealName)
|
||||||
|
}
|
||||||
|
|
||||||
|
targetD := findRefTarget("A", "+D")
|
||||||
|
if targetD == nil {
|
||||||
|
t.Fatal("Could not find reference A in +D")
|
||||||
|
}
|
||||||
|
// Check if targetD is App2.A
|
||||||
|
if targetD.Parent == nil || targetD.Parent.RealName != "+App2" {
|
||||||
|
t.Errorf("App2.D.Ref resolved to wrong target: %v (Parent %v)", targetD.RealName, targetD.Parent.RealName)
|
||||||
|
}
|
||||||
|
}
|
||||||
101
test/validator_variable_usage_test.go
Normal file
101
test/validator_variable_usage_test.go
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
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 TestVariableValidation(t *testing.T) {
|
||||||
|
// Need a schema that enforces strict types to test usage validation.
|
||||||
|
// We can use built-in types or rely on Variable Definition validation.
|
||||||
|
|
||||||
|
// Test Case 1: Variable Definition Mismatch
|
||||||
|
contentDef := `
|
||||||
|
#var Positive: uint = -5
|
||||||
|
`
|
||||||
|
pt := index.NewProjectTree()
|
||||||
|
p := parser.NewParser(contentDef)
|
||||||
|
cfg, err := p.Parse()
|
||||||
|
if err != nil { t.Fatal(err) }
|
||||||
|
pt.AddFile("def.marte", cfg)
|
||||||
|
|
||||||
|
v := validator.NewValidator(pt, ".")
|
||||||
|
v.CheckVariables()
|
||||||
|
|
||||||
|
foundError := false
|
||||||
|
for _, d := range v.Diagnostics {
|
||||||
|
if strings.Contains(d.Message, "Variable 'Positive' value mismatch") {
|
||||||
|
foundError = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !foundError {
|
||||||
|
t.Error("Expected error for invalid variable definition")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Case 2: Variable Usage Mismatch
|
||||||
|
// We need a class with specific field type.
|
||||||
|
// PIDGAM.Kp is float | int.
|
||||||
|
// Let's use string variable.
|
||||||
|
contentUsage := `
|
||||||
|
#var MyStr: string = "hello"
|
||||||
|
+MyPID = {
|
||||||
|
Class = PIDGAM
|
||||||
|
Kp = $MyStr
|
||||||
|
Ki = 0.0
|
||||||
|
Kd = 0.0
|
||||||
|
}
|
||||||
|
`
|
||||||
|
pt2 := index.NewProjectTree()
|
||||||
|
p2 := parser.NewParser(contentUsage)
|
||||||
|
cfg2, err := p2.Parse()
|
||||||
|
if err != nil { t.Fatal(err) }
|
||||||
|
pt2.AddFile("usage.marte", cfg2)
|
||||||
|
|
||||||
|
v2 := validator.NewValidator(pt2, ".")
|
||||||
|
v2.ValidateProject() // Should run CUE validation on nodes
|
||||||
|
|
||||||
|
foundUsageError := false
|
||||||
|
for _, d := range v2.Diagnostics {
|
||||||
|
// Schema validation error
|
||||||
|
if strings.Contains(d.Message, "Schema Validation Error") &&
|
||||||
|
(strings.Contains(d.Message, "conflicting values") || strings.Contains(d.Message, "mismatched types")) {
|
||||||
|
foundUsageError = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !foundUsageError {
|
||||||
|
t.Error("Expected error for invalid variable usage in PIDGAM.Kp")
|
||||||
|
for _, d := range v2.Diagnostics {
|
||||||
|
t.Logf("Diag: %s", d.Message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Case 3: Valid Usage
|
||||||
|
contentValid := `
|
||||||
|
#var MyGain: float = 1.5
|
||||||
|
+MyPID = {
|
||||||
|
Class = PIDGAM
|
||||||
|
Kp = $MyGain
|
||||||
|
Ki = 0.0
|
||||||
|
Kd = 0.0
|
||||||
|
}
|
||||||
|
`
|
||||||
|
pt3 := index.NewProjectTree()
|
||||||
|
p3 := parser.NewParser(contentValid)
|
||||||
|
cfg3, err := p3.Parse()
|
||||||
|
if err != nil { t.Fatal(err) }
|
||||||
|
pt3.AddFile("valid.marte", cfg3)
|
||||||
|
|
||||||
|
v3 := validator.NewValidator(pt3, ".")
|
||||||
|
v3.ValidateProject()
|
||||||
|
|
||||||
|
for _, d := range v3.Diagnostics {
|
||||||
|
if strings.Contains(d.Message, "Schema Validation Error") {
|
||||||
|
t.Errorf("Unexpected schema error: %s", d.Message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user