bootstrapped
This commit is contained in:
@@ -21,9 +21,11 @@ type Reference struct {
|
|||||||
type ProjectNode struct {
|
type ProjectNode struct {
|
||||||
Name string // Normalized name
|
Name string // Normalized name
|
||||||
RealName string // The actual name used in definition (e.g. +Node)
|
RealName string // The actual name used in definition (e.g. +Node)
|
||||||
|
Doc string // Aggregated documentation
|
||||||
Fragments []*Fragment
|
Fragments []*Fragment
|
||||||
Children map[string]*ProjectNode
|
Children map[string]*ProjectNode
|
||||||
Parent *ProjectNode
|
Parent *ProjectNode
|
||||||
|
Metadata map[string]string // Store extra info like Class, Type, Size
|
||||||
}
|
}
|
||||||
|
|
||||||
type Fragment struct {
|
type Fragment struct {
|
||||||
@@ -31,12 +33,14 @@ type Fragment struct {
|
|||||||
Definitions []parser.Definition
|
Definitions []parser.Definition
|
||||||
IsObject bool
|
IsObject bool
|
||||||
ObjectPos parser.Position
|
ObjectPos parser.Position
|
||||||
|
Doc string // Documentation for this fragment (if object)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProjectTree() *ProjectTree {
|
func NewProjectTree() *ProjectTree {
|
||||||
return &ProjectTree{
|
return &ProjectTree{
|
||||||
Root: &ProjectNode{
|
Root: &ProjectNode{
|
||||||
Children: make(map[string]*ProjectNode),
|
Children: make(map[string]*ProjectNode),
|
||||||
|
Metadata: make(map[string]string),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,7 +53,6 @@ func NormalizeName(name string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pt *ProjectTree) RemoveFile(file string) {
|
func (pt *ProjectTree) RemoveFile(file string) {
|
||||||
// Remove references from this file
|
|
||||||
newRefs := []Reference{}
|
newRefs := []Reference{}
|
||||||
for _, ref := range pt.References {
|
for _, ref := range pt.References {
|
||||||
if ref.File != file {
|
if ref.File != file {
|
||||||
@@ -58,7 +61,6 @@ func (pt *ProjectTree) RemoveFile(file string) {
|
|||||||
}
|
}
|
||||||
pt.References = newRefs
|
pt.References = newRefs
|
||||||
|
|
||||||
// Remove fragments from tree
|
|
||||||
pt.removeFileFromNode(pt.Root, file)
|
pt.removeFileFromNode(pt.Root, file)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,13 +73,60 @@ func (pt *ProjectTree) removeFileFromNode(node *ProjectNode, file string) {
|
|||||||
}
|
}
|
||||||
node.Fragments = newFragments
|
node.Fragments = newFragments
|
||||||
|
|
||||||
|
// Re-aggregate documentation
|
||||||
|
node.Doc = ""
|
||||||
|
for _, frag := range node.Fragments {
|
||||||
|
if frag.Doc != "" {
|
||||||
|
if node.Doc != "" {
|
||||||
|
node.Doc += "\n\n"
|
||||||
|
}
|
||||||
|
node.Doc += frag.Doc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-aggregate metadata
|
||||||
|
node.Metadata = make(map[string]string)
|
||||||
|
pt.rebuildMetadata(node)
|
||||||
|
|
||||||
for _, child := range node.Children {
|
for _, child := range node.Children {
|
||||||
pt.removeFileFromNode(child, file)
|
pt.removeFileFromNode(child, file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pt *ProjectTree) rebuildMetadata(node *ProjectNode) {
|
||||||
|
for _, frag := range node.Fragments {
|
||||||
|
for _, def := range frag.Definitions {
|
||||||
|
if f, ok := def.(*parser.Field); ok {
|
||||||
|
pt.extractFieldMetadata(node, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pt *ProjectTree) extractFieldMetadata(node *ProjectNode, f *parser.Field) {
|
||||||
|
key := f.Name
|
||||||
|
val := ""
|
||||||
|
switch v := f.Value.(type) {
|
||||||
|
case *parser.StringValue:
|
||||||
|
val = v.Value
|
||||||
|
case *parser.ReferenceValue:
|
||||||
|
val = v.Value
|
||||||
|
case *parser.IntValue:
|
||||||
|
val = v.Raw
|
||||||
|
}
|
||||||
|
|
||||||
|
if val == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capture relevant fields
|
||||||
|
if key == "Class" || key == "Type" || key == "NumberOfElements" || key == "NumberOfDimensions" || key == "DataSource" {
|
||||||
|
node.Metadata[key] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (pt *ProjectTree) AddFile(file string, config *parser.Configuration) {
|
func (pt *ProjectTree) AddFile(file string, config *parser.Configuration) {
|
||||||
pt.RemoveFile(file) // Ensure clean state for this file
|
pt.RemoveFile(file)
|
||||||
|
|
||||||
node := pt.Root
|
node := pt.Root
|
||||||
if config.Package != nil {
|
if config.Package != nil {
|
||||||
@@ -93,6 +142,7 @@ func (pt *ProjectTree) AddFile(file string, config *parser.Configuration) {
|
|||||||
RealName: part,
|
RealName: part,
|
||||||
Children: make(map[string]*ProjectNode),
|
Children: make(map[string]*ProjectNode),
|
||||||
Parent: node,
|
Parent: node,
|
||||||
|
Metadata: make(map[string]string),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
node = node.Children[part]
|
node = node.Children[part]
|
||||||
@@ -105,10 +155,13 @@ func (pt *ProjectTree) AddFile(file string, config *parser.Configuration) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, def := range config.Definitions {
|
for _, def := range config.Definitions {
|
||||||
|
doc := pt.findDoc(config.Comments, def.Pos())
|
||||||
|
|
||||||
switch d := def.(type) {
|
switch d := def.(type) {
|
||||||
case *parser.Field:
|
case *parser.Field:
|
||||||
fileFragment.Definitions = append(fileFragment.Definitions, d)
|
fileFragment.Definitions = append(fileFragment.Definitions, d)
|
||||||
pt.indexValue(file, d.Value)
|
pt.indexValue(file, d.Value)
|
||||||
|
// Metadata update not really relevant for package node usually, but consistency
|
||||||
case *parser.ObjectNode:
|
case *parser.ObjectNode:
|
||||||
norm := NormalizeName(d.Name)
|
norm := NormalizeName(d.Name)
|
||||||
if _, ok := node.Children[norm]; !ok {
|
if _, ok := node.Children[norm]; !ok {
|
||||||
@@ -117,13 +170,22 @@ func (pt *ProjectTree) AddFile(file string, config *parser.Configuration) {
|
|||||||
RealName: d.Name,
|
RealName: d.Name,
|
||||||
Children: make(map[string]*ProjectNode),
|
Children: make(map[string]*ProjectNode),
|
||||||
Parent: node,
|
Parent: node,
|
||||||
|
Metadata: make(map[string]string),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
child := node.Children[norm]
|
child := node.Children[norm]
|
||||||
if child.RealName == norm && d.Name != norm {
|
if child.RealName == norm && d.Name != norm {
|
||||||
child.RealName = d.Name
|
child.RealName = d.Name
|
||||||
}
|
}
|
||||||
pt.addObjectFragment(child, file, d)
|
|
||||||
|
if doc != "" {
|
||||||
|
if child.Doc != "" {
|
||||||
|
child.Doc += "\n\n"
|
||||||
|
}
|
||||||
|
child.Doc += doc
|
||||||
|
}
|
||||||
|
|
||||||
|
pt.addObjectFragment(child, file, d, doc, config.Comments)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,18 +194,22 @@ func (pt *ProjectTree) AddFile(file string, config *parser.Configuration) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pt *ProjectTree) addObjectFragment(node *ProjectNode, file string, obj *parser.ObjectNode) {
|
func (pt *ProjectTree) addObjectFragment(node *ProjectNode, file string, obj *parser.ObjectNode, doc string, comments []parser.Comment) {
|
||||||
frag := &Fragment{
|
frag := &Fragment{
|
||||||
File: file,
|
File: file,
|
||||||
IsObject: true,
|
IsObject: true,
|
||||||
ObjectPos: obj.Position,
|
ObjectPos: obj.Position,
|
||||||
|
Doc: doc,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, def := range obj.Subnode.Definitions {
|
for _, def := range obj.Subnode.Definitions {
|
||||||
|
subDoc := pt.findDoc(comments, def.Pos())
|
||||||
|
|
||||||
switch d := def.(type) {
|
switch d := def.(type) {
|
||||||
case *parser.Field:
|
case *parser.Field:
|
||||||
frag.Definitions = append(frag.Definitions, d)
|
frag.Definitions = append(frag.Definitions, d)
|
||||||
pt.indexValue(file, d.Value)
|
pt.indexValue(file, d.Value)
|
||||||
|
pt.extractFieldMetadata(node, d)
|
||||||
case *parser.ObjectNode:
|
case *parser.ObjectNode:
|
||||||
norm := NormalizeName(d.Name)
|
norm := NormalizeName(d.Name)
|
||||||
if _, ok := node.Children[norm]; !ok {
|
if _, ok := node.Children[norm]; !ok {
|
||||||
@@ -152,19 +218,66 @@ func (pt *ProjectTree) addObjectFragment(node *ProjectNode, file string, obj *pa
|
|||||||
RealName: d.Name,
|
RealName: d.Name,
|
||||||
Children: make(map[string]*ProjectNode),
|
Children: make(map[string]*ProjectNode),
|
||||||
Parent: node,
|
Parent: node,
|
||||||
|
Metadata: make(map[string]string),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
child := node.Children[norm]
|
child := node.Children[norm]
|
||||||
if child.RealName == norm && d.Name != norm {
|
if child.RealName == norm && d.Name != norm {
|
||||||
child.RealName = d.Name
|
child.RealName = d.Name
|
||||||
}
|
}
|
||||||
pt.addObjectFragment(child, file, d)
|
|
||||||
|
if subDoc != "" {
|
||||||
|
if child.Doc != "" {
|
||||||
|
child.Doc += "\n\n"
|
||||||
|
}
|
||||||
|
child.Doc += subDoc
|
||||||
|
}
|
||||||
|
|
||||||
|
pt.addObjectFragment(child, file, d, subDoc, comments)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
node.Fragments = append(node.Fragments, frag)
|
node.Fragments = append(node.Fragments, frag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pt *ProjectTree) findDoc(comments []parser.Comment, pos parser.Position) string {
|
||||||
|
var docBuilder strings.Builder
|
||||||
|
targetLine := pos.Line - 1
|
||||||
|
var docIndices []int
|
||||||
|
|
||||||
|
for i := len(comments) - 1; i >= 0; i-- {
|
||||||
|
c := comments[i]
|
||||||
|
if c.Position.Line > pos.Line {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if c.Position.Line == pos.Line {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Position.Line == targetLine {
|
||||||
|
if c.Doc {
|
||||||
|
docIndices = append(docIndices, i)
|
||||||
|
targetLine--
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else if c.Position.Line < targetLine {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := len(docIndices) - 1; i >= 0; i-- {
|
||||||
|
txt := strings.TrimPrefix(comments[docIndices[i]].Text, "//#")
|
||||||
|
txt = strings.TrimSpace(txt)
|
||||||
|
if docBuilder.Len() > 0 {
|
||||||
|
docBuilder.WriteString("\n")
|
||||||
|
}
|
||||||
|
docBuilder.WriteString(txt)
|
||||||
|
}
|
||||||
|
|
||||||
|
return docBuilder.String()
|
||||||
|
}
|
||||||
|
|
||||||
func (pt *ProjectTree) indexValue(file string, val parser.Value) {
|
func (pt *ProjectTree) indexValue(file string, val parser.Value) {
|
||||||
switch v := val.(type) {
|
switch v := val.(type) {
|
||||||
case *parser.ReferenceValue:
|
case *parser.ReferenceValue:
|
||||||
@@ -199,7 +312,6 @@ func (pt *ProjectTree) findNode(root *ProjectNode, name string) *ProjectNode {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// QueryResult holds the result of a query at a position
|
|
||||||
type QueryResult struct {
|
type QueryResult struct {
|
||||||
Node *ProjectNode
|
Node *ProjectNode
|
||||||
Field *parser.Field
|
Field *parser.Field
|
||||||
@@ -207,38 +319,29 @@ type QueryResult struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pt *ProjectTree) Query(file string, line, col int) *QueryResult {
|
func (pt *ProjectTree) Query(file string, line, col int) *QueryResult {
|
||||||
// 1. Check References
|
|
||||||
for i := range pt.References {
|
for i := range pt.References {
|
||||||
ref := &pt.References[i]
|
ref := &pt.References[i]
|
||||||
if ref.File == file {
|
if ref.File == file {
|
||||||
// Check if pos is within reference range
|
|
||||||
// Approx length
|
|
||||||
if line == ref.Position.Line && col >= ref.Position.Column && col < ref.Position.Column+len(ref.Name) {
|
if line == ref.Position.Line && col >= ref.Position.Column && col < ref.Position.Column+len(ref.Name) {
|
||||||
return &QueryResult{Reference: ref}
|
return &QueryResult{Reference: ref}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Check Definitions (traverse tree)
|
|
||||||
return pt.queryNode(pt.Root, file, line, col)
|
return pt.queryNode(pt.Root, file, line, col)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pt *ProjectTree) queryNode(node *ProjectNode, file string, line, col int) *QueryResult {
|
func (pt *ProjectTree) queryNode(node *ProjectNode, file string, line, col int) *QueryResult {
|
||||||
for _, frag := range node.Fragments {
|
for _, frag := range node.Fragments {
|
||||||
if frag.File == file {
|
if frag.File == file {
|
||||||
// Check Object definition itself
|
|
||||||
if frag.IsObject {
|
if frag.IsObject {
|
||||||
// Object definition usually starts at 'Name'.
|
|
||||||
// Position is start of Name.
|
|
||||||
if line == frag.ObjectPos.Line && col >= frag.ObjectPos.Column && col < frag.ObjectPos.Column+len(node.RealName) {
|
if line == frag.ObjectPos.Line && col >= frag.ObjectPos.Column && col < frag.ObjectPos.Column+len(node.RealName) {
|
||||||
return &QueryResult{Node: node}
|
return &QueryResult{Node: node}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check definitions in fragment
|
|
||||||
for _, def := range frag.Definitions {
|
for _, def := range frag.Definitions {
|
||||||
if f, ok := def.(*parser.Field); ok {
|
if f, ok := def.(*parser.Field); ok {
|
||||||
// Check field name range
|
|
||||||
if line == f.Position.Line && col >= f.Position.Column && col < f.Position.Column+len(f.Name) {
|
if line == f.Position.Line && col >= f.Position.Column && col < f.Position.Column+len(f.Name) {
|
||||||
return &QueryResult{Field: f}
|
return &QueryResult{Field: f}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,7 +174,6 @@ func handleDidChange(params DidChangeTextDocumentParams) {
|
|||||||
if len(params.ContentChanges) == 0 {
|
if len(params.ContentChanges) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Full sync: text is in ContentChanges[0].Text
|
|
||||||
text := params.ContentChanges[0].Text
|
text := params.ContentChanges[0].Text
|
||||||
path := uriToPath(params.TextDocument.URI)
|
path := uriToPath(params.TextDocument.URI)
|
||||||
p := parser.NewParser(text)
|
p := parser.NewParser(text)
|
||||||
@@ -187,7 +186,6 @@ func handleDidChange(params DidChangeTextDocumentParams) {
|
|||||||
|
|
||||||
func handleHover(params HoverParams) *Hover {
|
func handleHover(params HoverParams) *Hover {
|
||||||
path := uriToPath(params.TextDocument.URI)
|
path := uriToPath(params.TextDocument.URI)
|
||||||
// LSP 0-based to Parser 1-based
|
|
||||||
line := params.Position.Line + 1
|
line := params.Position.Line + 1
|
||||||
col := params.Position.Character + 1
|
col := params.Position.Character + 1
|
||||||
|
|
||||||
@@ -199,28 +197,26 @@ func handleHover(params HoverParams) *Hover {
|
|||||||
var content string
|
var content string
|
||||||
|
|
||||||
if res.Node != nil {
|
if res.Node != nil {
|
||||||
// Try to find Class field
|
content = formatNodeInfo(res.Node)
|
||||||
class := "Unknown"
|
|
||||||
for _, frag := range res.Node.Fragments {
|
|
||||||
for _, def := range frag.Definitions {
|
|
||||||
if f, ok := def.(*parser.Field); ok && f.Name == "Class" {
|
|
||||||
if s, ok := f.Value.(*parser.StringValue); ok {
|
|
||||||
class = s.Value
|
|
||||||
} else if r, ok := f.Value.(*parser.ReferenceValue); ok {
|
|
||||||
class = r.Value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
content = fmt.Sprintf("**Object**: `%s`\n\n**Class**: `%s`", res.Node.RealName, class)
|
|
||||||
} else if res.Field != nil {
|
} else if res.Field != nil {
|
||||||
content = fmt.Sprintf("**Field**: `%s`", res.Field.Name)
|
content = fmt.Sprintf("**Field**: `%s`", res.Field.Name)
|
||||||
} else if res.Reference != nil {
|
} else if res.Reference != nil {
|
||||||
targetName := "Unresolved"
|
targetName := "Unresolved"
|
||||||
|
fullInfo := ""
|
||||||
|
targetDoc := ""
|
||||||
|
|
||||||
if res.Reference.Target != nil {
|
if res.Reference.Target != nil {
|
||||||
targetName = res.Reference.Target.RealName
|
targetName = res.Reference.Target.RealName
|
||||||
|
targetDoc = res.Reference.Target.Doc
|
||||||
|
fullInfo = formatNodeInfo(res.Reference.Target)
|
||||||
}
|
}
|
||||||
|
|
||||||
content = fmt.Sprintf("**Reference**: `%s` -> `%s`", res.Reference.Name, targetName)
|
content = fmt.Sprintf("**Reference**: `%s` -> `%s`", res.Reference.Name, targetName)
|
||||||
|
if fullInfo != "" {
|
||||||
|
content += fmt.Sprintf("\n\n---\n%s", fullInfo)
|
||||||
|
} else if targetDoc != "" { // Fallback if formatNodeInfo returned empty (unlikely)
|
||||||
|
content += fmt.Sprintf("\n\n%s", targetDoc)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if content == "" {
|
if content == "" {
|
||||||
@@ -235,6 +231,42 @@ func handleHover(params HoverParams) *Hover {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func formatNodeInfo(node *index.ProjectNode) string {
|
||||||
|
class := node.Metadata["Class"]
|
||||||
|
if class == "" {
|
||||||
|
class = "Unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
info := fmt.Sprintf("**Object**: `%s`\n\n**Class**: `%s`", node.RealName, class)
|
||||||
|
|
||||||
|
// Check if it's a Signal (has Type or DataSource)
|
||||||
|
typ := node.Metadata["Type"]
|
||||||
|
ds := node.Metadata["DataSource"]
|
||||||
|
|
||||||
|
if typ != "" || ds != "" {
|
||||||
|
sigInfo := "\n"
|
||||||
|
if typ != "" {
|
||||||
|
sigInfo += fmt.Sprintf("**Type**: `%s` ", typ)
|
||||||
|
}
|
||||||
|
if ds != "" {
|
||||||
|
sigInfo += fmt.Sprintf("**DataSource**: `%s` ", ds)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size
|
||||||
|
dims := node.Metadata["NumberOfDimensions"]
|
||||||
|
elems := node.Metadata["NumberOfElements"]
|
||||||
|
if dims != "" || elems != "" {
|
||||||
|
sigInfo += fmt.Sprintf("**Size**: `[%s]`, `%s` dims ", elems, dims)
|
||||||
|
}
|
||||||
|
info += sigInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
if node.Doc != "" {
|
||||||
|
info += fmt.Sprintf("\n\n%s", node.Doc)
|
||||||
|
}
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
func respond(id interface{}, result interface{}) {
|
func respond(id interface{}, result interface{}) {
|
||||||
msg := JsonRpcMessage{
|
msg := JsonRpcMessage{
|
||||||
Jsonrpc: "2.0",
|
Jsonrpc: "2.0",
|
||||||
|
|||||||
@@ -62,11 +62,16 @@ func (v *Validator) validateNode(node *index.ProjectNode) {
|
|||||||
// Root node usually doesn't have a name or is implicit
|
// Root node usually doesn't have a name or is implicit
|
||||||
if node.RealName != "" && (node.RealName[0] == '+' || node.RealName[0] == '$') {
|
if node.RealName != "" && (node.RealName[0] == '+' || node.RealName[0] == '$') {
|
||||||
hasClass := false
|
hasClass := false
|
||||||
|
hasType := false
|
||||||
for _, frag := range node.Fragments {
|
for _, frag := range node.Fragments {
|
||||||
for _, def := range frag.Definitions {
|
for _, def := range frag.Definitions {
|
||||||
if f, ok := def.(*parser.Field); ok && f.Name == "Class" {
|
if f, ok := def.(*parser.Field); ok {
|
||||||
|
if f.Name == "Class" {
|
||||||
hasClass = true
|
hasClass = true
|
||||||
break
|
}
|
||||||
|
if f.Name == "Type" {
|
||||||
|
hasType = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if hasClass {
|
if hasClass {
|
||||||
@@ -74,7 +79,7 @@ func (v *Validator) validateNode(node *index.ProjectNode) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !hasClass {
|
if !hasClass && !hasType {
|
||||||
// Report error on the first fragment's position
|
// Report error on the first fragment's position
|
||||||
pos := parser.Position{Line: 1, Column: 1}
|
pos := parser.Position{Line: 1, Column: 1}
|
||||||
file := ""
|
file := ""
|
||||||
@@ -84,7 +89,7 @@ func (v *Validator) validateNode(node *index.ProjectNode) {
|
|||||||
}
|
}
|
||||||
v.Diagnostics = append(v.Diagnostics, Diagnostic{
|
v.Diagnostics = append(v.Diagnostics, Diagnostic{
|
||||||
Level: LevelError,
|
Level: LevelError,
|
||||||
Message: fmt.Sprintf("Node %s is an object and must contain a 'Class' field", node.RealName),
|
Message: fmt.Sprintf("Node %s is an object and must contain a 'Class' field (or be a Signal with 'Type')", node.RealName),
|
||||||
Position: pos,
|
Position: pos,
|
||||||
File: file,
|
File: file,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -74,6 +74,7 @@ The LSP server should provide the following capabilities:
|
|||||||
|
|
||||||
- **Nodes (`+` / `$`)**: The prefixes `+` and `$` indicate that the node represents an object.
|
- **Nodes (`+` / `$`)**: The prefixes `+` and `$` indicate that the node represents an object.
|
||||||
- **Constraint**: These nodes *must* contain a field named `Class` within their subnode definition.
|
- **Constraint**: These nodes *must* contain a field named `Class` within their subnode definition.
|
||||||
|
- **Signals**: Signals are considered nodes but **not** objects. They do not require a `Class` field.
|
||||||
- **Pragmas (`//!`)**: Used to suppress specific diagnostics. The developer can use these to explain why a rule is being ignored.
|
- **Pragmas (`//!`)**: Used to suppress specific diagnostics. The developer can use these to explain why a rule is being ignored.
|
||||||
- **Structure**: A configuration is composed by one or more definitions.
|
- **Structure**: A configuration is composed by one or more definitions.
|
||||||
|
|
||||||
|
|||||||
@@ -79,6 +79,30 @@ func TestCheckDuplicate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSignalNoClassValidation(t *testing.T) {
|
||||||
|
inputFile := "integration/signal_no_class.marte"
|
||||||
|
content, err := ioutil.ReadFile(inputFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to read %s: %v", inputFile, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
p := parser.NewParser(string(content))
|
||||||
|
config, err := p.Parse()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Parse failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
idx := index.NewProjectTree()
|
||||||
|
idx.AddFile(inputFile, config)
|
||||||
|
|
||||||
|
v := validator.NewValidator(idx)
|
||||||
|
v.ValidateProject()
|
||||||
|
|
||||||
|
if len(v.Diagnostics) > 0 {
|
||||||
|
t.Errorf("Expected no errors for signal without Class, but got: %v", v.Diagnostics)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestFmtCommand(t *testing.T) {
|
func TestFmtCommand(t *testing.T) {
|
||||||
inputFile := "integration/fmt.marte"
|
inputFile := "integration/fmt.marte"
|
||||||
content, err := ioutil.ReadFile(inputFile)
|
content, err := ioutil.ReadFile(inputFile)
|
||||||
|
|||||||
Reference in New Issue
Block a user