From 46c9e96e307cd32f53e898243113b6ed5308eb02 Mon Sep 17 00:00:00 2001 From: Anand Hegde Date: Sat, 19 Sep 2026 23:01:47 +0530 Subject: [PATCH] feat(code): add Kotlin comment extraction Extract `//` line comments and `/* */` block comments, including KDoc `/** */`, from `.kt` and `.kts` files using the Kotlin grammar already vendored in go-tree-sitter. The padding lists `/**` as a marker so that a one-line KDoc comment's alerts land on the right column; `cStyle` stops at `/*` and would put them two columns short. TestComments now pairs each input with its output by file name rather than by position, since `10.kt` sorts before `2.py`. Closes #1125. --- internal/core/format.go | 1 + internal/lint/code/comments_test.go | 10 +++++-- internal/lint/code/kt.go | 27 ++++++++++++++++++ internal/lint/code/lang.go | 2 ++ testdata/comments/in/10.kt | 22 +++++++++++++++ testdata/comments/in/11.kts | 7 +++++ testdata/comments/out/10.json | 44 +++++++++++++++++++++++++++++ testdata/comments/out/11.json | 23 +++++++++++++++ testdata/e2e/lint.yaml | 13 +++++++++ testdata/fixtures/formats/test.kt | 22 +++++++++++++++ 10 files changed, 168 insertions(+), 3 deletions(-) create mode 100644 internal/lint/code/kt.go create mode 100644 testdata/comments/in/10.kt create mode 100644 testdata/comments/in/11.kts create mode 100644 testdata/comments/out/10.json create mode 100644 testdata/comments/out/11.json create mode 100644 testdata/fixtures/formats/test.kt diff --git a/internal/core/format.go b/internal/core/format.go index fb3b1cc5..0359fc48 100755 --- a/internal/core/format.go +++ b/internal/core/format.go @@ -68,6 +68,7 @@ var FormatByExtension = map[string][]string{ `\.(?:ipynb)$`: {".ipynb", "markup"}, `\.(?:java|bsh)$`: {".java", "code"}, `\.(?:jl)$`: {".jl", "code"}, + `\.(?:kt|kts)$`: {".kt", "code"}, `\.(?:js|jsx)$`: {".js", "code"}, `\.(?:lua)$`: {".lua", "code"}, `\.(?:md|mdown|markdown|markdn|[Rr]md)$`: {".md", "markup"}, diff --git a/internal/lint/code/comments_test.go b/internal/lint/code/comments_test.go index 99b222b2..7d0cb648 100644 --- a/internal/lint/code/comments_test.go +++ b/internal/lint/code/comments_test.go @@ -43,7 +43,11 @@ func TestComments(t *testing.T) { cleaned = append(cleaned, f) } - for i, f := range cleaned { + for _, f := range cleaned { + // Match each input to its output by name: the directory is read in + // lexical order, so `10.kt` would otherwise pair with `2.json`. + name := strings.TrimSuffix(f.Name(), filepath.Ext(f.Name())) + b, err1 := os.ReadFile(fmt.Sprintf("%s/in/%s", testDir, f.Name())) if err1 != nil { t.Error(err1) @@ -59,14 +63,14 @@ func TestComments(t *testing.T) { t.Error(err3) } - b2, err4 := os.ReadFile(fmt.Sprintf("%s/out/%d.json", testDir, i)) + b2, err4 := os.ReadFile(fmt.Sprintf("%s/out/%s.json", testDir, name)) if err4 != nil { t.Error(err4) } markup := toJSON(comments) if markup != string(b2) { - bin := filepath.Join(binDir, fmt.Sprintf("%d.json", i)) + bin := filepath.Join(binDir, name+".json") _ = os.WriteFile(bin, []byte(markup), 0600) t.Errorf("%s", markup) } diff --git a/internal/lint/code/kt.go b/internal/lint/code/kt.go new file mode 100644 index 00000000..eb72d9c0 --- /dev/null +++ b/internal/lint/code/kt.go @@ -0,0 +1,27 @@ +package code + +import ( + "regexp" + + "github.com/smacker/go-tree-sitter/kotlin" + "github.com/vale-cli/vale/v3/internal/core" +) + +// Kotlin extracts `//` line comments and `/* */` block comments, including +// KDoc (`/** */`), which the grammar parses as an ordinary block comment. +func Kotlin() *Language { + return &Language{ + Delims: regexp.MustCompile(`//|/\*\*?|\*/`), + Prefix: cStylePrefix, + Parser: kotlin.GetLanguage(), + Queries: []core.Scope{ + {Name: "", Expr: "(line_comment)+ @comment", Type: ""}, + {Name: "", Expr: "(multiline_comment) @comment", Type: ""}, + }, + // `/**` is listed so that a one-line KDoc comment's padding matches + // what Delims takes off; cStyle alone stops at `/*`. + Padding: func(s string) int { + return computePadding(s, []string{"//", "/*", "/**"}) + }, + } +} diff --git a/internal/lint/code/lang.go b/internal/lint/code/lang.go index ac072a09..2d97b61e 100644 --- a/internal/lint/code/lang.go +++ b/internal/lint/code/lang.go @@ -59,6 +59,8 @@ func GetLanguageFromExt(ext string) (*Language, error) { return Julia(), nil case ".java": return Java(), nil + case ".kt": + return Kotlin(), nil case ".lua": return Lua(), nil case ".php": diff --git a/testdata/comments/in/10.kt b/testdata/comments/in/10.kt new file mode 100644 index 00000000..f8fea54d --- /dev/null +++ b/testdata/comments/in/10.kt @@ -0,0 +1,22 @@ +// NOTE: a line comment +// XXX: continued on the next line +package test + +/** + * TODO: a KDoc block. + * + * @param name FIXME: a tag description. + */ +fun greet(name: String): String { + /* NOTE: a block comment */ + val s = "TODO: a string, not a comment" + return s // FIXME: a trailing comment +} + +/* + * XXX: a multi-line block comment. + */ +class Test + +/** NOTE: a one-line KDoc. */ +fun one() = 1 diff --git a/testdata/comments/in/11.kts b/testdata/comments/in/11.kts new file mode 100644 index 00000000..db56c7eb --- /dev/null +++ b/testdata/comments/in/11.kts @@ -0,0 +1,7 @@ +// A script's leading comment. +plugins { + kotlin("jvm") version "2.0.0" /* inline block */ +} + +/** One line of KDoc. */ +val url = "https://example.com // not a comment" diff --git a/testdata/comments/out/10.json b/testdata/comments/out/10.json new file mode 100644 index 00000000..50acba75 --- /dev/null +++ b/testdata/comments/out/10.json @@ -0,0 +1,44 @@ +[ + { + "Text": "NOTE: a line comment\nXXX: continued on the next line\n", + "Source": "// NOTE: a line comment\n// XXX: continued on the next line\n", + "Line": 1, + "Offset": 0, + "Scope": "text.comment.line" + }, + { + "Text": "\nTODO: a KDoc block.\n\n@param name FIXME: a tag description.\n\n", + "Source": "/**\n * TODO: a KDoc block.\n *\n * @param name FIXME: a tag description.\n */", + "Line": 5, + "Offset": 0, + "Scope": "text.comment.block" + }, + { + "Text": "NOTE: a block comment ", + "Source": "/* NOTE: a block comment */", + "Line": 11, + "Offset": 4, + "Scope": "text.comment.line" + }, + { + "Text": "FIXME: a trailing comment", + "Source": "// FIXME: a trailing comment", + "Line": 13, + "Offset": 13, + "Scope": "text.comment.line" + }, + { + "Text": "\nXXX: a multi-line block comment.\n\n", + "Source": "/*\n * XXX: a multi-line block comment.\n */", + "Line": 16, + "Offset": 0, + "Scope": "text.comment.block" + }, + { + "Text": "NOTE: a one-line KDoc. ", + "Source": "/** NOTE: a one-line KDoc. */", + "Line": 21, + "Offset": 0, + "Scope": "text.comment.line" + } +] \ No newline at end of file diff --git a/testdata/comments/out/11.json b/testdata/comments/out/11.json new file mode 100644 index 00000000..e7edbc3e --- /dev/null +++ b/testdata/comments/out/11.json @@ -0,0 +1,23 @@ +[ + { + "Text": "A script's leading comment.", + "Source": "// A script's leading comment.", + "Line": 1, + "Offset": 0, + "Scope": "text.comment.line" + }, + { + "Text": "inline block ", + "Source": "/* inline block */", + "Line": 3, + "Offset": 34, + "Scope": "text.comment.line" + }, + { + "Text": "One line of KDoc. ", + "Source": "/** One line of KDoc. */", + "Line": 6, + "Offset": 0, + "Scope": "text.comment.line" + } +] \ No newline at end of file diff --git a/testdata/e2e/lint.yaml b/testdata/e2e/lint.yaml index 61beea83..594ee04d 100644 --- a/testdata/e2e/lint.yaml +++ b/testdata/e2e/lint.yaml @@ -40,6 +40,19 @@ cases: test.java:1:4:vale.Annotations:'XXX' left in text test.java:9:4:vale.Annotations:'TODO' left in text + - name: kotlin + args: test.kt + exit: 0 + want: | + test.kt:1:4:vale.Annotations:'NOTE' left in text + test.kt:2:4:vale.Annotations:'XXX' left in text + test.kt:6:4:vale.Annotations:'TODO' left in text + test.kt:8:16:vale.Annotations:'FIXME' left in text + test.kt:11:8:vale.Annotations:'NOTE' left in text + test.kt:13:17:vale.Annotations:'FIXME' left in text + test.kt:17:4:vale.Annotations:'XXX' left in text + test.kt:21:5:vale.Annotations:'NOTE' left in text + - name: proto args: test.proto exit: 0 diff --git a/testdata/fixtures/formats/test.kt b/testdata/fixtures/formats/test.kt new file mode 100644 index 00000000..f8fea54d --- /dev/null +++ b/testdata/fixtures/formats/test.kt @@ -0,0 +1,22 @@ +// NOTE: a line comment +// XXX: continued on the next line +package test + +/** + * TODO: a KDoc block. + * + * @param name FIXME: a tag description. + */ +fun greet(name: String): String { + /* NOTE: a block comment */ + val s = "TODO: a string, not a comment" + return s // FIXME: a trailing comment +} + +/* + * XXX: a multi-line block comment. + */ +class Test + +/** NOTE: a one-line KDoc. */ +fun one() = 1