Proper multifile
This commit is contained in:
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user