Skip to content

Commit

Permalink
Replace pylint with ruff (#277)
Browse files Browse the repository at this point in the history
* fix code to comply with ruff

* replace pylint with ruff in pre-commit

* remove pylint disable from codemodder code

* update contributing.md

* add ruff to makefile
  • Loading branch information
clavedeluna authored Feb 19, 2024
1 parent 9b5d38b commit 8d6a03d
Show file tree
Hide file tree
Showing 35 changed files with 48 additions and 121 deletions.
18 changes: 0 additions & 18 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,6 @@ concurrency:
cancel-in-progress: true

jobs:
linting:
name: Pylint Checks
runs-on: ubuntu-20.04
timeout-minutes: 5
steps:
- name: Set Up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Check out code
uses: actions/checkout@v4
- name: Install Codemodder Package
run: pip install .
- name: Install Dependencies
run: pip install ".[test]"
- name: Run pylint
run: make lint

complexity:
name: Code Complexity
runs-on: ubuntu-20.04
Expand Down
8 changes: 8 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,11 @@ repos:
"types-PyYAML==6.0",
"types-toml~=0.10",
]
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.2.2
hooks:
- id: ruff
exclude: tests/samples/
# todo: replace black with this?
# Run the formatter.
# - id: ruff-format
11 changes: 5 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@

## Pre-commit install

