Improving LSP
This commit is contained in:
@@ -442,6 +442,36 @@ func (v *Validator) validateGAMSignal(gamNode, signalNode *index.ProjectNode, di
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate Value initialization
|
||||||
|
if valField, hasValue := fields["Value"]; hasValue && len(valField) > 0 {
|
||||||
|
var typeStr string
|
||||||
|
if typeFields, ok := fields["Type"]; ok && len(typeFields) > 0 {
|
||||||
|
typeStr = v.getFieldValue(typeFields[0], signalNode)
|
||||||
|
} else if signalNode.Target != nil {
|
||||||
|
if t, ok := signalNode.Target.Metadata["Type"]; ok {
|
||||||
|
typeStr = t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if typeStr != "" && v.Schema != nil {
|
||||||
|
ctx := v.Schema.Context
|
||||||
|
typeVal := ctx.CompileString(typeStr)
|
||||||
|
if typeVal.Err() == nil {
|
||||||
|
valInterface := v.valueToInterface(valField[0].Value, signalNode)
|
||||||
|
valVal := ctx.Encode(valInterface)
|
||||||
|
res := typeVal.Unify(valVal)
|
||||||
|
if err := res.Validate(cue.Concrete(true)); err != nil {
|
||||||
|
v.Diagnostics = append(v.Diagnostics, Diagnostic{
|
||||||
|
Level: LevelError,
|
||||||
|
Message: fmt.Sprintf("Value initialization mismatch for signal '%s': %v", signalNode.RealName, err),
|
||||||
|
Position: valField[0].Position,
|
||||||
|
File: v.getNodeFile(signalNode),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Validator) checkSignalProperty(gamSig, dsSig *index.ProjectNode, prop string) {
|
func (v *Validator) checkSignalProperty(gamSig, dsSig *index.ProjectNode, prop string) {
|
||||||
|
|||||||
@@ -79,11 +79,6 @@ func TestLSPAppTestRepro(t *testing.T) {
|
|||||||
t.Error("LSP missing unresolved variable error")
|
t.Error("LSP missing unresolved variable error")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check INOUT consumed but not produced
|
|
||||||
if !strings.Contains(output, "consumed by GAM '+FnA'") {
|
|
||||||
t.Error("LSP missing consumed but not produced error")
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.Failed() {
|
if t.Failed() {
|
||||||
t.Log(output)
|
t.Log(output)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,13 +92,7 @@ func TestLSPDiagnosticsAppTest(t *testing.T) {
|
|||||||
t.Error("Missing diagnostic for unresolved variable '@Value'")
|
t.Error("Missing diagnostic for unresolved variable '@Value'")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Check INOUT Ordering Error (Signal A consumed but not produced)
|
// 2. Check INOUT Unused Warning (Signal B produced but not consumed)
|
||||||
// Message format: INOUT Signal 'A' (DS '+DDB') is consumed by GAM '+FnA' ... before being produced ...
|
|
||||||
if !strings.Contains(output, "INOUT Signal 'A'") || !strings.Contains(output, "before being produced") {
|
|
||||||
t.Error("Missing diagnostic for INOUT ordering error (Signal A)")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Check INOUT Unused Warning (Signal B produced but not consumed)
|
|
||||||
// Message format: INOUT Signal 'B' ... produced ... but never consumed ...
|
// Message format: INOUT Signal 'B' ... produced ... but never consumed ...
|
||||||
if !strings.Contains(output, "INOUT Signal 'B'") || !strings.Contains(output, "never consumed") {
|
if !strings.Contains(output, "INOUT Signal 'B'") || !strings.Contains(output, "never consumed") {
|
||||||
t.Error("Missing diagnostic for unused INOUT signal (Signal B)")
|
t.Error("Missing diagnostic for unused INOUT signal (Signal B)")
|
||||||
|
|||||||
44
test/lsp_value_validation_test.go
Normal file
44
test/lsp_value_validation_test.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/marte-community/marte-dev-tools/internal/index"
|
||||||
|
"github.com/marte-community/marte-dev-tools/internal/lsp"
|
||||||
|
"github.com/marte-community/marte-dev-tools/internal/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLSPValueValidation(t *testing.T) {
|
||||||
|
lsp.Tree = index.NewProjectTree()
|
||||||
|
lsp.Documents = make(map[string]string)
|
||||||
|
lsp.GlobalSchema = schema.LoadFullSchema(".")
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
lsp.Output = &buf
|
||||||
|
|
||||||
|
content := `
|
||||||
|
+Data = {
|
||||||
|
Class = ReferenceContainer
|
||||||
|
+DS = { Class = GAMDataSource Signals = { S = { Type = uint8 } } }
|
||||||
|
}
|
||||||
|
+GAM = {
|
||||||
|
Class = IOGAM
|
||||||
|
InputSignals = {
|
||||||
|
S = { DataSource = DS Type = uint8 Value = 1024 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+App = { Class = RealTimeApplication +States = { Class = ReferenceContainer +S = { Class = RealTimeState Threads = { +T = { Class = RealTimeThread Functions = { GAM } } } } } }
|
||||||
|
`
|
||||||
|
uri := "file://value.marte"
|
||||||
|
lsp.HandleDidOpen(lsp.DidOpenTextDocumentParams{
|
||||||
|
TextDocument: lsp.TextDocumentItem{URI: uri, Text: content},
|
||||||
|
})
|
||||||
|
|
||||||
|
output := buf.String()
|
||||||
|
if !strings.Contains(output, "Value initialization mismatch") {
|
||||||
|
t.Error("LSP did not report value validation error")
|
||||||
|
t.Log(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
101
test/validator_inout_value_test.go
Normal file
101
test/validator_inout_value_test.go
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/marte-community/marte-dev-tools/internal/index"
|
||||||
|
"github.com/marte-community/marte-dev-tools/internal/parser"
|
||||||
|
"github.com/marte-community/marte-dev-tools/internal/validator"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestINOUTValueInitialization(t *testing.T) {
|
||||||
|
content := `
|
||||||
|
+Data = {
|
||||||
|
Class = ReferenceContainer
|
||||||
|
+MyDS = {
|
||||||
|
Class = GAMDataSource
|
||||||
|
#meta = { multithreaded = false }
|
||||||
|
Signals = { Sig1 = { Type = uint32 } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+GAM1 = {
|
||||||
|
Class = IOGAM
|
||||||
|
InputSignals = {
|
||||||
|
Sig1 = {
|
||||||
|
DataSource = MyDS
|
||||||
|
Type = uint32
|
||||||
|
Value = 10 // Initialization
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+GAM2 = {
|
||||||
|
Class = IOGAM
|
||||||
|
InputSignals = {
|
||||||
|
Sig1 = { DataSource = MyDS Type = uint32 } // Consumes initialized signal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+App = {
|
||||||
|
Class = RealTimeApplication
|
||||||
|
+States = {
|
||||||
|
Class = ReferenceContainer
|
||||||
|
+State1 = {
|
||||||
|
Class = RealTimeState
|
||||||
|
+Thread1 = {
|
||||||
|
Class = RealTimeThread
|
||||||
|
Functions = { GAM1, GAM2 } // Should Pass
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
pt := index.NewProjectTree()
|
||||||
|
p := parser.NewParser(content)
|
||||||
|
cfg, err := p.Parse()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
pt.AddFile("main.marte", cfg)
|
||||||
|
|
||||||
|
v := validator.NewValidator(pt, ".")
|
||||||
|
v.ValidateProject()
|
||||||
|
|
||||||
|
for _, d := range v.Diagnostics {
|
||||||
|
if strings.Contains(d.Message, "before being produced") {
|
||||||
|
t.Errorf("Unexpected error: %s", d.Message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestINOUTValueTypeMismatch(t *testing.T) {
|
||||||
|
content := `
|
||||||
|
+Data = { Class = ReferenceContainer +DS = { Class = GAMDataSource #meta = { multithreaded = false } Signals = { S = { Type = uint8 } } } }
|
||||||
|
+GAM1 = {
|
||||||
|
Class = IOGAM
|
||||||
|
InputSignals = {
|
||||||
|
S = { DataSource = DS Type = uint8 Value = 1024 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+App = { Class = RealTimeApplication +States = { Class = ReferenceContainer +S = { Class = RealTimeState Threads = { +T = { Class = RealTimeThread Functions = { GAM1 } } } } } }
|
||||||
|
`
|
||||||
|
pt := index.NewProjectTree()
|
||||||
|
p := parser.NewParser(content)
|
||||||
|
cfg, err := p.Parse()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
pt.AddFile("fail.marte", cfg)
|
||||||
|
|
||||||
|
v := validator.NewValidator(pt, ".")
|
||||||
|
v.ValidateProject()
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, d := range v.Diagnostics {
|
||||||
|
if strings.Contains(d.Message, "Value initialization mismatch") {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Error("Expected Value initialization mismatch error")
|
||||||
|
}
|
||||||
|
}
|
||||||
46
test/validator_unused_value_test.go
Normal file
46
test/validator_unused_value_test.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/marte-community/marte-dev-tools/internal/index"
|
||||||
|
"github.com/marte-community/marte-dev-tools/internal/parser"
|
||||||
|
"github.com/marte-community/marte-dev-tools/internal/validator"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUnusedGAMValueValidation(t *testing.T) {
|
||||||
|
content := `
|
||||||
|
+Data = {
|
||||||
|
Class = ReferenceContainer
|
||||||
|
+DS = { Class = GAMDataSource Signals = { S = { Type = uint8 } } }
|
||||||
|
}
|
||||||
|
+UnusedGAM = {
|
||||||
|
Class = IOGAM
|
||||||
|
InputSignals = {
|
||||||
|
S = { DataSource = DS Type = uint8 Value = 1024 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+App = { Class = RealTimeApplication }
|
||||||
|
`
|
||||||
|
pt := index.NewProjectTree()
|
||||||
|
p := parser.NewParser(content)
|
||||||
|
cfg, err := p.Parse()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
pt.AddFile("unused.marte", cfg)
|
||||||
|
|
||||||
|
v := validator.NewValidator(pt, ".")
|
||||||
|
v.ValidateProject()
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for _, d := range v.Diagnostics {
|
||||||
|
if strings.Contains(d.Message, "Value initialization mismatch") {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Error("Expected Value initialization mismatch error for unused GAM")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user