11name : ANTLR Grammar Lint
22
3+ # This workflow runs the antlr-v4-linter on all ANTLR grammar files (.g4)
4+ # to ensure they follow best practices and coding standards.
5+ #
6+ # The linter will check for:
7+ # - Naming conventions (rules, tokens, labels)
8+ # - Grammar complexity issues
9+ # - Documentation requirements
10+ # - Performance optimizations
11+ # - Syntax and structural problems
12+ # - Token management best practices
13+
314on :
15+ # Trigger on push events that modify grammar files
416 push :
517 paths :
618 - ' **/*.g4'
19+ - ' .github/workflows/antlr-lint.yml' # Re-run if workflow itself changes
20+
21+ # Trigger on pull requests that modify grammar files
722 pull_request :
823 paths :
924 - ' **/*.g4'
25+ - ' .github/workflows/antlr-lint.yml'
26+
27+ # Allow manual triggering from Actions tab
1028 workflow_dispatch :
29+ inputs :
30+ verbose :
31+ description : ' Enable verbose output'
32+ required : false
33+ default : ' false'
34+ type : choice
35+ options :
36+ - ' true'
37+ - ' false'
1138
1239jobs :
1340 lint-grammars :
41+ name : Lint ANTLR Grammars
1442 runs-on : ubuntu-latest
1543
44+ # Define the dialects to check
45+ # Add new dialects here as they are added to the repository
1646 strategy :
1747 matrix :
1848 include :
1949 - dialect : redshift
2050 path : redshift
51+ # Future dialects can be added like:
52+ # - dialect: postgres
53+ # path: postgres
54+ # - dialect: mysql
55+ # path: mysql
2156
2257 steps :
23- - name : Checkout parser repository
58+ # Step 1: Checkout the parser repository containing grammar files
59+ - name : 📥 Checkout parser repository
2460 uses : actions/checkout@v4
2561 with :
2662 path : parser
2763
28- - name : Checkout antlr-v4-linter repository
64+ # Step 2: Checkout the antlr-v4-linter tool repository
65+ - name : 📥 Checkout antlr-v4-linter tool
2966 uses : actions/checkout@v4
3067 with :
3168 repository : bytebase/antlr-v4-linter
3269 path : antlr-v4-linter
3370
34- - name : Set up Python
71+ # Step 3: Set up Python environment
72+ - name : 🐍 Set up Python
3573 uses : actions/setup-python@v5
3674 with :
3775 python-version : ' 3.10'
76+ cache : ' pip'
77+ cache-dependency-path : ' antlr-v4-linter/pyproject.toml'
3878
39- - name : Install antlr-v4-linter
79+ # Step 4: Install the antlr-v4-linter tool
80+ - name : 📦 Install antlr-v4-linter
4081 run : |
82+ echo "Installing antlr-v4-linter and its dependencies..."
4183 cd antlr-v4-linter
84+
85+ # Install in editable mode for development
4286 pip install -e .
43- # Verify installation
87+
88+ # Verify installation was successful
89+ echo "Verifying installation..."
4490 which antlr-lint
45- antlr-lint --version || true
91+ antlr-lint --version || echo "Version command not available"
92+
93+ # Show available commands
94+ echo "Available commands:"
95+ antlr-lint --help
96+
97+ # Step 5: Create or check for configuration file
98+ - name : ⚙️ Setup linter configuration
99+ working-directory : parser
100+ run : |
101+ # Check if a custom configuration exists for this dialect
102+ if [ -f "${{ matrix.path }}/antlr-lint.json" ]; then
103+ echo "✅ Found custom configuration for ${{ matrix.dialect }}"
104+ echo "CONFIG_FILE=${{ matrix.path }}/antlr-lint.json" >> $GITHUB_ENV
105+ elif [ -f "antlr-lint.json" ]; then
106+ echo "✅ Found global configuration"
107+ echo "CONFIG_FILE=antlr-lint.json" >> $GITHUB_ENV
108+ else
109+ echo "ℹ️ No configuration found, using defaults"
110+ echo "CONFIG_FILE=" >> $GITHUB_ENV
111+ fi
46112
47- - name : Lint ${{ matrix.dialect }} grammar files
113+ # Step 6: Run the linter on grammar files with detailed output
114+ - name : 🔍 Lint ${{ matrix.dialect }} grammar files
48115 working-directory : parser
49116 run : |
50- echo "Linting ANTLR grammar files for ${{ matrix.dialect }}"
117+ echo "========================================="
118+ echo "Linting ANTLR grammar files for: ${{ matrix.dialect }}"
119+ echo "Path: ${{ matrix.path }}"
120+ echo "========================================="
121+ echo ""
51122
52- # Track if any errors occurred
53- has_errors=false
123+ # Initialize counters
124+ total_files=0
125+ failed_files=0
126+ passed_files=0
54127
55- # Find all .g4 files in the dialect directory
56- for file in $(find ${{ matrix.path }} -name "*.g4" -type f); do
57- echo "Checking: $file"
128+ # Create a temporary file to store all issues
129+ issues_file=$(mktemp)
130+
131+ # Process each .g4 file
132+ for file in $(find ${{ matrix.path }} -name "*.g4" -type f | sort); do
133+ total_files=$((total_files + 1))
134+ echo "📄 Checking: $file"
135+ echo "----------------------------------------"
136+
137+ # Prepare config option if config file exists
138+ config_opt=""
139+ if [ -n "$CONFIG_FILE" ]; then
140+ config_opt="--config $CONFIG_FILE"
141+ fi
142+
143+ # Add verbose flag if requested
144+ verbose_opt=""
145+ if [ "${{ github.event.inputs.verbose }}" = "true" ]; then
146+ verbose_opt="--verbose"
147+ fi
58148
59- # Run antlr-lint on the grammar file
60- if antlr-lint lint "$file"; then
61- echo "✅ $file: Passed linting"
149+ # Run linter and capture output
150+ output_file=$(mktemp)
151+ if antlr-lint lint $verbose_opt $config_opt "$file" 2>&1 | tee "$output_file"; then
152+ echo "✅ PASSED: No issues found"
153+ passed_files=$((passed_files + 1))
62154 else
63- echo "❌ $file: Failed linting"
64- echo "::error file=$file::ANTLR grammar linting failed"
65- has_errors=true
155+ echo "❌ FAILED: Issues detected"
156+ failed_files=$((failed_files + 1))
157+
158+ # Parse the output for GitHub annotations
159+ # Extract file path, line numbers, and messages for GitHub annotations
160+ while IFS= read -r line; do
161+ # Look for patterns like "filename:line:column: severity: message"
162+ # or "filename:line: severity: message"
163+ if echo "$line" | grep -E "^.*\.g4:[0-9]+:" > /dev/null; then
164+ # Extract components
165+ file_path=$(echo "$line" | cut -d: -f1)
166+ line_num=$(echo "$line" | cut -d: -f2)
167+
168+ # Check if there's a column number
169+ if echo "$line" | cut -d: -f3 | grep -E "^[0-9]+$" > /dev/null; then
170+ col_num=$(echo "$line" | cut -d: -f3)
171+ rest=$(echo "$line" | cut -d: -f4-)
172+ else
173+ col_num=""
174+ rest=$(echo "$line" | cut -d: -f3-)
175+ fi
176+
177+ # Determine severity and message
178+ if echo "$rest" | grep -i "error" > /dev/null; then
179+ severity="error"
180+ message=$(echo "$rest" | sed 's/^ *error: *//')
181+ elif echo "$rest" | grep -i "warning" > /dev/null; then
182+ severity="warning"
183+ message=$(echo "$rest" | sed 's/^ *warning: *//')
184+ else
185+ severity="notice"
186+ message=$(echo "$rest" | sed 's/^ *//')
187+ fi
188+
189+ # Output GitHub annotation
190+ if [ -n "$col_num" ]; then
191+ echo "::${severity} file=${file_path},line=${line_num},col=${col_num}::${message}"
192+ else
193+ echo "::${severity} file=${file_path},line=${line_num}::${message}"
194+ fi
195+
196+ # Store for summary
197+ echo "${severity}: ${file_path}:${line_num} - ${message}" >> "$issues_file"
198+ fi
199+ done < "$output_file"
200+
201+ # If no specific line annotations were found, create a general file-level annotation
202+ if ! grep -q "::" "$output_file"; then
203+ echo "::error file=${file}::ANTLR grammar linting failed. Check the workflow logs for details."
204+ fi
66205 fi
206+
207+ rm -f "$output_file"
208+ echo ""
67209 done
68210
69- if [ "$has_errors" = true ]; then
211+ # Summary statistics
212+ echo "========================================="
213+ echo "📊 Linting Summary for ${{ matrix.dialect }}"
214+ echo "========================================="
215+ echo "Total files checked: ${total_files}"
216+ echo "✅ Passed: ${passed_files}"
217+ echo "❌ Failed: ${failed_files}"
218+ echo ""
219+
220+ # If there were failures, show a summary of issues
221+ if [ $failed_files -gt 0 ]; then
222+ echo "📋 Issues Summary:"
223+ echo "----------------------------------------"
224+ cat "$issues_file" | sort | uniq
225+ echo ""
70226 echo "❌ Grammar linting failed for ${{ matrix.dialect }}"
227+ echo "Please fix the issues above and try again."
228+ rm -f "$issues_file"
71229 exit 1
72230 else
73- echo "✅ All grammar files for ${{ matrix.dialect }} passed linting"
231+ echo "✅ All grammar files for ${{ matrix.dialect }} passed linting!"
232+ rm -f "$issues_file"
74233 fi
75234
76- - name : Summary
235+ # Step 7: Upload linter results as artifacts (useful for debugging)
236+ - name : 📤 Upload linting results
237+ if : failure()
238+ uses : actions/upload-artifact@v4
239+ with :
240+ name : linting-results-${{ matrix.dialect }}
241+ path : |
242+ parser/${{ matrix.path }}/*.g4
243+ parser/antlr-lint.json
244+ parser/${{ matrix.path }}/antlr-lint.json
245+ retention-days : 7
246+
247+ # Step 8: Create job summary
248+ - name : 📝 Create job summary
77249 if : always()
78250 working-directory : parser
79251 run : |
80- echo "## ANTLR Grammar Lint Results" >> $GITHUB_STEP_SUMMARY
81- echo "Dialect: **${{ matrix.dialect }}**" >> $GITHUB_STEP_SUMMARY
82- echo "Path: \`${{ matrix.path }}\`" >> $GITHUB_STEP_SUMMARY
252+ echo "# 🔍 ANTLR Grammar Lint Results" >> $GITHUB_STEP_SUMMARY
253+ echo "" >> $GITHUB_STEP_SUMMARY
254+
255+ echo "## Dialect: \`${{ matrix.dialect }}\`" >> $GITHUB_STEP_SUMMARY
256+ echo "**Path:** \`${{ matrix.path }}\`" >> $GITHUB_STEP_SUMMARY
257+ echo "" >> $GITHUB_STEP_SUMMARY
258+
259+ echo "### 📄 Grammar Files Checked:" >> $GITHUB_STEP_SUMMARY
83260 echo "" >> $GITHUB_STEP_SUMMARY
84261
85- echo "### Files checked:" >> $GITHUB_STEP_SUMMARY
86- find ${{ matrix.path }} -name "*.g4" -type f | while read -r file; do
87- echo "- \`$file\`" >> $GITHUB_STEP_SUMMARY
88- done
262+ echo "| File | Status |" >> $GITHUB_STEP_SUMMARY
263+ echo "|------|--------|" >> $GITHUB_STEP_SUMMARY
264+
265+ for file in $(find ${{ matrix.path }} -name "*.g4" -type f | sort); do
266+ # Check if this file had issues (simplified check)
267+ if antlr-lint lint "$file" > /dev/null 2>&1; then
268+ echo "| \`${file}\` | ✅ Passed |" >> $GITHUB_STEP_SUMMARY
269+ else
270+ echo "| \`${file}\` | ❌ Failed |" >> $GITHUB_STEP_SUMMARY
271+ fi
272+ done
273+
274+ echo "" >> $GITHUB_STEP_SUMMARY
275+ echo "---" >> $GITHUB_STEP_SUMMARY
276+ echo "*Workflow run at: $(date -u '+%Y-%m-%d %H:%M:%S UTC')*" >> $GITHUB_STEP_SUMMARY
0 commit comments