diff --git a/.coderabbit.yaml b/.coderabbit.yaml index 0a5a718..5fcf397 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -1,5 +1,7 @@ +--- +# yaml-language-server: $schema=https://www.coderabbit.ai/integrations/schema.v2.json remote_config: - url: "https://raw.githubusercontent.com/ivuorinen/coderabbit/refs/heads/main/coderabbit.yaml" + url: "https://raw.githubusercontent.com/ivuorinen/coderabbit/main/coderabbit.yaml" path_instructions: - path: ".serena/**/*" instructions: >- diff --git a/.github/ISSUE_TEMPLATE/grammar_issue.md b/.github/ISSUE_TEMPLATE/grammar_issue.md index d6df017..2b38eb8 100644 --- a/.github/ISSUE_TEMPLATE/grammar_issue.md +++ b/.github/ISSUE_TEMPLATE/grammar_issue.md @@ -20,7 +20,7 @@ assignees: ivuorinen # Paste the problematic ShellSpec code here ``` -### Current parse tree output +### Current parse tree output If you can run `tree-sitter parse`, please include the current output: @@ -29,6 +29,7 @@ If you can run `tree-sitter parse`, please include the current output: ``` ### Expected parse tree structure + Describe or show what the parse tree should look like: ```text diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 8ab3e77..8d55cdf 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,5 +1,5 @@ --- -# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json +# yaml-language-server: $schema=https://www.schemastore.org/github-workflow.json name: "CodeQL" on: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fba79d0..9df3862 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,5 +1,5 @@ --- -# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json +# yaml-language-server: $schema=https://www.schemastore.org/github-workflow.json name: Release on: @@ -67,20 +67,36 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 with: script: | + // Find CI workflow by name or filename + const { data: allWorkflows } = await github.rest.actions.listRepoWorkflows({ + owner: context.repo.owner, + repo: context.repo.repo + }); + + const ciWorkflow = allWorkflows.workflows.find(w => + w.name === 'CI' || + w.path === '.github/workflows/test.yml' + ); + + if (!ciWorkflow) { + core.setFailed('Could not find CI workflow'); + return; + } + const { data: workflows } = await github.rest.actions.listWorkflowRuns({ owner: context.repo.owner, repo: context.repo.repo, - workflow_id: 'test.yml', + workflow_id: ciWorkflow.id, head_sha: context.sha, status: 'completed' }); const latestRun = workflows.workflow_runs[0]; if (!latestRun || latestRun.conclusion !== 'success') { - core.setFailed('CI workflow has not passed for this commit'); + core.setFailed(`CI workflow has not passed for this commit. Status: ${latestRun?.conclusion || 'not found'}`); } - console.log(`CI status: ${latestRun?.conclusion || 'not found'}`) + console.log(`CI workflow ID: ${ciWorkflow.id}, Status: ${latestRun?.conclusion || 'not found'}`) security: name: 🔒 Security Scan @@ -96,7 +112,15 @@ jobs: uses: actions/setup-node@7c12f8017d5436eb855f1ed4399f037a36fbd9e8 # v5.2.1 with: node-version: 24 - cache: npm + + - name: Cache Node.js dependencies + uses: actions/cache@dfa6ed13c88fe79b56c4ffc79a42db2e8d2b15a5 # v4.2.0 + with: + path: ~/.npm + key: ${{ runner.os }}-node-24-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node-24- + ${{ runner.os }}-node- - name: Install Dependencies run: npm ci || { echo "❌ npm install failed"; npm install; } @@ -126,16 +150,41 @@ jobs: uses: actions/setup-node@7c12f8017d5436eb855f1ed4399f037a36fbd9e8 # v5.2.1 with: node-version: 24 - cache: npm registry-url: "https://registry.npmjs.org" + - name: Cache Node.js dependencies + uses: actions/cache@dfa6ed13c88fe79b56c4ffc79a42db2e8d2b15a5 # v4.2.0 + with: + path: ~/.npm + key: ${{ runner.os }}-node-24-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node-24- + ${{ runner.os }}-node- + - name: Install Dependencies run: npm ci || { echo "❌ npm install failed"; npm install; } + - name: Cache Tree-sitter CLI + uses: actions/cache@dfa6ed13c88fe79b56c4ffc79a42db2e8d2b15a5 # v4.2.0 + with: + path: ~/.npm/_npx + key: ${{ runner.os }}-tree-sitter-cli-${{ hashFiles('package.json') }} + - name: Install Tree-sitter CLI run: npm install -g tree-sitter-cli + - name: Cache Generated Grammar + uses: actions/cache@dfa6ed13c88fe79b56c4ffc79a42db2e8d2b15a5 # v4.2.0 + id: cache-grammar + with: + path: | + src/parser.c + src/tree_sitter/ + binding.gyp + key: ${{ runner.os }}-grammar-${{ hashFiles('grammar.js', 'package.json') }} + - name: Generate Grammar + if: steps.cache-grammar.outputs.cache-hit != 'true' run: npm run generate - name: 🏗️ Build Parser diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 8940719..9e5ba6d 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -1,5 +1,5 @@ --- -# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json +# yaml-language-server: $schema=https://www.schemastore.org/github-workflow.json name: Stale on: diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml index a2023a6..80fe901 100644 --- a/.github/workflows/sync-labels.yml +++ b/.github/workflows/sync-labels.yml @@ -1,5 +1,5 @@ --- -# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json +# yaml-language-server: $schema=https://www.schemastore.org/github-workflow.json name: Sync Labels on: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6089fda..796d8b1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,5 +1,5 @@ --- -# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json +# yaml-language-server: $schema=https://www.schemastore.org/github-workflow.json name: CI on: @@ -34,21 +34,58 @@ jobs: uses: actions/setup-node@7c12f8017d5436eb855f1ed4399f037a36fbd9e8 # v5.2.1 with: node-version: ${{ matrix.node-version }} - cache: npm + + - name: Cache Node.js dependencies + uses: actions/cache@dfa6ed13c88fe79b56c4ffc79a42db2e8d2b15a5 # v4.2.0 + id: cache-npm + with: + path: ~/.npm + key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node-${{ matrix.node-version }}- + ${{ runner.os }}-node- - name: Install Dependencies run: npm ci || { echo "❌ npm install failed"; npm install; } shell: bash + - name: Cache Tree-sitter CLI + uses: actions/cache@dfa6ed13c88fe79b56c4ffc79a42db2e8d2b15a5 # v4.2.0 + id: cache-tree-sitter + with: + path: ~/.npm/_npx + key: ${{ runner.os }}-tree-sitter-cli-${{ hashFiles('package.json') }} + - name: Install Tree-sitter CLI run: npm install -g tree-sitter-cli shell: bash + - name: Cache Generated Grammar + uses: actions/cache@dfa6ed13c88fe79b56c4ffc79a42db2e8d2b15a5 # v4.2.0 + id: cache-grammar + with: + path: | + src/parser.c + src/tree_sitter/ + binding.gyp + key: ${{ runner.os }}-grammar-${{ hashFiles('grammar.js', 'package.json') }} + - name: Generate Grammar + if: steps.cache-grammar.outputs.cache-hit != 'true' run: npm run generate shell: bash + - name: Cache Built Parser + uses: actions/cache@dfa6ed13c88fe79b56c4ffc79a42db2e8d2b15a5 # v4.2.0 + id: cache-parser + with: + path: | + build/ + node_modules/ + key: ${{ runner.os }}-parser-${{ matrix.node-version }}-${{ hashFiles('src/parser.c', 'binding.gyp', 'package.json') }} + - name: Build Parser + if: steps.cache-parser.outputs.cache-hit != 'true' run: npm run build shell: bash @@ -91,7 +128,7 @@ jobs: End EOF - tree-sitter parse test_sample.shellspec --quiet || { + tree-sitter parse --language=shellspec test_sample.shellspec --quiet || { echo "❌ Parser failed on sample ShellSpec code" exit 1 } @@ -111,7 +148,15 @@ jobs: uses: actions/setup-node@7c12f8017d5436eb855f1ed4399f037a36fbd9e8 # v5.2.1 with: node-version: 24 - cache: npm + + - name: Cache Node.js dependencies + uses: actions/cache@dfa6ed13c88fe79b56c4ffc79a42db2e8d2b15a5 # v4.2.0 + with: + path: ~/.npm + key: ${{ runner.os }}-node-24-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node-24- + ${{ runner.os }}-node- - name: Install Dependencies run: npm ci || { echo "❌ npm install failed"; npm install; } @@ -134,7 +179,18 @@ jobs: uses: actions/setup-node@7c12f8017d5436eb855f1ed4399f037a36fbd9e8 # v5.2.1 with: node-version: 24 - cache: npm + + - name: Cache Node.js dependencies + uses: actions/cache@dfa6ed13c88fe79b56c4ffc79a42db2e8d2b15a5 # v4.2.0 + with: + path: ~/.npm + key: ${{ runner.os }}-node-24-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-node-24- + ${{ runner.os }}-node- + + - name: Install Dependencies + run: npm ci || { echo "❌ npm install failed"; npm install; } - name: Test Coverage Analysis id: coverage @@ -142,11 +198,15 @@ jobs: echo "## Test Coverage Report" > coverage_report.md echo "" >> coverage_report.md - # Run tests and capture output + # Run tests and capture output with exit code + set +e # Don't exit on test failure TEST_OUTPUT=$(npm test 2>&1) - TOTAL_TESTS=$(echo "$TEST_OUTPUT" | grep -cE "^\s+[0-9]+\.") - PASSING_TESTS=$(echo "$TEST_OUTPUT" | grep -cE "^\s+[0-9]+\. ✓") - FAILING_TESTS=$(echo "$TEST_OUTPUT" | grep -cE "^\s+[0-9]+\. ✗") + TEST_EXIT=$? + set -e # Re-enable exit on error + + TOTAL_TESTS=$(echo "$TEST_OUTPUT" | grep -cE "^\s+[0-9]+\." || echo "0") + PASSING_TESTS=$(echo "$TEST_OUTPUT" | grep -cE "^\s+[0-9]+\. ✓" || echo "0") + FAILING_TESTS=$(echo "$TEST_OUTPUT" | grep -cE "^\s+[0-9]+\. ✗" || echo "0") { echo "- **Total Tests:** $TOTAL_TESTS" @@ -165,7 +225,7 @@ jobs: echo "" echo "### Test Files" } >> coverage_report.md - echo "$TEST_OUTPUT" | grep -E "^\s+[a-z_]+:" | sed 's/^/- /' >> coverage_report.md + echo "$TEST_OUTPUT" | grep -E "^[[:space:]]+[A-Za-z0-9._/\\-]+:" | sed 's/^/- /' >> coverage_report.md cat coverage_report.md @@ -177,13 +237,13 @@ jobs: } >> "$GITHUB_OUTPUT" # Validate test coverage requirements - if [ "$TOTAL_TESTS" -lt 55 ]; then - echo "❌ Expected at least 55 tests, found $TOTAL_TESTS" + if [ "${TOTAL_TESTS:-0}" -lt 55 ]; then + echo "❌ Expected at least 55 tests, found ${TOTAL_TESTS:-0}" exit 1 fi - if [ "$FAILING_TESTS" -gt 0 ]; then - echo "❌ Found $FAILING_TESTS failing tests" + if [ "${FAILING_TESTS:-0}" -gt 0 ] || [ "$TEST_EXIT" -ne 0 ]; then + echo "❌ Found ${FAILING_TESTS:-0} failing tests or test runner failed (exit code: $TEST_EXIT)" exit 1 fi diff --git a/.gitignore b/.gitignore index 56f1245..d5d8214 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,7 @@ report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json yarn-debug.log* yarn-error.log yarn-error.log* +/.idea/ +.idea/* +/.vscode/ +.fleet/ diff --git a/.mega-linter.yml b/.mega-linter.yml index 141f589..6b9882b 100644 --- a/.mega-linter.yml +++ b/.mega-linter.yml @@ -1,4 +1,5 @@ --- +# yaml-language-server: $schema=https://raw.githubusercontent.com/megalinter/megalinter/main/megalinter/descriptors/schemas/megalinter-configuration.jsonschema.json # Configuration file for MegaLinter # See all available variables at # https://megalinter.io/configuration/ and in linters documentation @@ -18,8 +19,6 @@ SHOW_SKIPPED_LINTERS: false # Show skipped linters in MegaLinter log DISABLE_LINTERS: - REPOSITORY_DEVSKIM - JSON_PRETTIER - - BASH_EXEC - - BASH_SHELLCHECK - SPELL_LYCHEE YAML_YAMLLINT_CONFIG_FILE: .yamllint.yml diff --git a/.serena/memories/project_status_verified_2025.md b/.serena/memories/project_status_verified_2025.md index ef3e35e..1f95ff3 100644 --- a/.serena/memories/project_status_verified_2025.md +++ b/.serena/memories/project_status_verified_2025.md @@ -2,7 +2,7 @@ ## Current Status: PRODUCTION READY ✅ -This memory contains only verified, accurate information about the current project state as of December 2025. +This memory contains only verified, accurate information about the current project state as of September 2025. ## Core Project Facts @@ -141,7 +141,8 @@ src/ ### Configuration Files (Verified) ```text -├── .editorconfig # Code formatting rules +├── .coderabbit.yaml # CodeRabbit config +├── .editorconfig # Code formatting rules ├── .gitignore # Git ignore patterns ├── .markdownlint.json # Markdown linting config ├── .mega-linter.yml # MegaLinter configuration @@ -235,8 +236,11 @@ src/ ## Summary -The tree-sitter-shellspec project is a **professionally developed, production-ready** grammar that successfully parses all documented ShellSpec constructs. -With 61/61 tests passing, zero grammar warnings, optimized CI/CD workflows, and comprehensive tooling, it represents a high-quality open source project ready for immediate use in development workflows and editor integrations. +The tree-sitter-shellspec project is a **professionally developed, production-ready** +grammar that successfully parses all documented ShellSpec constructs. +With 61/61 tests passing, zero grammar warnings, optimized CI/CD workflows, +and comprehensive tooling, it represents a high-quality open source project +ready for immediate use in development workflows and editor integrations. **Last Verified**: December 2025 **Status**: All claims in this memory have been verified against the actual project state. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2bba267..720dcf1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -81,11 +81,8 @@ npm run rebuild # Check code style npm run lint -npm run format -- --check - -# Fix code style issues -npm run lint:fix -npm run format -- --write +npm run lint:yaml +npm run lint:markdown ``` ## Grammar Development diff --git a/README.md b/README.md index dbce6de..142a75e 100644 --- a/README.md +++ b/README.md @@ -222,8 +222,8 @@ End ### Prerequisites -- [Node.js](https://nodejs.org/) (v16 or later) -- [Tree-sitter CLI](https://github.com/tree-sitter/tree-sitter/tree/master/cli) (`npm install -g tree-sitter-cli`) +- [Node.js](https://nodejs.org/) (v22 or later) +- Tree-sitter CLI (provided via devDependency) — use `npx tree-sitter ` ### Setup diff --git a/grammar.js b/grammar.js index 514c638..b0d41de 100644 --- a/grammar.js +++ b/grammar.js @@ -13,18 +13,19 @@ module.exports = grammar(bashGrammar, { name: "shellspec", // Add conflicts to handle ambiguity between commands and ShellSpec constructs - conflicts: ($) => [ - // Essential bash conflicts only - [$._expression, $.command_name], - [$.command, $.variable_assignments], - [$.redirected_statement, $.command], - [$.redirected_statement, $.command_substitution], - [$.function_definition, $.command_name], - [$.pipeline], - // Required ShellSpec conflicts - [$.command_name, $.shellspec_data_block], - [$.shellspec_hook_block], - ], + conflicts: ($, previous) => + previous.concat([ + // Essential bash conflicts only + [$._expression, $.command_name], + [$.command, $.variable_assignments], + [$.redirected_statement, $.command], + [$.redirected_statement, $.command_substitution], + [$.function_definition, $.command_name], + [$.pipeline], + // Required ShellSpec conflicts + [$.command_name, $.shellspec_data_block], + [$.shellspec_hook_block], + ]), rules: { // Extend the main statement rule to include ShellSpec blocks and directives diff --git a/src/grammar.json b/src/grammar.json index ac4363d..9a8f48a 100644 --- a/src/grammar.json +++ b/src/grammar.json @@ -7824,6 +7824,29 @@ } ], "conflicts": [ + [ + "_expression", + "command_name" + ], + [ + "command", + "variable_assignments" + ], + [ + "redirected_statement", + "command" + ], + [ + "redirected_statement", + "command_substitution" + ], + [ + "function_definition", + "command_name" + ], + [ + "pipeline" + ], [ "_expression", "command_name" diff --git a/src/node-types.json b/src/node-types.json index 14fe1b4..3a09588 100644 --- a/src/node-types.json +++ b/src/node-types.json @@ -3328,4 +3328,4 @@ "type": "~", "named": false } -] \ No newline at end of file +]