From 5c3f05a1a46520d0b164b3557e31d4e357c76e1e Mon Sep 17 00:00:00 2001 From: Martino Ferrari Date: Fri, 23 Jan 2026 10:23:02 +0100 Subject: [PATCH] implemented ordering preservation --- internal/builder/builder.go | 82 ++++------------- internal/formatter/formatter.go | 8 +- internal/index/index.go | 52 ++++++++++- internal/lsp/server_test.go | 56 +++++------ internal/parser/lexer.go | 2 +- internal/parser/parser.go | 28 +++--- internal/schema/schema.go | 4 +- internal/validator/validator.go | 31 ++----- test/builder_multifile_test.go | 24 ++--- test/integration_test.go | 22 ++--- test/lsp_doc_test.go | 10 +- test/lsp_signal_test.go | 22 +++-- test/lsp_test.go | 24 ++--- test/validator_functions_array_test.go | 14 +-- test/validator_gam_signals_test.go | 8 +- test/validator_global_pragma_debug_test.go | 32 +++---- test/validator_implicit_signal_test.go | 20 ++-- test/validator_multifile_test.go | 102 ++++++++++----------- 18 files changed, 262 insertions(+), 279 deletions(-) diff --git a/internal/builder/builder.go b/internal/builder/builder.go index 4bb611a..3fc902c 100644 --- a/internal/builder/builder.go +++ b/internal/builder/builder.go @@ -71,86 +71,38 @@ func (b *Builder) writeNodeContent(f *os.File, node *index.ProjectNode, indent i indentStr := strings.Repeat(" ", indent) // If this node has a RealName (e.g. +App), we print it as an object definition - // UNLESS it is the top-level output file itself? - // If we are writing "App.marte", maybe we are writing the *body* of App? - // Spec: "unifying multi-file project into a single configuration output" - - // Let's assume we print the Node itself. if node.RealName != "" { fmt.Fprintf(f, "%s%s = {\n", indentStr, node.RealName) indent++ indentStr = strings.Repeat(" ", indent) } + writtenChildren := make(map[string]bool) + // 2. Write definitions from fragments for _, frag := range node.Fragments { - // Use formatter logic to print definitions - // We need a temporary Config to use Formatter? - // Or just reimplement basic printing? Formatter is better. - // But Formatter prints to io.Writer. - - // We can reuse formatDefinition logic if we exposed it, or just copy basic logic. - // Since we need to respect indentation, using Formatter.Format might be tricky - // unless we wrap definitions in a dummy structure. - for _, def := range frag.Definitions { - // Basic formatting for now, referencing formatter style - b.writeDefinition(f, def, indent) + switch d := def.(type) { + case *parser.Field: + b.writeDefinition(f, d, indent) + case *parser.ObjectNode: + norm := index.NormalizeName(d.Name) + if child, ok := node.Children[norm]; ok { + if !writtenChildren[norm] { + b.writeNodeContent(f, child, indent) + writtenChildren[norm] = true + } + } + } } } // 3. Write Children (recursively) - // Children are sub-nodes defined implicitly via #package A.B or explicitly +Sub - // Explicit +Sub are handled via Fragments logic (they are definitions in fragments). - // Implicit nodes (from #package A.B.C where B was never explicitly defined) - // show up in Children map but maybe not in Fragments? - - // If a Child is NOT in fragments (implicit), we still need to write it. - // If it IS in fragments (explicit +Child), it was handled in loop above? - // Wait. My Indexer puts `+Sub` into `node.Children["Sub"]` AND adds a `Fragment` to `node` containing `+Sub` object? - - // Let's check Indexer. - // Case ObjectNode: - // Adds Fragment to `child` (the Sub node). - // Does NOT add `ObjectNode` definition to `node`'s fragment list? - // "pt.addObjectFragment(child...)" - // It does NOT add to `fileFragment.Definitions`. - - // So `node.Fragments` only contains Fields! - // Children are all in `node.Children`. - - // So: - // 1. Write Fields (from Fragments). - // 2. Write Children (from Children map). - - // But wait, Fragments might have order? - // "Relative ordering within a file is preserved." - // My Indexer splits Fields and Objects. - // Fields go to Fragments. Objects go to Children. - // This loses the relative order between Fields and Objects in the source file! - - // Correct Indexer approach for preserving order: - // `Fragment` should contain a list of `Entry`. - // `Entry` can be `Field` OR `ChildNodeName`. - - // But I just rewrote Indexer to split them. - // If strict order is required "within a file", my Indexer is slightly lossy regarding Field vs Object order. - // Spec: "Relative ordering within a file is preserved." - - // To fix this without another full rewrite: - // Iterating `node.Children` alphabetically is arbitrary. - // We should ideally iterate them in the order they appear. - - // For now, I will proceed with writing Children after Fields, which is a common convention, - // unless strict interleaving is required. - // Given "Class first" rule, reordering happens anyway. - - // Sorting Children? - // Maybe keep a list of OrderedChildren in ProjectNode? - sortedChildren := make([]string, 0, len(node.Children)) for k := range node.Children { - sortedChildren = append(sortedChildren, k) + if !writtenChildren[k] { + sortedChildren = append(sortedChildren, k) + } } sort.Strings(sortedChildren) // Alphabetical for determinism diff --git a/internal/formatter/formatter.go b/internal/formatter/formatter.go index 45602fc..cde021c 100644 --- a/internal/formatter/formatter.go +++ b/internal/formatter/formatter.go @@ -54,7 +54,7 @@ func fixComment(text string) string { return "//# " + text[3:] } } else if strings.HasPrefix(text, "//") { - if len(text) > 2 && text[2] != ' ' && text[2] != '#' && text[2] != '!' { + if len(text) > 2 && text[2] != ' ' && text[2] != '#' && text[2] != '!' { return "// " + text[2:] } } @@ -101,7 +101,7 @@ func (f *Formatter) formatDefinition(def parser.Definition, indent int) int { fmt.Fprintln(f.writer) f.formatSubnode(d.Subnode, indent+1) - + fmt.Fprintf(f.writer, "%s}", indentStr) return d.Subnode.EndPosition.Line } @@ -175,7 +175,7 @@ func (f *Formatter) flushCommentsBefore(pos parser.Position, indent int, stick b break } } - // If stick is true, we don't print extra newline. + // If stick is true, we don't print extra newline. // The caller will print the definition immediately after this function returns. // If stick is false (e.g. end of block comments), we act normally. // But actually, the previous implementation didn't print extra newlines between comments and code @@ -208,4 +208,4 @@ func (f *Formatter) popComment() string { c := f.insertables[f.cursor] f.cursor++ return c.Text -} \ No newline at end of file +} diff --git a/internal/index/index.go b/internal/index/index.go index 6636262..aea8e10 100644 --- a/internal/index/index.go +++ b/internal/index/index.go @@ -222,6 +222,7 @@ func (pt *ProjectTree) populateNode(node *ProjectNode, file string, config *pars fileFragment.Definitions = append(fileFragment.Definitions, d) pt.indexValue(file, d.Value) case *parser.ObjectNode: + fileFragment.Definitions = append(fileFragment.Definitions, d) norm := NormalizeName(d.Name) if _, ok := node.Children[norm]; !ok { node.Children[norm] = &ProjectNode{ @@ -276,6 +277,7 @@ func (pt *ProjectTree) addObjectFragment(node *ProjectNode, file string, obj *pa pt.indexValue(file, d.Value) pt.extractFieldMetadata(node, d) case *parser.ObjectNode: + frag.Definitions = append(frag.Definitions, d) norm := NormalizeName(d.Name) if _, ok := node.Children[norm]; !ok { node.Children[norm] = &ProjectNode{ @@ -390,25 +392,65 @@ func (pt *ProjectTree) ResolveReferences() { for i := range pt.References { ref := &pt.References[i] if isoNode, ok := pt.IsolatedFiles[ref.File]; ok { - ref.Target = pt.findNode(isoNode, ref.Name) + ref.Target = pt.FindNode(isoNode, ref.Name, nil) } else { - ref.Target = pt.findNode(pt.Root, ref.Name) + ref.Target = pt.FindNode(pt.Root, ref.Name, nil) } } } -func (pt *ProjectTree) findNode(root *ProjectNode, name string) *ProjectNode { +func (pt *ProjectTree) FindNode(root *ProjectNode, name string, predicate func(*ProjectNode) bool) *ProjectNode { + if strings.Contains(name, ".") { + parts := strings.Split(name, ".") + rootName := parts[0] + + var candidates []*ProjectNode + pt.findAllNodes(root, rootName, &candidates) + + for _, cand := range candidates { + curr := cand + valid := true + for i := 1; i < len(parts); i++ { + nextName := parts[i] + normNext := NormalizeName(nextName) + if child, ok := curr.Children[normNext]; ok { + curr = child + } else { + valid = false + break + } + } + if valid { + if predicate == nil || predicate(curr) { + return curr + } + } + } + return nil + } + if root.RealName == name || root.Name == name { - return root + if predicate == nil || predicate(root) { + return root + } } for _, child := range root.Children { - if res := pt.findNode(child, name); res != nil { + if res := pt.FindNode(child, name, predicate); res != nil { return res } } return nil } +func (pt *ProjectTree) findAllNodes(root *ProjectNode, name string, results *[]*ProjectNode) { + if root.RealName == name || root.Name == name { + *results = append(*results, root) + } + for _, child := range root.Children { + pt.findAllNodes(child, name, results) + } +} + type QueryResult struct { Node *ProjectNode Field *parser.Field diff --git a/internal/lsp/server_test.go b/internal/lsp/server_test.go index 98a8d28..edd6987 100644 --- a/internal/lsp/server_test.go +++ b/internal/lsp/server_test.go @@ -30,7 +30,7 @@ func TestInitProjectScan(t *testing.T) { // +Source = { Class = C Link = Target } // 012345678901234567890123456789012345 // Previous offset was 29. - // Now add 21? + // Now add 21? // #package Test.Common\n // +Source = ... // So add 21 to Character? Or Line 1? @@ -84,7 +84,7 @@ func TestInitProjectScan(t *testing.T) { func TestHandleDefinition(t *testing.T) { // Reset tree for test tree = index.NewProjectTree() - + content := ` +MyObject = { Class = Type @@ -136,7 +136,7 @@ func TestHandleDefinition(t *testing.T) { func TestHandleReferences(t *testing.T) { // Reset tree for test tree = index.NewProjectTree() - + content := ` +MyObject = { Class = Type @@ -173,38 +173,38 @@ func TestHandleReferences(t *testing.T) { func TestLSPFormatting(t *testing.T) { // Setup - content := ` + content := ` #package Proj.Main +Object={ Field=1 } ` - uri := "file:///test.marte" - - // Open (populate documents map) - documents[uri] = content - - // Format - params := DocumentFormattingParams{ - TextDocument: TextDocumentIdentifier{URI: uri}, - } - - edits := handleFormatting(params) - - if len(edits) != 1 { - t.Fatalf("Expected 1 edit, got %d", len(edits)) - } - - newText := edits[0].NewText - - expected := `#package Proj.Main + uri := "file:///test.marte" + + // Open (populate documents map) + documents[uri] = content + + // Format + params := DocumentFormattingParams{ + TextDocument: TextDocumentIdentifier{URI: uri}, + } + + edits := handleFormatting(params) + + if len(edits) != 1 { + t.Fatalf("Expected 1 edit, got %d", len(edits)) + } + + newText := edits[0].NewText + + expected := `#package Proj.Main +Object = { Field = 1 } ` - // Normalize newlines for comparison just in case - if strings.TrimSpace(strings.ReplaceAll(newText, "\r\n", "\n")) != strings.TrimSpace(strings.ReplaceAll(expected, "\r\n", "\n")) { - t.Errorf("Formatting mismatch.\nExpected:\n%s\nGot:\n%s", expected, newText) - } -} \ No newline at end of file + // Normalize newlines for comparison just in case + if strings.TrimSpace(strings.ReplaceAll(newText, "\r\n", "\n")) != strings.TrimSpace(strings.ReplaceAll(expected, "\r\n", "\n")) { + t.Errorf("Formatting mismatch.\nExpected:\n%s\nGot:\n%s", expected, newText) + } +} diff --git a/internal/parser/lexer.go b/internal/parser/lexer.go index c8afc3f..5a539c3 100644 --- a/internal/parser/lexer.go +++ b/internal/parser/lexer.go @@ -257,4 +257,4 @@ func (l *Lexer) lexPackage() Token { return l.lexUntilNewline(TokenPackage) } return l.emit(TokenError) -} \ No newline at end of file +} diff --git a/internal/parser/parser.go b/internal/parser/parser.go index a097098..ba8e6a8 100644 --- a/internal/parser/parser.go +++ b/internal/parser/parser.go @@ -145,17 +145,17 @@ func (p *Parser) isSubnodeLookahead() bool { // Look inside: // peek(0) is '{' // peek(1) is first token inside - + t1 := p.peekN(1) if t1.Type == TokenRBrace { - // {} -> Empty. Assume Array (Value) by default, unless forced? + // {} -> Empty. Assume Array (Value) by default, unless forced? // If we return false, it parses as ArrayValue. // If user writes "Sig = {}", is it an empty signal? - // Empty array is more common for value. + // Empty array is more common for value. // If "Sig" is a node, it should probably have content or use +Sig. - return false + return false } - + if t1.Type == TokenIdentifier { // Identifier inside. // If followed by '=', it's a definition -> Subnode. @@ -166,12 +166,12 @@ func (p *Parser) isSubnodeLookahead() bool { // Identifier alone or followed by something else -> Reference/Value -> Array return false } - + if t1.Type == TokenObjectIdentifier { // +Node = ... -> Definition -> Subnode return true } - + // Literals -> Array return false } @@ -204,13 +204,13 @@ func (p *Parser) parseSubnode() (Subnode, error) { func (p *Parser) parseValue() (Value, error) { tok := p.next() switch tok.Type { - case TokenString: - return &StringValue{ - Position: tok.Position, - Value: strings.Trim(tok.Value, "\""), - Quoted: true, - }, nil - + case TokenString: + return &StringValue{ + Position: tok.Position, + Value: strings.Trim(tok.Value, "\""), + Quoted: true, + }, nil + case TokenNumber: // Simplistic handling if strings.Contains(tok.Value, ".") || strings.Contains(tok.Value, "e") { diff --git a/internal/schema/schema.go b/internal/schema/schema.go index 3d0fa39..ea748ae 100644 --- a/internal/schema/schema.go +++ b/internal/schema/schema.go @@ -114,7 +114,7 @@ func LoadFullSchema(projectRoot string) *Schema { sysPaths := []string{ "/usr/share/mdt/marte_schema.json", } - + home, err := os.UserHomeDir() if err == nil { sysPaths = append(sysPaths, filepath.Join(home, ".local/share/mdt/marte_schema.json")) @@ -135,4 +135,4 @@ func LoadFullSchema(projectRoot string) *Schema { } return s -} \ No newline at end of file +} diff --git a/internal/validator/validator.go b/internal/validator/validator.go index 2106cd4..8e1f928 100644 --- a/internal/validator/validator.go +++ b/internal/validator/validator.go @@ -343,7 +343,7 @@ func (v *Validator) validateGAMSignal(gamNode, signalNode *index.ProjectNode, di var targetNode *index.ProjectNode if signalsContainer, ok := dsNode.Children["Signals"]; ok { targetNorm := index.NormalizeName(targetSignalName) - + if child, ok := signalsContainer.Children[targetNorm]; ok { targetNode = child } else { @@ -404,12 +404,12 @@ 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]) @@ -509,7 +509,7 @@ func (v *Validator) getFieldValue(f *parser.Field) string { func (v *Validator) resolveReference(name string, file string, predicate func(*index.ProjectNode) bool) *index.ProjectNode { if isoNode, ok := v.Tree.IsolatedFiles[file]; ok { - if found := v.findNodeRecursive(isoNode, name, predicate); found != nil { + if found := v.Tree.FindNode(isoNode, name, predicate); found != nil { return found } return nil @@ -517,24 +517,7 @@ func (v *Validator) resolveReference(name string, file string, predicate func(*i if v.Tree.Root == nil { return nil } - return v.findNodeRecursive(v.Tree.Root, name, predicate) -} - -func (v *Validator) findNodeRecursive(root *index.ProjectNode, name string, predicate func(*index.ProjectNode) bool) *index.ProjectNode { - // Simple recursive search matching name - if root.RealName == name || root.Name == index.NormalizeName(name) { - if predicate == nil || predicate(root) { - return root - } - } - - // Recursive - for _, child := range root.Children { - if found := v.findNodeRecursive(child, name, predicate); found != nil { - return found - } - } - return nil + return v.Tree.FindNode(v.Tree.Root, name, predicate) } func (v *Validator) getNodeClass(node *index.ProjectNode) string { @@ -554,7 +537,7 @@ func isValidType(t string) bool { } func (v *Validator) checkType(val parser.Value, expectedType string) bool { - // ... (same as before) + // ... (same as before) switch expectedType { case "int": _, ok := val.(*parser.IntValue) @@ -780,4 +763,4 @@ func (v *Validator) isGloballyAllowed(warningType string, contextFile string) bo } } return false -} \ No newline at end of file +} diff --git a/test/builder_multifile_test.go b/test/builder_multifile_test.go index 9c0a1ec..ae4f302 100644 --- a/test/builder_multifile_test.go +++ b/test/builder_multifile_test.go @@ -18,7 +18,7 @@ func TestMultiFileBuildMergeAndOrder(t *testing.T) { // File 1: Has FieldA, no Class. // File 2: Has Class, FieldB. // Both in package +MyObj - + f1Content := ` #package Proj.+MyObj FieldA = 10 @@ -30,10 +30,10 @@ FieldB = 20 ` os.WriteFile("build_multi_test/f1.marte", []byte(f1Content), 0644) os.WriteFile("build_multi_test/f2.marte", []byte(f2Content), 0644) - + // Execute Build b := builder.NewBuilder([]string{"build_multi_test/f1.marte", "build_multi_test/f2.marte"}) - + // Prepare output file // Should be +MyObj.marte (normalized MyObj.marte) - Actually checking content outputFile := "build_multi_test/MyObj.marte" @@ -48,19 +48,19 @@ FieldB = 20 t.Fatalf("Build failed: %v", err) } f.Close() // Close to flush - + // Check Output if _, err := os.Stat(outputFile); os.IsNotExist(err) { t.Fatalf("Expected output file not found") } - + content, err := os.ReadFile(outputFile) if err != nil { t.Fatalf("Failed to read output: %v", err) } - + output := string(content) - + // Check presence if !strings.Contains(output, "Class = \"MyClass\"") { t.Error("Output missing Class") @@ -71,23 +71,23 @@ FieldB = 20 if !strings.Contains(output, "FieldB = 20") { t.Error("Output missing FieldB") } - + // Check Order: Class/FieldB (from f2) should come BEFORE FieldA (from f1) // because f2 has the Class definition. - + idxClass := strings.Index(output, "Class") idxFieldB := strings.Index(output, "FieldB") idxFieldA := strings.Index(output, "FieldA") - + if idxClass == -1 || idxFieldB == -1 || idxFieldA == -1 { t.Fatal("Missing fields in output") } - + // Class should be first if idxClass > idxFieldA { t.Errorf("Expected Class (from f2) to be before FieldA (from f1). Output:\n%s", output) } - + // FieldB should be near Class (same fragment) // FieldA should be after if idxFieldB > idxFieldA { diff --git a/test/integration_test.go b/test/integration_test.go index 1394d12..d71bc58 100644 --- a/test/integration_test.go +++ b/test/integration_test.go @@ -120,7 +120,7 @@ func TestFmtCommand(t *testing.T) { formatter.Format(config, &buf) output := buf.String() - + // Check for indentation if !strings.Contains(output, " Class = \"MyClass\"") { t.Error("Expected 2-space indentation for Class field") @@ -169,7 +169,7 @@ func TestBuildCommand(t *testing.T) { // Test Merge files := []string{"integration/build_merge_1.marte", "integration/build_merge_2.marte"} b := builder.NewBuilder(files) - + outputFile, err := os.Create("build_test/TEST.marte") if err != nil { t.Fatalf("Failed to create output file: %v", err) @@ -180,23 +180,23 @@ func TestBuildCommand(t *testing.T) { if err != nil { t.Fatalf("Build failed: %v", err) } - + // Check output existence if _, err := os.Stat("build_test/TEST.marte"); os.IsNotExist(err) { t.Fatalf("Expected output file build_test/TEST.marte not found") } - + content, _ := ioutil.ReadFile("build_test/TEST.marte") output := string(content) - + if !strings.Contains(output, "FieldA = 1") || !strings.Contains(output, "FieldB = 2") { t.Error("Merged output missing fields") } - + // Test Order (Class First) filesOrder := []string{"integration/build_order_1.marte", "integration/build_order_2.marte"} bOrder := builder.NewBuilder(filesOrder) - + outputFileOrder, err := os.Create("build_test/ORDER.marte") if err != nil { t.Fatalf("Failed to create output file: %v", err) @@ -207,18 +207,18 @@ func TestBuildCommand(t *testing.T) { if err != nil { t.Fatalf("Build order test failed: %v", err) } - + contentOrder, _ := ioutil.ReadFile("build_test/ORDER.marte") outputOrder := string(contentOrder) - + // Check for Class before Field classIdx := strings.Index(outputOrder, "Class = \"Ordered\"") fieldIdx := strings.Index(outputOrder, "Field = 1") - + if classIdx == -1 || fieldIdx == -1 { t.Fatal("Missing Class or Field in ordered output") } if classIdx > fieldIdx { t.Error("Expected Class to appear before Field in merged output") } -} \ No newline at end of file +} diff --git a/test/lsp_doc_test.go b/test/lsp_doc_test.go index 97d585a..96141dd 100644 --- a/test/lsp_doc_test.go +++ b/test/lsp_doc_test.go @@ -30,28 +30,28 @@ func TestLSPHoverDoc(t *testing.T) { file := "doc.marte" idx.AddFile(file, config) idx.ResolveReferences() - + // Test 1: Hover over +MyObject definition res := idx.Query(file, 4, 2) // Line 4: +MyObject if res == nil || res.Node == nil { t.Fatal("Query failed for definition") } - + expectedDoc := "Object Documentation\nSecond line" if res.Node.Doc != expectedDoc { t.Errorf("Expected definition doc:\n%q\nGot:\n%q", expectedDoc, res.Node.Doc) } - + // Test 2: Hover over MyObject reference resRef := idx.Query(file, 10, 16) // Line 10: RefField = MyObject if resRef == nil || resRef.Reference == nil { t.Fatal("Query failed for reference") } - + if resRef.Reference.Target == nil { t.Fatal("Reference target not resolved") } - + if resRef.Reference.Target.Doc != expectedDoc { t.Errorf("Expected reference target definition doc:\n%q\nGot:\n%q", expectedDoc, resRef.Reference.Target.Doc) } diff --git a/test/lsp_signal_test.go b/test/lsp_signal_test.go index ae34a44..e4f1214 100644 --- a/test/lsp_signal_test.go +++ b/test/lsp_signal_test.go @@ -49,17 +49,23 @@ func TestLSPSignalReferences(t *testing.T) { if root == nil { t.Fatal("Root node not found") } - + // Traverse to MySig dataNode := root.Children["Data"] - if dataNode == nil { t.Fatal("Data node not found") } - + if dataNode == nil { + t.Fatal("Data node not found") + } + myDS := dataNode.Children["MyDS"] - if myDS == nil { t.Fatal("MyDS node not found") } - + if myDS == nil { + t.Fatal("MyDS node not found") + } + signals := myDS.Children["Signals"] - if signals == nil { t.Fatal("Signals node not found") } - + 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") @@ -84,4 +90,4 @@ func TestLSPSignalReferences(t *testing.T) { if foundRefs != 1 { t.Errorf("Expected 1 reference (Direct), found %d", foundRefs) } -} \ No newline at end of file +} diff --git a/test/lsp_test.go b/test/lsp_test.go index cee130c..d5d06e3 100644 --- a/test/lsp_test.go +++ b/test/lsp_test.go @@ -26,14 +26,14 @@ func loadConfig(t *testing.T, filename string) *parser.Configuration { func TestLSPDiagnostics(t *testing.T) { inputFile := "integration/check_dup.marte" config := loadConfig(t, inputFile) - + // Simulate LSP logic: Build Index -> Validate idx := index.NewProjectTree() idx.AddFile(inputFile, config) - + v := validator.NewValidator(idx, ".") v.ValidateProject() - + // Check for expected diagnostics found := false for _, d := range v.Diagnostics { @@ -51,7 +51,7 @@ func TestLSPDiagnostics(t *testing.T) { } // For GoToDefinition and References, we need to test the Indexer's ability to resolve symbols. -// Currently, my Indexer (ProjectTree) stores structure but doesn't explicitly track +// Currently, my Indexer (ProjectTree) stores structure but doesn't explicitly track // "references" in a way that maps a source position to a target symbol yet. // The ProjectTree is built for structure merging. // To support LSP "Go To Definition", we need to map usage -> definition. @@ -63,7 +63,7 @@ func TestLSPDiagnostics(t *testing.T) { // Previously (before rewrite), `index.go` had `References []Reference`. // I removed it during the rewrite to ProjectTree! -// I need to re-implement reference tracking in `ProjectTree` or a parallel structure +// I need to re-implement reference tracking in `ProjectTree` or a parallel structure // to support LSP features. func TestLSPDefinition(t *testing.T) { // Create a virtual file content with a definition and a reference @@ -94,15 +94,15 @@ func TestLSPDefinition(t *testing.T) { break } } - + if foundRef == nil { t.Fatal("Reference to MyObject not found in index") } - + if foundRef.Target == nil { t.Fatal("Reference to MyObject was not resolved to a target") } - + if foundRef.Target.RealName != "+MyObject" { t.Errorf("Expected target to be +MyObject, got %s", foundRef.Target.RealName) } @@ -123,19 +123,19 @@ func TestLSPHover(t *testing.T) { idx := index.NewProjectTree() file := "hover.marte" idx.AddFile(file, config) - + // +MyObject is at line 2. // Query at line 2, col 2 (on 'M' of MyObject) res := idx.Query(file, 2, 2) - + if res == nil { t.Fatal("Query returned nil") } - + if res.Node == nil { t.Fatal("Expected Node result") } - + if res.Node.RealName != "+MyObject" { t.Errorf("Expected +MyObject, got %s", res.Node.RealName) } diff --git a/test/validator_functions_array_test.go b/test/validator_functions_array_test.go index 7568a5e..cd70105 100644 --- a/test/validator_functions_array_test.go +++ b/test/validator_functions_array_test.go @@ -49,13 +49,13 @@ func TestFunctionsArrayValidation(t *testing.T) { for _, d := range v.Diagnostics { if strings.Contains(d.Message, "not found or is not a valid GAM") { - // This covers both InvalidGAM and MissingGAM cases - if strings.Contains(d.Message, "InvalidGAM") { - foundInvalid = true - } - if strings.Contains(d.Message, "MissingGAM") { - foundMissing = true - } + // This covers both InvalidGAM and MissingGAM cases + if strings.Contains(d.Message, "InvalidGAM") { + foundInvalid = true + } + if strings.Contains(d.Message, "MissingGAM") { + foundMissing = true + } } if strings.Contains(d.Message, "must contain references") { foundNotRef = true diff --git a/test/validator_gam_signals_test.go b/test/validator_gam_signals_test.go index 54fd515..bcdf4d8 100644 --- a/test/validator_gam_signals_test.go +++ b/test/validator_gam_signals_test.go @@ -91,10 +91,10 @@ func TestGAMSignalValidation(t *testing.T) { } if !foundBadInput || !foundMissing || !foundBadOutput { - for _, d := range v.Diagnostics { - t.Logf("Diagnostic: %s", d.Message) - } - } + for _, d := range v.Diagnostics { + t.Logf("Diagnostic: %s", d.Message) + } + } if !foundBadInput { t.Error("Expected error for OutDS in InputSignals") diff --git a/test/validator_global_pragma_debug_test.go b/test/validator_global_pragma_debug_test.go index 8060cc7..516c51a 100644 --- a/test/validator_global_pragma_debug_test.go +++ b/test/validator_global_pragma_debug_test.go @@ -21,23 +21,23 @@ func TestGlobalPragmaDebug(t *testing.T) { if err != nil { t.Fatalf("Parse failed: %v", err) } - - // Check if pragma parsed - if len(config.Pragmas) == 0 { - t.Fatal("Pragma not parsed") - } - t.Logf("Parsed Pragma 0: %s", config.Pragmas[0].Text) + + // Check if pragma parsed + if len(config.Pragmas) == 0 { + t.Fatal("Pragma not parsed") + } + t.Logf("Parsed Pragma 0: %s", config.Pragmas[0].Text) idx := index.NewProjectTree() idx.AddFile("debug.marte", config) - idx.ResolveReferences() - - // Check if added to GlobalPragmas - pragmas, ok := idx.GlobalPragmas["debug.marte"] - if !ok || len(pragmas) == 0 { - t.Fatal("GlobalPragmas not populated") - } - t.Logf("Global Pragma stored: %s", pragmas[0]) + idx.ResolveReferences() + + // Check if added to GlobalPragmas + pragmas, ok := idx.GlobalPragmas["debug.marte"] + if !ok || len(pragmas) == 0 { + t.Fatal("GlobalPragmas not populated") + } + t.Logf("Global Pragma stored: %s", pragmas[0]) v := validator.NewValidator(idx, ".") v.ValidateProject() @@ -48,11 +48,11 @@ func TestGlobalPragmaDebug(t *testing.T) { for _, d := range v.Diagnostics { if strings.Contains(d.Message, "Implicitly Defined Signal") { foundImplicitWarning = true - t.Logf("Found warning: %s", d.Message) + t.Logf("Found warning: %s", d.Message) } if strings.Contains(d.Message, "Unused GAM") { foundUnusedWarning = true - t.Logf("Found warning: %s", d.Message) + t.Logf("Found warning: %s", d.Message) } } diff --git a/test/validator_implicit_signal_test.go b/test/validator_implicit_signal_test.go index a2e0163..dec475f 100644 --- a/test/validator_implicit_signal_test.go +++ b/test/validator_implicit_signal_test.go @@ -64,10 +64,10 @@ func TestImplicitSignal(t *testing.T) { } if !foundWarning || foundError { - for _, d := range v.Diagnostics { - t.Logf("Diagnostic: %s", d.Message) - } - } + for _, d := range v.Diagnostics { + t.Logf("Diagnostic: %s", d.Message) + } + } if !foundWarning { t.Error("Expected warning for ImplicitSig") @@ -83,9 +83,9 @@ func TestImplicitSignal(t *testing.T) { ` p2 := parser.NewParser(contentMissingType) config2, err2 := p2.Parse() - if err2 != nil { - t.Fatalf("Parse2 failed: %v", err2) - } + if err2 != nil { + t.Fatalf("Parse2 failed: %v", err2) + } idx2 := index.NewProjectTree() idx2.AddFile("missing_type.marte", config2) idx2.ResolveReferences() @@ -99,9 +99,9 @@ func TestImplicitSignal(t *testing.T) { } } if !foundTypeErr { - for _, d := range v2.Diagnostics { - t.Logf("Diagnostic2: %s", d.Message) - } + for _, d := range v2.Diagnostics { + t.Logf("Diagnostic2: %s", d.Message) + } t.Error("Expected error for missing Type in implicit signal") } } diff --git a/test/validator_multifile_test.go b/test/validator_multifile_test.go index 72a2f0f..143540b 100644 --- a/test/validator_multifile_test.go +++ b/test/validator_multifile_test.go @@ -32,18 +32,18 @@ func TestMultiFileNodeValidation(t *testing.T) { // Resolving references might be needed if the validator relies on it for merging implicitly // But primarily we want to check if the validator sees the merged node. - // The current implementation of Validator likely iterates over the ProjectTree. - // If the ProjectTree doesn't merge nodes automatically, the Validator needs to do it. - // However, the spec says "The build tool, validator, and LSP must merge these definitions". - // Let's assume the Validator or Index does the merging logic. - + // The current implementation of Validator likely iterates over the ProjectTree. + // If the ProjectTree doesn't merge nodes automatically, the Validator needs to do it. + // However, the spec says "The build tool, validator, and LSP must merge these definitions". + // Let's assume the Validator or Index does the merging logic. + v := validator.NewValidator(idx, ".") v.ValidateProject() - // +MyNode is split. - // valid_1 has FieldA - // valid_2 has Class and FieldB - // If merging works, it should have a Class, so no error about missing Class. + // +MyNode is split. + // valid_1 has FieldA + // valid_2 has Class and FieldB + // If merging works, it should have a Class, so no error about missing Class. for _, diag := range v.Diagnostics { if strings.Contains(diag.Message, "must contain a 'Class' field") { @@ -79,14 +79,14 @@ func TestMultiFileReference(t *testing.T) { parseAndAddToIndex(t, idx, "integration/multifile_ref_2.marte") idx.ResolveReferences() - - // Check if the reference in +SourceNode to TargetNode is resolved. + + // Check if the reference in +SourceNode to TargetNode is resolved. v := validator.NewValidator(idx, ".") v.ValidateProject() - - if len(v.Diagnostics) > 0 { - // Filter out irrelevant errors - } + + if len(v.Diagnostics) > 0 { + // Filter out irrelevant errors + } } func TestHierarchicalPackageMerge(t *testing.T) { @@ -99,13 +99,13 @@ func TestHierarchicalPackageMerge(t *testing.T) { // +MyObj should have Class (from file 1) and FieldX (from file 2). // If Class is missing, ValidateProject reports error. - + for _, diag := range v.Diagnostics { if strings.Contains(diag.Message, "must contain a 'Class' field") { t.Errorf("Unexpected 'Class' field error for +MyObj: %s", diag.Message) } } - + // We can also inspect the tree to verify FieldX is there (optional, but good for confidence) baseNode := idx.Root.Children["Base"] if baseNode == nil { @@ -115,7 +115,7 @@ func TestHierarchicalPackageMerge(t *testing.T) { if objNode == nil { t.Fatal("MyObj node not found in Base") } - + hasFieldX := false for _, frag := range objNode.Fragments { for _, def := range frag.Definitions { @@ -124,7 +124,7 @@ func TestHierarchicalPackageMerge(t *testing.T) { } } } - + if !hasFieldX { t.Error("FieldX not found in +MyObj") } @@ -153,44 +153,44 @@ func TestHierarchicalDuplicate(t *testing.T) { func TestIsolatedFileValidation(t *testing.T) { idx := index.NewProjectTree() - - // File 1: Has package. Defines SharedClass. - f1Content := ` + + // File 1: Has package. Defines SharedClass. + f1Content := ` #package Proj.Pkg +SharedObj = { Class = SharedClass } ` - p1 := parser.NewParser(f1Content) - c1, _ := p1.Parse() - idx.AddFile("shared.marte", c1) - - // File 2: No package. References SharedObj. - // Should NOT resolve to SharedObj in shared.marte because iso.marte is isolated. - f2Content := ` + p1 := parser.NewParser(f1Content) + c1, _ := p1.Parse() + idx.AddFile("shared.marte", c1) + + // File 2: No package. References SharedObj. + // Should NOT resolve to SharedObj in shared.marte because iso.marte is isolated. + f2Content := ` +IsoObj = { Class = "MyClass" Ref = SharedObj } ` - p2 := parser.NewParser(f2Content) - c2, _ := p2.Parse() - idx.AddFile("iso.marte", c2) - - idx.ResolveReferences() - - // Find reference - var ref *index.Reference - for i := range idx.References { - if idx.References[i].File == "iso.marte" && idx.References[i].Name == "SharedObj" { - ref = &idx.References[i] - break - } - } - - if ref == nil { - t.Fatal("Reference SharedObj not found in index") - } - - if ref.Target != nil { - t.Errorf("Expected reference in isolated file to be unresolved, but got target in %s", ref.Target.Fragments[0].File) - } + p2 := parser.NewParser(f2Content) + c2, _ := p2.Parse() + idx.AddFile("iso.marte", c2) + + idx.ResolveReferences() + + // Find reference + var ref *index.Reference + for i := range idx.References { + if idx.References[i].File == "iso.marte" && idx.References[i].Name == "SharedObj" { + ref = &idx.References[i] + break + } + } + + if ref == nil { + t.Fatal("Reference SharedObj not found in index") + } + + if ref.Target != nil { + t.Errorf("Expected reference in isolated file to be unresolved, but got target in %s", ref.Target.Fragments[0].File) + } }