Pragma and signal validation added

This commit is contained in:
Martino Ferrari
2026-01-22 02:29:54 +01:00
parent 93d48bd3ed
commit 8fe319de2d
6 changed files with 448 additions and 22 deletions

View File

@@ -51,6 +51,7 @@ type ProjectNode struct {
Parent *ProjectNode
Metadata map[string]string // Store extra info like Class, Type, Size
Target *ProjectNode // Points to referenced node (for Direct References/Links)
Pragmas []string
}
type Fragment struct {
@@ -201,6 +202,7 @@ func (pt *ProjectTree) populateNode(node *ProjectNode, file string, config *pars
for _, def := range config.Definitions {
doc := pt.findDoc(config.Comments, def.Pos())
pragmas := pt.findPragmas(config.Pragmas, def.Pos())
switch d := def.(type) {
case *parser.Field:
@@ -229,7 +231,11 @@ func (pt *ProjectTree) populateNode(node *ProjectNode, file string, config *pars
child.Doc += doc
}
pt.addObjectFragment(child, file, d, doc, config.Comments)
if len(pragmas) > 0 {
child.Pragmas = append(child.Pragmas, pragmas...)
}
pt.addObjectFragment(child, file, d, doc, config.Comments, config.Pragmas)
}
}
@@ -238,7 +244,7 @@ func (pt *ProjectTree) populateNode(node *ProjectNode, file string, config *pars
}
}
func (pt *ProjectTree) addObjectFragment(node *ProjectNode, file string, obj *parser.ObjectNode, doc string, comments []parser.Comment) {
func (pt *ProjectTree) addObjectFragment(node *ProjectNode, file string, obj *parser.ObjectNode, doc string, comments []parser.Comment, pragmas []parser.Pragma) {
frag := &Fragment{
File: file,
IsObject: true,
@@ -248,6 +254,7 @@ func (pt *ProjectTree) addObjectFragment(node *ProjectNode, file string, obj *pa
for _, def := range obj.Subnode.Definitions {
subDoc := pt.findDoc(comments, def.Pos())
subPragmas := pt.findPragmas(pragmas, def.Pos())
switch d := def.(type) {
case *parser.Field:
@@ -277,7 +284,11 @@ func (pt *ProjectTree) addObjectFragment(node *ProjectNode, file string, obj *pa
child.Doc += subDoc
}
pt.addObjectFragment(child, file, d, subDoc, comments)
if len(subPragmas) > 0 {
child.Pragmas = append(child.Pragmas, subPragmas...)
}
pt.addObjectFragment(child, file, d, subDoc, comments, pragmas)
}
}
@@ -322,6 +333,30 @@ func (pt *ProjectTree) findDoc(comments []parser.Comment, pos parser.Position) s
return docBuilder.String()
}
func (pt *ProjectTree) findPragmas(pragmas []parser.Pragma, pos parser.Position) []string {
var found []string
targetLine := pos.Line - 1
for i := len(pragmas) - 1; i >= 0; i-- {
p := pragmas[i]
if p.Position.Line > pos.Line {
continue
}
if p.Position.Line == pos.Line {
continue
}
if p.Position.Line == targetLine {
txt := strings.TrimSpace(strings.TrimPrefix(p.Text, "//!"))
found = append(found, txt)
targetLine--
} else if p.Position.Line < targetLine {
break
}
}
return found
}
func (pt *ProjectTree) indexValue(file string, val parser.Value) {
switch v := val.(type) {
case *parser.ReferenceValue:

View File

@@ -2,6 +2,7 @@ package validator
import (
"fmt"
"strings"
"github.com/marte-dev/marte-dev-tools/internal/index"
"github.com/marte-dev/marte-dev-tools/internal/parser"
@@ -353,12 +354,22 @@ func (v *Validator) validateGAMSignal(gamNode, signalNode *index.ProjectNode, di
}
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),
})
suppress := false
for _, p := range signalNode.Pragmas {
if strings.HasPrefix(p, "implicit:") {
suppress = true
break
}
}
if !suppress {
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{
@@ -367,6 +378,17 @@ func (v *Validator) validateGAMSignal(gamNode, signalNode *index.ProjectNode, di
Position: v.getNodePosition(signalNode),
File: v.getNodeFile(signalNode),
})
} else {
// Check Type validity even for implicit
typeVal := v.getFieldValue(typeFields[0])
if !isValidType(typeVal) {
v.Diagnostics = append(v.Diagnostics, Diagnostic{
Level: LevelError,
Message: fmt.Sprintf("Invalid Type '%s' for Signal '%s'", typeVal, signalNode.RealName),
Position: typeFields[0].Position,
File: v.getNodeFile(signalNode),
})
}
}
} else {
signalNode.Target = targetNode
@@ -376,9 +398,71 @@ func (v *Validator) validateGAMSignal(gamNode, signalNode *index.ProjectNode, di
v.updateReferenceTarget(v.getNodeFile(signalNode), val.Position, targetNode)
}
}
// Property checks
v.checkSignalProperty(signalNode, targetNode, "Type")
v.checkSignalProperty(signalNode, targetNode, "NumberOfElements")
v.checkSignalProperty(signalNode, targetNode, "NumberOfDimensions")
// Check Type validity if present
if typeFields, ok := fields["Type"]; ok && len(typeFields) > 0 {
typeVal := v.getFieldValue(typeFields[0])
if !isValidType(typeVal) {
v.Diagnostics = append(v.Diagnostics, Diagnostic{
Level: LevelError,
Message: fmt.Sprintf("Invalid Type '%s' for Signal '%s'", typeVal, signalNode.RealName),
Position: typeFields[0].Position,
File: v.getNodeFile(signalNode),
})
}
}
}
}
func (v *Validator) checkSignalProperty(gamSig, dsSig *index.ProjectNode, prop string) {
gamVal := gamSig.Metadata[prop]
dsVal := dsSig.Metadata[prop]
if gamVal == "" {
return
}
if dsVal != "" && gamVal != dsVal {
if prop == "Type" {
if v.checkCastPragma(gamSig, dsVal, gamVal) {
return
}
}
v.Diagnostics = append(v.Diagnostics, Diagnostic{
Level: LevelError,
Message: fmt.Sprintf("Signal '%s' property '%s' mismatch: defined '%s', referenced '%s'", gamSig.RealName, prop, dsVal, gamVal),
Position: v.getNodePosition(gamSig),
File: v.getNodeFile(gamSig),
})
}
}
func (v *Validator) checkCastPragma(node *index.ProjectNode, defType, curType string) bool {
for _, p := range node.Pragmas {
if strings.HasPrefix(p, "cast(") {
content := strings.TrimPrefix(p, "cast(")
if idx := strings.Index(content, ")"); idx != -1 {
content = content[:idx]
parts := strings.Split(content, ",")
if len(parts) == 2 {
d := strings.TrimSpace(parts[0])
c := strings.TrimSpace(parts[1])
if d == defType && c == curType {
return true
}
}
}
}
}
return false
}
func (v *Validator) updateReferenceTarget(file string, pos parser.Position, target *index.ProjectNode) {
for i := range v.Tree.References {
ref := &v.Tree.References[i]
@@ -409,6 +493,10 @@ func (v *Validator) getFieldValue(f *parser.Field) string {
return val.Value
case *parser.ReferenceValue:
return val.Value
case *parser.IntValue:
return val.Raw
case *parser.FloatValue:
return val.Raw
}
return ""
}
@@ -533,14 +621,24 @@ func (v *Validator) collectTargetUsage(node *index.ProjectNode, referenced map[*
}
func (v *Validator) checkUnusedRecursive(node *index.ProjectNode, referenced map[*index.ProjectNode]bool) {
// Heuristic for GAM
if isGAM(node) {
if !referenced[node] {
v.Diagnostics = append(v.Diagnostics, Diagnostic{
Level: LevelWarning,
Message: fmt.Sprintf("Unused GAM: %s is defined but not referenced in any thread or scheduler", node.RealName),
Position: v.getNodePosition(node),
File: v.getNodeFile(node),
})
suppress := false
for _, p := range node.Pragmas {
if strings.HasPrefix(p, "unused:") {
suppress = true
break
}
}
if !suppress {
v.Diagnostics = append(v.Diagnostics, Diagnostic{
Level: LevelWarning,
Message: fmt.Sprintf("Unused GAM: %s is defined but not referenced in any thread or scheduler", node.RealName),
Position: v.getNodePosition(node),
File: v.getNodeFile(node),
})
}
}
}
@@ -549,12 +647,21 @@ func (v *Validator) checkUnusedRecursive(node *index.ProjectNode, referenced map
if signalsNode, ok := node.Children["Signals"]; ok {
for _, signal := range signalsNode.Children {
if !referenced[signal] {
v.Diagnostics = append(v.Diagnostics, Diagnostic{
Level: LevelWarning,
Message: fmt.Sprintf("Unused Signal: %s is defined in DataSource %s but never referenced", signal.RealName, node.RealName),
Position: v.getNodePosition(signal),
File: v.getNodeFile(signal),
})
suppress := false
for _, p := range signal.Pragmas {
if strings.HasPrefix(p, "unused:") {
suppress = true
break
}
}
if !suppress {
v.Diagnostics = append(v.Diagnostics, Diagnostic{
Level: LevelWarning,
Message: fmt.Sprintf("Unused Signal: %s is defined in DataSource %s but never referenced", signal.RealName, node.RealName),
Position: v.getNodePosition(signal),
File: v.getNodeFile(signal),
})
}
}
}
}
@@ -602,4 +709,4 @@ func (v *Validator) getNodeFile(node *index.ProjectNode) string {
return node.Fragments[0].File
}
return ""
}
}