mirror of
https://github.com/ivuorinen/tree-sitter-shellspec.git
synced 2026-03-10 08:01:45 +00:00
feat: implement complete tree-sitter-shellspec grammar with comprehensive testing (#1)
* feat: implement complete tree-sitter-shellspec grammar with comprehensive testing - Add full ShellSpec grammar extending tree-sitter-bash - Support all ShellSpec constructs: Describe, Context, It, hooks, utilities - Include Data block parsing with statements and argument styles - Add 61 comprehensive test cases covering real-world patterns - Implement optimized GitHub workflows with CI/CD automation - Configure complete development tooling (linting, formatting, pre-commit) - Add comprehensive documentation and contribution guidelines - Optimize grammar conflicts to zero warnings - Support editor integration for Neovim, VS Code, Emacs Breaking Changes: - Initial release, no previous API to break BREAKING CHANGE: Initial implementation of tree-sitter-shellspec grammar * fix(ci): checkout before using local actions * fix(ci): use inline steps instead of actions * fix(ci): add checkout before testing, cleanup * chore(ci): add coderabbit config * chore: lint and code review fixes * chore(ci): update workflows * refactor: enhance CI/CD workflows and apply CodeRabbit suggestions - Convert GitHub Actions from local to inline actions for better maintainability - Add comprehensive caching for npm dependencies, tree-sitter CLI, and build artifacts - Fix checkout steps missing in test matrix jobs - Apply defensive programming in test coverage validation - Use local tree-sitter CLI via npx instead of global installation - Update tree-sitter-cli to v0.25.0 for compatibility with tree-sitter-bash - Add proper tree-sitter field to package.json with grammar metadata - Fix grammar precedence for Data blocks (#| lines now have higher precedence) - Standardize dates in memory files to September 12, 2025 - Enhance workflow robustness with dynamic workflow ID resolution - Improve test file pattern matching and error handling This commit addresses all CodeRabbit review suggestions and optimizes GitHub Actions workflows for better performance and reliability. * fix: apply CodeRabbit nitpick suggestions and improve code quality - Fix grammar.js TypeScript errors by correcting optional field usage - Update .yamlignore to use more robust glob pattern (**/node_modules/**) - Remove hard-coded test count from README.md for maintainability - Fix shellcheck directive format (add space after #) in all test specs - Fix typos throughout test specifications: - 'can not' → 'cannot' - 'expantion' → 'expansion' - 'singnal' → 'signal' - 'It mean' → 'It means' - Update CODE_OF_CONDUCT.md HTTP links to HTTPS - Update tree-sitter parse command to use --scope instead of --language - Add comments to .mega-linter.yml explaining disabled linters All grammar tests still pass (61/61) and the parser functions correctly with the updated tree-sitter CLI v0.25.0. * perf: optimize grammar for 32x faster parsing performance - Reduce grammar conflicts to essential bash and ShellSpec rules only - Restore original precedence values for consistent rule ordering - Simplify Data block rule while maintaining all functionality - Add required statements field to match test expectations Performance improvements: - Parse speed: ~55 bytes/ms → 1784 bytes/ms (32x faster) - All 61 tests still pass (100% success rate) - Significantly reduced parser generation time and runtime complexity The optimizations focused on minimizing unnecessary conflicts and simplifying complex choice structures while preserving full ShellSpec grammar compatibility and correctness. * fix(ci): ensure parser is built before testing in GitHub workflows - Add explicit parser build step before sample code testing - Remove --scope parameter that requires parser to be compiled first - Fix tree-sitter CLI v0.25.0 compatibility issue in CI environment The issue was that tree-sitter CLI v0.25.0 requires the parser to be compiled before it can recognize custom language scopes. This fix ensures the parser is always built before attempting to parse test files, resolving the 'Unknown scope' error in GitHub Actions. * feat(ci): expand cache paths to support all Node.js package managers - Add comprehensive caching for npm, yarn, and pnpm package managers - Cache paths now include: - npm: ~/.npm, node_modules/.cache - yarn: ~/.yarn, ~/.cache/yarn, ~/.cache/yarn/global - pnpm: ~/.pnpm-store, ~/.cache/pnpm, ~/.local/share/pnpm/global - Update cache keys to include all lockfile types (package-lock.json, yarn.lock, pnpm-lock.yaml) - Rename 'Cache Tree-sitter CLI' to 'Cache npx store' for clarity - Apply changes consistently across test, lint, and coverage jobs This improves cache hit rates and build performance regardless of which Node.js package manager is used in the development environment. * chore: tweaks to megalinter and grammar.js * fix(scanner): address memory safety and correctness issues in C code - Add len==0 check in set_contains() to prevent buffer overflow - Add missing stdlib.h include in scanner.c - Clear heredoc stack properly in deserialize when length==0 - Ensure NUL termination in delimiter deserialization - Create alloc.c to define ts_current_* symbols for TREE_SITTER_REUSE_ALLOCATOR All changes tested with full test suite: 61/61 tests passing. Addresses PR #1 review comments from CodeRabbit. * ci: improve workflow determinism and security scanning - Add --language=shellspec flag to tree-sitter parse for deterministic grammar selection - Add C++ language to CodeQL analysis to scan src/scanner.c for security issues Addresses PR #1 review comments from CodeRabbit. * test: fix typos and incorrect hook usage in spec files - Fix 'yot' → 'yet' typos in test/spec/03.example_spec.sh - Fix 'Sometime' → 'Sometimes' and cpunum.sh references in test/spec/22.sourcced_spec.sh - Fix Before → After in after hook section of test/spec/07.before_after_hook_spec.sh - Improve wording and capitalization throughout hook spec file All 61 tests still passing after corrections. Addresses PR #1 review comments from Copilot and CodeRabbit. * docs: update Node.js requirement to match CI configuration - Change Node.js requirement from v16 to v22+ to align with CI matrix - Update tree-sitter CLI recommendation from global install to npx usage - Matches actual devDependency configuration in package.json Addresses PR #1 review comment from CodeRabbit. * chore: update dependencies and workflow actions - Update GitHub Actions to latest versions (checkout v6, setup-node v6, cache v4.3) - Update package dependencies - Format workflow files - Update .gitignore and project configuration * fix(ci): remove unsupported --language flag from tree-sitter parse The --language flag is not supported in tree-sitter-cli 0.25.10. Tree-sitter correctly auto-detects the grammar based on file extension. * chore: add prettier and format all files - Install prettier ^3.6.2 - Add .prettierrc with project formatting rules - Add .prettierignore to exclude generated files and dependencies - Add npm scripts: format and format:check - Format all files with prettier * chore: add eclint for editorconfig linting and fix violations - Install eclint ^2.8.1 for editorconfig validation and fixing - Add .eclintignore to exclude generated files and dependencies - Add npm scripts: lint:editorconfig and lint:editorconfig:fix - Fix indentation issues in CONTRIBUTING.md (3 spaces -> 2 spaces) - Fix code alignment in scanner.c to match editorconfig rules - Regenerate parser after scanner.c formatting changes * feat: add post-generation script to preserve buffer overflow fix Created scripts/post-generate.sh that automatically re-applies the critical buffer overflow fix to parser.h after tree-sitter generate runs. This fix prevents undefined behavior in set_contains() when accessing an empty array. The script is automatically executed after tree-sitter generate via the npm generate script. Added generate:only for cases where post-processing should be skipped. * fix: address code review findings and critical issues Critical Fixes: - Fixed EditorConfig violations in grammar.js, scanner.c, README.md, .mega-linter.yml - Changed JSDoc comments from 1-space to 2-space indent per .editorconfig - Fixed line length violations in README.md and .mega-linter.yml - Updated test count badge from 59/59 to 61/61 in README.md - Created queries/highlights.scm for syntax highlighting support - Updated package.json with repository and files fields Configuration Updates: - Added repository field pointing to GitHub - Added files field to control npm package contents - Properly formatted CONTRIBUTING.md with prettier All 61 tests passing (100% success rate) All critical EditorConfig violations resolved * enhance: add Data block test coverage and improve syntax highlighting High Priority Enhancements: - Added 2 new Data block test cases for :raw and :expand modifiers - Enhanced syntax highlighting with "End" keyword (block terminator) - Added Data block modifiers (:raw, :expand, #|) to highlighting Test Coverage: - 63/63 tests passing (100%) - Test count increased from 61 to 63 - Average parse speed: 623 bytes/ms * docs: add comprehensive grammar documentation and precedence explanation Medium Priority Enhancement: - Added detailed precedence strategy comments explaining how ShellSpec extends bash - Documented all 5 conflicts with resolution strategies - Explained why conflicts are necessary and optimal - Added context about GLR parsing and precedence hints Documentation improvements: - Precedence levels clearly explained (bash: 1, ShellSpec: 2) - Each conflict documented with resolution strategy - Notes on intentional design decisions - Helps future maintainers understand grammar design * fix: resolve documentation inconsistencies and add ExampleGroup variants Documentation Fixes: - README.md: Update test count from 59 to 63 (badge, features, test command) - README.md: Fix lint script references to actual npm scripts - CONTRIBUTING.md: Correct format script reference to npm run format:check - package.json: Remove non-existent yamllint script, split lint:markdown into check/fix variants Grammar Enhancements: - Add fExampleGroup and xExampleGroup to Context block variants - Regenerate parser with new grammar (63/63 tests passing, 100% success rate) Syntax Highlighting: - Add fExampleGroup and xExampleGroup to focused/skipped block highlights - Remove non-matching Data modifier tokens (:raw, :expand, #|) - Add "End" keyword as block terminator Memory File Corrections: - Remove incorrect merge_group trigger references - Remove pr-lint.yml workflow references (deleted in previous optimization) - Update test counts with timestamps (59→63, added 2025-12-11) - Update conflict count (13→5, optimized) Code Style: - Auto-format renovate.json and tree-sitter.json with prettier * chore: update dependencies and project configuration - Align tree-sitter dependencies to latest versions (bash 0.25.1, cli 0.25.10) - Clean up .gitignore redundant patterns and normalize path styles - Improve CodeRabbit configuration with path filters and simplified instructions - Add test corpus exclusion to match project intent * docs: improve documentation and memory files - Update CONTRIBUTING.md code style check commands with actual available scripts - Use npx tree-sitter in test examples to avoid assuming global installation - Improve project status memory file with proper JSON formatting - Add CI enforcement recommendation for zero-conflict grammar generation - Align prerequisites with CI requirements (Node 22+) * ci: improve workflow configuration and reliability - Replace global read-all permissions with scoped permissions (contents: read, actions: write) - Fix cache configuration to exclude node_modules and include package-lock.json - Improve CI workflow resolution with flexible path matching and pagination - Verify version instead of committing version bumps from CI - Detect prereleases and publish with appropriate npm tags (next vs latest) - Use generic test suite description in release notes to avoid drift * fix: remove non-existent locals.scm reference from tree-sitter.json Remove queries/locals.scm from locals array as the file does not exist. Only queries/highlights.scm is present in the repository. * security: replace vulnerable eclint with editorconfig-checker - Remove eclint@2.8.1 (has 15 vulnerabilities, possibly abandoned) - Add editorconfig-checker@6.1.1 (actively maintained, zero vulnerabilities) - Update npm scripts to use editorconfig-checker commands - Resolves all 15 security vulnerabilities (8 moderate, 7 high) editorconfig-checker is a more modern, actively maintained alternative written in Go with no Node.js dependency vulnerabilities. * style: fix JSDoc comment indentation * fix(ci): separate CodeQL languages in matrix Previously 'actions,javascript' was treated as a single language. Now correctly split into separate 'actions' and 'javascript' entries. * chore(deps): update GitHub Actions dependencies - actions/checkout: v6.0.0 -> v6.0.1 - actions/setup-node: v6.0.0 -> v6.1.0 - softprops/action-gh-release: v2.4.2 -> v2.5.0 - ivuorinen/actions/*: v2025.11.x -> v2025.12.10 * ci: restore pr-lint workflow from main * chore(deps): update GitHub Actions dependencies Update action pins: checkout v6.0.2, setup-node v6.3.0, cache v5.0.3, pr-lint v2026.03.07. Add checkov skip comment, VERSION prefix strip, and scanner.c to grammar cache key. * feat: extend grammar with hooks, mocks, statements, and directives Add 27 ShellSpec-specific grammar rules covering hook blocks/statements, mock blocks, When/The/Assert statements, Path/Set/Dump/Intercept statements, Parameters variants, Pending/Skip/Todo, and percent directives. Update highlights and test corpus with 128 passing tests. * docs: update README, CLAUDE.md, and test spec comments Comprehensive README rewrite documenting all 27 grammar rules, block types, statement types, and directives. Add CLAUDE.md project instructions for Claude Code. Update test spec file comments for clarity. * chore: update project config and dependencies Update tree-sitter-cli to ^0.26.6, remove broken lint:editorconfig:fix script. Update shellcheck disabled rules. Add JSDoc header to post-generate script. Update gitignore for build artifacts. * fix(ci): use env var for version in release publish step Replace direct expression interpolation with VERSION env var to fix actionlint SC2193 false positive in the npm publish step. * style: fix editorconfig and markdownlint issues Break long cache key YAML value into multiline scalar to comply with 160-character line length limit. * ci: update MegaLinter config for ShellSpec project Add disabled linters for generated/DSL code false positives, v8r exclude pattern, and broader path filter. Remove .coderabbit.yaml in favor of shared org config. * chore: add Claude Code automation config Add hooks (pre-edit guard for generated files, post-edit lint), skills (generate-and-test, add-shellspec-rule, debug-parse-failure, update-highlights, validate-release), and grammar-validator agent. * chore: update Serena memory files Update project status, real-world ShellSpec patterns, and GitHub workflows optimization memory files. * fix(ci): fix MegaLinter config and remove C from CodeQL - Change FILTER_REGEX_EXCLUDE from `>` to `>-` to strip trailing newline that silently broke all path exclusions - Add YAML_V8R_FILTER_REGEX_EXCLUDE to skip schema validation on .mega-linter.yml (schemastore enum is outdated for BASH_BASH_EXEC) - Remove "c" from CodeQL language matrix since src/parser.c is generated and produces false positives * fix: add missing test spec stub files - Add test/spec/lib.sh stub with calc() function (referenced by 01.very_simple_spec.sh) - Add test/spec/count_cpus.sh stub (referenced by 21.intercept_spec.sh)
This commit is contained in:
13
.claude/agents/grammar-validator.md
Normal file
13
.claude/agents/grammar-validator.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Grammar Validator Agent
|
||||||
|
|
||||||
|
Validate the tree-sitter-shellspec grammar by running tests and parsing spec files.
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
Run these checks and report results:
|
||||||
|
|
||||||
|
1. Run `npm test` and capture output. Report total tests and any failures.
|
||||||
|
2. For each file in `test/spec/*.sh`, run `tree-sitter parse <file>` and count ERROR nodes.
|
||||||
|
3. Report a summary: tests passed/failed, spec files with errors, total error count.
|
||||||
|
|
||||||
|
Only report — do not edit any files.
|
||||||
28
.claude/hooks/post-edit-lint.sh
Executable file
28
.claude/hooks/post-edit-lint.sh
Executable file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Post-edit lint: regenerates parser, checks editorconfig, validates corpus.
|
||||||
|
# Used by Claude Code PostToolUse hook via settings.json.
|
||||||
|
# Expects $CLAUDE_FILE_PATH to be set by the hook runner.
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
file="${CLAUDE_FILE_PATH:-}"
|
||||||
|
|
||||||
|
# Auto-regenerate parser after grammar.js changes
|
||||||
|
case "$file" in
|
||||||
|
*/grammar.js)
|
||||||
|
npm run generate >/dev/null 2>&1 || true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# EditorConfig compliance check
|
||||||
|
npx editorconfig-checker "$file" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Corpus format validation for test files
|
||||||
|
case "$file" in
|
||||||
|
*/test/corpus/*.txt)
|
||||||
|
if ! grep -cq '==========' "$file" 2>/dev/null \
|
||||||
|
|| ! grep -cq '^---$' "$file" 2>/dev/null; then
|
||||||
|
echo "WARNING: Corpus file may have invalid format"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
17
.claude/hooks/pre-edit-guard.sh
Executable file
17
.claude/hooks/pre-edit-guard.sh
Executable file
@@ -0,0 +1,17 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Pre-edit guard: blocks edits to generated and locked files.
|
||||||
|
# Used by Claude Code PreToolUse hook via settings.json.
|
||||||
|
# Expects $CLAUDE_FILE_PATH to be set by the hook runner.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
case "${CLAUDE_FILE_PATH:-}" in
|
||||||
|
*/src/parser.c | */src/grammar.json | */src/node-types.json)
|
||||||
|
echo "BLOCKED: Do not edit generated files in src/"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
*/package-lock.json)
|
||||||
|
echo "BLOCKED: Do not edit lock files directly"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
26
.claude/settings.json
Normal file
26
.claude/settings.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"hooks": {
|
||||||
|
"PreToolUse": [
|
||||||
|
{
|
||||||
|
"matcher": "Edit|Write",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": ".claude/hooks/pre-edit-guard.sh"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"PostToolUse": [
|
||||||
|
{
|
||||||
|
"matcher": "Edit|Write",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": ".claude/hooks/post-edit-lint.sh"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
48
.claude/skills/add-shellspec-rule/SKILL.md
Normal file
48
.claude/skills/add-shellspec-rule/SKILL.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
name: add-shellspec-rule
|
||||||
|
description: Add a new ShellSpec grammar rule with tests, following project conventions
|
||||||
|
---
|
||||||
|
|
||||||
|
# Add ShellSpec Rule
|
||||||
|
|
||||||
|
Follow these steps to add a new ShellSpec construct to the grammar:
|
||||||
|
|
||||||
|
## Arguments
|
||||||
|
|
||||||
|
- `rule_name` (required): Name of the ShellSpec construct (e.g., "Tag", "Filter")
|
||||||
|
- `type` (required): One of `block` (needs End terminator), `statement` (single-line), `directive` (% prefixed)
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. **Research the construct** in ShellSpec documentation. Understand its syntax, variants, and where it appears in real spec files.
|
||||||
|
|
||||||
|
2. **Add the rule to `grammar.js`**:
|
||||||
|
- Prefix the rule name with `shellspec_` (e.g., `shellspec_tag_statement`)
|
||||||
|
- For blocks: use `prec.right()` with `seq(keyword, ..., repeat($._terminated_statement), "End")`
|
||||||
|
- For statements: use `seq(keyword, ...args)`
|
||||||
|
- For directives: use `seq("%" + name, ...args)`
|
||||||
|
- Use `field()` for named parts (e.g., `field("description", ...)`)
|
||||||
|
|
||||||
|
3. **Register in `_statement_not_subshell`**: Add the new rule to the choice array in `_statement_not_subshell`.
|
||||||
|
|
||||||
|
4. **Create corpus tests** in the appropriate `test/corpus/*.txt` file:
|
||||||
|
- Include basic usage, all variants, and edge cases
|
||||||
|
- Follow the corpus format: `===` header, code, `---` separator, S-expression AST
|
||||||
|
- Aim for 3-5 test cases minimum
|
||||||
|
|
||||||
|
5. **Update `queries/highlights.scm`**: Add highlighting patterns for any new keywords.
|
||||||
|
|
||||||
|
6. **Verify**:
|
||||||
|
- Run `npm run generate` — check for new conflicts (minimize them)
|
||||||
|
- Run `npm test` — all tests must pass
|
||||||
|
- Run spec file check: `for f in test/spec/*.sh; do tree-sitter parse "$f" 2>&1 | grep -c ERROR; done`
|
||||||
|
|
||||||
|
7. **Update documentation**:
|
||||||
|
- Update the rule list in CLAUDE.md
|
||||||
|
- Update the ShellSpec Language Support section in README.md
|
||||||
|
|
||||||
|
## Grammar Gotchas (from CLAUDE.md)
|
||||||
|
|
||||||
|
- Don't use compound keyword tokens (e.g., `"Data:raw"`) — they force tokenizer behavior globally
|
||||||
|
- `prec(N)` on simple alternatives can beat `prec.right(M)` on blocks at the initial ambiguity point
|
||||||
|
- `[ ... ]` is `$.test_command` in tree-sitter-bash, not literal bracket tokens
|
||||||
53
.claude/skills/debug-parse-failure/SKILL.md
Normal file
53
.claude/skills/debug-parse-failure/SKILL.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
---
|
||||||
|
name: debug-parse-failure
|
||||||
|
description: Debug a ShellSpec parse failure by identifying ERROR nodes and tracing to grammar rules
|
||||||
|
---
|
||||||
|
|
||||||
|
# Debug Parse Failure
|
||||||
|
|
||||||
|
Diagnose why a ShellSpec file or snippet doesn't parse correctly.
|
||||||
|
|
||||||
|
## Arguments
|
||||||
|
|
||||||
|
- `file` (optional): Path to a file that fails to parse
|
||||||
|
- `snippet` (optional): Inline ShellSpec code to debug
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. **Parse the file/snippet** and identify ERROR nodes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tree-sitter parse <file> 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
|
If debugging a snippet, write it to a temp file first.
|
||||||
|
|
||||||
|
2. **Locate ERROR nodes**: Look for `(ERROR)` in the AST output. Note:
|
||||||
|
- The line/column where the error starts
|
||||||
|
- What the parser expected vs. what it found
|
||||||
|
- The surrounding AST context (what parsed successfully around it)
|
||||||
|
|
||||||
|
3. **Identify the grammar rule**: Based on the ShellSpec construct that failed:
|
||||||
|
- Read the relevant rule in `grammar.js`
|
||||||
|
- Check if the input matches the rule's expected pattern
|
||||||
|
- Look for keyword mismatches, missing fields, or unexpected tokens
|
||||||
|
|
||||||
|
4. **Check common causes** (from Grammar Gotchas):
|
||||||
|
- Is a compound keyword token interfering? (e.g., `"Data:raw"` as single token)
|
||||||
|
- Is a precedence conflict causing the wrong rule to win?
|
||||||
|
- Is a bash construct (like `[ ... ]` → `$.test_command`) not being recognized?
|
||||||
|
- Is the construct inside a block that doesn't include it in `_terminated_statement`?
|
||||||
|
|
||||||
|
5. **Test a fix**:
|
||||||
|
- Edit `grammar.js` with the proposed fix
|
||||||
|
- Run `npm run generate && npm test` to verify no regressions
|
||||||
|
- Re-parse the original file to confirm the fix works
|
||||||
|
|
||||||
|
6. **Verify broadly**: Run the full spec file check:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
for f in test/spec/*.sh; do
|
||||||
|
errors=$(tree-sitter parse "$f" 2>&1 | grep -c ERROR || true)
|
||||||
|
if [ "$errors" -gt 0 ]; then echo "ERRORS in $f: $errors"; fi
|
||||||
|
done
|
||||||
|
```
|
||||||
40
.claude/skills/generate-and-test/SKILL.md
Normal file
40
.claude/skills/generate-and-test/SKILL.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
name: generate-and-test
|
||||||
|
description: Generate parser from grammar.js, run all tests, and verify spec files parse cleanly
|
||||||
|
---
|
||||||
|
|
||||||
|
# Generate and Test
|
||||||
|
|
||||||
|
Run the full grammar validation workflow:
|
||||||
|
|
||||||
|
1. **Generate the parser** from grammar.js:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run generate
|
||||||
|
```
|
||||||
|
|
||||||
|
If generation fails, stop and report the error.
|
||||||
|
|
||||||
|
2. **Run all corpus tests**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm test
|
||||||
|
```
|
||||||
|
|
||||||
|
All tests must pass (100% success rate required). If any fail, report which tests failed.
|
||||||
|
|
||||||
|
3. **Verify real spec files parse without errors**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
for f in test/spec/*.sh; do
|
||||||
|
errors=$(tree-sitter parse "$f" 2>&1 | grep -c ERROR || true)
|
||||||
|
if [ "$errors" -gt 0 ]; then
|
||||||
|
echo "ERRORS in $f: $errors"
|
||||||
|
tree-sitter parse "$f" 2>&1 | grep ERROR
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
Report any spec files with parse errors.
|
||||||
|
|
||||||
|
4. **Summary**: Report total corpus tests passed, and spec file parse status.
|
||||||
30
.claude/skills/update-highlights/SKILL.md
Normal file
30
.claude/skills/update-highlights/SKILL.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
---
|
||||||
|
name: update-highlights
|
||||||
|
description: Update highlights.scm to cover all grammar rules, detecting missing patterns
|
||||||
|
---
|
||||||
|
|
||||||
|
# Update Highlights
|
||||||
|
|
||||||
|
Ensure `queries/highlights.scm` has syntax highlighting patterns for all grammar rules.
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. **Extract all ShellSpec keywords from grammar.js**: Find every string literal used as a keyword
|
||||||
|
(Describe, It, When, The, etc.) and every rule name prefixed with `shellspec_`.
|
||||||
|
|
||||||
|
2. **Read current highlights.scm**: Check which keywords and rules already have highlight patterns.
|
||||||
|
|
||||||
|
3. **Identify gaps**: List any keywords or node types from grammar.js that are missing from highlights.scm.
|
||||||
|
|
||||||
|
4. **Add missing patterns** following the existing conventions in highlights.scm:
|
||||||
|
- Block keywords (Describe, Context, It, etc.) → `@keyword`
|
||||||
|
- Modifier prefixes (f, x) → `@attribute`
|
||||||
|
- Hook keywords (BeforeEach, AfterAll, etc.) → `@keyword`
|
||||||
|
- Statement keywords (When, The, Assert, etc.) → `@keyword`
|
||||||
|
- Directive keywords (%text, %const, etc.) → `@function.macro`
|
||||||
|
- String descriptions → `@string`
|
||||||
|
- Matcher/subject words → `@variable.builtin` or `@function.builtin`
|
||||||
|
- `End` keyword → `@keyword`
|
||||||
|
|
||||||
|
5. **Verify**: Run `tree-sitter test` to ensure highlights don't cause issues.
|
||||||
|
Optionally test with `tree-sitter highlight test/spec/*.sh` if available.
|
||||||
58
.claude/skills/validate-release/SKILL.md
Normal file
58
.claude/skills/validate-release/SKILL.md
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
---
|
||||||
|
name: validate-release
|
||||||
|
description: Run full pre-release validation - tests, spec parsing, highlight coverage, build
|
||||||
|
disable-model-invocation: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Validate Release
|
||||||
|
|
||||||
|
Run comprehensive pre-release checks before tagging a release.
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
1. **Run all corpus tests**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm test
|
||||||
|
```
|
||||||
|
|
||||||
|
Must be 100% passing. Report the total test count (must be >= 96).
|
||||||
|
|
||||||
|
2. **Verify spec files parse cleanly**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
total_errors=0
|
||||||
|
for f in test/spec/*.sh; do
|
||||||
|
errors=$(tree-sitter parse "$f" 2>&1 | grep -c ERROR || true)
|
||||||
|
if [ "$errors" -gt 0 ]; then
|
||||||
|
echo "ERRORS in $f: $errors"
|
||||||
|
total_errors=$((total_errors + errors))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo "Total spec file errors: $total_errors"
|
||||||
|
```
|
||||||
|
|
||||||
|
Must be 0 errors.
|
||||||
|
|
||||||
|
3. **Check highlight coverage**: Compare keywords in grammar.js against patterns in
|
||||||
|
queries/highlights.scm. Report any uncovered keywords.
|
||||||
|
|
||||||
|
4. **Build the parser**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
Must succeed without errors.
|
||||||
|
|
||||||
|
5. **Verify README accuracy**:
|
||||||
|
- Count rules in grammar.js and compare to the number documented in README.md
|
||||||
|
- Check that all block types, statement types, and directive types listed in README match grammar.js
|
||||||
|
|
||||||
|
6. **Check EditorConfig compliance**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx editorconfig-checker
|
||||||
|
```
|
||||||
|
|
||||||
|
7. **Summary**: Report pass/fail for each check, with details on any failures. All checks must pass for release.
|
||||||
21
.eclintignore
Normal file
21
.eclintignore
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
src/parser.c
|
||||||
|
src/grammar.json
|
||||||
|
src/node-types.json
|
||||||
|
src/tree_sitter/
|
||||||
|
|
||||||
|
# Build artifacts
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
megalinter-reports/
|
||||||
|
|
||||||
|
# Lock files
|
||||||
|
package-lock.json
|
||||||
|
pnpm-lock.yaml
|
||||||
|
yarn.lock
|
||||||
44
.github/CODE_OF_CONDUCT.md
vendored
44
.github/CODE_OF_CONDUCT.md
vendored
@@ -33,15 +33,15 @@ fullest extent, we want to know.
|
|||||||
|
|
||||||
The following behaviors are expected and requested of all community members:
|
The following behaviors are expected and requested of all community members:
|
||||||
|
|
||||||
* Participate in an authentic and active way. In doing so, you contribute to the
|
- Participate in an authentic and active way. In doing so, you contribute to the
|
||||||
health and longevity of this community.
|
health and longevity of this community.
|
||||||
* Exercise consideration and respect in your speech and actions.
|
- Exercise consideration and respect in your speech and actions.
|
||||||
* Attempt collaboration before conflict.
|
- Attempt collaboration before conflict.
|
||||||
* Refrain from demeaning, discriminatory, or harassing behavior and speech.
|
- Refrain from demeaning, discriminatory, or harassing behavior and speech.
|
||||||
* Be mindful of your surroundings and of your fellow participants. Alert
|
- Be mindful of your surroundings and of your fellow participants. Alert
|
||||||
community leaders if you notice a dangerous situation, someone in distress, or
|
community leaders if you notice a dangerous situation, someone in distress, or
|
||||||
violations of this Code of Conduct, even if they seem inconsequential.
|
violations of this Code of Conduct, even if they seem inconsequential.
|
||||||
* Remember that community event venues may be shared with members of the public;
|
- Remember that community event venues may be shared with members of the public;
|
||||||
please be respectful to all patrons of these locations.
|
please be respectful to all patrons of these locations.
|
||||||
|
|
||||||
## 4. Unacceptable Behavior
|
## 4. Unacceptable Behavior
|
||||||
@@ -49,23 +49,23 @@ The following behaviors are expected and requested of all community members:
|
|||||||
The following behaviors are considered harassment and are unacceptable within
|
The following behaviors are considered harassment and are unacceptable within
|
||||||
our community:
|
our community:
|
||||||
|
|
||||||
* Violence, threats of violence or violent language directed against another
|
- Violence, threats of violence or violent language directed against another
|
||||||
person.
|
person.
|
||||||
* Sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory
|
- Sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory
|
||||||
jokes and language.
|
jokes and language.
|
||||||
* Posting or displaying sexually explicit or violent material.
|
- Posting or displaying sexually explicit or violent material.
|
||||||
* Posting or threatening to post other people's personally identifying
|
- Posting or threatening to post other people's personally identifying
|
||||||
information ("doxing").
|
information ("doxing").
|
||||||
* Personal insults, particularly those related to gender, sexual orientation,
|
- Personal insults, particularly those related to gender, sexual orientation,
|
||||||
race, religion, or disability.
|
race, religion, or disability.
|
||||||
* Inappropriate photography or recording.
|
- Inappropriate photography or recording.
|
||||||
* Inappropriate physical contact. You should have someone's consent before
|
- Inappropriate physical contact. You should have someone's consent before
|
||||||
touching them.
|
touching them.
|
||||||
* Unwelcome sexual attention. This includes, sexualized comments or jokes;
|
- Unwelcome sexual attention. This includes, sexualized comments or jokes;
|
||||||
inappropriate touching, groping, and unwelcomed sexual advances.
|
inappropriate touching, groping, and unwelcomed sexual advances.
|
||||||
* Deliberate intimidation, stalking or following (online or in person).
|
- Deliberate intimidation, stalking or following (online or in person).
|
||||||
* Advocating for, or encouraging, any of the above behavior.
|
- Advocating for, or encouraging, any of the above behavior.
|
||||||
* Sustained disruption of community events, including talks and presentations.
|
- Sustained disruption of community events, including talks and presentations.
|
||||||
|
|
||||||
## 5. Weapons Policy
|
## 5. Weapons Policy
|
||||||
|
|
||||||
@@ -133,13 +133,13 @@ under a [Creative Commons Attribution-ShareAlike license][cc-by-sa].
|
|||||||
Portions of text derived from the [Django Code of Conduct][django] and
|
Portions of text derived from the [Django Code of Conduct][django] and
|
||||||
the [Geek Feminism Anti-Harassment Policy][geek-feminism].
|
the [Geek Feminism Anti-Harassment Policy][geek-feminism].
|
||||||
|
|
||||||
* _Revision 2.3. Posted 6 March 2017._
|
- _Revision 2.3. Posted 6 March 2017._
|
||||||
* _Revision 2.2. Posted 4 February 2016._
|
- _Revision 2.2. Posted 4 February 2016._
|
||||||
* _Revision 2.1. Posted 23 June 2014._
|
- _Revision 2.1. Posted 23 June 2014._
|
||||||
* _Revision 2.0, adopted by the [Stumptown Syndicate][stumptown] board on 10
|
- _Revision 2.0, adopted by the [Stumptown Syndicate][stumptown] board on 10
|
||||||
January 2013. Posted 17 March 2013._
|
January 2013. Posted 17 March 2013._
|
||||||
|
|
||||||
[stumptown]: https://github.com/stumpsyn
|
[stumptown]: https://github.com/stumpsyn
|
||||||
[cc-by-sa]: https://creativecommons.org/licenses/by-sa/3.0/
|
[cc-by-sa]: https://creativecommons.org/licenses/by-sa/3.0/
|
||||||
[django]: https://www.djangoproject.com/conduct/
|
[django]: https://www.djangoproject.com/conduct/
|
||||||
[geek-feminism]: http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy
|
[geek-feminism]: https://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy
|
||||||
|
|||||||
56
.github/ISSUE_TEMPLATE/bug_report.md
vendored
56
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,38 +1,44 @@
|
|||||||
---
|
---
|
||||||
name: Bug report
|
name: Bug report
|
||||||
about: Create a report to help us improve
|
about: Report a parsing issue or bug in tree-sitter-shellspec
|
||||||
title: ''
|
title: "[BUG] "
|
||||||
labels: bug
|
labels: bug
|
||||||
assignees: ivuorinen
|
assignees: ivuorinen
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Describe the bug**
|
**Describe the bug**
|
||||||
A clear and concise description of what the bug is.
|
A clear and concise description of the parsing issue or bug.
|
||||||
|
|
||||||
**To Reproduce**
|
**ShellSpec code that doesn't parse correctly**
|
||||||
Steps to reproduce the behavior:
|
Please provide the ShellSpec code that causes the issue:
|
||||||
1. Go to '...'
|
|
||||||
2. Click on '....'
|
|
||||||
3. Scroll down to '....'
|
|
||||||
4. See error
|
|
||||||
|
|
||||||
**Expected behavior**
|
```shellspec
|
||||||
A clear and concise description of what you expected to happen.
|
# Paste your ShellSpec code here
|
||||||
|
```
|
||||||
|
|
||||||
**Screenshots**
|
**Expected parsing behavior**
|
||||||
If applicable, add screenshots to help explain your problem.
|
A clear description of how the code should be parsed or what syntax highlighting you expected.
|
||||||
|
|
||||||
**Desktop (please complete the following information):**
|
**Actual behavior**
|
||||||
- OS: [e.g. iOS]
|
What actually happens when the parser encounters this code? Include any error messages.
|
||||||
- Browser [e.g. chrome, safari]
|
|
||||||
- Version [e.g. 22]
|
|
||||||
|
|
||||||
**Smartphone (please complete the following information):**
|
**Environment:**
|
||||||
- Device: [e.g. iPhone6]
|
|
||||||
- OS: [e.g. iOS8.1]
|
|
||||||
- Browser [e.g. stock browser, safari]
|
|
||||||
- Version [e.g. 22]
|
|
||||||
|
|
||||||
**Additional context**
|
- OS: [e.g. Linux, macOS, Windows]
|
||||||
Add any other context about the problem here.
|
- Editor: [e.g. Neovim, VS Code, Emacs]
|
||||||
|
- tree-sitter-shellspec version: [e.g. 0.1.0]
|
||||||
|
- tree-sitter version: [e.g. 0.20.0]
|
||||||
|
- ShellSpec version: [e.g. 0.28.1]
|
||||||
|
|
||||||
|
**Tree-sitter parse output (if applicable)**
|
||||||
|
If you can run `tree-sitter parse`, please include the output:
|
||||||
|
|
||||||
|
```text
|
||||||
|
# tree-sitter parse output here
|
||||||
|
```
|
||||||
|
|
||||||
|
## Additional context
|
||||||
|
|
||||||
|
- Is this code from a real ShellSpec test file?
|
||||||
|
- Does the code work correctly with the ShellSpec test runner?
|
||||||
|
- Any other context that might help debug the issue.
|
||||||
|
|||||||
32
.github/ISSUE_TEMPLATE/feature_request.md
vendored
32
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,20 +1,32 @@
|
|||||||
---
|
---
|
||||||
name: Feature request
|
name: Feature request
|
||||||
about: Suggest an idea for this project
|
about: Suggest a grammar enhancement or new feature for tree-sitter-shellspec
|
||||||
title: ''
|
title: "[FEATURE] "
|
||||||
labels: enhancement
|
labels: enhancement
|
||||||
assignees: ivuorinen
|
assignees: ivuorinen
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
**Is your feature request related to a ShellSpec parsing issue?**
|
||||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
A clear description of what ShellSpec syntax is not currently supported. Ex. "Data blocks with :expand modifier are not parsed correctly"
|
||||||
|
|
||||||
|
**ShellSpec syntax example**
|
||||||
|
Please provide an example of the ShellSpec syntax you'd like to see supported:
|
||||||
|
|
||||||
|
```shellspec
|
||||||
|
# Example ShellSpec code that should be supported
|
||||||
|
```
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
**Describe the solution you'd like**
|
||||||
A clear and concise description of what you want to happen.
|
A clear description of how this syntax should be parsed or highlighted.
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
**Current behavior**
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
How does the parser currently handle this syntax? (if at all)
|
||||||
|
|
||||||
**Additional context**
|
**Use case**
|
||||||
Add any other context or screenshots about the feature request here.
|
Why is this syntax important? How commonly is it used in ShellSpec tests?
|
||||||
|
|
||||||
|
## Additional context
|
||||||
|
|
||||||
|
- Link to ShellSpec documentation for this feature (if available)
|
||||||
|
- Examples from real-world ShellSpec test suites
|
||||||
|
- Any other context or screenshots about the feature request
|
||||||
|
|||||||
64
.github/ISSUE_TEMPLATE/grammar_issue.md
vendored
Normal file
64
.github/ISSUE_TEMPLATE/grammar_issue.md
vendored
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
---
|
||||||
|
name: Grammar Issue
|
||||||
|
about: Report a specific grammar parsing problem or conflict
|
||||||
|
title: "[GRAMMAR] "
|
||||||
|
labels: grammar, bug
|
||||||
|
assignees: ivuorinen
|
||||||
|
---
|
||||||
|
|
||||||
|
## Grammar Issue Type
|
||||||
|
|
||||||
|
- [ ] Parsing error (code doesn't parse at all)
|
||||||
|
- [ ] Incorrect parse tree structure
|
||||||
|
- [ ] Grammar conflicts during generation
|
||||||
|
- [ ] Performance issue with large files
|
||||||
|
- [ ] Integration issue with tree-sitter-bash
|
||||||
|
|
||||||
|
## ShellSpec code causing the issue
|
||||||
|
|
||||||
|
```shellspec
|
||||||
|
# Paste the problematic ShellSpec code here
|
||||||
|
```
|
||||||
|
|
||||||
|
### Current parse tree output
|
||||||
|
|
||||||
|
If you can run `tree-sitter parse`, please include the current output:
|
||||||
|
|
||||||
|
```text
|
||||||
|
# Current parse tree here
|
||||||
|
```
|
||||||
|
|
||||||
|
### Expected parse tree structure
|
||||||
|
|
||||||
|
Describe or show what the parse tree should look like:
|
||||||
|
|
||||||
|
```text
|
||||||
|
# Expected parse tree structure here
|
||||||
|
```
|
||||||
|
|
||||||
|
### Grammar generation output
|
||||||
|
|
||||||
|
If this causes issues during `npm run generate`, include any conflict warnings or errors:
|
||||||
|
|
||||||
|
```text
|
||||||
|
# Grammar generation output here
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment
|
||||||
|
|
||||||
|
- tree-sitter-shellspec version: [e.g. 0.1.0]
|
||||||
|
- tree-sitter CLI version: [e.g. 0.20.8]
|
||||||
|
- Node.js version: [e.g. 18.17.0]
|
||||||
|
- OS: [e.g. macOS 13.5]
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- How does this affect syntax highlighting?
|
||||||
|
- Does it break editor functionality?
|
||||||
|
- Is this blocking real-world usage?
|
||||||
|
|
||||||
|
## Additional context
|
||||||
|
|
||||||
|
- Related to specific ShellSpec features?
|
||||||
|
- Reproducible with minimal example?
|
||||||
|
- Any workarounds discovered?
|
||||||
8
.github/renovate.json
vendored
8
.github/renovate.json
vendored
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
"extends": [
|
"extends": [
|
||||||
"github>ivuorinen/renovate-config"
|
"github>ivuorinen/renovate-config"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -25,10 +25,10 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
language: ["actions"]
|
language: ["actions", "javascript"]
|
||||||
steps:
|
steps:
|
||||||
- name: CodeQL Analysis
|
- name: CodeQL Analysis
|
||||||
uses: ivuorinen/actions/codeql-analysis@97105fc2a909360678588cb50caf0be5144be486 # v2026.03.06
|
uses: ivuorinen/actions/codeql-analysis@242ecca8f01357727f5b2b040154ccdece61720b # v2026.03.07
|
||||||
with:
|
with:
|
||||||
language: ${{ matrix.language }}
|
language: ${{ matrix.language }}
|
||||||
queries: security-and-quality
|
queries: security-and-quality
|
||||||
|
|||||||
2
.github/workflows/pr-lint.yml
vendored
2
.github/workflows/pr-lint.yml
vendored
@@ -27,4 +27,4 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Run PR Lint
|
- name: Run PR Lint
|
||||||
# https://github.com/ivuorinen/actions
|
# https://github.com/ivuorinen/actions
|
||||||
uses: ivuorinen/actions/pr-lint@6e8f2aae9d0846d901d9eba15b8e94a2900573dc # v2026.03.02
|
uses: ivuorinen/actions/pr-lint@242ecca8f01357727f5b2b040154ccdece61720b # v2026.03.07
|
||||||
|
|||||||
277
.github/workflows/release.yml
vendored
Normal file
277
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
---
|
||||||
|
# yaml-language-server: $schema=https://www.schemastore.org/github-workflow.json
|
||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*.*.*"
|
||||||
|
# checkov:skip=CKV_GHA_7:Release workflow requires version input for manual dispatch
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: "Version to release (e.g., 1.0.0)"
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate:
|
||||||
|
name: 🔍 Validate Release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 10
|
||||||
|
|
||||||
|
outputs:
|
||||||
|
version: ${{ steps.version.outputs.version }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: ⤵️ Checkout Repository
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: 🔢 Extract Version
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||||
|
VERSION="${{ github.event.inputs.version }}"
|
||||||
|
VERSION="${VERSION#v}"
|
||||||
|
echo "version=v${VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
VERSION="${GITHUB_REF#refs/tags/}"
|
||||||
|
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
echo "Releasing version: ${VERSION}"
|
||||||
|
|
||||||
|
- name: ✅ Validate Version Format
|
||||||
|
run: |
|
||||||
|
VERSION="${{ steps.version.outputs.version }}"
|
||||||
|
if [[ ! $VERSION =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?$ ]]; then
|
||||||
|
echo "❌ Invalid version format: $VERSION"
|
||||||
|
echo "Expected format: v1.0.0 or v1.0.0-beta.1"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✅ Version format is valid: $VERSION"
|
||||||
|
|
||||||
|
# Tests and linting are handled by the CI workflow that runs on push
|
||||||
|
# This workflow only needs to run once CI passes on the tag
|
||||||
|
check-ci:
|
||||||
|
name: ✅ Verify CI Status
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 5
|
||||||
|
needs: validate
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: 📋 Check CI Workflow Status
|
||||||
|
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const wfList = await github.rest.actions.listRepoWorkflows({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
});
|
||||||
|
const wf =
|
||||||
|
wfList.data.workflows.find(w => w.path.endsWith('/test.yml')) ||
|
||||||
|
wfList.data.workflows.find(w => (w.name || '').toLowerCase() === 'ci');
|
||||||
|
if (!wf) core.setFailed('CI workflow not found (test.yml or CI).');
|
||||||
|
const { data } = await github.rest.actions.listWorkflowRuns({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
workflow_id: wf.id,
|
||||||
|
head_sha: context.sha,
|
||||||
|
status: 'completed',
|
||||||
|
per_page: 1
|
||||||
|
});
|
||||||
|
const latestRun = data.workflow_runs?.[0];
|
||||||
|
if (!latestRun) core.setFailed('No completed CI runs found for this commit.');
|
||||||
|
if (latestRun.conclusion !== 'success') {
|
||||||
|
core.setFailed(`CI workflow conclusion: ${latestRun.conclusion}`);
|
||||||
|
}
|
||||||
|
console.log(`CI status: ${latestRun.conclusion}`)
|
||||||
|
|
||||||
|
security:
|
||||||
|
name: 🔒 Security Scan
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
needs: validate
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout Repository
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
|
- name: Setup Node.js 24
|
||||||
|
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
|
with:
|
||||||
|
node-version: 24
|
||||||
|
|
||||||
|
- name: Cache Node.js dependencies
|
||||||
|
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.npm
|
||||||
|
~/.yarn
|
||||||
|
~/.cache/yarn
|
||||||
|
~/.pnpm-store
|
||||||
|
~/.cache/pnpm
|
||||||
|
node_modules/.cache
|
||||||
|
key: ${{ runner.os }}-node-24-${{ hashFiles('**/package-lock.json', '**/yarn.lock', '**/pnpm-lock.yaml') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-node-24-
|
||||||
|
${{ runner.os }}-node-
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: npm ci || { echo "❌ npm install failed"; npm install; }
|
||||||
|
|
||||||
|
- name: 🔍 Run Security Audit
|
||||||
|
run: npm audit --audit-level=high
|
||||||
|
|
||||||
|
release:
|
||||||
|
name: 🚀 Release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
needs: [validate, check-ci, security]
|
||||||
|
if: always() && needs.validate.result == 'success' && needs.check-ci.result == 'success' && needs.security.result == 'success'
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
packages: write
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: ⤵️ Checkout Repository
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup Node.js 24
|
||||||
|
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
|
with:
|
||||||
|
node-version: 24
|
||||||
|
registry-url: "https://registry.npmjs.org"
|
||||||
|
|
||||||
|
- name: Cache Node.js dependencies
|
||||||
|
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.npm
|
||||||
|
~/.yarn
|
||||||
|
~/.cache/yarn
|
||||||
|
~/.pnpm-store
|
||||||
|
~/.cache/pnpm
|
||||||
|
node_modules/.cache
|
||||||
|
key: ${{ runner.os }}-node-24-${{ hashFiles('**/package-lock.json', '**/yarn.lock', '**/pnpm-lock.yaml') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-node-24-
|
||||||
|
${{ runner.os }}-node-
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: npm ci || { echo "❌ npm install failed"; npm install; }
|
||||||
|
|
||||||
|
- name: Cache Generated Grammar
|
||||||
|
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
|
id: cache-grammar
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
src/parser.c
|
||||||
|
src/tree_sitter/
|
||||||
|
binding.gyp
|
||||||
|
key: ${{ runner.os }}-grammar-${{ hashFiles('grammar.js', 'package.json', 'src/scanner.c') }}
|
||||||
|
|
||||||
|
- name: Generate Grammar
|
||||||
|
if: steps.cache-grammar.outputs.cache-hit != 'true'
|
||||||
|
run: npm run generate
|
||||||
|
|
||||||
|
- name: 🏗️ Build Parser
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: 🔎 Verify package.json version matches input
|
||||||
|
if: github.event_name == 'workflow_dispatch'
|
||||||
|
run: |
|
||||||
|
INPUT="${{ github.event.inputs.version }}"
|
||||||
|
EXPECTED="v${INPUT#v}"
|
||||||
|
PKG="v$(node -p "require('./package.json').version")"
|
||||||
|
if [ "$PKG" != "$EXPECTED" ]; then
|
||||||
|
echo "package.json version ($PKG) does not match requested release ($EXPECTED)."
|
||||||
|
echo "Bump package.json in a PR before running workflow_dispatch."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: 🏷️ Create Tag
|
||||||
|
if: github.event_name == 'workflow_dispatch'
|
||||||
|
run: |
|
||||||
|
VERSION="v${{ github.event.inputs.version }}"
|
||||||
|
git tag "${VERSION#v}"
|
||||||
|
git push origin "${VERSION#v}"
|
||||||
|
|
||||||
|
- name: 📝 Generate Release Notes
|
||||||
|
id: release_notes
|
||||||
|
run: |
|
||||||
|
VERSION="${{ needs.validate.outputs.version }}"
|
||||||
|
PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "## Release ${VERSION}"
|
||||||
|
echo ""
|
||||||
|
} > release_notes.md
|
||||||
|
|
||||||
|
if [ -n "$PREV_TAG" ]; then
|
||||||
|
{
|
||||||
|
echo "### Changes since ${PREV_TAG}"
|
||||||
|
echo ""
|
||||||
|
} >> release_notes.md
|
||||||
|
git log --oneline --pretty=format:"- %s" "${PREV_TAG}..HEAD" >> release_notes.md
|
||||||
|
else
|
||||||
|
{
|
||||||
|
echo "### Initial Release"
|
||||||
|
echo ""
|
||||||
|
echo "- Initial release of tree-sitter-shellspec"
|
||||||
|
echo "- Complete ShellSpec grammar support"
|
||||||
|
echo "- Comprehensive test suite with broad coverage"
|
||||||
|
echo "- Real-world compatibility with official ShellSpec examples"
|
||||||
|
} >> release_notes.md
|
||||||
|
fi
|
||||||
|
|
||||||
|
{
|
||||||
|
echo ""
|
||||||
|
echo "### Installation"
|
||||||
|
echo ""
|
||||||
|
echo "\`\`\`bash"
|
||||||
|
echo "npm install @ivuorinen/tree-sitter-shellspec"
|
||||||
|
echo "\`\`\`"
|
||||||
|
} >> release_notes.md
|
||||||
|
|
||||||
|
- name: 🚀 Create GitHub Release
|
||||||
|
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
|
||||||
|
with:
|
||||||
|
tag_name: ${{ needs.validate.outputs.version }}
|
||||||
|
name: Release ${{ needs.validate.outputs.version }}
|
||||||
|
body_path: release_notes.md
|
||||||
|
draft: false
|
||||||
|
prerelease: ${{ contains(needs.validate.outputs.version, '-') }}
|
||||||
|
generate_release_notes: false
|
||||||
|
|
||||||
|
- name: 📦 Publish to npm
|
||||||
|
run: |
|
||||||
|
if [[ "$VERSION" == *"-"* ]]; then
|
||||||
|
PUBLISH_TAG="next"
|
||||||
|
else
|
||||||
|
PUBLISH_TAG="latest"
|
||||||
|
fi
|
||||||
|
echo "Publishing with tag: $PUBLISH_TAG"
|
||||||
|
npm publish --access public --tag "$PUBLISH_TAG"
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
VERSION: ${{ needs.validate.outputs.version }}
|
||||||
|
|
||||||
|
- name: 📊 Release Summary
|
||||||
|
run: |
|
||||||
|
echo "🎉 Successfully released ${{ needs.validate.outputs.version }}"
|
||||||
|
echo "📦 Published to npm: https://www.npmjs.com/package/@ivuorinen/tree-sitter-shellspec"
|
||||||
|
echo "🏷️ GitHub Release: ${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ needs.validate.outputs.version }}"
|
||||||
6
.github/workflows/stale.yml
vendored
6
.github/workflows/stale.yml
vendored
@@ -1,10 +1,10 @@
|
|||||||
---
|
---
|
||||||
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
|
# yaml-language-server: $schema=https://www.schemastore.org/github-workflow.json
|
||||||
name: Stale
|
name: Stale
|
||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 8 * * *' # Every day at 08:00
|
- cron: "0 8 * * *" # Every day at 08:00
|
||||||
workflow_call:
|
workflow_call:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
@@ -23,4 +23,4 @@ jobs:
|
|||||||
issues: write
|
issues: write
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- uses: ivuorinen/actions/stale@6e8f2aae9d0846d901d9eba15b8e94a2900573dc # v2026.03.02
|
- uses: ivuorinen/actions/stale@242ecca8f01357727f5b2b040154ccdece61720b # v2026.03.07
|
||||||
|
|||||||
11
.github/workflows/sync-labels.yml
vendored
11
.github/workflows/sync-labels.yml
vendored
@@ -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
|
name: Sync Labels
|
||||||
|
|
||||||
on:
|
on:
|
||||||
@@ -8,13 +8,12 @@ on:
|
|||||||
- main
|
- main
|
||||||
- master
|
- master
|
||||||
paths:
|
paths:
|
||||||
- '.github/labels.yml'
|
- ".github/labels.yml"
|
||||||
- '.github/workflows/sync-labels.yml'
|
- ".github/workflows/sync-labels.yml"
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '34 5 * * *' # Run every day at 05:34 AM UTC
|
- cron: "34 5 * * *" # Run every day at 05:34 AM UTC
|
||||||
workflow_call:
|
workflow_call:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
merge_group:
|
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
@@ -38,4 +37,4 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: ⤵️ Sync Latest Labels Definitions
|
- name: ⤵️ Sync Latest Labels Definitions
|
||||||
uses: ivuorinen/actions/sync-labels@6e8f2aae9d0846d901d9eba15b8e94a2900573dc # v2026.03.02
|
uses: ivuorinen/actions/sync-labels@242ecca8f01357727f5b2b040154ccdece61720b # v2026.03.07
|
||||||
|
|||||||
288
.github/workflows/test.yml
vendored
Normal file
288
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
---
|
||||||
|
# yaml-language-server: $schema=https://www.schemastore.org/github-workflow.json
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main, master]
|
||||||
|
pull_request:
|
||||||
|
branches: [main, master]
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
actions: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
name: 🧪 Test Suite
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version: [22, 24]
|
||||||
|
fail-fast: false
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout Repository
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
|
- name: Setup Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
|
- name: Cache Node.js dependencies
|
||||||
|
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
|
id: cache-npm
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.npm
|
||||||
|
~/.yarn
|
||||||
|
~/.cache/yarn
|
||||||
|
~/.pnpm-store
|
||||||
|
~/.cache/pnpm
|
||||||
|
node_modules/.cache
|
||||||
|
key: ${{ runner.os }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json', '**/yarn.lock', '**/pnpm-lock.yaml') }}
|
||||||
|
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 npx store
|
||||||
|
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
|
id: cache-npx
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.npm/_npx
|
||||||
|
~/.cache/yarn/global
|
||||||
|
~/.local/share/pnpm/global
|
||||||
|
key: ${{ runner.os }}-npx-${{ hashFiles('package.json') }}
|
||||||
|
|
||||||
|
- name: Cache Generated Grammar
|
||||||
|
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
|
id: cache-grammar
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
src/parser.c
|
||||||
|
src/tree_sitter/
|
||||||
|
binding.gyp
|
||||||
|
key: ${{ runner.os }}-grammar-${{ hashFiles('grammar.js', 'package.json', 'src/scanner.c') }}
|
||||||
|
|
||||||
|
- name: Generate Grammar
|
||||||
|
if: steps.cache-grammar.outputs.cache-hit != 'true'
|
||||||
|
run: npm run generate
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Cache Built Parser
|
||||||
|
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
|
id: cache-parser
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
build/
|
||||||
|
key: >-
|
||||||
|
${{ runner.os }}-parser-${{ matrix.node-version }}-${{
|
||||||
|
hashFiles('package-lock.json', 'src/parser.c', 'binding.gyp',
|
||||||
|
'src/**/*.cc', 'src/**/*.h') }}
|
||||||
|
|
||||||
|
- name: Build Parser
|
||||||
|
if: steps.cache-parser.outputs.cache-hit != 'true'
|
||||||
|
run: npm run build
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Test Parser with Sample Code
|
||||||
|
run: |
|
||||||
|
# Ensure parser is built
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
cat << 'EOF' > test_sample.shellspec
|
||||||
|
#!/usr/bin/env shellspec
|
||||||
|
|
||||||
|
Describe 'Calculator'
|
||||||
|
Include ./lib/calculator.sh
|
||||||
|
|
||||||
|
Before 'setup_calculator'
|
||||||
|
After 'cleanup_calculator'
|
||||||
|
|
||||||
|
Context 'when adding numbers'
|
||||||
|
It 'adds two positive numbers'
|
||||||
|
When call add 2 3
|
||||||
|
The output should eq 5
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'handles zero'
|
||||||
|
When call add 0 5
|
||||||
|
The output should eq 5
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Context 'when input is invalid'
|
||||||
|
Skip if "validation not implemented" ! command -v validate
|
||||||
|
|
||||||
|
It 'handles empty input'
|
||||||
|
When call add "" ""
|
||||||
|
The status should be failure
|
||||||
|
End
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'works without describe block'
|
||||||
|
When call echo "test"
|
||||||
|
The output should eq "test"
|
||||||
|
End
|
||||||
|
EOF
|
||||||
|
|
||||||
|
npx tree-sitter parse test_sample.shellspec --quiet || {
|
||||||
|
echo "❌ Parser failed on sample ShellSpec code"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
echo "✅ Parser successfully handled sample code"
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
lint:
|
||||||
|
name: 🧹 Code Quality
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout Repository
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
|
with:
|
||||||
|
node-version: 24
|
||||||
|
|
||||||
|
- name: Cache Node.js dependencies
|
||||||
|
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.npm
|
||||||
|
~/.yarn
|
||||||
|
~/.cache/yarn
|
||||||
|
~/.pnpm-store
|
||||||
|
~/.cache/pnpm
|
||||||
|
node_modules/.cache
|
||||||
|
key: ${{ runner.os }}-node-24-${{ hashFiles('**/package-lock.json', '**/yarn.lock', '**/pnpm-lock.yaml') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-node-24-
|
||||||
|
${{ runner.os }}-node-
|
||||||
|
|
||||||
|
- name: Install Dependencies
|
||||||
|
run: npm ci || { echo "❌ npm install failed"; npm install; }
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: 🧹 Run Linter
|
||||||
|
uses: ivuorinen/actions/pr-lint@242ecca8f01357727f5b2b040154ccdece61720b # v2026.03.07
|
||||||
|
|
||||||
|
coverage:
|
||||||
|
name: 📊 Test Coverage
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
needs: test
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout Repository
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
|
with:
|
||||||
|
node-version: 24
|
||||||
|
|
||||||
|
- name: Cache Node.js dependencies
|
||||||
|
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.npm
|
||||||
|
~/.yarn
|
||||||
|
~/.cache/yarn
|
||||||
|
~/.pnpm-store
|
||||||
|
~/.cache/pnpm
|
||||||
|
node_modules/.cache
|
||||||
|
key: ${{ runner.os }}-node-24-${{ hashFiles('**/package-lock.json', '**/yarn.lock', '**/pnpm-lock.yaml') }}
|
||||||
|
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
|
||||||
|
run: |
|
||||||
|
echo "## Test Coverage Report" > coverage_report.md
|
||||||
|
echo "" >> coverage_report.md
|
||||||
|
|
||||||
|
# Run tests and capture output with exit code
|
||||||
|
set +e # Don't exit on test failure
|
||||||
|
TEST_OUTPUT=$(npm test 2>&1)
|
||||||
|
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"
|
||||||
|
echo "- **Passing:** $PASSING_TESTS ✅"
|
||||||
|
echo "- **Failing:** $FAILING_TESTS ❌"
|
||||||
|
} >> coverage_report.md
|
||||||
|
|
||||||
|
if [ "$TOTAL_TESTS" -gt 0 ]; then
|
||||||
|
COVERAGE_PERCENT=$(( (PASSING_TESTS * 100) / TOTAL_TESTS ))
|
||||||
|
echo "- **Coverage:** $COVERAGE_PERCENT%" >> coverage_report.md
|
||||||
|
else
|
||||||
|
COVERAGE_PERCENT=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
{
|
||||||
|
echo ""
|
||||||
|
echo "### Test Files"
|
||||||
|
} >> coverage_report.md
|
||||||
|
echo "$TEST_OUTPUT" | grep -E "^[[:space:]]+[A-Za-z0-9._/\\-]+:" | sed 's/^/- /' >> coverage_report.md
|
||||||
|
|
||||||
|
cat coverage_report.md
|
||||||
|
|
||||||
|
# Set outputs
|
||||||
|
{
|
||||||
|
echo "total-tests=$TOTAL_TESTS"
|
||||||
|
echo "passing-tests=$PASSING_TESTS"
|
||||||
|
echo "coverage-percent=$COVERAGE_PERCENT"
|
||||||
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
# Validate test coverage requirements
|
||||||
|
if [ "${TOTAL_TESTS:-0}" -lt 115 ]; then
|
||||||
|
echo "❌ Expected at least 115 tests, found ${TOTAL_TESTS:-0}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
echo "✅ Test coverage is acceptable: $PASSING_TESTS/$TOTAL_TESTS tests passing ($COVERAGE_PERCENT%)"
|
||||||
|
shell: bash
|
||||||
160
.gitignore
vendored
160
.gitignore
vendored
@@ -1,134 +1,48 @@
|
|||||||
.php-cs-fixer.cache
|
*.cache
|
||||||
.php-cs-fixer.php
|
*.iws
|
||||||
composer.phar
|
|
||||||
/vendor/
|
|
||||||
.phpunit.result.cache
|
|
||||||
.phpunit.cache
|
|
||||||
/app/phpunit.xml
|
|
||||||
/phpunit.xml
|
|
||||||
/build/
|
|
||||||
logs
|
|
||||||
*.log
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
lerna-debug.log*
|
|
||||||
.pnpm-debug.log*
|
|
||||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
|
||||||
pids
|
|
||||||
*.pid
|
|
||||||
*.seed
|
|
||||||
*.pid.lock
|
|
||||||
lib-cov
|
|
||||||
coverage
|
|
||||||
*.lcov
|
*.lcov
|
||||||
.nyc_output
|
*.log
|
||||||
.grunt
|
*.pem
|
||||||
bower_components
|
*.pid
|
||||||
.lock-wscript
|
*.pid.lock
|
||||||
build/Release
|
*.seed
|
||||||
node_modules/
|
|
||||||
jspm_packages/
|
|
||||||
web_modules/
|
|
||||||
*.tsbuildinfo
|
|
||||||
.npm
|
|
||||||
.eslintcache
|
|
||||||
.stylelintcache
|
|
||||||
.rpt2_cache/
|
|
||||||
.rts2_cache_cjs/
|
|
||||||
.rts2_cache_es/
|
|
||||||
.rts2_cache_umd/
|
|
||||||
.node_repl_history
|
|
||||||
*.tgz
|
*.tgz
|
||||||
.yarn-integrity
|
*.tsbuildinfo
|
||||||
.env
|
*~
|
||||||
.env.development.local
|
.DS_Store
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
.env.local
|
|
||||||
.cache
|
.cache
|
||||||
.parcel-cache
|
|
||||||
.next
|
|
||||||
out
|
|
||||||
.nuxt
|
|
||||||
dist
|
|
||||||
.cache/
|
|
||||||
.vuepress/dist
|
|
||||||
.temp
|
|
||||||
.docusaurus
|
.docusaurus
|
||||||
.serverless/
|
|
||||||
.fusebox/
|
|
||||||
.dynamodb/
|
.dynamodb/
|
||||||
.tern-port
|
.eslintcache
|
||||||
.vscode-test
|
.node_repl_history
|
||||||
.yarn/cache
|
.nyc_output
|
||||||
.yarn/unplugged
|
|
||||||
.yarn/build-state.yml
|
|
||||||
.yarn/install-state.gz
|
|
||||||
.pnp.*
|
.pnp.*
|
||||||
|
.pnp.js
|
||||||
|
.pnpm-debug.log*
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_*
|
||||||
|
.temp
|
||||||
|
.tern-port
|
||||||
|
.yarn-integrity
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.yarn/unplugged
|
||||||
[._]*.s[a-v][a-z]
|
[._]*.s[a-v][a-z]
|
||||||
!*.svg # comment out if you don't need vector files
|
|
||||||
[._]*.sw[a-p]
|
[._]*.sw[a-p]
|
||||||
|
[._]*.un~
|
||||||
[._]s[a-rt-v][a-z]
|
[._]s[a-rt-v][a-z]
|
||||||
[._]ss[a-gi-z]
|
[._]ss[a-gi-z]
|
||||||
[._]sw[a-p]
|
[._]sw[a-p]
|
||||||
Session.vim
|
coverage/
|
||||||
Sessionx.vim
|
megalinter-reports/
|
||||||
.netrwhist
|
node_modules/
|
||||||
*~
|
npm-debug.log*
|
||||||
tags
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
[._]*.un~
|
yarn-debug.log*
|
||||||
.idea/**/workspace.xml
|
yarn-error.log*
|
||||||
.idea/**/tasks.xml
|
.idea/
|
||||||
.idea/**/usage.statistics.xml
|
.vscode/
|
||||||
.idea/**/dictionaries
|
.fleet/
|
||||||
.idea/**/shelf
|
shellspec.so
|
||||||
.idea/**/aws.xml
|
.claude/settings.local.json
|
||||||
.idea/**/contentModel.xml
|
|
||||||
.idea/**/dataSources/
|
|
||||||
.idea/**/dataSources.ids
|
|
||||||
.idea/**/dataSources.local.xml
|
|
||||||
.idea/**/sqlDataSources.xml
|
|
||||||
.idea/**/dynamic.xml
|
|
||||||
.idea/**/uiDesigner.xml
|
|
||||||
.idea/**/dbnavigator.xml
|
|
||||||
.idea/**/gradle.xml
|
|
||||||
.idea/**/libraries
|
|
||||||
cmake-build-*/
|
|
||||||
.idea/**/mongoSettings.xml
|
|
||||||
*.iws
|
|
||||||
out/
|
|
||||||
.idea_modules/
|
|
||||||
atlassian-ide-plugin.xml
|
|
||||||
.idea/replstate.xml
|
|
||||||
.idea/sonarlint/
|
|
||||||
com_crashlytics_export_strings.xml
|
|
||||||
crashlytics.properties
|
|
||||||
crashlytics-build.properties
|
|
||||||
fabric.properties
|
|
||||||
.idea/httpRequests
|
|
||||||
.idea/caches/build_file_checksums.ser
|
|
||||||
npm-debug.log
|
|
||||||
yarn-error.log
|
|
||||||
bootstrap/compiled.php
|
|
||||||
app/storage/
|
|
||||||
public/storage
|
|
||||||
public/hot
|
|
||||||
public_html/storage
|
|
||||||
public_html/hot
|
|
||||||
storage/*.key
|
|
||||||
Homestead.yaml
|
|
||||||
Homestead.json
|
|
||||||
/.vagrant
|
|
||||||
/node_modules
|
|
||||||
/.pnp
|
|
||||||
.pnp.js
|
|
||||||
/coverage
|
|
||||||
/.next/
|
|
||||||
/out/
|
|
||||||
/build
|
|
||||||
.DS_Store
|
|
||||||
*.pem
|
|
||||||
.env*.local
|
|
||||||
.vercel
|
|
||||||
next-env.d.ts
|
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
---
|
---
|
||||||
|
# yaml-language-server:
|
||||||
|
# $schema=https://raw.githubusercontent.com/megalinter/megalinter/main/megalinter/descriptors/schemas/megalinter-configuration.jsonschema.json
|
||||||
# Configuration file for MegaLinter
|
# Configuration file for MegaLinter
|
||||||
# See all available variables at
|
# See all available variables at
|
||||||
# https://megalinter.io/configuration/ and in linters documentation
|
# https://megalinter.io/configuration/ and in linters documentation
|
||||||
|
|
||||||
APPLY_FIXES: all
|
APPLY_FIXES: none
|
||||||
|
FLAVOR_SUGGESTIONS: true
|
||||||
SHOW_ELAPSED_TIME: false # Show elapsed time at the end of MegaLinter run
|
SHOW_ELAPSED_TIME: false # Show elapsed time at the end of MegaLinter run
|
||||||
PARALLEL: true
|
PARALLEL: true
|
||||||
VALIDATE_ALL_CODEBASE: true
|
VALIDATE_ALL_CODEBASE: true
|
||||||
@@ -17,19 +20,28 @@ SHOW_SKIPPED_LINTERS: false # Show skipped linters in MegaLinter log
|
|||||||
|
|
||||||
DISABLE_LINTERS:
|
DISABLE_LINTERS:
|
||||||
- REPOSITORY_DEVSKIM
|
- REPOSITORY_DEVSKIM
|
||||||
|
- C_CLANG_FORMAT # Generated code may not follow all style rules
|
||||||
ENABLE_LINTERS:
|
- C_CPPCHECK # Generated src/ files (false positives)
|
||||||
- YAML_YAMLLINT
|
- C_CPPLINT # Generated src/ files (false positives)
|
||||||
- MARKDOWN_MARKDOWNLINT
|
- CPP_CPPCHECK # Generated src/ files (false positives)
|
||||||
- YAML_PRETTIER
|
- CPP_CPPLINT # Generated src/ files (false positives)
|
||||||
- JSON_PRETTIER
|
- BASH_SHFMT # Doesn't understand ShellSpec DSL syntax
|
||||||
- JAVASCRIPT_ES
|
- BASH_BASH_EXEC # Test spec files are sourced by ShellSpec, not executed directly
|
||||||
- TYPESCRIPT_ES
|
- JSON_PRETTIER # Disabled for causing problems
|
||||||
|
- SPELL_LYCHEE # Disabled due to too many false positives
|
||||||
|
- SPELL_CSPELL # Disabled due to too many false positives
|
||||||
|
- REPOSITORY_TRUFFLEHOG # Disabled due to being far too slow
|
||||||
|
- JAVASCRIPT_PRETTIER # We are not using Prettier for JS
|
||||||
|
|
||||||
YAML_YAMLLINT_CONFIG_FILE: .yamllint.yml
|
YAML_YAMLLINT_CONFIG_FILE: .yamllint.yml
|
||||||
MARKDOWN_MARKDOWNLINT_CONFIG_FILE: .markdownlint.json
|
MARKDOWN_MARKDOWNLINT_CONFIG_FILE: .markdownlint.json
|
||||||
JAVASCRIPT_ES_CONFIG_FILE: .eslintrc.json
|
|
||||||
TYPESCRIPT_ES_CONFIG_FILE: .eslintrc.json
|
|
||||||
|
|
||||||
FILTER_REGEX_EXCLUDE: >
|
# v8r workaround: tree-sitter.json schema returns 404
|
||||||
(node_modules|\.automation/test|docs/json-schemas|\.github/workflows)
|
JSON_V8R_FILTER_REGEX_EXCLUDE: "tree-sitter\\.json"
|
||||||
|
|
||||||
|
# Exclude some paths from all linters as they are generated or LLM-managed
|
||||||
|
YAML_V8R_FILTER_REGEX_EXCLUDE: "(mega-linter\\.yml|\\.serena/)"
|
||||||
|
|
||||||
|
# Exclude some paths from all linters as they are generated or LLM-managed
|
||||||
|
FILTER_REGEX_EXCLUDE: >-
|
||||||
|
(node_modules|test/spec|src/|megalinter-reports|\.serena|\.claude)
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ repos:
|
|||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v6.0.0
|
rev: v6.0.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: requirements-txt-fixer
|
|
||||||
- id: detect-private-key
|
- id: detect-private-key
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
args: [--markdown-linebreak-ext=md]
|
args: [--markdown-linebreak-ext=md]
|
||||||
@@ -33,31 +32,26 @@ repos:
|
|||||||
hooks:
|
hooks:
|
||||||
- id: yamllint
|
- id: yamllint
|
||||||
|
|
||||||
- repo: https://github.com/scop/pre-commit-shfmt
|
|
||||||
rev: v3.11.0-1
|
|
||||||
hooks:
|
|
||||||
- id: shfmt
|
|
||||||
|
|
||||||
- repo: https://github.com/koalaman/shellcheck-precommit
|
- repo: https://github.com/koalaman/shellcheck-precommit
|
||||||
rev: v0.11.0
|
rev: v0.11.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: shellcheck
|
- id: shellcheck
|
||||||
args: ['--severity=warning']
|
args: ["--severity=warning"]
|
||||||
|
|
||||||
- repo: https://github.com/rhysd/actionlint
|
- repo: https://github.com/rhysd/actionlint
|
||||||
rev: v1.7.11
|
rev: v1.7.11
|
||||||
hooks:
|
hooks:
|
||||||
- id: actionlint
|
- id: actionlint
|
||||||
args: ['-shellcheck=']
|
args: ["-shellcheck="]
|
||||||
|
|
||||||
- repo: https://github.com/renovatebot/pre-commit-hooks
|
- repo: https://github.com/renovatebot/pre-commit-hooks
|
||||||
rev: 43.31.6
|
rev: 43.59.3
|
||||||
hooks:
|
hooks:
|
||||||
- id: renovate-config-validator
|
- id: renovate-config-validator
|
||||||
|
|
||||||
- repo: https://github.com/bridgecrewio/checkov.git
|
- repo: https://github.com/bridgecrewio/checkov.git
|
||||||
rev: '3.2.507'
|
rev: "3.2.508"
|
||||||
hooks:
|
hooks:
|
||||||
- id: checkov
|
- id: checkov
|
||||||
args:
|
args:
|
||||||
- '--quiet'
|
- "--quiet"
|
||||||
|
|||||||
24
.prettierignore
Normal file
24
.prettierignore
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
src/parser.c
|
||||||
|
src/grammar.json
|
||||||
|
src/node-types.json
|
||||||
|
src/tree_sitter/
|
||||||
|
|
||||||
|
# Build artifacts
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
megalinter-reports/
|
||||||
|
|
||||||
|
# Test specs (shell scripts have specific formatting)
|
||||||
|
test/spec/
|
||||||
|
|
||||||
|
# Lock files
|
||||||
|
package-lock.json
|
||||||
|
pnpm-lock.yaml
|
||||||
|
yarn.lock
|
||||||
11
.prettierrc
Normal file
11
.prettierrc
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"semi": true,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"singleQuote": false,
|
||||||
|
"printWidth": 100,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"useTabs": false,
|
||||||
|
"arrowParens": "always",
|
||||||
|
"endOfLine": "lf",
|
||||||
|
"proseWrap": "preserve"
|
||||||
|
}
|
||||||
1
.serena/.gitignore
vendored
Normal file
1
.serena/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/cache
|
||||||
41
.serena/memories/code_style_conventions.md
Normal file
41
.serena/memories/code_style_conventions.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# Code Style and Conventions
|
||||||
|
|
||||||
|
## EditorConfig Rules
|
||||||
|
|
||||||
|
All files follow `.editorconfig` specifications:
|
||||||
|
|
||||||
|
- **Charset**: UTF-8
|
||||||
|
- **Line endings**: LF (Unix style)
|
||||||
|
- **Indentation**: 2 spaces (no tabs except Makefiles)
|
||||||
|
- **Max line length**: 160 characters
|
||||||
|
- **Final newline**: Required
|
||||||
|
- **Trim trailing whitespace**: Yes (except .md files)
|
||||||
|
|
||||||
|
## JavaScript/Grammar Conventions
|
||||||
|
|
||||||
|
- Use 2-space indentation
|
||||||
|
- JSDoc comments for file headers
|
||||||
|
- TypeScript reference comments for tree-sitter DSL
|
||||||
|
- Semicolons and consistent formatting
|
||||||
|
- Descriptive field names in grammar rules
|
||||||
|
|
||||||
|
## Grammar Design Patterns
|
||||||
|
|
||||||
|
- Use `prec.right(1, seq(...))` for block structures
|
||||||
|
- Handle conflicts explicitly in the `conflicts` array
|
||||||
|
- Extend original bash rules with `choice(original, new_rules)`
|
||||||
|
- Use `field()` for semantic labeling of important parts
|
||||||
|
- Block patterns: `BlockType description statements End`
|
||||||
|
|
||||||
|
## File Organization
|
||||||
|
|
||||||
|
- `grammar.js`: Main grammar definition
|
||||||
|
- `src/`: Generated parser files (don't edit manually)
|
||||||
|
- Configuration files in root directory
|
||||||
|
- GitHub workflows in `.github/workflows/`
|
||||||
|
|
||||||
|
## Naming Conventions
|
||||||
|
|
||||||
|
- Snake_case for grammar rule names
|
||||||
|
- Descriptive names for block types and fields
|
||||||
|
- Prefix ShellSpec-specific rules with `shellspec_`
|
||||||
272
.serena/memories/complete_project_overview_2025.md
Normal file
272
.serena/memories/complete_project_overview_2025.md
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
# Tree-sitter-shellspec Project Complete Overview (2025)
|
||||||
|
|
||||||
|
## Project Status Summary
|
||||||
|
|
||||||
|
**Production-Ready** tree-sitter grammar for ShellSpec BDD testing framework with comprehensive tooling and CI/CD pipeline.
|
||||||
|
|
||||||
|
## Core Statistics
|
||||||
|
|
||||||
|
- **Tests**: 61/61 passing (100% success rate)
|
||||||
|
- **Grammar Rules**: 8 main ShellSpec constructs + extended bash grammar
|
||||||
|
- **Test Coverage**: 1,302 lines across 7 corpus files
|
||||||
|
- **Conflicts**: 8 essential conflicts (optimally minimized)
|
||||||
|
- **Package Version**: 0.1.0
|
||||||
|
- **License**: MIT
|
||||||
|
|
||||||
|
## Project Architecture
|
||||||
|
|
||||||
|
### Grammar Implementation (`grammar.js`)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
module.exports = grammar(bashGrammar, {
|
||||||
|
name: "shellspec",
|
||||||
|
conflicts: [
|
||||||
|
// 6 inherited bash conflicts
|
||||||
|
[$._expression, $.command_name],
|
||||||
|
[$.command, $.variable_assignments],
|
||||||
|
[$.redirected_statement, $.command],
|
||||||
|
[$.redirected_statement, $.command_substitution],
|
||||||
|
[$.function_definition, $.command_name],
|
||||||
|
[$.pipeline],
|
||||||
|
// 2 essential ShellSpec conflicts
|
||||||
|
[$.command_name, $.shellspec_data_block],
|
||||||
|
[$.shellspec_hook_block],
|
||||||
|
],
|
||||||
|
rules: {
|
||||||
|
// 8 ShellSpec rule extensions
|
||||||
|
shellspec_describe_block, // Describe/fDescribe/xDescribe
|
||||||
|
shellspec_context_block, // Context/ExampleGroup variants
|
||||||
|
shellspec_it_block, // It/Example/Specify variants
|
||||||
|
shellspec_hook_block, // BeforeEach/AfterEach/etc blocks
|
||||||
|
shellspec_utility_block, // Parameters/Skip/Pending/Todo
|
||||||
|
shellspec_data_block, // Data blocks with statements/arguments
|
||||||
|
shellspec_hook_statement, // Before/After statements
|
||||||
|
shellspec_directive_statement, // Include/Skip if
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Supported ShellSpec Constructs
|
||||||
|
|
||||||
|
#### Block Types (with variants)
|
||||||
|
|
||||||
|
- **Describe blocks**: `Describe`, `fDescribe`, `xDescribe`
|
||||||
|
- **Context blocks**: `Context`, `ExampleGroup`, `fContext`, `xContext`
|
||||||
|
- **It blocks**: `It`, `Example`, `Specify`, `fIt`, `fExample`, `fSpecify`, `xIt`, `xExample`, `xSpecify`
|
||||||
|
- **Hook blocks**: `BeforeEach`, `AfterEach`, `BeforeAll`, `AfterAll`, `BeforeCall`, `AfterCall`, `BeforeRun`, `AfterRun`
|
||||||
|
- **Utility blocks**: `Parameters`, `Skip`, `Pending`, `Todo`
|
||||||
|
- **Data blocks**: Block-style with statements, string arguments, function arguments
|
||||||
|
|
||||||
|
#### Statement Types
|
||||||
|
|
||||||
|
- **Hook statements**: `Before func1 func2`, `After cleanup`
|
||||||
|
- **Include directives**: `Include ./helper.sh`
|
||||||
|
- **Conditional Skip**: `Skip if "reason" condition`
|
||||||
|
|
||||||
|
#### Advanced Features Implemented
|
||||||
|
|
||||||
|
- ✅ Mixed ShellSpec/bash code parsing
|
||||||
|
- ✅ Complex nested structures
|
||||||
|
- ✅ Real-world pattern support
|
||||||
|
- ✅ Top-level It blocks (no Describe required)
|
||||||
|
- ✅ Multiple argument handling
|
||||||
|
- ✅ String/raw string/word variants
|
||||||
|
- ✅ Proper precedence and conflict resolution
|
||||||
|
|
||||||
|
## Test Suite Structure
|
||||||
|
|
||||||
|
### Test Coverage Distribution
|
||||||
|
|
||||||
|
```text
|
||||||
|
context_blocks.txt (131 lines) - 7 tests
|
||||||
|
describe_blocks.txt (143 lines) - 7 tests
|
||||||
|
hook_blocks.txt (219 lines) - 12 tests
|
||||||
|
it_blocks.txt (213 lines) - 10 tests
|
||||||
|
nested_structures.txt (236 lines) - 6 tests
|
||||||
|
real_world_patterns.txt (102 lines) - 6 tests
|
||||||
|
utility_blocks.txt (258 lines) - 13 tests
|
||||||
|
Total: 1,302 lines, 61 tests
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Categories
|
||||||
|
|
||||||
|
1. **Basic constructs** (40 tests) - Core block types and variants
|
||||||
|
2. **Real-world patterns** (6 tests) - Official ShellSpec examples
|
||||||
|
3. **Complex scenarios** (6 tests) - Nested structures, mixed content
|
||||||
|
4. **Utility features** (13 tests) - Data blocks, directives, parameters
|
||||||
|
5. **Edge cases** - Empty blocks, multiple arguments, conditional logic
|
||||||
|
|
||||||
|
## Development Environment
|
||||||
|
|
||||||
|
### Package Configuration
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "@ivuorinen/tree-sitter-shellspec",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"dependencies": {
|
||||||
|
"tree-sitter-bash": "^0.25.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"markdownlint-cli": "^0.42.0",
|
||||||
|
"nodemon": "^3.0.1",
|
||||||
|
"tree-sitter-cli": "^0.24.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Development Scripts
|
||||||
|
|
||||||
|
- `npm run generate` - Generate parser from grammar
|
||||||
|
- `npm test` - Run full test suite (61 tests)
|
||||||
|
- `npm run dev` - Generate + test workflow
|
||||||
|
- `npm run dev:watch` - Watch mode for development
|
||||||
|
- `npm run lint` - MegaLinter code quality check
|
||||||
|
- `npm run lint:markdown` - Markdown formatting
|
||||||
|
- `npm run clean` - Remove generated files
|
||||||
|
- `npm run rebuild` - Clean + generate + build
|
||||||
|
|
||||||
|
### Quality Assurance Tools
|
||||||
|
|
||||||
|
#### MegaLinter Configuration (`.mega-linter.yml`)
|
||||||
|
|
||||||
|
- **Enabled**: YAML, Markdown, Grammar validation
|
||||||
|
- **Disabled**: DevSkim, JSON Prettier, Bash exec/shellcheck, Lychee
|
||||||
|
- **Features**: Auto-fix, parallel execution, SARIF reports
|
||||||
|
- **Exclusions**: node_modules, test/spec, generated files
|
||||||
|
|
||||||
|
#### Code Style
|
||||||
|
|
||||||
|
- **EditorConfig**: `.editorconfig` with consistent formatting rules
|
||||||
|
- **YAML**: `.yamllint.yml` for YAML file validation
|
||||||
|
- **Markdown**: `.markdownlint.json` with 200 char line limit
|
||||||
|
- **Pre-commit**: `.pre-commit-config.yaml` for git hooks
|
||||||
|
|
||||||
|
## CI/CD Pipeline
|
||||||
|
|
||||||
|
### GitHub Actions Workflows
|
||||||
|
|
||||||
|
1. **test.yml** - Multi-node testing (Node 22, 24)
|
||||||
|
2. **release.yml** - Automated releases
|
||||||
|
3. **codeql.yml** - Security code scanning
|
||||||
|
4. **stale.yml** - Issue/PR management
|
||||||
|
5. **sync-labels.yml** - Label synchronization
|
||||||
|
|
||||||
|
### Custom GitHub Actions
|
||||||
|
|
||||||
|
```text
|
||||||
|
.github/actions/
|
||||||
|
├── setup-dev/ # Development environment setup
|
||||||
|
├── setup-node/ # Node.js environment
|
||||||
|
├── setup-treesitter/ # Tree-sitter CLI
|
||||||
|
├── test-grammar/ # Grammar testing
|
||||||
|
└── test-coverage/ # Coverage analysis
|
||||||
|
```
|
||||||
|
|
||||||
|
### Quality Gates
|
||||||
|
|
||||||
|
- **Minimum tests**: 55 (currently 61)
|
||||||
|
- **Test success rate**: 100%
|
||||||
|
- **Code coverage**: Tracked and reported
|
||||||
|
- **Lint compliance**: Required for PRs
|
||||||
|
- **Security scanning**: CodeQL integration
|
||||||
|
|
||||||
|
## File Structure Analysis
|
||||||
|
|
||||||
|
### Core Files
|
||||||
|
|
||||||
|
- `grammar.js` - Main grammar definition
|
||||||
|
- `package.json` - Project configuration
|
||||||
|
- `README.md` - Comprehensive documentation
|
||||||
|
- `LICENSE` - MIT license
|
||||||
|
- `CONTRIBUTING.md` - Contribution guidelines
|
||||||
|
|
||||||
|
### Configuration Files
|
||||||
|
|
||||||
|
- `.editorconfig` - Editor formatting rules
|
||||||
|
- `.gitignore` - Git exclusions
|
||||||
|
- `.markdownlint.json` - Markdown linting rules
|
||||||
|
- `.mega-linter.yml` - Code quality configuration
|
||||||
|
- `.pre-commit-config.yaml` - Git hooks
|
||||||
|
- `.shellcheckrc` - Shell script linting
|
||||||
|
- `.yamllint.yml` - YAML validation
|
||||||
|
- `tree-sitter.json` - Tree-sitter configuration
|
||||||
|
|
||||||
|
### Generated Files
|
||||||
|
|
||||||
|
- `src/parser.c` - Generated C parser (40K+ lines)
|
||||||
|
- `src/grammar.json` - Grammar JSON representation
|
||||||
|
- `src/node-types.json` - AST node type definitions
|
||||||
|
- `src/scanner.c` - External scanner
|
||||||
|
- `src/tree_sitter/` - Tree-sitter headers
|
||||||
|
|
||||||
|
### Documentation & Examples
|
||||||
|
|
||||||
|
- Comprehensive README with usage examples
|
||||||
|
- Multiple ShellSpec pattern demonstrations
|
||||||
|
- Editor integration guides (Neovim, VS Code, Emacs)
|
||||||
|
- Contributing guidelines with development setup
|
||||||
|
|
||||||
|
## Production Readiness Assessment
|
||||||
|
|
||||||
|
### ✅ Strengths
|
||||||
|
|
||||||
|
- **Complete ShellSpec support** - All documented constructs implemented
|
||||||
|
- **Excellent test coverage** - 61 comprehensive tests, 100% pass rate
|
||||||
|
- **Real-world validation** - Tested against official ShellSpec examples
|
||||||
|
- **Professional tooling** - Full CI/CD, code quality, security scanning
|
||||||
|
- **Optimized performance** - Minimal conflicts, efficient parsing
|
||||||
|
- **Developer experience** - Watch mode, clear documentation, easy setup
|
||||||
|
- **Standards compliance** - MIT license, semantic versioning, conventional commits
|
||||||
|
|
||||||
|
### 🔄 Enhancement Opportunities
|
||||||
|
|
||||||
|
- **Advanced Data syntax** - `:raw`, `:expand` modifiers (grammar foundation exists)
|
||||||
|
- **Assertion parsing** - When/The statement structures
|
||||||
|
- **Performance tuning** - Further conflict reduction if possible
|
||||||
|
- **Editor plugins** - Dedicated syntax highlighting themes
|
||||||
|
- **Documentation expansion** - More usage examples and tutorials
|
||||||
|
|
||||||
|
### 📊 Key Metrics
|
||||||
|
|
||||||
|
- **Grammar generation**: Clean (no errors/warnings)
|
||||||
|
- **Parse performance**: Efficient (proper precedence prevents backtracking)
|
||||||
|
- **Memory usage**: Minimal overhead over base bash grammar
|
||||||
|
- **Compatibility**: Full backward compatibility with bash
|
||||||
|
- **Maintainability**: Well-structured, documented, tested
|
||||||
|
|
||||||
|
## Deployment & Distribution
|
||||||
|
|
||||||
|
### NPM Package
|
||||||
|
|
||||||
|
- Scoped package: `@ivuorinen/tree-sitter-shellspec`
|
||||||
|
- Ready for npm publish
|
||||||
|
- Proper semantic versioning
|
||||||
|
- Complete package.json metadata
|
||||||
|
|
||||||
|
### Installation Methods
|
||||||
|
|
||||||
|
1. **NPM**: `npm install @ivuorinen/tree-sitter-shellspec`
|
||||||
|
2. **Git**: Clone and build from source
|
||||||
|
3. **Manual**: Download release artifacts
|
||||||
|
|
||||||
|
### Editor Support Ready
|
||||||
|
|
||||||
|
- **Neovim**: nvim-treesitter integration ready
|
||||||
|
- **VS Code**: Tree-sitter extension compatible
|
||||||
|
- **Emacs**: tree-sitter-mode integration ready
|
||||||
|
- **Other**: Any Tree-sitter compatible editor
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
The tree-sitter-shellspec project is a **production-ready, professionally developed** grammar implementation that provides comprehensive ShellSpec BDD syntax support.
|
||||||
|
It features excellent test coverage, robust CI/CD, quality tooling, and clear documentation, making it suitable for immediate use in development workflows and editor integrations.
|
||||||
|
|
||||||
|
The project demonstrates best practices in:
|
||||||
|
|
||||||
|
- Grammar development and testing
|
||||||
|
- Open source project structure
|
||||||
|
- CI/CD automation
|
||||||
|
- Code quality assurance
|
||||||
|
- Developer experience design
|
||||||
|
- Community contribution facilitation
|
||||||
209
.serena/memories/github_workflows_optimization_2025.md
Normal file
209
.serena/memories/github_workflows_optimization_2025.md
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
# GitHub Workflows Optimization (2025)
|
||||||
|
|
||||||
|
## Problem Analysis
|
||||||
|
|
||||||
|
The project had significant duplication in GitHub Actions workflows, causing unnecessary resource consumption and longer execution times.
|
||||||
|
|
||||||
|
### Original Issues Identified
|
||||||
|
|
||||||
|
#### 1. Critical Duplication - Linting (3x redundancy)
|
||||||
|
|
||||||
|
- **test.yml**: Ran linting in `lint` job
|
||||||
|
- **pr-lint.yml**: Ran identical linting with same action (`ivuorinen/actions/pr-lint`)
|
||||||
|
- **release.yml**: Ran identical linting again in `lint` job
|
||||||
|
- **Impact**: Same linting executed 3 times for every PR + push event
|
||||||
|
|
||||||
|
#### 2. High Duplication - Test Suite (2x redundancy)
|
||||||
|
|
||||||
|
- **test.yml**: Full test suite with matrix (Node 22, 24)
|
||||||
|
- **release.yml**: Identical test suite with same matrix
|
||||||
|
- **Impact**: 4 test jobs (2x2 matrix) running twice on every main branch push
|
||||||
|
|
||||||
|
#### 3. Medium Duplication - Environment Setup
|
||||||
|
|
||||||
|
- Multiple workflows using `./.github/actions/setup-dev` and `./.github/actions/setup-node`
|
||||||
|
- Same Node.js setup repeated across jobs
|
||||||
|
|
||||||
|
#### 4. Trigger Overlap
|
||||||
|
|
||||||
|
- Both `test.yml` and `pr-lint.yml` triggering on push/PR to main
|
||||||
|
- `merge_group` trigger in multiple workflows causing additional runs
|
||||||
|
|
||||||
|
## Optimization Implementation
|
||||||
|
|
||||||
|
### 1. Consolidated Main CI Workflow
|
||||||
|
|
||||||
|
**File**: `.github/workflows/test.yml` → Renamed to "CI"
|
||||||
|
|
||||||
|
- **Purpose**: Single source of truth for all continuous integration
|
||||||
|
- **Triggers**: push, pull_request to main/master
|
||||||
|
- **Jobs**: test (matrix), lint, coverage
|
||||||
|
- **Result**: Eliminated duplicate linting, maintained full functionality
|
||||||
|
|
||||||
|
### 2. Removed Redundant Workflow
|
||||||
|
|
||||||
|
**Action**: Deleted `.github/workflows/pr-lint.yml`
|
||||||
|
|
||||||
|
- **Reason**: Identical functionality already covered by CI workflow
|
||||||
|
- **Impact**: Eliminated 1 runner job per PR/push event
|
||||||
|
|
||||||
|
### 3. Optimized Release Workflow
|
||||||
|
|
||||||
|
**File**: `.github/workflows/release.yml`
|
||||||
|
**Changes**:
|
||||||
|
|
||||||
|
- **Removed**: Duplicate `test` and `lint` jobs
|
||||||
|
- **Added**: `check-ci` job that verifies CI workflow passed
|
||||||
|
- **Logic**: Only proceed with release if CI already passed for the commit
|
||||||
|
- **Dependencies**: `needs: [validate, check-ci, security]` (was `[validate, test, lint, security]`)
|
||||||
|
|
||||||
|
**Technical Implementation**:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
check-ci:
|
||||||
|
name: ✅ Verify CI Status
|
||||||
|
steps:
|
||||||
|
- name: 📋 Check CI Workflow Status
|
||||||
|
uses: actions/github-script@v8
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const wfList = await github.rest.actions.listRepoWorkflows({
|
||||||
|
owner: context.repo.owner, repo: context.repo.repo,
|
||||||
|
});
|
||||||
|
const wf =
|
||||||
|
wfList.data.workflows.find(w => w.path.endsWith('/test.yml')) ||
|
||||||
|
wfList.data.workflows.find(w => (w.name || '').toLowerCase() === 'ci');
|
||||||
|
if (!wf) core.setFailed('CI workflow not found');
|
||||||
|
const { data } = await github.rest.actions.listWorkflowRuns({
|
||||||
|
owner: context.repo.owner, repo: context.repo.repo,
|
||||||
|
workflow_id: wf.id, head_sha: context.sha,
|
||||||
|
status: 'completed', per_page: 1,
|
||||||
|
});
|
||||||
|
const latestRun = data.workflow_runs?.[0];
|
||||||
|
if (!latestRun) core.setFailed('No completed CI runs found');
|
||||||
|
if (latestRun.conclusion !== 'success')
|
||||||
|
core.setFailed(`CI conclusion: ${latestRun.conclusion}`);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Reduced Trigger Scope
|
||||||
|
|
||||||
|
**Files**: `codeql.yml`, `sync-labels.yml`
|
||||||
|
**Change**: Removed `merge_group` trigger
|
||||||
|
**Reason**: CI workflow already covers merge group testing
|
||||||
|
**Impact**: Fewer unnecessary runs on merge queue events
|
||||||
|
|
||||||
|
## Resource Savings Analysis
|
||||||
|
|
||||||
|
### Before Optimization
|
||||||
|
|
||||||
|
**Per PR/Push to main**:
|
||||||
|
|
||||||
|
- CI Jobs: 4 (test matrix 2x2)
|
||||||
|
- Linting Jobs: 3 (test.yml + pr-lint.yml + potential release)
|
||||||
|
- Total Runner Minutes: ~45-60 minutes
|
||||||
|
- Redundant Executions: High
|
||||||
|
|
||||||
|
**Per Release**:
|
||||||
|
|
||||||
|
- Test Jobs: 2 (CI + Release duplicate)
|
||||||
|
- Lint Jobs: 2 (CI + Release duplicate)
|
||||||
|
- Setup Jobs: Multiple redundant setups
|
||||||
|
- Total Runner Minutes: ~30-45 minutes
|
||||||
|
|
||||||
|
### After Optimization
|
||||||
|
|
||||||
|
**Per PR/Push to main**:
|
||||||
|
|
||||||
|
- CI Jobs: 4 (test matrix 2x2)
|
||||||
|
- Linting Jobs: 1 (consolidated in CI)
|
||||||
|
- Total Runner Minutes: ~15-20 minutes
|
||||||
|
- Redundant Executions: Eliminated
|
||||||
|
|
||||||
|
**Per Release**:
|
||||||
|
|
||||||
|
- Test Jobs: 0 (relies on CI status check)
|
||||||
|
- Lint Jobs: 0 (relies on CI status check)
|
||||||
|
- Setup Jobs: Minimal (only for release-specific tasks)
|
||||||
|
- Total Runner Minutes: ~5-10 minutes
|
||||||
|
|
||||||
|
### Resource Reduction Summary
|
||||||
|
|
||||||
|
- **Linting**: 67% reduction (3 → 1 job per event)
|
||||||
|
- **Testing**: 50% reduction for releases (eliminated duplicate test matrix)
|
||||||
|
- **Overall Runtime**: ~60% reduction in total runner minutes
|
||||||
|
- **Complexity**: Simplified workflow dependencies and logic
|
||||||
|
|
||||||
|
## Current Workflow Structure
|
||||||
|
|
||||||
|
### 1. CI Workflow (`test.yml`)
|
||||||
|
|
||||||
|
- **Triggers**: push, pull_request (to main/master)
|
||||||
|
- **Jobs**: test (Node 22,24), lint, coverage
|
||||||
|
- **Purpose**: Primary quality gate for all code changes
|
||||||
|
|
||||||
|
### 2. Release Workflow (`release.yml`)
|
||||||
|
|
||||||
|
- **Triggers**: tags (v*.*.\*), manual dispatch
|
||||||
|
- **Jobs**: validate, check-ci, security, release
|
||||||
|
- **Purpose**: Streamlined release process with CI dependency
|
||||||
|
|
||||||
|
### 3. Security Workflows
|
||||||
|
|
||||||
|
- **CodeQL**: push/PR analysis + weekly scans
|
||||||
|
- **Release Security**: npm audit before release
|
||||||
|
|
||||||
|
### 4. Maintenance Workflows
|
||||||
|
|
||||||
|
- **Stale**: Daily cleanup of old issues/PRs
|
||||||
|
- **Sync Labels**: Daily label synchronization
|
||||||
|
|
||||||
|
## Quality Assurance
|
||||||
|
|
||||||
|
### Validation Steps Taken
|
||||||
|
|
||||||
|
1. **YAML Syntax**: All workflows pass `yamllint` validation
|
||||||
|
2. **Action References**: Verified all custom actions exist (`.github/actions/*`)
|
||||||
|
3. **Dependency Logic**: Confirmed workflow dependencies are correctly structured
|
||||||
|
4. **Trigger Coverage**: Ensured all necessary events still trigger appropriate workflows
|
||||||
|
|
||||||
|
### Risk Mitigation
|
||||||
|
|
||||||
|
1. **CI Status Check**: Release workflow validates CI passed before proceeding
|
||||||
|
2. **Fallback Options**: Manual workflow dispatch available for releases
|
||||||
|
3. **Security Maintained**: All security scanning workflows preserved
|
||||||
|
4. **Concurrency Controls**: Proper concurrency groups prevent resource conflicts
|
||||||
|
|
||||||
|
## Future Optimization Opportunities
|
||||||
|
|
||||||
|
### Potential Further Improvements
|
||||||
|
|
||||||
|
1. **Conditional Jobs**: Skip certain jobs based on file changes (e.g., skip tests if only docs changed)
|
||||||
|
2. **Caching Optimization**: Enhanced npm/node_modules caching across workflows
|
||||||
|
3. **Matrix Reduction**: Consider reducing Node.js versions (keep only LTS + latest)
|
||||||
|
4. **Parallel Security**: Run security scans in parallel with CI rather than in release
|
||||||
|
|
||||||
|
### Monitoring Recommendations
|
||||||
|
|
||||||
|
1. **Track Runner Usage**: Monitor GitHub Actions usage metrics
|
||||||
|
2. **Performance Metrics**: Measure workflow completion times
|
||||||
|
3. **Failure Analysis**: Ensure optimization doesn't increase failure rates
|
||||||
|
4. **Cost Analysis**: Evaluate runner minute consumption reduction
|
||||||
|
|
||||||
|
## Implementation Impact
|
||||||
|
|
||||||
|
### Immediate Benefits
|
||||||
|
|
||||||
|
- ✅ **Faster CI**: Reduced redundant executions
|
||||||
|
- ✅ **Cleaner Logs**: Simplified workflow status
|
||||||
|
- ✅ **Resource Efficiency**: ~60% reduction in runner minutes
|
||||||
|
- ✅ **Maintainability**: Consolidated logic, fewer files to manage
|
||||||
|
|
||||||
|
### Maintained Capabilities
|
||||||
|
|
||||||
|
- ✅ **Quality Gates**: All testing and linting preserved
|
||||||
|
- ✅ **Security**: Full security scanning maintained
|
||||||
|
- ✅ **Release Process**: Streamlined but complete release pipeline
|
||||||
|
- ✅ **Development Workflow**: No impact on developer experience
|
||||||
|
|
||||||
|
The optimization successfully eliminated redundant workflow executions while maintaining all quality assurance and automation capabilities,
|
||||||
|
resulting in significant resource savings and improved CI/CD efficiency.
|
||||||
271
.serena/memories/project_status_verified_2025.md
Normal file
271
.serena/memories/project_status_verified_2025.md
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
# Tree-sitter-shellspec: Verified Project Status (2025)
|
||||||
|
|
||||||
|
## Current Status: PRODUCTION READY ✅
|
||||||
|
|
||||||
|
This memory contains only verified, accurate information about the current project state as of September 12, 2025.
|
||||||
|
|
||||||
|
## Core Project Facts
|
||||||
|
|
||||||
|
### Package Information
|
||||||
|
|
||||||
|
- **Name**: `@ivuorinen/tree-sitter-shellspec`
|
||||||
|
- **Version**: 0.1.0
|
||||||
|
- **License**: MIT
|
||||||
|
- **Author**: Ismo Vuorinen
|
||||||
|
- **Main**: grammar.js
|
||||||
|
|
||||||
|
### Dependencies (Verified)
|
||||||
|
|
||||||
|
package.json excerpts:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"dependencies": {
|
||||||
|
"tree-sitter-bash": "^0.25.1"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
"devDependencies": {
|
||||||
|
"editorconfig-checker": "^6.1.1",
|
||||||
|
"markdownlint-cli": "^0.47.0",
|
||||||
|
"nodemon": "^3.0.1",
|
||||||
|
"prettier": "^3.6.2",
|
||||||
|
"tree-sitter-cli": "^0.25.10"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### NPM Scripts (Verified - 17 total)
|
||||||
|
|
||||||
|
package.json excerpt:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"scripts": {
|
||||||
|
"generate": "tree-sitter generate && ./scripts/post-generate.sh",
|
||||||
|
"generate:only": "tree-sitter generate",
|
||||||
|
"test": "tree-sitter test",
|
||||||
|
"parse": "tree-sitter parse",
|
||||||
|
"web": "tree-sitter web-ui",
|
||||||
|
"build": "tree-sitter build",
|
||||||
|
"dev": "npm run generate && npm run test",
|
||||||
|
"dev:watch": "nodemon --watch grammar.js --watch test/ --ext js,txt --exec 'npm run dev'",
|
||||||
|
"lint": "npx mega-linter-runner",
|
||||||
|
"lint:markdown": "markdownlint . --config .markdownlint.json --ignore node_modules",
|
||||||
|
"lint:markdown:fix": "markdownlint . --config .markdownlint.json --ignore node_modules --fix",
|
||||||
|
"lint:editorconfig": "editorconfig-checker",
|
||||||
|
"lint:editorconfig:fix": "editorconfig-checker -fix",
|
||||||
|
"format": "prettier --write .",
|
||||||
|
"format:check": "prettier --check .",
|
||||||
|
"precommit": "pre-commit run --all-files",
|
||||||
|
"clean": "rm -rf src/parser.c src/grammar.json src/node-types.json",
|
||||||
|
"rebuild": "npm run clean && npm run generate"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test Suite Status (VERIFIED)
|
||||||
|
|
||||||
|
### Current Test Count: 63/63 PASSING ✅
|
||||||
|
|
||||||
|
Verified by actual test execution - all tests pass successfully.
|
||||||
|
|
||||||
|
### Test Files Structure (Verified)
|
||||||
|
|
||||||
|
```text
|
||||||
|
test/corpus/
|
||||||
|
├── context_blocks.txt # Context/ExampleGroup blocks
|
||||||
|
├── describe_blocks.txt # Describe/fDescribe/xDescribe blocks
|
||||||
|
├── hook_blocks.txt # BeforeEach/AfterEach/etc blocks
|
||||||
|
├── it_blocks.txt # It/Example/Specify blocks
|
||||||
|
├── nested_structures.txt # Complex nesting scenarios
|
||||||
|
├── real_world_patterns.txt # Official ShellSpec examples
|
||||||
|
└── utility_blocks.txt # Data/Parameters/Skip/Pending/Todo blocks
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Distribution (Verified)
|
||||||
|
|
||||||
|
- **context_blocks**: 7 tests
|
||||||
|
- **describe_blocks**: 7 tests
|
||||||
|
- **hook_blocks**: 12 tests
|
||||||
|
- **it_blocks**: 10 tests
|
||||||
|
- **nested_structures**: 6 tests
|
||||||
|
- **real_world_patterns**: 6 tests
|
||||||
|
- **utility_blocks**: 15 tests
|
||||||
|
- **Total**: 63 tests (100% pass rate)
|
||||||
|
|
||||||
|
## Grammar Implementation Status
|
||||||
|
|
||||||
|
### Grammar Conflicts: OPTIMIZED ✅
|
||||||
|
|
||||||
|
**Current Status**: Zero conflict warnings during generation
|
||||||
|
|
||||||
|
- Grammar generates cleanly with no warnings
|
||||||
|
- All essential conflicts properly configured
|
||||||
|
- Unnecessary conflicts eliminated through optimization
|
||||||
|
|
||||||
|
**CI Enforcement Recommendation**: Add CI guard to fail on any conflicts/warnings
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Add to CI workflow before tests
|
||||||
|
- name: Generate grammar (fail on conflicts)
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
npx tree-sitter generate 2>&1 | tee generate.log
|
||||||
|
! rg -nP '(conflict|warn)' generate.log
|
||||||
|
```
|
||||||
|
|
||||||
|
### Supported ShellSpec Constructs (Verified)
|
||||||
|
|
||||||
|
#### Block Types
|
||||||
|
|
||||||
|
- **Describe blocks**: `Describe`, `fDescribe`, `xDescribe`
|
||||||
|
- **Context blocks**: `Context`, `ExampleGroup`, `fContext`, `xContext`
|
||||||
|
- **It blocks**: `It`, `Example`, `Specify` + focused/skipped variants
|
||||||
|
- **Hook blocks**: `BeforeEach`, `AfterEach`, `BeforeAll`, `AfterAll`, `BeforeCall`, `AfterCall`, `BeforeRun`, `AfterRun`
|
||||||
|
- **Utility blocks**: `Parameters`, `Skip`, `Pending`, `Todo`
|
||||||
|
- **Data blocks**: Block-style with statements, string arguments, function arguments
|
||||||
|
|
||||||
|
#### Statement Types
|
||||||
|
|
||||||
|
- **Hook statements**: `Before func1 func2`, `After cleanup`
|
||||||
|
- **Include directives**: `Include ./helper.sh`
|
||||||
|
- **Conditional Skip**: `Skip if "reason" condition`
|
||||||
|
|
||||||
|
#### Key Features
|
||||||
|
|
||||||
|
- ✅ Mixed ShellSpec/bash code parsing
|
||||||
|
- ✅ Complex nested structures
|
||||||
|
- ✅ Real-world pattern support (tested against official examples)
|
||||||
|
- ✅ Top-level It blocks (no Describe wrapper required)
|
||||||
|
- ✅ Multiple argument handling
|
||||||
|
- ✅ String/raw string/word variants
|
||||||
|
|
||||||
|
## File Structure (Verified)
|
||||||
|
|
||||||
|
### Root Files
|
||||||
|
|
||||||
|
```text
|
||||||
|
├── grammar.js # Main grammar definition
|
||||||
|
├── package.json # Package configuration
|
||||||
|
├── package-lock.json # Locked dependencies
|
||||||
|
├── LICENSE # MIT license
|
||||||
|
├── README.md # Project documentation
|
||||||
|
├── CONTRIBUTING.md # Contribution guidelines
|
||||||
|
└── tree-sitter.json # Tree-sitter configuration
|
||||||
|
```
|
||||||
|
|
||||||
|
### Source Directory (Generated - DO NOT EDIT)
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/
|
||||||
|
├── parser.c # Generated C parser
|
||||||
|
├── grammar.json # Generated grammar JSON
|
||||||
|
├── node-types.json # Generated AST node types
|
||||||
|
├── scanner.c # External scanner
|
||||||
|
└── tree_sitter/ # C headers
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration Files (Verified)
|
||||||
|
|
||||||
|
```text
|
||||||
|
├── .coderabbit.yaml # CodeRabbit config
|
||||||
|
├── .editorconfig # Code formatting rules
|
||||||
|
├── .gitignore # Git ignore patterns
|
||||||
|
├── .markdownlint.json # Markdown linting config
|
||||||
|
├── .mega-linter.yml # MegaLinter configuration
|
||||||
|
├── .pre-commit-config.yaml # Pre-commit hooks
|
||||||
|
├── .shellcheckrc # ShellCheck config
|
||||||
|
├── .yamllint.yml # YAML linting rules
|
||||||
|
└── .yamlignore # YAML ignore patterns
|
||||||
|
```
|
||||||
|
|
||||||
|
## GitHub Workflows (Verified & Optimized)
|
||||||
|
|
||||||
|
### Current Workflows (5 total)
|
||||||
|
|
||||||
|
```text
|
||||||
|
.github/workflows/
|
||||||
|
├── test.yml # Main CI (renamed from "Test" to "CI")
|
||||||
|
├── release.yml # Release automation
|
||||||
|
├── codeql.yml # Security analysis
|
||||||
|
├── stale.yml # Issue/PR management
|
||||||
|
└── sync-labels.yml # Label synchronization
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**: `pr-lint.yml` was removed during optimization to eliminate duplication
|
||||||
|
|
||||||
|
### CI/CD Status
|
||||||
|
|
||||||
|
- ✅ All workflows operational
|
||||||
|
- ✅ Multi-node testing (Node.js 22, 24)
|
||||||
|
- ✅ Automated linting and security scanning
|
||||||
|
- ✅ Optimized to reduce runner resource consumption by ~60%
|
||||||
|
|
||||||
|
## Development Environment
|
||||||
|
|
||||||
|
### Quality Assurance Tools (Verified)
|
||||||
|
|
||||||
|
- **MegaLinter**: Comprehensive code quality checks
|
||||||
|
- **Markdownlint**: Markdown formatting (properly configured)
|
||||||
|
- **YAMLLint**: YAML file validation
|
||||||
|
- **EditorConfig**: Consistent code formatting
|
||||||
|
- **Pre-commit hooks**: Automated quality gates
|
||||||
|
|
||||||
|
### Development Workflow
|
||||||
|
|
||||||
|
1. **Primary**: `npm run dev` (generate + test)
|
||||||
|
2. **Watch mode**: `npm run dev:watch` (auto-regeneration)
|
||||||
|
3. **Quality check**: `npm run lint` (MegaLinter)
|
||||||
|
4. **Clean build**: `npm run rebuild`
|
||||||
|
|
||||||
|
## Production Readiness Indicators
|
||||||
|
|
||||||
|
### ✅ Quality Metrics
|
||||||
|
|
||||||
|
- **Test Coverage**: 63/63 tests passing (100%)
|
||||||
|
- **Grammar Generation**: Clean (zero warnings)
|
||||||
|
- **Code Quality**: All linters passing
|
||||||
|
- **CI/CD**: Fully automated and optimized
|
||||||
|
- **Documentation**: Comprehensive README with examples
|
||||||
|
|
||||||
|
### ✅ Professional Standards
|
||||||
|
|
||||||
|
- MIT license with proper attribution
|
||||||
|
- Semantic versioning
|
||||||
|
- Comprehensive contribution guidelines
|
||||||
|
- Code of conduct
|
||||||
|
- Issue templates
|
||||||
|
- Automated dependency management
|
||||||
|
|
||||||
|
### ✅ Technical Excellence
|
||||||
|
|
||||||
|
- Extends tree-sitter-bash efficiently
|
||||||
|
- Handles real-world ShellSpec patterns
|
||||||
|
- Compatible with multiple Node.js versions
|
||||||
|
- Optimized grammar conflicts
|
||||||
|
- Professional tooling integration
|
||||||
|
|
||||||
|
## Immediate Capabilities
|
||||||
|
|
||||||
|
### What Works Now
|
||||||
|
|
||||||
|
- ✅ Complete ShellSpec syntax parsing
|
||||||
|
- ✅ Editor integration ready (Neovim, VS Code, Emacs)
|
||||||
|
- ✅ NPM package ready for distribution
|
||||||
|
- ✅ All documented features tested and working
|
||||||
|
- ✅ Production-ready parser generation
|
||||||
|
|
||||||
|
### Future Enhancement Opportunities (Optional)
|
||||||
|
|
||||||
|
- Data block pipe filters (`Data | filter` syntax)
|
||||||
|
- ShellSpec assertion parsing (When/The statements)
|
||||||
|
- Additional editor-specific plugins
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
The tree-sitter-shellspec project is a **professionally developed, production-ready**
|
||||||
|
grammar that successfully parses all documented ShellSpec constructs.
|
||||||
|
With 63/63 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**: September 12, 2025
|
||||||
|
**Status**: All claims in this memory have been verified against the actual project state.
|
||||||
130
.serena/memories/real_world_shellspec_patterns.md
Normal file
130
.serena/memories/real_world_shellspec_patterns.md
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
# Real-World ShellSpec Patterns Analysis
|
||||||
|
|
||||||
|
Based on analysis of 24 official ShellSpec example files from test/spec/, this document captures the comprehensive patterns discovered and how they've been integrated into the grammar.
|
||||||
|
|
||||||
|
## Key Findings from Official Examples
|
||||||
|
|
||||||
|
### 1. Hook Statement Patterns
|
||||||
|
|
||||||
|
**Discovery**: ShellSpec uses both block-style hooks (with End) and statement-style hooks (without End)
|
||||||
|
|
||||||
|
**Statement-style hooks** (newly supported):
|
||||||
|
|
||||||
|
- `Before 'setup'` - Single function call
|
||||||
|
- `Before 'setup1' 'setup2'` - Multiple function calls
|
||||||
|
- `After 'cleanup'` - Cleanup functions
|
||||||
|
- `Before 'value=10'` - Inline code execution
|
||||||
|
|
||||||
|
**Grammar Implementation**: Added `shellspec_hook_statement` rule to handle Before/After statements without End blocks.
|
||||||
|
|
||||||
|
### 2. Directive Patterns
|
||||||
|
|
||||||
|
**Discovery**: ShellSpec has several directive-style statements that don't follow the typical block structure
|
||||||
|
|
||||||
|
**Include directive**:
|
||||||
|
|
||||||
|
- `Include ./lib.sh` - Include external scripts
|
||||||
|
- `Include ./support/custom_matcher.sh`
|
||||||
|
|
||||||
|
**Conditional Skip**:
|
||||||
|
|
||||||
|
- `Skip if "reason" condition` - Skip with conditions
|
||||||
|
- `Skip if 'function returns "skip"' [ "$(conditions)" = "skip" ]` - Complex conditions
|
||||||
|
|
||||||
|
**Grammar Implementation**: Added `shellspec_directive_statement` rule for Include and conditional Skip patterns.
|
||||||
|
|
||||||
|
### 3. Data Block Complexity (Future Enhancement)
|
||||||
|
|
||||||
|
**Discovery**: Data blocks have sophisticated syntax not yet fully supported:
|
||||||
|
|
||||||
|
- `Data:raw` and `Data:expand` modifiers for variable expansion control
|
||||||
|
- `Data | filter` syntax for piping data through filters
|
||||||
|
- `#|content` line prefix syntax for multi-line data
|
||||||
|
- Function-based data: `Data function_name`
|
||||||
|
- String-based data: `Data 'string content'`
|
||||||
|
|
||||||
|
**Current Status**: Basic Data blocks work as utility blocks, but advanced syntax requires future enhancement.
|
||||||
|
|
||||||
|
### 4. Top-Level Examples
|
||||||
|
|
||||||
|
**Discovery**: ShellSpec allows It/Example/Specify blocks at the top level without requiring Describe/Context wrapping.
|
||||||
|
|
||||||
|
**Pattern**:
|
||||||
|
|
||||||
|
```shellspec
|
||||||
|
It 'is simple'
|
||||||
|
When call echo 'ok'
|
||||||
|
The output should eq 'ok'
|
||||||
|
End
|
||||||
|
```
|
||||||
|
|
||||||
|
**Grammar Implementation**: Already supported through existing `shellspec_it_block` rule.
|
||||||
|
|
||||||
|
### 5. Complex Nesting and Context Switching
|
||||||
|
|
||||||
|
**Discovery**: Real-world examples show sophisticated nesting:
|
||||||
|
|
||||||
|
- Describe > Context > It hierarchies
|
||||||
|
- Mixed hook scoping (hooks defined at different nesting levels)
|
||||||
|
- Before/After hooks with multiple arguments for setup chains
|
||||||
|
- Comments and shellcheck directives intermixed
|
||||||
|
|
||||||
|
## Grammar Enhancements Made
|
||||||
|
|
||||||
|
### New Rules Added
|
||||||
|
|
||||||
|
1. `shellspec_hook_statement` - For Before/After without End
|
||||||
|
2. `shellspec_directive_statement` - For Include and conditional Skip
|
||||||
|
3. Enhanced conflicts array to handle new statement types
|
||||||
|
|
||||||
|
### Test Coverage Added
|
||||||
|
|
||||||
|
- 63 total tests (as of 2025-12-11; originally 59, up from 53)
|
||||||
|
- New `real_world_patterns.txt` test file
|
||||||
|
- 6 additional tests covering hook statements, directives, and complex patterns
|
||||||
|
- 4 additional tests for Data block modifiers (added 2025-12-11)
|
||||||
|
|
||||||
|
## Integration Status
|
||||||
|
|
||||||
|
✅ **Fully Integrated**:
|
||||||
|
|
||||||
|
- Hook statements (Before/After without End)
|
||||||
|
- Include directive
|
||||||
|
- Conditional Skip statements
|
||||||
|
- Top-level It blocks
|
||||||
|
|
||||||
|
⚠️ **Partially Supported**:
|
||||||
|
|
||||||
|
- Data blocks (basic functionality works, advanced syntax needs work)
|
||||||
|
- Complex conditional expressions in Skip
|
||||||
|
|
||||||
|
🔄 **Future Enhancements Needed**:
|
||||||
|
|
||||||
|
- Data block pipe filters (`Data | filter` syntax)
|
||||||
|
- More sophisticated conditional parsing for Skip
|
||||||
|
|
||||||
|
Note: Data block modifiers (`:raw`, `:expand`) and `#|` line syntax ARE implemented in grammar.js (lines 162-164, 173).
|
||||||
|
|
||||||
|
## Real-World Usage Patterns Observed
|
||||||
|
|
||||||
|
1. **Hook Chains**: Multiple Before/After hooks for complex setup/teardown
|
||||||
|
2. **Conditional Logic**: Heavy use of conditional Skip statements
|
||||||
|
3. **External Dependencies**: Frequent use of Include for modular test organization
|
||||||
|
4. **Mixed Context**: ShellSpec blocks mixed with regular bash functions and variables
|
||||||
|
5. **Assertion Patterns**: Consistent use of When/The assertion syntax
|
||||||
|
6. **Descriptive Strings**: Heavy use of descriptive string literals for test names
|
||||||
|
|
||||||
|
## Grammar Statistics
|
||||||
|
|
||||||
|
- **Block types**: 5 (Describe, Context, It, Hook, Utility)
|
||||||
|
- **Statement types**: 2 (Hook statements, Directives)
|
||||||
|
- **Keywords supported**: 25+ ShellSpec keywords
|
||||||
|
- **Test coverage**: 100% (63/63 tests passing as of 2025-12-11)
|
||||||
|
- **Conflict warnings**: 5 (optimized from 13, all necessary)
|
||||||
|
|
||||||
|
## Recommendations for Future Development
|
||||||
|
|
||||||
|
1. **Priority 1**: Implement advanced Data block syntax for better real-world compatibility
|
||||||
|
2. **Priority 2**: Enhance conditional Skip parsing to handle complex bash expressions
|
||||||
|
3. **Priority 3**: Optimize conflict declarations to reduce parser warnings
|
||||||
|
4. **Priority 4**: Add support for ShellSpec assertion syntax (When/The statements)
|
||||||
102
.serena/memories/suggested_commands.md
Normal file
102
.serena/memories/suggested_commands.md
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
# Suggested Commands
|
||||||
|
|
||||||
|
## Development Commands
|
||||||
|
|
||||||
|
### NPM Scripts (Preferred)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Quick development cycle
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Generate parser from grammar
|
||||||
|
npm run generate
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
npm test
|
||||||
|
|
||||||
|
# Interactive grammar testing
|
||||||
|
npm run web
|
||||||
|
|
||||||
|
# Clean and rebuild
|
||||||
|
npm run clean
|
||||||
|
npm run rebuild
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tree-sitter Direct Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate the parser from grammar.js
|
||||||
|
tree-sitter generate
|
||||||
|
|
||||||
|
# Test the grammar against test files
|
||||||
|
tree-sitter test
|
||||||
|
|
||||||
|
# Parse a specific file to debug
|
||||||
|
tree-sitter parse <file>
|
||||||
|
|
||||||
|
# Web UI for testing grammar
|
||||||
|
tree-sitter web-ui
|
||||||
|
|
||||||
|
# Clean generated files
|
||||||
|
rm -rf src/parser.c src/grammar.json src/node-types.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### Linting and Formatting
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Comprehensive linting (preferred)
|
||||||
|
npm run lint
|
||||||
|
|
||||||
|
# Specific linters
|
||||||
|
npm run lint:yaml
|
||||||
|
npm run lint:markdown
|
||||||
|
|
||||||
|
# Run pre-commit hooks manually
|
||||||
|
npm run precommit
|
||||||
|
|
||||||
|
# Direct linter commands
|
||||||
|
yamllint .
|
||||||
|
markdownlint . --config .markdownlint.json --fix
|
||||||
|
shellcheck **/*.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### Git and Version Control
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Standard git workflow
|
||||||
|
git add .
|
||||||
|
git commit -m "message"
|
||||||
|
git push
|
||||||
|
|
||||||
|
# Pre-commit hooks run automatically on commit
|
||||||
|
```
|
||||||
|
|
||||||
|
## System Commands (Darwin/macOS)
|
||||||
|
|
||||||
|
- `ls` - list files
|
||||||
|
- `find` - find files/directories
|
||||||
|
- `grep` - search text patterns
|
||||||
|
- `cd` - change directory
|
||||||
|
- `pwd` - print working directory
|
||||||
|
- `which` - locate command
|
||||||
|
|
||||||
|
## Node.js/npm Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install dependencies
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Using nvm (available at /Users/ivuorinen/.local/share/nvm/nvm.sh)
|
||||||
|
nvm use
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run specific test patterns (if needed)
|
||||||
|
tree-sitter test --debug
|
||||||
|
|
||||||
|
# Parse sample files for debugging
|
||||||
|
echo "Describe 'test' ... End" | tree-sitter parse
|
||||||
|
```
|
||||||
61
.serena/memories/task_completion_checklist.md
Normal file
61
.serena/memories/task_completion_checklist.md
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# Task Completion Checklist
|
||||||
|
|
||||||
|
When completing any development task in this project, follow these steps:
|
||||||
|
|
||||||
|
## 1. Code Quality Checks
|
||||||
|
|
||||||
|
- [ ] Run `npm run generate` or `tree-sitter generate` to regenerate parser after grammar changes
|
||||||
|
- [ ] Run `npm test` or `tree-sitter test` if test files exist
|
||||||
|
- [ ] Check EditorConfig compliance (blocking errors)
|
||||||
|
- [ ] Fix any linting issues (considered blocking)
|
||||||
|
|
||||||
|
## 2. Linting and Formatting
|
||||||
|
|
||||||
|
- [ ] Run `npm run lint` or `npx mega-linter-runner` for comprehensive linting
|
||||||
|
- [ ] Fix all linting errors (NO linting issues are acceptable)
|
||||||
|
- [ ] Run `npm run precommit` or `pre-commit run --all-files` to verify pre-commit hooks pass
|
||||||
|
- [ ] Use autofixers before manual lint fixing
|
||||||
|
|
||||||
|
## 3. Specific Linters to Run
|
||||||
|
|
||||||
|
- [ ] `npm run lint:yaml` or `yamllint .` for YAML files
|
||||||
|
- [ ] `npm run lint:markdown` or `markdownlint . --config .markdownlint.json --fix` for Markdown
|
||||||
|
- [ ] `shellcheck` for any shell scripts
|
||||||
|
|
||||||
|
## 4. Development Workflow
|
||||||
|
|
||||||
|
- [ ] Use `npm run dev` for quick development cycles (generate + test)
|
||||||
|
- [ ] Use `npm run web` for interactive grammar testing
|
||||||
|
- [ ] Use `npm run rebuild` if encountering parser issues
|
||||||
|
|
||||||
|
## 5. Git Workflow
|
||||||
|
|
||||||
|
- [ ] Ensure you are in the project root directory
|
||||||
|
- [ ] Stage changes with `git add`
|
||||||
|
- [ ] Commit with descriptive message
|
||||||
|
- [ ] **NEVER** use `git commit --no-verify`
|
||||||
|
- [ ] **NEVER** commit automatically unless explicitly requested
|
||||||
|
|
||||||
|
## 6. Tree-sitter Specific
|
||||||
|
|
||||||
|
- [ ] Verify grammar generates without errors
|
||||||
|
- [ ] Test parsing of sample ShellSpec files if available
|
||||||
|
- [ ] Ensure conflicts are properly handled
|
||||||
|
- [ ] Check that new rules don't break existing bash parsing
|
||||||
|
- [ ] Run test suite and ensure reasonable pass rate (aim for >85%)
|
||||||
|
|
||||||
|
## 7. Testing
|
||||||
|
|
||||||
|
- [ ] Add tests to `test/corpus/` for new grammar features
|
||||||
|
- [ ] Ensure tests follow tree-sitter test format
|
||||||
|
- [ ] Verify test expectations match actual parser output
|
||||||
|
- [ ] Update test expectations if grammar behavior changes
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
|
||||||
|
- EditorConfig violations are blocking errors
|
||||||
|
- All linting errors must be fixed before completion
|
||||||
|
- Use full paths when changing directories
|
||||||
|
- Use `nvm` for Node.js version management
|
||||||
|
- Never modify generated files in `src/` manually
|
||||||
|
- Current test suite has 53 tests with 87% pass rate (46/53)
|
||||||
53
.serena/project.yml
Normal file
53
.serena/project.yml
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
ignore_all_files_in_gitignore: true
|
||||||
|
ignored_paths:
|
||||||
|
- megalinter-reports
|
||||||
|
read_only: false
|
||||||
|
|
||||||
|
# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details.
|
||||||
|
# Below is the complete list of tools for convenience.
|
||||||
|
# To make sure you have the latest list of tools, and to view their descriptions,
|
||||||
|
# execute `uv run scripts/print_tool_overview.py`.
|
||||||
|
#
|
||||||
|
# * `activate_project`: Activates a project by name.
|
||||||
|
# * `check_onboarding_performed`: Checks whether project onboarding was already performed.
|
||||||
|
# * `create_text_file`: Creates/overwrites a file in the project directory.
|
||||||
|
# * `delete_lines`: Deletes a range of lines within a file.
|
||||||
|
# * `delete_memory`: Deletes a memory from Serena's project-specific memory store.
|
||||||
|
# * `execute_shell_command`: Executes a shell command.
|
||||||
|
# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced.
|
||||||
|
# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type).
|
||||||
|
# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).
|
||||||
|
# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
|
||||||
|
# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
|
||||||
|
# * `initial_instructions`: Gets the initial instructions for the current project.
|
||||||
|
# Should only be used in settings where the system prompt cannot be set,
|
||||||
|
# e.g. in clients you have no control over, like Claude Desktop.
|
||||||
|
# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
|
||||||
|
# * `insert_at_line`: Inserts content at a given line in a file.
|
||||||
|
# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
|
||||||
|
# * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
|
||||||
|
# * `list_memories`: Lists memories in Serena's project-specific memory store.
|
||||||
|
# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
|
||||||
|
# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context).
|
||||||
|
# * `read_file`: Reads a file within the project directory.
|
||||||
|
# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store.
|
||||||
|
# * `remove_project`: Removes a project from the Serena configuration.
|
||||||
|
# * `replace_lines`: Replaces a range of lines within a file with new content.
|
||||||
|
# * `replace_symbol_body`: Replaces the full definition of a symbol.
|
||||||
|
# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen.
|
||||||
|
# * `search_for_pattern`: Performs a search for a pattern in the project.
|
||||||
|
# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase.
|
||||||
|
# * `switch_modes`: Activates modes by providing a list of their names
|
||||||
|
# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information.
|
||||||
|
# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task.
|
||||||
|
# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed.
|
||||||
|
# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
|
||||||
|
excluded_tools: []
|
||||||
|
initial_prompt: ""
|
||||||
|
project_name: "tree-sitter-shellspec"
|
||||||
|
languages:
|
||||||
|
- cpp
|
||||||
|
- typescript
|
||||||
|
- bash
|
||||||
|
included_optional_tools: []
|
||||||
|
encoding: utf-8
|
||||||
@@ -1 +1,3 @@
|
|||||||
disable=SC2129
|
# ShellSpec test functions are invoked indirectly by the DSL framework,
|
||||||
|
# so SC2329 ("function not invoked") is a false positive here.
|
||||||
|
disable=SC2329
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
**/node_modules/**
|
||||||
|
|||||||
@@ -11,3 +11,7 @@ rules:
|
|||||||
min-spaces-from-content: 1
|
min-spaces-from-content: 1
|
||||||
trailing-spaces:
|
trailing-spaces:
|
||||||
level: warning
|
level: warning
|
||||||
|
|
||||||
|
ignore: |
|
||||||
|
/.git/
|
||||||
|
/node_modules/
|
||||||
|
|||||||
331
CLAUDE.md
Normal file
331
CLAUDE.md
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
This is a tree-sitter grammar for ShellSpec (a BDD testing framework for POSIX shell scripts).
|
||||||
|
The grammar extends tree-sitter-bash to parse ShellSpec-specific constructs like
|
||||||
|
Describe/Context/It blocks, hooks, and directives.
|
||||||
|
|
||||||
|
## Development Commands
|
||||||
|
|
||||||
|
### Core Workflow
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate parser from grammar.js
|
||||||
|
npm run generate
|
||||||
|
|
||||||
|
# Run all tests (must be 100% passing)
|
||||||
|
npm test
|
||||||
|
|
||||||
|
# Combined generate + test workflow
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Watch mode for active development
|
||||||
|
npm run dev:watch
|
||||||
|
|
||||||
|
# Build the parser
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# Full rebuild (clean + generate + build)
|
||||||
|
npm run rebuild
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run all tests
|
||||||
|
npm test
|
||||||
|
|
||||||
|
# Test specific patterns (use tree-sitter CLI via npx)
|
||||||
|
npx tree-sitter test -i "describe_blocks"
|
||||||
|
npx tree-sitter test -i "real_world_patterns"
|
||||||
|
|
||||||
|
# Parse a specific file to test grammar
|
||||||
|
npx tree-sitter parse path/to/file.shellspec
|
||||||
|
```
|
||||||
|
|
||||||
|
### Code Quality
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run all linters (MegaLinter)
|
||||||
|
npm run lint
|
||||||
|
|
||||||
|
# Fix markdown issues
|
||||||
|
npm run lint:markdown
|
||||||
|
|
||||||
|
# Run pre-commit hooks manually
|
||||||
|
npm run precommit
|
||||||
|
```
|
||||||
|
|
||||||
|
## Grammar Architecture
|
||||||
|
|
||||||
|
### Core Grammar Structure
|
||||||
|
|
||||||
|
The grammar extends `tree-sitter-bash` with 27 ShellSpec-specific rules:
|
||||||
|
|
||||||
|
**Block rules** (require `End` terminator):
|
||||||
|
|
||||||
|
1. `shellspec_describe_block` - Describe/fDescribe/xDescribe blocks
|
||||||
|
2. `shellspec_context_block` - Context/ExampleGroup blocks (with f/x variants)
|
||||||
|
3. `shellspec_it_block` - It/Example/Specify blocks (with f/x variants)
|
||||||
|
4. `shellspec_hook_block` - BeforeEach/AfterEach/BeforeAll/AfterAll/BeforeCall/AfterCall/BeforeRun/AfterRun
|
||||||
|
5. `shellspec_utility_block` - Parameters blocks (with :block/:value/:matrix/:dynamic variants)
|
||||||
|
6. `shellspec_data_block` - Data blocks with :raw/:expand modifiers, #| lines, pipe filters
|
||||||
|
7. `shellspec_mock_block` - Mock command blocks
|
||||||
|
|
||||||
|
**Statement rules** (single-line, no `End`):
|
||||||
|
|
||||||
|
1. `shellspec_hook_statement` - Before/After/BeforeEach/AfterEach/BeforeAll/AfterAll/BeforeCall/AfterCall/BeforeRun/AfterRun statements
|
||||||
|
2. `shellspec_directive_statement` - Include and conditional Skip if
|
||||||
|
3. `shellspec_when_statement` - When call/run evaluation statements
|
||||||
|
4. `shellspec_the_statement` - The subject should matcher expectations
|
||||||
|
5. `shellspec_assert_statement` - Assert function calls
|
||||||
|
6. `shellspec_path_statement` - Path/File/Dir statements
|
||||||
|
7. `shellspec_set_statement` - Set option statements
|
||||||
|
8. `shellspec_dump_statement` - Dump standalone
|
||||||
|
9. `shellspec_intercept_statement` - Intercept statements
|
||||||
|
10. `shellspec_todo_statement` - Todo standalone
|
||||||
|
11. `shellspec_pending_statement` - Pending standalone
|
||||||
|
12. `shellspec_skip_statement` - Skip standalone
|
||||||
|
|
||||||
|
**Directive rules** (% prefixed):
|
||||||
|
|
||||||
|
1. `shellspec_text_directive` - %text/%text:raw/%text:expand directives
|
||||||
|
2. `shellspec_const_directive` - %const and % directives
|
||||||
|
3. `shellspec_output_directive` - %puts/%putsn/%-/%= directives
|
||||||
|
4. `shellspec_preserve_directive` - %preserve directive
|
||||||
|
5. `shellspec_logger_directive` - %logger directive
|
||||||
|
|
||||||
|
**Helper rules** (used internally by other rules):
|
||||||
|
|
||||||
|
1. `shellspec_subject` - Subject words in The statements
|
||||||
|
2. `shellspec_matcher` - Matcher words in The statements
|
||||||
|
3. `shellspec_data_line_content` - Content after #| prefix
|
||||||
|
|
||||||
|
### Grammar Pattern
|
||||||
|
|
||||||
|
All blocks follow this structure:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
prec.right(1, seq(
|
||||||
|
choice("BlockType", "fBlockType", "xBlockType"),
|
||||||
|
field("description", choice($.string, $.raw_string, $.word)),
|
||||||
|
repeat($._terminated_statement),
|
||||||
|
"End"
|
||||||
|
))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Conflict Management
|
||||||
|
|
||||||
|
The grammar has 6 total conflicts (minimized for performance):
|
||||||
|
|
||||||
|
- 3 inherited from bash grammar
|
||||||
|
- 3 ShellSpec-specific: `[$.command_name, $.shellspec_data_block]`, `[$.command_name, $.shellspec_hook_statement]`, and `[$.shellspec_hook_block]`
|
||||||
|
|
||||||
|
When adding new rules, minimize new conflicts. Test thoroughly with `npm test` after grammar changes.
|
||||||
|
|
||||||
|
### Grammar Gotchas
|
||||||
|
|
||||||
|
- **Compound keyword tokenization**: Adding `"Data:raw"` as a single keyword token in ANY variant
|
||||||
|
forces the tokenizer to prefer it everywhere, breaking variants that expect `"Data"` `":"` `"raw"`
|
||||||
|
as separate tokens. Only use compound keywords in variants where they're strictly required
|
||||||
|
(e.g., pipe+#| variant).
|
||||||
|
- **Precedence at shift/reduce boundaries**: `prec(N)` on a simple alternative (e.g., `Data arg`)
|
||||||
|
applies at reduce time. A block alternative's higher `prec.right(M)` only takes effect when `End`
|
||||||
|
is matched. Adding even `prec(1)` to a simple variant can cause it to win over `prec.right(4)`
|
||||||
|
blocks at the initial ambiguity point.
|
||||||
|
- **Bash test expressions**: `[ ... ]` parses as `$.test_command` in tree-sitter-bash, not as
|
||||||
|
literal `[`/`]` tokens. Use `$.test_command` when grammar rules need to accept bracket
|
||||||
|
test expressions.
|
||||||
|
- **Verify spec files after grammar changes**: Corpus tests can pass while real spec files still
|
||||||
|
have parse errors. Always run
|
||||||
|
`for f in test/spec/*.sh; do tree-sitter parse "$f" 2>&1 | grep -c ERROR; done` after changes.
|
||||||
|
|
||||||
|
## Testing Requirements
|
||||||
|
|
||||||
|
### Quality Gates
|
||||||
|
|
||||||
|
- **Minimum tests**: 96 (currently 128 tests passing)
|
||||||
|
- **Success rate**: Must be 100% (no failing tests allowed)
|
||||||
|
- **Coverage**: All ShellSpec constructs must be tested
|
||||||
|
- **CI validation**: Tests run on Node 22 and 24
|
||||||
|
|
||||||
|
### Test Structure
|
||||||
|
|
||||||
|
Tests are organized in `test/corpus/`:
|
||||||
|
|
||||||
|
- `describe_blocks.txt` - Describe block variants
|
||||||
|
- `context_blocks.txt` - Context/ExampleGroup blocks
|
||||||
|
- `it_blocks.txt` - It/Example/Specify blocks
|
||||||
|
- `hook_blocks.txt` - Hook blocks and statements
|
||||||
|
- `utility_blocks.txt` - Parameters/Skip/Pending/Todo/Data blocks
|
||||||
|
- `nested_structures.txt` - Complex nesting scenarios
|
||||||
|
- `real_world_patterns.txt` - Official ShellSpec examples
|
||||||
|
- `when_the_assert.txt` - When/The/Assert evaluation and expectation statements
|
||||||
|
- `mock_blocks.txt` - Mock command blocks
|
||||||
|
- `shellspec_statements.txt` - Path/Set/Dump/Intercept statements
|
||||||
|
- `parameters_variants.txt` - Parameters :block/:value/:matrix/:dynamic variants
|
||||||
|
- `pending_skip_statements.txt` - Pending/Skip/Todo standalone statements
|
||||||
|
- `percent_directives.txt` - %text/%const/%puts/%preserve/%logger directives
|
||||||
|
|
||||||
|
### Test Format
|
||||||
|
|
||||||
|
Each test follows tree-sitter's corpus format:
|
||||||
|
|
||||||
|
```text
|
||||||
|
==================
|
||||||
|
Test name
|
||||||
|
==================
|
||||||
|
|
||||||
|
ShellSpec code here
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
(program
|
||||||
|
(expected AST structure))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code Style
|
||||||
|
|
||||||
|
### EditorConfig Rules (MUST follow)
|
||||||
|
|
||||||
|
- **Indentation**: 2 spaces (no tabs, except Makefiles)
|
||||||
|
- **Line endings**: LF (Unix)
|
||||||
|
- **Charset**: UTF-8
|
||||||
|
- **Max line length**: 160 characters
|
||||||
|
- **Trailing whitespace**: Remove (except .md files)
|
||||||
|
- **Final newline**: Required
|
||||||
|
|
||||||
|
### JavaScript/Grammar Conventions
|
||||||
|
|
||||||
|
- Use JSDoc comments for file headers
|
||||||
|
- Include TypeScript reference for tree-sitter DSL: `/// <reference types="tree-sitter-cli/dsl" />`
|
||||||
|
- Prefix ShellSpec rules with `shellspec_`
|
||||||
|
- Use descriptive field names (e.g., `field("description", ...)`)
|
||||||
|
- Use `prec.right()` for right-associative block structures
|
||||||
|
|
||||||
|
## Development Workflow
|
||||||
|
|
||||||
|
### Making Grammar Changes
|
||||||
|
|
||||||
|
1. **Edit `grammar.js`** - Make your changes
|
||||||
|
2. **Generate parser** - `npm run generate`
|
||||||
|
3. **Test changes** - `npm test` (must be 100% passing)
|
||||||
|
4. **Lint code** - `npm run lint` (must pass)
|
||||||
|
5. **Build parser** - `npm run build`
|
||||||
|
|
||||||
|
### Adding New ShellSpec Constructs
|
||||||
|
|
||||||
|
1. Add the rule to `grammar.js` in the `rules` object
|
||||||
|
2. Extend `_statement_not_subshell` to include the new rule
|
||||||
|
3. Create comprehensive test cases in appropriate `test/corpus/*.txt` file
|
||||||
|
4. Verify no new conflicts introduced
|
||||||
|
5. Update README.md if adding user-facing features
|
||||||
|
|
||||||
|
### Debugging Parse Failures
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Parse a file and see where it fails
|
||||||
|
npx tree-sitter parse path/to/failing.shellspec
|
||||||
|
|
||||||
|
# View detailed AST
|
||||||
|
npx tree-sitter parse path/to/file.shellspec --debug
|
||||||
|
|
||||||
|
# Open web UI for interactive debugging
|
||||||
|
npm run web
|
||||||
|
```
|
||||||
|
|
||||||
|
## CI/CD Pipeline
|
||||||
|
|
||||||
|
### GitHub Actions
|
||||||
|
|
||||||
|
- **test.yml**: Runs tests on Node 22 and 24, validates parser generation
|
||||||
|
- **lint**: MegaLinter code quality checks
|
||||||
|
- **coverage**: Validates minimum 96 tests passing
|
||||||
|
- All checks must pass for PR merge
|
||||||
|
|
||||||
|
### Disabled Linters
|
||||||
|
|
||||||
|
The following linters are intentionally disabled in `.mega-linter.yml`:
|
||||||
|
|
||||||
|
- C_CLANG_FORMAT (generated code may not follow style)
|
||||||
|
- JSON_PRETTIER (causes problems)
|
||||||
|
- SPELL_LYCHEE/CSPELL (too many false positives)
|
||||||
|
- JAVASCRIPT_PRETTIER (not using Prettier)
|
||||||
|
|
||||||
|
## ShellSpec Language Support
|
||||||
|
|
||||||
|
### Block Types Supported
|
||||||
|
|
||||||
|
- **Describe**: `Describe`, `fDescribe`, `xDescribe`
|
||||||
|
- **Context**: `Context`, `ExampleGroup`, `fContext`, `xContext`
|
||||||
|
- **It**: `It`, `Example`, `Specify`, `fIt`, `fExample`, `fSpecify`, `xIt`, `xExample`, `xSpecify`
|
||||||
|
- **Hooks**: `BeforeEach`, `AfterEach`, `BeforeAll`, `AfterAll`, `BeforeCall`, `AfterCall`, `BeforeRun`, `AfterRun`
|
||||||
|
- **Utility**: `Parameters` (with `:block`/`:value`/`:matrix`/`:dynamic`), `Data` (with `:raw`/`:expand`)
|
||||||
|
- **Mock**: `Mock` command blocks
|
||||||
|
|
||||||
|
### Statement Types
|
||||||
|
|
||||||
|
- **Hook statements**: `Before func1 func2`, `After cleanup`, `BeforeRun setup`, `AfterRun cleanup`, etc.
|
||||||
|
- **Include**: `Include ./helper.sh`
|
||||||
|
- **Conditional Skip**: `Skip if "reason" condition` (supports `[ ... ]` test expressions)
|
||||||
|
- **When**: `When call func`, `When run command cmd`
|
||||||
|
- **The**: `The output should equal "expected"`
|
||||||
|
- **Assert**: `Assert function args`
|
||||||
|
- **Path/File/Dir**: `Path name="value"`, `File name="value"`, `Dir name="value"`
|
||||||
|
- **Set**: `Set option:value`
|
||||||
|
- **Dump**: `Dump` standalone
|
||||||
|
- **Intercept**: `Intercept name`
|
||||||
|
- **Todo/Pending/Skip**: Standalone statement variants
|
||||||
|
|
||||||
|
### Directive Types
|
||||||
|
|
||||||
|
- **%text**: `%text`, `%text:raw`, `%text:expand` with `#|` content lines
|
||||||
|
- **%const / %**: `%const NAME "value"`, `% NAME "value"`
|
||||||
|
- **%puts / %putsn / %- / %=**: Output directives
|
||||||
|
- **%preserve**: `%preserve VAR`
|
||||||
|
- **%logger**: `%logger "message"`
|
||||||
|
|
||||||
|
### Known Limitations
|
||||||
|
|
||||||
|
- `%` standalone shorthand may conflict with bash job control in edge cases
|
||||||
|
- `%text` with multiple `#|` lines may not work outside of block contexts
|
||||||
|
- Tagging (`Describe 'name' tag:value`) and `%` directives beyond the supported set
|
||||||
|
(`%text`, `%const`, `%puts`, `%putsn`, `%preserve`, `%logger`) are not yet supported
|
||||||
|
- These are documented in README.md "Areas for Contribution"
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
|
||||||
|
- **Never modify generated files** in `src/` directory (parser.c, grammar.json, node-types.json)
|
||||||
|
- **Always run tests** after grammar changes - failing tests block PRs
|
||||||
|
- **Follow EditorConfig** rules strictly - violations are blocking errors
|
||||||
|
- **Maintain 100% test pass rate** - CI enforces minimum 96 tests passing
|
||||||
|
- **Use system `tree-sitter`** for CLI commands — the `npx tree-sitter` binary may not work; fall back to the system-installed tree-sitter CLI
|
||||||
|
- The grammar extends bash, so all bash syntax remains valid
|
||||||
|
|
||||||
|
## Claude Code Automations
|
||||||
|
|
||||||
|
### Hooks (`.claude/hooks/`)
|
||||||
|
|
||||||
|
Hook scripts are in `.claude/hooks/`, invoked by `.claude/settings.json`:
|
||||||
|
|
||||||
|
- **`pre-edit-guard.sh`** (PreToolUse): Blocks edits to generated files
|
||||||
|
(`src/parser.c`, `src/grammar.json`, `src/node-types.json`) and lock files
|
||||||
|
- **`post-edit-lint.sh`** (PostToolUse): Auto-regenerates parser after
|
||||||
|
`grammar.js` edits, checks EditorConfig compliance, validates corpus format
|
||||||
|
|
||||||
|
### Skills (`.claude/skills/`)
|
||||||
|
|
||||||
|
| Skill | Invocation | Description |
|
||||||
|
| ----- | ---------- | ----------- |
|
||||||
|
| `/generate-and-test` | User | Generate parser, run tests, verify spec files |
|
||||||
|
| `/add-shellspec-rule` | User | Guided workflow to add a new grammar rule with tests |
|
||||||
|
| `/debug-parse-failure` | User | Diagnose ERROR nodes and trace to grammar rules |
|
||||||
|
| `/update-highlights` | User | Sync highlights.scm with all grammar keywords |
|
||||||
|
| `/validate-release` | User only | Full pre-release validation checklist |
|
||||||
|
|
||||||
|
### Agents (`.claude/agents/`)
|
||||||
|
|
||||||
|
- **grammar-validator**: Runs tests and spec file parsing, reports results without editing
|
||||||
358
CONTRIBUTING.md
Normal file
358
CONTRIBUTING.md
Normal file
@@ -0,0 +1,358 @@
|
|||||||
|
# Contributing to tree-sitter-shellspec
|
||||||
|
|
||||||
|
Thank you for your interest in contributing to tree-sitter-shellspec! This document provides guidelines and information for contributors.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Getting Started](#getting-started)
|
||||||
|
- [Development Setup](#development-setup)
|
||||||
|
- [Grammar Development](#grammar-development)
|
||||||
|
- [Testing](#testing)
|
||||||
|
- [Code Style](#code-style)
|
||||||
|
- [Submitting Changes](#submitting-changes)
|
||||||
|
- [Reporting Issues](#reporting-issues)
|
||||||
|
- [Areas for Contribution](#areas-for-contribution)
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- [Node.js](https://nodejs.org/) (v22 or later)
|
||||||
|
- Tree-sitter CLI (provided via devDependency) — use `npx tree-sitter <cmd>`
|
||||||
|
- [Git](https://git-scm.com/)
|
||||||
|
- Basic knowledge of [Tree-sitter grammars](https://tree-sitter.github.io/tree-sitter/creating-parsers)
|
||||||
|
- Familiarity with [ShellSpec](https://shellspec.info/) syntax
|
||||||
|
|
||||||
|
### Fork and Clone
|
||||||
|
|
||||||
|
1. Fork the repository on GitHub
|
||||||
|
2. Clone your fork locally:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/YOUR_USERNAME/tree-sitter-shellspec.git
|
||||||
|
cd tree-sitter-shellspec
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Add the upstream repository:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git remote add upstream https://github.com/ivuorinen/tree-sitter-shellspec.git
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development Setup
|
||||||
|
|
||||||
|
1. **Install dependencies:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
1. **Generate the grammar:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run generate
|
||||||
|
```
|
||||||
|
|
||||||
|
1. **Run tests:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm test
|
||||||
|
```
|
||||||
|
|
||||||
|
1. **Build the parser:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Development Workflow
|
||||||
|
|
||||||
|
Use the provided npm scripts for common development tasks:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Development loop (generate + test)
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Watch mode for continuous development
|
||||||
|
npm run dev:watch
|
||||||
|
|
||||||
|
# Clean and rebuild everything
|
||||||
|
npm run rebuild
|
||||||
|
|
||||||
|
# Check code style
|
||||||
|
npm run lint
|
||||||
|
npm run lint:yaml
|
||||||
|
npm run lint:markdown
|
||||||
|
```
|
||||||
|
|
||||||
|
## Grammar Development
|
||||||
|
|
||||||
|
### Understanding the Grammar Structure
|
||||||
|
|
||||||
|
The grammar in `grammar.js` extends [tree-sitter-bash](https://github.com/tree-sitter/tree-sitter-bash) and adds ShellSpec-specific constructs:
|
||||||
|
|
||||||
|
- **Block types**: Describe, Context, It, Example, Specify
|
||||||
|
- **Hook types**: BeforeEach, AfterEach, BeforeAll, AfterAll, etc.
|
||||||
|
- **Utility blocks**: Data, Parameters, Skip, Pending, Todo
|
||||||
|
- **Statement types**: Before/After hooks, Include directive
|
||||||
|
- **Directives**: Include, conditional Skip
|
||||||
|
|
||||||
|
### Making Grammar Changes
|
||||||
|
|
||||||
|
1. **Edit `grammar.js`** with your changes
|
||||||
|
2. **Generate the parser:** `npm run generate`
|
||||||
|
3. **Test your changes:** `npm test`
|
||||||
|
4. **Add test cases** in `test/corpus/` for new functionality
|
||||||
|
5. **Update documentation** if needed
|
||||||
|
|
||||||
|
### Grammar Development Guidelines
|
||||||
|
|
||||||
|
- **Follow existing patterns** - Look at similar rules in the grammar
|
||||||
|
- **Use appropriate precedence** - Avoid conflicts with bash grammar
|
||||||
|
- **Test extensively** - Add comprehensive test cases
|
||||||
|
- **Document new syntax** - Update README.md with examples
|
||||||
|
- **Consider real-world usage** - Test with actual ShellSpec files
|
||||||
|
|
||||||
|
### Adding Test Cases
|
||||||
|
|
||||||
|
Create or update files in `test/corpus/`:
|
||||||
|
|
||||||
|
```text
|
||||||
|
================================================================================
|
||||||
|
Test Name
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
ShellSpec code here
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(expected_parse_tree
|
||||||
|
structure_here)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Test Organization:**
|
||||||
|
|
||||||
|
- `describe_blocks.txt` - Describe block variations
|
||||||
|
- `context_blocks.txt` - Context block variations
|
||||||
|
- `it_blocks.txt` - It/Example/Specify blocks
|
||||||
|
- `hook_blocks.txt` - Hook block patterns
|
||||||
|
- `utility_blocks.txt` - Data/Parameters/Skip/etc.
|
||||||
|
- `nested_structures.txt` - Complex nested patterns
|
||||||
|
- `real_world_patterns.txt` - Patterns from official examples
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Running Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run all tests
|
||||||
|
npm test
|
||||||
|
|
||||||
|
# Test specific patterns
|
||||||
|
npx tree-sitter test --filter "describe_blocks"
|
||||||
|
npx tree-sitter test --filter "real_world_patterns"
|
||||||
|
|
||||||
|
# Test with debug output
|
||||||
|
npx tree-sitter test --debug
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Coverage Requirements
|
||||||
|
|
||||||
|
- **All new grammar rules** must have test coverage
|
||||||
|
- **Existing tests** must continue to pass
|
||||||
|
- **Real-world examples** should be included when possible
|
||||||
|
- **Edge cases** should be tested
|
||||||
|
|
||||||
|
### Testing New Functionality
|
||||||
|
|
||||||
|
1. Add test cases before implementing
|
||||||
|
2. Run tests to see them fail
|
||||||
|
3. Implement the grammar changes
|
||||||
|
4. Run tests until they pass
|
||||||
|
5. Add additional edge case tests
|
||||||
|
|
||||||
|
## Code Style
|
||||||
|
|
||||||
|
### Grammar Style
|
||||||
|
|
||||||
|
- Use **consistent indentation** (2 spaces)
|
||||||
|
- Add **descriptive comments** for complex rules
|
||||||
|
- Use **meaningful rule names** (e.g., `shellspec_describe_block`)
|
||||||
|
- Group **related rules** together
|
||||||
|
- Follow **tree-sitter conventions**
|
||||||
|
|
||||||
|
### JavaScript Style
|
||||||
|
|
||||||
|
- Follow **Prettier** formatting
|
||||||
|
- Use **const** for immutable values
|
||||||
|
- Add **JSDoc comments** for exported functions
|
||||||
|
- Follow **Node.js best practices**
|
||||||
|
|
||||||
|
### Documentation Style
|
||||||
|
|
||||||
|
- Use **clear, concise language**
|
||||||
|
- Provide **practical examples**
|
||||||
|
- Keep **README.md** up to date
|
||||||
|
- Include **code comments** where needed
|
||||||
|
|
||||||
|
## Submitting Changes
|
||||||
|
|
||||||
|
### Before Submitting
|
||||||
|
|
||||||
|
1. **Ensure all tests pass:** `npm test`
|
||||||
|
2. **Check code style:** `npm run lint && npm run lint:editorconfig && npm run lint:markdown`
|
||||||
|
3. **Update documentation** if needed
|
||||||
|
4. **Test with real ShellSpec files** when possible
|
||||||
|
5. **Run the full development cycle:** `npm run rebuild`
|
||||||
|
|
||||||
|
### Pull Request Process
|
||||||
|
|
||||||
|
1. **Create a feature branch:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git checkout -b feature/your-feature-name
|
||||||
|
```
|
||||||
|
|
||||||
|
1. **Make your changes** following the guidelines above
|
||||||
|
|
||||||
|
2. **Commit with clear messages:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git commit -m "feat: add support for Data block modifiers
|
||||||
|
|
||||||
|
- Add :raw and :expand modifier support
|
||||||
|
- Update test cases for new syntax
|
||||||
|
- Add documentation examples"
|
||||||
|
```
|
||||||
|
|
||||||
|
1. **Push to your fork:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git push origin feature/your-feature-name
|
||||||
|
```
|
||||||
|
|
||||||
|
1. **Create a Pull Request** with:
|
||||||
|
|
||||||
|
- Clear description of changes
|
||||||
|
- References to related issues
|
||||||
|
- Test results and coverage
|
||||||
|
- Breaking change notes (if any)
|
||||||
|
|
||||||
|
### Commit Message Guidelines
|
||||||
|
|
||||||
|
Use [Conventional Commits](https://conventionalcommits.org/):
|
||||||
|
|
||||||
|
- `feat:` - New features
|
||||||
|
- `fix:` - Bug fixes
|
||||||
|
- `docs:` - Documentation changes
|
||||||
|
- `test:` - Test additions or changes
|
||||||
|
- `refactor:` - Code refactoring
|
||||||
|
- `chore:` - Maintenance tasks
|
||||||
|
|
||||||
|
## Reporting Issues
|
||||||
|
|
||||||
|
### Bug Reports
|
||||||
|
|
||||||
|
Use the [Bug Report template](.github/ISSUE_TEMPLATE/bug_report.md) and include:
|
||||||
|
|
||||||
|
- ShellSpec code that doesn't parse correctly
|
||||||
|
- Expected vs actual behavior
|
||||||
|
- Environment information
|
||||||
|
- Tree-sitter parse output (if available)
|
||||||
|
|
||||||
|
### Feature Requests
|
||||||
|
|
||||||
|
Use the [Feature Request template](.github/ISSUE_TEMPLATE/feature_request.md) and include:
|
||||||
|
|
||||||
|
- ShellSpec syntax example
|
||||||
|
- Use case description
|
||||||
|
- Current behavior
|
||||||
|
- Links to ShellSpec documentation
|
||||||
|
|
||||||
|
### Grammar Issues
|
||||||
|
|
||||||
|
Use the [Grammar Issue template](.github/ISSUE_TEMPLATE/grammar_issue.md) for:
|
||||||
|
|
||||||
|
- Parsing errors
|
||||||
|
- Grammar conflicts
|
||||||
|
- Performance problems
|
||||||
|
- Integration issues
|
||||||
|
|
||||||
|
## Areas for Contribution
|
||||||
|
|
||||||
|
### High Priority
|
||||||
|
|
||||||
|
1. **Enhanced Data block support**
|
||||||
|
|
||||||
|
- `:raw` and `:expand` modifiers
|
||||||
|
- Pipe filter syntax (`Data | command`)
|
||||||
|
- Multi-line `#|` syntax
|
||||||
|
|
||||||
|
1. **Assertion parsing**
|
||||||
|
|
||||||
|
- When/The statement structures
|
||||||
|
- Matcher syntax parsing
|
||||||
|
- Subject/predicate analysis
|
||||||
|
|
||||||
|
1. **Performance optimization**
|
||||||
|
|
||||||
|
- Reduce parser conflicts
|
||||||
|
- Optimize grammar rules
|
||||||
|
- Improve parsing speed
|
||||||
|
|
||||||
|
### Medium Priority
|
||||||
|
|
||||||
|
1. **Editor integration**
|
||||||
|
|
||||||
|
- Neovim configuration examples
|
||||||
|
- VS Code extension support
|
||||||
|
- Emacs tree-sitter integration
|
||||||
|
|
||||||
|
1. **Tooling improvements**
|
||||||
|
|
||||||
|
- Syntax highlighting themes
|
||||||
|
- Language server features
|
||||||
|
- Code formatting rules
|
||||||
|
|
||||||
|
1. **Documentation**
|
||||||
|
|
||||||
|
- Usage tutorials
|
||||||
|
- Grammar development guide
|
||||||
|
- Editor setup instructions
|
||||||
|
|
||||||
|
### Low Priority
|
||||||
|
|
||||||
|
1. **Advanced features**
|
||||||
|
|
||||||
|
- ShellSpec custom matchers
|
||||||
|
- Configuration file parsing
|
||||||
|
- Metadata extraction
|
||||||
|
|
||||||
|
## Development Resources
|
||||||
|
|
||||||
|
### Useful Links
|
||||||
|
|
||||||
|
- [Tree-sitter Documentation](https://tree-sitter.github.io/tree-sitter/)
|
||||||
|
- [ShellSpec Documentation](https://shellspec.info/)
|
||||||
|
- [tree-sitter-bash Grammar](https://github.com/tree-sitter/tree-sitter-bash)
|
||||||
|
- [Tree-sitter Playground](https://tree-sitter.github.io/tree-sitter/playground)
|
||||||
|
|
||||||
|
### Learning Resources
|
||||||
|
|
||||||
|
- [Creating Tree-sitter Parsers](https://tree-sitter.github.io/tree-sitter/creating-parsers)
|
||||||
|
- [Grammar DSL Reference](https://tree-sitter.github.io/tree-sitter/creating-parsers#the-grammar-dsl)
|
||||||
|
- [Tree-sitter Conflicts](https://tree-sitter.github.io/tree-sitter/creating-parsers#conflicts)
|
||||||
|
|
||||||
|
## Getting Help
|
||||||
|
|
||||||
|
- **GitHub Discussions**: For questions and general discussion
|
||||||
|
- **Issues**: For bugs and feature requests
|
||||||
|
- **Pull Requests**: For code review and collaboration
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
By contributing to tree-sitter-shellspec, you agree that your contributions will be licensed under the MIT License.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Thank you for contributing to tree-sitter-shellspec! 🎉
|
||||||
444
README.md
Normal file
444
README.md
Normal file
@@ -0,0 +1,444 @@
|
|||||||
|
# tree-sitter-shellspec
|
||||||
|
|
||||||
|
[](https://github.com/ivuorinen/tree-sitter-shellspec)
|
||||||
|
[](https://github.com/ivuorinen/tree-sitter-shellspec)
|
||||||
|
[](https://tree-sitter.github.io/)
|
||||||
|
|
||||||
|
A comprehensive [Tree-sitter](https://tree-sitter.github.io/) grammar for
|
||||||
|
[ShellSpec](https://shellspec.info/) - a BDD (Behavior Driven Development) testing framework for POSIX shell scripts.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This grammar extends the [tree-sitter-bash](https://github.com/tree-sitter/tree-sitter-bash) grammar to provide complete parsing support
|
||||||
|
for ShellSpec's BDD constructs.
|
||||||
|
|
||||||
|
It enables syntax highlighting, code navigation, and tooling integration for ShellSpec test files.
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- **Complete ShellSpec syntax support** - All block types, hooks, When/The/Assert DSL, Mock blocks, and % directives
|
||||||
|
- **Real-world compatibility** - Tested against official ShellSpec examples
|
||||||
|
- **Bash integration** - Seamlessly handles mixed ShellSpec/bash code
|
||||||
|
- **Production ready** - 100% test coverage with 120 comprehensive test cases
|
||||||
|
- **Editor support** - Works with any Tree-sitter compatible editor
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Using npm
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install @ivuorinen/tree-sitter-shellspec
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/ivuorinen/tree-sitter-shellspec.git
|
||||||
|
cd tree-sitter-shellspec
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Grammar Support
|
||||||
|
|
||||||
|
### Block Types
|
||||||
|
|
||||||
|
#### Describe Blocks (Example Groups)
|
||||||
|
|
||||||
|
```shellspec
|
||||||
|
Describe 'Calculator functions'
|
||||||
|
# Test cases go here
|
||||||
|
End
|
||||||
|
|
||||||
|
# Variants: Describe, fDescribe (focused), xDescribe (skipped)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Context Blocks (Sub-groups)
|
||||||
|
|
||||||
|
```shellspec
|
||||||
|
Context 'when input is valid'
|
||||||
|
# Specific test scenarios
|
||||||
|
End
|
||||||
|
|
||||||
|
# Variants: Context, ExampleGroup, fContext, xContext
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Example Blocks (Test Cases)
|
||||||
|
|
||||||
|
```shellspec
|
||||||
|
It 'should calculate sum correctly'
|
||||||
|
When call add 2 3
|
||||||
|
The output should eq 5
|
||||||
|
End
|
||||||
|
|
||||||
|
# Variants: It, Example, Specify, fIt, fExample, fSpecify, xIt, xExample, xSpecify
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hook Types
|
||||||
|
|
||||||
|
#### Block-Style Hooks
|
||||||
|
|
||||||
|
```shellspec
|
||||||
|
BeforeEach 'setup test environment'
|
||||||
|
# Setup code here
|
||||||
|
End
|
||||||
|
|
||||||
|
AfterEach 'cleanup after test'
|
||||||
|
# Cleanup code here
|
||||||
|
End
|
||||||
|
|
||||||
|
# Available: BeforeEach, AfterEach, BeforeAll, AfterAll, BeforeCall, AfterCall, BeforeRun, AfterRun
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Statement-Style Hooks
|
||||||
|
|
||||||
|
```shellspec
|
||||||
|
Before 'setup_function'
|
||||||
|
Before 'setup1' 'setup2' # Multiple functions
|
||||||
|
After 'cleanup_function'
|
||||||
|
Before 'variable=value' # Inline code
|
||||||
|
```
|
||||||
|
|
||||||
|
### Utility Blocks
|
||||||
|
|
||||||
|
#### Data Blocks
|
||||||
|
|
||||||
|
```shellspec
|
||||||
|
Data 'test input data'
|
||||||
|
item1 value1
|
||||||
|
item2 value2
|
||||||
|
End
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
```shellspec
|
||||||
|
Parameters
|
||||||
|
'param1'
|
||||||
|
'param2'
|
||||||
|
End
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Test Control
|
||||||
|
|
||||||
|
```shellspec
|
||||||
|
Skip 'not implemented yet'
|
||||||
|
# Skipped test code
|
||||||
|
End
|
||||||
|
|
||||||
|
Pending 'work in progress'
|
||||||
|
# Code that should fail for now
|
||||||
|
End
|
||||||
|
|
||||||
|
Todo 'implement feature X' # Note without block
|
||||||
|
```
|
||||||
|
|
||||||
|
### Directives
|
||||||
|
|
||||||
|
#### Include External Scripts
|
||||||
|
|
||||||
|
```shellspec
|
||||||
|
Include ./helper_functions.sh
|
||||||
|
Include ./custom_matchers.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Conditional Skip
|
||||||
|
|
||||||
|
```shellspec
|
||||||
|
Skip if "platform not supported" [ "$PLATFORM" != "linux" ]
|
||||||
|
Skip if "command not available" ! command -v docker
|
||||||
|
```
|
||||||
|
|
||||||
|
### When/The/Assert (Core Assertion DSL)
|
||||||
|
|
||||||
|
```shellspec
|
||||||
|
It 'should calculate correctly'
|
||||||
|
When call add 2 3
|
||||||
|
The output should eq 5
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'should handle errors'
|
||||||
|
When run command invalid_cmd
|
||||||
|
The status should be failure
|
||||||
|
The stderr should not eq ""
|
||||||
|
End
|
||||||
|
|
||||||
|
Assert check_result
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mock Blocks
|
||||||
|
|
||||||
|
```shellspec
|
||||||
|
Mock curl
|
||||||
|
echo '{"status": "ok"}'
|
||||||
|
End
|
||||||
|
```
|
||||||
|
|
||||||
|
### % Directives
|
||||||
|
|
||||||
|
```shellspec
|
||||||
|
%const API_URL: "https://api.example.com"
|
||||||
|
|
||||||
|
%text
|
||||||
|
#|line one
|
||||||
|
#|line two
|
||||||
|
|
||||||
|
%text | tr 'a-z' 'A-Z'
|
||||||
|
#|hello world
|
||||||
|
|
||||||
|
%puts "output without newline"
|
||||||
|
%putsn "output with newline"
|
||||||
|
%preserve RESULT
|
||||||
|
%logger "debug info"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Additional Statements
|
||||||
|
|
||||||
|
```shellspec
|
||||||
|
Path helper=./lib/helper.sh
|
||||||
|
Set 'errexit:on'
|
||||||
|
Dump
|
||||||
|
Intercept my_func
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
### Basic Test Structure
|
||||||
|
|
||||||
|
```shellspec
|
||||||
|
#!/usr/bin/env shellspec
|
||||||
|
|
||||||
|
Describe 'My Application'
|
||||||
|
Include ./lib/my_app.sh
|
||||||
|
|
||||||
|
Before 'setup_test_env'
|
||||||
|
After 'cleanup_test_env'
|
||||||
|
|
||||||
|
Context 'when user provides valid input'
|
||||||
|
It 'processes input correctly'
|
||||||
|
When call process_input "valid data"
|
||||||
|
The status should be success
|
||||||
|
The output should include "Processing complete"
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'returns expected format'
|
||||||
|
When call format_output "test"
|
||||||
|
The output should match pattern "^Result: .*"
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Context 'when user provides invalid input'
|
||||||
|
Skip if "validation not implemented" ! grep -q "validate" lib/my_app.sh
|
||||||
|
|
||||||
|
It 'handles errors gracefully'
|
||||||
|
When call process_input ""
|
||||||
|
The status should be failure
|
||||||
|
The stderr should include "Error: Invalid input"
|
||||||
|
End
|
||||||
|
End
|
||||||
|
End
|
||||||
|
```
|
||||||
|
|
||||||
|
### Top-Level Examples (No Describe Required)
|
||||||
|
|
||||||
|
```shellspec
|
||||||
|
It 'can run without describe block'
|
||||||
|
When call echo "hello"
|
||||||
|
The output should eq "hello"
|
||||||
|
End
|
||||||
|
```
|
||||||
|
|
||||||
|
### Complex Hook Chains
|
||||||
|
|
||||||
|
```shellspec
|
||||||
|
Describe 'Complex setup scenario'
|
||||||
|
Before 'init_database' 'load_fixtures' 'start_services'
|
||||||
|
After 'stop_services' 'cleanup_database'
|
||||||
|
|
||||||
|
BeforeEach 'reset test state'
|
||||||
|
test_counter=0
|
||||||
|
temp_dir=$(mktemp -d)
|
||||||
|
End
|
||||||
|
|
||||||
|
AfterEach 'verify cleanup'
|
||||||
|
[ "$test_counter" -gt 0 ]
|
||||||
|
rm -rf "$temp_dir"
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'runs with full setup chain'
|
||||||
|
When call complex_operation
|
||||||
|
The status should be success
|
||||||
|
End
|
||||||
|
End
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- [Node.js](https://nodejs.org/) (v22 or later)
|
||||||
|
- Tree-sitter CLI (provided via devDependency) — use `npx tree-sitter <cmd>`
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/ivuorinen/tree-sitter-shellspec.git
|
||||||
|
cd tree-sitter-shellspec
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Available Scripts
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate parser from grammar
|
||||||
|
npm run generate
|
||||||
|
|
||||||
|
# Run test suite
|
||||||
|
npm test
|
||||||
|
|
||||||
|
# Build the parser
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# Development workflow
|
||||||
|
npm run dev # Generate + test
|
||||||
|
npm run dev:watch # Watch mode for development
|
||||||
|
|
||||||
|
# Linting and formatting
|
||||||
|
npm run lint # Check code style
|
||||||
|
npm run lint:editorconfig:fix # Auto-fix EditorConfig issues
|
||||||
|
npm run lint:markdown # Auto-fix markdown issues (includes --fix)
|
||||||
|
npm run format # Format code with prettier
|
||||||
|
|
||||||
|
# Utilities
|
||||||
|
npm run clean # Clean generated files
|
||||||
|
npm run rebuild # Clean + generate + build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
The grammar includes comprehensive test coverage:
|
||||||
|
|
||||||
|
- **Comprehensive test cases** covering all ShellSpec constructs
|
||||||
|
- **Real-world patterns** from official ShellSpec repository
|
||||||
|
- **Edge cases** and complex nesting scenarios
|
||||||
|
- **Mixed content** (ShellSpec + bash code)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run all tests
|
||||||
|
npm test
|
||||||
|
|
||||||
|
# Test specific patterns
|
||||||
|
tree-sitter test -i "describe_blocks"
|
||||||
|
tree-sitter test -i "real_world_patterns"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Grammar Structure
|
||||||
|
|
||||||
|
The grammar extends tree-sitter-bash with 27 rules organized as follows:
|
||||||
|
|
||||||
|
**Block rules:**
|
||||||
|
|
||||||
|
- `shellspec_describe_block` - Describe/fDescribe/xDescribe blocks
|
||||||
|
- `shellspec_context_block` - Context/ExampleGroup blocks
|
||||||
|
- `shellspec_it_block` - It/Example/Specify blocks
|
||||||
|
- `shellspec_hook_block` - BeforeEach/AfterEach/etc. blocks
|
||||||
|
- `shellspec_utility_block` - Parameters blocks
|
||||||
|
- `shellspec_data_block` - Data blocks with content types
|
||||||
|
- `shellspec_mock_block` - Mock command blocks
|
||||||
|
|
||||||
|
**Statement rules:**
|
||||||
|
|
||||||
|
- `shellspec_when_statement` - When call/run statements
|
||||||
|
- `shellspec_the_statement` - The subject should matcher assertions
|
||||||
|
- `shellspec_assert_statement` - Assert function assertions
|
||||||
|
- `shellspec_hook_statement` - Before/After statements
|
||||||
|
- `shellspec_directive_statement` - Include and conditional Skip
|
||||||
|
- `shellspec_path_statement` - Path alias declarations
|
||||||
|
- `shellspec_set_statement` - Set option directives
|
||||||
|
- `shellspec_dump_statement` - Dump debugging output
|
||||||
|
- `shellspec_intercept_statement` - Intercept function calls
|
||||||
|
- `shellspec_todo_statement` - Todo markers
|
||||||
|
- `shellspec_pending_statement` - Pending markers
|
||||||
|
- `shellspec_skip_statement` - Skip markers
|
||||||
|
|
||||||
|
**Directive rules:**
|
||||||
|
|
||||||
|
- `shellspec_text_directive` - %text heredoc-style blocks
|
||||||
|
- `shellspec_const_directive` - %const variable declarations
|
||||||
|
- `shellspec_output_directive` - %puts/%putsn/%-/%= output directives
|
||||||
|
- `shellspec_preserve_directive` - %preserve variable preservation
|
||||||
|
- `shellspec_logger_directive` - %logger debug output
|
||||||
|
|
||||||
|
**Helper rules:**
|
||||||
|
|
||||||
|
- `shellspec_subject` - Subject expressions in The statements
|
||||||
|
- `shellspec_matcher` - Matcher expressions in The statements
|
||||||
|
- `shellspec_data_line_content` - Content lines in Data blocks
|
||||||
|
|
||||||
|
## Editor Integration
|
||||||
|
|
||||||
|
### Neovim (with nvim-treesitter)
|
||||||
|
|
||||||
|
Add to your Tree-sitter config:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
require'nvim-treesitter.configs'.setup {
|
||||||
|
ensure_installed = { "bash", "shellspec" },
|
||||||
|
highlight = {
|
||||||
|
enable = true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### VS Code
|
||||||
|
|
||||||
|
Install a Tree-sitter extension that supports custom grammars, then add this grammar to your configuration.
|
||||||
|
|
||||||
|
### Emacs (with tree-sitter-mode)
|
||||||
|
|
||||||
|
Add to your configuration:
|
||||||
|
|
||||||
|
```elisp
|
||||||
|
(add-to-list 'tree-sitter-major-mode-language-alist '(sh-mode . shellspec))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Contributions are welcome! Please see our [contributing guidelines](CONTRIBUTING.md) for details.
|
||||||
|
|
||||||
|
### Areas for Contribution
|
||||||
|
|
||||||
|
- **Tagging support** - `Describe "name" tag:value` syntax
|
||||||
|
- **Additional % directives** - `%data`, `%printf`, `%sleep` and other utility directives
|
||||||
|
- **Advanced subject/matcher semantics** - Ordinal references, compound modifiers in The statements
|
||||||
|
- **Editor plugins** - Syntax highlighting themes for various editors
|
||||||
|
- **Performance optimization** - Reduce parse time for large spec files
|
||||||
|
|
||||||
|
### Reporting Issues
|
||||||
|
|
||||||
|
Please report issues with:
|
||||||
|
|
||||||
|
- ShellSpec code that doesn't parse correctly
|
||||||
|
- Missing syntax highlighting
|
||||||
|
- Performance problems
|
||||||
|
- Documentation improvements
|
||||||
|
|
||||||
|
## Related Projects
|
||||||
|
|
||||||
|
- [ShellSpec](https://github.com/shellspec/shellspec) - The BDD testing framework
|
||||||
|
- [tree-sitter-bash](https://github.com/tree-sitter/tree-sitter-bash) - Base bash grammar
|
||||||
|
- [Tree-sitter](https://tree-sitter.github.io/) - Parser generator framework
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License - see [LICENSE](LICENSE) file for details.
|
||||||
|
|
||||||
|
## Acknowledgments
|
||||||
|
|
||||||
|
- [ShellSpec project](https://shellspec.info/) for the excellent BDD testing framework
|
||||||
|
- [Tree-sitter team](https://tree-sitter.github.io/) for the parsing framework
|
||||||
|
- [tree-sitter-bash](https://github.com/tree-sitter/tree-sitter-bash) contributors for the base grammar
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Star this project** ⭐ if you find it useful for your ShellSpec development workflow!
|
||||||
410
grammar.js
Normal file
410
grammar.js
Normal file
@@ -0,0 +1,410 @@
|
|||||||
|
/**
|
||||||
|
* @file ShellSpec grammar for tree-sitter (extends bash)
|
||||||
|
* @author Ismo Vuorinen <ismo@ivuorinen.net>
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// <reference types="tree-sitter-cli/dsl" />
|
||||||
|
// @ts-check
|
||||||
|
|
||||||
|
const bashGrammar = require("tree-sitter-bash/grammar");
|
||||||
|
|
||||||
|
module.exports = grammar(bashGrammar, {
|
||||||
|
name: "shellspec",
|
||||||
|
|
||||||
|
// Precedence Strategy:
|
||||||
|
// ShellSpec extends bash grammar by adding BDD test constructs. The key design
|
||||||
|
// principle is to make ShellSpec blocks take precedence over bash commands when
|
||||||
|
// followed by their specific syntax (descriptions, End keywords).
|
||||||
|
//
|
||||||
|
// Precedence levels:
|
||||||
|
// - Level 1: bash_statement (base level, all bash constructs)
|
||||||
|
// - Level 2: ShellSpec statements (higher precedence than bash)
|
||||||
|
// - Level 5: Data block with #| lines (highest specificity)
|
||||||
|
//
|
||||||
|
// This allows "Describe", "It", etc. to work as both:
|
||||||
|
// 1. ShellSpec block keywords (when followed by description + End)
|
||||||
|
// 2. Regular bash commands/functions (in any other context)
|
||||||
|
|
||||||
|
conflicts: ($, previous) =>
|
||||||
|
previous.concat([
|
||||||
|
// Essential bash conflicts only
|
||||||
|
[$._expression, $.command_name],
|
||||||
|
[$.command, $.variable_assignments],
|
||||||
|
[$.function_definition, $.command_name],
|
||||||
|
// Required ShellSpec conflicts
|
||||||
|
[$.command_name, $.shellspec_data_block],
|
||||||
|
[$.command_name, $.shellspec_hook_statement],
|
||||||
|
[$.shellspec_hook_block],
|
||||||
|
]),
|
||||||
|
|
||||||
|
rules: {
|
||||||
|
// Extend the main statement rule to include ShellSpec blocks and directives
|
||||||
|
_statement_not_subshell: ($, original) =>
|
||||||
|
choice(
|
||||||
|
// @ts-ignore
|
||||||
|
original,
|
||||||
|
$.shellspec_describe_block,
|
||||||
|
$.shellspec_context_block,
|
||||||
|
$.shellspec_it_block,
|
||||||
|
$.shellspec_hook_block,
|
||||||
|
$.shellspec_utility_block,
|
||||||
|
$.shellspec_data_block,
|
||||||
|
$.shellspec_hook_statement,
|
||||||
|
$.shellspec_directive_statement,
|
||||||
|
// Phase 1: When/The/Assert statements
|
||||||
|
$.shellspec_when_statement,
|
||||||
|
$.shellspec_the_statement,
|
||||||
|
$.shellspec_assert_statement,
|
||||||
|
// Phase 2: Mock block, Path/Set/Dump/Intercept statements
|
||||||
|
$.shellspec_mock_block,
|
||||||
|
$.shellspec_path_statement,
|
||||||
|
$.shellspec_set_statement,
|
||||||
|
$.shellspec_dump_statement,
|
||||||
|
$.shellspec_intercept_statement,
|
||||||
|
// Phase 3: Todo standalone statement
|
||||||
|
$.shellspec_todo_statement,
|
||||||
|
// Phase 4: Pending/Skip standalone statements
|
||||||
|
$.shellspec_pending_statement,
|
||||||
|
$.shellspec_skip_statement,
|
||||||
|
// Phase 4: Percent directives
|
||||||
|
$.shellspec_text_directive,
|
||||||
|
$.shellspec_const_directive,
|
||||||
|
$.shellspec_output_directive,
|
||||||
|
$.shellspec_preserve_directive,
|
||||||
|
$.shellspec_logger_directive,
|
||||||
|
),
|
||||||
|
|
||||||
|
// ShellSpec Describe blocks
|
||||||
|
shellspec_describe_block: ($) =>
|
||||||
|
prec.right(
|
||||||
|
1,
|
||||||
|
seq(
|
||||||
|
choice("Describe", "fDescribe", "xDescribe"),
|
||||||
|
field("description", choice($.string, $.raw_string, $.word)),
|
||||||
|
repeat($._terminated_statement),
|
||||||
|
"End",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// ShellSpec Context/ExampleGroup blocks
|
||||||
|
shellspec_context_block: ($) =>
|
||||||
|
prec.right(
|
||||||
|
1,
|
||||||
|
seq(
|
||||||
|
choice(
|
||||||
|
"Context",
|
||||||
|
"ExampleGroup",
|
||||||
|
"fContext",
|
||||||
|
"xContext",
|
||||||
|
"fExampleGroup",
|
||||||
|
"xExampleGroup",
|
||||||
|
),
|
||||||
|
field("description", choice($.string, $.raw_string, $.word)),
|
||||||
|
repeat($._terminated_statement),
|
||||||
|
"End",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// ShellSpec It/Example/Specify blocks
|
||||||
|
shellspec_it_block: ($) =>
|
||||||
|
prec.right(
|
||||||
|
1,
|
||||||
|
seq(
|
||||||
|
choice(
|
||||||
|
"It",
|
||||||
|
"Example",
|
||||||
|
"Specify",
|
||||||
|
"fIt",
|
||||||
|
"fExample",
|
||||||
|
"fSpecify",
|
||||||
|
"xIt",
|
||||||
|
"xExample",
|
||||||
|
"xSpecify",
|
||||||
|
),
|
||||||
|
field("description", choice($.string, $.raw_string, $.word)),
|
||||||
|
repeat($._terminated_statement),
|
||||||
|
"End",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// ShellSpec hooks as blocks (with End)
|
||||||
|
shellspec_hook_block: ($) =>
|
||||||
|
prec.right(
|
||||||
|
1,
|
||||||
|
seq(
|
||||||
|
choice(
|
||||||
|
"BeforeEach",
|
||||||
|
"AfterEach",
|
||||||
|
"BeforeAll",
|
||||||
|
"AfterAll",
|
||||||
|
"BeforeCall",
|
||||||
|
"AfterCall",
|
||||||
|
"BeforeRun",
|
||||||
|
"AfterRun",
|
||||||
|
),
|
||||||
|
optional(field("label", choice($.string, $.raw_string, $.word))),
|
||||||
|
repeat($._terminated_statement),
|
||||||
|
"End",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// ShellSpec utility blocks (Parameters, Skip, Pending - Data has its own rule)
|
||||||
|
shellspec_utility_block: ($) =>
|
||||||
|
prec.right(
|
||||||
|
1,
|
||||||
|
seq(
|
||||||
|
choice(
|
||||||
|
"Parameters",
|
||||||
|
"Parameters:block",
|
||||||
|
"Parameters:value",
|
||||||
|
"Parameters:matrix",
|
||||||
|
"Parameters:dynamic",
|
||||||
|
),
|
||||||
|
optional(field("label", choice($.string, $.raw_string, $.word))),
|
||||||
|
repeat($._terminated_statement),
|
||||||
|
"End",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// ShellSpec Data blocks - optimized for performance while maintaining functionality
|
||||||
|
shellspec_data_block: ($) =>
|
||||||
|
choice(
|
||||||
|
// Block style with pipe filter + #| lines (highest precedence)
|
||||||
|
prec.right(
|
||||||
|
6,
|
||||||
|
seq(
|
||||||
|
choice("Data", "Data:raw", "Data:expand"),
|
||||||
|
"|",
|
||||||
|
repeat1(field("filter", choice($.string, $.raw_string, $.word))),
|
||||||
|
repeat1(seq("#|", field("data_line", $.shellspec_data_line_content))),
|
||||||
|
"End",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Block style with #| lines (supports both "Data :raw" and "Data:raw" forms)
|
||||||
|
prec.right(
|
||||||
|
5,
|
||||||
|
seq(
|
||||||
|
choice(
|
||||||
|
seq("Data", optional(seq(":", field("modifier", choice("raw", "expand"))))),
|
||||||
|
"Data:raw",
|
||||||
|
"Data:expand",
|
||||||
|
),
|
||||||
|
repeat1(seq("#|", field("data_line", $.shellspec_data_line_content))),
|
||||||
|
"End",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Block style with regular statements
|
||||||
|
prec.right(
|
||||||
|
4,
|
||||||
|
seq(
|
||||||
|
"Data",
|
||||||
|
optional(seq(":", field("modifier", choice("raw", "expand")))),
|
||||||
|
optional(field("label", choice($.string, $.raw_string, $.word))),
|
||||||
|
field("statements", repeat($._terminated_statement)),
|
||||||
|
"End",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Argument(s) with pipe filter (no End, single line)
|
||||||
|
prec.right(
|
||||||
|
3,
|
||||||
|
seq(
|
||||||
|
"Data",
|
||||||
|
field("argument", choice($.string, $.raw_string, $.word)),
|
||||||
|
repeat(field("extra_argument", choice($.string, $.raw_string, $.word))),
|
||||||
|
"|",
|
||||||
|
repeat1(field("filter", choice($.string, $.raw_string, $.word))),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// String argument style (no End) - lowest precedence
|
||||||
|
seq(
|
||||||
|
"Data",
|
||||||
|
optional(seq(":", field("modifier", choice("raw", "expand")))),
|
||||||
|
field("argument", choice($.string, $.raw_string, $.word)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Phase 1: When statement — core ShellSpec assertion DSL
|
||||||
|
shellspec_when_statement: ($) =>
|
||||||
|
prec.right(
|
||||||
|
2,
|
||||||
|
seq(
|
||||||
|
"When",
|
||||||
|
field(
|
||||||
|
"type",
|
||||||
|
choice("call", seq("run", optional(choice("command", "script", "source")))),
|
||||||
|
),
|
||||||
|
field("function", choice($.string, $.raw_string, $.word)),
|
||||||
|
repeat(field("argument", choice($.string, $.raw_string, $.word))),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Phase 1: The statement — core ShellSpec expectation DSL
|
||||||
|
// Subject consumes words until "should", then matcher consumes the rest
|
||||||
|
shellspec_subject: ($) => repeat1(choice($.string, $.raw_string, $.word)),
|
||||||
|
|
||||||
|
shellspec_matcher: ($) => repeat1(choice($.string, $.raw_string, $.word)),
|
||||||
|
|
||||||
|
shellspec_the_statement: ($) =>
|
||||||
|
prec.right(
|
||||||
|
2,
|
||||||
|
seq(
|
||||||
|
"The",
|
||||||
|
field("subject", $.shellspec_subject),
|
||||||
|
"should",
|
||||||
|
optional(field("negation", "not")),
|
||||||
|
field("matcher", $.shellspec_matcher),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Phase 1: Assert statement
|
||||||
|
shellspec_assert_statement: ($) =>
|
||||||
|
prec.right(
|
||||||
|
2,
|
||||||
|
seq("Assert", repeat1(field("argument", choice($.string, $.raw_string, $.word)))),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Phase 2: Mock block
|
||||||
|
shellspec_mock_block: ($) =>
|
||||||
|
prec.right(
|
||||||
|
1,
|
||||||
|
seq(
|
||||||
|
"Mock",
|
||||||
|
field("name", choice($.string, $.raw_string, $.word)),
|
||||||
|
repeat($._terminated_statement),
|
||||||
|
"End",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Phase 2: Path/File/Dir statement
|
||||||
|
shellspec_path_statement: ($) =>
|
||||||
|
prec.right(
|
||||||
|
2,
|
||||||
|
seq(
|
||||||
|
choice("Path", "File", "Dir"),
|
||||||
|
repeat1(field("argument", choice($.string, $.raw_string, $.word))),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Phase 2: Set statement
|
||||||
|
shellspec_set_statement: ($) =>
|
||||||
|
prec.right(2, seq("Set", repeat1(field("option", choice($.string, $.raw_string, $.word))))),
|
||||||
|
|
||||||
|
// Phase 2: Dump statement (standalone, no arguments)
|
||||||
|
shellspec_dump_statement: () => prec.right(2, "Dump"),
|
||||||
|
|
||||||
|
// Phase 2: Intercept statement
|
||||||
|
shellspec_intercept_statement: ($) =>
|
||||||
|
prec.right(
|
||||||
|
2,
|
||||||
|
seq("Intercept", repeat1(field("argument", choice($.string, $.raw_string, $.word)))),
|
||||||
|
),
|
||||||
|
|
||||||
|
// ShellSpec hooks as statements (standalone, without End)
|
||||||
|
shellspec_hook_statement: ($) =>
|
||||||
|
prec.right(
|
||||||
|
2,
|
||||||
|
seq(
|
||||||
|
choice(
|
||||||
|
"Before",
|
||||||
|
"After",
|
||||||
|
"BeforeEach",
|
||||||
|
"AfterEach",
|
||||||
|
"BeforeAll",
|
||||||
|
"AfterAll",
|
||||||
|
"BeforeCall",
|
||||||
|
"AfterCall",
|
||||||
|
"BeforeRun",
|
||||||
|
"AfterRun",
|
||||||
|
),
|
||||||
|
repeat1(field("argument", choice($.string, $.raw_string, $.word))),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// ShellSpec directives (Include, Skip with conditions)
|
||||||
|
shellspec_directive_statement: ($) =>
|
||||||
|
prec.right(
|
||||||
|
2,
|
||||||
|
choice(
|
||||||
|
// Include directive
|
||||||
|
seq("Include", field("path", choice($.string, $.raw_string, $.word))),
|
||||||
|
// Skip with conditions (only conditional skip, simple skip handled by utility_block)
|
||||||
|
prec.right(
|
||||||
|
3,
|
||||||
|
seq(
|
||||||
|
"Skip",
|
||||||
|
"if",
|
||||||
|
field("reason", choice($.string, $.raw_string, $.word)),
|
||||||
|
field(
|
||||||
|
"condition",
|
||||||
|
repeat1(
|
||||||
|
choice($.word, $.string, $.raw_string, $.command_substitution, $.test_command),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Phase 3: Todo standalone statement (without End block)
|
||||||
|
shellspec_todo_statement: ($) =>
|
||||||
|
prec.right(2, seq("Todo", field("description", choice($.string, $.raw_string, $.word)))),
|
||||||
|
|
||||||
|
// Phase 4: Pending standalone statement (without End block)
|
||||||
|
shellspec_pending_statement: ($) =>
|
||||||
|
prec.right(2, seq("Pending", field("reason", choice($.string, $.raw_string, $.word)))),
|
||||||
|
|
||||||
|
// Phase 4: Skip standalone statement (without End block)
|
||||||
|
shellspec_skip_statement: ($) =>
|
||||||
|
prec.right(2, seq("Skip", field("reason", choice($.string, $.raw_string, $.word)))),
|
||||||
|
|
||||||
|
// Phase 4: %text directive
|
||||||
|
shellspec_text_directive: ($) =>
|
||||||
|
prec.right(
|
||||||
|
5,
|
||||||
|
seq(
|
||||||
|
choice("%text", "%text:raw", "%text:expand"),
|
||||||
|
optional(seq("|", repeat1(field("filter", choice($.string, $.raw_string, $.word))))),
|
||||||
|
repeat1(seq("#|", field("data_line", $.shellspec_data_line_content))),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Reusable data line content for #| lines
|
||||||
|
shellspec_data_line_content: () => /[^\n]*/,
|
||||||
|
|
||||||
|
// Phase 4: %const directive
|
||||||
|
shellspec_const_directive: ($) =>
|
||||||
|
prec.right(
|
||||||
|
2,
|
||||||
|
seq(
|
||||||
|
choice("%const", "%"),
|
||||||
|
field("name", $.word),
|
||||||
|
field("value", choice($.string, $.raw_string, $.word)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Phase 4: Output directives
|
||||||
|
shellspec_output_directive: ($) =>
|
||||||
|
prec.right(
|
||||||
|
2,
|
||||||
|
seq(
|
||||||
|
choice("%puts", "%putsn", "%-", "%="),
|
||||||
|
repeat1(field("argument", choice($.string, $.raw_string, $.word))),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Phase 4: %preserve directive
|
||||||
|
shellspec_preserve_directive: ($) =>
|
||||||
|
prec.right(
|
||||||
|
2,
|
||||||
|
seq("%preserve", repeat1(field("variable", choice($.string, $.raw_string, $.word)))),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Phase 4: %logger directive
|
||||||
|
shellspec_logger_directive: ($) =>
|
||||||
|
prec.right(
|
||||||
|
2,
|
||||||
|
seq("%logger", repeat1(field("argument", choice($.string, $.raw_string, $.word)))),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
});
|
||||||
1569
package-lock.json
generated
Normal file
1569
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
60
package.json
Normal file
60
package.json
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
{
|
||||||
|
"name": "@ivuorinen/tree-sitter-shellspec",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "ShellSpec grammar for tree-sitter (extends bash)",
|
||||||
|
"main": "grammar.js",
|
||||||
|
"author": "Ismo Vuorinen",
|
||||||
|
"license": "MIT",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/ivuorinen/tree-sitter-shellspec.git"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"grammar.js",
|
||||||
|
"src",
|
||||||
|
"queries",
|
||||||
|
"binding.gyp",
|
||||||
|
"bindings",
|
||||||
|
"scripts"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"generate": "npx tree-sitter generate && ./scripts/post-generate.sh",
|
||||||
|
"generate:only": "npx tree-sitter generate",
|
||||||
|
"test": "npx tree-sitter test",
|
||||||
|
"parse": "npx tree-sitter parse",
|
||||||
|
"web": "npx tree-sitter web-ui",
|
||||||
|
"build": "npx tree-sitter build .",
|
||||||
|
"dev": "npm run generate && npm run test",
|
||||||
|
"dev:watch": "nodemon --watch grammar.js --watch test/ --ext js,txt --exec 'npm run dev'",
|
||||||
|
"lint": "npx mega-linter-runner",
|
||||||
|
"lint:markdown": "markdownlint . --config .markdownlint.json --ignore node_modules",
|
||||||
|
"lint:markdown:fix": "markdownlint . --config .markdownlint.json --ignore node_modules --fix",
|
||||||
|
"lint:editorconfig": "editorconfig-checker",
|
||||||
|
"format": "prettier --write .",
|
||||||
|
"format:check": "prettier --check .",
|
||||||
|
"precommit": "pre-commit run --all-files",
|
||||||
|
"clean": "rm -rf src/parser.c src/grammar.json src/node-types.json",
|
||||||
|
"rebuild": "npm run clean && npm run generate"
|
||||||
|
},
|
||||||
|
"tree-sitter": [
|
||||||
|
{
|
||||||
|
"scope": "source.shellspec",
|
||||||
|
"file-types": [
|
||||||
|
"shellspec"
|
||||||
|
],
|
||||||
|
"path": ".",
|
||||||
|
"grammar-path": "grammar.js",
|
||||||
|
"highlights": "queries/highlights.scm"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"tree-sitter-bash": "^0.25.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"editorconfig-checker": "^6.1.1",
|
||||||
|
"markdownlint-cli": "^0.48.0",
|
||||||
|
"nodemon": "^3.0.1",
|
||||||
|
"prettier": "^3.6.2",
|
||||||
|
"tree-sitter-cli": "^0.26.6"
|
||||||
|
}
|
||||||
|
}
|
||||||
150
queries/highlights.scm
Normal file
150
queries/highlights.scm
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
; ShellSpec Syntax Highlighting
|
||||||
|
; Extends tree-sitter-bash highlighting
|
||||||
|
|
||||||
|
; Block keywords (BDD test structure)
|
||||||
|
[
|
||||||
|
"Describe"
|
||||||
|
"Context"
|
||||||
|
"ExampleGroup"
|
||||||
|
"It"
|
||||||
|
"Example"
|
||||||
|
"Specify"
|
||||||
|
] @keyword.function
|
||||||
|
|
||||||
|
; Focused blocks (for running specific tests)
|
||||||
|
[
|
||||||
|
"fDescribe"
|
||||||
|
"fContext"
|
||||||
|
"fExampleGroup"
|
||||||
|
"fIt"
|
||||||
|
"fExample"
|
||||||
|
"fSpecify"
|
||||||
|
] @keyword.function.focused
|
||||||
|
|
||||||
|
; Skipped blocks (for temporarily disabling tests)
|
||||||
|
[
|
||||||
|
"xDescribe"
|
||||||
|
"xContext"
|
||||||
|
"xExampleGroup"
|
||||||
|
"xIt"
|
||||||
|
"xExample"
|
||||||
|
"xSpecify"
|
||||||
|
] @keyword.function.skipped
|
||||||
|
|
||||||
|
; Hook keywords
|
||||||
|
[
|
||||||
|
"Before"
|
||||||
|
"After"
|
||||||
|
"BeforeAll"
|
||||||
|
"AfterAll"
|
||||||
|
"BeforeEach"
|
||||||
|
"AfterEach"
|
||||||
|
"BeforeRun"
|
||||||
|
"AfterRun"
|
||||||
|
"BeforeCall"
|
||||||
|
"AfterCall"
|
||||||
|
] @keyword.control.hook
|
||||||
|
|
||||||
|
; When/The/Assert keywords (core assertion DSL)
|
||||||
|
[
|
||||||
|
"When"
|
||||||
|
"The"
|
||||||
|
"Assert"
|
||||||
|
] @keyword
|
||||||
|
|
||||||
|
; When evaluation type keywords
|
||||||
|
[
|
||||||
|
"call"
|
||||||
|
"run"
|
||||||
|
"command"
|
||||||
|
"script"
|
||||||
|
"source"
|
||||||
|
] @keyword.operator
|
||||||
|
|
||||||
|
; The statement keywords
|
||||||
|
[
|
||||||
|
"should"
|
||||||
|
"not"
|
||||||
|
] @keyword.control
|
||||||
|
|
||||||
|
; Mock block keyword
|
||||||
|
[
|
||||||
|
"Mock"
|
||||||
|
] @keyword.function
|
||||||
|
|
||||||
|
; Utility blocks
|
||||||
|
[
|
||||||
|
"Data"
|
||||||
|
"Data:raw"
|
||||||
|
"Data:expand"
|
||||||
|
"Parameters"
|
||||||
|
"Parameters:block"
|
||||||
|
"Parameters:value"
|
||||||
|
"Parameters:matrix"
|
||||||
|
"Parameters:dynamic"
|
||||||
|
] @keyword.function.data
|
||||||
|
|
||||||
|
; Skip/Pending/Todo keywords
|
||||||
|
[
|
||||||
|
"Skip"
|
||||||
|
"Pending"
|
||||||
|
"Todo"
|
||||||
|
] @keyword.function.pending
|
||||||
|
|
||||||
|
; Statement keywords (Path/File/Dir, Set, Dump, Intercept)
|
||||||
|
[
|
||||||
|
"Path"
|
||||||
|
"File"
|
||||||
|
"Dir"
|
||||||
|
"Set"
|
||||||
|
"Dump"
|
||||||
|
"Intercept"
|
||||||
|
] @keyword
|
||||||
|
|
||||||
|
; Block terminator
|
||||||
|
[
|
||||||
|
"End"
|
||||||
|
] @keyword.control
|
||||||
|
|
||||||
|
; Directives
|
||||||
|
[
|
||||||
|
"Include"
|
||||||
|
] @keyword.directive
|
||||||
|
|
||||||
|
; % directives (text, const, output, preserve, logger)
|
||||||
|
[
|
||||||
|
"%text"
|
||||||
|
"%text:raw"
|
||||||
|
"%text:expand"
|
||||||
|
"%const"
|
||||||
|
"%"
|
||||||
|
"%puts"
|
||||||
|
"%putsn"
|
||||||
|
"%-"
|
||||||
|
"%="
|
||||||
|
"%preserve"
|
||||||
|
"%logger"
|
||||||
|
] @keyword.directive
|
||||||
|
|
||||||
|
; Comments (inherit from bash)
|
||||||
|
(comment) @comment
|
||||||
|
|
||||||
|
; Strings (inherit from bash)
|
||||||
|
(string) @string
|
||||||
|
(raw_string) @string
|
||||||
|
|
||||||
|
; Functions (inherit from bash)
|
||||||
|
(function_definition
|
||||||
|
name: (word) @function)
|
||||||
|
|
||||||
|
; Variables (inherit from bash)
|
||||||
|
(variable_name) @variable
|
||||||
|
|
||||||
|
; Operators (inherit from bash)
|
||||||
|
[
|
||||||
|
"&&"
|
||||||
|
"||"
|
||||||
|
"|"
|
||||||
|
";"
|
||||||
|
"&"
|
||||||
|
] @operator
|
||||||
39
scripts/post-generate.sh
Executable file
39
scripts/post-generate.sh
Executable file
@@ -0,0 +1,39 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# post-generate.sh — Patches generated tree-sitter files after `tree-sitter generate`.
|
||||||
|
#
|
||||||
|
# The tree-sitter CLI generates src/tree_sitter/parser.h without a bounds check
|
||||||
|
# in the `set_contains` function, which can read past an empty array. Until this
|
||||||
|
# is fixed upstream, this script injects an early `len == 0` guard after every
|
||||||
|
# generation pass. The "generate" npm script calls this automatically.
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "Running post-generation fixes..."
|
||||||
|
|
||||||
|
# Apply critical safety fixes that get overwritten during generation
|
||||||
|
echo " - Applying critical safety fixes..."
|
||||||
|
|
||||||
|
# Fix 1: Buffer overflow prevention in parser.h
|
||||||
|
# The set_contains function needs a len==0 check to prevent accessing ranges[0]
|
||||||
|
# This fix gets overwritten every time tree-sitter generate runs
|
||||||
|
if ! grep -q "if (len == 0) return false;" src/tree_sitter/parser.h; then
|
||||||
|
# Insert the safety check right after the function opening
|
||||||
|
# Target: static inline bool set_contains(...) {
|
||||||
|
# Insert: if (len == 0) return false;
|
||||||
|
# Before: uint32_t index = 0;
|
||||||
|
|
||||||
|
# Use perl for cross-platform compatibility (macOS and Linux)
|
||||||
|
perl -i -pe '
|
||||||
|
BEGIN { $in_func = 0; $done = 0; }
|
||||||
|
if (/static inline bool set_contains/) { $in_func = 1; }
|
||||||
|
if ($in_func && /^\s+uint32_t index = 0;/ && !$done) {
|
||||||
|
print " if (len == 0) return false;\n";
|
||||||
|
$done = 1;
|
||||||
|
}
|
||||||
|
if (/^}/ && $in_func) { $in_func = 0; }
|
||||||
|
' src/tree_sitter/parser.h
|
||||||
|
echo " ✓ Applied buffer overflow fix to parser.h"
|
||||||
|
else
|
||||||
|
echo " ✓ Buffer overflow fix already present"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Post-generation fixes complete!"
|
||||||
9020
src/grammar.json
Normal file
9020
src/grammar.json
Normal file
File diff suppressed because it is too large
Load Diff
4157
src/node-types.json
Normal file
4157
src/node-types.json
Normal file
File diff suppressed because it is too large
Load Diff
450536
src/parser.c
Normal file
450536
src/parser.c
Normal file
File diff suppressed because it is too large
Load Diff
1291
src/scanner.c
Normal file
1291
src/scanner.c
Normal file
File diff suppressed because it is too large
Load Diff
9
src/tree_sitter/alloc.c
Normal file
9
src/tree_sitter/alloc.c
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#include "tree_sitter/alloc.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#ifdef TREE_SITTER_REUSE_ALLOCATOR
|
||||||
|
void *(*ts_current_malloc)(size_t) = malloc;
|
||||||
|
void *(*ts_current_calloc)(size_t,size_t) = calloc;
|
||||||
|
void *(*ts_current_realloc)(void*,size_t) = realloc;
|
||||||
|
void (*ts_current_free)(void*) = free;
|
||||||
|
#endif
|
||||||
54
src/tree_sitter/alloc.h
Normal file
54
src/tree_sitter/alloc.h
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
#ifndef TREE_SITTER_ALLOC_H_
|
||||||
|
#define TREE_SITTER_ALLOC_H_
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
// Allow clients to override allocation functions
|
||||||
|
#ifdef TREE_SITTER_REUSE_ALLOCATOR
|
||||||
|
|
||||||
|
extern void *(*ts_current_malloc)(size_t size);
|
||||||
|
extern void *(*ts_current_calloc)(size_t count, size_t size);
|
||||||
|
extern void *(*ts_current_realloc)(void *ptr, size_t size);
|
||||||
|
extern void (*ts_current_free)(void *ptr);
|
||||||
|
|
||||||
|
#ifndef ts_malloc
|
||||||
|
#define ts_malloc ts_current_malloc
|
||||||
|
#endif
|
||||||
|
#ifndef ts_calloc
|
||||||
|
#define ts_calloc ts_current_calloc
|
||||||
|
#endif
|
||||||
|
#ifndef ts_realloc
|
||||||
|
#define ts_realloc ts_current_realloc
|
||||||
|
#endif
|
||||||
|
#ifndef ts_free
|
||||||
|
#define ts_free ts_current_free
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#ifndef ts_malloc
|
||||||
|
#define ts_malloc malloc
|
||||||
|
#endif
|
||||||
|
#ifndef ts_calloc
|
||||||
|
#define ts_calloc calloc
|
||||||
|
#endif
|
||||||
|
#ifndef ts_realloc
|
||||||
|
#define ts_realloc realloc
|
||||||
|
#endif
|
||||||
|
#ifndef ts_free
|
||||||
|
#define ts_free free
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // TREE_SITTER_ALLOC_H_
|
||||||
330
src/tree_sitter/array.h
Normal file
330
src/tree_sitter/array.h
Normal file
@@ -0,0 +1,330 @@
|
|||||||
|
#ifndef TREE_SITTER_ARRAY_H_
|
||||||
|
#define TREE_SITTER_ARRAY_H_
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "./alloc.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#pragma warning(push)
|
||||||
|
#pragma warning(disable : 4101)
|
||||||
|
#elif defined(__GNUC__) || defined(__clang__)
|
||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wunused-variable"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define Array(T) \
|
||||||
|
struct { \
|
||||||
|
T *contents; \
|
||||||
|
uint32_t size; \
|
||||||
|
uint32_t capacity; \
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialize an array.
|
||||||
|
#define array_init(self) \
|
||||||
|
((self)->size = 0, (self)->capacity = 0, (self)->contents = NULL)
|
||||||
|
|
||||||
|
/// Create an empty array.
|
||||||
|
#define array_new() \
|
||||||
|
{ NULL, 0, 0 }
|
||||||
|
|
||||||
|
/// Get a pointer to the element at a given `index` in the array.
|
||||||
|
#define array_get(self, _index) \
|
||||||
|
(assert((uint32_t)(_index) < (self)->size), &(self)->contents[_index])
|
||||||
|
|
||||||
|
/// Get a pointer to the first element in the array.
|
||||||
|
#define array_front(self) array_get(self, 0)
|
||||||
|
|
||||||
|
/// Get a pointer to the last element in the array.
|
||||||
|
#define array_back(self) array_get(self, (self)->size - 1)
|
||||||
|
|
||||||
|
/// Clear the array, setting its size to zero. Note that this does not free any
|
||||||
|
/// memory allocated for the array's contents.
|
||||||
|
#define array_clear(self) ((self)->size = 0)
|
||||||
|
|
||||||
|
/// Reserve `new_capacity` elements of space in the array. If `new_capacity` is
|
||||||
|
/// less than the array's current capacity, this function has no effect.
|
||||||
|
#define array_reserve(self, new_capacity) \
|
||||||
|
((self)->contents = _array__reserve( \
|
||||||
|
(void *)(self)->contents, &(self)->capacity, \
|
||||||
|
array_elem_size(self), new_capacity) \
|
||||||
|
)
|
||||||
|
|
||||||
|
/// Free any memory allocated for this array. Note that this does not free any
|
||||||
|
/// memory allocated for the array's contents.
|
||||||
|
#define array_delete(self) \
|
||||||
|
do { \
|
||||||
|
if ((self)->contents) ts_free((self)->contents); \
|
||||||
|
(self)->contents = NULL; \
|
||||||
|
(self)->size = 0; \
|
||||||
|
(self)->capacity = 0; \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/// Push a new `element` onto the end of the array.
|
||||||
|
#define array_push(self, element) \
|
||||||
|
do { \
|
||||||
|
(self)->contents = _array__grow( \
|
||||||
|
(void *)(self)->contents, (self)->size, &(self)->capacity, \
|
||||||
|
1, array_elem_size(self) \
|
||||||
|
); \
|
||||||
|
(self)->contents[(self)->size++] = (element); \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
/// Increase the array's size by `count` elements.
|
||||||
|
/// New elements are zero-initialized.
|
||||||
|
#define array_grow_by(self, count) \
|
||||||
|
do { \
|
||||||
|
if ((count) == 0) break; \
|
||||||
|
(self)->contents = _array__grow( \
|
||||||
|
(self)->contents, (self)->size, &(self)->capacity, \
|
||||||
|
count, array_elem_size(self) \
|
||||||
|
); \
|
||||||
|
memset((self)->contents + (self)->size, 0, (count) * array_elem_size(self)); \
|
||||||
|
(self)->size += (count); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/// Append all elements from one array to the end of another.
|
||||||
|
#define array_push_all(self, other) \
|
||||||
|
array_extend((self), (other)->size, (other)->contents)
|
||||||
|
|
||||||
|
/// Append `count` elements to the end of the array, reading their values from the
|
||||||
|
/// `contents` pointer.
|
||||||
|
#define array_extend(self, count, other_contents) \
|
||||||
|
(self)->contents = _array__splice( \
|
||||||
|
(void*)(self)->contents, &(self)->size, &(self)->capacity, \
|
||||||
|
array_elem_size(self), (self)->size, 0, count, other_contents \
|
||||||
|
)
|
||||||
|
|
||||||
|
/// Remove `old_count` elements from the array starting at the given `index`. At
|
||||||
|
/// the same index, insert `new_count` new elements, reading their values from the
|
||||||
|
/// `new_contents` pointer.
|
||||||
|
#define array_splice(self, _index, old_count, new_count, new_contents) \
|
||||||
|
(self)->contents = _array__splice( \
|
||||||
|
(void *)(self)->contents, &(self)->size, &(self)->capacity, \
|
||||||
|
array_elem_size(self), _index, old_count, new_count, new_contents \
|
||||||
|
)
|
||||||
|
|
||||||
|
/// Insert one `element` into the array at the given `index`.
|
||||||
|
#define array_insert(self, _index, element) \
|
||||||
|
(self)->contents = _array__splice( \
|
||||||
|
(void *)(self)->contents, &(self)->size, &(self)->capacity, \
|
||||||
|
array_elem_size(self), _index, 0, 1, &(element) \
|
||||||
|
)
|
||||||
|
|
||||||
|
/// Remove one element from the array at the given `index`.
|
||||||
|
#define array_erase(self, _index) \
|
||||||
|
_array__erase((void *)(self)->contents, &(self)->size, array_elem_size(self), _index)
|
||||||
|
|
||||||
|
/// Pop the last element off the array, returning the element by value.
|
||||||
|
#define array_pop(self) ((self)->contents[--(self)->size])
|
||||||
|
|
||||||
|
/// Assign the contents of one array to another, reallocating if necessary.
|
||||||
|
#define array_assign(self, other) \
|
||||||
|
(self)->contents = _array__assign( \
|
||||||
|
(void *)(self)->contents, &(self)->size, &(self)->capacity, \
|
||||||
|
(const void *)(other)->contents, (other)->size, array_elem_size(self) \
|
||||||
|
)
|
||||||
|
|
||||||
|
/// Swap one array with another
|
||||||
|
#define array_swap(self, other) \
|
||||||
|
do { \
|
||||||
|
void *_array_swap_tmp = (void *)(self)->contents; \
|
||||||
|
(self)->contents = (other)->contents; \
|
||||||
|
(other)->contents = _array_swap_tmp; \
|
||||||
|
_array__swap(&(self)->size, &(self)->capacity, \
|
||||||
|
&(other)->size, &(other)->capacity); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/// Get the size of the array contents
|
||||||
|
#define array_elem_size(self) (sizeof *(self)->contents)
|
||||||
|
|
||||||
|
/// Search a sorted array for a given `needle` value, using the given `compare`
|
||||||
|
/// callback to determine the order.
|
||||||
|
///
|
||||||
|
/// If an existing element is found to be equal to `needle`, then the `index`
|
||||||
|
/// out-parameter is set to the existing value's index, and the `exists`
|
||||||
|
/// out-parameter is set to true. Otherwise, `index` is set to an index where
|
||||||
|
/// `needle` should be inserted in order to preserve the sorting, and `exists`
|
||||||
|
/// is set to false.
|
||||||
|
#define array_search_sorted_with(self, compare, needle, _index, _exists) \
|
||||||
|
_array__search_sorted(self, 0, compare, , needle, _index, _exists)
|
||||||
|
|
||||||
|
/// Search a sorted array for a given `needle` value, using integer comparisons
|
||||||
|
/// of a given struct field (specified with a leading dot) to determine the order.
|
||||||
|
///
|
||||||
|
/// See also `array_search_sorted_with`.
|
||||||
|
#define array_search_sorted_by(self, field, needle, _index, _exists) \
|
||||||
|
_array__search_sorted(self, 0, _compare_int, field, needle, _index, _exists)
|
||||||
|
|
||||||
|
/// Insert a given `value` into a sorted array, using the given `compare`
|
||||||
|
/// callback to determine the order.
|
||||||
|
#define array_insert_sorted_with(self, compare, value) \
|
||||||
|
do { \
|
||||||
|
unsigned _index, _exists; \
|
||||||
|
array_search_sorted_with(self, compare, &(value), &_index, &_exists); \
|
||||||
|
if (!_exists) array_insert(self, _index, value); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/// Insert a given `value` into a sorted array, using integer comparisons of
|
||||||
|
/// a given struct field (specified with a leading dot) to determine the order.
|
||||||
|
///
|
||||||
|
/// See also `array_search_sorted_by`.
|
||||||
|
#define array_insert_sorted_by(self, field, value) \
|
||||||
|
do { \
|
||||||
|
unsigned _index, _exists; \
|
||||||
|
array_search_sorted_by(self, field, (value) field, &_index, &_exists); \
|
||||||
|
if (!_exists) array_insert(self, _index, value); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
// Private
|
||||||
|
|
||||||
|
// Pointers to individual `Array` fields (rather than the entire `Array` itself)
|
||||||
|
// are passed to the various `_array__*` functions below to address strict aliasing
|
||||||
|
// violations that arises when the _entire_ `Array` struct is passed as `Array(void)*`.
|
||||||
|
//
|
||||||
|
// The `Array` type itself was not altered as a solution in order to avoid breakage
|
||||||
|
// with existing consumers (in particular, parsers with external scanners).
|
||||||
|
|
||||||
|
/// This is not what you're looking for, see `array_erase`.
|
||||||
|
static inline void _array__erase(void* self_contents, uint32_t *size,
|
||||||
|
size_t element_size, uint32_t index) {
|
||||||
|
assert(index < *size);
|
||||||
|
char *contents = (char *)self_contents;
|
||||||
|
memmove(contents + index * element_size, contents + (index + 1) * element_size,
|
||||||
|
(*size - index - 1) * element_size);
|
||||||
|
(*size)--;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is not what you're looking for, see `array_reserve`.
|
||||||
|
static inline void *_array__reserve(void *contents, uint32_t *capacity,
|
||||||
|
size_t element_size, uint32_t new_capacity) {
|
||||||
|
void *new_contents = contents;
|
||||||
|
if (new_capacity > *capacity) {
|
||||||
|
if (contents) {
|
||||||
|
new_contents = ts_realloc(contents, new_capacity * element_size);
|
||||||
|
} else {
|
||||||
|
new_contents = ts_malloc(new_capacity * element_size);
|
||||||
|
}
|
||||||
|
*capacity = new_capacity;
|
||||||
|
}
|
||||||
|
return new_contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is not what you're looking for, see `array_assign`.
|
||||||
|
static inline void *_array__assign(void* self_contents, uint32_t *self_size, uint32_t *self_capacity,
|
||||||
|
const void *other_contents, uint32_t other_size, size_t element_size) {
|
||||||
|
void *new_contents = _array__reserve(self_contents, self_capacity, element_size, other_size);
|
||||||
|
*self_size = other_size;
|
||||||
|
memcpy(new_contents, other_contents, *self_size * element_size);
|
||||||
|
return new_contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is not what you're looking for, see `array_swap`.
|
||||||
|
static inline void _array__swap(uint32_t *self_size, uint32_t *self_capacity,
|
||||||
|
uint32_t *other_size, uint32_t *other_capacity) {
|
||||||
|
uint32_t tmp_size = *self_size;
|
||||||
|
uint32_t tmp_capacity = *self_capacity;
|
||||||
|
*self_size = *other_size;
|
||||||
|
*self_capacity = *other_capacity;
|
||||||
|
*other_size = tmp_size;
|
||||||
|
*other_capacity = tmp_capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is not what you're looking for, see `array_push` or `array_grow_by`.
|
||||||
|
static inline void *_array__grow(void *contents, uint32_t size, uint32_t *capacity,
|
||||||
|
uint32_t count, size_t element_size) {
|
||||||
|
void *new_contents = contents;
|
||||||
|
uint32_t new_size = size + count;
|
||||||
|
if (new_size > *capacity) {
|
||||||
|
uint32_t new_capacity = *capacity * 2;
|
||||||
|
if (new_capacity < 8) new_capacity = 8;
|
||||||
|
if (new_capacity < new_size) new_capacity = new_size;
|
||||||
|
new_contents = _array__reserve(contents, capacity, element_size, new_capacity);
|
||||||
|
}
|
||||||
|
return new_contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This is not what you're looking for, see `array_splice`.
|
||||||
|
static inline void *_array__splice(void *self_contents, uint32_t *size, uint32_t *capacity,
|
||||||
|
size_t element_size,
|
||||||
|
uint32_t index, uint32_t old_count,
|
||||||
|
uint32_t new_count, const void *elements) {
|
||||||
|
uint32_t new_size = *size + new_count - old_count;
|
||||||
|
uint32_t old_end = index + old_count;
|
||||||
|
uint32_t new_end = index + new_count;
|
||||||
|
assert(old_end <= *size);
|
||||||
|
|
||||||
|
void *new_contents = _array__reserve(self_contents, capacity, element_size, new_size);
|
||||||
|
|
||||||
|
char *contents = (char *)new_contents;
|
||||||
|
if (*size > old_end) {
|
||||||
|
memmove(
|
||||||
|
contents + new_end * element_size,
|
||||||
|
contents + old_end * element_size,
|
||||||
|
(*size - old_end) * element_size
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (new_count > 0) {
|
||||||
|
if (elements) {
|
||||||
|
memcpy(
|
||||||
|
(contents + index * element_size),
|
||||||
|
elements,
|
||||||
|
new_count * element_size
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
memset(
|
||||||
|
(contents + index * element_size),
|
||||||
|
0,
|
||||||
|
new_count * element_size
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*size += new_count - old_count;
|
||||||
|
|
||||||
|
return new_contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A binary search routine, based on Rust's `std::slice::binary_search_by`.
|
||||||
|
/// This is not what you're looking for, see `array_search_sorted_with` or `array_search_sorted_by`.
|
||||||
|
#define _array__search_sorted(self, start, compare, suffix, needle, _index, _exists) \
|
||||||
|
do { \
|
||||||
|
*(_index) = start; \
|
||||||
|
*(_exists) = false; \
|
||||||
|
uint32_t size = (self)->size - *(_index); \
|
||||||
|
if (size == 0) break; \
|
||||||
|
int comparison; \
|
||||||
|
while (size > 1) { \
|
||||||
|
uint32_t half_size = size / 2; \
|
||||||
|
uint32_t mid_index = *(_index) + half_size; \
|
||||||
|
comparison = compare(&((self)->contents[mid_index] suffix), (needle)); \
|
||||||
|
if (comparison <= 0) *(_index) = mid_index; \
|
||||||
|
size -= half_size; \
|
||||||
|
} \
|
||||||
|
comparison = compare(&((self)->contents[*(_index)] suffix), (needle)); \
|
||||||
|
if (comparison == 0) *(_exists) = true; \
|
||||||
|
else if (comparison < 0) *(_index) += 1; \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
/// Helper macro for the `_sorted_by` routines below. This takes the left (existing)
|
||||||
|
/// parameter by reference in order to work with the generic sorting function above.
|
||||||
|
#define _compare_int(a, b) ((int)*(a) - (int)(b))
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#pragma warning(pop)
|
||||||
|
#elif defined(__GNUC__) || defined(__clang__)
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // TREE_SITTER_ARRAY_H_
|
||||||
287
src/tree_sitter/parser.h
Normal file
287
src/tree_sitter/parser.h
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
#ifndef TREE_SITTER_PARSER_H_
|
||||||
|
#define TREE_SITTER_PARSER_H_
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#define ts_builtin_sym_error ((TSSymbol)-1)
|
||||||
|
#define ts_builtin_sym_end 0
|
||||||
|
#define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024
|
||||||
|
|
||||||
|
#ifndef TREE_SITTER_API_H_
|
||||||
|
typedef uint16_t TSStateId;
|
||||||
|
typedef uint16_t TSSymbol;
|
||||||
|
typedef uint16_t TSFieldId;
|
||||||
|
typedef struct TSLanguage TSLanguage;
|
||||||
|
typedef struct TSLanguageMetadata {
|
||||||
|
uint8_t major_version;
|
||||||
|
uint8_t minor_version;
|
||||||
|
uint8_t patch_version;
|
||||||
|
} TSLanguageMetadata;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
TSFieldId field_id;
|
||||||
|
uint8_t child_index;
|
||||||
|
bool inherited;
|
||||||
|
} TSFieldMapEntry;
|
||||||
|
|
||||||
|
// Used to index the field and supertype maps.
|
||||||
|
typedef struct {
|
||||||
|
uint16_t index;
|
||||||
|
uint16_t length;
|
||||||
|
} TSMapSlice;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool visible;
|
||||||
|
bool named;
|
||||||
|
bool supertype;
|
||||||
|
} TSSymbolMetadata;
|
||||||
|
|
||||||
|
typedef struct TSLexer TSLexer;
|
||||||
|
|
||||||
|
struct TSLexer {
|
||||||
|
int32_t lookahead;
|
||||||
|
TSSymbol result_symbol;
|
||||||
|
void (*advance)(TSLexer *, bool);
|
||||||
|
void (*mark_end)(TSLexer *);
|
||||||
|
uint32_t (*get_column)(TSLexer *);
|
||||||
|
bool (*is_at_included_range_start)(const TSLexer *);
|
||||||
|
bool (*eof)(const TSLexer *);
|
||||||
|
void (*log)(const TSLexer *, const char *, ...);
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
TSParseActionTypeShift,
|
||||||
|
TSParseActionTypeReduce,
|
||||||
|
TSParseActionTypeAccept,
|
||||||
|
TSParseActionTypeRecover,
|
||||||
|
} TSParseActionType;
|
||||||
|
|
||||||
|
typedef union {
|
||||||
|
struct {
|
||||||
|
uint8_t type;
|
||||||
|
TSStateId state;
|
||||||
|
bool extra;
|
||||||
|
bool repetition;
|
||||||
|
} shift;
|
||||||
|
struct {
|
||||||
|
uint8_t type;
|
||||||
|
uint8_t child_count;
|
||||||
|
TSSymbol symbol;
|
||||||
|
int16_t dynamic_precedence;
|
||||||
|
uint16_t production_id;
|
||||||
|
} reduce;
|
||||||
|
uint8_t type;
|
||||||
|
} TSParseAction;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint16_t lex_state;
|
||||||
|
uint16_t external_lex_state;
|
||||||
|
} TSLexMode;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint16_t lex_state;
|
||||||
|
uint16_t external_lex_state;
|
||||||
|
uint16_t reserved_word_set_id;
|
||||||
|
} TSLexerMode;
|
||||||
|
|
||||||
|
typedef union {
|
||||||
|
TSParseAction action;
|
||||||
|
struct {
|
||||||
|
uint8_t count;
|
||||||
|
bool reusable;
|
||||||
|
} entry;
|
||||||
|
} TSParseActionEntry;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int32_t start;
|
||||||
|
int32_t end;
|
||||||
|
} TSCharacterRange;
|
||||||
|
|
||||||
|
struct TSLanguage {
|
||||||
|
uint32_t abi_version;
|
||||||
|
uint32_t symbol_count;
|
||||||
|
uint32_t alias_count;
|
||||||
|
uint32_t token_count;
|
||||||
|
uint32_t external_token_count;
|
||||||
|
uint32_t state_count;
|
||||||
|
uint32_t large_state_count;
|
||||||
|
uint32_t production_id_count;
|
||||||
|
uint32_t field_count;
|
||||||
|
uint16_t max_alias_sequence_length;
|
||||||
|
const uint16_t *parse_table;
|
||||||
|
const uint16_t *small_parse_table;
|
||||||
|
const uint32_t *small_parse_table_map;
|
||||||
|
const TSParseActionEntry *parse_actions;
|
||||||
|
const char * const *symbol_names;
|
||||||
|
const char * const *field_names;
|
||||||
|
const TSMapSlice *field_map_slices;
|
||||||
|
const TSFieldMapEntry *field_map_entries;
|
||||||
|
const TSSymbolMetadata *symbol_metadata;
|
||||||
|
const TSSymbol *public_symbol_map;
|
||||||
|
const uint16_t *alias_map;
|
||||||
|
const TSSymbol *alias_sequences;
|
||||||
|
const TSLexerMode *lex_modes;
|
||||||
|
bool (*lex_fn)(TSLexer *, TSStateId);
|
||||||
|
bool (*keyword_lex_fn)(TSLexer *, TSStateId);
|
||||||
|
TSSymbol keyword_capture_token;
|
||||||
|
struct {
|
||||||
|
const bool *states;
|
||||||
|
const TSSymbol *symbol_map;
|
||||||
|
void *(*create)(void);
|
||||||
|
void (*destroy)(void *);
|
||||||
|
bool (*scan)(void *, TSLexer *, const bool *symbol_whitelist);
|
||||||
|
unsigned (*serialize)(void *, char *);
|
||||||
|
void (*deserialize)(void *, const char *, unsigned);
|
||||||
|
} external_scanner;
|
||||||
|
const TSStateId *primary_state_ids;
|
||||||
|
const char *name;
|
||||||
|
const TSSymbol *reserved_words;
|
||||||
|
uint16_t max_reserved_word_set_size;
|
||||||
|
uint32_t supertype_count;
|
||||||
|
const TSSymbol *supertype_symbols;
|
||||||
|
const TSMapSlice *supertype_map_slices;
|
||||||
|
const TSSymbol *supertype_map_entries;
|
||||||
|
TSLanguageMetadata metadata;
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline bool set_contains(const TSCharacterRange *ranges, uint32_t len, int32_t lookahead) {
|
||||||
|
if (len == 0) return false;
|
||||||
|
uint32_t index = 0;
|
||||||
|
uint32_t size = len - index;
|
||||||
|
while (size > 1) {
|
||||||
|
uint32_t half_size = size / 2;
|
||||||
|
uint32_t mid_index = index + half_size;
|
||||||
|
const TSCharacterRange *range = &ranges[mid_index];
|
||||||
|
if (lookahead >= range->start && lookahead <= range->end) {
|
||||||
|
return true;
|
||||||
|
} else if (lookahead > range->end) {
|
||||||
|
index = mid_index;
|
||||||
|
}
|
||||||
|
size -= half_size;
|
||||||
|
}
|
||||||
|
const TSCharacterRange *range = &ranges[index];
|
||||||
|
return (lookahead >= range->start && lookahead <= range->end);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Lexer Macros
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#define UNUSED __pragma(warning(suppress : 4101))
|
||||||
|
#else
|
||||||
|
#define UNUSED __attribute__((unused))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define START_LEXER() \
|
||||||
|
bool result = false; \
|
||||||
|
bool skip = false; \
|
||||||
|
UNUSED \
|
||||||
|
bool eof = false; \
|
||||||
|
int32_t lookahead; \
|
||||||
|
goto start; \
|
||||||
|
next_state: \
|
||||||
|
lexer->advance(lexer, skip); \
|
||||||
|
start: \
|
||||||
|
skip = false; \
|
||||||
|
lookahead = lexer->lookahead;
|
||||||
|
|
||||||
|
#define ADVANCE(state_value) \
|
||||||
|
{ \
|
||||||
|
state = state_value; \
|
||||||
|
goto next_state; \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define ADVANCE_MAP(...) \
|
||||||
|
{ \
|
||||||
|
static const uint16_t map[] = { __VA_ARGS__ }; \
|
||||||
|
for (uint32_t i = 0; i < sizeof(map) / sizeof(map[0]); i += 2) { \
|
||||||
|
if (map[i] == lookahead) { \
|
||||||
|
state = map[i + 1]; \
|
||||||
|
goto next_state; \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define SKIP(state_value) \
|
||||||
|
{ \
|
||||||
|
skip = true; \
|
||||||
|
state = state_value; \
|
||||||
|
goto next_state; \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define ACCEPT_TOKEN(symbol_value) \
|
||||||
|
result = true; \
|
||||||
|
lexer->result_symbol = symbol_value; \
|
||||||
|
lexer->mark_end(lexer);
|
||||||
|
|
||||||
|
#define END_STATE() return result;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Parse Table Macros
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define SMALL_STATE(id) ((id) - LARGE_STATE_COUNT)
|
||||||
|
|
||||||
|
#define STATE(id) id
|
||||||
|
|
||||||
|
#define ACTIONS(id) id
|
||||||
|
|
||||||
|
#define SHIFT(state_value) \
|
||||||
|
{{ \
|
||||||
|
.shift = { \
|
||||||
|
.type = TSParseActionTypeShift, \
|
||||||
|
.state = (state_value) \
|
||||||
|
} \
|
||||||
|
}}
|
||||||
|
|
||||||
|
#define SHIFT_REPEAT(state_value) \
|
||||||
|
{{ \
|
||||||
|
.shift = { \
|
||||||
|
.type = TSParseActionTypeShift, \
|
||||||
|
.state = (state_value), \
|
||||||
|
.repetition = true \
|
||||||
|
} \
|
||||||
|
}}
|
||||||
|
|
||||||
|
#define SHIFT_EXTRA() \
|
||||||
|
{{ \
|
||||||
|
.shift = { \
|
||||||
|
.type = TSParseActionTypeShift, \
|
||||||
|
.extra = true \
|
||||||
|
} \
|
||||||
|
}}
|
||||||
|
|
||||||
|
#define REDUCE(symbol_name, children, precedence, prod_id) \
|
||||||
|
{{ \
|
||||||
|
.reduce = { \
|
||||||
|
.type = TSParseActionTypeReduce, \
|
||||||
|
.symbol = symbol_name, \
|
||||||
|
.child_count = children, \
|
||||||
|
.dynamic_precedence = precedence, \
|
||||||
|
.production_id = prod_id \
|
||||||
|
}, \
|
||||||
|
}}
|
||||||
|
|
||||||
|
#define RECOVER() \
|
||||||
|
{{ \
|
||||||
|
.type = TSParseActionTypeRecover \
|
||||||
|
}}
|
||||||
|
|
||||||
|
#define ACCEPT_INPUT() \
|
||||||
|
{{ \
|
||||||
|
.type = TSParseActionTypeAccept \
|
||||||
|
}}
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // TREE_SITTER_PARSER_H_
|
||||||
131
test/corpus/context_blocks.txt
Normal file
131
test/corpus/context_blocks.txt
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
================================================================================
|
||||||
|
Basic Context block
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Context "when condition is true"
|
||||||
|
echo "test"
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_context_block
|
||||||
|
description: (string
|
||||||
|
(string_content))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))
|
||||||
|
argument: (string
|
||||||
|
(string_content)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
ExampleGroup block
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
ExampleGroup "group of examples"
|
||||||
|
echo "test"
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_context_block
|
||||||
|
description: (string
|
||||||
|
(string_content))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))
|
||||||
|
argument: (string
|
||||||
|
(string_content)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Focused Context block
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
fContext "focused context"
|
||||||
|
echo "focused"
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_context_block
|
||||||
|
description: (string
|
||||||
|
(string_content))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))
|
||||||
|
argument: (string
|
||||||
|
(string_content)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Skipped Context block
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
xContext "skipped context"
|
||||||
|
echo "skipped"
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_context_block
|
||||||
|
description: (string
|
||||||
|
(string_content))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))
|
||||||
|
argument: (string
|
||||||
|
(string_content)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Context with raw string
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Context 'raw string context'
|
||||||
|
local var="test"
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_context_block
|
||||||
|
description: (raw_string)
|
||||||
|
(declaration_command
|
||||||
|
(variable_assignment
|
||||||
|
name: (variable_name)
|
||||||
|
value: (string
|
||||||
|
(string_content))))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Context with word description
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Context simple_context
|
||||||
|
echo "test"
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_context_block
|
||||||
|
description: (word)
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))
|
||||||
|
argument: (string
|
||||||
|
(string_content)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Empty Context block
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Context "empty context"
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_context_block
|
||||||
|
description: (string
|
||||||
|
(string_content))))
|
||||||
143
test/corpus/describe_blocks.txt
Normal file
143
test/corpus/describe_blocks.txt
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
================================================================================
|
||||||
|
Basic Describe block
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Describe "basic functionality"
|
||||||
|
echo "test"
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_describe_block
|
||||||
|
description: (string
|
||||||
|
(string_content))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))
|
||||||
|
argument: (string
|
||||||
|
(string_content)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Focused Describe block
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
fDescribe "focused test"
|
||||||
|
echo "focused"
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_describe_block
|
||||||
|
description: (string
|
||||||
|
(string_content))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))
|
||||||
|
argument: (string
|
||||||
|
(string_content)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Skipped Describe block
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
xDescribe "skipped test"
|
||||||
|
echo "skipped"
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_describe_block
|
||||||
|
description: (string
|
||||||
|
(string_content))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))
|
||||||
|
argument: (string
|
||||||
|
(string_content)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Describe with raw string
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Describe 'raw string test'
|
||||||
|
echo "test"
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_describe_block
|
||||||
|
description: (raw_string)
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))
|
||||||
|
argument: (string
|
||||||
|
(string_content)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Describe with word description
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Describe simple_test
|
||||||
|
echo "test"
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_describe_block
|
||||||
|
description: (word)
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))
|
||||||
|
argument: (string
|
||||||
|
(string_content)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Empty Describe block
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Describe "empty test"
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_describe_block
|
||||||
|
description: (string
|
||||||
|
(string_content))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Describe with multiple statements
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Describe "multiple statements"
|
||||||
|
echo "first"
|
||||||
|
echo "second"
|
||||||
|
local var="value"
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_describe_block
|
||||||
|
description: (string
|
||||||
|
(string_content))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))
|
||||||
|
argument: (string
|
||||||
|
(string_content)))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))
|
||||||
|
argument: (string
|
||||||
|
(string_content)))
|
||||||
|
(declaration_command
|
||||||
|
(variable_assignment
|
||||||
|
name: (variable_name)
|
||||||
|
value: (string
|
||||||
|
(string_content))))))
|
||||||
291
test/corpus/hook_blocks.txt
Normal file
291
test/corpus/hook_blocks.txt
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
================================================================================
|
||||||
|
BeforeEach hook
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
BeforeEach
|
||||||
|
setup_environment
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_hook_block
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
BeforeEach with label
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
BeforeEach "setup database"
|
||||||
|
init_database
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_hook_block
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(string
|
||||||
|
(string_content))))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
AfterEach hook
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
AfterEach
|
||||||
|
cleanup_environment
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_hook_block
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
AfterEach with label
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
AfterEach "cleanup database"
|
||||||
|
cleanup_database
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_hook_block
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(string
|
||||||
|
(string_content))))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
BeforeAll hook
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
BeforeAll
|
||||||
|
global_setup
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_hook_block
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
BeforeAll with raw string label
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
BeforeAll 'global setup'
|
||||||
|
global_setup
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_hook_block
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(raw_string)))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
AfterAll hook
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
AfterAll
|
||||||
|
global_cleanup
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_hook_block
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
BeforeCall hook
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
BeforeCall
|
||||||
|
prepare_call
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_hook_block
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
AfterCall hook
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
AfterCall
|
||||||
|
verify_call
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_hook_block
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
BeforeRun hook
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
BeforeRun
|
||||||
|
prepare_run
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_hook_block
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
AfterRun hook
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
AfterRun
|
||||||
|
verify_run
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_hook_block
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Hook with multiple statements
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
BeforeEach "complex setup"
|
||||||
|
export TEST_VAR="value"
|
||||||
|
mkdir -p /tmp/test
|
||||||
|
touch /tmp/test/file
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_hook_block
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(string
|
||||||
|
(string_content))))
|
||||||
|
(declaration_command
|
||||||
|
(variable_assignment
|
||||||
|
name: (variable_name)
|
||||||
|
value: (string
|
||||||
|
(string_content))))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))
|
||||||
|
argument: (word)
|
||||||
|
argument: (word))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))
|
||||||
|
argument: (word))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
BeforeRun standalone statement
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
BeforeRun my_setup
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_hook_statement
|
||||||
|
argument: (word)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
AfterRun standalone statement
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
AfterRun my_cleanup
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_hook_statement
|
||||||
|
argument: (word)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
BeforeCall standalone statement
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
BeforeCall pre_call
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_hook_statement
|
||||||
|
argument: (word)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
AfterCall standalone statement
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
AfterCall post_call
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_hook_statement
|
||||||
|
argument: (word)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
BeforeEach standalone statement
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
BeforeEach setup_func
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_hook_statement
|
||||||
|
argument: (word)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
AfterEach standalone statement
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
AfterEach cleanup_func
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_hook_statement
|
||||||
|
argument: (word)))
|
||||||
213
test/corpus/it_blocks.txt
Normal file
213
test/corpus/it_blocks.txt
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
================================================================================
|
||||||
|
Basic It block
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
It "should work correctly"
|
||||||
|
echo "test"
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_it_block
|
||||||
|
description: (string
|
||||||
|
(string_content))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))
|
||||||
|
argument: (string
|
||||||
|
(string_content)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Example block
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Example "example behavior"
|
||||||
|
echo "example"
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_it_block
|
||||||
|
description: (string
|
||||||
|
(string_content))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))
|
||||||
|
argument: (string
|
||||||
|
(string_content)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Specify block
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Specify "specific behavior"
|
||||||
|
echo "specify"
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_it_block
|
||||||
|
description: (string
|
||||||
|
(string_content))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))
|
||||||
|
argument: (string
|
||||||
|
(string_content)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Focused It block
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
fIt "focused test"
|
||||||
|
echo "focused"
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_it_block
|
||||||
|
description: (string
|
||||||
|
(string_content))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))
|
||||||
|
argument: (string
|
||||||
|
(string_content)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Focused Example block
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
fExample "focused example"
|
||||||
|
echo "focused"
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_it_block
|
||||||
|
description: (string
|
||||||
|
(string_content))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))
|
||||||
|
argument: (string
|
||||||
|
(string_content)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Focused Specify block
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
fSpecify "focused specify"
|
||||||
|
echo "focused"
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_it_block
|
||||||
|
description: (string
|
||||||
|
(string_content))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))
|
||||||
|
argument: (string
|
||||||
|
(string_content)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Skipped It block
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
xIt "skipped test"
|
||||||
|
echo "skipped"
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_it_block
|
||||||
|
description: (string
|
||||||
|
(string_content))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))
|
||||||
|
argument: (string
|
||||||
|
(string_content)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Skipped Example block
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
xExample "skipped example"
|
||||||
|
echo "skipped"
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_it_block
|
||||||
|
description: (string
|
||||||
|
(string_content))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))
|
||||||
|
argument: (string
|
||||||
|
(string_content)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Skipped Specify block
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
xSpecify "skipped specify"
|
||||||
|
echo "skipped"
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_it_block
|
||||||
|
description: (string
|
||||||
|
(string_content))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))
|
||||||
|
argument: (string
|
||||||
|
(string_content)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
It with complex assertions
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
It "should handle complex logic"
|
||||||
|
local result
|
||||||
|
result=$(some_function "param")
|
||||||
|
[ "$result" = "expected" ]
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_it_block
|
||||||
|
description: (string
|
||||||
|
(string_content))
|
||||||
|
(declaration_command
|
||||||
|
(variable_name))
|
||||||
|
(variable_assignment
|
||||||
|
name: (variable_name)
|
||||||
|
value: (command_substitution
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))
|
||||||
|
argument: (string
|
||||||
|
(string_content)))))
|
||||||
|
(test_command
|
||||||
|
(binary_expression
|
||||||
|
left: (string
|
||||||
|
(simple_expansion
|
||||||
|
(variable_name)))
|
||||||
|
right: (string
|
||||||
|
(string_content))))))
|
||||||
76
test/corpus/mock_blocks.txt
Normal file
76
test/corpus/mock_blocks.txt
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
================================================================================
|
||||||
|
Mock simple command
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Mock curl
|
||||||
|
echo "mocked response"
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_mock_block
|
||||||
|
name: (word)
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))
|
||||||
|
argument: (string
|
||||||
|
(string_content)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Mock command with string name
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Mock "git"
|
||||||
|
echo "mock git"
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_mock_block
|
||||||
|
name: (string
|
||||||
|
(string_content))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))
|
||||||
|
argument: (string
|
||||||
|
(string_content)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Mock empty command
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Mock curl
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_mock_block
|
||||||
|
name: (word)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Mock with multiple statements
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Mock curl
|
||||||
|
echo "status: 200"
|
||||||
|
echo "body: ok"
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_mock_block
|
||||||
|
name: (word)
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))
|
||||||
|
argument: (string
|
||||||
|
(string_content)))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))
|
||||||
|
argument: (string
|
||||||
|
(string_content)))))
|
||||||
236
test/corpus/nested_structures.txt
Normal file
236
test/corpus/nested_structures.txt
Normal file
@@ -0,0 +1,236 @@
|
|||||||
|
================================================================================
|
||||||
|
Describe with Context
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Describe "main feature"
|
||||||
|
Context "when condition A"
|
||||||
|
echo "setup A"
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_describe_block
|
||||||
|
description: (string
|
||||||
|
(string_content))
|
||||||
|
(shellspec_context_block
|
||||||
|
description: (string
|
||||||
|
(string_content))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))
|
||||||
|
argument: (string
|
||||||
|
(string_content))))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Describe with It
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Describe "main feature"
|
||||||
|
It "should work"
|
||||||
|
echo "test"
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_describe_block
|
||||||
|
description: (string
|
||||||
|
(string_content))
|
||||||
|
(shellspec_it_block
|
||||||
|
description: (string
|
||||||
|
(string_content))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))
|
||||||
|
argument: (string
|
||||||
|
(string_content))))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Context with It
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Context "when ready"
|
||||||
|
It "should execute"
|
||||||
|
echo "executing"
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_context_block
|
||||||
|
description: (string
|
||||||
|
(string_content))
|
||||||
|
(shellspec_it_block
|
||||||
|
description: (string
|
||||||
|
(string_content))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))
|
||||||
|
argument: (string
|
||||||
|
(string_content))))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Describe with hooks and tests
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Describe "complete feature"
|
||||||
|
BeforeEach
|
||||||
|
setup_test
|
||||||
|
End
|
||||||
|
|
||||||
|
It "should work correctly"
|
||||||
|
run_test
|
||||||
|
End
|
||||||
|
|
||||||
|
AfterEach
|
||||||
|
cleanup_test
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_describe_block
|
||||||
|
description: (string
|
||||||
|
(string_content))
|
||||||
|
(shellspec_hook_block
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))))
|
||||||
|
(shellspec_it_block
|
||||||
|
description: (string
|
||||||
|
(string_content))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))))
|
||||||
|
(shellspec_hook_block
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Complex nested structure
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Describe "main functionality"
|
||||||
|
BeforeAll
|
||||||
|
global_setup
|
||||||
|
End
|
||||||
|
|
||||||
|
Context "when user is authenticated"
|
||||||
|
BeforeEach
|
||||||
|
login_user
|
||||||
|
End
|
||||||
|
|
||||||
|
It "should access protected resource"
|
||||||
|
access_resource
|
||||||
|
End
|
||||||
|
|
||||||
|
Context "and has admin privileges"
|
||||||
|
It "should access admin panel"
|
||||||
|
access_admin
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
AfterEach
|
||||||
|
logout_user
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
AfterAll
|
||||||
|
global_cleanup
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_describe_block
|
||||||
|
description: (string
|
||||||
|
(string_content))
|
||||||
|
(shellspec_hook_block
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))))
|
||||||
|
(shellspec_context_block
|
||||||
|
description: (string
|
||||||
|
(string_content))
|
||||||
|
(shellspec_hook_block
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))))
|
||||||
|
(shellspec_it_block
|
||||||
|
description: (string
|
||||||
|
(string_content))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))))
|
||||||
|
(shellspec_context_block
|
||||||
|
description: (string
|
||||||
|
(string_content))
|
||||||
|
(shellspec_it_block
|
||||||
|
description: (string
|
||||||
|
(string_content))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word)))))
|
||||||
|
(shellspec_hook_block
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word)))))
|
||||||
|
(shellspec_hook_block
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Mixed with regular bash
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
function helper() {
|
||||||
|
echo "helper function"
|
||||||
|
}
|
||||||
|
|
||||||
|
Describe "using bash functions"
|
||||||
|
It "should call helper"
|
||||||
|
result=$(helper)
|
||||||
|
echo "$result"
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(comment)
|
||||||
|
(function_definition
|
||||||
|
name: (word)
|
||||||
|
body: (compound_statement
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))
|
||||||
|
argument: (string
|
||||||
|
(string_content)))))
|
||||||
|
(shellspec_describe_block
|
||||||
|
description: (string
|
||||||
|
(string_content))
|
||||||
|
(shellspec_it_block
|
||||||
|
description: (string
|
||||||
|
(string_content))
|
||||||
|
(variable_assignment
|
||||||
|
name: (variable_name)
|
||||||
|
value: (command_substitution
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word)))))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word))
|
||||||
|
argument: (string
|
||||||
|
(simple_expansion
|
||||||
|
(variable_name)))))))
|
||||||
110
test/corpus/parameters_variants.txt
Normal file
110
test/corpus/parameters_variants.txt
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
================================================================================
|
||||||
|
Parameters:block variant
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Parameters:block
|
||||||
|
"arg1" "arg2"
|
||||||
|
"arg3" "arg4"
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_utility_block
|
||||||
|
label: (string
|
||||||
|
(string_content))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(string
|
||||||
|
(string_content))))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(string
|
||||||
|
(string_content)))
|
||||||
|
argument: (string
|
||||||
|
(string_content)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Parameters:value variant
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Parameters:value
|
||||||
|
val1
|
||||||
|
val2
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_utility_block
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word)))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Parameters:matrix variant
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Parameters:matrix
|
||||||
|
"a" "b"
|
||||||
|
"c" "d"
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_utility_block
|
||||||
|
label: (string
|
||||||
|
(string_content))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(string
|
||||||
|
(string_content))))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(string
|
||||||
|
(string_content)))
|
||||||
|
argument: (string
|
||||||
|
(string_content)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Parameters:dynamic variant
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Parameters:dynamic
|
||||||
|
generate_params
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_utility_block
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Parameters:block with label
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Parameters:block "test cases"
|
||||||
|
"case1" "expected1"
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_utility_block
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(string
|
||||||
|
(string_content))))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(string
|
||||||
|
(string_content)))
|
||||||
|
argument: (string
|
||||||
|
(string_content)))))
|
||||||
95
test/corpus/pending_skip_statements.txt
Normal file
95
test/corpus/pending_skip_statements.txt
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
================================================================================
|
||||||
|
Inline Pending with reason
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Pending 'not yet implemented'
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_pending_statement
|
||||||
|
reason: (raw_string)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Inline Pending with double-quoted reason
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Pending "not yet implemented"
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_pending_statement
|
||||||
|
reason: (string
|
||||||
|
(string_content))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Inline Pending inside It block
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
It 'should do something'
|
||||||
|
Pending 'not yet implemented'
|
||||||
|
When call my_func
|
||||||
|
The output should equal "hello"
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_it_block
|
||||||
|
description: (raw_string)
|
||||||
|
(shellspec_pending_statement
|
||||||
|
reason: (raw_string))
|
||||||
|
(shellspec_when_statement
|
||||||
|
function: (word))
|
||||||
|
(shellspec_the_statement
|
||||||
|
subject: (shellspec_subject
|
||||||
|
(word))
|
||||||
|
matcher: (shellspec_matcher
|
||||||
|
(word)
|
||||||
|
(string
|
||||||
|
(string_content))))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Inline Skip with reason
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Skip 'not supported on this platform'
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_skip_statement
|
||||||
|
reason: (raw_string)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Inline Skip with double-quoted reason
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Skip "not supported"
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_skip_statement
|
||||||
|
reason: (string
|
||||||
|
(string_content))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Inline Skip inside It block
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
It 'should do something'
|
||||||
|
Skip 'not supported'
|
||||||
|
When call my_func
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_it_block
|
||||||
|
description: (raw_string)
|
||||||
|
(shellspec_skip_statement
|
||||||
|
reason: (raw_string))
|
||||||
|
(shellspec_when_statement
|
||||||
|
function: (word))))
|
||||||
206
test/corpus/percent_directives.txt
Normal file
206
test/corpus/percent_directives.txt
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
================================================================================
|
||||||
|
%text directive with data line
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
%text
|
||||||
|
#|line one
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_text_directive
|
||||||
|
data_line: (shellspec_data_line_content)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
%text:raw directive
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
%text:raw
|
||||||
|
#|raw content $var
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_text_directive
|
||||||
|
data_line: (shellspec_data_line_content)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
%text:expand directive
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
%text:expand
|
||||||
|
#|expanded $var
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_text_directive
|
||||||
|
data_line: (shellspec_data_line_content)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
%text with pipe filter
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
%text | tr 'a-z' 'A-Z'
|
||||||
|
#|hello
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_text_directive
|
||||||
|
filter: (word)
|
||||||
|
filter: (raw_string)
|
||||||
|
filter: (raw_string)
|
||||||
|
data_line: (shellspec_data_line_content)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
%const directive
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
%const NAME: value
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_const_directive
|
||||||
|
name: (word)
|
||||||
|
value: (word)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
%const directive with string value
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
%const NAME: "hello world"
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_const_directive
|
||||||
|
name: (word)
|
||||||
|
value: (string
|
||||||
|
(string_content))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
% shorthand directive
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
% VERSION: "1.0"
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_const_directive
|
||||||
|
name: (word)
|
||||||
|
value: (string
|
||||||
|
(string_content))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
%puts directive
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
%puts value
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_output_directive
|
||||||
|
argument: (word)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
%putsn directive
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
%putsn value
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_output_directive
|
||||||
|
argument: (word)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
%- directive
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
%- value
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_output_directive
|
||||||
|
argument: (word)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
%= directive
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
%= value
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_output_directive
|
||||||
|
argument: (word)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
%puts with string argument
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
%puts "hello world"
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_output_directive
|
||||||
|
argument: (string
|
||||||
|
(string_content))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
%preserve directive
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
%preserve VAR1 VAR2
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_preserve_directive
|
||||||
|
variable: (word)
|
||||||
|
variable: (word)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
%preserve single variable
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
%preserve RESULT
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_preserve_directive
|
||||||
|
variable: (word)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
%logger directive
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
%logger "debug message"
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_logger_directive
|
||||||
|
argument: (string
|
||||||
|
(string_content))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
%logger with word argument
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
%logger debug_info
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_logger_directive
|
||||||
|
argument: (word)))
|
||||||
95
test/corpus/real_world_patterns.txt
Normal file
95
test/corpus/real_world_patterns.txt
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
================================================================================
|
||||||
|
Before hook statements
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Before 'setup'
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_hook_statement
|
||||||
|
argument: (raw_string)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
After hook with multiple arguments
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Before 'setup1' 'setup2'
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_hook_statement
|
||||||
|
argument: (raw_string)
|
||||||
|
argument: (raw_string)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Include directive
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Include ./lib.sh
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_directive_statement
|
||||||
|
path: (word)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Skip with conditional
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Skip if "function returns success" conditions
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_directive_statement
|
||||||
|
reason: (string
|
||||||
|
(string_content))
|
||||||
|
condition: (word)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Skip with complex conditional
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Skip if 'function returns "skip"' [ "$(conditions)" = "skip" ]
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_directive_statement
|
||||||
|
reason: (raw_string)
|
||||||
|
condition: (test_command
|
||||||
|
(binary_expression
|
||||||
|
left: (string
|
||||||
|
(command_substitution
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word)))))
|
||||||
|
right: (string
|
||||||
|
(string_content))))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Top-level It without Describe
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
It 'is simple'
|
||||||
|
When call echo 'ok'
|
||||||
|
The output should eq 'ok'
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_it_block
|
||||||
|
description: (raw_string)
|
||||||
|
(shellspec_when_statement
|
||||||
|
function: (word)
|
||||||
|
argument: (raw_string))
|
||||||
|
(shellspec_the_statement
|
||||||
|
subject: (shellspec_subject
|
||||||
|
(word))
|
||||||
|
matcher: (shellspec_matcher
|
||||||
|
(word)
|
||||||
|
(raw_string)))))
|
||||||
96
test/corpus/shellspec_statements.txt
Normal file
96
test/corpus/shellspec_statements.txt
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
================================================================================
|
||||||
|
Path statement
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Path helper=./lib/helper.sh
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_path_statement
|
||||||
|
argument: (word)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
File statement
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
File config_file
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_path_statement
|
||||||
|
argument: (word)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Dir statement
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Dir testdir
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_path_statement
|
||||||
|
argument: (word)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Set statement with option
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Set 'errexit:on'
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_set_statement
|
||||||
|
option: (raw_string)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Set statement with multiple options
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Set 'errexit:on' 'nounset:on'
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_set_statement
|
||||||
|
option: (raw_string)
|
||||||
|
option: (raw_string)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Dump statement
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Dump
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_dump_statement))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Intercept statement
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Intercept my_func
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_intercept_statement
|
||||||
|
argument: (word)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Intercept with string argument
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Intercept "network_call"
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_intercept_statement
|
||||||
|
argument: (string
|
||||||
|
(string_content))))
|
||||||
323
test/corpus/utility_blocks.txt
Normal file
323
test/corpus/utility_blocks.txt
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
================================================================================
|
||||||
|
Data block
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Data
|
||||||
|
item1
|
||||||
|
item2
|
||||||
|
item3
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_data_block
|
||||||
|
statements: (command
|
||||||
|
name: (command_name
|
||||||
|
(word)))
|
||||||
|
statements: (command
|
||||||
|
name: (command_name
|
||||||
|
(word)))
|
||||||
|
statements: (command
|
||||||
|
name: (command_name
|
||||||
|
(word)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Data block with label
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Data "test data"
|
||||||
|
"value 1"
|
||||||
|
"value 2"
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_data_block
|
||||||
|
statements: (command
|
||||||
|
name: (command_name
|
||||||
|
(string
|
||||||
|
(string_content))))
|
||||||
|
statements: (command
|
||||||
|
name: (command_name
|
||||||
|
(string
|
||||||
|
(string_content))))
|
||||||
|
statements: (command
|
||||||
|
name: (command_name
|
||||||
|
(string
|
||||||
|
(string_content))))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Parameters block
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
param1
|
||||||
|
param2
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_utility_block
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word)))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(word)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Parameters with label
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Parameters "test parameters"
|
||||||
|
"first param"
|
||||||
|
"second param"
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_utility_block
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(string
|
||||||
|
(string_content))))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(string
|
||||||
|
(string_content))))
|
||||||
|
(command
|
||||||
|
name: (command_name
|
||||||
|
(string
|
||||||
|
(string_content))))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Skip with reason
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Skip 'skipped for now'
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_skip_statement
|
||||||
|
reason: (raw_string)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Skip with double-quoted reason
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Skip "not implemented yet"
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_skip_statement
|
||||||
|
reason: (string
|
||||||
|
(string_content))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Pending with reason
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Pending 'work in progress'
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_pending_statement
|
||||||
|
reason: (raw_string)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Pending with double-quoted reason
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Pending "waiting for fix"
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_pending_statement
|
||||||
|
reason: (string
|
||||||
|
(string_content))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Todo standalone
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Todo "implement feature X"
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_todo_statement
|
||||||
|
description: (string
|
||||||
|
(string_content))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Todo with raw string
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Todo 'implement feature X'
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_todo_statement
|
||||||
|
description: (raw_string)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Empty utility block
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Data "empty data"
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_data_block
|
||||||
|
statements: (command
|
||||||
|
name: (command_name
|
||||||
|
(string
|
||||||
|
(string_content))))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Data string argument style
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Data "inline data"
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_data_block
|
||||||
|
argument: (string
|
||||||
|
(string_content))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Data function argument style
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Data get_test_data
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_data_block
|
||||||
|
argument: (word)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Data block with :raw modifier
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Data :raw
|
||||||
|
'raw data here'
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_data_block
|
||||||
|
statements: (command
|
||||||
|
name: (command_name
|
||||||
|
(word)))
|
||||||
|
statements: (command
|
||||||
|
name: (command_name
|
||||||
|
(raw_string)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Data block with :expand modifier
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Data :expand
|
||||||
|
"expanded $variable"
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_data_block
|
||||||
|
statements: (command
|
||||||
|
name: (command_name
|
||||||
|
(word)))
|
||||||
|
statements: (command
|
||||||
|
name: (command_name
|
||||||
|
(string
|
||||||
|
(string_content)
|
||||||
|
(simple_expansion
|
||||||
|
(variable_name)))))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Data block with pipe filter
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Data | tr 'abc' 'ABC'
|
||||||
|
#|aaa
|
||||||
|
#|bbb
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_data_block
|
||||||
|
filter: (word)
|
||||||
|
filter: (raw_string)
|
||||||
|
filter: (raw_string)
|
||||||
|
data_line: (shellspec_data_line_content)
|
||||||
|
data_line: (shellspec_data_line_content)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Data block with pipe filter and modifier
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Data:expand | tr 'a' 'A'
|
||||||
|
#|hello
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_data_block
|
||||||
|
filter: (word)
|
||||||
|
filter: (raw_string)
|
||||||
|
filter: (raw_string)
|
||||||
|
data_line: (shellspec_data_line_content)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Data function argument with pipe filter
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Data foo a b c | tr 'abc' 'ABC'
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_data_block
|
||||||
|
argument: (word)
|
||||||
|
extra_argument: (word)
|
||||||
|
extra_argument: (word)
|
||||||
|
extra_argument: (word)
|
||||||
|
filter: (word)
|
||||||
|
filter: (raw_string)
|
||||||
|
filter: (raw_string)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Data string argument with pipe filter
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Data 'abc' | tr 'abc' 'ABC'
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_data_block
|
||||||
|
argument: (raw_string)
|
||||||
|
filter: (word)
|
||||||
|
filter: (raw_string)
|
||||||
|
filter: (raw_string)))
|
||||||
250
test/corpus/when_the_assert.txt
Normal file
250
test/corpus/when_the_assert.txt
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
================================================================================
|
||||||
|
When call simple function
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
When call my_func
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_when_statement
|
||||||
|
function: (word)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
When call function with arguments
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
When call add 2 3
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_when_statement
|
||||||
|
function: (word)
|
||||||
|
argument: (word)
|
||||||
|
argument: (word)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
When call function with string argument
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
When call my_func "hello world"
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_when_statement
|
||||||
|
function: (word)
|
||||||
|
argument: (string
|
||||||
|
(string_content))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
When run simple
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
When run my_func
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_when_statement
|
||||||
|
function: (word)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
When run command
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
When run command expr 1 + 2
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_when_statement
|
||||||
|
function: (word)
|
||||||
|
argument: (word)
|
||||||
|
argument: (word)
|
||||||
|
argument: (word)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
When run script
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
When run script ./test.sh
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_when_statement
|
||||||
|
function: (word)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
When run source
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
When run source ./lib.sh arg1
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_when_statement
|
||||||
|
function: (word)
|
||||||
|
argument: (word)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
The output should eq string
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
The output should eq 'ok'
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_the_statement
|
||||||
|
subject: (shellspec_subject
|
||||||
|
(word))
|
||||||
|
matcher: (shellspec_matcher
|
||||||
|
(word)
|
||||||
|
(raw_string))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
The status should be success
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
The status should be success
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_the_statement
|
||||||
|
subject: (shellspec_subject
|
||||||
|
(word))
|
||||||
|
matcher: (shellspec_matcher
|
||||||
|
(word)
|
||||||
|
(word))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
The output should not eq bad
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
The output should not eq "bad"
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_the_statement
|
||||||
|
subject: (shellspec_subject
|
||||||
|
(word))
|
||||||
|
matcher: (shellspec_matcher
|
||||||
|
(word)
|
||||||
|
(string
|
||||||
|
(string_content)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
The status should be failure
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
The status should be failure
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_the_statement
|
||||||
|
subject: (shellspec_subject
|
||||||
|
(word))
|
||||||
|
matcher: (shellspec_matcher
|
||||||
|
(word)
|
||||||
|
(word))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
The with multi-word subject
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
The line 1 of output should eq "first"
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_the_statement
|
||||||
|
subject: (shellspec_subject
|
||||||
|
(word)
|
||||||
|
(word)
|
||||||
|
(word)
|
||||||
|
(word))
|
||||||
|
matcher: (shellspec_matcher
|
||||||
|
(word)
|
||||||
|
(string
|
||||||
|
(string_content)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Assert simple function
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Assert my_function
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_assert_statement
|
||||||
|
argument: (word)))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Assert with multiple arguments
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
Assert check_result "expected value"
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_assert_statement
|
||||||
|
argument: (word)
|
||||||
|
argument: (string
|
||||||
|
(string_content))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
When and The inside It block
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
It "should add numbers"
|
||||||
|
When call add 2 3
|
||||||
|
The output should eq 5
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_it_block
|
||||||
|
description: (string
|
||||||
|
(string_content))
|
||||||
|
(shellspec_when_statement
|
||||||
|
function: (word)
|
||||||
|
argument: (word)
|
||||||
|
argument: (word))
|
||||||
|
(shellspec_the_statement
|
||||||
|
subject: (shellspec_subject
|
||||||
|
(word))
|
||||||
|
matcher: (shellspec_matcher
|
||||||
|
(word)
|
||||||
|
(word)))))
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
Assert inside It block
|
||||||
|
================================================================================
|
||||||
|
|
||||||
|
It "should validate"
|
||||||
|
When call validate "input"
|
||||||
|
Assert check_valid
|
||||||
|
End
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(program
|
||||||
|
(shellspec_it_block
|
||||||
|
description: (string
|
||||||
|
(string_content))
|
||||||
|
(shellspec_when_statement
|
||||||
|
function: (word)
|
||||||
|
argument: (string
|
||||||
|
(string_content)))
|
||||||
|
(shellspec_assert_statement
|
||||||
|
argument: (word))))
|
||||||
17
test/spec/01.very_simple_spec.sh
Normal file
17
test/spec/01.very_simple_spec.sh
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# shellcheck shell=sh
|
||||||
|
|
||||||
|
It 'is simple'
|
||||||
|
When call echo 'ok'
|
||||||
|
The output should eq 'ok'
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe 'lib.sh'
|
||||||
|
Include ./lib.sh # include other script
|
||||||
|
|
||||||
|
Describe 'calc()'
|
||||||
|
It 'calculates'
|
||||||
|
When call calc 1 + 1
|
||||||
|
The output should eq 2
|
||||||
|
End
|
||||||
|
End
|
||||||
|
End
|
||||||
24
test/spec/02.example_group_spec.sh
Normal file
24
test/spec/02.example_group_spec.sh
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# shellcheck shell=sh
|
||||||
|
|
||||||
|
Describe 'this is "example group"'
|
||||||
|
Context 'this is also "example group"'
|
||||||
|
# You can write an "example" here
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe '"example group" can be nestable'
|
||||||
|
# You can write an "example" here
|
||||||
|
End
|
||||||
|
|
||||||
|
Context 'this is also "example group"'
|
||||||
|
# You can write an "example" here
|
||||||
|
|
||||||
|
Describe '"example group" can be nestable'
|
||||||
|
# You can write an "example" here
|
||||||
|
End
|
||||||
|
|
||||||
|
# You can write an "example" here
|
||||||
|
End
|
||||||
|
# You can write an "example" here
|
||||||
|
End
|
||||||
|
|
||||||
|
# You can write an "example" here
|
||||||
37
test/spec/03.example_spec.sh
Normal file
37
test/spec/03.example_spec.sh
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# shellcheck shell=sh
|
||||||
|
|
||||||
|
Describe 'example example'
|
||||||
|
It 'is "example"'
|
||||||
|
When call echo 'foo'
|
||||||
|
The output should eq 'foo'
|
||||||
|
End
|
||||||
|
|
||||||
|
Example 'is "example"'
|
||||||
|
When call echo 'bar'
|
||||||
|
The output should eq 'bar'
|
||||||
|
End
|
||||||
|
|
||||||
|
Specify 'is also "example"'
|
||||||
|
When call echo 'baz'
|
||||||
|
The output should eq 'baz'
|
||||||
|
End
|
||||||
|
|
||||||
|
Example 'this is "Not yet implemented" example block'
|
||||||
|
:
|
||||||
|
End
|
||||||
|
|
||||||
|
Todo 'what to do' # same as "Not yet implemented" example but not block
|
||||||
|
|
||||||
|
It 'not allows define "example group" in "example"'
|
||||||
|
# Describe 'example group'
|
||||||
|
# this is syntax error
|
||||||
|
# End
|
||||||
|
The value 1 should eq 1
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
# example group is not required
|
||||||
|
It 'is "example" without "example group"'
|
||||||
|
When call echo 'foo'
|
||||||
|
The output should eq 'foo'
|
||||||
|
End
|
||||||
75
test/spec/04.evaluation_spec.sh
Normal file
75
test/spec/04.evaluation_spec.sh
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# shellcheck shell=sh disable=SC2016
|
||||||
|
|
||||||
|
Describe 'evaluation example'
|
||||||
|
Describe 'call evaluation'
|
||||||
|
It 'calls function'
|
||||||
|
foo() { echo "foo"; }
|
||||||
|
When call foo # this is evaluation
|
||||||
|
The output should eq "foo"
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'calls external command also'
|
||||||
|
When call expr 1 + 2
|
||||||
|
The output should eq 3
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'calls the defined function instead of external command that same name'
|
||||||
|
expr() { echo "be called"; }
|
||||||
|
When call expr 1 + 2
|
||||||
|
The output should eq "be called"
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'must be one call each example'
|
||||||
|
When call echo 1
|
||||||
|
When call echo 2 # cannot be called more than once.
|
||||||
|
The output should eq 1
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'not calling is allowed'
|
||||||
|
The value 123 should eq 123
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'cannot be called after expectation'
|
||||||
|
The value 123 should eq 123
|
||||||
|
When call echo 1 # cannot be called after expectation.
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'calls external command'
|
||||||
|
expr() { echo "not called"; }
|
||||||
|
When run command expr 1 + 2
|
||||||
|
The output should eq 3
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe 'run evaluation'
|
||||||
|
It 'can trap exit'
|
||||||
|
abort() { exit 1; }
|
||||||
|
When run abort # if use "call evaluation", shellspec is terminate
|
||||||
|
The status should be failure
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'cannot modify variable because it run with in subshell'
|
||||||
|
set_value() { SHELLSPEC_VERSION=$1; }
|
||||||
|
When run set_value 'no-version'
|
||||||
|
The value "$SHELLSPEC_VERSION" should not eq 'no-version'
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'calls BeforeRun/AfterRun hook'
|
||||||
|
before_run() {
|
||||||
|
# You can temporary redefine function here
|
||||||
|
# redefined function is restored after run evaluation
|
||||||
|
# because run evaluation runs with in subshell
|
||||||
|
echo before
|
||||||
|
}
|
||||||
|
after_run() {
|
||||||
|
echo after
|
||||||
|
}
|
||||||
|
BeforeRun before_run
|
||||||
|
AfterRun after_run
|
||||||
|
When run echo 123
|
||||||
|
The line 1 of output should eq 'before'
|
||||||
|
The line 2 of output should eq 123
|
||||||
|
The line 3 of output should eq 'after'
|
||||||
|
End
|
||||||
|
End
|
||||||
|
End
|
||||||
29
test/spec/05.expectation_spec.sh
Normal file
29
test/spec/05.expectation_spec.sh
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# shellcheck shell=sh
|
||||||
|
# Sample ShellSpec file for manual parser testing (not executed by `npm test`).
|
||||||
|
# Some examples intentionally demonstrate failing assertions.
|
||||||
|
|
||||||
|
Describe 'expectation example'
|
||||||
|
It 'is succeeds because expectation is successful'
|
||||||
|
foo() { echo "foo"; }
|
||||||
|
When call foo
|
||||||
|
The output should eq "foo" # this is expectation
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'is failure because expectation is fail'
|
||||||
|
foo() { echo "foo"; }
|
||||||
|
When call foo
|
||||||
|
The output should eq "bar"
|
||||||
|
End
|
||||||
|
|
||||||
|
Example 'you can write multiple expectations'
|
||||||
|
foo() {
|
||||||
|
echo "foo"
|
||||||
|
value=123
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
When call foo
|
||||||
|
The output should eq "foo"
|
||||||
|
The value "$value" should eq 123
|
||||||
|
The status should eq 1
|
||||||
|
End
|
||||||
|
End
|
||||||
49
test/spec/06.scope_spec.sh
Normal file
49
test/spec/06.scope_spec.sh
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# shellcheck shell=sh
|
||||||
|
|
||||||
|
# Each block (example group / example) runs within subshell.
|
||||||
|
# It means that it works like lexical scope.
|
||||||
|
|
||||||
|
Describe 'scope example'
|
||||||
|
foo() { echo "foo"; } # It can call from anywhere within this example group
|
||||||
|
|
||||||
|
# By the way, you can only use shellspec DSL or define function here.
|
||||||
|
# Of course it is possible to write freely within the defined function
|
||||||
|
# but other code may breaks isolation of tests.
|
||||||
|
|
||||||
|
It 'calls "foo"'
|
||||||
|
When call foo
|
||||||
|
The output should eq 'foo'
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'defines "bar" function'
|
||||||
|
bar() { echo "bar"; }
|
||||||
|
When call bar
|
||||||
|
The output should eq 'bar'
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'cannot call "bar" function, because different scope'
|
||||||
|
When call bar
|
||||||
|
The status should be failure # probably status is 127
|
||||||
|
The stderr should be present # probably stderr is "bar: not found"
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'redefines "foo" function'
|
||||||
|
foo() { echo "FOO"; }
|
||||||
|
When call foo
|
||||||
|
The output should eq 'FOO'
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'calls "foo" function of outer scope (not previous example)'
|
||||||
|
When call foo
|
||||||
|
The output should eq 'foo'
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe 'sub block'
|
||||||
|
foo() { echo "Foo"; }
|
||||||
|
|
||||||
|
It 'calls "foo" function of upper scope'
|
||||||
|
When call foo
|
||||||
|
The output should eq 'Foo'
|
||||||
|
End
|
||||||
|
End
|
||||||
|
End
|
||||||
77
test/spec/07.before_after_hook_spec.sh
Normal file
77
test/spec/07.before_after_hook_spec.sh
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# shellcheck shell=sh
|
||||||
|
|
||||||
|
# ShellSpec has Before and After hooks.
|
||||||
|
# Those hooks are executed for each example (It/Example/Specify).
|
||||||
|
# Note: ShellSpec also supports BeforeAll/AfterAll, BeforeCall/AfterCall,
|
||||||
|
# and BeforeRun/AfterRun hooks (see the grammar for the full list).
|
||||||
|
|
||||||
|
Describe 'before / after hook example'
|
||||||
|
Describe '1: before hook'
|
||||||
|
setup() { value=10; }
|
||||||
|
Before 'setup'
|
||||||
|
|
||||||
|
add_value() {
|
||||||
|
value=$((value + $1))
|
||||||
|
echo "$value"
|
||||||
|
}
|
||||||
|
|
||||||
|
It 'is called before executing the example'
|
||||||
|
When call add_value 10
|
||||||
|
The output should eq 20
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'is called for each example'
|
||||||
|
When call add_value 100
|
||||||
|
The output should eq 110
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe '2: before hook'
|
||||||
|
setup1() { value1=10; }
|
||||||
|
setup2() { value2=20; }
|
||||||
|
Before 'setup1' 'setup2'
|
||||||
|
|
||||||
|
add_values() {
|
||||||
|
echo "$((value1 + value2))"
|
||||||
|
}
|
||||||
|
|
||||||
|
It 'can register multiple'
|
||||||
|
When call add_values
|
||||||
|
The output should eq 30
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe '3: before hook'
|
||||||
|
Before 'value=10'
|
||||||
|
|
||||||
|
echo_value() { echo "$value"; }
|
||||||
|
|
||||||
|
It 'can also specify code instead of a function'
|
||||||
|
When call echo_value
|
||||||
|
The output should eq 10
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe '4: before hook'
|
||||||
|
setup() { false; } # setup fails
|
||||||
|
Before 'setup'
|
||||||
|
echo_ok() { echo ok; }
|
||||||
|
|
||||||
|
It 'fails because the before hook fails'
|
||||||
|
When call echo_ok
|
||||||
|
The output should eq 'ok'
|
||||||
|
End
|
||||||
|
|
||||||
|
# This behavior can be used to verify initialization of before hook.
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe '5: after hook'
|
||||||
|
cleanup() { :; } # clean up something
|
||||||
|
After 'cleanup'
|
||||||
|
|
||||||
|
It 'is called after executing the example'
|
||||||
|
When call echo ok
|
||||||
|
The output should eq 'ok'
|
||||||
|
End
|
||||||
|
End
|
||||||
|
End
|
||||||
42
test/spec/08.calling_order_of_hook_spec.sh
Normal file
42
test/spec/08.calling_order_of_hook_spec.sh
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# shellcheck shell=sh
|
||||||
|
|
||||||
|
hook() { %logger "$1 $2 ${SHELLSPEC_EXAMPLE_ID}"; }
|
||||||
|
|
||||||
|
Describe '1'
|
||||||
|
Before "hook before 1" "hook before 2"
|
||||||
|
After "hook after 1" "hook after 2"
|
||||||
|
|
||||||
|
Describe '1-1'
|
||||||
|
Before "hook before 3"
|
||||||
|
After "hook after 3"
|
||||||
|
|
||||||
|
# The before hook is called by defined order
|
||||||
|
%logger "==== before example 1-1-1 ===="
|
||||||
|
Example '1-1-1'
|
||||||
|
%logger "example ${SHELLSPEC_EXAMPLE_ID}"
|
||||||
|
When call :
|
||||||
|
End
|
||||||
|
%logger "==== after example 1-1-1 ===="
|
||||||
|
# The after hook is called by defined reverse order
|
||||||
|
|
||||||
|
# The before hook is called for each example
|
||||||
|
%logger "==== before example 1-1-2 ===="
|
||||||
|
Example '1-1-2'
|
||||||
|
%logger "example ${SHELLSPEC_EXAMPLE_ID}"
|
||||||
|
When call :
|
||||||
|
End
|
||||||
|
%logger "==== after example 1-1-2 ===="
|
||||||
|
# The after hook is called for each example
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe '1-2'
|
||||||
|
# The before 3 hook is not called
|
||||||
|
%logger "==== before example 1-2-1 ===="
|
||||||
|
Example '1-2-1'
|
||||||
|
%logger "example ${SHELLSPEC_EXAMPLE_ID}"
|
||||||
|
When call :
|
||||||
|
End
|
||||||
|
%logger "==== after example 1-2-1 ===="
|
||||||
|
# The after 3 hook is not called
|
||||||
|
End
|
||||||
|
End
|
||||||
117
test/spec/09.subject_spec.sh
Normal file
117
test/spec/09.subject_spec.sh
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
# shellcheck shell=sh
|
||||||
|
|
||||||
|
Describe 'subject example'
|
||||||
|
Describe 'stdout'
|
||||||
|
foo() { echo "ok"; }
|
||||||
|
|
||||||
|
It 'uses the stdout as the subject'
|
||||||
|
When call foo
|
||||||
|
The stdout should eq "ok"
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'has an alias "output"'
|
||||||
|
When call foo
|
||||||
|
The output should eq "ok"
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe 'with entire'
|
||||||
|
It 'does not remove last LF'
|
||||||
|
When call foo
|
||||||
|
The entire output should eq "ok${SHELLSPEC_LF}"
|
||||||
|
End
|
||||||
|
|
||||||
|
# Without "entire", the "output" subject act as like
|
||||||
|
# the command substitution.
|
||||||
|
#
|
||||||
|
# For example "echo" outputs a newline at the end.
|
||||||
|
# In spite of that `[ "$(echo ok)" = "ok" ]` will success.
|
||||||
|
# Because the command substitution removes trailing newlines.
|
||||||
|
#
|
||||||
|
# The "entire output" subject does not remove trailing newlines.
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe 'stderr'
|
||||||
|
foo() { echo "err" >&2; }
|
||||||
|
|
||||||
|
It 'uses the stderr as the subject'
|
||||||
|
When call foo
|
||||||
|
The stderr should eq "err"
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'has an alias "error"'
|
||||||
|
When call foo
|
||||||
|
The error should eq "err"
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe 'with entire'
|
||||||
|
It 'does not remove last LF'
|
||||||
|
When call foo
|
||||||
|
The entire error should eq "err${SHELLSPEC_LF}"
|
||||||
|
End
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe 'status'
|
||||||
|
foo() { return 123; }
|
||||||
|
|
||||||
|
It 'uses the status as the subject'
|
||||||
|
When call foo
|
||||||
|
The status should eq 123
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe 'variable'
|
||||||
|
foo() { var=456; }
|
||||||
|
|
||||||
|
It 'uses the variable as the subject'
|
||||||
|
When call foo
|
||||||
|
The variable var should eq 456
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe 'value'
|
||||||
|
foo() { var=789; }
|
||||||
|
|
||||||
|
It 'uses the value as the subject'
|
||||||
|
When call foo
|
||||||
|
The value "$var" should eq 789
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe 'function'
|
||||||
|
foo() { echo "ok"; }
|
||||||
|
|
||||||
|
It 'is alias for value'
|
||||||
|
The function "foo" should eq "foo"
|
||||||
|
The "foo()" should eq "foo" # shorthand for function
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'uses with result modifier'
|
||||||
|
The result of "foo()" should eq "ok"
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe 'path'
|
||||||
|
# Path helper defines path alias.
|
||||||
|
Path hosts-file='/etc/hosts'
|
||||||
|
|
||||||
|
It 'uses the resolved path as the subject'
|
||||||
|
The path hosts-file should eq '/etc/hosts'
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'has an alias "file"'
|
||||||
|
Path hosts='/etc/hosts'
|
||||||
|
The file hosts should eq '/etc/hosts'
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'has an alias "dir"'
|
||||||
|
Path target='/foo/bar/baz/target'
|
||||||
|
The dir target should eq '/foo/bar/baz/target'
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'is same as value if path alias not found. but improve readability'
|
||||||
|
The path '/etc/hosts' should eq '/etc/hosts'
|
||||||
|
End
|
||||||
|
End
|
||||||
|
End
|
||||||
76
test/spec/10.modifier_spec.sh
Normal file
76
test/spec/10.modifier_spec.sh
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# shellcheck shell=sh
|
||||||
|
|
||||||
|
Describe 'modifier example'
|
||||||
|
data() {
|
||||||
|
echo '1 a A'
|
||||||
|
echo '2 b B'
|
||||||
|
echo '3 c C'
|
||||||
|
echo '4 d D'
|
||||||
|
}
|
||||||
|
|
||||||
|
Describe 'line modifier'
|
||||||
|
It 'gets specified line'
|
||||||
|
When call data
|
||||||
|
The line 2 of stdout should eq "2 b B"
|
||||||
|
The stdout line 2 should eq "2 b B" # you can also write like this
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe 'lines modifier'
|
||||||
|
It 'counts lines'
|
||||||
|
When call data
|
||||||
|
The lines of stdout should eq 4
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe 'word modifier'
|
||||||
|
It 'gets specified word'
|
||||||
|
When call data
|
||||||
|
The word 5 of stdout should eq "b"
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe 'length modifier'
|
||||||
|
It 'counts length'
|
||||||
|
When call data
|
||||||
|
The length of stdout should eq 23 # 6 * 4 - 1
|
||||||
|
# Each lines length is 6 including newline,
|
||||||
|
# but trailing newlines are removed.
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe 'contents modifier'
|
||||||
|
It 'counts length'
|
||||||
|
The contents of file "data.txt" should eq "data"
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe 'result modifier'
|
||||||
|
echo_ok() { echo ok; }
|
||||||
|
It 'calls function'
|
||||||
|
The result of function echo_ok should eq "ok"
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe 'modifier'
|
||||||
|
It 'can use ordinal number (0 - 20)'
|
||||||
|
When call data
|
||||||
|
The second line of stdout should eq "2 b B"
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'can use abbreviation of ordinal number'
|
||||||
|
When call data
|
||||||
|
The 2nd line of stdout should eq "2 b B"
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'is chainable'
|
||||||
|
When call data
|
||||||
|
The word 2 of line 2 of stdout should eq "b"
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'can use language chain'
|
||||||
|
When call data
|
||||||
|
The word 2 of the line 2 of the stdout should eq "b"
|
||||||
|
End
|
||||||
|
End
|
||||||
|
End
|
||||||
118
test/spec/11.matcher_spec.sh
Normal file
118
test/spec/11.matcher_spec.sh
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
# shellcheck shell=sh
|
||||||
|
|
||||||
|
Describe 'matcher example'
|
||||||
|
Describe 'status matchers'
|
||||||
|
Describe 'be success matcher'
|
||||||
|
It 'checks if status is successful'
|
||||||
|
When call true
|
||||||
|
The status should be success # status is 0
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe 'be failure matcher'
|
||||||
|
It 'checks if status is failed'
|
||||||
|
When call false
|
||||||
|
The status should be failure # status is 1-255
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
# If you want to check status number, use equal matcher.
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe 'stat matchers'
|
||||||
|
Describe 'exists'
|
||||||
|
It 'checks if path exists'
|
||||||
|
The path 'data.txt' should exist
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'checks if path is file'
|
||||||
|
The path 'data.txt' should be file
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'checks if path is directory'
|
||||||
|
The path 'data.txt' should be directory
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
# There are many other stat matchers.
|
||||||
|
# be empty, be symlink, be pipe, be socket, be readable, be writable,
|
||||||
|
# be executable, be block_device, be character_device,
|
||||||
|
# has setgid, has setuid
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe 'variable matchers'
|
||||||
|
Before 'prepare'
|
||||||
|
|
||||||
|
Describe 'be defined'
|
||||||
|
prepare() { var=''; }
|
||||||
|
It 'checks if variable is defined'
|
||||||
|
The value "$var" should be defined
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe 'be undefined'
|
||||||
|
prepare() { unset var; }
|
||||||
|
It 'checks if variable is undefined'
|
||||||
|
The variable var should be undefined
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe 'be present'
|
||||||
|
prepare() { var=123; }
|
||||||
|
It 'checks if variable is present'
|
||||||
|
The value "$var" should be present # non-zero length string
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe 'be blank'
|
||||||
|
prepare() { var=""; }
|
||||||
|
It 'checks if variable is blank'
|
||||||
|
The value "$var" should be blank # unset or zero length string
|
||||||
|
End
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe 'string matchers'
|
||||||
|
Describe 'equal'
|
||||||
|
It 'checks if subject equals specified string'
|
||||||
|
The value "foobarbaz" should equal "foobarbaz"
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe 'start with'
|
||||||
|
It 'checks if subject start with specified string'
|
||||||
|
The value "foobarbaz" should start with "foo"
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe 'end with'
|
||||||
|
It 'checks if subject end with specified string'
|
||||||
|
The value "foobarbaz" should end with "baz"
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe 'include'
|
||||||
|
It 'checks if subject include specified string'
|
||||||
|
The value "foobarbaz" should include "bar"
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe 'match'
|
||||||
|
It 'checks if subject match specified pattern'
|
||||||
|
# Using shell script's pattern matching
|
||||||
|
The value "foobarbaz" should match pattern "f??bar*"
|
||||||
|
End
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe 'satisfy matcher'
|
||||||
|
formula() {
|
||||||
|
eval "value=${formula:?}"
|
||||||
|
[ $(($1)) -eq 1 ]
|
||||||
|
}
|
||||||
|
|
||||||
|
It 'checks if satisfy condition'
|
||||||
|
The value 10 should satisfy formula "value >= 5"
|
||||||
|
End
|
||||||
|
End
|
||||||
|
End
|
||||||
100
test/spec/12.skip_spec.sh
Normal file
100
test/spec/12.skip_spec.sh
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
# shellcheck shell=sh
|
||||||
|
|
||||||
|
Describe 'skip example'
|
||||||
|
Describe 'calc()'
|
||||||
|
calc() { echo "$(($*))"; }
|
||||||
|
|
||||||
|
It 'can add'
|
||||||
|
When call calc 1 + 1
|
||||||
|
The output should eq 2
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'can minus'
|
||||||
|
When call calc 1 - 1
|
||||||
|
The output should eq 0
|
||||||
|
End
|
||||||
|
|
||||||
|
# Skip examples of after this line in current example group
|
||||||
|
Skip "decimal point cannot be calculated"
|
||||||
|
|
||||||
|
It 'can add decimal point'
|
||||||
|
When call calc 1.1 + 1.1
|
||||||
|
The output should eq 2.2
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'can minus decimal point'
|
||||||
|
When call calc 1.1 - 1.1
|
||||||
|
The output should eq 0
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe 'Multiplication' # example group is also skipped
|
||||||
|
It 'can multiply decimal point'
|
||||||
|
When call calc 1.1 '*' 1.1
|
||||||
|
The output should eq 1.21
|
||||||
|
End
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe 'status_to_signal()'
|
||||||
|
status_to_signal() {
|
||||||
|
if [ 128 -le "$1" ] && [ "$1" -le 192 ]; then
|
||||||
|
echo "$(($1 - 128))"
|
||||||
|
else
|
||||||
|
# Not implemented: echo "status is out of range" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
It 'cannot convert status to signal'
|
||||||
|
When call status_to_signal 0
|
||||||
|
The status should be failure
|
||||||
|
|
||||||
|
# Skip expection of after this line in current example
|
||||||
|
Skip 'outputs error message is not implemented'
|
||||||
|
The error should be present
|
||||||
|
End
|
||||||
|
|
||||||
|
# This example is going to execute
|
||||||
|
It 'converts status to signal'
|
||||||
|
When call status_to_signal 137
|
||||||
|
The output should eq 9
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
# "temporary skip" cannot hidden with "--skip-message quiet" option
|
||||||
|
Describe 'temporary skip'
|
||||||
|
Example 'with Skip helper'
|
||||||
|
Skip # without reason
|
||||||
|
When call foo
|
||||||
|
The status should be success
|
||||||
|
End
|
||||||
|
|
||||||
|
xExample 'with xExample (prepend "x")'
|
||||||
|
When call foo
|
||||||
|
The status should be success
|
||||||
|
End
|
||||||
|
|
||||||
|
xDescribe 'with xDescribe (prepend "x")'
|
||||||
|
Example 'this is also skipped'
|
||||||
|
When call foo
|
||||||
|
The status should be success
|
||||||
|
End
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe 'conditional skip'
|
||||||
|
Example 'skip1'
|
||||||
|
conditions() { return 0; }
|
||||||
|
Skip if "function returns success" conditions
|
||||||
|
When call echo ok
|
||||||
|
The stdout should eq ok
|
||||||
|
End
|
||||||
|
|
||||||
|
Example 'skip2'
|
||||||
|
conditions() { echo "skip"; }
|
||||||
|
Skip if 'function returns "skip"' [ "$(conditions)" = "skip" ]
|
||||||
|
When call echo ok
|
||||||
|
The stdout should eq ok
|
||||||
|
End
|
||||||
|
End
|
||||||
|
End
|
||||||
22
test/spec/13.pending_spec.sh
Normal file
22
test/spec/13.pending_spec.sh
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# shellcheck shell=sh
|
||||||
|
|
||||||
|
# Pending is better than skip in some case. Skip is just only skips,
|
||||||
|
# but Pending is runs example and decide the success or failure.
|
||||||
|
# The pend example success if the expectations fails as expected.
|
||||||
|
# The pend example fails if the expectation succeeds unexpectedly.
|
||||||
|
|
||||||
|
Describe 'pending example'
|
||||||
|
Example 'this example not fails (because it is not yet implemented as expected)'
|
||||||
|
Pending 'not yet implemented'
|
||||||
|
echo_ok() { :; } # not yet implemented
|
||||||
|
When call echo_ok
|
||||||
|
The output should eq "ok"
|
||||||
|
End
|
||||||
|
|
||||||
|
Example 'this example fails (because it is implemented as unexpected)'
|
||||||
|
Pending 'not yet implemented'
|
||||||
|
echo_ok() { echo ok; } # implemented
|
||||||
|
When call echo_ok
|
||||||
|
The output should eq "ok"
|
||||||
|
End
|
||||||
|
End
|
||||||
13
test/spec/14.include_helper_spec.sh
Normal file
13
test/spec/14.include_helper_spec.sh
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# shellcheck shell=sh
|
||||||
|
|
||||||
|
Describe 'include helper example'
|
||||||
|
Describe 'include helper'
|
||||||
|
# Include helper is include external file immediately.
|
||||||
|
Include ./lib.sh
|
||||||
|
|
||||||
|
Example 'include external file'
|
||||||
|
When call calc 1 + 2
|
||||||
|
The output should eq 3
|
||||||
|
End
|
||||||
|
End
|
||||||
|
End
|
||||||
85
test/spec/15.data_helper_spec.sh
Normal file
85
test/spec/15.data_helper_spec.sh
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
# shellcheck shell=sh disable=SC2016
|
||||||
|
|
||||||
|
# Data helper is easy way to input data from stdin for evaluation.
|
||||||
|
# Removes `#|` from the beginning of the each line in the Data helper,
|
||||||
|
# the rest is the input data.
|
||||||
|
|
||||||
|
Describe 'Data helper'
|
||||||
|
Example 'provide with Data helper block style'
|
||||||
|
Data
|
||||||
|
#|item1 123
|
||||||
|
#|item2 456
|
||||||
|
#|item3 789
|
||||||
|
End
|
||||||
|
When call awk '{total+=$2} END{print total}'
|
||||||
|
The output should eq 1368
|
||||||
|
End
|
||||||
|
|
||||||
|
Example 'provide string with Data helper'
|
||||||
|
Data '123 + 456 + 789'
|
||||||
|
When call bc
|
||||||
|
The output should eq 1368
|
||||||
|
End
|
||||||
|
|
||||||
|
Example 'provide from function with Data helper'
|
||||||
|
data() {
|
||||||
|
echo item1 123
|
||||||
|
echo item2 456
|
||||||
|
echo item3 789
|
||||||
|
}
|
||||||
|
Data data
|
||||||
|
When call awk '{total+=$2} END{print total}'
|
||||||
|
The output should eq 1368
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe 'Data helper with filter'
|
||||||
|
Example 'from block'
|
||||||
|
Data | tr 'abc' 'ABC'
|
||||||
|
#|aaa
|
||||||
|
#|bbb
|
||||||
|
End
|
||||||
|
|
||||||
|
When call cat -
|
||||||
|
The first line of output should eq 'AAA'
|
||||||
|
The second line of output should eq 'BBB'
|
||||||
|
End
|
||||||
|
|
||||||
|
Example 'from function'
|
||||||
|
foo() { printf '%s\n' "$@"; }
|
||||||
|
Data foo a b c | tr 'abc' 'ABC' # comment
|
||||||
|
When call cat -
|
||||||
|
The first line of output should eq 'A'
|
||||||
|
The second line of output should eq 'B'
|
||||||
|
The third line of output should eq "C"
|
||||||
|
The lines of entire output should eq 3
|
||||||
|
End
|
||||||
|
|
||||||
|
Example 'from string'
|
||||||
|
Data 'abc' | tr 'abc' 'ABC' # comment
|
||||||
|
When call cat -
|
||||||
|
The output should eq ABC
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe 'variable expansion'
|
||||||
|
Before 'item=123'
|
||||||
|
|
||||||
|
Example 'not expand variable (default)'
|
||||||
|
Data:raw
|
||||||
|
#|item $item
|
||||||
|
End
|
||||||
|
When call cat -
|
||||||
|
The output should eq 'item $item'
|
||||||
|
End
|
||||||
|
|
||||||
|
Example 'expand variable'
|
||||||
|
Data:expand
|
||||||
|
#|item $item
|
||||||
|
End
|
||||||
|
When call cat -
|
||||||
|
The output should eq 'item 123'
|
||||||
|
End
|
||||||
|
|
||||||
|
# variable expansion is supported by block style only.
|
||||||
|
End
|
||||||
|
End
|
||||||
93
test/spec/16.text_directive_spec.sh
Normal file
93
test/spec/16.text_directive_spec.sh
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
# shellcheck shell=sh disable=SC2016
|
||||||
|
|
||||||
|
# %text directive is easy way to output text like here document.
|
||||||
|
# Removes `#|` from the beginning of the each line in the %text directive,
|
||||||
|
# the rest is the output text.
|
||||||
|
|
||||||
|
Describe '%text directive'
|
||||||
|
It 'outputs texts'
|
||||||
|
output() {
|
||||||
|
echo "start" # you can write code here
|
||||||
|
%text
|
||||||
|
#|aaa
|
||||||
|
#|bbb
|
||||||
|
#|ccc
|
||||||
|
echo "end" # you can write code here
|
||||||
|
}
|
||||||
|
When call output
|
||||||
|
The line 1 of output should eq 'start'
|
||||||
|
The line 2 of output should eq 'aaa'
|
||||||
|
The line 3 of output should eq 'bbb'
|
||||||
|
The line 4 of output should eq "ccc"
|
||||||
|
The line 5 of output should eq 'end'
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'sets to variable'
|
||||||
|
output() {
|
||||||
|
texts=$(
|
||||||
|
%text
|
||||||
|
#|aaa
|
||||||
|
#|bbb
|
||||||
|
#|ccc
|
||||||
|
)
|
||||||
|
echo "$texts"
|
||||||
|
}
|
||||||
|
When call output
|
||||||
|
The line 1 of output should eq 'aaa'
|
||||||
|
The line 2 of output should eq 'bbb'
|
||||||
|
The line 3 of output should eq "ccc"
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'outputs texts with filter'
|
||||||
|
output() {
|
||||||
|
%text | tr 'a-z_' 'A-Z_'
|
||||||
|
#|abc
|
||||||
|
}
|
||||||
|
When call output
|
||||||
|
The output should eq 'ABC'
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe 'variable expansion'
|
||||||
|
Before 'text=abc'
|
||||||
|
|
||||||
|
Example 'not expand variable (default)'
|
||||||
|
output() {
|
||||||
|
%text:raw
|
||||||
|
#|$text
|
||||||
|
}
|
||||||
|
When call output
|
||||||
|
The output should eq '$text'
|
||||||
|
End
|
||||||
|
|
||||||
|
Example 'expand variable'
|
||||||
|
output() {
|
||||||
|
%text:expand
|
||||||
|
#|$text
|
||||||
|
}
|
||||||
|
When call output
|
||||||
|
The output should eq 'abc'
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
It 'outputs texts with more complex code'
|
||||||
|
output() {
|
||||||
|
if true; then
|
||||||
|
set -- 1 2 3 4 5
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
%text:expand | tr 'a-z_' 'A-Z_'
|
||||||
|
#|value $(($1 * 10))
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
else
|
||||||
|
%text
|
||||||
|
#|text
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
When call output
|
||||||
|
The line 1 of output should eq 'VALUE 10'
|
||||||
|
The line 2 of output should eq 'VALUE 20'
|
||||||
|
The line 3 of output should eq 'VALUE 30'
|
||||||
|
The line 4 of output should eq "VALUE 40"
|
||||||
|
The line 5 of output should eq 'VALUE 50'
|
||||||
|
End
|
||||||
|
End
|
||||||
34
test/spec/17.putsn_puts_directive_spec.sh
Normal file
34
test/spec/17.putsn_puts_directive_spec.sh
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# shellcheck shell=sh
|
||||||
|
|
||||||
|
Describe '%putsn directive'
|
||||||
|
Example 'outputs arguments'
|
||||||
|
foo() { %putsn value; }
|
||||||
|
When call foo
|
||||||
|
The entire output should eq "value${SHELLSPEC_LF}"
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe '%= directive'
|
||||||
|
Example 'is alias for %putsn'
|
||||||
|
# shellcheck disable=SC2276
|
||||||
|
foo() { %= value; }
|
||||||
|
When call foo
|
||||||
|
The entire output should eq "value${SHELLSPEC_LF}"
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe '%puts directive'
|
||||||
|
Example 'outputs arguments without last <LF>'
|
||||||
|
foo() { %puts value; }
|
||||||
|
When call foo
|
||||||
|
The entire output should eq "value"
|
||||||
|
End
|
||||||
|
End
|
||||||
|
|
||||||
|
Describe '%- directive'
|
||||||
|
Example 'is alias for %puts'
|
||||||
|
foo() { %- value; }
|
||||||
|
When call foo
|
||||||
|
The entire output should eq "value"
|
||||||
|
End
|
||||||
|
End
|
||||||
29
test/spec/18.const_directive_spec.sh
Normal file
29
test/spec/18.const_directive_spec.sh
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# shellcheck shell=sh
|
||||||
|
|
||||||
|
%const NAME: value
|
||||||
|
# shellcheck disable=SC2288
|
||||||
|
% MAJOR_VERSION: "${SHELLSPEC_VERSION%%.*}"
|
||||||
|
# % OK: "$(echo_ok)" # echo_ok not found
|
||||||
|
|
||||||
|
# %const (% is short hand) directive is define constant value.
|
||||||
|
# The characters that can be used for variable name is upper capital, number
|
||||||
|
# and underscore only. It cannot be define inside of the example group or
|
||||||
|
# the example.
|
||||||
|
#
|
||||||
|
# The timing of evaluation of the value is the specfile translation process.
|
||||||
|
# So you can access shellspec variables, but you cannot access variable or
|
||||||
|
# function in the specfile.
|
||||||
|
#
|
||||||
|
# This feature assumed use with conditional skip. The conditional skip may runs
|
||||||
|
# outside of the examples. As a result, sometime you may need variables defined
|
||||||
|
# outside of the examples.
|
||||||
|
|
||||||
|
Describe '%const directive'
|
||||||
|
echo_ok() { echo ok; }
|
||||||
|
version_check() { [ "$MAJOR_VERSION" -lt "$1" ]; }
|
||||||
|
|
||||||
|
Skip if 'too old version' version_check 1
|
||||||
|
Example
|
||||||
|
The variable NAME should eq 'value'
|
||||||
|
End
|
||||||
|
End
|
||||||
8
test/spec/19.logger_directive_spec.sh
Normal file
8
test/spec/19.logger_directive_spec.sh
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# shellcheck shell=sh
|
||||||
|
|
||||||
|
Describe 'Logger helper'
|
||||||
|
%logger 'this is log'
|
||||||
|
Example 'outputs log'
|
||||||
|
%logger 'this is log'
|
||||||
|
End
|
||||||
|
End
|
||||||
18
test/spec/20.mock_stub_spec.sh
Normal file
18
test/spec/20.mock_stub_spec.sh
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# shellcheck shell=sh
|
||||||
|
|
||||||
|
Describe 'mock stub example'
|
||||||
|
unixtime() { date +%s; }
|
||||||
|
get_next_day() { echo $(($(unixtime) + 86400)); }
|
||||||
|
|
||||||
|
Example 'redefine date command'
|
||||||
|
date() { echo 1546268400; }
|
||||||
|
When call get_next_day
|
||||||
|
The stdout should eq 1546354800
|
||||||
|
End
|
||||||
|
|
||||||
|
Example 'use the date command'
|
||||||
|
# Date is not redefined because this is another subshell
|
||||||
|
When call unixtime
|
||||||
|
The stdout should not eq 1546268400
|
||||||
|
End
|
||||||
|
End
|
||||||
36
test/spec/21.intercept_spec.sh
Normal file
36
test/spec/21.intercept_spec.sh
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# shellcheck shell=sh
|
||||||
|
|
||||||
|
Describe 'intercept example'
|
||||||
|
Intercept begin
|
||||||
|
__begin__() {
|
||||||
|
# Define stubs for cat
|
||||||
|
cat() {
|
||||||
|
if [ "${1:-}" = "/proc/cpuinfo" ];then
|
||||||
|
%text
|
||||||
|
#|processor : 0
|
||||||
|
#|vendor_id : GenuineIntel
|
||||||
|
#|cpu family : 6
|
||||||
|
#|model : 58
|
||||||
|
#|model name : Intel(R) Core(TM) i7-3770 CPU @ 3.40GHz
|
||||||
|
#|
|
||||||
|
#|processor : 1
|
||||||
|
#|vendor_id : GenuineIntel
|
||||||
|
#|cpu family : 6
|
||||||
|
#|model : 58
|
||||||
|
#|model name : Intel(R) Core(TM) i7-3770 CPU @ 3.40GHz
|
||||||
|
#|
|
||||||
|
#|processor : 2
|
||||||
|
#|vendor_id : GenuineIntel
|
||||||
|
#|cpu family : 6
|
||||||
|
#|model : 58
|
||||||
|
#|model name : Intel(R) Core(TM) i7-3770 CPU @ 3.40GHz
|
||||||
|
else
|
||||||
|
command cat "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Example 'test cpunum.sh with stubbed cat /cpu/info'
|
||||||
|
When run source ./count_cpus.sh
|
||||||
|
The stdout should eq 3
|
||||||
|
End
|
||||||
|
End
|
||||||
35
test/spec/22.sourced_spec.sh
Normal file
35
test/spec/22.sourced_spec.sh
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# shellcheck shell=sh
|
||||||
|
|
||||||
|
# Sometimes, functions are defined in a single shell script.
|
||||||
|
# You will want to test it. but you do not want to run the script.
|
||||||
|
# You want to test only the function, right?
|
||||||
|
Describe 'sourced return example'
|
||||||
|
Include ./count_lines.sh
|
||||||
|
|
||||||
|
Example 'test count_lines with stubbed data'
|
||||||
|
Data
|
||||||
|
#|1
|
||||||
|
#|2
|
||||||
|
#|3
|
||||||
|
#|4
|
||||||
|
#|5
|
||||||
|
End
|
||||||
|
|
||||||
|
When call count_lines
|
||||||
|
The stdout should eq 5
|
||||||
|
End
|
||||||
|
|
||||||
|
Example 'test count_lines with stubbed data'
|
||||||
|
Data data
|
||||||
|
data() {
|
||||||
|
%putsn "line1"
|
||||||
|
%putsn "line2"
|
||||||
|
%putsn "line3"
|
||||||
|
%putsn "line4"
|
||||||
|
%puts "line5 (without newline)"
|
||||||
|
}
|
||||||
|
|
||||||
|
When call count_lines
|
||||||
|
The stdout should eq 5
|
||||||
|
End
|
||||||
|
End
|
||||||
14
test/spec/23.custom_matcher_spec.sh
Normal file
14
test/spec/23.custom_matcher_spec.sh
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# shellcheck shell=sh
|
||||||
|
|
||||||
|
# regexp custom matcher is defined in "support/custom_matcher.sh" and
|
||||||
|
# imported by "spec_helper.sh"
|
||||||
|
|
||||||
|
Describe 'custom matcher'
|
||||||
|
Describe 'regexp'
|
||||||
|
number() { echo 12345; }
|
||||||
|
It 'checks with regular expression'
|
||||||
|
When call number
|
||||||
|
The output should regexp '[0-9]*$'
|
||||||
|
End
|
||||||
|
End
|
||||||
|
End
|
||||||
4
test/spec/count_cpus.sh
Normal file
4
test/spec/count_cpus.sh
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# shellcheck shell=sh
|
||||||
|
# Stub script referenced by 21.intercept_spec.sh
|
||||||
|
|
||||||
|
cat /proc/cpuinfo | grep -c '^processor'
|
||||||
6
test/spec/lib.sh
Normal file
6
test/spec/lib.sh
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# shellcheck shell=sh
|
||||||
|
# Stub library referenced by 01.very_simple_spec.sh
|
||||||
|
|
||||||
|
calc() {
|
||||||
|
eval "echo \$(( $1 $2 $3 ))"
|
||||||
|
}
|
||||||
7
test/spec/spec_helper.sh
Normal file
7
test/spec/spec_helper.sh
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# shellcheck shell=sh
|
||||||
|
|
||||||
|
# set -eu
|
||||||
|
|
||||||
|
spec_helper_configure() {
|
||||||
|
import 'support/custom_matcher'
|
||||||
|
}
|
||||||
28
test/spec/support/custom_matcher.sh
Normal file
28
test/spec/support/custom_matcher.sh
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# shellcheck shell=sh
|
||||||
|
|
||||||
|
# imported by "spec_helper.sh"
|
||||||
|
|
||||||
|
shellspec_syntax 'shellspec_matcher_regexp'
|
||||||
|
|
||||||
|
shellspec_matcher_regexp() {
|
||||||
|
shellspec_matcher__match() {
|
||||||
|
SHELLSPEC_EXPECT="$1"
|
||||||
|
[ "${SHELLSPEC_SUBJECT+x}" ] || return 1
|
||||||
|
expr "$SHELLSPEC_SUBJECT" : "$SHELLSPEC_EXPECT" >/dev/null || return 1
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Message when the matcher fails with "should"
|
||||||
|
shellspec_matcher__failure_message() {
|
||||||
|
shellspec_putsn "expected: $1 match $2"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Message when the matcher fails with "should not"
|
||||||
|
shellspec_matcher__failure_message_when_negated() {
|
||||||
|
shellspec_putsn "expected: $1 not match $2"
|
||||||
|
}
|
||||||
|
|
||||||
|
# checking for parameter count
|
||||||
|
shellspec_syntax_param count [ $# -eq 1 ] || return 0
|
||||||
|
shellspec_matcher_do_match "$@"
|
||||||
|
}
|
||||||
38
tree-sitter.json
Normal file
38
tree-sitter.json
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"grammars": [
|
||||||
|
{
|
||||||
|
"name": "shellspec",
|
||||||
|
"camelcase": "Shellspec",
|
||||||
|
"scope": "source.shellspec",
|
||||||
|
"path": ".",
|
||||||
|
"file-types": [
|
||||||
|
"shellspec"
|
||||||
|
],
|
||||||
|
"highlights": [
|
||||||
|
"queries/highlights.scm"
|
||||||
|
],
|
||||||
|
"injection-regex": "shellspec"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"version": "0.1.0",
|
||||||
|
"license": "MIT",
|
||||||
|
"description": "ShellSpec grammar for tree-sitter (extends bash)",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Ismo Vuorinen"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": {
|
||||||
|
"repository": "https://github.com/ivuorinen/tree-sitter-shellspec"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bindings": {
|
||||||
|
"c": true,
|
||||||
|
"go": true,
|
||||||
|
"node": true,
|
||||||
|
"python": true,
|
||||||
|
"rust": true,
|
||||||
|
"swift": true
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user