4 Commits

8 changed files with 972 additions and 19 deletions

View File

@@ -0,0 +1,37 @@
# Development Commands
## Key Make Targets
- `make help` - Show all available targets with descriptions
- `make check` - Quick health check (tools and version consistency)
- `make test` - Run complete test suite
- `make lint` - Run all linters
- `make format` - Format all code (auto-fix where possible)
- `make ci` - Full CI pipeline (check, test, lint)
- `make clean` - Remove temporary files
- `make dev-setup` - Set up development environment
## Testing
- `make test-unit` - Lua unit tests only
- `make test-integration` - Integration tests
- `make test-golden` - Golden master tests
- `make test-bin` - Standalone formatter tests
- Test runner: `./tests/run_tests.sh`
## Linting
- Uses pre-commit hooks
- ShellCheck for shell scripts
- StyLua for Lua formatting
- markdownlint for Markdown
- yamllint for YAML files
- shfmt for shell script formatting
## Version Management
- Three files must stay in sync:
- `lua/shellspec/init.lua` (M._VERSION)
- `plugin/shellspec.vim` (g:shellspec_version)
- `bin/shellspec-format` (version string)
- `make version-check` verifies consistency

View File

@@ -0,0 +1,31 @@
# Release Process
## Command
Always use `make release` for releases, not manual version bumping.
## Available Release Commands
- `make release` - Interactive release with menu (patch/minor/major)
- `make release-patch` - Bump patch version (X.Y.Z → X.Y.Z+1)
- `make release-minor` - Bump minor version (X.Y.Z → X.Y+1.0)
- `make release-major` - Bump major version (X.Y.Z → X+1.0.0)
## What make release does
1. Checks git status is clean
2. Verifies version consistency across files
3. Runs complete test suite
4. Runs all linters
5. Calculates and prompts for new version
6. Updates versions in all files:
- `lua/shellspec/init.lua` - M._VERSION
- `plugin/shellspec.vim` - g:shellspec_version
- `bin/shellspec-format` - version string
7. Creates git commit with version bump
8. Creates git tag (with v prefix, e.g., v2.0.3)
9. Provides next steps for pushing
## Manual Process (DO NOT USE)
The old manual process was error-prone and didn't update all version files consistently.

290
Makefile Normal file
View File

