mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-06-28 06:41:43 +00:00
test: add pytlint imports plugin and fix errors
This commit is contained in:
@ -25,19 +25,19 @@ from pylint.lint import PyLinter
|
||||
from typing import Any
|
||||
|
||||
|
||||
class MethodTypeOrder(StrEnum):
|
||||
class MethodType(StrEnum):
|
||||
"""
|
||||
method type enumeration
|
||||
|
||||
Attributes:
|
||||
Class(MethodTypeOrder): (class attribute) class method
|
||||
Delete(MethodTypeOrder): (class attribute) destructor-like methods
|
||||
Init(MethodTypeOrder): (class attribute) initialization method
|
||||
Magic(MethodTypeOrder): (class attribute) other magical methods
|
||||
New(MethodTypeOrder): (class attribute) constructor method
|
||||
Normal(MethodTypeOrder): (class attribute) usual method
|
||||
Property(MethodTypeOrder): (class attribute) property method
|
||||
Static(MethodTypeOrder): (class attribute) static method
|
||||
Class(MethodType): (class attribute) class method
|
||||
Delete(MethodType): (class attribute) destructor-like methods
|
||||
Init(MethodType): (class attribute) initialization method
|
||||
Magic(MethodType): (class attribute) other magical methods
|
||||
New(MethodType): (class attribute) constructor method
|
||||
Normal(MethodType): (class attribute) usual method
|
||||
Property(MethodType): (class attribute) property method
|
||||
Static(MethodType): (class attribute) static method
|
||||
"""
|
||||
|
||||
Class = "classmethod"
|
||||
@ -59,20 +59,20 @@ class DefinitionOrder(BaseRawFileChecker):
|
||||
"""
|
||||
|
||||
DECORATED_METHODS_ORDER = {
|
||||
"cached_property": MethodTypeOrder.Property,
|
||||
"classmethod": MethodTypeOrder.Class,
|
||||
"property": MethodTypeOrder.Property,
|
||||
"staticmethod": MethodTypeOrder.Static,
|
||||
"cached_property": MethodType.Property,
|
||||
"classmethod": MethodType.Class,
|
||||
"property": MethodType.Property,
|
||||
"staticmethod": MethodType.Static,
|
||||
}
|
||||
|
||||
name = "method-ordering"
|
||||
msgs = {
|
||||
"W6001": (
|
||||
"Invalid method order %s, expected %s",
|
||||
"methods-out-of-order",
|
||||
"Methods are defined out of recommended order.",
|
||||
)
|
||||
),
|
||||
}
|
||||
name = "method-ordering"
|
||||
options = (
|
||||
(
|
||||
"method-type-order",
|
||||
@ -114,7 +114,7 @@ class DefinitionOrder(BaseRawFileChecker):
|
||||
return list(filter(is_defined_function, source))
|
||||
|
||||
@staticmethod
|
||||
def resolve_type(function: nodes.FunctionDef) -> MethodTypeOrder:
|
||||
def resolve_type(function: nodes.FunctionDef) -> MethodType:
|
||||
"""
|
||||
resolve type of the function
|
||||
|
||||
@ -122,15 +122,15 @@ class DefinitionOrder(BaseRawFileChecker):
|
||||
function(nodes.FunctionDef): function definition
|
||||
|
||||
Returns:
|
||||
MethodTypeOrder: resolved function type
|
||||
MethodType: resolved function type
|
||||
"""
|
||||
# init methods
|
||||
if function.name in ("__init__", "__post_init__"):
|
||||
return MethodTypeOrder.Init
|
||||
return MethodType.Init
|
||||
if function.name in ("__new__",):
|
||||
return MethodTypeOrder.New
|
||||
return MethodType.New
|
||||
if function.name in ("__del__",):
|
||||
return MethodTypeOrder.Delete
|
||||
return MethodType.Delete
|
||||
|
||||
# decorated methods
|
||||
decorators = []
|
||||
@ -142,10 +142,10 @@ class DefinitionOrder(BaseRawFileChecker):
|
||||
|
||||
# magic methods
|
||||
if function.name.startswith("__") and function.name.endswith("__"):
|
||||
return MethodTypeOrder.Magic
|
||||
return MethodType.Magic
|
||||
|
||||
# normal method
|
||||
return MethodTypeOrder.Normal
|
||||
return MethodType.Normal
|
||||
|
||||
def check_class(self, clazz: nodes.ClassDef) -> None:
|
||||
"""
|
||||
@ -184,7 +184,7 @@ class DefinitionOrder(BaseRawFileChecker):
|
||||
try:
|
||||
function_type_index = self.linter.config.method_type_order.index(function_type)
|
||||
except ValueError:
|
||||
function_type_index = 10 # not in the list
|
||||
function_type_index = len(self.linter.config.method_type_order) # not in the list
|
||||
|
||||
return function_type_index, function.name
|
||||
|
||||
|
196
pylint_plugins/import_order.py
Normal file
196
pylint_plugins/import_order.py
Normal file
@ -0,0 +1,196 @@
|
||||
#
|
||||
# Copyright (c) 2021-2023 ahriman team.
|
||||
#
|
||||
# This file is part of ahriman
|
||||
# (see https://github.com/arcan1s/ahriman).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from astroid import nodes
|
||||
from collections.abc import Iterable
|
||||
from enum import StrEnum
|
||||
from pylint.checkers import BaseRawFileChecker
|
||||
from pylint.lint import PyLinter
|
||||
from typing import Any
|
||||
|
||||
|
||||
class ImportType(StrEnum):
|
||||
"""
|
||||
import type enumeration
|
||||
|
||||
Attributes:
|
||||
Package(MethodTypeOrder): (class attribute) package import
|
||||
PackageFrom(MethodTypeOrder): (class attribute) package import, from clause
|
||||
System(ImportType): (class attribute) system installed packages
|
||||
SystemFrom(MethodTypeOrder): (class attribute) system installed packages, from clause
|
||||
"""
|
||||
|
||||
Package = "package"
|
||||
PackageFrom = "package-from"
|
||||
System = "system"
|
||||
SystemFrom = "system-from"
|
||||
|
||||
|
||||
class ImportOrder(BaseRawFileChecker):
|
||||
"""
|
||||
check if imports are defined in recommended order
|
||||
"""
|
||||
|
||||
msgs = {
|
||||
"W6002": (
|
||||
"Invalid import order %s, expected before %s",
|
||||
"imports-out-of-order",
|
||||
"Imports are defined out of recommended order.",
|
||||
),
|
||||
"W6003": (
|
||||
"Import contains more than one package: %s",
|
||||
"multiple-package-imports",
|
||||
"Multiple package imports are not allowed.",
|
||||
),
|
||||
"W6004": (
|
||||
"Invalid from import order %s, expected %s",
|
||||
"from-imports-out-of-order",
|
||||
"From imports are defined out of recommended order.",
|
||||
),
|
||||
}
|
||||
name = "import-ordering"
|
||||
options = (
|
||||
(
|
||||
"import-type-order",
|
||||
{
|
||||
"default": [
|
||||
"system",
|
||||
"system-from",
|
||||
"package",
|
||||
"package-from",
|
||||
],
|
||||
"type": "csv",
|
||||
"metavar": "<comma-separated types>",
|
||||
"help": "Import types order to check.",
|
||||
},
|
||||
),
|
||||
(
|
||||
"root-module",
|
||||
{
|
||||
"default": "ahriman",
|
||||
"type": "string",
|
||||
"help": "Root module name",
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def imports(source: Iterable[Any], start_lineno: int = 0) -> list[nodes.Import | nodes.ImportFrom]:
|
||||
"""
|
||||
extract import nodes from list of raw nodes
|
||||
|
||||
Args:
|
||||
source(Iterable[Any]): all available nodes
|
||||
start_lineno(int, optional): minimal allowed line number (Default value = 0)
|
||||
|
||||
Returns:
|
||||
list[nodes.Import | nodes.ImportFrom]: list of import nodes
|
||||
"""
|
||||
|
||||
def is_defined_import(imports: Any) -> bool:
|
||||
return isinstance(imports, (nodes.Import, nodes.ImportFrom)) \
|
||||
and imports.lineno is not None \
|
||||
and imports.lineno >= start_lineno
|
||||
|
||||
return list(filter(is_defined_import, source))
|
||||
|
||||
def check_from_imports(self, imports: nodes.ImportFrom) -> None:
|
||||
"""
|
||||
check import from statement
|
||||
|
||||
Args:
|
||||
imports(nodes.ImportFrom): import from node
|
||||
"""
|
||||
imported = [names for names, _ in imports.names]
|
||||
for real, expected in zip(imported, sorted(imported)):
|
||||
if real == expected:
|
||||
continue
|
||||
self.add_message("from-imports-out-of-order", line=imports.lineno, args=(real, expected))
|
||||
break
|
||||
|
||||
def check_imports(self, imports: list[nodes.Import | nodes.ImportFrom], root_package: str) -> None:
|
||||
"""
|
||||
check imports
|
||||
|
||||
Args:
|
||||
imports(list[nodes.Import | nodes.ImportFrom]): list of imports in their defined order
|
||||
root_package(str): root package name
|
||||
"""
|
||||
last_statement: tuple[int, str] | None = None
|
||||
|
||||
for statement in imports:
|
||||
# define types and perform specific checks
|
||||
if isinstance(statement, nodes.ImportFrom):
|
||||
import_name = statement.modname
|
||||
root, *_ = import_name.split(".", maxsplit=1)
|
||||
import_type = ImportType.PackageFrom if root_package == root else ImportType.SystemFrom
|
||||
# check from import itself
|
||||
self.check_from_imports(statement)
|
||||
else:
|
||||
import_name = next(name for name, _ in statement.names)
|
||||
root, *_ = import_name.split(".", maxsplit=1)[0]
|
||||
import_type = ImportType.Package if root_package == root else ImportType.System
|
||||
# check import itself
|
||||
self.check_package_imports(statement)
|
||||
|
||||
# extract index
|
||||
try:
|
||||
import_type_index = self.linter.config.import_type_order.index(import_type)
|
||||
except ValueError:
|
||||
import_type_index = len(self.linter.config.import_type_order)
|
||||
|
||||
# check ordering if possible
|
||||
if last_statement is not None:
|
||||
_, last_statement_name = last_statement
|
||||
if last_statement > (import_type_index, import_name):
|
||||
self.add_message("imports-out-of-order", line=statement.lineno,
|
||||
args=(import_name, last_statement_name))
|
||||
|
||||
# update the last value
|
||||
last_statement = import_type_index, import_name
|
||||
|
||||
def check_package_imports(self, imports: nodes.Import) -> None:
|
||||
"""
|
||||
check package import
|
||||
|
||||
Args:
|
||||
imports(nodes.Import): package import node
|
||||
"""
|
||||
if len(imports.names) != 1:
|
||||
self.add_message("multiple-package-imports", line=imports.lineno, args=(imports.names,))
|
||||
|
||||
def process_module(self, node: nodes.Module) -> None:
|
||||
"""
|
||||
process module
|
||||
|
||||
Args:
|
||||
node(nodes.Module): module node to check
|
||||
"""
|
||||
root_module, *_ = node.qname().split(".")
|
||||
self.check_imports(self.imports(node.values()), root_module)
|
||||
|
||||
|
||||
def register(linter: PyLinter) -> None:
|
||||
"""
|
||||
register custom checker
|
||||
|
||||
Args:
|
||||
linter(PyLinter): linter in which checker should be registered
|
||||
"""
|
||||
linter.register_checker(ImportOrder(linter))
|
Reference in New Issue
Block a user