From 26f4ff5569a3ba8a84c0be58fd5866d57471e492 Mon Sep 17 00:00:00 2001 From: Matthew Hughes Date: Fri, 19 Jan 2024 20:14:01 +0000 Subject: [PATCH] Add syntax support for toolchain directive This directive was added to `go.mod` with Go 1.21[1] So that this directive can be highlighted in `go.mod`. It's not entirely clear exactly what the fully supported syntax is. The docs[1] suggests any Go release version, e.g. * `go1.20` * `go1.21.1` * `go1.21.1rc1` `golang.org/x/mod` gives a much more relaxed definition, requiring just that things match against the regex `^default$|^go1($|\.)`[2] Finally there's `FromToolchain` from the stdlib's internals for processing versions[3] which is broader than that from[1] but more limited than that from[2], supporting arbitrary suffixes (after any of `" \t-"`) appended to the version, e.g. * go1.21.3-somesuffix * go1.21rc2-somesuffix * go1.21 some-suffix The approach taken for the syntax matching here is closest to this final condition, and will not include some toolchain verison's that would be supported by the `modfile` regex, e.g. * go1.21.1blah * go1.21!some-suffix Since these would be rejected by the `go` tool itself with an error like > go: invalid toolchain "go1.21.1blah" in go.mod --- autoload/go/highlight_test.vim | 114 +++++++++++++++++++++++++++++++++ syntax/gomod.vim | 39 +++++++---- 2 files changed, 141 insertions(+), 12 deletions(-) diff --git a/autoload/go/highlight_test.vim b/autoload/go/highlight_test.vim index 5f84bf48be..bf04a8296a 100644 --- a/autoload/go/highlight_test.vim +++ b/autoload/go/highlight_test.vim @@ -671,6 +671,120 @@ function! s:functionCallHighlightGroup(testname, value) endtry endfunc +function! Test_gomodToolchainVersion_highlight() abort + try + syntax on + + let g:go_gopls_enabled = 0 + let l:wd = getcwd() + let l:dir = gotest#write_file('gomodtest/go.mod', [ + \ 'module github.com/fatih/vim-go', + \ '', + \ 'toolchain default', + \ 'toolchain go1', + \ 'toolchain go1', + \ 'toolchain go1.21', + \ 'toolchain go1.21rc3', + \ 'toolchain go1.21.3-somesuffix', + \ 'toolchain go1.21rc2-somesuffix', + \ 'toolchain go1.21 some-suffix', + \ '']) + + let l:lineno = 3 + let l:lineclose = line('$') + while l:lineno < l:lineclose + let l:line = getline(l:lineno) + let l:split_idx = stridx(l:line, ' ') + let l:idx = 0 + let l:col = 1 + + while l:idx < len(l:line) - 1 + call cursor(l:lineno, l:col) + let l:synname = synIDattr(synID(l:lineno, l:col, 1), 'name') + let l:errlen = len(v:errors) + + if l:idx < l:split_idx + call assert_equal('gomodToolchain', l:synname, 'toolchain on line ' . l:lineno . ' and col ' . l:col) + elseif l:idx > l:split_idx + call assert_equal('gomodToolchainVersion', l:synname, 'version on line ' . l:lineno . ' and col ' . l:col) + endif + + if l:errlen < len(v:errors) + break + endif + + let l:col += 1 + let l:idx += 1 + endwhile + let l:lineno += 1 + endwhile + + finally + call go#util#Chdir(l:wd) + call delete(l:dir, 'rf') + endtry +endfunc + +function! Test_gomodToolchainVersion_invalid_highlight() abort + try + syntax on + let g:go_gopls_enabled = 0 + let l:wd = getcwd() + + " 1. No release candidate for patch versions + " 2+3. Release version can only be followed by 'rcN' or a valid suffix + " 4+5. toolchain version must start with 'go' + let l:dir = gotest#write_file('gomodtest/go.mod', [ + \ 'module github.com/fatih/vim-go', + \ '', + \ 'toolchain go2', + \ 'toolchain go1.21.1.4', + \ 'toolchain go1.21.1blah', + \ 'toolchain go1.21!some-suffix', + \ 'toolchain something-else', + \ '']) + + let l:lineno = 3 + let l:lineclose = line('$') + while l:lineno < l:lineclose + let l:line = getline(l:lineno) + let l:col = col([l:lineno, '$']) - 1 + let l:idx = len(l:line) - 1 + " e.g. go1.21.1rc2 is valid until 'rc2' + " each 'go*' test above has last version number '1' + let l:valid_version_start_idx = strridx(l:line, '1') + + if l:valid_version_start_idx != -1 + let l:end_idx = l:valid_version_start_idx + else + " the whole version is invalid + let l:end_idx = stridx(l:line, ' ') + 1 + endif + + while l:idx > l:end_idx + call cursor(l:lineno, l:col) + let l:synname = synIDattr(synID(l:lineno, l:col, 1), 'name') + let l:errlen = len(v:errors) + + call assert_notequal('gomodToolchainVersion', l:synname, 'version on line ' . l:lineno . ' and col ' . l:col) + + if l:errlen < len(v:errors) + break + endif + + let l:col -= 1 + let l:idx -= 1 + endwhile + let l:lineno += 1 + endwhile + + finally + call go#util#Chdir(l:wd) + call delete(l:dir, 'rf') + endtry +endfunc + + " restore Vi compatibility settings let &cpo = s:cpo_save unlet s:cpo_save diff --git a/syntax/gomod.vim b/syntax/gomod.vim index b9780dda2f..60da6d2235 100644 --- a/syntax/gomod.vim +++ b/syntax/gomod.vim @@ -11,12 +11,13 @@ syntax case match " https://golang.org/ref/mod#go-mod-file-grammar " match keywords -syntax keyword gomodModule module -syntax keyword gomodGo go contained -syntax keyword gomodRequire require -syntax keyword gomodExclude exclude -syntax keyword gomodReplace replace -syntax keyword gomodRetract retract +syntax keyword gomodModule module +syntax keyword gomodGo go contained +syntax keyword gomodToolchain toolchain contained +syntax keyword gomodRequire require +syntax keyword gomodExclude exclude +syntax keyword gomodReplace replace +syntax keyword gomodRetract retract " require, exclude, replace, and go can be also grouped into block syntax region gomodRequire start='require (' end=')' transparent contains=gomodRequire,gomodVersion @@ -24,14 +25,16 @@ syntax region gomodExclude start='exclude (' end=')' transparent contains=gomodE syntax region gomodReplace start='replace (' end=')' transparent contains=gomodReplace,gomodVersion syntax region gomodRetract start='retract (' end=')' transparent contains=gomodVersionRange,gomodVersion syntax match gomodGo '^go .*$' transparent contains=gomodGo,gomodGoVersion +syntax match gomodToolchain '^toolchain .*$' transparent contains=gomodToolchain,gomodToolchainVersion " set highlights -highlight default link gomodModule Keyword -highlight default link gomodGo Keyword -highlight default link gomodRequire Keyword -highlight default link gomodExclude Keyword -highlight default link gomodReplace Keyword -highlight default link gomodRetract Keyword +highlight default link gomodModule Keyword +highlight default link gomodGo Keyword +highlight default link gomodToolchain Keyword +highlight default link gomodRequire Keyword +highlight default link gomodExclude Keyword +highlight default link gomodReplace Keyword +highlight default link gomodRetract Keyword " comments are always in form of // ... syntax region gomodComment start="//" end="$" contains=@Spell @@ -45,6 +48,18 @@ highlight default link gomodString String syntax match gomodReplaceOperator "\v\=\>" highlight default link gomodReplaceOperator Operator +" match toolchain versions, based on https://go.dev/doc/toolchain#version, +" * default +" * go1 +" * go1.X.Y +" * go1.X +" * go1.XrcN +" * go1.XrcN-somesuffix +" * go1.X.Y-somesuffix +syntax match gomodToolchainVersion "default$" contained +syntax match gomodToolchainVersion "go1\(.\d\+\)\{,2\}\(rc\d\+\)\?\([ \t-].*\)\?" contained +highlight default link gomodToolchainVersion Identifier + " match go versions syntax match gomodGoVersion "1\.\d\+" contained highlight default link gomodGoVersion Identifier