ci/lint/README.md
Python implementation of the FastLED comprehensive linting suite.
This module replaced the 589-line bash ./lint script with a Python implementation following the trampoline pattern (similar to ./test → test.py).
The bash script ./lint is now a minimal 4-line trampoline:
#!/bin/bash
set -e
cd "$(dirname "$0")"
uv run ci/lint.py "$@"
All logic is implemented in Python under ci/lint/.
ci/lint/
├── __init__.py # Package initialization
├── args_parser.py # CLI argument parsing
├── duration_tracker.py # Timing and summary table generation
├── orchestrator.py # Parallel execution engine
├── output_capture.py # Thread-safe output buffering
├── stage_impls.py # Stage implementation (C++, JS, Python)
└── stages.py # LintStage dataclass definition
LintStage (stages.py):
DurationTracker (duration_tracker.py):
LintOrchestrator (orchestrator.py):
ThreadPoolExecutorLintArgs (args_parser.py):
--js, --cpp, --no-fingerprint, --full, --iwyu, --strictPython Pipeline (sequential within parallel):
--strict)C++ Linting:
--full or --iwyu)JavaScript Linting:
When running multiple stages (e.g., bash lint):
When running a single stage (e.g., bash lint --js):
Stages use fingerprint caching to skip unchanged files:
ci.cpp_lint_cache.pyci.python_lint_cache.pyci.js_lint_cache.pyCache behavior:
check_*_files_changed() → True if linting neededmark_*_lint_success() → Mark cache successinvalidate_*_cache() → Invalidate on failure--no-fingerprint bypasses all cachesAll exception handlers follow the KBI linter pattern:
except KeyboardInterrupt:
_thread.interrupt_main()
raise
Each stage has a timeout (default: 300s):
future.result(timeout=...)Temp directories cleaned up via try/finally:
try:
self.tmpdir = Path(tempfile.mkdtemp(prefix="fastled_lint_"))
# ... run stages ...
finally:
if self.tmpdir and self.tmpdir.exists():
shutil.rmtree(self.tmpdir, ignore_errors=True)
JavaScript linting uses shell=True on Windows to properly invoke bash scripts:
if sys.platform in ("win32", "cygwin", "msys"):
result = subprocess.run("bash ci/lint-js-fast", shell=True, ...)
else:
result = subprocess.run(["bash", "ci/lint-js-fast"], ...)
All paths use pathlib.Path for cross-platform compatibility.
Test infrastructure prepared at tests/ci/lint/:
conftest.py - Shared fixtureshelpers/ - Mock cache, filesystem, output parserBefore (bash script):
&, wait $!)After (Python implementation):
All functionality preserved:
Comparable performance to bash:
Planned improvements (not in scope for initial refactor):
Add function to stage_impls.py:
def run_my_linter() -> bool:
result = subprocess.run([...], capture_output=False)
return result.returncode == 0
Create stage in lint.py:
stages.append(
LintStage(
name="my_linter",
display_name="MY LINTER",
run_fn=run_my_linter,
timeout=120.0,
)
)
Cache modules are in ci/*_lint_cache.py:
get_*_files() to change monitored filesTwoLayerFingerprintCache(..., "cache_name")lint.backup (589 lines)ci/lint.pyci/lint/tests/ci/lint/test.py (test suite trampoline)