Skip to content

feat: add linter action for antlr #8

feat: add linter action for antlr

feat: add linter action for antlr #8

Workflow file for this run

name: ANTLR Grammar Lint
# This workflow runs the antlr-v4-linter on all ANTLR grammar files (.g4)
# to ensure they follow best practices and coding standards.
#
# The linter will check for:
# - Naming conventions (rules, tokens, labels)
# - Grammar complexity issues
# - Documentation requirements
# - Performance optimizations
# - Syntax and structural problems
# - Token management best practices
on:
# Trigger on push events that modify grammar files
push:
paths:
- '**/*.g4'
- '.github/workflows/antlr-lint.yml' # Re-run if workflow itself changes
# Trigger on pull requests that modify grammar files
pull_request:
paths:
- '**/*.g4'
- '.github/workflows/antlr-lint.yml'
# Allow manual triggering from Actions tab
workflow_dispatch:
inputs:
verbose:
description: 'Enable verbose output'
required: false
default: 'false'
type: choice
options:
- 'true'
- 'false'
jobs:
lint-grammars:
name: Lint ANTLR Grammars
runs-on: ubuntu-latest
# Define the dialects to check
# Add new dialects here as they are added to the repository
strategy:
matrix:
include:
- dialect: redshift
path: redshift
# Future dialects can be added like:
# - dialect: postgres
# path: postgres
# - dialect: mysql
# path: mysql
steps:
# Step 1: Checkout the parser repository containing grammar files
- name: πŸ“₯ Checkout parser repository
uses: actions/checkout@v4
with:
path: parser
# Step 2: Checkout the antlr-v4-linter tool repository
- name: πŸ“₯ Checkout antlr-v4-linter tool
uses: actions/checkout@v4
with:
repository: bytebase/antlr-v4-linter
path: antlr-v4-linter
# Step 3: Set up Python environment
- name: 🐍 Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.10'
cache: 'pip'
cache-dependency-path: 'antlr-v4-linter/pyproject.toml'
# Step 4: Install the antlr-v4-linter tool
- name: πŸ“¦ Install antlr-v4-linter
run: |
echo "Installing antlr-v4-linter and its dependencies..."
cd antlr-v4-linter
# Install in editable mode for development
pip install -e .
# Verify installation was successful
echo "Verifying installation..."
which antlr-lint
antlr-lint --version || echo "Version command not available"
# Show available commands
echo "Available commands:"
antlr-lint --help
# Step 5: Create or check for configuration file
- name: βš™οΈ Setup linter configuration
working-directory: parser
run: |
# Check if a custom configuration exists for this dialect
if [ -f "${{ matrix.path }}/antlr-lint.json" ]; then
echo "βœ… Found custom configuration for ${{ matrix.dialect }}"
echo "CONFIG_FILE=${{ matrix.path }}/antlr-lint.json" >> $GITHUB_ENV
elif [ -f "antlr-lint.json" ]; then
echo "βœ… Found global configuration"
echo "CONFIG_FILE=antlr-lint.json" >> $GITHUB_ENV
else
echo "ℹ️ No configuration found, using defaults"
echo "CONFIG_FILE=" >> $GITHUB_ENV
fi
# Step 6: Run the linter on grammar files with detailed output
- name: πŸ” Lint ${{ matrix.dialect }} grammar files
working-directory: parser
run: |
echo "========================================="
echo "Linting ANTLR grammar files for: ${{ matrix.dialect }}"
echo "Path: ${{ matrix.path }}"
echo "========================================="
echo ""
# Initialize counters
total_files=0
failed_files=0
passed_files=0
# Create a temporary file to store all issues
issues_file=$(mktemp)
# Process each .g4 file
for file in $(find ${{ matrix.path }} -name "*.g4" -type f | sort); do
total_files=$((total_files + 1))
echo "πŸ“„ Checking: $file"
echo "----------------------------------------"
# Prepare config option if config file exists
config_opt=""
if [ -n "$CONFIG_FILE" ]; then
config_opt="--config $CONFIG_FILE"
fi
# Add verbose flag if requested
verbose_opt=""
if [ "${{ github.event.inputs.verbose }}" = "true" ]; then
verbose_opt="--verbose"
fi
# Run linter and capture output
output_file=$(mktemp)
antlr-lint lint $verbose_opt $config_opt "$file" 2>&1 | tee "$output_file"
# Check if there are any errors or warnings in the output
if grep -E "ERROR|WARNING" "$output_file" > /dev/null; then
echo "❌ FAILED: Issues detected"
failed_files=$((failed_files + 1))
# Parse the output for GitHub annotations
# Look for the table format with Location, Severity, Rule, and Message
while IFS= read -r line; do
# Look for lines with the format: β”‚ 827:1 β”‚ ERROR β”‚ S001 β”‚ Message...
if echo "$line" | grep -E "^β”‚ [0-9]+:[0-9]+" > /dev/null; then
# Extract location (line:column)
location=$(echo "$line" | sed -n 's/^β”‚ *\([0-9]*:[0-9]*\).*/\1/p' | tr -d ' ')
line_num=$(echo "$location" | cut -d: -f1)
col_num=$(echo "$location" | cut -d: -f2)
# Extract severity
if echo "$line" | grep -i "ERROR" > /dev/null; then
severity="error"
elif echo "$line" | grep -i "WARNING" > /dev/null; then
severity="warning"
else
severity="notice"
fi
# Extract rule code
rule=$(echo "$line" | sed -n 's/.*β”‚ *\([A-Z][0-9]*\) *β”‚.*/\1/p')
# Extract message - everything after the rule code
message=$(echo "$line" | sed -n 's/.*β”‚ [A-Z][0-9]* *β”‚ *\(.*\) *β”‚$/\1/p' | sed 's/ *$//')
# Output GitHub annotation
if [ -n "$col_num" ] && [ "$col_num" != "1" ]; then
echo "::${severity} file=${file},line=${line_num},col=${col_num}::[$rule] ${message}"
else
echo "::${severity} file=${file},line=${line_num}::[$rule] ${message}"
fi
# Store for summary
echo "${severity}: ${file}:${line_num} - [$rule] ${message}" >> "$issues_file"
fi
done < "$output_file"
# If no specific line annotations were found, create a general file-level annotation
if ! grep -q "::" "$output_file"; then
echo "::error file=${file}::ANTLR grammar linting failed. Check the workflow logs for details."
fi
else
echo "βœ… PASSED: No issues found"
passed_files=$((passed_files + 1))
fi
rm -f "$output_file"
echo ""
done
# Summary statistics
echo "========================================="
echo "πŸ“Š Linting Summary for ${{ matrix.dialect }}"
echo "========================================="
echo "Total files checked: ${total_files}"
echo "βœ… Passed: ${passed_files}"
echo "❌ Failed: ${failed_files}"
echo ""
# If there were failures, show a summary of issues
if [ $failed_files -gt 0 ]; then
echo "πŸ“‹ Issues Summary:"
echo "----------------------------------------"
cat "$issues_file" | sort | uniq
echo ""
echo "❌ Grammar linting failed for ${{ matrix.dialect }}"
echo "Please fix the issues above and try again."
rm -f "$issues_file"
exit 1
else
echo "βœ… All grammar files for ${{ matrix.dialect }} passed linting!"
rm -f "$issues_file"
fi
# Step 7: Upload linter results as artifacts (useful for debugging)
- name: πŸ“€ Upload linting results
if: failure()
uses: actions/upload-artifact@v4
with:
name: linting-results-${{ matrix.dialect }}
path: |
parser/${{ matrix.path }}/*.g4
parser/antlr-lint.json
parser/${{ matrix.path }}/antlr-lint.json
retention-days: 7
# Step 8: Create job summary
- name: πŸ“ Create job summary
if: always()
working-directory: parser
run: |
echo "# πŸ” ANTLR Grammar Lint Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "## Dialect: \`${{ matrix.dialect }}\`" >> $GITHUB_STEP_SUMMARY
echo "**Path:** \`${{ matrix.path }}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### πŸ“„ Grammar Files Checked:" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| File | Status |" >> $GITHUB_STEP_SUMMARY
echo "|------|--------|" >> $GITHUB_STEP_SUMMARY
for file in $(find ${{ matrix.path }} -name "*.g4" -type f | sort); do
# Check if this file had issues
output=$(antlr-lint lint "$file" 2>&1)
if echo "$output" | grep -E "ERROR|WARNING" > /dev/null; then
echo "| \`${file}\` | ❌ Failed |" >> $GITHUB_STEP_SUMMARY
else
echo "| \`${file}\` | βœ… Passed |" >> $GITHUB_STEP_SUMMARY
fi
done
echo "" >> $GITHUB_STEP_SUMMARY
echo "---" >> $GITHUB_STEP_SUMMARY
echo "*Workflow run at: $(date -u '+%Y-%m-%d %H:%M:%S UTC')*" >> $GITHUB_STEP_SUMMARY