diff --git a/server/internal/lsp/analysis/hover.go b/server/internal/lsp/analysis/hover.go index 0b2ba21..aea68cf 100644 --- a/server/internal/lsp/analysis/hover.go +++ b/server/internal/lsp/analysis/hover.go @@ -17,7 +17,7 @@ func GetHoverInfo(document *document.Document, pos lsp.Position, storage *docume symbol := symbolResult.Get() var description string - var sizeInfo string + var sizeInfo string // TODO reimplement this var extraLine string switch symbol.Kind { case ast.VAR, ast.CONST: @@ -28,7 +28,7 @@ func GetHoverInfo(document *document.Document, pos lsp.Position, storage *docume case ast.FIELD: switch symbol.Type.NodeDecl.(type) { - case ast.TypeInfo: + case *ast.TypeInfo: description = fmt.Sprintf("%s %s", symbol.Type.Name, symbol.Name) } @@ -47,6 +47,11 @@ func GetHoverInfo(document *document.Document, pos lsp.Position, storage *docume ) } + docComment := symbol.NodeDecl.GetDocComment() + if docComment.IsSome() { + extraLine += "\n\n" + docComment.Get().DisplayBodyWithContracts() + } + isModule := false if !isModule { extraLine += "\n\nIn module **[" + symbol.Module.String() + "]**" diff --git a/server/internal/lsp/ast/ast.go b/server/internal/lsp/ast/ast.go index a8a6385..114202e 100644 --- a/server/internal/lsp/ast/ast.go +++ b/server/internal/lsp/ast/ast.go @@ -56,6 +56,8 @@ type Node interface { EndPosition() lsp.Position GetRange() lsp.Range GetId() NodeId + SetDocComment(docComment *DocComment) + GetDocComment() option.Option[*DocComment] } type Expression interface { @@ -87,12 +89,21 @@ type NodeAttributes struct { Range lsp.Range Attributes []string Id NodeId + DocComment option.Option[*DocComment] } func (n NodeAttributes) StartPosition() lsp.Position { return n.Range.Start } func (n NodeAttributes) EndPosition() lsp.Position { return n.Range.End } func (n NodeAttributes) GetRange() lsp.Range { return n.Range } func (n NodeAttributes) GetId() NodeId { return n.Id } +func (n *NodeAttributes) SetDocComment(docComment *DocComment) { + if docComment != nil { + n.DocComment = option.Some(docComment) + } else { + n.DocComment = option.None[*DocComment]() + } +} +func (n *NodeAttributes) GetDocComment() option.Option[*DocComment] { return n.DocComment } func ChangeNodePosition(n *NodeAttributes, start sitter.Point, end sitter.Point) { n.Range.Start = lsp.Position{Line: uint(start.Row), Column: uint(start.Column)} @@ -145,21 +156,6 @@ type Import struct { func (*Import) stmtNode() {} -// Deprecated: using GenDecl for enums -type EnumProperty struct { - NodeAttributes - Type TypeInfo - Name Ident -} - -// EnumMember -// Deprecated: using GenDecl for enums -type EnumMember struct { - NodeAttributes - Name Ident - Value CompositeLiteral -} - // Deprecated not used type PropertyValue struct { NodeAttributes @@ -167,16 +163,6 @@ type PropertyValue struct { Value Expression } -// StructMemberDecl -// Deprecated -type StructMemberDecl struct { - NodeAttributes - Names []Ident - Type TypeInfo - BitRange option.Option[[2]uint] - IsInlined bool -} - type FaultMember struct { NodeAttributes Name *Ident diff --git a/server/internal/lsp/ast/ast_builder.go b/server/internal/lsp/ast/ast_builder.go index 74863fb..98e7b4a 100644 --- a/server/internal/lsp/ast/ast_builder.go +++ b/server/internal/lsp/ast/ast_builder.go @@ -2,6 +2,7 @@ package ast import ( "github.com/pherrymason/c3-lsp/internal/lsp" + "github.com/pherrymason/c3-lsp/pkg/option" sitter "github.com/smacker/go-tree-sitter" ) @@ -46,9 +47,9 @@ func (d *NodeAttrsBuilder) WithSitterStartEnd(start sitter.Point, end sitter.Poi return d } -func (i *NodeAttrsBuilder) WithSitterPos(node *sitter.Node) *NodeAttrsBuilder { - i.WithSitterStartEnd(node.StartPoint(), node.EndPoint()) - return i +func (d *NodeAttrsBuilder) WithSitterPos(node *sitter.Node) *NodeAttrsBuilder { + d.WithSitterStartEnd(node.StartPoint(), node.EndPoint()) + return d } func (d *NodeAttrsBuilder) WithRangePositions(startRow uint, startCol uint, endRow uint, endCol uint) *NodeAttrsBuilder { @@ -62,6 +63,14 @@ func (d *NodeAttrsBuilder) WithRange(aRange lsp.Range) *NodeAttrsBuilder { return d } +func (d *NodeAttrsBuilder) WithDocComment(docComment string) *NodeAttrsBuilder { + d.bn.DocComment = option.Some(&DocComment{ + body: docComment, + contracts: []*DocCommentContract{}, + }) + return d +} + // -- // IdentifierBuilder // -- @@ -203,13 +212,15 @@ func (b *TypeInfoBuilder) Build() *TypeInfo { // DefDeclBuilder // -- type DefDeclBuilder struct { - d DefDecl - a NodeAttrsBuilder + def DefDecl + a NodeAttrsBuilder } func NewDefDeclBuilder(nodeId NodeId) *DefDeclBuilder { return &DefDeclBuilder{ - d: DefDecl{}, + def: DefDecl{ + Ident: NewIdentifierBuilder().Build(), + }, a: *NewNodeAttributesBuilder(), } } @@ -220,23 +231,23 @@ func (b *DefDeclBuilder) WithSitterPos(node *sitter.Node) *DefDeclBuilder { } func (b *DefDeclBuilder) WithName(name string) *DefDeclBuilder { - b.d.Name.Name = name + b.def.Ident.Name = name return b } func (b *DefDeclBuilder) WithIdentifierSitterPos(node *sitter.Node) *DefDeclBuilder { - b.d.Name.Range.Start = lsp.Position{uint(node.StartPoint().Row), uint(node.StartPoint().Column)} - b.d.Name.Range.End = lsp.Position{uint(node.EndPoint().Row), uint(node.EndPoint().Column)} + b.def.Ident.Range.Start = lsp.Position{uint(node.StartPoint().Row), uint(node.StartPoint().Column)} + b.def.Ident.Range.End = lsp.Position{uint(node.EndPoint().Row), uint(node.EndPoint().Column)} return b } func (b *DefDeclBuilder) WithExpression(expression Expression) *DefDeclBuilder { - b.d.Expr = expression + b.def.Expr = expression return b } func (b *DefDeclBuilder) Build() DefDecl { - def := b.d + def := b.def def.NodeAttributes = b.a.Build() return def diff --git a/server/internal/lsp/ast/doc_comments.go b/server/internal/lsp/ast/doc_comments.go new file mode 100644 index 0000000..40c87be --- /dev/null +++ b/server/internal/lsp/ast/doc_comments.go @@ -0,0 +1,51 @@ +package ast + +type DocCommentContract struct { + name string + body string +} + +type DocComment struct { + body string + contracts []*DocCommentContract +} + +// Creates a doc comment with the given body. +func NewDocComment(body string) *DocComment { + return &DocComment{ + body: body, + contracts: []*DocCommentContract{}, + } +} + +// Creates a contract with the given name and body. +// It is expected that the name begins with '@'. +func NewDocCommentContract(name string, body string) *DocCommentContract { + return &DocCommentContract{ + name, + body, + } +} + +// Add contracts to the given doc comment. +func (d *DocComment) AddContracts(contracts []*DocCommentContract) { + d.contracts = append(d.contracts, contracts...) +} + +func (d *DocComment) GetBody() string { + return d.body +} + +// Return a string displaying the body and contracts as markdown. +func (d *DocComment) DisplayBodyWithContracts() string { + out := d.body + + for _, c := range d.contracts { + out += "\n\n**" + c.name + "**" + if c.body != "" { + out += " " + c.body + } + } + + return out +} diff --git a/server/internal/lsp/ast/factory/convert.go b/server/internal/lsp/ast/factory/convert.go index acf8d3b..bdc78a7 100644 --- a/server/internal/lsp/ast/factory/convert.go +++ b/server/internal/lsp/ast/factory/convert.go @@ -71,11 +71,13 @@ func (c *ASTConverter) ConvertToAST(cstNode *sitter.Node, sourceCode string, fil anonymousModule := false var lastMod *ast.Module var lastNode *sitter.Node + var lastDocComment *ast.DocComment + var node *sitter.Node for i := 0; i < int(cstNode.ChildCount()); i++ { node = cstNode.Child(i) parsedModulesCount := len(prg.Modules) - if parsedModulesCount == 0 && node.Type() != "module" { + if parsedModulesCount == 0 && node.Type() != "module" && node.Type() != "doc_comment" { anonymousModule = true prg.AddModule( ast.NewModule(0, symbols.NormalizeModuleName(fileName), lsp.NewRangeFromSitterNode(node), prg), @@ -85,7 +87,6 @@ func (c *ASTConverter) ConvertToAST(cstNode *sitter.Node, sourceCode string, fil if parsedModulesCount > 0 { lastMod = prg.Modules[len(prg.Modules)-1] - if anonymousModule && node.Type() != "module" { // Update end position lastMod.NodeAttributes.Range.End = lsp.Position{Line: uint(node.EndPoint().Row), Column: uint(node.EndPoint().Column)} @@ -93,6 +94,8 @@ func (c *ASTConverter) ConvertToAST(cstNode *sitter.Node, sourceCode string, fil } switch node.Type() { + case "doc_comment": + lastDocComment = c.convert_doc_comment(node, source) case "module": if anonymousModule { anonymousModule = false @@ -100,6 +103,10 @@ func (c *ASTConverter) ConvertToAST(cstNode *sitter.Node, sourceCode string, fil } module := convert_module(node, source) + if lastDocComment != nil { + module.SetDocComment(lastDocComment) + } + prg.AddModule(module) if lastMod != nil && lastNode != nil { // Update range end of module @@ -115,42 +122,87 @@ func (c *ASTConverter) ConvertToAST(cstNode *sitter.Node, sourceCode string, fil lastMod.Imports = append(lastMod.Imports, anImport) case "global_declaration": - variable := c.convert_global_declaration(node, source) - lastMod.Declarations = append(lastMod.Declarations, &variable) + declaration := c.convert_global_declaration(node, source) + if lastDocComment != nil { + declaration.SetDocComment(lastDocComment) + } + lastMod.Declarations = append(lastMod.Declarations, &declaration) case "enum_declaration": - lastMod.Declarations = append(lastMod.Declarations, c.convert_enum_declaration(node, source)) + declaration := c.convert_enum_declaration(node, source) + if lastDocComment != nil { + declaration.SetDocComment(lastDocComment) + } + lastMod.Declarations = append(lastMod.Declarations, declaration) case "struct_declaration": - lastMod.Declarations = append(lastMod.Declarations, c.convert_struct_declaration(node, source)) + declaration := c.convert_struct_declaration(node, source) + if lastDocComment != nil { + declaration.SetDocComment(lastDocComment) + } + lastMod.Declarations = append(lastMod.Declarations, declaration) case "bitstruct_declaration": - lastMod.Declarations = append(lastMod.Declarations, c.convert_bitstruct_declaration(node, source)) + declaration := c.convert_bitstruct_declaration(node, source) + if lastDocComment != nil { + declaration.SetDocComment(lastDocComment) + } + lastMod.Declarations = append(lastMod.Declarations, declaration) case "fault_declaration": - lastMod.Declarations = append(lastMod.Declarations, c.convert_fault_declaration(node, source)) + declaration := c.convert_fault_declaration(node, source) + if lastDocComment != nil { + declaration.SetDocComment(lastDocComment) + } + lastMod.Declarations = append(lastMod.Declarations, declaration) case "const_declaration": - lastMod.Declarations = append(lastMod.Declarations, c.convert_const_declaration(node, source)) + declaration := c.convert_const_declaration(node, source) + if lastDocComment != nil { + declaration.SetDocComment(lastDocComment) + } + lastMod.Declarations = append(lastMod.Declarations, declaration) case "define_declaration": - lastMod.Declarations = append(lastMod.Declarations, c.convert_def_declaration(node, source)) + declaration := c.convert_def_declaration(node, source) + if lastDocComment != nil { + declaration.SetDocComment(lastDocComment) + } + lastMod.Declarations = append(lastMod.Declarations, declaration) case "func_definition", "func_declaration": - lastMod.Declarations = append(lastMod.Declarations, c.convert_function_declaration(node, source)) + declaration := c.convert_function_declaration(node, source) + if lastDocComment != nil { + declaration.SetDocComment(lastDocComment) + } + lastMod.Declarations = append(lastMod.Declarations, declaration) case "interface_declaration": - lastMod.Declarations = append(lastMod.Declarations, c.convert_interface_declaration(node, source)) + declaration := c.convert_interface_declaration(node, source) + if lastDocComment != nil { + declaration.SetDocComment(lastDocComment) + } + lastMod.Declarations = append(lastMod.Declarations, declaration) case "macro_declaration": - lastMod.Declarations = append(lastMod.Declarations, c.convert_macro_declaration(node, source)) + declaration := c.convert_macro_declaration(node, source) + if lastDocComment != nil { + declaration.SetDocComment(lastDocComment) + } + lastMod.Declarations = append(lastMod.Declarations, declaration) } lastNode = node } if node != nil { - lastMod.NodeAttributes.Range.End = lsp.Position{Line: uint(node.EndPoint().Row), Column: uint(node.EndPoint().Column)} + lastMod.NodeAttributes.Range.End = + lsp.Position{Line: uint(node.EndPoint().Row), Column: uint(node.EndPoint().Column)} + + if node.Type() != "doc_comment" { + // Ensure the next node won't receive the same doc comment + lastDocComment = nil + } } return prg @@ -1924,6 +1976,60 @@ func (c *ASTConverter) convert_paren_expr(node *sitter.Node, source []byte) ast. } } +/* +// From grammar.js in tree-sitter-c3 v0.2.3: + +// Doc comments and contracts +// ------------------------- +// NOTE parsed by scanner.c (scan_doc_comment_contract_text) +doc_comment_contract: $ => seq( + + field('name', $.at_ident), + optional($.doc_comment_contract_text) + +), +doc_comment: $ => seq( + + '<*', + optional($.doc_comment_text), // NOTE parsed by scanner.c (scan_doc_comment_text) + repeat($.doc_comment_contract), + '*>', + +), + +// (...) + +at_ident: _ => token(seq('@', IDENT)), +*/ +func (c *ASTConverter) convert_doc_comment(node *sitter.Node, sourceCode []byte) *ast.DocComment { + body := "" + bodyNode := node.Child(1) + if bodyNode.Type() == "doc_comment_text" { + body = bodyNode.Content(sourceCode) + } + + docComment := ast.NewDocComment(body) + + if node.ChildCount() >= 4 { + for i := 2; i <= int(node.ChildCount())-2; i++ { + contractNode := node.Child(i) + if contractNode.Type() == "doc_comment_contract" { + name := contractNode.ChildByFieldName("name").Content(sourceCode) + body := "" + if contractNode.ChildCount() >= 2 { + body = contractNode.Child(1).Content(sourceCode) + } + + contract := ast.NewDocCommentContract(name, body) + + docComment.AddContracts([]*ast.DocCommentContract{contract}) + } + } + } + + return docComment +} + func debugNode(node *sitter.Node, source []byte, tag string) { if node == nil { log.Printf("Node is nil\n") diff --git a/server/internal/lsp/ast/factory/convert_declarations_test.go b/server/internal/lsp/ast/factory/convert_declarations_test.go index 7f3f3db..46e26de 100644 --- a/server/internal/lsp/ast/factory/convert_declarations_test.go +++ b/server/internal/lsp/ast/factory/convert_declarations_test.go @@ -53,9 +53,11 @@ func TestConvertToAST_module(t *testing.T) { }) t.Run("multiple named modules", func(t *testing.T) { - source := ` + source := `<* foo is first module *> module foo; int variable = 0; + + <* app is second module *> module app; fn void main() { };` @@ -64,10 +66,12 @@ func TestConvertToAST_module(t *testing.T) { tree := cv.ConvertToAST(GetCST(source), source, "path/file/xxx.c3") assert.Equal(t, "foo", tree.Modules[0].Name) - assert.Equal(t, lsp.NewRange(1, 1, 2, 18), tree.Modules[0].Range, "Range of module foo is wrong") + assert.Equal(t, lsp.NewRange(1, 1, 4, 27), tree.Modules[0].Range, "Range of module foo is wrong") + assert.Equal(t, "foo is first module", tree.Modules[0].DocComment.Get().GetBody()) assert.Equal(t, "app", tree.Modules[1].Name) - assert.Equal(t, lsp.NewRange(3, 1, 5, 3), tree.Modules[1].Range, "Range of module app is wrong") + assert.Equal(t, lsp.NewRange(5, 1, 7, 3), tree.Modules[1].Range, "Range of module app is wrong") + assert.Equal(t, "app is second module", tree.Modules[1].DocComment.Get().GetBody()) }) } @@ -108,89 +112,117 @@ func TestConvertToAST_module_with_imports(t *testing.T) { // ----------------------------------------------------- // Convert global variable declaration -func TestConvertToAST_global_variable_unitialized(t *testing.T) { - source := `module foo; +func TestConvertToAST_global_variable(t *testing.T) { + t.Run("parses global variable unitialized", func(t *testing.T) { + source := `module foo; + <* abc *> int hello;` - cv := newTestAstConverter() - tree := cv.ConvertToAST(GetCST(source), source, "file.c3") + cv := newTestAstConverter() + tree := cv.ConvertToAST(GetCST(source), source, "file.c3") - decl := tree.Modules[0].Declarations[0].(*ast.GenDecl) - spec := decl.Spec.(*ast.ValueSpec) + decl := tree.Modules[0].Declarations[0].(*ast.GenDecl) + spec := decl.Spec.(*ast.ValueSpec) - assert.Equal(t, "hello", spec.Names[0].Name) - assert.Equal(t, lsp.NewRange(1, 5, 1, 10), spec.Names[0].Range) - assert.Equal(t, "int", spec.Type.Identifier.Name) - assert.Equal(t, lsp.NewRange(1, 1, 1, 4), spec.Type.Identifier.Range) -} + assert.Equal(t, "hello", spec.Names[0].Name) + assert.Equal(t, lsp.NewRange(2, 5, 2, 10), spec.Names[0].Range) + assert.Equal(t, "int", spec.Type.Identifier.Name) + assert.Equal(t, lsp.NewRange(2, 1, 2, 4), spec.Type.Identifier.Range) + assert.Equal(t, "abc", decl.DocComment.Get().GetBody()) + }) -func TestConvertToAST_global_variable_with_scalar_initialization(t *testing.T) { - source := `module foo; + t.Run("parses global variable with scalar initialization", func(t *testing.T) { + source := `module foo; int hello = 3;` - cv := newTestAstConverter() - tree := cv.ConvertToAST(GetCST(source), source, "file.c3") + cv := newTestAstConverter() + tree := cv.ConvertToAST(GetCST(source), source, "file.c3") - decl := tree.Modules[0].Declarations[0].(*ast.GenDecl) + decl := tree.Modules[0].Declarations[0].(*ast.GenDecl) - spec := decl.Spec.(*ast.ValueSpec) - assert.Equal(t, "hello", spec.Names[0].Name) - assert.Equal(t, &ast.BasicLit{ - NodeAttributes: ast.NewNodeAttributesBuilder().WithRange(lsp.NewRange(1, 13, 1, 14)).Build(), - Kind: ast.INT, - Value: "3", - }, spec.Value) -} + spec := decl.Spec.(*ast.ValueSpec) + assert.Equal(t, "hello", spec.Names[0].Name) + assert.Equal(t, &ast.BasicLit{ + NodeAttributes: ast.NewNodeAttributesBuilder().WithRange(lsp.NewRange(1, 13, 1, 14)).Build(), + Kind: ast.INT, + Value: "3", + }, spec.Value) + }) -func TestConvertToAST_declare_multiple_variables_in_single_statement(t *testing.T) { - source := `module foo; + t.Run("parses global multiple variables in single statement", func(t *testing.T) { + source := `module foo; int dog, cat, elephant;` - cv := newTestAstConverter() - tree := cv.ConvertToAST(GetCST(source), source, "file.c3") + cv := newTestAstConverter() + tree := cv.ConvertToAST(GetCST(source), source, "file.c3") - decl := tree.Modules[0].Declarations[0].(*ast.GenDecl) - assert.Len(t, decl.Spec.(*ast.ValueSpec).Names, 3) + decl := tree.Modules[0].Declarations[0].(*ast.GenDecl) + assert.Len(t, decl.Spec.(*ast.ValueSpec).Names, 3) + + assert.Equal(t, "dog", decl.Spec.(*ast.ValueSpec).Names[0].Name) + assert.Equal(t, lsp.NewRange(1, 5, 1, 8), decl.Spec.(*ast.ValueSpec).Names[0].Range) + assert.Equal(t, "cat", decl.Spec.(*ast.ValueSpec).Names[1].Name) + assert.Equal(t, lsp.NewRange(1, 10, 1, 13), decl.Spec.(*ast.ValueSpec).Names[1].Range) + assert.Equal(t, "elephant", decl.Spec.(*ast.ValueSpec).Names[2].Name) + assert.Equal(t, lsp.NewRange(1, 15, 1, 23), decl.Spec.(*ast.ValueSpec).Names[2].Range) + assert.Equal(t, "int", decl.Spec.(*ast.ValueSpec).Type.Identifier.Name) + }) + + t.Run("parses constant declaration", func(t *testing.T) { + source := `module foo; + <* abc *> + const int HELLO = 3;` + + cv := newTestAstConverter() + tree := cv.ConvertToAST(GetCST(source), source, "file.c3") - assert.Equal(t, "dog", decl.Spec.(*ast.ValueSpec).Names[0].Name) - assert.Equal(t, lsp.NewRange(1, 5, 1, 8), decl.Spec.(*ast.ValueSpec).Names[0].Range) - assert.Equal(t, "cat", decl.Spec.(*ast.ValueSpec).Names[1].Name) - assert.Equal(t, lsp.NewRange(1, 10, 1, 13), decl.Spec.(*ast.ValueSpec).Names[1].Range) - assert.Equal(t, "elephant", decl.Spec.(*ast.ValueSpec).Names[2].Name) - assert.Equal(t, lsp.NewRange(1, 15, 1, 23), decl.Spec.(*ast.ValueSpec).Names[2].Range) - assert.Equal(t, "int", decl.Spec.(*ast.ValueSpec).Type.Identifier.Name) + decl := tree.Modules[0].Declarations[0].(*ast.GenDecl) + assert.Equal(t, ast.Token(ast.CONST), decl.Token) + assert.Equal(t, "abc", decl.DocComment.Get().GetBody()) + + spec := decl.Spec.(*ast.ValueSpec) + assert.Equal(t, "HELLO", spec.Names[0].Name) + assert.Equal(t, &ast.BasicLit{ + NodeAttributes: ast.NewNodeAttributesBuilder().WithRange(lsp.NewRange(2, 19, 2, 20)).Build(), + Kind: ast.INT, + Value: "3", + }, spec.Value) + }) } // ----------------------------------------------------- func TestConvertToAST_enum_decl(t *testing.T) { - source := `module foo; + t.Run("parses enum decl", func(t *testing.T) { + source := `module foo; + <* colors enum *> enum Colors { RED, BLUE, GREEN } enum TypedColors:int { RED, BLUE, GREEN } // Typed enums` - cv := newTestAstConverter() - tree := cv.ConvertToAST(GetCST(source), source, "file.c3") - - enumDecl := tree.Modules[0].Declarations[0].(*ast.GenDecl) - assert.Equal(t, ast.Token(ast.ENUM), enumDecl.Token) - assert.Equal(t, "Colors", enumDecl.Spec.(*ast.TypeSpec).Name.Name) - assert.Equal(t, lsp.NewRange(1, 1, 1, 33), enumDecl.Range) + cv := newTestAstConverter() + tree := cv.ConvertToAST(GetCST(source), source, "file.c3") - enumType := enumDecl.Spec.(*ast.TypeSpec).TypeDescription.(*ast.EnumType) - assert.Equal(t, option.None[ast.TypeInfo](), enumType.BaseType) - - assert.Equal(t, []ast.Expression{}, enumType.StaticValues, "No fields should be present") - assert.Len(t, enumType.Values, 3) - assert.Equal(t, "RED", enumType.Values[0].Name.Name) - assert.Equal(t, lsp.NewRange(1, 15, 1, 18), enumType.Values[0].Name.Range) - assert.Equal(t, "BLUE", enumType.Values[1].Name.Name) - assert.Equal(t, lsp.NewRange(1, 20, 1, 24), enumType.Values[1].Name.Range) - assert.Equal(t, "GREEN", enumType.Values[2].Name.Name) - assert.Equal(t, lsp.NewRange(1, 26, 1, 31), enumType.Values[2].Name.Range) - - enumDecl = tree.Modules[0].Declarations[1].(*ast.GenDecl) - assert.Equal(t, "int", enumDecl.Spec.(*ast.TypeSpec).TypeDescription.(*ast.EnumType).BaseType.Get().Identifier.Name) - return + enumDecl := tree.Modules[0].Declarations[0].(*ast.GenDecl) + assert.Equal(t, ast.Token(ast.ENUM), enumDecl.Token) + assert.Equal(t, "Colors", enumDecl.Spec.(*ast.TypeSpec).Name.Name) + assert.Equal(t, lsp.NewRange(2, 1, 2, 33), enumDecl.Range) + assert.Equal(t, "colors enum", enumDecl.DocComment.Get().GetBody()) + + enumType := enumDecl.Spec.(*ast.TypeSpec).TypeDescription.(*ast.EnumType) + assert.Equal(t, option.None[*ast.TypeInfo](), enumType.BaseType) + + assert.Equal(t, []ast.Expression{}, enumType.StaticValues, "No fields should be present") + assert.Len(t, enumType.Values, 3) + assert.Equal(t, "RED", enumType.Values[0].Name.Name) + assert.Equal(t, lsp.NewRange(2, 15, 2, 18), enumType.Values[0].Name.Range) + assert.Equal(t, "BLUE", enumType.Values[1].Name.Name) + assert.Equal(t, lsp.NewRange(2, 20, 2, 24), enumType.Values[1].Name.Range) + assert.Equal(t, "GREEN", enumType.Values[2].Name.Name) + assert.Equal(t, lsp.NewRange(2, 26, 2, 31), enumType.Values[2].Name.Range) + + enumDecl = tree.Modules[0].Declarations[1].(*ast.GenDecl) + assert.Equal(t, "int", enumDecl.Spec.(*ast.TypeSpec).TypeDescription.(*ast.EnumType).BaseType.Get().Identifier.Name) + }) } func TestConvertToAST_enum_decl_with_associated_params(t *testing.T) { @@ -210,7 +242,7 @@ func TestConvertToAST_enum_decl_with_associated_params(t *testing.T) { assert.Equal(t, ast.NewIdentifierBuilder(). WithName("desc"). - WithStartEnd(1, 26, 1, 30).BuildPtr(), + WithStartEnd(1, 26, 1, 30).Build(), enumType.StaticValues[0].(*ast.Field).Name, ) assert.Equal(t, "String", enumType.StaticValues[0].(*ast.Field).Type.Identifier.String()) @@ -218,14 +250,14 @@ func TestConvertToAST_enum_decl_with_associated_params(t *testing.T) { assert.Equal(t, ast.NewIdentifierBuilder(). WithName("active"). - WithStartEnd(1, 37, 1, 43).BuildPtr(), + WithStartEnd(1, 37, 1, 43).Build(), enumType.StaticValues[1].(*ast.Field).Name, ) assert.Equal(t, "bool", enumType.StaticValues[1].(*ast.Field).Type.Identifier.String()) assert.Equal(t, ast.NewIdentifierBuilder(). WithName("ke"). - WithStartEnd(1, 50, 1, 52).BuildPtr(), + WithStartEnd(1, 50, 1, 52).Build(), enumType.StaticValues[2].(*ast.Field).Name, ) assert.Equal(t, "char", enumType.StaticValues[2].(*ast.Field).Type.Identifier.String()) @@ -234,6 +266,7 @@ func TestConvertToAST_enum_decl_with_associated_params(t *testing.T) { func TestConvertToAST_struct_decl(t *testing.T) { t.Run("Test basic struct declaration", func(t *testing.T) { source := `module foo; + <* abc *> struct MyStruct { int data; char key; @@ -247,19 +280,20 @@ func TestConvertToAST_struct_decl(t *testing.T) { spec := decl.Spec.(*ast.TypeSpec) structType := spec.TypeDescription.(*ast.StructType) - structRange := lsp.NewRange(1, 1, 5, 2) + structRange := lsp.NewRange(2, 1, 6, 2) assert.Equal(t, structRange, decl.Range) assert.Equal(t, structRange, spec.Range) assert.Equal(t, structRange, structType.Range) assert.Equal(t, "MyStruct", spec.Name.Name) - assert.Equal(t, lsp.NewRange(1, 8, 1, 16), spec.Name.Range) + assert.Equal(t, lsp.NewRange(2, 8, 2, 16), spec.Name.Range) + assert.Equal(t, "abc", decl.DocComment.Get().GetBody()) assert.Equal(t, &ast.StructField{ - NodeAttributes: aWithPos(2, 2, 2, 11), + NodeAttributes: aWithPos(3, 2, 3, 11), Names: []*ast.Ident{ { - NodeAttributes: aWithPos(2, 6, 2, 10), + NodeAttributes: aWithPos(3, 6, 3, 10), Name: "data", }, }, @@ -271,36 +305,36 @@ func TestConvertToAST_struct_decl(t *testing.T) { }, structType.Fields[0]) assert.Equal(t, &ast.StructField{ - NodeAttributes: aWithPos(3, 2, 3, 11), + NodeAttributes: aWithPos(4, 2, 4, 11), Names: []*ast.Ident{ { - NodeAttributes: aWithPos(3, 7, 3, 10), + NodeAttributes: aWithPos(4, 7, 4, 10), Name: "key", }, }, - Type: ast.TypeInfo{ - NodeAttributes: aWithPos(3, 2, 3, 6), - Identifier: ast.NewIdentifierBuilder().WithName("char").WithStartEnd(3, 2, 3, 6).BuildPtr(), + Type: &ast.TypeInfo{ + NodeAttributes: aWithPos(4, 2, 4, 6), + Identifier: ast.NewIdentifierBuilder().WithName("char").WithStartEnd(4, 2, 4, 6).Build(), BuiltIn: true, }, }, structType.Fields[1]) assert.Equal(t, &ast.StructField{ - NodeAttributes: aWithPos(4, 2, 4, 24), + NodeAttributes: aWithPos(5, 2, 5, 24), Names: []*ast.Ident{ { - NodeAttributes: aWithPos(4, 17, 4, 23), + NodeAttributes: aWithPos(5, 17, 5, 23), Name: "camera", }, }, - Type: ast.TypeInfo{ - NodeAttributes: aWithPos(4, 2, 4, 16), + Type: &ast.TypeInfo{ + NodeAttributes: aWithPos(5, 2, 5, 16), Identifier: &ast.Ident{ - NodeAttributes: ast.NewNodeAttributesBuilder().WithRangePositions(4, 2, 4, 16).Build(), + NodeAttributes: ast.NewNodeAttributesBuilder().WithRangePositions(5, 2, 5, 16).Build(), ModulePath: ast.NewIdentifierBuilder(). WithName("raylib"). - WithStartEnd(4, 2, 4, 10). - BuildPtr(), + WithStartEnd(5, 2, 5, 10). + Build(), Name: "Camera", }, BuiltIn: false, @@ -353,9 +387,9 @@ func TestConvertToAST_struct_decl(t *testing.T) { assert.Equal(t, lsp.NewRange(3, 5, 3, 11), subField.Range) assert.Equal(t, "a", subField.Names[0].Name) assert.Equal(t, lsp.NewRange(3, 9, 3, 10), subField.Names[0].Range) - assert.Equal(t, "int", subField.Type.(ast.TypeInfo).Identifier.Name) - assert.Equal(t, true, subField.Type.(ast.TypeInfo).BuiltIn) - assert.Equal(t, lsp.NewRange(3, 5, 3, 8), subField.Type.(ast.TypeInfo).Range) + assert.Equal(t, "int", subField.Type.(*ast.TypeInfo).Identifier.Name) + assert.Equal(t, true, subField.Type.(*ast.TypeInfo).BuiltIn) + assert.Equal(t, lsp.NewRange(3, 5, 3, 8), subField.Type.(*ast.TypeInfo).Range) }) t.Run("struct with anonymous bit-structs", func(t *testing.T) { @@ -869,7 +903,7 @@ func TestConvertToAST_method_declaration(t *testing.T) { assert.Equal(t, "Object", methodDecl.Signature.ReturnType.Identifier.Name, "Return type") assert.Equal(t, uint(1), methodDecl.Signature.ReturnType.Pointer, "Return type is pointer") assert.Equal(t, lsp.Position{1, 4}, methodDecl.Signature.ReturnType.NodeAttributes.Range.Start) - assert.Equal(t, lsp.Position{1, 10}, methodDecl.Signature.ReturnType.NodeAttributes.Range.End) + assert.Equal(t, lsp.Position{1, 11}, methodDecl.Signature.ReturnType.NodeAttributes.Range.End) assert.Equal(t, 2, len(methodDecl.Signature.Parameters)) assert.Equal(t, @@ -891,7 +925,7 @@ func TestConvertToAST_method_declaration(t *testing.T) { WithName("int").WithNameStartEnd(1, 36, 1, 39). IsBuiltin(). IsPointer(). - WithStartEnd(1, 36, 1, 39). + WithStartEnd(1, 36, 1, 40). Build(), NodeAttributes: ast.NewNodeAttributesBuilder(). WithRangePositions(1, 36, 1, 48).Build(), @@ -941,6 +975,7 @@ func TestConvertToAST_method_declaration_mutable(t *testing.T) { func TestConvertToAST_interface_decl(t *testing.T) { source := `module foo; + <* abc *> interface MyInterface { fn void method1(); fn int method2(char arg); @@ -950,11 +985,14 @@ func TestConvertToAST_interface_decl(t *testing.T) { tree := cv.ConvertToAST(GetCST(source), source, "file.c3") interfaceDecl := tree.Modules[0].Declarations[0].(*ast.InterfaceDecl) + assert.Equal(t, lsp.NewRange(2, 1, 5, 2), interfaceDecl.GetRange()) + assert.Equal(t, "abc", interfaceDecl.DocComment.Get().GetBody()) assert.Equal(t, 2, len(interfaceDecl.Methods)) } func TestConvertToAST_macro_decl(t *testing.T) { source := `module foo; + <* abc *> macro m(x) { return x + 2; }` @@ -963,12 +1001,15 @@ func TestConvertToAST_macro_decl(t *testing.T) { tree := cv.ConvertToAST(GetCST(source), source, "file.c3") macroDecl := tree.Modules[0].Declarations[0].(*ast.MacroDecl) - assert.Equal(t, lsp.Position{1, 1}, macroDecl.Range.Start) - assert.Equal(t, lsp.Position{3, 2}, macroDecl.Range.End) + startRow := uint(2) + endRow := uint(4) + assert.Equal(t, lsp.Position{startRow, 1}, macroDecl.Range.Start) + assert.Equal(t, lsp.Position{endRow, 2}, macroDecl.Range.End) + assert.Equal(t, "abc", macroDecl.DocComment.Get().GetBody()) assert.Equal(t, "m", macroDecl.Signature.Name.Name) - assert.Equal(t, lsp.Position{1, 7}, macroDecl.Signature.Name.Range.Start) - assert.Equal(t, lsp.Position{1, 8}, macroDecl.Signature.Name.Range.End) + assert.Equal(t, lsp.Position{startRow, 7}, macroDecl.Signature.Name.Range.Start) + assert.Equal(t, lsp.Position{startRow, 8}, macroDecl.Signature.Name.Range.End) assert.Equal(t, 1, len(macroDecl.Signature.Parameters)) assert.Equal( diff --git a/server/internal/lsp/server/TextDocumentHover.go b/server/internal/lsp/server/TextDocumentHover.go index e666e97..e660038 100644 --- a/server/internal/lsp/server/TextDocumentHover.go +++ b/server/internal/lsp/server/TextDocumentHover.go @@ -30,6 +30,10 @@ func (srv *Server) TextDocumentHover(context *glsp.Context, params *protocol.Hov return nil, nil } + // ----------------------- + // Old implementation + // ----------------------- + pos := symbols.NewPositionFromLSPPosition(params.Position) docId := utils.NormalizePath(params.TextDocument.URI) foundSymbolOption := srv.search.FindSymbolDeclarationInWorkspace(docId, pos, srv.state)