Proper multifile

This commit is contained in:
Martino Ferrari
2026-01-19 23:46:03 +01:00
parent 1eda6a2a38
commit d3818504b5
12 changed files with 496 additions and 205 deletions

View File

@@ -2,8 +2,8 @@ package validator
import (
"fmt"
"github.com/marte-dev/marte-dev-tools/internal/parser"
"github.com/marte-dev/marte-dev-tools/internal/index"
"github.com/marte-dev/marte-dev-tools/internal/parser"
)
type DiagnosticLevel int
@@ -22,74 +22,87 @@ type Diagnostic struct {
type Validator struct {
Diagnostics []Diagnostic
Index *index.Index
Tree *index.ProjectTree
}
func NewValidator(idx *index.Index) *Validator {
return &Validator{Index: idx}
func NewValidator(tree *index.ProjectTree) *Validator {
return &Validator{Tree: tree}
}
func (v *Validator) Validate(file string, config *parser.Configuration) {
for _, def := range config.Definitions {
v.validateDefinition(file, "", config, def)
func (v *Validator) ValidateProject() {
if v.Tree == nil || v.Tree.Root == nil {
return
}
v.validateNode(v.Tree.Root)
}
func (v *Validator) validateDefinition(file string, path string, config *parser.Configuration, def parser.Definition) {
switch d := def.(type) {
case *parser.ObjectNode:
name := d.Name
fullPath := name
if path != "" {
fullPath = path + "." + name
func (v *Validator) validateNode(node *index.ProjectNode) {
// Check for duplicate fields in this node
fields := make(map[string]string) // FieldName -> File
for _, frag := range node.Fragments {
for _, def := range frag.Definitions {
if f, ok := def.(*parser.Field); ok {
if existingFile, exists := fields[f.Name]; exists {
// Duplicate field
v.Diagnostics = append(v.Diagnostics, Diagnostic{
Level: LevelError,
Message: fmt.Sprintf("Duplicate Field Definition: '%s' is already defined in %s", f.Name, existingFile),
Position: f.Position,
File: frag.File,
})
} else {
fields[f.Name] = frag.File
}
}
}
}
// Check for mandatory 'Class' field for +/$ nodes
if d.Name != "" && (d.Name[0] == '+' || d.Name[0] == '$') {
hasClass := false
for _, subDef := range d.Subnode.Definitions {
if f, ok := subDef.(*parser.Field); ok && f.Name == "Class" {
// Check for mandatory Class if it's an object node (+/$)
// Root node usually doesn't have a name or is implicit
if node.RealName != "" && (node.RealName[0] == '+' || node.RealName[0] == '$') {
hasClass := false
for _, frag := range node.Fragments {
for _, def := range frag.Definitions {
if f, ok := def.(*parser.Field); ok && f.Name == "Class" {
hasClass = true
break
}
}
if !hasClass {
v.Diagnostics = append(v.Diagnostics, Diagnostic{
Level: LevelError,
Message: fmt.Sprintf("Node %s is an object and must contain a 'Class' field", d.Name),
Position: d.Position,
File: file,
})
if hasClass {
break
}
}
// GAM specific validation
// (This is a placeholder, real logic would check if it's a GAM)
for _, subDef := range d.Subnode.Definitions {
v.validateDefinition(file, fullPath, config, subDef)
if !hasClass {
// Report error on the first fragment's position
pos := parser.Position{Line: 1, Column: 1}
file := ""
if len(node.Fragments) > 0 {
pos = node.Fragments[0].ObjectPos
file = node.Fragments[0].File
}
v.Diagnostics = append(v.Diagnostics, Diagnostic{
Level: LevelError,
Message: fmt.Sprintf("Node %s is an object and must contain a 'Class' field", node.RealName),
Position: pos,
File: file,
})
}
}
// Recursively validate children
for _, child := range node.Children {
v.validateNode(child)
}
}
// Legacy/Compatibility method if needed, but we prefer ValidateProject
func (v *Validator) Validate(file string, config *parser.Configuration) {
// No-op or local checks if any
}
func (v *Validator) CheckUnused() {
if v.Index == nil {
return
}
referencedSymbols := make(map[*index.Symbol]bool)
for _, ref := range v.Index.References {
if ref.Target != nil {
referencedSymbols[ref.Target] = true
}
}
for _, sym := range v.Index.Symbols {
// Heuristic: if it's a GAM or Signal, check if referenced
// (Refining this later with proper class checks)
if !referencedSymbols[sym] {
// Logic to determine if it should be warned as unused
// e.g. if sym.Class is a GAM or if it's a signal in a DataSource
}
}
}
// To implement unused check, we'd need reference tracking in Index
// For now, focusing on duplicate fields and class validation
}