Skip to content

Commit

Permalink
feat: support multi-line, line-scope comments
Browse files Browse the repository at this point in the history
  • Loading branch information
jdkato committed May 19, 2024
1 parent cafee06 commit d7b0fe1
Show file tree
Hide file tree
Showing 9 changed files with 105 additions and 167 deletions.
65 changes: 64 additions & 1 deletion internal/lint/code/comments.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package code

import (
"bytes"
"context"
"sort"
"strings"

sitter "github.com/smacker/go-tree-sitter"
)
Expand All @@ -16,6 +18,67 @@ type Comment struct {
Scope string
}

// doneMerging determines when we should *stop* concatenating line-scoped
// comments.
func doneMerging(curr, prev Comment) bool {
if prev.Line != curr.Line-1 {
// If the comments aren't on consecutive lines, don't merge them.
return true
} else if prev.Offset != curr.Offset {
// If the comments aren't at the same offset, don't merge them.
return true
}
return false
}

func coalesce(comments []Comment) []Comment {
var joined []Comment

tBuf := bytes.Buffer{}
sBuf := bytes.Buffer{}

for i, comment := range comments {
if comment.Scope == "text.comment.block" { //nolint:gocritic
joined = append(joined, comment)
} else if i == 0 || doneMerging(comment, comments[i-1]) {
if tBuf.Len() > 0 {
// We have comments to merge ...
last := joined[len(joined)-1]

last.Text += tBuf.String()
last.Source += ("\n" + sBuf.String())

joined[len(joined)-1] = last

tBuf.Reset()
sBuf.Reset()
}
joined = append(joined, comment)
} else {
tBuf.WriteString(comment.Text)
sBuf.WriteString(comment.Source + "\n")
}
}

if tBuf.Len() > 0 {
last := joined[len(joined)-1]

last.Text += tBuf.String()
last.Source += ("\n" + sBuf.String())

joined[len(joined)-1] = last

tBuf.Reset()
sBuf.Reset()
}

for i, comment := range joined {
joined[i].Text = strings.TrimLeft(comment.Text, " ")
}

return joined
}

// GetComments returns all comments in the given source code.
func GetComments(source []byte, lang *Language) ([]Comment, error) {
var comments []Comment
Expand Down Expand Up @@ -43,5 +106,5 @@ func GetComments(source []byte, lang *Language) ([]Comment, error) {
})
}