@@ -0,0 +1,290 @@
# Makefile for nvim-shellspec
# Provides help, linting, testing, and release functionality
# Colors for output
RED := \033[0;31m
GREEN := \033[0;32m
YELLOW := \033[1;33m
BLUE := \033[0;34m
NC := \033[0m # No Color
# Version files
VERSION_LUA := lua/shellspec/init.lua
VERSION_VIM := plugin/shellspec.vim
VERSION_BIN := bin/shellspec-format
# Commands
MAKE := make
PRE_COMMIT := pre-commit
TEST_RUNNER := ./tests/run_tests.sh
# Default target
.PHONY: help
help: ## Display this help message
@echo "$(BLUE)nvim-shellspec Makefile$(NC)"
@echo "$(BLUE)==========================================$(NC)"
@echo ""
@echo "$(GREEN)Available targets:$(NC)"
@echo ""
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf " $(YELLOW)%-20s$(NC) %s\n", $$1, $$2}' $(MAKEFILE_LIST)
@echo ""
@echo "$(GREEN)Current versions:$(NC)"
@$(MAKE) --no-print-directory version-check
@echo ""
@echo "$(GREEN)Usage examples:$(NC)"
@echo " $(YELLOW)make test$(NC) # Run all tests"
@echo " $(YELLOW)make lint$(NC) # Run all linters"
@echo " $(YELLOW)make release-patch$(NC) # Bump patch version and create tag"
@echo ""
.PHONY: check
check: ## Quick health check (verify tools and version consistency)
@echo "$(BLUE)Running health check...$(NC)"
@echo ""
@echo "$(GREEN)Checking required tools:$(NC)"
@which pre-commit >/dev/null 2>&1 && echo " ✓ pre-commit found" || echo " $(RED)✗ pre-commit not found$(NC)"
@which git >/dev/null 2>&1 && echo " ✓ git found" || echo " $(RED)✗ git not found$(NC)"
@which bash >/dev/null 2>&1 && echo " ✓ bash found" || echo " $(RED)✗ bash not found$(NC)"
@test -f $(TEST_RUNNER) && echo " ✓ test runner found" || echo " $(RED)✗ test runner not found$(NC)"
@echo ""
@echo "$(GREEN)Version consistency:$(NC)"
@$(MAKE) --no-print-directory version-check
@echo ""
.PHONY: version
version: version-check ## Display current versions
.PHONY: version-check
version-check: ## Check version consistency across files
@echo "$(GREEN)Version information:$(NC)"
@lua_version=$$(grep '_VERSION = ' $(VERSION_LUA) | sed 's/.*"\(.*\)".*/\1/'); \
vim_version=$$(grep "g:shellspec_version = " $(VERSION_VIM) | sed "s/.*'\(.*\)'.*/\1/"); \
bin_version=$$(grep 'echo "shellspec-format ' $(VERSION_BIN) | sed 's/.*shellspec-format \([0-9.]*\).*/\1/'); \
echo " Lua module: $$lua_version"; \
echo " VimScript: $$vim_version"; \
echo " Binary script: $$bin_version"; \
if [ "$$lua_version" = "$$vim_version" ] && [ "$$vim_version" = "$$bin_version" ]; then \
echo " $(GREEN)✓ All versions match$(NC)"; \
else \
echo " $(RED)✗ Version mismatch detected$(NC)"; \
exit 1; \
fi
# Linting targets
.PHONY: lint
lint: ## Run all linters
@echo "$(BLUE)Running all linters...$(NC)"
$(PRE_COMMIT) run --all-files
.PHONY: lint-fix
lint-fix: format ## Run linters with auto-fix (alias for format)
.PHONY: format
format: ## Format all code (auto-fix where possible)
@echo "$(BLUE)Formatting all code...$(NC)"
$(PRE_COMMIT) run --all-files
.PHONY: lint-lua
lint-lua: ## Format Lua code with StyLua
@echo "$(BLUE)Formatting Lua code...$(NC)"
$(PRE_COMMIT) run stylua-github --all-files
.PHONY: lint-shell
lint-shell: ## Lint shell scripts with ShellCheck and format with shfmt
@echo "$(BLUE)Linting shell scripts...$(NC)"
$(PRE_COMMIT) run shellcheck --all-files
$(PRE_COMMIT) run shfmt --all-files
.PHONY: lint-markdown
lint-markdown: ## Lint and format Markdown files
@echo "$(BLUE)Linting Markdown files...$(NC)"
$(PRE_COMMIT) run markdownlint --all-files
.PHONY: lint-yaml
lint-yaml: ## Lint YAML files
@echo "$(BLUE)Linting YAML files...$(NC)"
$(PRE_COMMIT) run yamllint --all-files
# Testing targets
.PHONY: test
test: ## Run complete test suite
@echo "$(BLUE)Running complete test suite...$(NC)"
$(TEST_RUNNER)
.PHONY: test-unit
test-unit: ## Run only Lua unit tests
@echo "$(BLUE)Running unit tests...$(NC)"
cd tests && timeout 30 nvim --headless -u NONE -c "set rtp+=.." -c "luafile format_spec.lua" -c "quit"
.PHONY: test-integration
test-integration: ## Run integration tests
@echo "$(BLUE)Running integration tests...$(NC)"
cd tests && timeout 30 ./integration_test.sh
.PHONY: test-golden
test-golden: ## Run golden master tests
@echo "$(BLUE)Running golden master tests...$(NC)"
cd tests && timeout 30 ./golden_master_test.sh
.PHONY: test-bin
test-bin: ## Run standalone formatter tests
@echo "$(BLUE)Running standalone formatter tests...$(NC)"
cd tests && ./bin_format_spec.sh
# Release targets
.PHONY: release
release: ## Interactive release (prompts for version type)
@echo "$(BLUE)Interactive Release$(NC)"
@echo ""
@echo "Select release type:"
@echo " 1) $(GREEN)patch$(NC) (2.0.0 → 2.0.1) - Bug fixes"
@echo " 2) $(YELLOW)minor$(NC) (2.0.0 → 2.1.0) - New features"
@echo " 3) $(RED)major$(NC) (2.0.0 → 3.0.0) - Breaking changes"
@echo ""
@read -p "Enter choice (1-3): " choice; \
case $$choice in \
1) $(MAKE) release-patch ;; \
2) $(MAKE) release-minor ;; \
3) $(MAKE) release-major ;; \
*) echo "$(RED)Invalid choice$(NC)"; exit 1 ;; \
esac
.PHONY: release-patch
release-patch: ## Bump patch version (X.Y.Z → X.Y.Z+1)
@$(MAKE) --no-print-directory _release TYPE=patch
.PHONY: release-minor
release-minor: ## Bump minor version (X.Y.Z → X.Y+1.0)
@$(MAKE) --no-print-directory _release TYPE=minor
.PHONY: release-major
release-major: ## Bump major version (X.Y.Z → X+1.0.0)
@$(MAKE) --no-print-directory _release TYPE=major
.PHONY: _release
_release: ## Internal release target (use release-* targets instead)
@if [ "$(TYPE)" = "" ]; then echo "$(RED)Error: TYPE not specified$(NC)"; exit 1; fi
@echo "$(BLUE)Starting $(TYPE) release...$(NC)"
@echo ""
# Check git status
@echo "$(GREEN)Checking git status...$(NC)"
@if [ -n "$$(git status --porcelain)" ]; then \
echo "$(RED)Error: Working directory not clean$(NC)"; \
git status --short; \
exit 1; \
fi
@echo " ✓ Working directory is clean"
# Check version consistency
@echo ""
@echo "$(GREEN)Checking version consistency...$(NC)"
@$(MAKE) --no-print-directory version-check
# Run tests
@echo ""
@echo "$(GREEN)Running tests...$(NC)"
@$(MAKE) --no-print-directory test
# Run linters
@echo ""
@echo "$(GREEN)Running linters...$(NC)"
@$(MAKE) --no-print-directory lint
# Calculate new version
@echo ""
@echo "$(GREEN)Calculating new version...$(NC)"
@current_version=$$(grep '_VERSION = ' $(VERSION_LUA) | sed 's/.*"\(.*\)".*/\1/'); \
echo " Current version: $$current_version"; \
new_version=$$(echo "$$current_version" | awk -F. -v type=$(TYPE) '{ \
if (type == "major") printf "%d.0.0", $$1+1; \
else if (type == "minor") printf "%d.%d.0", $$1, $$2+1; \
else if (type == "patch") printf "%d.%d.%d", $$1, $$2, $$3+1; \
}'); \
echo " New version: $$new_version"; \
echo ""; \
read -p "Continue with release? (y/N): " confirm; \
if [ "$$confirm" != "y" ] && [ "$$confirm" != "Y" ]; then \
echo "$(YELLOW)Release cancelled$(NC)"; \
exit 1; \
fi; \
echo ""; \
echo "$(GREEN)Updating version in files...$(NC)"; \
sed -i.bak "s/M._VERSION = \".*\"/M._VERSION = \"$$new_version\"/" $(VERSION_LUA) && rm $(VERSION_LUA).bak; \
sed -i.bak "s/let g:shellspec_version = '.*'/let g:shellspec_version = '$$new_version'/" $(VERSION_VIM) && rm $(VERSION_VIM).bak; \
sed -i.bak "s/shellspec-format [0-9.]*/shellspec-format $$new_version/" $(VERSION_BIN) && rm $(VERSION_BIN).bak; \
echo " ✓ Updated $(VERSION_LUA)"; \
echo " ✓ Updated $(VERSION_VIM)"; \
echo " ✓ Updated $(VERSION_BIN)"; \
echo ""; \
echo "$(GREEN)Creating git commit...$(NC)"; \
git add $(VERSION_LUA) $(VERSION_VIM) $(VERSION_BIN); \
git commit -m "chore: bump version to $$new_version"; \
echo " ✓ Created commit"; \
echo ""; \
echo "$(GREEN)Creating git tag...$(NC)"; \
git tag -a "v$$new_version" -m "Release version $$new_version"; \
echo " ✓ Created tag v$$new_version"; \
echo ""; \
echo "$(GREEN)$(TYPE) release completed successfully!$(NC)"; \
echo ""; \
echo "$(BLUE)Next steps:$(NC)"; \
echo " 1. Review the changes: $(YELLOW)git show$(NC)"; \
echo " 2. Push the release: $(YELLOW)git push origin main --tags$(NC)"; \
echo " 3. Create GitHub release from tag v$$new_version"; \
echo ""
# Utility targets
.PHONY: clean
clean: ## Remove temporary files and test artifacts
@echo "$(BLUE)Cleaning temporary files...$(NC)"
find . -name "*.bak" -delete
find . -name "*.tmp" -delete
find /tmp -name "*shellspec*" -delete 2>/dev/null || true
find /var/folders -name "*shellspec*" -delete 2>/dev/null || true
@echo " ✓ Cleaned temporary files"
.PHONY: install
install: ## Install pre-commit hooks
@echo "$(BLUE)Installing pre-commit hooks...$(NC)"
$(PRE_COMMIT) install
@echo " ✓ Pre-commit hooks installed"
# Development convenience targets
.PHONY: dev-setup
dev-setup: install ## Set up development environment
@echo "$(BLUE)Setting up development environment...$(NC)"
@$(MAKE) --no-print-directory check
@echo ""
@echo "$(GREEN)Development environment ready!$(NC)"
.PHONY: ci
ci: check test lint ## Run CI pipeline (check, test, lint)
@echo ""
@echo "$(GREEN)CI pipeline completed successfully!$(NC)"
# Debug targets
.PHONY: debug
debug: ## Show debug information
@echo "$(BLUE)Debug Information$(NC)"
@echo "$(BLUE)==================$(NC)"
@echo ""
@echo "$(GREEN)Environment:$(NC)"
@echo " PWD: $(PWD)"
@echo " SHELL: $(SHELL)"
@echo " MAKE: $(MAKE)"
@echo ""
@echo "$(GREEN)Git status:$(NC)"
@git status --short || echo " Not in git repository"
@echo ""
@echo "$(GREEN)Tools:$(NC)"
@echo " pre-commit: $$(which pre-commit || echo 'not found')"
@echo " git: $$(which git || echo 'not found')"
@echo " nvim: $$(which nvim || echo 'not found')"
@echo ""
@$(MAKE) --no-print-directory version-check
# Ensure all targets are PHONY (no file dependencies)
.PHONY: _release help check version version-check lint lint-fix format lint-lua lint-shell lint-markdown lint-yaml
.PHONY: test test-unit test-integration test-golden test-bin release release-patch release-minor release-major
.PHONY: clean install dev-setup ci debug

