From f4bd301a7df375d2204b1d0a8e8cda66815d14ce Mon Sep 17 00:00:00 2001 From: Iain Lane Date: Tue, 6 Oct 2026 12:07:15 +0100 Subject: [PATCH] fix: choose a file's grammar by its `[formats]` or `--ext` language Vale decides how to read a file from its extension. In a source file, it lints only the comments. To find the comments, it parses the file with a tree-sitter grammar for the file's language. Clojure and PowerShell have no grammar, so Vale finds their comments with regular-expression comment patterns instead. Files with unknown extensions are linted as prose. A user can tell Vale to read a file as another language, either with the `[formats]` section of `.vale.ini` or with the `--ext` flag. Users do this for languages that Vale does not support, such as Nix, by choosing a supported language with the same comment syntax. With `nix = pl` in `[formats]`, Vale should lint the comments in `default.nix` and skip the Nix code. Today, this only works when Vale finds the target language's comments with patterns. When the target language has a grammar, Vale skips every comment in the file. This limited coverage is a regression: before 3.5.0, every code language had comment patterns, and reading a file as any of them worked. Vale removed each language's patterns when the language moved to tree-sitter: Python, Rust, Ruby, Go, JavaScript, TypeScript, C++ and Protocol Buffers in 3.5.0, and the rest apart from Clojure and PowerShell in 3.18.0 (e82432d7). `lintCode` looks up the grammar by the file's real extension, not by the target language's extension. `.nix` has no grammar, so `lintCode` falls back to `lintCodeOld`, which looks up comment patterns by the target language's extension. Perl has no patterns, so `lintCodeOld` returns without linting any comments. The same lookup means that the target language has no effect when the real extension has a grammar: under `--ext=.c`, Vale still parses a `.ts` file as TypeScript. For markup, and for the comment patterns, the target language decides how Vale reads the file. The lookup by real extension dates from 172714de. At that time, Go, JavaScript, TypeScript and several other languages shared the normalized extension `.c`, so a lookup by the normalized extension would have parsed them all as C. Each of these languages now has its own normalized extension, so the two lookups give the same grammar unless the user has chosen a target language. Look up the grammar by the target language's extension in `lintCode`. Do the same in `lintDataComments`, which checks for a grammar before it calls `lintCode` to lint a data file's comments. Files without a `[formats]` entry or `--ext` are parsed as before. A user who maps one language with a grammar to another, such as `ts = js`, now gets the target language's grammar. Assisted-by: Claude Code --- internal/lint/code.go | 2 +- internal/lint/data.go | 2 +- testdata/e2e/lint.yaml | 76 +++++++++++++++++++++++++++++++++++++++++ testdata/e2e/views.yaml | 31 +++++++++++++++++ 4 files changed, 109 insertions(+), 2 deletions(-) diff --git a/internal/lint/code.go b/internal/lint/code.go index 07ae7260..6db7ab58 100755 --- a/internal/lint/code.go +++ b/internal/lint/code.go @@ -42,7 +42,7 @@ func (l *Linter) skipsComment(scope string) bool { } func (l *Linter) lintCode(f *core.File) error { - lang, err := code.GetLanguageFromExt(f.RealExt) + lang, err := code.GetLanguageFromExt(f.NormedExt) if err != nil { // No tree-sitter grammar available for this file type. return l.lintCodeOld(f) diff --git a/internal/lint/data.go b/internal/lint/data.go index d0e1cfb2..48b6ca08 100644 --- a/internal/lint/data.go +++ b/internal/lint/data.go @@ -106,7 +106,7 @@ func (l *Linter) lintData(f *core.File) error { // lintDataComments lints the comments of a data file that has a grammar -- // YAML or TOML -- and does nothing for one that has no comments to read. func (l *Linter) lintDataComments(f *core.File) error { - if _, err := code.GetLanguageFromExt(f.RealExt); err == nil { + if _, err := code.GetLanguageFromExt(f.NormedExt); err == nil { return l.lintCode(f) } return nil diff --git a/testdata/e2e/lint.yaml b/testdata/e2e/lint.yaml index fff2649a..644ecf92 100644 --- a/testdata/e2e/lint.yaml +++ b/testdata/e2e/lint.yaml @@ -1852,6 +1852,82 @@ cases: test.pl:5:21:vale.Annotations:'XXX' left in text test.pl:8:7:vale.Annotations:'TODO' left in text + - name: perl-format-mapping + about: a `.nix` file has no grammar, so under `nix = pl` Vale reads its + comments as Perl comments + files: + .vale.ini: | + StylesPath = styles + MinAlertLevel = suggestion + + [formats] + nix = pl + + [*] + T.Tok = YES + styles/T/Tok.yml: | + extends: existence + message: "'%s' found" + level: error + tokens: + - ZQX + default.nix: | + # A ZQX in a comment. + { value = "ZQX"; } + args: default.nix + exit: 1 + want: | + default.nix:1:5:T.Tok:'ZQX' found + + - name: perl-ext-flag + about: a `.nix` file has no grammar, so under `--ext=.pl` Vale reads its + comments as Perl comments + files: + .vale.ini: | + StylesPath = styles + MinAlertLevel = suggestion + + [*] + T.Tok = YES + styles/T/Tok.yml: | + extends: existence + message: "'%s' found" + level: error + tokens: + - ZQX + default.nix: | + # A ZQX in a comment. + { value = "ZQX"; } + args: "--ext=.pl default.nix" + exit: 1 + want: | + default.nix:1:5:T.Tok:'ZQX' found + + - name: ext-flag-overrides-grammar + about: "`--ext=.c` parses a `.ts` file as C even though TypeScript has a + grammar, so `//` in a template literal starts a comment" + files: + .vale.ini: | + StylesPath = styles + MinAlertLevel = suggestion + + [*] + T.Tok = YES + styles/T/Tok.yml: | + extends: existence + message: "'%s' found" + level: error + tokens: + - ZQX + test.ts: | + const s = `// ZQX in a template literal`; + // ZQX in a comment. + args: "--ext=.c test.ts" + exit: 1 + want: | + test.ts:1:15:T.Tok:'ZQX' found + test.ts:2:4:T.Tok:'ZQX' found + - name: php args: test.php exit: 0 diff --git a/testdata/e2e/views.yaml b/testdata/e2e/views.yaml index 421c4085..b991fe13 100644 --- a/testdata/e2e/views.yaml +++ b/testdata/e2e/views.yaml @@ -498,3 +498,34 @@ cases: site.yml:1:7:Vale.Spelling:Did you really mean 'defalt'? site.yml:2:23:Vale.Spelling:Did you really mean 'storag'? site.yml:3:29:Vale.Spelling:Did you really mean 'trailng'? + + - name: data-comments-format-mapping + about: a `.conf` file has no grammar, so under `conf = yml` Vale reads its + comments as YAML comments + files: + .vale.ini: | + StylesPath = styles + MinAlertLevel = suggestion + + [formats] + conf = yml + + [*.conf] + BasedOnStyles = Vale + Vale.Terms = NO + Vale.Avoid = NO + Vale.Repetition = NO + View = Data + styles/config/views/Data.yml: | + engine: dasel + scopes: + - name: title + expr: title + site.conf: | + # The defalt settings. + title: A guide to the storag layer + args: site.conf + exit: 1 + want: | + site.conf:1:7:Vale.Spelling:Did you really mean 'defalt'? + site.conf:2:23:Vale.Spelling:Did you really mean 'storag'?