From ab27112d8f6c321be1deb885fb06ecaddf021b4d Mon Sep 17 00:00:00 2001 From: Josue Nina Date: Wed, 21 Jan 2026 17:23:21 -0500 Subject: [PATCH 01/11] Add static code analysis to CI --- .github/workflows/build.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e40dbb73..dd827a1c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,6 +25,25 @@ jobs: pip install setuptools==69.5.1 wheel pip install -r requirements.txt + # Static analysis tools + - name: Install static analysis tools + if: runner.os == 'Linux' + run: | + pip install mypy==1.8.0 + pip install flake8==7.0.0 + pip install black==24.1.1 + + - name: Run static code analysis + if: runner.os == 'Linux' + run: | + # Critical checks + mypy lean/ --ignore-missing-imports --check-untyped-defs + flake8 lean/ --select=F821 --ignore=ALL + + # Warning checks (don't fail the build) + flake8 lean/ --select=F401 --ignore=ALL --exit-zero + black --check lean/ --quiet || true + - name: Run tests run: python -m pytest -s -rs From 5fffc62d7517ace75f9f8cca4d9f34d4f621c41f Mon Sep 17 00:00:00 2001 From: Josue Nina Date: Wed, 21 Jan 2026 17:37:40 -0500 Subject: [PATCH 02/11] Limit mypy CI check --- .github/workflows/build.yml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dd827a1c..857f2ed8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -37,7 +37,23 @@ jobs: if: runner.os == 'Linux' run: | # Critical checks - mypy lean/ --ignore-missing-imports --check-untyped-defs + mypy_output=$(mypy lean/ \ + --ignore-missing-imports \ + --check-untyped-defs \ + --show-error-codes \ + --no-error-summary 2>&1) + + missing_args=$(echo "$mypy_output" | grep -E "Missing positional argument|\[call-arg\]") + + if [ -n "$missing_args" ]; then + echo "❌ ERROR: Missing function arguments detected:" + echo "" + echo "$missing_args" + echo "" + echo "When adding/removing parameters from methods, ensure all call sites are updated." + exit 1 + fi + flake8 lean/ --select=F821 --ignore=ALL # Warning checks (don't fail the build) From 64cc71046c8ff12671631788d79eab4933b322d5 Mon Sep 17 00:00:00 2001 From: Josue Nina Date: Wed, 21 Jan 2026 17:45:40 -0500 Subject: [PATCH 03/11] Fix issues with mypy --- .github/workflows/build.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 857f2ed8..37cf6859 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -41,14 +41,12 @@ jobs: --ignore-missing-imports \ --check-untyped-defs \ --show-error-codes \ - --no-error-summary 2>&1) + --no-error-summary 2>&1 || true) - missing_args=$(echo "$mypy_output" | grep -E "Missing positional argument|\[call-arg\]") - - if [ -n "$missing_args" ]; then - echo "❌ ERROR: Missing function arguments detected:" + if echo "$mypy_output" | grep -q -E "Missing positional argument|\[call-arg\]"; then + echo "ERROR: Missing function arguments detected:" echo "" - echo "$missing_args" + echo "$mypy_output" | grep -E "Missing positional argument|\[call-arg\]" echo "" echo "When adding/removing parameters from methods, ensure all call sites are updated." exit 1 From 77a821d01c6f6575befa50210189f2c29a103d85 Mon Sep 17 00:00:00 2001 From: Josue Nina Date: Fri, 23 Jan 2026 00:56:45 -0500 Subject: [PATCH 04/11] Use script to improve error reporting --- .github/workflows/build.yml | 32 +------- static_analysis.py | 152 ++++++++++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+), 29 deletions(-) create mode 100644 static_analysis.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 37cf6859..73bbee23 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,37 +26,11 @@ jobs: pip install -r requirements.txt # Static analysis tools - - name: Install static analysis tools + - name: Static Code Analysis if: runner.os == 'Linux' run: | - pip install mypy==1.8.0 - pip install flake8==7.0.0 - pip install black==24.1.1 - - - name: Run static code analysis - if: runner.os == 'Linux' - run: | - # Critical checks - mypy_output=$(mypy lean/ \ - --ignore-missing-imports \ - --check-untyped-defs \ - --show-error-codes \ - --no-error-summary 2>&1 || true) - - if echo "$mypy_output" | grep -q -E "Missing positional argument|\[call-arg\]"; then - echo "ERROR: Missing function arguments detected:" - echo "" - echo "$mypy_output" | grep -E "Missing positional argument|\[call-arg\]" - echo "" - echo "When adding/removing parameters from methods, ensure all call sites are updated." - exit 1 - fi - - flake8 lean/ --select=F821 --ignore=ALL - - # Warning checks (don't fail the build) - flake8 lean/ --select=F401 --ignore=ALL --exit-zero - black --check lean/ --quiet || true + pip install mypy==1.15.0 flake8==7.0.0 + python static_analysis.py - name: Run tests run: python -m pytest -s -rs diff --git a/static_analysis.py b/static_analysis.py new file mode 100644 index 00000000..89f5eaf4 --- /dev/null +++ b/static_analysis.py @@ -0,0 +1,152 @@ +import subprocess +import sys + +def display_warning_summary(warnings): + print("\nWarnings:") + unused_count = sum(1 for e in warnings if e.startswith('F401:')) + if unused_count > 0: + print(f" - Unused imports: {unused_count}") + + print(" Consider addressing warnings in future updates.") + +def run_analysis(): + print("Running static analysis...") + print("=" * 60) + + all_critical_errors = [] + all_warnings = [] + + # Check for missing arguments with mypy - CRITICAL + print("\n1. Checking for missing function arguments...") + print("-" * 40) + + result = subprocess.run( + ["python", "-m", "mypy", "lean/", + "--show-error-codes", + "--no-error-summary", + "--ignore-missing-imports", + "--check-untyped-defs"], + capture_output=True, + text=True + ) + + # Filter for critical call argument mismatches + call_arg_errors = [] + + for line in (result.stdout + result.stderr).split('\n'): + if not line.strip(): + continue + + # Look for call-arg errors (this covers both "too many" and "missing" arguments) + if '[call-arg]' in line: + # Skip false positives + if any(pattern in line for pattern in + ['click.', 'subprocess.', 'Module "', 'has incompatible type "Optional', + 'validator', 'pydantic', '__call__', 'OnlyValueValidator', 'V1Validator', + 'QCParameter', 'QCBacktest']): + continue + call_arg_errors.append(line.strip()) + + # Display call argument mismatches + if call_arg_errors: + print("CRITICAL: Missing function arguments found:") + for error in call_arg_errors: + # Clean path for better display + clean_error = error.replace('/home/runner/work/lean-cli/lean-cli/', '') + print(f" {clean_error}") + + all_critical_errors.extend(call_arg_errors) + else: + print("No argument mismatch errors found") + + # Check for undefined variables with flake8 - CRITICAL + print("\n2. Checking for undefined variables...") + print("-" * 40) + + result = subprocess.run( + ["python", "-m", "flake8", "lean/", + "--select=F821", + "--ignore=ALL", + "--count"], + capture_output=True, + text=True + ) + + if result.stdout.strip() and result.stdout.strip() != "0": + detail = subprocess.run( + ["python", "-m", "flake8", "lean/", "--select=F821", "--ignore=ALL"], + capture_output=True, + text=True + ) + + undefined_errors = [e.strip() for e in detail.stdout.split('\n') if e.strip()] + print(f"CRITICAL: {len(undefined_errors)} undefined variable(s) found:") + + for error in undefined_errors: + print(f" {error}") + + all_critical_errors.extend([f"F821: {e}" for e in undefined_errors]) + else: + print("No undefined variables found") + + # Check for unused imports with flake8 - WARNING + print("\n3. Checking for unused imports...") + print("-" * 40) + + result = subprocess.run( + ["python", "-m", "flake8", "lean/", + "--select=F401", + "--ignore=ALL", + "--count", + "--exit-zero"], + capture_output=True, + text=True + ) + + if result.stdout.strip() and result.stdout.strip() != "0": + detail = subprocess.run( + ["python", "-m", "flake8", "lean/", "--select=F401", "--ignore=ALL", "--exit-zero"], + capture_output=True, + text=True + ) + + unused_imports = [e.strip() for e in detail.stdout.split('\n') if e.strip()] + if unused_imports: + print(f"WARNING: {len(unused_imports)} unused import(s) found:") + + for error in unused_imports: + print(f" {error}") + + all_warnings.extend([f"F401: {e}" for e in unused_imports]) + else: + print("No unused imports found") + else: + print("No unused imports found") + + print("\n" + "=" * 60) + + # Summary + if all_critical_errors: + total_errors = len(all_critical_errors) + print(f"BUILD FAILED: Found {total_errors} critical error(s)") + + print("\nSummary of critical errors:") + print(f" - Function call argument mismatches: {len(call_arg_errors)}") + undefined_count = sum(1 for e in all_critical_errors if e.startswith('F821:')) + print(f" - Undefined variables: {undefined_count}") + + if all_warnings: + display_warning_summary(all_warnings) + + return 1 + + if all_warnings: + print(f"BUILD PASSED with {len(all_warnings)} warning(s)") + display_warning_summary(all_warnings) + return 0 + + print("SUCCESS: All checks passed with no warnings") + return 0 + +if __name__ == "__main__": + sys.exit(run_analysis()) From 99acf6a654184b7500fd3a273b0b08728062c186 Mon Sep 17 00:00:00 2001 From: Josue Nina Date: Fri, 23 Jan 2026 00:57:39 -0500 Subject: [PATCH 05/11] Clean up code --- lean/commands/lean.py | 1 - lean/commands/live/deploy.py | 2 +- lean/components/api/live_client.py | 3 +-- lean/components/util/compiler.py | 8 ++++---- lean/components/util/project_manager.py | 1 - lean/main.py | 2 +- lean/models/pydantic.py | 4 ++-- 7 files changed, 9 insertions(+), 12 deletions(-) diff --git a/lean/commands/lean.py b/lean/commands/lean.py index 0de5c04d..749000bb 100644 --- a/lean/commands/lean.py +++ b/lean/commands/lean.py @@ -10,7 +10,6 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from typing import Optional from click import group, option, Context, pass_context, echo diff --git a/lean/commands/live/deploy.py b/lean/commands/live/deploy.py index e9cf75c8..be7e8867 100644 --- a/lean/commands/live/deploy.py +++ b/lean/commands/live/deploy.py @@ -12,7 +12,7 @@ # limitations under the License. from pathlib import Path -from typing import Any, Dict, List, Optional, Tuple +from typing import List, Optional, Tuple from click import option, argument, Choice from lean.click import LeanCommand, PathParameter from lean.components.util.name_rename import rename_internal_config_to_user_friendly_format diff --git a/lean/components/api/live_client.py b/lean/components/api/live_client.py index 8dbc862e..729e2d8a 100644 --- a/lean/components/api/live_client.py +++ b/lean/components/api/live_client.py @@ -11,11 +11,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -from datetime import datetime from typing import List, Optional from lean.components.api.api_client import * -from lean.models.api import QCFullLiveAlgorithm, QCLiveAlgorithmStatus, QCMinimalLiveAlgorithm, QCNotificationMethod, QCRestResponse +from lean.models.api import QCFullLiveAlgorithm, QCMinimalLiveAlgorithm, QCNotificationMethod, QCRestResponse class LiveClient: diff --git a/lean/components/util/compiler.py b/lean/components/util/compiler.py index 585c1a4d..4ec3fa62 100644 --- a/lean/components/util/compiler.py +++ b/lean/components/util/compiler.py @@ -111,13 +111,14 @@ def _compile() -> Dict[str, Any]: "mounts": [], "volumes": {} } - lean_runner.mount_project_and_library_directories(project_dir, run_options) - lean_runner.setup_language_specific_run_options(run_options, project_dir, algorithm_file, False, False) project_config = project_config_manager.get_project_config(project_dir) engine_image = cli_config_manager.get_engine_image( project_config.get("engine-image", None)) + lean_runner.mount_project_and_library_directories(project_dir, run_options) + lean_runner.setup_language_specific_run_options(run_options, project_dir, algorithm_file, False, False, engine_image) + message["result"] = docker_manager.run_image(engine_image, **run_options) temp_manager.delete_temporary_directories_when_done = False return message @@ -153,8 +154,7 @@ def _parse_python_errors(python_output: str, color_coding_required: bool) -> lis errors.append(f"{bcolors.FAIL}Build Error File: {match[0]} Line {match[1]} Column {match[2]} - {match[3]}{bcolors.ENDC}\n") else: errors.append(f"Build Error File: {match[0]} Line {match[1]} Column {match[2]} - {match[3]}\n") - - for match in re.findall(r"\*\*\* Sorry: ([^(]+) \(([^,]+), line (\d+)\)", python_output): + for match in findall(r"\*\*\* Sorry: ([^(]+) \(([^,]+), line (\d+)\)", python_output): if color_coding_required: errors.append(f"{bcolors.FAIL}Build Error File: {match[1]} Line {match[2]} Column 0 - {match[0]}{bcolors.ENDC}\n") else: diff --git a/lean/components/util/project_manager.py b/lean/components/util/project_manager.py index 60dffc12..25533217 100644 --- a/lean/components/util/project_manager.py +++ b/lean/components/util/project_manager.py @@ -366,7 +366,6 @@ def restore_csharp_project(self, csproj_file: Path, no_local: bool) -> None: """ from shutil import which from subprocess import run, STDOUT, PIPE - from lean.models.errors import MoreInfoError if no_local: return diff --git a/lean/main.py b/lean/main.py index 851cd1f2..061c721b 100644 --- a/lean/main.py +++ b/lean/main.py @@ -97,7 +97,7 @@ def main() -> None: if temp_manager.delete_temporary_directories_when_done: temp_manager.delete_temporary_directories() except Exception as exception: - from traceback import format_exc, print_exc + from traceback import format_exc from click import UsageError, Abort from requests import exceptions from io import StringIO diff --git a/lean/models/pydantic.py b/lean/models/pydantic.py index e8a8a6b8..abe5fed6 100644 --- a/lean/models/pydantic.py +++ b/lean/models/pydantic.py @@ -16,9 +16,9 @@ # We keep all this imports here, even if not used like validator, so other files can import them through this file # to avoid having to check the pydantic version in every file. # All imports should be done through this file to avoid pydantic version related errors. - from pydantic import BaseModel, ValidationError, Field, validator + from pydantic import BaseModel, ValidationError else: - from pydantic.v1 import BaseModel, ValidationError, Field, validator + from pydantic.v1 import BaseModel, ValidationError class WrappedBaseModel(BaseModel): """A version of Pydantic's BaseModel which makes the input data accessible in case of a validation error.""" From cec39950d742dbb8e710ee87b859938225af866e Mon Sep 17 00:00:00 2001 From: Josue Nina Date: Fri, 23 Jan 2026 01:00:16 -0500 Subject: [PATCH 06/11] Fix issue with mypy --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 73bbee23..a66df28e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,7 +29,7 @@ jobs: - name: Static Code Analysis if: runner.os == 'Linux' run: | - pip install mypy==1.15.0 flake8==7.0.0 + pip install mypy==1.14.1 flake8==7.0.0 python static_analysis.py - name: Run tests From 65f1e26fe693e3117361b2a0e71dd6ce1c1d1c1a Mon Sep 17 00:00:00 2001 From: Josue Nina Date: Fri, 23 Jan 2026 01:11:49 -0500 Subject: [PATCH 07/11] Fix broken imports in pydantic --- lean/models/pydantic.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lean/models/pydantic.py b/lean/models/pydantic.py index abe5fed6..e8a8a6b8 100644 --- a/lean/models/pydantic.py +++ b/lean/models/pydantic.py @@ -16,9 +16,9 @@ # We keep all this imports here, even if not used like validator, so other files can import them through this file # to avoid having to check the pydantic version in every file. # All imports should be done through this file to avoid pydantic version related errors. - from pydantic import BaseModel, ValidationError + from pydantic import BaseModel, ValidationError, Field, validator else: - from pydantic.v1 import BaseModel, ValidationError + from pydantic.v1 import BaseModel, ValidationError, Field, validator class WrappedBaseModel(BaseModel): """A version of Pydantic's BaseModel which makes the input data accessible in case of a validation error.""" From 95a2e5b7fc243119e6154caaa0d8a27ab59dd3da Mon Sep 17 00:00:00 2001 From: Josue Nina Date: Mon, 26 Jan 2026 10:41:48 -0500 Subject: [PATCH 08/11] Solve review comments --- .github/workflows/build.yml | 2 +- static_analysis.py | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a66df28e..18e2f630 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,7 +29,7 @@ jobs: - name: Static Code Analysis if: runner.os == 'Linux' run: | - pip install mypy==1.14.1 flake8==7.0.0 + pip install mypy flake8 python static_analysis.py - name: Run tests diff --git a/static_analysis.py b/static_analysis.py index 89f5eaf4..ec594ab6 100644 --- a/static_analysis.py +++ b/static_analysis.py @@ -1,3 +1,16 @@ +# QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals. +# Lean CLI v1.0. Copyright 2021 QuantConnect Corporation. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + import subprocess import sys From 61e82f86ec63404c189ce6261e480cb83b31f4f3 Mon Sep 17 00:00:00 2001 From: Josue Nina Date: Mon, 26 Jan 2026 10:58:24 -0500 Subject: [PATCH 09/11] Pin the latest version of mypy and flake8 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 18e2f630..c235f34d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,7 +29,7 @@ jobs: - name: Static Code Analysis if: runner.os == 'Linux' run: | - pip install mypy flake8 + pip install mypy==1.19.1 flake8==7.3.0 python static_analysis.py - name: Run tests From edf2622c3ad7a7bdc22799b93906f4a8aa85de49 Mon Sep 17 00:00:00 2001 From: Josue Nina Date: Mon, 26 Jan 2026 11:22:44 -0500 Subject: [PATCH 10/11] Exclude run for python 3.8 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c235f34d..c968bdf1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,7 +27,7 @@ jobs: # Static analysis tools - name: Static Code Analysis - if: runner.os == 'Linux' + if: runner.os == 'Linux' && matrix.python-version != '3.8' run: | pip install mypy==1.19.1 flake8==7.3.0 python static_analysis.py From 1fb9953b810e5fa81ffe47f649b38baddd909cad Mon Sep 17 00:00:00 2001 From: Josue Nina Date: Mon, 26 Jan 2026 16:47:51 -0500 Subject: [PATCH 11/11] Simplify script --- static_analysis.py | 168 ++++++++++++++++++--------------------------- 1 file changed, 68 insertions(+), 100 deletions(-) diff --git a/static_analysis.py b/static_analysis.py index ec594ab6..cc4baf32 100644 --- a/static_analysis.py +++ b/static_analysis.py @@ -14,25 +14,15 @@ import subprocess import sys -def display_warning_summary(warnings): - print("\nWarnings:") - unused_count = sum(1 for e in warnings if e.startswith('F401:')) - if unused_count > 0: - print(f" - Unused imports: {unused_count}") - - print(" Consider addressing warnings in future updates.") - -def run_analysis(): - print("Running static analysis...") - print("=" * 60) - - all_critical_errors = [] - all_warnings = [] - - # Check for missing arguments with mypy - CRITICAL - print("\n1. Checking for missing function arguments...") - print("-" * 40) - +MYPY_IGNORE_PATTERNS = [ + 'click.', 'subprocess.', 'Module "', + 'has incompatible type "Optional', + 'validator', 'pydantic', '__call__', + 'OnlyValueValidator', 'V1Validator', + 'QCParameter', 'QCBacktest' +] + +def run_mypy_check(): result = subprocess.run( ["python", "-m", "mypy", "lean/", "--show-error-codes", @@ -43,119 +33,97 @@ def run_analysis(): text=True ) - # Filter for critical call argument mismatches - call_arg_errors = [] + errors = [] + for line in result.stdout.splitlines() + result.stderr.splitlines(): + if not line.strip() or '[call-arg]' not in line: + continue - for line in (result.stdout + result.stderr).split('\n'): - if not line.strip(): + # Skip false positives + if any(pattern in line for pattern in MYPY_IGNORE_PATTERNS): continue - # Look for call-arg errors (this covers both "too many" and "missing" arguments) - if '[call-arg]' in line: - # Skip false positives - if any(pattern in line for pattern in - ['click.', 'subprocess.', 'Module "', 'has incompatible type "Optional', - 'validator', 'pydantic', '__call__', 'OnlyValueValidator', 'V1Validator', - 'QCParameter', 'QCBacktest']): - continue - call_arg_errors.append(line.strip()) - - # Display call argument mismatches - if call_arg_errors: - print("CRITICAL: Missing function arguments found:") - for error in call_arg_errors: - # Clean path for better display - clean_error = error.replace('/home/runner/work/lean-cli/lean-cli/', '') - print(f" {clean_error}") + errors.append(line.strip()) - all_critical_errors.extend(call_arg_errors) - else: - print("No argument mismatch errors found") - - # Check for undefined variables with flake8 - CRITICAL - print("\n2. Checking for undefined variables...") - print("-" * 40) + return errors +def run_flake8_check(select_code): result = subprocess.run( ["python", "-m", "flake8", "lean/", - "--select=F821", + f"--select={select_code}", "--ignore=ALL", - "--count"], + "--exit-zero"], capture_output=True, text=True ) - if result.stdout.strip() and result.stdout.strip() != "0": - detail = subprocess.run( - ["python", "-m", "flake8", "lean/", "--select=F821", "--ignore=ALL"], - capture_output=True, - text=True - ) + errors = [line.strip() for line in result.stdout.splitlines() if line.strip()] + return errors, len(errors) - undefined_errors = [e.strip() for e in detail.stdout.split('\n') if e.strip()] - print(f"CRITICAL: {len(undefined_errors)} undefined variable(s) found:") +def display_errors(title, errors, is_critical = True): + level = "CRITICAL" if is_critical else "WARNING" + if errors: + print(f"{level}: {len(errors)} {title} found:") + for error in errors: + # Clean path for better display + clean_error = error.replace('/home/runner/work/lean-cli/lean-cli/', '') + print(f" {clean_error}") + else: + print(f"No {title} found") - for error in undefined_errors: - print(f" {error}") +def display_warning_summary(unused_count): + print("\nWarnings:") + if unused_count > 0: + print(f" - Unused imports: {unused_count}") + print(" Consider addressing warnings in future updates.") - all_critical_errors.extend([f"F821: {e}" for e in undefined_errors]) - else: - print("No undefined variables found") +def run_analysis() -> int: + print("Running static analysis...") + print("=" * 60) - # Check for unused imports with flake8 - WARNING - print("\n3. Checking for unused imports...") - print("-" * 40) + critical_error_count = 0 + warning_count = 0 - result = subprocess.run( - ["python", "-m", "flake8", "lean/", - "--select=F401", - "--ignore=ALL", - "--count", - "--exit-zero"], - capture_output=True, - text=True - ) + # Check for missing function arguments with mypy + print("\n1. Checking for missing function arguments...") + print("-" * 40) - if result.stdout.strip() and result.stdout.strip() != "0": - detail = subprocess.run( - ["python", "-m", "flake8", "lean/", "--select=F401", "--ignore=ALL", "--exit-zero"], - capture_output=True, - text=True - ) + call_arg_errors = run_mypy_check() + display_errors("function call argument mismatch(es)", call_arg_errors) + critical_error_count += len(call_arg_errors) - unused_imports = [e.strip() for e in detail.stdout.split('\n') if e.strip()] - if unused_imports: - print(f"WARNING: {len(unused_imports)} unused import(s) found:") + # Check for undefined variables with flake8 + print("\n2. Checking for undefined variables...") + print("-" * 40) - for error in unused_imports: - print(f" {error}") + undefined_errors, undefined_count = run_flake8_check("F821") + display_errors("undefined variable(s)", undefined_errors) + critical_error_count += undefined_count - all_warnings.extend([f"F401: {e}" for e in unused_imports]) - else: - print("No unused imports found") - else: - print("No unused imports found") + # Check for unused imports with flake8 + print("\n3. Checking for unused imports...") + print("-" * 40) - print("\n" + "=" * 60) + unused_imports, unused_count = run_flake8_check("F401") + display_errors("unused import(s)", unused_imports, is_critical=False) + warning_count += unused_count # Summary - if all_critical_errors: - total_errors = len(all_critical_errors) - print(f"BUILD FAILED: Found {total_errors} critical error(s)") + print("\n" + "=" * 60) + if critical_error_count > 0: + print(f"BUILD FAILED: Found {critical_error_count} critical error(s)") print("\nSummary of critical errors:") print(f" - Function call argument mismatches: {len(call_arg_errors)}") - undefined_count = sum(1 for e in all_critical_errors if e.startswith('F821:')) print(f" - Undefined variables: {undefined_count}") - if all_warnings: - display_warning_summary(all_warnings) + if warning_count > 0: + display_warning_summary(unused_count) return 1 - if all_warnings: - print(f"BUILD PASSED with {len(all_warnings)} warning(s)") - display_warning_summary(all_warnings) + if warning_count > 0: + print(f"BUILD PASSED with {warning_count} warning(s)") + display_warning_summary(unused_count) return 0 print("SUCCESS: All checks passed with no warnings")