2525 steps :
2626 - name : Checkout
2727 uses : actions/checkout@v5
28+ with :
29+ fetch-depth : 2 # need to fetch 2 to get list of changed files
30+
31+ - name : Get changed files
32+ id : changed_files
33+ shell : bash
34+ run : |
35+ firstCommit='${{ github.event.pull_request.base.sha }}'
36+ lastCommit='${{ github.event.pull_request.head.sha }}'
37+ changedFiles=$(git diff --name-only --diff-filter=d "${firstCommit}" "${lastCommit}" | tr '\n' ' ')
38+
39+ echo "Changed files: ${changedFiles}"
40+ echo "CHANGED_FILES=${changedFiles}" >> "${GITHUB_OUTPUT}"
2841
2942 - name : Download problem matchers
3043 shell : bash
4861 [cmake-lint]="${gh_path}/cmake-lint.json"
4962 [gcc]="${gh_path}/gcc.json"
5063 [hadolint]="${gh_path}/hadolint.json"
64+ [linelint]="${gh_path}/linelint.json"
5165 [shellcheck-gcc]="${gh_path}/shellcheck-gcc.json"
5266 [yamllint]="${gh_path}/yamllint.json"
5367 )
7387 python-version : ' 3.12'
7488
7589 - name : Install Python dependencies
90+ shell : bash
7691 run : |
7792 # shellcheck disable=SC2102 # this is triggered by the [toolchain] extra
7893 python -m pip install --upgrade \
@@ -101,7 +116,14 @@ jobs:
101116 "https://raw.githubusercontent.com/LizardByte/.github/master/.github/actionlint.yml"
102117 fi
103118
119+ - name : Install linelint
120+ shell : bash
121+ run : |
122+ go install github.com/fernandrone/linelint@latest
123+ echo "${HOME}/go/bin" >> "${GITHUB_PATH}"
124+
104125 - name : Replace shell
126+ # for actionlint
105127 shell : bash
106128 run : |
107129 # Replace in workflow files
@@ -130,6 +152,7 @@ jobs:
130152 - name : C++ - find files
131153 id : cpp_files
132154 if : always()
155+ shell : bash
133156 run : |
134157 # find files
135158 found_files=$(find . -type f \
@@ -176,6 +199,7 @@ jobs:
176199
177200 - name : C++ - Clang format (simple)
178201 if : always() && steps.clang_format_diff.outcome == 'failure'
202+ shell : bash
179203 run : |
180204 echo "::add-matcher::.github/matchers/clang-format.json"
181205 set +e
@@ -193,6 +217,7 @@ jobs:
193217 - name : CMake - find files
194218 id : cmake_files
195219 if : always()
220+ shell : bash
196221 run : |
197222 # find files
198223 found_files=$(find . -type f -iname "CMakeLists.txt" -o -iname "*.cmake")
@@ -221,6 +246,7 @@ jobs:
221246
222247 - name : CMake - cmake-lint
223248 if : always() && steps.cmake_files.outputs.found_files
249+ shell : bash
224250 run : |
225251 echo "::add-matcher::.github/matchers/cmake-lint.json"
226252 set +e
@@ -234,6 +260,7 @@ jobs:
234260 - name : Docker - find files
235261 id : docker_files
236262 if : always()
263+ shell : bash
237264 run : |
238265 found_files=$(find . -type f -iname "Dockerfile" -o -iname "*.dockerfile")
239266
@@ -244,6 +271,7 @@ jobs:
244271
245272 - name : Docker - hadolint
246273 if : always() && steps.docker_files.outputs.found_files
274+ shell : bash
247275 run : |
248276 docker pull hadolint/hadolint
249277
@@ -286,6 +314,38 @@ jobs:
286314
287315 exit ${error}
288316
317+ - name : eol-lint
318+ if : always()
319+ shell : bash
320+ run : |
321+ # Only print one error per file, with correct line number
322+ error=0
323+ for file in ${{ steps.changed_files.outputs.CHANGED_FILES }}; do
324+ # Get last two lines
325+ last_two=$(tail -n 2 "$file")
326+ last_line=$(echo "$last_two" | tail -n 1)
327+ second_last_line=$(echo "$last_two" | head -n 1)
328+
329+ # Check for exactly one blank line at end
330+ if [[ "$last_line" != "" || "$second_last_line" == "" ]]; then
331+ error=1
332+ error_title="EOL linting error"
333+ error_message="File '$file' does not end with a single empty blank line."
334+ error_line_number=$(wc -l < "$file")
335+ echo "::error file=$file,line=$error_line_number,title=$error_title::$error_message"
336+ fi
337+ done
338+
339+ exit ${error}
340+
341+ - name : linelint
342+ if : always()
343+ shell : bash
344+ run : |
345+ echo "::add-matcher::.github/matchers/linelint.json"
346+ linelint ${{ steps.changed_files.outputs.CHANGED_FILES }}
347+ echo "::remove-matcher owner=linelint::"
348+
289349 - name : PowerShell - PSScriptAnalyzer
290350 if : always()
291351 shell : pwsh
@@ -339,6 +399,7 @@ jobs:
339399
340400 - name : Python - flake8
341401 if : always()
402+ shell : bash
342403 run : |
343404 echo "::group::problem matcher"
344405 set +e
@@ -359,6 +420,7 @@ jobs:
359420
360421 - name : Python - nbqa flake8
361422 if : always()
423+ shell : bash
362424 run : |
363425 echo "::group::problem matcher"
364426 set +e
@@ -381,6 +443,7 @@ jobs:
381443
382444 - name : Python - nb-clean
383445 if : always()
446+ shell : bash
384447 run : |
385448 output=$(find . -name '*.ipynb' -exec nb-clean check {} \;)
386449
@@ -393,6 +456,7 @@ jobs:
393456 - name : Rust - find Cargo.toml
394457 id : run_cargo
395458 if : always()
459+ shell : bash
396460 run : |
397461 # check if Cargo.toml exists
398462 if [ -f "Cargo.toml" ]; then
@@ -412,6 +476,7 @@ jobs:
412476
413477 - name : Rust - cargo fmt
414478 if : always() && steps.run_cargo.outputs.found_cargo == 'true'
479+ shell : bash
415480 run : |
416481 set +e
417482 error=0
@@ -449,6 +514,7 @@ jobs:
449514 - name : shellcheck - find files
450515 id : shellcheck_files
451516 if : always()
517+ shell : bash
452518 run : |
453519 found_files=$(find . -type f -iname "*.bash" -o -iname "*.sh")
454520
@@ -459,6 +525,7 @@ jobs:
459525
460526 - name : shellcheck
461527 if : always() && steps.shellcheck_files.outputs.found_files
528+ shell : bash
462529 run : |
463530 echo "::add-matcher::.github/matchers/shellcheck-gcc.json"
464531 set +e
@@ -472,6 +539,7 @@ jobs:
472539 - name : YAML - find files
473540 id : yaml_files
474541 if : always()
542+ shell : bash
475543 run : |
476544 # space separated list of files
477545 FILES=.clang-format
@@ -491,6 +559,7 @@ jobs:
491559 - name : YAML - yamllint
492560 id : yamllint
493561 if : always()
562+ shell : bash
494563 run : |
495564 if [ ! -f .yamllint.yml ]; then
496565 curl -sSL https://raw.githubusercontent.com/LizardByte/.github/master/.yamllint.yml -o .yamllint.yml
0 commit comments