Implemented more robust LSP diagnostics and better parsing logic
This commit is contained in:
@@ -435,7 +435,7 @@ func (pt *ProjectTree) ResolveReferences() {
|
||||
continue
|
||||
}
|
||||
|
||||
ref.Target = pt.resolveScopedName(container, ref.Name)
|
||||
ref.Target = pt.ResolveName(container, ref.Name, nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -617,51 +617,19 @@ func (pt *ProjectTree) findNodeContaining(node *ProjectNode, file string, pos pa
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pt *ProjectTree) resolveScopedName(ctx *ProjectNode, name string) *ProjectNode {
|
||||
func (pt *ProjectTree) ResolveName(ctx *ProjectNode, name string, predicate func(*ProjectNode) bool) *ProjectNode {
|
||||
if ctx == nil {
|
||||
return pt.FindNode(pt.Root, name, nil)
|
||||
return pt.FindNode(pt.Root, name, predicate)
|
||||
}
|
||||
|
||||
parts := strings.Split(name, ".")
|
||||
first := parts[0]
|
||||
normFirst := NormalizeName(first)
|
||||
|
||||
var startNode *ProjectNode
|
||||
curr := ctx
|
||||
|
||||
for curr != nil {
|
||||
if child, ok := curr.Children[normFirst]; ok {
|
||||
startNode = child
|
||||
break
|
||||
if found := pt.FindNode(curr, name, predicate); found != nil {
|
||||
return found
|
||||
}
|
||||
curr = curr.Parent
|
||||
}
|
||||
|
||||
if startNode == nil && ctx != pt.Root {
|
||||
if child, ok := pt.Root.Children[normFirst]; ok {
|
||||
startNode = child
|
||||
}
|
||||
}
|
||||
|
||||
if startNode == nil {
|
||||
// Fallback to deep search from context root
|
||||
root := ctx
|
||||
for root.Parent != nil {
|
||||
root = root.Parent
|
||||
}
|
||||
return pt.FindNode(root, name, nil)
|
||||
}
|
||||
|
||||
curr = startNode
|
||||
for i := 1; i < len(parts); i++ {
|
||||
norm := NormalizeName(parts[i])
|
||||
if child, ok := curr.Children[norm]; ok {
|
||||
curr = child
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return curr
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pt *ProjectTree) ResolveVariable(ctx *ProjectNode, name string) *VariableInfo {
|
||||
|
||||
@@ -336,13 +336,9 @@ func HandleDidOpen(params DidOpenTextDocumentParams) {
|
||||
path := uriToPath(params.TextDocument.URI)
|
||||
Documents[params.TextDocument.URI] = params.TextDocument.Text
|
||||
p := parser.NewParser(params.TextDocument.Text)
|
||||
config, err := p.Parse()
|
||||
config, _ := p.Parse()
|
||||
|
||||
if err != nil {
|
||||
publishParserError(params.TextDocument.URI, err)
|
||||
} else {
|
||||
publishParserError(params.TextDocument.URI, nil)
|
||||
}
|
||||
publishParserErrors(params.TextDocument.URI, p.Errors())
|
||||
|
||||
if config != nil {
|
||||
Tree.AddFile(path, config)
|
||||
@@ -369,13 +365,9 @@ func HandleDidChange(params DidChangeTextDocumentParams) {
|
||||
Documents[uri] = text
|
||||
path := uriToPath(uri)
|
||||
p := parser.NewParser(text)
|
||||
config, err := p.Parse()
|
||||
config, _ := p.Parse()
|
||||
|
||||
if err != nil {
|
||||
publishParserError(uri, err)
|
||||
} else {
|
||||
publishParserError(uri, nil)
|
||||
}
|
||||
publishParserErrors(uri, p.Errors())
|
||||
|
||||
if config != nil {
|
||||
Tree.AddFile(path, config)
|
||||
@@ -465,6 +457,9 @@ func runValidation(_ string) {
|
||||
// Collect all known files to ensure we clear diagnostics for fixed files
|
||||
knownFiles := make(map[string]bool)
|
||||
collectFiles(Tree.Root, knownFiles)
|
||||
for _, node := range Tree.IsolatedFiles {
|
||||
collectFiles(node, knownFiles)
|
||||
}
|
||||
|
||||
// Initialize all known files with empty diagnostics
|
||||
for f := range knownFiles {
|
||||
@@ -473,8 +468,10 @@ func runValidation(_ string) {
|
||||
|
||||
for _, d := range v.Diagnostics {
|
||||
severity := 1 // Error
|
||||
levelStr := "ERROR"
|
||||
if d.Level == validator.LevelWarning {
|
||||
severity = 2 // Warning
|
||||
levelStr = "WARNING"
|
||||
}
|
||||
|
||||
diag := LSPDiagnostic{
|
||||
@@ -483,7 +480,7 @@ func runValidation(_ string) {
|
||||
End: Position{Line: d.Position.Line - 1, Character: d.Position.Column - 1 + 10}, // Arbitrary length
|
||||
},
|
||||
Severity: severity,
|
||||
Message: d.Message,
|
||||
Message: fmt.Sprintf("%s: %s", levelStr, d.Message),
|
||||
Source: "mdt",
|
||||
}
|
||||
|
||||
@@ -508,44 +505,36 @@ func runValidation(_ string) {
|
||||
}
|
||||
}
|
||||
|
||||
func publishParserError(uri string, err error) {
|
||||
if err == nil {
|
||||
notification := JsonRpcMessage{
|
||||
Jsonrpc: "2.0",
|
||||
Method: "textDocument/publishDiagnostics",
|
||||
Params: mustMarshal(PublishDiagnosticsParams{
|
||||
URI: uri,
|
||||
Diagnostics: []LSPDiagnostic{},
|
||||
}),
|
||||
}
|
||||
send(notification)
|
||||
return
|
||||
}
|
||||
func publishParserErrors(uri string, errors []error) {
|
||||
diagnostics := []LSPDiagnostic{}
|
||||
|
||||
var line, col int
|
||||
var msg string
|
||||
// Try parsing "line:col: message"
|
||||
n, _ := fmt.Sscanf(err.Error(), "%d:%d: ", &line, &col)
|
||||
if n == 2 {
|
||||
parts := strings.SplitN(err.Error(), ": ", 2)
|
||||
if len(parts) == 2 {
|
||||
msg = parts[1]
|
||||
for _, err := range errors {
|
||||
var line, col int
|
||||
var msg string
|
||||
// Try parsing "line:col: message"
|
||||
n, _ := fmt.Sscanf(err.Error(), "%d:%d: ", &line, &col)
|
||||
if n == 2 {
|
||||
parts := strings.SplitN(err.Error(), ": ", 2)
|
||||
if len(parts) == 2 {
|
||||
msg = parts[1]
|
||||
}
|
||||
} else {
|
||||
// Fallback
|
||||
line = 1
|
||||
col = 1
|
||||
msg = err.Error()
|
||||
}
|
||||
} else {
|
||||
// Fallback
|
||||
line = 1
|
||||
col = 1
|
||||
msg = err.Error()
|
||||
}
|
||||
|
||||
diag := LSPDiagnostic{
|
||||
Range: Range{
|
||||
Start: Position{Line: line - 1, Character: col - 1},
|
||||
End: Position{Line: line - 1, Character: col},
|
||||
},
|
||||
Severity: 1, // Error
|
||||
Message: msg,
|
||||
Source: "mdt-parser",
|
||||
diag := LSPDiagnostic{
|
||||
Range: Range{
|
||||
Start: Position{Line: line - 1, Character: col - 1},
|
||||
End: Position{Line: line - 1, Character: col},
|
||||
},
|
||||
Severity: 1, // Error
|
||||
Message: msg,
|
||||
Source: "mdt-parser",
|
||||
}
|
||||
diagnostics = append(diagnostics, diag)
|
||||
}
|
||||
|
||||
notification := JsonRpcMessage{
|
||||
@@ -553,13 +542,16 @@ func publishParserError(uri string, err error) {
|
||||
Method: "textDocument/publishDiagnostics",
|
||||
Params: mustMarshal(PublishDiagnosticsParams{
|
||||
URI: uri,
|
||||
Diagnostics: []LSPDiagnostic{diag},
|
||||
Diagnostics: diagnostics,
|
||||
}),
|
||||
}
|
||||
send(notification)
|
||||
}
|
||||
|
||||
func collectFiles(node *index.ProjectNode, files map[string]bool) {
|
||||
if node == nil {
|
||||
return
|
||||
}
|
||||
for _, frag := range node.Fragments {
|
||||
files[frag.File] = true
|
||||
}
|
||||
|
||||
@@ -299,6 +299,8 @@ func (p *Parser) parseAtom() (Value, bool) {
|
||||
return &ReferenceValue{Position: tok.Position, Value: tok.Value}, true
|
||||
case TokenVariableReference:
|
||||
return &VariableReferenceValue{Position: tok.Position, Name: tok.Value}, true
|
||||
case TokenObjectIdentifier:
|
||||
return &VariableReferenceValue{Position: tok.Position, Name: tok.Value}, true
|
||||
case TokenLBrace:
|
||||
arr := &ArrayValue{Position: tok.Position}
|
||||
for {
|
||||
@@ -380,3 +382,7 @@ func (p *Parser) parseVariableDefinition(startTok Token) (Definition, bool) {
|
||||
DefaultValue: defVal,
|
||||
}, true
|
||||
}
|
||||
|
||||
func (p *Parser) Errors() []error {
|
||||
return p.errors
|
||||
}
|
||||
|
||||
@@ -304,7 +304,7 @@ func (v *Validator) validateGAMSignal(gamNode, signalNode *index.ProjectNode, di
|
||||
return // Ignore implicit signals or missing datasource (handled elsewhere if mandatory)
|
||||
}
|
||||
|
||||
dsNode := v.resolveReference(dsName, v.getNodeFile(signalNode), isDataSource)
|
||||
dsNode := v.resolveReference(dsName, signalNode, isDataSource)
|
||||
if dsNode == nil {
|
||||
v.Diagnostics = append(v.Diagnostics, Diagnostic{
|
||||
Level: LevelError,
|
||||
@@ -565,17 +565,8 @@ func (v *Validator) getFieldValue(f *parser.Field, ctx *index.ProjectNode) strin
|
||||
return ""
|
||||
}
|
||||
|
||||
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.Tree.FindNode(isoNode, name, predicate); found != nil {
|
||||
return found
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if v.Tree.Root == nil {
|
||||
return nil
|
||||
}
|
||||
return v.Tree.FindNode(v.Tree.Root, name, predicate)
|
||||
func (v *Validator) resolveReference(name string, ctx *index.ProjectNode, predicate func(*index.ProjectNode) bool) *index.ProjectNode {
|
||||
return v.Tree.ResolveName(ctx, name, predicate)
|
||||
}
|
||||
|
||||
func (v *Validator) getNodeClass(node *index.ProjectNode) string {
|
||||
@@ -740,7 +731,7 @@ func (v *Validator) checkFunctionsArray(node *index.ProjectNode, fields map[stri
|
||||
if arr, ok := f.Value.(*parser.ArrayValue); ok {
|
||||
for _, elem := range arr.Elements {
|
||||
if ref, ok := elem.(*parser.ReferenceValue); ok {
|
||||
target := v.resolveReference(ref.Value, v.getNodeFile(node), isGAM)
|
||||
target := v.resolveReference(ref.Value, node, isGAM)
|
||||
if target == nil {
|
||||
v.Diagnostics = append(v.Diagnostics, Diagnostic{
|
||||
Level: LevelError,
|
||||
@@ -799,19 +790,20 @@ func (v *Validator) CheckDataSourceThreading() {
|
||||
return
|
||||
}
|
||||
|
||||
// 1. Find RealTimeApplication
|
||||
var appNode *index.ProjectNode
|
||||
var appNodes []*index.ProjectNode
|
||||
findApp := func(n *index.ProjectNode) {
|
||||
if cls, ok := n.Metadata["Class"]; ok && cls == "RealTimeApplication" {
|
||||
appNode = n
|
||||
appNodes = append(appNodes, n)
|
||||
}
|
||||
}
|
||||
v.Tree.Walk(findApp)
|
||||
|
||||
if appNode == nil {
|
||||
return
|
||||
for _, appNode := range appNodes {
|
||||
v.checkAppDataSourceThreading(appNode)
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Validator) checkAppDataSourceThreading(appNode *index.ProjectNode) {
|
||||
// 2. Find States
|
||||
var statesNode *index.ProjectNode
|
||||
if s, ok := appNode.Children["States"]; ok {
|
||||
@@ -882,7 +874,7 @@ func (v *Validator) getThreadGAMs(thread *index.ProjectNode) []*index.ProjectNod
|
||||
if arr, ok := f.Value.(*parser.ArrayValue); ok {
|
||||
for _, elem := range arr.Elements {
|
||||
if ref, ok := elem.(*parser.ReferenceValue); ok {
|
||||
target := v.resolveReference(ref.Value, v.getNodeFile(thread), isGAM)
|
||||
target := v.resolveReference(ref.Value, thread, isGAM)
|
||||
if target != nil {
|
||||
gams = append(gams, target)
|
||||
}
|
||||
@@ -904,7 +896,7 @@ func (v *Validator) getGAMDataSources(gam *index.ProjectNode) []*index.ProjectNo
|
||||
fields := v.getFields(sig)
|
||||
if dsFields, ok := fields["DataSource"]; ok && len(dsFields) > 0 {
|
||||
dsName := v.getFieldValue(dsFields[0], sig)
|
||||
dsNode := v.resolveReference(dsName, v.getNodeFile(sig), isDataSource)
|
||||
dsNode := v.resolveReference(dsName, sig, isDataSource)
|
||||
if dsNode != nil {
|
||||
dsMap[dsNode] = true
|
||||
}
|
||||
@@ -938,18 +930,20 @@ func (v *Validator) CheckINOUTOrdering() {
|
||||
return
|
||||
}
|
||||
|
||||
var appNode *index.ProjectNode
|
||||
var appNodes []*index.ProjectNode
|
||||
findApp := func(n *index.ProjectNode) {
|
||||
if cls, ok := n.Metadata["Class"]; ok && cls == "RealTimeApplication" {
|
||||
appNode = n
|
||||
appNodes = append(appNodes, n)
|
||||
}
|
||||
}
|
||||
v.Tree.Walk(findApp)
|
||||
|
||||
if appNode == nil {
|
||||
return
|
||||
for _, appNode := range appNodes {
|
||||
v.checkAppINOUTOrdering(appNode)
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Validator) checkAppINOUTOrdering(appNode *index.ProjectNode) {
|
||||
var statesNode *index.ProjectNode
|
||||
if s, ok := appNode.Children["States"]; ok {
|
||||
statesNode = s
|
||||
@@ -1049,7 +1043,7 @@ func (v *Validator) processGAMSignalsForOrdering(gam *index.ProjectNode, contain
|
||||
if dsNode == nil {
|
||||
if dsFields, ok := fields["DataSource"]; ok && len(dsFields) > 0 {
|
||||
dsName := v.getFieldValue(dsFields[0], sig)
|
||||
dsNode = v.resolveReference(dsName, v.getNodeFile(sig), isDataSource)
|
||||
dsNode = v.resolveReference(dsName, sig, isDataSource)
|
||||
}
|
||||
if aliasFields, ok := fields["Alias"]; ok && len(aliasFields) > 0 {
|
||||
sigName = v.getFieldValue(aliasFields[0], sig)
|
||||
|
||||
Reference in New Issue
Block a user