return comments, nil
return coalesce(comments), nil
}
2 changes: 1 addition & 1 deletion internal/lint/code/go.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (

func Go() *Language {
return &Language{
Delims: regexp.MustCompile(`// ?|/\* ?| ?\*/`),
Delims: regexp.MustCompile(`//|/\*|\*/`),
Parser: golang.GetLanguage(),
Queries: []string{`(comment)+ @comment`},
Padding: cStyle,
Expand Down
10 changes: 5 additions & 5 deletions internal/lint/code/py.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (

func Python() *Language {
return &Language{
Delims: regexp.MustCompile(`#\s?|\s*"""\s*|\s*'''\s*`),
Delims: regexp.MustCompile(`#|"""|'''`),
Parser: python.GetLanguage(),
Queries: []string{
`(comment)+ @comment`,
Expand All @@ -18,11 +18,11 @@ func Python() *Language {
(#offset! @docstring 0 3 0 -3))`,
// Class docstring
`((class_definition
body: (block . (expression_statement (string) @rst)))
(#offset! @rst 0 3 0 -3))`,
body: (block . (expression_statement (string) @docstring)))
(#offset! @docstring 0 3 0 -3))`,
// Module docstring
`((module . (expression_statement (string) @rst))
(#offset! @rst 0 3 0 -3))`,
`((module . (expression_statement (string) @docstring))
(#offset! @docstring 0 3 0 -3))`,
},
}
}
9 changes: 9 additions & 0 deletions internal/lint/code/query.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package code

import (
"bytes"
"strings"

sitter "github.com/smacker/go-tree-sitter"
Expand Down Expand Up @@ -38,6 +39,14 @@ func (qe *QueryEngine) run(q *sitter.Query, source []byte) []Comment {
scope := "text.comment.line"
if strings.Count(cText, "\n") > 1 {
scope = "text.comment.block"

buf := bytes.Buffer{}
for _, line := range strings.Split(cText, "\n") {
buf.WriteString(strings.TrimLeft(line, " "))
buf.WriteString("\n")
}

cText = buf.String()
}

comments = append(comments, Comment{
Expand Down
2 changes: 1 addition & 1 deletion internal/lint/code/rs.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (

func Rust() *Language {
return &Language{
Delims: regexp.MustCompile(`/{2,3}\s?`),
Delims: regexp.MustCompile(`/{2,3}`),
Parser: rust.GetLanguage(),
Queries: []string{`(line_comment)+ @comment`},
}
Expand Down
8 changes: 7 additions & 1 deletion testdata/comments/in/2.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@

def FIXME():
"""
FIXME:
FIXME: this is *mardown*.
```python
print("FIXME: this is *python*.")
```
New line.
"""
print("""
FIXME: This should *not* be linted.
Expand Down
41 changes: 3 additions & 38 deletions testdata/comments/out/0.json
Original file line number Diff line number Diff line change
@@ -1,53 +1,18 @@
[
{
"Text": "\nPackage lint implments Vale's syntax-aware linting functionality.\n\nThe package is split into core linting logic (this file), source code\n(code.go), and markup (markup.go).\n",
"Text": "\nPackage lint implments Vale's syntax-aware linting functionality.\n\nThe package is split into core linting logic (this file), source code\n(code.go), and markup (markup.go).\n\n",
"Source": "/*\nPackage lint implments Vale's syntax-aware linting functionality.\n\nThe package is split into core linting logic (this file), source code\n(code.go), and markup (markup.go).\n*/",
"Line": 1,
"Offset": 0,
"Scope": "text.comment.block"
},
{
"Text": "Println formats using the default formats for its oprands and writes to",
"Source": "// Println formats using the default formats for its oprands and writes to",
"Text": "Println formats using the default formats for its oprands and writes to standard output. Spaces are always added between operands and a newline is appended. It returns the number of bytes written and any write error encountered.",
"Source": "// Println formats using the default formats for its oprands and writes to\n// standard output.\n//\n// Spaces are always added between operands and a newline is appended.\n//\n// It returns the number of bytes written and any write error encountered.\n",
"Line": 11,
"Offset": 0,
"Scope": "text.comment.line"
},
{
"Text": "standard output.",
"Source": "// standard output.",
"Line": 12,
"Offset": 0,
"Scope": "text.comment.line"
},
{
"Text": "",
"Source": "//",
"Line": 13,
"Offset": 0,
"Scope": "text.comment.line"
},
{
"Text": "Spaces are always added between operands and a newline is appended.",
"Source": "// Spaces are always added between operands and a newline is appended.",
"Line": 14,
"Offset": 0,
"Scope": "text.comment.line"
},
{
"Text": "",
"Source": "//",
"Line": 15,
"Offset": 0,
"Scope": "text.comment.line"
},
{
"Text": "It returns the number of bytes written and any write error encountered.",
"Source": "// It returns the number of bytes written and any write error encountered.",
"Line": 16,
"Offset": 0,
"Scope": "text.comment.line"
},
{
"Text": "foo bar",
"Source": "// foo bar",
Expand Down
113 changes: 4 additions & 109 deletions testdata/comments/out/1.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,122 +14,17 @@
"Scope": "text.comment.line"
},
{
"Text": "Returns a person with the name given them\n",
"Source": "/// Returns a person with the name given them\n",
"Text": "Returns a person with the name given them\n\n # Arguments\n\n * `foof` - A string slice that holds the name of the person\n\n # Examples\n\n ```rust\n You can have rust code between fences inside the comments\n If you pass --test to `rustdoc`, it will even test it for you!\n use doc::Person;\n let person = Person::new(\"name\");\n ```\n",
"Source": "/// Returns a person with the name given them\n\n///\n\n/// # Arguments\n\n///\n\n/// * `foof` - A string slice that holds the name of the person\n\n///\n\n/// # Examples\n\n///\n\n/// ```rust\n\n/// // You can have rust code between fences inside the comments\n\n/// // If you pass --test to `rustdoc`, it will even test it for you!\n\n/// use doc::Person;\n\n/// let person = Person::new(\"name\");\n\n/// ```\n\n",
"Line": 10,
"Offset": 4,
"Scope": "text.comment.line"
},
{
"Text": "",
"Source": "///\n",
"Line": 11,
"Offset": 4,
"Scope": "text.comment.line"
},
{
"Text": "# Arguments\n",
"Source": "/// # Arguments\n",
"Line": 12,
"Offset": 4,
"Scope": "text.comment.line"
},
{
"Text": "",
"Source": "///\n",
"Line": 13,
"Offset": 4,
"Scope": "text.comment.line"
},
{
"Text": "* `foof` - A string slice that holds the name of the person\n",
"Source": "/// * `foof` - A string slice that holds the name of the person\n",
"Line": 14,
"Offset": 4,
"Scope": "text.comment.line"
},
{
"Text": "",
"Source": "///\n",
"Line": 15,
"Offset": 4,
"Scope": "text.comment.line"
},
{
"Text": "# Examples\n",
"Source": "/// # Examples\n",
"Line": 16,
"Offset": 4,
"Scope": "text.comment.line"
},
{
"Text": "",
"Source": "///\n",
"Line": 17,
"Offset": 4,
"Scope": "text.comment.line"
},
{
"Text": "```rust\n",
"Source": "/// ```rust\n",
"Line": 18,
"Offset": 4,
"Scope": "text.comment.line"
},
{
"Text": "You can have rust code between fences inside the comments\n",
"Source": "/// // You can have rust code between fences inside the comments\n",
"Line": 19,
"Offset": 4,
"Scope": "text.comment.line"
},
{
"Text": "If you pass --test to `rustdoc`, it will even test it for you!\n",
"Source": "/// // If you pass --test to `rustdoc`, it will even test it for you!\n",
"Line": 20,
"Offset": 4,
"Scope": "text.comment.line"
},
{
"Text": "use doc::Person;\n",
"Source": "/// use doc::Person;\n",
"Line": 21,
"Offset": 4,
"Scope": "text.comment.line"
},
{
"Text": "let person = Person::new(\"name\");\n",
"Source": "/// let person = Person::new(\"name\");\n",
"Line": 22,
"Offset": 4,
"Scope": "text.comment.line"
},
{
"Text": "```\n",
"Source": "/// ```\n",
"Line": 23,
"Offset": 4,
"Scope": "text.comment.line"
},
{
"Text": "Gives a friendly hello!\n",
"Source": "/// Gives a friendly hello!\n",
"Text": "Gives a friendly hello!\n\n Says \"Hello, [name]\" to the `Person` it is called on.\n",
"Source": "/// Gives a friendly hello!\n\n///\n\n/// Says \"Hello, [name]\" to the `Person` it is called on.\n\n",
"Line": 30,
"Offset": 4,
"Scope": "text.comment.line"
},
{
"Text": "",
"Source": "///\n",
"Line": 31,
"Offset": 4,
"Scope": "text.comment.line"
},
{
"Text": "Says \"Hello, [name]\" to the `Person` it is called on.\n",
"Source": "/// Says \"Hello, [name]\" to the `Person` it is called on.\n",
"Line": 32,
"Offset": 4,
"Scope": "text.comment.line"
}
]

0 comments on commit d7b0fe1

Please sign in to comment.