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..3e8debf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: - - id: requirements-txt-fixer - id: detect-private-key - id: trailing-whitespace args: [--markdown-linebreak-ext=md] @@ -42,13 +41,13 @@ repos: rev: v0.11.0 hooks: - id: shellcheck - args: ['--severity=warning'] + args: ["--severity=warning"] - repo: https://github.com/rhysd/actionlint rev: v1.7.7 hooks: - id: actionlint - args: ['-shellcheck='] + args: ["-shellcheck="] - repo: https://github.com/renovatebot/pre-commit-hooks rev: 41.82.10 @@ -56,8 +55,8 @@ repos: - id: renovate-config-validator - repo: https://github.com/bridgecrewio/checkov.git - rev: '3.2.467' + rev: "3.2.467" hooks: - id: checkov args: - - '--quiet' + - "--quiet" 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..4ac1017 --- /dev/null +++ b/README.md @@ -0,0 +1,62 @@ +# Zed ShellSpec Extension + +Language support for ShellSpec BDD testing framework in Zed editor. + +## Features + +- Syntax highlighting for ShellSpec DSL keywords +- Smart indentation for nested test blocks +- Code outline navigation +- Bracket matching and auto-completion +- Test execution integration + +## Installation + +1. Install via Zed's extension gallery (when published) +2. Or manually: clone to `~/.config/zed/extensions/shellspec/` + +## File Types + +Automatically detects files with: +- `*_spec.sh` +- `*.spec.sh` + +## Usage + +### Test Execution + +Use Zed's task system to run tests: + +```json +{ + "tasks": { + "shellspec-test": { + "label": "Run ShellSpec Test", + "command": "shellspec", + "args": ["$ZED_RELATIVE_FILE"], + "cwd": "$ZED_WORKTREE_ROOT" + }, + "shellspec-all": { + "label": "Run All Tests", + "command": "shellspec", + "cwd": "$ZED_WORKTREE_ROOT" + } + } +} +``` + +### Language Server + +Uses bash-language-server for shell script features. Install with: + +```bash +npm install -g bash-language-server +``` + +## Contributing + +Issues and PRs welcome at https://github.com/ivuorinen/zed-shellspec + +## License + +MIT diff --git a/extension.toml b/extension.toml new file mode 100644 index 0000000..7d165e7 --- /dev/null +++ b/extension.toml @@ -0,0 +1,15 @@ +id = "shellspec" +name = "ShellSpec BDD Testing" +version = "0.1.0" +schema_version = 1 +authors = ["Ismo Vuorinen "] +description = "Language support for ShellSpec BDD testing framework" +repository = "https://github.com/ivuorinen/shellspec-zed" + +[grammars.shellspec] +repository = "https://github.com/tree-sitter/tree-sitter-bash" +rev = "main" + +[language_servers.bash-language-server] +name = "Bash Language Server" +languages = ["ShellSpec"] diff --git a/languages/shellspec/brackets.scm b/languages/shellspec/brackets.scm new file mode 100644 index 0000000..2f67b42 --- /dev/null +++ b/languages/shellspec/brackets.scm @@ -0,0 +1,25 @@ +; Quote pairs +("'" @open "'" @close) +("\"" @open "\"" @close) + +; Command substitution +("$(" @open ")" @close) +("${" @open "}" @close) + +; Parentheses +("(" @open ")" @close) + +; Square brackets +("[" @open "]" @close) + +; Curly braces +("{" @open "}" @close) + +; ShellSpec block structure (if using End keyword) +(simple_command + (command_name) @_cmd + (#match? @_cmd "^(Describe|Context|It|Specify|Example)$")) @open + +(simple_command + (command_name) @_end + (#eq? @_end "End")) @close diff --git a/languages/shellspec/config.toml b/languages/shellspec/config.toml new file mode 100644 index 0000000..68e8ef0 --- /dev/null +++ b/languages/shellspec/config.toml @@ -0,0 +1,29 @@ +name = "ShellSpec" +grammar = "bash" +path_suffixes = ["_spec.sh", ".spec.sh"] +line_comments = ["# "] +tab_size = 2 +hard_tabs = false +first_line_pattern = "^#!/.*shellspec" + +[[brackets]] +start = "'" +end = "'" +close = true +newline = false +not_in = ["string", "comment"] + +[[brackets]] +start = "\"" +end = "\"" +close = true +newline = false +not_in = ["string", "comment"] + +[[brackets]] +start = "$(" +end = ")" +close = true +newline = true + +block_comment = { start = ": '", end = "'" } diff --git a/languages/shellspec/highlights.scm b/languages/shellspec/highlights.scm new file mode 100644 index 0000000..bec9ff5 --- /dev/null +++ b/languages/shellspec/highlights.scm @@ -0,0 +1,73 @@ +; BDD Structure Keywords +["Describe" "Context" "ExampleGroup"] @keyword.function +["It" "Specify" "Example"] @keyword.function +["Todo"] @keyword.function + +; Prefixed block keywords +["xDescribe" "xContext" "xExampleGroup" "xIt" "xSpecify" "xExample"] @keyword.function.inactive +["fDescribe" "fContext" "fExampleGroup" "fIt" "fSpecify" "fExample"] @keyword.function.focus + +; Control flow +["Pending" "Skip"] @keyword.control +["When" "The" "Assert"] @keyword.control +["End"] @keyword.control + +; Hook keywords +["BeforeEach" "AfterEach" "BeforeAll" "AfterAll"] @keyword.function +["BeforeCall" "AfterCall" "BeforeRun" "AfterRun"] @keyword.function +["Before" "After"] @keyword.function + +; Helper keywords +["Include" "Set" "Data" "Parameters" "Dump"] @keyword +["Path" "File" "Dir"] @keyword + +; Evaluation keywords +["call" "run" "command" "script" "source"] @function.method + +; Assertion keywords +["should" "not"] @keyword.operator +["output" "stdout" "error" "stderr" "status" "variable" "path"] @variable.builtin + +; Matchers +["equal" "eq" "be" "exist" "valid" "satisfy"] @function.method +["match" "start_with" "end_with" "include" "contain"] @function.method + +; Modifiers +["line" "word" "length" "contents" "result"] @variable.parameter +["first" "second" "third" "of"] @variable.parameter + +; Language chains +["a" "an" "as" "the"] @keyword.operator + +; Skip conditional +(word) @keyword.control + (#match? @keyword.control "^Skip\\s+if$") + +; Test descriptions and strings +(string) @string +(raw_string) @string + +; Comments +(comment) @comment + +; Numbers +(number) @number + +; Variables +(variable_name) @variable +(variable_assignment) @variable + +; Function definitions +(function_definition + name: (word) @function) + +; Command names +(command_name) @function + +; Data block markers +(word) @punctuation.special + (#eq? @punctuation.special "#|") + +; Tags (key:value pairs) +(word) @label + (#match? @label "\\w+:\\w+") diff --git a/languages/shellspec/indents.scm b/languages/shellspec/indents.scm new file mode 100644 index 0000000..8a5e9f5 --- /dev/null +++ b/languages/shellspec/indents.scm @@ -0,0 +1,38 @@ +; Indent content inside BDD blocks +(simple_command + (command_name) @_name + (#match? @_name "^(Describe|Context|ExampleGroup|It|Specify|Example)$")) @indent + +; Indent prefixed blocks +(simple_command + (command_name) @_name + (#match? @_name "^[xf](Describe|Context|ExampleGroup|It|Specify|Example)$")) @indent + +; Indent hook content +(simple_command + (command_name) @_name + (#match? @_name "^(BeforeEach|AfterEach|BeforeAll|AfterAll|BeforeCall|AfterCall|BeforeRun|AfterRun)$")) @indent + +; Indent Data blocks +(simple_command + (command_name) @_name + (#match? @_name "^(Data|Parameters)$")) @indent + +; Indent function definitions +(function_definition) @indent + +; Indent compound statements +(compound_statement) @indent + +; Indent conditional statements +(if_statement) @indent +(for_statement) @indent +(while_statement) @indent + +; Indent pipeline continuations +(pipeline) @indent + +; Dedent End keyword +(simple_command + (command_name) @_name + (#eq? @_name "End")) @dedent diff --git a/languages/shellspec/injections.scm b/languages/shellspec/injections.scm new file mode 100644 index 0000000..0f311e2 --- /dev/null +++ b/languages/shellspec/injections.scm @@ -0,0 +1,40 @@ +; Inject shell highlighting in function definitions +(function_definition + body: (compound_statement) @injection.content + (#set! injection.language "bash")) + +; Inject shell in command substitutions +(command_substitution + "$(" @injection.punctuation.bracket + (_) @injection.content + (#set! injection.language "bash") + ")" @injection.punctuation.bracket) + +; Inject shell in When blocks with call/run +(simple_command + (command_name) @_when + (#eq? @_when "When") + (word) @_action + (#match? @_action "^(call|run)$") + (word)+ @injection.content + (#set! injection.language "bash")) + +; Inject shell in arithmetic expressions +(arithmetic_expansion + "$((" @injection.punctuation.bracket + (_) @injection.content + (#set! injection.language "bash") + "))" @injection.punctuation.bracket) + +; Inject shell in process substitution +(process_substitution + "<(" @injection.punctuation.bracket + (_) @injection.content + (#set! injection.language "bash") + ")" @injection.punctuation.bracket) + +(process_substitution + ">(" @injection.punctuation.bracket + (_) @injection.content + (#set! injection.language "bash") + ")" @injection.punctuation.bracket) diff --git a/languages/shellspec/outline.scm b/languages/shellspec/outline.scm new file mode 100644 index 0000000..9765246 --- /dev/null +++ b/languages/shellspec/outline.scm @@ -0,0 +1,33 @@ +; Test suites +(simple_command + (command_name) @_name + (#match? @_name "^(Describe|Context|ExampleGroup)$") + (word) @name) @item + +; Individual tests +(simple_command + (command_name) @_name + (#match? @_name "^(It|Specify|Example)$") + (word) @name) @item + +; Focused tests +(simple_command + (command_name) @_name + (#match? @_name "^f(Describe|Context|It|Specify|Example)$") + (word) @name) @item + +; Skipped tests +(simple_command + (command_name) @_name + (#match? @_name "^x(Describe|Context|It|Specify|Example)$") + (word) @name) @item + +; Hooks +(simple_command + (command_name) @_name + (#match? @_name "^(BeforeEach|AfterEach|BeforeAll|AfterAll)$") + (word) @name) @item + +; Function definitions +(function_definition + name: (word) @name) @item diff --git a/languages/shellspec/runnables.scm b/languages/shellspec/runnables.scm new file mode 100644 index 0000000..3bdb204 --- /dev/null +++ b/languages/shellspec/runnables.scm @@ -0,0 +1,29 @@ +; Individual test execution +(simple_command + (command_name) @_name + (#match? @_name "^(It|Specify|Example)$") + (word) @run) @shellspec-test + +; Test suite execution +(simple_command + (command_name) @_name + (#match? @_name "^(Describe|Context|ExampleGroup)$") + (word) @run) @shellspec-suite + +; Focused test execution +(simple_command + (command_name) @_name + (#match? @_name "^f(It|Specify|Example)$") + (word) @run) @shellspec-focused-test + +; Focused suite execution +(simple_command + (command_name) @_name + (#match? @_name "^f(Describe|Context|ExampleGroup)$") + (word) @run) @shellspec-focused-suite + +; Pending test markers +(simple_command + (command_name) @_name + (#match? @_name "^(Pending|Todo)$") + (word) @run) @shellspec-pending