View File

@@ -1,46 +1,305 @@
#!/bin/bash #!/bin/bash
# Standalone ShellSpec DSL formatter # Enhanced ShellSpec DSL formatter with HEREDOC and comment support
# Matches functionality from the nvim-shellspec plugin
set -e
# Default configuration
INDENT_SIZE=2
USE_SPACES=1
INDENT_COMMENTS=1
DEBUG=0
# State constants
STATE_NORMAL=1
STATE_HEREDOC=2
# Usage information
usage() {
cat <<'EOF'
Usage: shellspec-format 2.0.2[OPTIONS] [FILE...]
Enhanced ShellSpec DSL formatter with HEREDOC preservation and smart comment indentation.
OPTIONS:
-h, --help Show this help message
-s, --indent-size SIZE Set indentation size (default: 2)
-t, --tabs Use tabs instead of spaces
-n, --no-comment-indent Don't indent comments
-d, --debug Enable debug output
-v, --version Show version information
If no files are specified, reads from stdin and writes to stdout.
If files are specified, formats them in place.
EXAMPLES:
shellspec-format 2.0.2< input.spec.sh > output.spec.sh
shellspec-format 2.0.2file1.spec.sh file2.spec.sh
cat file.spec.sh | shellspec-format 2.0.2--indent-size 4 --tabs
EOF
}
version() {
echo "shellspec-format 2.0.2"
echo "Part of nvim-shellspec plugin"
}
# Debug logging
debug_log() {
if [[ $DEBUG -eq 1 ]]; then
echo "DEBUG: $*" >&2
fi
}
# Detect HEREDOC start and return delimiter
detect_heredoc_start() {
local line="$1"
local trimmed
trimmed=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
# Check for various HEREDOC patterns
local patterns=(
"<<([A-Z_][A-Z0-9_]*)"
"<<'([^']*)'"
"<<\"([^\"]*)\""
"<<-([A-Z_][A-Z0-9_]*)"
)
for pattern in "${patterns[@]}"; do
if [[ $trimmed =~ $pattern ]]; then
echo "${BASH_REMATCH[1]}"
return 0
fi
done
return 1
}
# Check if line ends a HEREDOC
is_heredoc_end() {
local line="$1"
local delimiter="$2"
local trimmed
trimmed=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
[[ -n "$delimiter" && "$trimmed" == "$delimiter" ]]
}
# Check if line is a ShellSpec block keyword
is_block_keyword() {
local line="$1"
local trimmed
trimmed=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
debug_log "Checking if block keyword: '$trimmed'"
# Standard block keywords
if [[ $trimmed =~ ^(Describe|Context|ExampleGroup|It|Specify|Example)[[:space:]] ]]; then
debug_log "Matched standard block keyword: '$trimmed'"
return 0
fi
# Prefixed block keywords (x for skip, f for focus)
if [[ $trimmed =~ ^[xf](Describe|Context|ExampleGroup|It|Specify|Example)[[:space:]] ]]; then
debug_log "Matched prefixed block keyword: '$trimmed'"
return 0
fi
# Data and Parameters blocks
if [[ $trimmed =~ ^(Data|Parameters)[[:space:]]*$ ]]; then
debug_log "Matched data/parameters block: '$trimmed'"
return 0
fi
# Hook keywords that create blocks (can be standalone)
if [[ $trimmed =~ ^(BeforeEach|AfterEach|BeforeAll|AfterAll|Before|After)[[:space:]]*$ ]]; then
debug_log "Matched hook keyword: '$trimmed'"
return 0
fi
# Additional hook keywords (can be standalone)
if [[ $trimmed =~ ^(BeforeCall|AfterCall|BeforeRun|AfterRun)[[:space:]]*$ ]]; then
debug_log "Matched additional hook keyword: '$trimmed'"
return 0
fi
debug_log "Not a block keyword: '$trimmed'"
return 1
}
# Check if line is an End keyword
is_end_keyword() {
local line="$1"
local trimmed
trimmed=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
[[ $trimmed =~ ^End[[:space:]]*$ ]]
}
# Check if line is a comment
is_comment() {
local line="$1"
local trimmed
trimmed=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
[[ $trimmed =~ ^# ]]
}
# Generate indentation string
make_indent() {
local level="$1"
local total_indent=$((level * INDENT_SIZE))
if [[ $USE_SPACES -eq 1 ]]; then
printf "%*s" $total_indent ""
else
printf "%*s" $level "" | tr ' ' '\t'
fi
}
# Main formatting function
format_shellspec() { format_shellspec() {
local indent=0 local indent_level=0
local state=$STATE_NORMAL
local heredoc_delimiter=""
local line local line
while IFS= read -r line; do while IFS= read -r line; do
local trimmed local trimmed
trimmed=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') trimmed=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
# Skip empty lines and comments # Handle empty lines
if [[ -z "$trimmed" || "$trimmed" =~ ^# ]]; then if [[ -z "$trimmed" ]]; then
echo "$line" echo "$line"
continue continue
fi fi
# Decrease indent for End # State machine for HEREDOC handling
if [[ "$trimmed" =~ ^End[[:space:]]*$ ]]; then case $state in
((indent > 0)) && ((indent--)) "$STATE_NORMAL")
fi # Check for HEREDOC start
if heredoc_delimiter=$(detect_heredoc_start "$line"); then
state=$STATE_HEREDOC
debug_log "HEREDOC start detected: '$heredoc_delimiter'"
# Apply current indentation to HEREDOC start line
printf "%s%s\n" "$(make_indent $indent_level)" "$trimmed"
continue
fi
# Apply indentation # Handle End keyword (decrease indent first)
printf "%*s%s\n" $((indent * 2)) "" "$trimmed" if is_end_keyword "$line"; then
((indent_level > 0)) && ((indent_level--))
printf "%s%s\n" "$(make_indent $indent_level)" "$trimmed"
continue
fi
# Increase indent after block keywords # Handle comments
if [[ "$trimmed" =~ ^(Describe|Context|ExampleGroup|It|Specify|Example) ]] || if is_comment "$line"; then
[[ "$trimmed" =~ ^[xf](Describe|Context|ExampleGroup|It|Specify|Example) ]] || if [[ $INDENT_COMMENTS -eq 1 ]]; then
[[ "$trimmed" =~ ^(Data|Parameters)[[:space:]]*$ ]]; then printf "%s%s\n" "$(make_indent $indent_level)" "$trimmed"
((indent++)) else
fi # Preserve original comment formatting
echo "$line"
fi
continue
fi
# Handle non-comment lines (ShellSpec commands, etc.)
printf "%s%s\n" "$(make_indent $indent_level)" "$trimmed"
# Increase indent after block keywords
if is_block_keyword "$line"; then
((indent_level++))
debug_log "Block keyword detected: '$trimmed', new indent: $indent_level"
fi
;;
"$STATE_HEREDOC")
# Check for HEREDOC end
if is_heredoc_end "$line" "$heredoc_delimiter"; then
state=$STATE_NORMAL
debug_log "HEREDOC end detected: '$heredoc_delimiter'"
# Apply current indentation to HEREDOC end line
printf "%s%s\n" "$(make_indent $indent_level)" "$trimmed"
heredoc_delimiter=""
else
# Preserve original indentation within HEREDOC
echo "$line"
fi
;;
esac
done done
} }
# Parse command line options
while [[ $# -gt 0 ]]; do
case $1 in
-h | --help)
usage
exit 0
;;
-v | --version)
version
exit 0
;;
-s | --indent-size)
INDENT_SIZE="$2"
if ! [[ $INDENT_SIZE =~ ^[0-9]+$ ]] || [[ $INDENT_SIZE -lt 1 ]]; then
echo "Error: indent-size must be a positive integer" >&2
exit 1
fi
shift 2
;;
-t | --tabs)
USE_SPACES=0
shift
;;
-n | --no-comment-indent)
INDENT_COMMENTS=0
shift
;;
-d | --debug)
DEBUG=1
shift
;;
--)
shift
break
;;
-*)
echo "Error: Unknown option $1" >&2
echo "Use --help for usage information" >&2
exit 1
;;
*)
break
;;
esac
done
# Main execution # Main execution
if [[ $# -eq 0 ]]; then if [[ $# -eq 0 ]]; then
# Read from stdin, write to stdout
debug_log "Reading from stdin"
format_shellspec format_shellspec
else else
# Process files in place
for file in "$@"; do for file in "$@"; do
if [[ -f "$file" ]]; then if [[ -f "$file" ]]; then
format_shellspec <"$file" >"${file}.tmp" && mv "${file}.tmp" "$file" debug_log "Processing file: $file"
temp_file=$(mktemp)
if format_shellspec <"$file" >"$temp_file"; then
mv "$temp_file" "$file"
debug_log "Successfully formatted: $file"
else
rm -f "$temp_file"
echo "Error: Failed to format $file" >&2
exit 1
fi
else else
echo "Error: File not found: $file" >&2 echo "Error: File not found: $file" >&2
exit 1
fi fi
done done
fi fi

View File

@@ -7,7 +7,7 @@ local format = require("shellspec.format")
local autocmds = require("shellspec.autocmds") local autocmds = require("shellspec.autocmds")
-- Version info -- Version info
M._VERSION = "2.0.0" M._VERSION = "2.0.2"
-- Setup function for Lua configuration -- Setup function for Lua configuration
function M.setup(opts) function M.setup(opts)

View File

@@ -8,6 +8,9 @@ if exists('g:loaded_shellspec')
endif endif
let g:loaded_shellspec = 1 let g:loaded_shellspec = 1
" Version information
let g:shellspec_version = '2.0.2'
" Detect Neovim and use appropriate implementation " Detect Neovim and use appropriate implementation
if has('nvim-0.7') if has('nvim-0.7')
" Use modern Neovim Lua implementation " Use modern Neovim Lua implementation

323
tests/bin_format_spec.sh Executable file
View File

@@ -0,0 +1,323 @@
#!/bin/bash
# Unit tests for bin/shellspec-format standalone formatter
# Tests the CLI formatter against the same test cases used for Lua implementation
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Test counters
TESTS_PASSED=0
TESTS_FAILED=0
# Get the script directory and project root
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
FORMATTER="$PROJECT_ROOT/bin/shellspec-format"
# Helper functions
print_test() {
echo -e "${YELLOW}[BIN-TEST]${NC} $1"
# Force flush
exec 1>&1
}
print_pass() {
echo -e "${GREEN}[PASS]${NC} $1"
((TESTS_PASSED++))
# Force flush
exec 1>&1
}
print_fail() {
echo -e "${RED}[FAIL]${NC} $1"
((TESTS_FAILED++))
# Force flush
exec 1>&1
}
print_summary() {
echo ""
echo "Standalone Formatter Test Results:"
echo " Passed: $TESTS_PASSED"
echo " Failed: $TESTS_FAILED"
echo " Total: $((TESTS_PASSED + TESTS_FAILED))"
if [ $TESTS_FAILED -gt 0 ]; then
echo -e "${RED}Some standalone formatter tests failed!${NC}"
exit 1
else
echo -e "${GREEN}All standalone formatter tests passed!${NC}"
fi
}
# Function to run a formatting test
run_format_test() {
local test_name="$1"
local input_content="$2"
local expected_content="$3"
print_test "Testing $test_name"
# Create temporary files
local input_file
local expected_file
local actual_file
input_file=$(mktemp -t "bin_format_input_XXXXXX.spec.sh")
expected_file=$(mktemp -t "bin_format_expected_XXXXXX.spec.sh")
actual_file=$(mktemp -t "bin_format_actual_XXXXXX.spec.sh")
# Debug: Show what we're testing
if [[ -n "${DEBUG:-}" ]]; then
echo " Input file: $input_file"
echo " Expected file: $expected_file"
echo " Actual file: $actual_file"
fi
# Write test data to files
printf "%s\n" "$input_content" >"$input_file"
printf "%s\n" "$expected_content" >"$expected_file"
# Format using the standalone formatter
if timeout 10 "$FORMATTER" <"$input_file" >"$actual_file" 2>/dev/null; then
# Compare with expected output
if diff -u "$expected_file" "$actual_file" >/dev/null; then
print_pass "$test_name formatting matches expected output"
else
print_fail "$test_name formatting does not match expected output"
echo "Expected:"
cat "$expected_file"
echo ""
echo "Actual:"
cat "$actual_file"
echo ""
echo "Diff:"
diff -u "$expected_file" "$actual_file" || true
echo ""
fi
else
print_fail "$test_name formatting command failed"
fi
# Clean up
rm -f "$input_file" "$expected_file" "$actual_file"
}
# Function to test CLI options
test_cli_options() {
local test_name="$1"
local options="$2"
local input_content="$3"
local expected_content="$4"
print_test "Testing $test_name"
# Create temporary files
local input_file
local expected_file
local actual_file
input_file=$(mktemp -t "bin_format_cli_input_XXXXXX.spec.sh")
expected_file=$(mktemp -t "bin_format_cli_expected_XXXXXX.spec.sh")
actual_file=$(mktemp -t "bin_format_cli_actual_XXXXXX.spec.sh")
# Write test data to files
printf "%s\n" "$input_content" >"$input_file"
printf "%s\n" "$expected_content" >"$expected_file"
# Format using the standalone formatter with options
if timeout 10 bash -c "$FORMATTER $options < '$input_file' > '$actual_file'" 2>/dev/null; then
# Compare with expected output
if diff -u "$expected_file" "$actual_file" >/dev/null; then
print_pass "$test_name formatting with options matches expected output"
else
print_fail "$test_name formatting with options does not match expected output"
echo "Options: $options"
echo "Expected:"
cat "$expected_file"
echo ""
echo "Actual:"
cat "$actual_file"
echo ""
echo "Diff:"
diff -u "$expected_file" "$actual_file" || true
echo ""
fi
else
print_fail "$test_name formatting command with options failed"
fi
# Clean up
rm -f "$input_file" "$expected_file" "$actual_file"
}
echo "Running bin/shellspec-format standalone formatter tests..."
echo "Project root: $PROJECT_ROOT"
echo "Formatter: $FORMATTER"
echo ""
# Verify formatter exists and is executable
if [[ ! -x "$FORMATTER" ]]; then
echo -e "${RED}Error: Formatter not found or not executable: $FORMATTER${NC}"
exit 1
fi
# Test 1: Basic block indentation (ported from format_spec.lua)
input1='Describe "test"
It "should work"
End
End'
expected1='Describe "test"
It "should work"
End
End'
run_format_test "Basic block indentation" "$input1" "$expected1"
# Test 2: Comment indentation (ported from format_spec.lua)
input2='Describe "test"
# Comment at Describe level
It "should work"
# Comment at It level
When call echo "test"
End
End'
expected2='Describe "test"
# Comment at Describe level
It "should work"
# Comment at It level
When call echo "test"
End
End'
run_format_test "Comment indentation" "$input2" "$expected2"
# Test 3: HEREDOC preservation (ported from format_spec.lua)
run_format_test \
"HEREDOC preservation" \
'Describe "test"
It "handles heredoc"
When call cat <<EOF
This should be preserved
Even nested
EOF
The output should include "test"
End
End' \
'Describe "test"
It "handles heredoc"
When call cat <<EOF
This should be preserved
Even nested
EOF
The output should include "test"
End
End'
# Test 4: Nested contexts (ported from format_spec.lua)
run_format_test \
"Nested contexts" \
'Describe "outer"
Context "when something"
It "should work"
When call echo "test"
The output should equal "test"
End
End
End' \
'Describe "outer"
Context "when something"
It "should work"
When call echo "test"
The output should equal "test"
End
End
End'
# Test 5: Hook keywords (ported from format_spec.lua)
run_format_test \
"Hook keywords" \
'Describe "test"
BeforeEach
setup_test
End
It "works"
When call test_function
End
End' \
'Describe "test"
BeforeEach
setup_test
End
It "works"
When call test_function
End
End'
# CLI-specific tests
# Test 6: Custom indent size
test_cli_options \
"Custom indent size (4 spaces)" \
"--indent-size 4" \
'Describe "test"
It "should work"
End
End' \
'Describe "test"
It "should work"
End
End'
# Test 7: Tab indentation
input7='Describe "test"
It "should work"
End
End'
expected7='Describe "test"'$'\n\t''It "should work"'$'\n\t''End'$'\n''End'
test_cli_options "Tab indentation" "--tabs" "$input7" "$expected7"
# Test 8: No comment indentation
test_cli_options \
"No comment indentation" \
"--no-comment-indent" \
'Describe "test"
# Top level comment
It "should work"
# Nested comment
End
End' \
'Describe "test"
# Top level comment
It "should work"
# Nested comment
End
End'
# Test 9: Complex combination - tabs with custom indent size
input9='Describe "test"
Context "nested"
It "should work"
End
End
End'
expected9='Describe "test"'$'\n\t''Context "nested"'$'\n\t\t''It "should work"'$'\n\t\t''End'$'\n\t''End'$'\n''End'
test_cli_options "Tabs with custom indent size" "--tabs --indent-size 1" "$input9" "$expected9"
# Test error handling
print_test "Testing error handling - invalid indent size"
if timeout 5 echo 'test' | "$FORMATTER" --indent-size 0 >/dev/null 2>&1; then
print_fail "Should have failed with invalid indent size"
else
print_pass "Correctly rejected invalid indent size"
fi
print_test "Testing error handling - unknown option"
if timeout 5 echo 'test' | "$FORMATTER" --unknown-option >/dev/null 2>&1; then
print_fail "Should have failed with unknown option"
else
print_pass "Correctly rejected unknown option"
fi
print_summary

View File

@@ -15,6 +15,7 @@ NC='\033[0m' # No Color
UNIT_PASSED=false UNIT_PASSED=false
INTEGRATION_PASSED=false INTEGRATION_PASSED=false
GOLDEN_PASSED=false GOLDEN_PASSED=false
BIN_FORMAT_PASSED=false
# Get the script directory and project root # Get the script directory and project root
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -110,6 +111,9 @@ echo ""
echo -e "${BLUE}----------------------------------------${NC}" echo -e "${BLUE}----------------------------------------${NC}"
echo "" echo ""
# Run bin formatter tests
run_test_suite "Standalone Formatter Tests" "script" "./tests/bin_format_spec.sh" BIN_FORMAT_PASSED
# Summary # Summary
echo -e "${BLUE}========================================${NC}" echo -e "${BLUE}========================================${NC}"
echo -e "${BLUE} Test Results Summary ${NC}" echo -e "${BLUE} Test Results Summary ${NC}"
@@ -134,10 +138,16 @@ else
echo -e "${RED}✗ Golden Master Tests: FAILED${NC}" echo -e "${RED}✗ Golden Master Tests: FAILED${NC}"
fi fi
if [ "$BIN_FORMAT_PASSED" = true ]; then
echo -e "${GREEN}✓ Standalone Formatter Tests: PASSED${NC}"
else
echo -e "${RED}✗ Standalone Formatter Tests: FAILED${NC}"
fi
echo "" echo ""
# Overall result # Overall result
if [ "$UNIT_PASSED" = true ] && [ "$INTEGRATION_PASSED" = true ] && [ "$GOLDEN_PASSED" = true ]; then if [ "$UNIT_PASSED" = true ] && [ "$INTEGRATION_PASSED" = true ] && [ "$GOLDEN_PASSED" = true ] && [ "$BIN_FORMAT_PASSED" = true ]; then
echo -e "${GREEN}🎉 ALL TESTS COMPLETED SUCCESSFULLY! 🎉${NC}" echo -e "${GREEN}🎉 ALL TESTS COMPLETED SUCCESSFULLY! 🎉${NC}"
echo "" echo ""
echo -e "${GREEN}The nvim-shellspec plugin is ready for use!${NC}" echo -e "${GREEN}The nvim-shellspec plugin is ready for use!${NC}"