mirror of
https://github.com/davidhalter/django-stubs.git
synced 2025-12-06 12:14:28 +08:00
improve annotations in some places (#202)
* improve annotations in some places * linting
This commit is contained in:
88
scripts/catch_non_abstract_annotation.py
Normal file
88
scripts/catch_non_abstract_annotation.py
Normal file
@@ -0,0 +1,88 @@
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
import libcst
|
||||
from libcst import Annotation, BaseExpression, FunctionDef, Name, Subscript
|
||||
from libcst.metadata import SyntacticPositionProvider
|
||||
|
||||
BASE_DIR = 'django-stubs'
|
||||
|
||||
fpath = os.path.join(BASE_DIR, 'core', 'checks', 'model_checks.pyi')
|
||||
with open(fpath, 'r') as f:
|
||||
contents = f.read()
|
||||
|
||||
|
||||
tree = libcst.parse_module(contents)
|
||||
|
||||
|
||||
class TypeAnnotationsAnalyzer(libcst.CSTVisitor):
|
||||
METADATA_DEPENDENCIES = (SyntacticPositionProvider,)
|
||||
|
||||
def __init__(self, fpath: str):
|
||||
super().__init__()
|
||||
self.fpath = fpath
|
||||
|
||||
def get_node_location(self, node: FunctionDef) -> str:
|
||||
start_line = self.get_metadata(SyntacticPositionProvider, node).start.line
|
||||
return f'{self.fpath}:{start_line}'
|
||||
|
||||
def show_error_for_node(self, node: FunctionDef, error_message: str):
|
||||
print(self.get_node_location(node), error_message)
|
||||
|
||||
def check_subscripted_annotation(self, annotation: BaseExpression) -> Optional[str]:
|
||||
if isinstance(annotation, Subscript):
|
||||
if isinstance(annotation.value, Name):
|
||||
error_message = self.check_concrete_class_usage(annotation.value)
|
||||
if error_message:
|
||||
return error_message
|
||||
|
||||
if annotation.value.value == 'Union':
|
||||
for slice_param in annotation.slice:
|
||||
if isinstance(slice_param.slice.value, Name):
|
||||
error_message = self.check_concrete_class_usage(annotation.value)
|
||||
if error_message:
|
||||
return error_message
|
||||
|
||||
def check_concrete_class_usage(self, name_node: Name) -> Optional[str]:
|
||||
if name_node.value == 'List':
|
||||
return (f'Concrete class {name_node.value!r} used for an iterable annotation. '
|
||||
f'Use abstract collection (Iterable, Collection, Sequence) instead')
|
||||
|
||||
def visit_FunctionDef(self, node: FunctionDef) -> Optional[bool]:
|
||||
params_node = node.params
|
||||
for param_node in [*params_node.params, *params_node.default_params]:
|
||||
param_name = param_node.name.value
|
||||
annotation_node = param_node.annotation # type: Annotation
|
||||
if annotation_node is not None:
|
||||
annotation = annotation_node.annotation
|
||||
if annotation.value == 'None':
|
||||
self.show_error_for_node(node, f'"None" type annotation used for parameter {param_name!r}')
|
||||
continue
|
||||
|
||||
error_message = self.check_subscripted_annotation(annotation)
|
||||
if error_message is not None:
|
||||
self.show_error_for_node(node, error_message)
|
||||
continue
|
||||
|
||||
if node.returns is not None:
|
||||
return_annotation = node.returns.annotation
|
||||
if isinstance(return_annotation, Subscript) and return_annotation.value.value == 'Union':
|
||||
self.show_error_for_node(node, 'Union is return type annotation')
|
||||
|
||||
return False
|
||||
|
||||
|
||||
for dirpath, dirnames, filenames in os.walk(BASE_DIR):
|
||||
for filename in filenames:
|
||||
fpath = os.path.join(dirpath, filename)
|
||||
# skip all other checks for now, low priority
|
||||
if not fpath.startswith(('django-stubs/db', 'django-stubs/views', 'django-stubs/apps',
|
||||
'django-stubs/http', 'django-stubs/contrib/postgres')):
|
||||
continue
|
||||
|
||||
with open(fpath, 'r') as f:
|
||||
contents = f.read()
|
||||
|
||||
tree = libcst.MetadataWrapper(libcst.parse_module(contents))
|
||||
analyzer = TypeAnnotationsAnalyzer(fpath)
|
||||
tree.visit(analyzer)
|
||||
Reference in New Issue
Block a user