We use [pre-commit](https://pre-commit.com/) to run some quick linting such as `black`.
While pre-commit isn't configured in CI, `black` is. `pylint` and running tests are not configured
for pre-commit so commiting is fast.
We use [pre-commit](https://pre-commit.com/) to run some quick linting such as `black` and `ruff`.
This also runs in CI.


## Local Development
Expand All @@ -15,11 +14,11 @@ but for local development here is a good workflow.
1. To use virtualenv, create an environment with `virtualenv pixeeenv` or `/usr/bin/env python3 -m venv pixeeenv`
to specify a specific Python version. If using `bash` or any compatible shell, activate with `source pixeeenv/bin/activate`. Otherwise, look at [`venv`'s documentation](https://docs.python.org/3/library/venv.html) for instructions.

1. `cd codemodder-python` and `pip install -e .` to install the package in development mode
2. `cd codemodder-python` and `pip install -e .` to install the package in development mode

1. Run `pip install ".[all]"` to install packages used for development and testing
3. Run `pip install ".[all]"` to install packages used for development and testing

1. You should now be able to run `pylint`, `pytest`, etc.
4. You should now be able to run `ruff`, `pytest`, etc.


## Docker
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pygoat-test:
${PYTEST} -v ci_tests/test_pygoat_findings.py

lint:
pylint -v codemodder core_codemods tests integration_tests
ruff check src tests integration_tests --exclude tests/samples/

radon:
radon cc codemodder --min A --total-average
Expand Down
1 change: 0 additions & 1 deletion integration_tests/base_test.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# pylint: disable=no-member, protected-access, attribute-defined-outside-init
import git
import json
import os
Expand Down
41 changes: 0 additions & 41 deletions pylintrc

This file was deleted.

4 changes: 2 additions & 2 deletions src/codemodder/codemods/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import libcst as cst

from codemodder.codemods.base_codemod import ( # pylint: disable=unused-import
from codemodder.codemods.base_codemod import ( # noqa: F401
BaseCodemod,
Metadata,
Reference,
Expand All @@ -13,7 +13,7 @@
LibcstResultTransformer,
LibcstTransformerPipeline,
)
from codemodder.file_context import FileContext # pylint: disable=unused-import
from codemodder.file_context import FileContext # noqa: F401
from codemodder.codemods.semgrep import SemgrepRuleDetector


Expand Down
2 changes: 1 addition & 1 deletion src/codemodder/codemods/base_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def __init__(
results: list[Result] | None,
line_exclude: list[int],
line_include: list[int],
): # pylint: disable=super-init-not-called
):
self.results = results
self.line_exclude = line_exclude
self.line_include = line_include
Expand Down
11 changes: 2 additions & 9 deletions src/codemodder/codemods/libcst_transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@ def update_code(file_path, new_code):
f.write(new_code)


class LibcstResultTransformer(
BaseTransformer
): # pylint: disable=too-many-public-methods
class LibcstResultTransformer(BaseTransformer):
"""
Transformer class that performs libcst-based transformations on a given file
Expand Down Expand Up @@ -76,7 +74,6 @@ def transform(
def _new_or_updated_node(self, original_node, updated_node):
if self.node_is_selected(original_node):
if (attr := getattr(self, "on_result_found", None)) is not None:
# pylint: disable=not-callable
new_node = attr(original_node, updated_node)
self.report_change(original_node)
return new_node
Expand All @@ -102,7 +99,6 @@ def leave_ClassDef(
return self._new_or_updated_node(original_node, updated_node)

def node_position(self, node):
# pylint: disable=no-member
# See https://github.com/Instagram/LibCST/blob/main/libcst/_metadata_dependent.py#L112
return self.get_metadata(self.METADATA_DEPENDENCIES[0], node)

Expand Down Expand Up @@ -134,14 +130,11 @@ def report_change(self, original_node):
)

def remove_unused_import(self, original_node):
# pylint: disable=no-member
RemoveImportsVisitor.remove_unused_import_by_node(self.context, original_node)

def add_needed_import(self, module, obj=None):
# TODO: do we need to check if this import already exists?
AddImportsVisitor.add_needed_import(
self.context, module, obj # pylint: disable=no-member
)
AddImportsVisitor.add_needed_import(self.context, module, obj)

def update_call_target(
self,
Expand Down
4 changes: 2 additions & 2 deletions src/codemodder/codemods/sonar.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ def from_core_codemod(
rules: list[str],
transformer: BaseTransformerPipeline | None = None,
new_references: list[Reference] | None = None,
): # pylint: disable=too-many-arguments
):
return SonarCodemod(
metadata=Metadata(
name=name,
summary="Sonar: " + other.summary,
review_guidance=other._metadata.review_guidance, # pylint: disable=protected-access
review_guidance=other._metadata.review_guidance,
references=(
other.references
if not new_references
Expand Down
2 changes: 1 addition & 1 deletion src/codemodder/codemods/transformations/clean_imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ def _natural_key(s):
"""
Converts a string into a key for natural sorting.
"""
convert = lambda text: int(text) if text.isdigit() else text.lower()
convert = lambda text: int(text) if text.isdigit() else text.lower() # noqa: E731
return [convert(imp) for imp in re.split(r"([0-9]+)", s)]


Expand Down
1 change: 0 additions & 1 deletion src/codemodder/codemods/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ class BaseType(Enum):
FALSE = 7


# pylint: disable-next=R0911
def infer_expression_type(node: cst.BaseExpression) -> Optional[BaseType]:
"""
Tries to infer if the resulting type of a given expression is one of the base literal types.
Expand Down
5 changes: 2 additions & 3 deletions src/codemodder/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from codemodder.codemods.base_codemod import BaseCodemod


class CodemodExecutionContext: # pylint: disable=too-many-instance-attributes
class CodemodExecutionContext:
_results_by_codemod: dict[str, list[ChangeSet]] = {}
_failures_by_codemod: dict[str, list[Path]] = {}
_dependency_update_by_codemod: dict[str, PackageStore | None] = {}
Expand All @@ -50,7 +50,7 @@ def __init__(
path_exclude: list[str],
tool_result_files_map: dict[str, list[str]] | None = None,
max_workers: int = 1,
): # pylint: disable=too-many-arguments
):
self.directory = directory
self.dry_run = dry_run
self.verbose = verbose
Expand Down Expand Up @@ -118,7 +118,6 @@ def process_dependencies(
self._dependency_update_by_codemod[codemod_id] = None
return record

# pylint: disable-next=cyclic-import
from codemodder.dependency_management import DependencyManager

for package_store in store_list:
Expand Down
2 changes: 1 addition & 1 deletion src/codemodder/dependency_management/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from .dependency_manager import DependencyManager
from .dependency_manager import DependencyManager # noqa: F401
2 changes: 1 addition & 1 deletion src/codemodder/file_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@


@dataclass
class FileContext: # pylint: disable=too-many-instance-attributes
class FileContext:
"""
Extra context for running codemods on a given file based on the cli parameters.
"""
Expand Down
1 change: 1 addition & 0 deletions src/codemodder/project_analysis/file_parsers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# ruff: noqa: F401
from .requirements_txt_file_parser import RequirementsTxtParser
from .pyproject_toml_file_parser import PyprojectTomlParser
from .setup_cfg_file_parser import SetupCfgParser
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class PackageStore:

def __init__(
self,
type: FileType, # pylint: disable=redefined-builtin
type: FileType,
file: Path,
dependencies: set[str | Requirement],
py_versions: list[str],
Expand Down
2 changes: 1 addition & 1 deletion src/codemodder/result.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class Result(ABCDataclass):
rule_id: str
locations: list[Location]

def match_location(self, pos, node): # pylint: disable=unused-argument
def match_location(self, pos, node):
for location in self.locations:
start_column = location.start.column
end_column = location.end.column
Expand Down
2 changes: 1 addition & 1 deletion src/codemodder/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def positional_to_keyword(
"""
new_args = []
for i, arg in enumerate(args):
if arg.keyword == None and pos_to_keyword[i] != None:
if arg.keyword is None and pos_to_keyword[i] is not None:
new_args.append(arg.with_changes(keyword=cst.Name(pos_to_keyword[i])))
else:
new_args.append(arg)
Expand Down
1 change: 1 addition & 0 deletions src/core_codemods/api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# ruff: noqa: F401
from codemodder.codemods.api import (
Metadata,
Reference,
Expand Down
10 changes: 5 additions & 5 deletions src/core_codemods/file_resource_leak.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ def __init__(
def transform_module_impl(self, tree: cst.Module) -> cst.Module:
fr = FindResources(self.context)
tree.visit(fr)
line_filter = lambda x: self.filter_by_path_includes_or_excludes(x[2])

def line_filter(x):
return self.filter_by_path_includes_or_excludes(x[2])

for k, v in fr.assigned_resources.items():
fr.assigned_resources[k] = [t for t in v if line_filter(t)]
fixer = ResourceLeakFixer(self.context, fr.assigned_resources)
Expand Down Expand Up @@ -185,9 +188,7 @@ def __init__(
self.leaked_assigned_resources = leaked_assigned_resources
self.changes: list[Change] = []

def _is_fixable(
self, block, index, named_targets, other_targets
) -> bool: # pylint: disable=too-many-arguments
def _is_fixable(self, block, index, named_targets, other_targets) -> bool:
# assigned to something that is not a Name?
if other_targets:
return False
Expand Down Expand Up @@ -306,7 +307,6 @@ def _last_ancestor_index(self, node, node_sequence) -> Optional[int]:
last = i
return last

# pylint: disable-next=too-many-arguments
def _wrap_in_with_statement(
self,
stmts: list[SimpleStatementLine],
Expand Down
1 change: 0 additions & 1 deletion src/core_codemods/fix_async_task_instantiation.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ class FixAsyncTaskInstantiation(SimpleCodemod, NameAndAncestorResolutionMixin):
change_description = "Replace instantiation of `asyncio.Task` with higher-level functions to create tasks."
_module_name = "asyncio"

# pylint: disable=too-many-return-statements
def leave_Call(self, original_node: cst.Call, updated_node: cst.Call) -> cst.Call:
if not self.filter_by_path_includes_or_excludes(
self.node_position(original_node)
Expand Down
2 changes: 1 addition & 1 deletion src/core_codemods/flask_json_response_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class FlaskJsonResponseTypeVisitor(
content_type_key = "Content-Type"
json_content_type = "application/json"

def __init__( # pylint: disable=super-init-not-called
def __init__(
self,
context: CodemodContext,
file_context: FileContext,
Expand Down
1 change: 0 additions & 1 deletion src/core_codemods/numpy_nan_equality.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ class NumpyNanEqualityTransformer(LibcstResultTransformer, NameResolutionMixin):

np_nan = "numpy.nan"

# pylint: disable-next=too-many-arguments
def _build_nan_comparison(
self, nan_node, node, preprend_not, lpar, rpar
) -> cst.BaseExpression:
Expand Down
5 changes: 2 additions & 3 deletions src/core_codemods/sql_parameterization.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def _build_param_element(self, prepend, middle, append, aliased_expr):
t = _extract_prefix_raw_value(self, e)
if t:
prefix, raw_value = t
if not "b" in prefix and not "r" in prefix and not "u" in prefix:
if all(char not in prefix for char in "bru"):
format_pieces.append(raw_value)
exception = True
if not exception:
Expand Down Expand Up @@ -231,7 +231,6 @@ def _fix_injection(

return (prepend, append)

# pylint: disable-next=too-many-arguments
def _remove_literal_and_gather_extra(
self, original_node, updated_node, prefix, new_raw_value, extra_raw_value
) -> Optional[cst.SimpleString]:
Expand Down Expand Up @@ -487,7 +486,7 @@ def _is_literal_start(
matches = list(quote_pattern.finditer(raw_value))
# avoid cases like: "where name = 'foo\\\'s name'"
# don't count \\' as these are escaped in string literals
return (matches != None) and len(matches) % 2 == modulo_2
return (matches is not None) and len(matches) % 2 == modulo_2

def _is_literal_end(
self, node: cst.CSTNode | tuple[cst.CSTNode, cst.CSTNode]
Expand Down
1 change: 0 additions & 1 deletion src/core_codemods/url_sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,6 @@ def _find_assignments(self, node: CSTNode):
Given a MetadataWrapper and a CSTNode representing an access, find all the possible assignments that it refers.
"""
scope = self.get_metadata(ScopeProvider, node)
# pylint: disable=protected-access
return next(iter(scope.accesses[node]))._Access__assignments

def find_single_assignment(self, node: CSTNode) -> Optional[CSTNode]:
Expand Down
Loading

0 comments on commit 8d6a03d

Please sign in to comment.