diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index f57b5f9..d2190be 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -12,6 +12,7 @@ A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: + 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' @@ -24,15 +25,17 @@ A clear and concise description of what you expected to happen. If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Browser [e.g. chrome, safari] - - Version [e.g. 22] + +- OS: [e.g. iOS] +- Browser [e.g. chrome, safari] +- Version [e.g. 22] **Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - Browser [e.g. stock browser, safari] - - Version [e.g. 22] + +- Device: [e.g. iPhone6] +- OS: [e.g. iOS8.1] +- Browser [e.g. stock browser, safari] +- Version [e.g. 22] **Additional context** Add any other context about the problem here. diff --git a/.github/renovate.json b/.github/renovate.json index e46316f..f02f654 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -1,6 +1,6 @@ { - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "github>ivuorinen/renovate-config" - ] + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "github>ivuorinen/renovate-config" + ] } diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 744cea4..a101394 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -32,15 +32,15 @@ jobs: uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Initialize CodeQL - uses: github/codeql-action/init@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.11 + uses: github/codeql-action/init@f1f6e5f6af878fb37288ce1c627459e94dbf7d01 # v3.30.1 with: languages: ${{ matrix.language }} queries: security-and-quality - name: Autobuild - uses: github/codeql-action/autobuild@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.11 + uses: github/codeql-action/autobuild@f1f6e5f6af878fb37288ce1c627459e94dbf7d01 # v3.30.1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.11 + uses: github/codeql-action/analyze@f1f6e5f6af878fb37288ce1c627459e94dbf7d01 # v3.30.1 with: category: '/language:${{matrix.language}}' diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml index c9dbe5a..7476f59 100644 --- a/.github/workflows/pr-lint.yml +++ b/.github/workflows/pr-lint.yml @@ -27,4 +27,4 @@ jobs: steps: - name: Run PR Lint # https://github.com/ivuorinen/actions - uses: ivuorinen/actions/pr-lint@8e88bf4cdb14c38c929ad2a89687d6eb635a3967 # 25.8.25 + uses: ivuorinen/actions/pr-lint@71b97baa7c71a55b48413309b86843b1b125e149 # 25.8.31 diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 8c9314f..26ee864 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -23,4 +23,4 @@ jobs: issues: write pull-requests: write steps: - - uses: ivuorinen/actions/stale@8e88bf4cdb14c38c929ad2a89687d6eb635a3967 # 25.8.25 + - uses: ivuorinen/actions/stale@71b97baa7c71a55b48413309b86843b1b125e149 # 25.8.31 diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml index 2fa6d17..6aec476 100644 --- a/.github/workflows/sync-labels.yml +++ b/.github/workflows/sync-labels.yml @@ -38,4 +38,4 @@ jobs: with: token: ${{ secrets.GITHUB_TOKEN }} - name: ⤵️ Sync Latest Labels Definitions - uses: ivuorinen/actions/sync-labels@8e88bf4cdb14c38c929ad2a89687d6eb635a3967 # 25.8.25 + uses: ivuorinen/actions/sync-labels@71b97baa7c71a55b48413309b86843b1b125e149 # 25.8.31 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 97eaf14..297e5fc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -34,7 +34,7 @@ repos: - id: yamllint - repo: https://github.com/scop/pre-commit-shfmt - rev: v3.11.0-1 + rev: v3.12.0-2 hooks: - id: shfmt @@ -51,12 +51,12 @@ repos: args: ['-shellcheck='] - repo: https://github.com/renovatebot/pre-commit-hooks - rev: 41.82.10 + rev: 41.97.9 hooks: - id: renovate-config-validator - repo: https://github.com/bridgecrewio/checkov.git - rev: '3.2.467' + rev: '3.2.469' hooks: - id: checkov args: diff --git a/LICENSE.md b/LICENSE similarity index 100% rename from LICENSE.md rename to LICENSE diff --git a/README.md b/README.md new file mode 100644 index 0000000..3b14078 --- /dev/null +++ b/README.md @@ -0,0 +1,81 @@ +# Neovim ShellSpec DSL Support + +Language support and formatter for ShellSpec DSL testing framework. + +## Installation + +### With [lazy.nvim](https://github.com/folke/lazy.nvim) + +```lua +{ + "ivuorinen/nvim-shellspec", + ft = "shellspec", +} +``` + +### With [vim-plug](https://github.com/junegunn/vim-plug) + +```vim +Plug 'ivuorinen/nvim-shellspec' +``` + +### Manual Installation + +```bash +git clone https://github.com/ivuorinen/nvim-shellspec.git ~/.config/nvim/pack/plugins/start/nvim-shellspec +``` + +## Features + +- **Syntax highlighting** for all ShellSpec DSL keywords +- **Automatic indentation** for block structures +- **Filetype detection** for `*_spec.sh`, `*.spec.sh`, and `spec/*.sh` +- **Formatting commands** with proper indentation + +## Usage + +### Commands + +- `:ShellSpecFormat` - Format entire buffer +- `:ShellSpecFormatRange` - Format selected lines + +### Auto-format + +Add to your config to enable auto-format on save: + +```vim +let g:shellspec_auto_format = 1 +``` + +### File Types + +Plugin activates for files matching: + +- `*_spec.sh` +- `*.spec.sh` +- `spec/*.sh` +- `test/*.sh` + +## Configuration + +```vim +" Enable auto-formatting on save +let g:shellspec_auto_format = 1 + +" Custom keybindings +autocmd FileType shellspec nnoremap f :ShellSpecFormat +autocmd FileType shellspec vnoremap f :ShellSpecFormatRange +``` + +## Contributing + +Contributions welcome! Please open issues and pull requests at: + + +## License + +MIT License - see repository for details. + +## Related + +- [ShellSpec](https://github.com/shellspec/shellspec) - BDD testing framework for shell scripts diff --git a/autoload/shellspec.vim b/autoload/shellspec.vim new file mode 100644 index 0000000..64968e6 --- /dev/null +++ b/autoload/shellspec.vim @@ -0,0 +1,56 @@ +" ShellSpec DSL formatter functions + +function! shellspec#format_buffer() abort + let l:pos = getpos('.') + let l:lines = getline(1, '$') + let l:formatted = shellspec#format_lines(l:lines) + + silent! %delete _ + call setline(1, l:formatted) + call setpos('.', l:pos) +endfunction + +function! shellspec#format_lines(lines) abort + let l:result = [] + let l:indent = 0 + + for l:line in a:lines + let l:trimmed = trim(l:line) + + " Skip empty lines and comments + if l:trimmed == '' || l:trimmed =~ '^#' + call add(l:result, l:line) + continue + endif + + " Decrease indent for End + if l:trimmed =~ '^End\s*$' + let l:indent = max([0, l:indent - 1]) + endif + + " Apply current indentation + let l:formatted = repeat(' ', l:indent) . l:trimmed + call add(l:result, l:formatted) + + " Increase indent after block keywords + if l:trimmed =~ '^\(Describe\|Context\|ExampleGroup\|It\|Specify\|Example\)' + let l:indent += 1 + elseif l:trimmed =~ '^\([xf]\)\(Describe\|Context\|ExampleGroup\|It\|Specify\|Example\)' + let l:indent += 1 + elseif l:trimmed =~ '^\(Data\|Parameters\)\s*$' + let l:indent += 1 + endif + endfor + + return l:result +endfunction + +function! shellspec#format_selection() abort + let l:start = line("'<") + let l:end = line("'>") + let l:lines = getline(l:start, l:end) + let l:formatted = shellspec#format_lines(l:lines) + + call deletebufline('%', l:start, l:end) + call append(l:start - 1, l:formatted) +endfunction diff --git a/bin/shellspec-format b/bin/shellspec-format new file mode 100755 index 0000000..172631c --- /dev/null +++ b/bin/shellspec-format @@ -0,0 +1,45 @@ +#!/bin/bash +# Standalone ShellSpec DSL formatter + +format_shellspec() { + local indent=0 + local line + + while IFS= read -r line; do + local trimmed=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + + # Skip empty lines and comments + if [[ -z "$trimmed" || "$trimmed" =~ ^# ]]; then + echo "$line" + continue + fi + + # Decrease indent for End + if [[ "$trimmed" =~ ^End[[:space:]]*$ ]]; then + ((indent > 0)) && ((indent--)) + fi + + # Apply indentation + printf "%*s%s\n" $((indent * 2)) "" "$trimmed" + + # Increase indent after block keywords + if [[ "$trimmed" =~ ^(Describe|Context|ExampleGroup|It|Specify|Example) ]] || + [[ "$trimmed" =~ ^[xf](Describe|Context|ExampleGroup|It|Specify|Example) ]] || + [[ "$trimmed" =~ ^(Data|Parameters)[[:space:]]*$ ]]; then + ((indent++)) + fi + done +} + +# Main execution +if [[ $# -eq 0 ]]; then + format_shellspec +else + for file in "$@"; do + if [[ -f "$file" ]]; then + format_shellspec < "$file" > "${file}.tmp" && mv "${file}.tmp" "$file" + else + echo "Error: File not found: $file" >&2 + fi + done +fi diff --git a/ftdetect/shellspec.vim b/ftdetect/shellspec.vim new file mode 100644 index 0000000..7343f78 --- /dev/null +++ b/ftdetect/shellspec.vim @@ -0,0 +1,5 @@ +" Filetype detection for ShellSpec DSL +autocmd BufRead,BufNewFile *_spec.sh setfiletype shellspec +autocmd BufRead,BufNewFile *.spec.sh setfiletype shellspec +autocmd BufRead,BufNewFile spec/*.sh setfiletype shellspec +autocmd BufRead,BufNewFile test/*.sh setfiletype shellspec diff --git a/indent/shellspec.vim b/indent/shellspec.vim new file mode 100644 index 0000000..c5310ff --- /dev/null +++ b/indent/shellspec.vim @@ -0,0 +1,46 @@ +" Indentation for ShellSpec DSL +if exists("b:did_indent") + finish +endif +let b:did_indent = 1 + +setlocal indentexpr=GetShellSpecIndent() +setlocal indentkeys=!^F,o,O,e,=End + +function! GetShellSpecIndent() + let line = getline(v:lnum) + let prevline = getline(v:lnum - 1) + + " Don't change indentation for comments + if line =~ '^\s*#' + return -1 + endif + + " End decreases indent + if line =~ '^\s*End\s*$' + return indent(v:lnum - 1) - &shiftwidth + endif + + " After block start keywords, increase indent + if prevline =~ '^\s*\(Describe\|Context\|ExampleGroup\|It\|Specify\|Example\)' + return indent(v:lnum - 1) + &shiftwidth + endif + + " After prefixed block keywords + if prevline =~ '^\s*\([xf]\)\(Describe\|Context\|ExampleGroup\|It\|Specify\|Example\)' + return indent(v:lnum - 1) + &shiftwidth + endif + + " After Data blocks + if prevline =~ '^\s*Data\s*$' + return indent(v:lnum - 1) + &shiftwidth + endif + + " After Parameters blocks + if prevline =~ '^\s*Parameters' + return indent(v:lnum - 1) + &shiftwidth + endif + + " Keep same indent for most lines + return indent(v:lnum - 1) +endfunction diff --git a/plugin/shellspec.vim b/plugin/shellspec.vim new file mode 100644 index 0000000..7de603f --- /dev/null +++ b/plugin/shellspec.vim @@ -0,0 +1,29 @@ +" ShellSpec DSL plugin +" Neovim language support for ShellSpec testing framework +" Repository: https://github.com/ivuorinen/nvim-shellspec +" Author: Ismo Vuorinen + +if exists('g:loaded_shellspec') + finish +endif +let g:loaded_shellspec = 1 + +" Commands +command! ShellSpecFormat call shellspec#format_buffer() +command! -range ShellSpecFormatRange call shellspec#format_selection() + +" Auto commands +augroup ShellSpec + autocmd! + autocmd FileType shellspec setlocal commentstring=#\ %s + autocmd FileType shellspec setlocal foldmethod=indent + autocmd FileType shellspec setlocal shiftwidth=2 tabstop=2 expandtab +augroup END + +" Optional: Auto-format on save +if get(g:, 'shellspec_auto_format', 0) + augroup ShellSpecAutoFormat + autocmd! + autocmd BufWritePre *.spec.sh,*_spec.sh ShellSpecFormat + augroup END +endif diff --git a/syntax/shellspec.vim b/syntax/shellspec.vim new file mode 100644 index 0000000..bd16723 --- /dev/null +++ b/syntax/shellspec.vim @@ -0,0 +1,82 @@ +" Syntax highlighting for ShellSpec DSL +if exists("b:current_syntax") + finish +endif + +" Keywords - Block structures +syn keyword shellspecBlock Describe Context ExampleGroup It Specify Example Todo End +syn keyword shellspecBlock xDescribe xContext xExampleGroup xIt xSpecify xExample +syn keyword shellspecBlock fDescribe fContext fExampleGroup fIt fSpecify fExample + +" Keywords - Control flow +syn keyword shellspecControl Pending Skip +syn match shellspecControl "\" + +" Keywords - Evaluation +syn keyword shellspecEval When call run command script source + +" Keywords - Expectation +syn keyword shellspecExpect The Assert should +syn keyword shellspecExpect output stdout error stderr status variable path +syn match shellspecExpect "\" + +" Keywords - Hooks +syn keyword shellspecHook BeforeEach AfterEach BeforeAll AfterAll Before After +syn keyword shellspecHook BeforeCall AfterCall BeforeRun AfterRun + +" Keywords - Helpers +syn keyword shellspecHelper Dump Include Set Path File Dir Data Parameters +syn match shellspecHelper "\" +syn match shellspecHelper "\" + +" Language chains +syn keyword shellspecChain a an as the + +" Matchers and modifiers +syn keyword shellspecMatcher equal eq be exist valid satisfy +syn keyword shellspecModifier line word length contents result first second third +syn keyword shellspecModifier of + +" Tags - for example groups and examples +syn match shellspecTag "\<\w\+:\w\+\>" contained + +" Strings +syn region shellspecString start=+"+ skip=+\\"+ end=+"+ contains=shellspecVariable +syn region shellspecString start=+'+ end=+'+ +syn match shellspecVariable "\$\w\+" contained +syn match shellspecVariable "\${\w\+}" contained + +" Comments +syn match shellspecComment "#.*$" contains=shellspecTodo +syn keyword shellspecTodo TODO FIXME XXX NOTE contained + +" Data blocks +syn match shellspecDataMarker "^#|" contained +syn region shellspecDataBlock start="Data\s*$" end="End" contains=shellspecDataMarker + +" Numbers +syn match shellspecNumber "\<\d\+\>" + +" Shell code in functions +syn include @shellCode $VIMRUNTIME/syntax/sh.vim +syn region shellspecShellCode start="^\s*\w\+\s*(" end="}" contains=@shellCode + +" Highlighting groups +hi def link shellspecBlock Statement +hi def link shellspecControl Conditional +hi def link shellspecEval Function +hi def link shellspecExpect Keyword +hi def link shellspecHook PreProc +hi def link shellspecHelper Special +hi def link shellspecChain Operator +hi def link shellspecMatcher Function +hi def link shellspecModifier Type +hi def link shellspecTag Label +hi def link shellspecString String +hi def link shellspecVariable Identifier +hi def link shellspecComment Comment +hi def link shellspecTodo Todo +hi def link shellspecDataMarker SpecialChar +hi def link shellspecNumber Number + +let b:current_syntax = "shellspec"