mostly good
This commit is contained in:
@@ -50,6 +50,7 @@ type ProjectNode struct {
|
|||||||
Children map[string]*ProjectNode
|
Children map[string]*ProjectNode
|
||||||
Parent *ProjectNode
|
Parent *ProjectNode
|
||||||
Metadata map[string]string // Store extra info like Class, Type, Size
|
Metadata map[string]string // Store extra info like Class, Type, Size
|
||||||
|
Target *ProjectNode // Points to referenced node (for Direct References/Links)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Fragment struct {
|
type Fragment struct {
|
||||||
@@ -384,6 +385,22 @@ func (pt *ProjectTree) Query(file string, line, col int) *QueryResult {
|
|||||||
return pt.queryNode(pt.Root, file, line, col)
|
return pt.queryNode(pt.Root, file, line, col)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pt *ProjectTree) Walk(visitor func(*ProjectNode)) {
|
||||||
|
if pt.Root != nil {
|
||||||
|
pt.walkRecursive(pt.Root, visitor)
|
||||||
|
}
|
||||||
|
for _, node := range pt.IsolatedFiles {
|
||||||
|
pt.walkRecursive(node, visitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pt *ProjectTree) walkRecursive(node *ProjectNode, visitor func(*ProjectNode)) {
|
||||||
|
visitor(node)
|
||||||
|
for _, child := range node.Children {
|
||||||
|
pt.walkRecursive(child, visitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (pt *ProjectTree) queryNode(node *ProjectNode, file string, line, col int) *QueryResult {
|
func (pt *ProjectTree) queryNode(node *ProjectNode, file string, line, col int) *QueryResult {
|
||||||
for _, frag := range node.Fragments {
|
for _, frag := range node.Fragments {
|
||||||
if frag.File == file {
|
if frag.File == file {
|
||||||
|
|||||||
@@ -406,7 +406,11 @@ func handleHover(params HoverParams) *Hover {
|
|||||||
var content string
|
var content string
|
||||||
|
|
||||||
if res.Node != nil {
|
if res.Node != nil {
|
||||||
content = formatNodeInfo(res.Node)
|
if res.Node.Target != nil {
|
||||||
|
content = fmt.Sprintf("**Link**: `%s` -> `%s`\n\n%s", res.Node.RealName, res.Node.Target.RealName, formatNodeInfo(res.Node.Target))
|
||||||
|
} else {
|
||||||
|
content = formatNodeInfo(res.Node)
|
||||||
|
}
|
||||||
} else if res.Field != nil {
|
} else if res.Field != nil {
|
||||||
content = fmt.Sprintf("**Field**: `%s`", res.Field.Name)
|
content = fmt.Sprintf("**Field**: `%s`", res.Field.Name)
|
||||||
} else if res.Reference != nil {
|
} else if res.Reference != nil {
|
||||||
@@ -454,7 +458,11 @@ func handleDefinition(params DefinitionParams) any {
|
|||||||
if res.Reference != nil && res.Reference.Target != nil {
|
if res.Reference != nil && res.Reference.Target != nil {
|
||||||
targetNode = res.Reference.Target
|
targetNode = res.Reference.Target
|
||||||
} else if res.Node != nil {
|
} else if res.Node != nil {
|
||||||
targetNode = res.Node
|
if res.Node.Target != nil {
|
||||||
|
targetNode = res.Node.Target
|
||||||
|
} else {
|
||||||
|
targetNode = res.Node
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if targetNode != nil {
|
if targetNode != nil {
|
||||||
@@ -497,23 +505,30 @@ func handleReferences(params ReferenceParams) []Location {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve canonical target (follow link if present)
|
||||||
|
canonical := targetNode
|
||||||
|
if targetNode.Target != nil {
|
||||||
|
canonical = targetNode.Target
|
||||||
|
}
|
||||||
|
|
||||||
var locations []Location
|
var locations []Location
|
||||||
if params.Context.IncludeDeclaration {
|
if params.Context.IncludeDeclaration {
|
||||||
for _, frag := range targetNode.Fragments {
|
for _, frag := range canonical.Fragments {
|
||||||
if frag.IsObject {
|
if frag.IsObject {
|
||||||
locations = append(locations, Location{
|
locations = append(locations, Location{
|
||||||
URI: "file://" + frag.File,
|
URI: "file://" + frag.File,
|
||||||
Range: Range{
|
Range: Range{
|
||||||
Start: Position{Line: frag.ObjectPos.Line - 1, Character: frag.ObjectPos.Column - 1},
|
Start: Position{Line: frag.ObjectPos.Line - 1, Character: frag.ObjectPos.Column - 1},
|
||||||
End: Position{Line: frag.ObjectPos.Line - 1, Character: frag.ObjectPos.Column - 1 + len(targetNode.RealName)},
|
End: Position{Line: frag.ObjectPos.Line - 1, Character: frag.ObjectPos.Column - 1 + len(canonical.RealName)},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 1. References from index (Aliases)
|
||||||
for _, ref := range tree.References {
|
for _, ref := range tree.References {
|
||||||
if ref.Target == targetNode {
|
if ref.Target == canonical {
|
||||||
locations = append(locations, Location{
|
locations = append(locations, Location{
|
||||||
URI: "file://" + ref.File,
|
URI: "file://" + ref.File,
|
||||||
Range: Range{
|
Range: Range{
|
||||||
@@ -524,6 +539,23 @@ func handleReferences(params ReferenceParams) []Location {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. References from Node Targets (Direct References)
|
||||||
|
tree.Walk(func(node *index.ProjectNode) {
|
||||||
|
if node.Target == canonical {
|
||||||
|
for _, frag := range node.Fragments {
|
||||||
|
if frag.IsObject {
|
||||||
|
locations = append(locations, Location{
|
||||||
|
URI: "file://" + frag.File,
|
||||||
|
Range: Range{
|
||||||
|
Start: Position{Line: frag.ObjectPos.Line - 1, Character: frag.ObjectPos.Column - 1},
|
||||||
|
End: Position{Line: frag.ObjectPos.Line - 1, Character: frag.ObjectPos.Column - 1 + len(node.RealName)},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return locations
|
return locations
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -285,7 +285,7 @@ func (v *Validator) validateGAMSignal(gamNode, signalNode *index.ProjectNode, di
|
|||||||
return // Ignore implicit signals or missing datasource (handled elsewhere if mandatory)
|
return // Ignore implicit signals or missing datasource (handled elsewhere if mandatory)
|
||||||
}
|
}
|
||||||
|
|
||||||
dsNode := v.resolveReference(dsName, v.getNodeFile(signalNode))
|
dsNode := v.resolveReference(dsName, v.getNodeFile(signalNode), isDataSource)
|
||||||
if dsNode == nil {
|
if dsNode == nil {
|
||||||
v.Diagnostics = append(v.Diagnostics, Diagnostic{
|
v.Diagnostics = append(v.Diagnostics, Diagnostic{
|
||||||
Level: LevelError,
|
Level: LevelError,
|
||||||
@@ -335,8 +335,8 @@ func (v *Validator) validateGAMSignal(gamNode, signalNode *index.ProjectNode, di
|
|||||||
targetSignalName = v.getFieldValue(aliasFields[0]) // Alias is usually the name in DataSource
|
targetSignalName = v.getFieldValue(aliasFields[0]) // Alias is usually the name in DataSource
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var targetNode *index.ProjectNode
|
||||||
if signalsContainer, ok := dsNode.Children["Signals"]; ok {
|
if signalsContainer, ok := dsNode.Children["Signals"]; ok {
|
||||||
var targetNode *index.ProjectNode
|
|
||||||
targetNorm := index.NormalizeName(targetSignalName)
|
targetNorm := index.NormalizeName(targetSignalName)
|
||||||
|
|
||||||
if child, ok := signalsContainer.Children[targetNorm]; ok {
|
if child, ok := signalsContainer.Children[targetNorm]; ok {
|
||||||
@@ -350,20 +350,30 @@ func (v *Validator) validateGAMSignal(gamNode, signalNode *index.ProjectNode, di
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if targetNode == nil {
|
if targetNode == nil {
|
||||||
|
v.Diagnostics = append(v.Diagnostics, Diagnostic{
|
||||||
|
Level: LevelWarning,
|
||||||
|
Message: fmt.Sprintf("Implicitly Defined Signal: '%s' is defined in GAM '%s' but not in DataSource '%s'", targetSignalName, gamNode.RealName, dsName),
|
||||||
|
Position: v.getNodePosition(signalNode),
|
||||||
|
File: v.getNodeFile(signalNode),
|
||||||
|
})
|
||||||
|
|
||||||
|
if typeFields, ok := fields["Type"]; !ok || len(typeFields) == 0 {
|
||||||
v.Diagnostics = append(v.Diagnostics, Diagnostic{
|
v.Diagnostics = append(v.Diagnostics, Diagnostic{
|
||||||
Level: LevelError,
|
Level: LevelError,
|
||||||
Message: fmt.Sprintf("Signal '%s' not found in DataSource '%s'", targetSignalName, dsName),
|
Message: fmt.Sprintf("Implicit signal '%s' must define Type", targetSignalName),
|
||||||
Position: v.getNodePosition(signalNode),
|
Position: v.getNodePosition(signalNode),
|
||||||
File: v.getNodeFile(signalNode),
|
File: v.getNodeFile(signalNode),
|
||||||
})
|
})
|
||||||
} else {
|
}
|
||||||
// Link Alias reference
|
} else {
|
||||||
if aliasFields, ok := fields["Alias"]; ok && len(aliasFields) > 0 {
|
signalNode.Target = targetNode
|
||||||
if val, ok := aliasFields[0].Value.(*parser.ReferenceValue); ok {
|
// Link Alias reference
|
||||||
v.updateReferenceTarget(v.getNodeFile(signalNode), val.Position, targetNode)
|
if aliasFields, ok := fields["Alias"]; ok && len(aliasFields) > 0 {
|
||||||
}
|
if val, ok := aliasFields[0].Value.(*parser.ReferenceValue); ok {
|
||||||
|
v.updateReferenceTarget(v.getNodeFile(signalNode), val.Position, targetNode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -403,9 +413,9 @@ func (v *Validator) getFieldValue(f *parser.Field) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Validator) resolveReference(name string, file string) *index.ProjectNode {
|
func (v *Validator) resolveReference(name string, file string, predicate func(*index.ProjectNode) bool) *index.ProjectNode {
|
||||||
if isoNode, ok := v.Tree.IsolatedFiles[file]; ok {
|
if isoNode, ok := v.Tree.IsolatedFiles[file]; ok {
|
||||||
if found := v.findNodeRecursive(isoNode, name); found != nil {
|
if found := v.findNodeRecursive(isoNode, name, predicate); found != nil {
|
||||||
return found
|
return found
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -413,24 +423,20 @@ func (v *Validator) resolveReference(name string, file string) *index.ProjectNod
|
|||||||
if v.Tree.Root == nil {
|
if v.Tree.Root == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return v.findNodeRecursive(v.Tree.Root, name)
|
return v.findNodeRecursive(v.Tree.Root, name, predicate)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Validator) findNodeRecursive(root *index.ProjectNode, name string) *index.ProjectNode {
|
func (v *Validator) findNodeRecursive(root *index.ProjectNode, name string, predicate func(*index.ProjectNode) bool) *index.ProjectNode {
|
||||||
// Simple recursive search matching name
|
// Simple recursive search matching name
|
||||||
if root.RealName == name || root.Name == index.NormalizeName(name) {
|
if root.RealName == name || root.Name == index.NormalizeName(name) {
|
||||||
return root
|
if predicate == nil || predicate(root) {
|
||||||
}
|
return root
|
||||||
|
}
|
||||||
// Fast lookup in children
|
|
||||||
norm := index.NormalizeName(name)
|
|
||||||
if child, ok := root.Children[norm]; ok {
|
|
||||||
return child
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recursive
|
// Recursive
|
||||||
for _, child := range root.Children {
|
for _, child := range root.Children {
|
||||||
if found := v.findNodeRecursive(child, name); found != nil {
|
if found := v.findNodeRecursive(child, name, predicate); found != nil {
|
||||||
return found
|
return found
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -495,7 +501,6 @@ func (v *Validator) getFileForField(f *parser.Field, node *index.ProjectNode) st
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v *Validator) CheckUnused() {
|
func (v *Validator) CheckUnused() {
|
||||||
// ... (same as before)
|
|
||||||
referencedNodes := make(map[*index.ProjectNode]bool)
|
referencedNodes := make(map[*index.ProjectNode]bool)
|
||||||
for _, ref := range v.Tree.References {
|
for _, ref := range v.Tree.References {
|
||||||
if ref.Target != nil {
|
if ref.Target != nil {
|
||||||
@@ -503,6 +508,13 @@ func (v *Validator) CheckUnused() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if v.Tree.Root != nil {
|
||||||
|
v.collectTargetUsage(v.Tree.Root, referencedNodes)
|
||||||
|
}
|
||||||
|
for _, node := range v.Tree.IsolatedFiles {
|
||||||
|
v.collectTargetUsage(node, referencedNodes)
|
||||||
|
}
|
||||||
|
|
||||||
if v.Tree.Root != nil {
|
if v.Tree.Root != nil {
|
||||||
v.checkUnusedRecursive(v.Tree.Root, referencedNodes)
|
v.checkUnusedRecursive(v.Tree.Root, referencedNodes)
|
||||||
}
|
}
|
||||||
@@ -511,9 +523,16 @@ func (v *Validator) CheckUnused() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *Validator) collectTargetUsage(node *index.ProjectNode, referenced map[*index.ProjectNode]bool) {
|
||||||
|
if node.Target != nil {
|
||||||
|
referenced[node.Target] = true
|
||||||
|
}
|
||||||
|
for _, child := range node.Children {
|
||||||
|
v.collectTargetUsage(child, referenced)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (v *Validator) checkUnusedRecursive(node *index.ProjectNode, referenced map[*index.ProjectNode]bool) {
|
func (v *Validator) checkUnusedRecursive(node *index.ProjectNode, referenced map[*index.ProjectNode]bool) {
|
||||||
// ... (same as before)
|
|
||||||
// Heuristic for GAM
|
|
||||||
if isGAM(node) {
|
if isGAM(node) {
|
||||||
if !referenced[node] {
|
if !referenced[node] {
|
||||||
v.Diagnostics = append(v.Diagnostics, Diagnostic{
|
v.Diagnostics = append(v.Diagnostics, Diagnostic{
|
||||||
|
|||||||
@@ -5,16 +5,30 @@ import (
|
|||||||
|
|
||||||
"github.com/marte-dev/marte-dev-tools/internal/index"
|
"github.com/marte-dev/marte-dev-tools/internal/index"
|
||||||
"github.com/marte-dev/marte-dev-tools/internal/parser"
|
"github.com/marte-dev/marte-dev-tools/internal/parser"
|
||||||
|
"github.com/marte-dev/marte-dev-tools/internal/validator"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLSPSignalMetadata(t *testing.T) {
|
func TestLSPSignalReferences(t *testing.T) {
|
||||||
content := `
|
content := `
|
||||||
+MySignal = {
|
+Data = {
|
||||||
Class = Signal
|
Class = ReferenceContainer
|
||||||
Type = uint32
|
+MyDS = {
|
||||||
NumberOfElements = 10
|
Class = FileReader
|
||||||
NumberOfDimensions = 1
|
Filename = "test"
|
||||||
DataSource = DDB1
|
Signals = {
|
||||||
|
MySig = { Type = uint32 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
+MyGAM = {
|
||||||
|
Class = IOGAM
|
||||||
|
InputSignals = {
|
||||||
|
MySig = {
|
||||||
|
DataSource = MyDS
|
||||||
|
Type = uint32
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
p := parser.NewParser(content)
|
p := parser.NewParser(content)
|
||||||
@@ -24,26 +38,50 @@ func TestLSPSignalMetadata(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
idx := index.NewProjectTree()
|
idx := index.NewProjectTree()
|
||||||
file := "signal.marte"
|
idx.AddFile("signal_refs.marte", config)
|
||||||
idx.AddFile(file, config)
|
idx.ResolveReferences()
|
||||||
|
|
||||||
res := idx.Query(file, 2, 2) // Query +MySignal
|
v := validator.NewValidator(idx, ".")
|
||||||
if res == nil || res.Node == nil {
|
v.ValidateProject()
|
||||||
t.Fatal("Query failed for signal definition")
|
|
||||||
|
// Find definition of MySig in MyDS
|
||||||
|
root := idx.IsolatedFiles["signal_refs.marte"]
|
||||||
|
if root == nil {
|
||||||
|
t.Fatal("Root node not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
meta := res.Node.Metadata
|
// Traverse to MySig
|
||||||
if meta["Class"] != "Signal" {
|
dataNode := root.Children["Data"]
|
||||||
t.Errorf("Expected Class Signal, got %s", meta["Class"])
|
if dataNode == nil { t.Fatal("Data node not found") }
|
||||||
}
|
|
||||||
if meta["Type"] != "uint32" {
|
myDS := dataNode.Children["MyDS"]
|
||||||
t.Errorf("Expected Type uint32, got %s", meta["Type"])
|
if myDS == nil { t.Fatal("MyDS node not found") }
|
||||||
}
|
|
||||||
if meta["NumberOfElements"] != "10" {
|
signals := myDS.Children["Signals"]
|
||||||
t.Errorf("Expected 10 elements, got %s", meta["NumberOfElements"])
|
if signals == nil { t.Fatal("Signals node not found") }
|
||||||
|
|
||||||
|
mySigDef := signals.Children["MySig"]
|
||||||
|
if mySigDef == nil {
|
||||||
|
t.Fatal("Definition of MySig not found in tree")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since handleHover logic is in internal/lsp which we can't easily test directly without
|
// Now simulate "Find References" on mySigDef
|
||||||
// exposing formatNodeInfo, we rely on the fact that Metadata is populated correctly.
|
foundRefs := 0
|
||||||
// If Metadata is correct, server.go logic (verified by code review) should display it.
|
idx.Walk(func(node *index.ProjectNode) {
|
||||||
|
if node.Target == mySigDef {
|
||||||
|
foundRefs++
|
||||||
|
// Check if node is the GAM signal
|
||||||
|
if node.RealName != "MySig" { // In GAM it is MySig
|
||||||
|
t.Errorf("Unexpected reference node name: %s", node.RealName)
|
||||||
|
}
|
||||||
|
// Check parent is InputSignals -> MyGAM
|
||||||
|
if node.Parent == nil || node.Parent.Parent == nil || node.Parent.Parent.RealName != "+MyGAM" {
|
||||||
|
t.Errorf("Reference node not in MyGAM")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if foundRefs != 1 {
|
||||||
|
t.Errorf("Expected 1 reference (Direct), found %d", foundRefs)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user