Enable automatic formatting for `sphinx/pycode/` (#12965)

This commit is contained in:
Adam Turner 2024-10-04 15:33:19 +01:00 committed by GitHub
parent c1d192e2c2
commit a44293a3a8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 118 additions and 91 deletions

View File

@ -475,7 +475,6 @@ exclude = [
"sphinx/ext/napoleon/docstring.py",
"sphinx/ext/todo.py",
"sphinx/ext/viewcode.py",
"sphinx/pycode/*",
"sphinx/registry.py",
"sphinx/search/*",
"sphinx/testing/*",

View File

@ -52,7 +52,9 @@ class ModuleAnalyzer:
try:
filename = loader.get_filename(modname)
except ImportError as err:
raise PycodeError('error getting filename for %r' % modname, err) from err
raise PycodeError(
'error getting filename for %r' % modname, err
) from err
if filename is None:
# all methods for getting filename failed, so raise...
raise PycodeError('no source found for module %r' % modname)
@ -70,12 +72,17 @@ class ModuleAnalyzer:
@classmethod
def for_string(
cls: type[ModuleAnalyzer], string: str, modname: str, srcname: str = '<string>',
cls: type[ModuleAnalyzer],
string: str,
modname: str,
srcname: str = '<string>',
) -> ModuleAnalyzer:
return cls(string, modname, srcname)
@classmethod
def for_file(cls: type[ModuleAnalyzer], filename: str, modname: str) -> ModuleAnalyzer:
def for_file(
cls: type[ModuleAnalyzer], filename: str, modname: str
) -> ModuleAnalyzer:
if ('file', filename) in cls.cache:
return cls.cache['file', filename]
try:
@ -126,7 +133,7 @@ class ModuleAnalyzer:
parser.parse()
self.attr_docs = {}
for (scope, comment) in parser.comments.items():
for scope, comment in parser.comments.items():
if comment:
self.attr_docs[scope] = [*comment.splitlines(), '']
else:

View File

@ -6,36 +6,34 @@ import ast
from typing import NoReturn, overload
OPERATORS: dict[type[ast.AST], str] = {
ast.Add: "+",
ast.And: "and",
ast.BitAnd: "&",
ast.BitOr: "|",
ast.BitXor: "^",
ast.Div: "/",
ast.FloorDiv: "//",
ast.Invert: "~",
ast.LShift: "<<",
ast.MatMult: "@",
ast.Mult: "*",
ast.Mod: "%",
ast.Not: "not",
ast.Pow: "**",
ast.Or: "or",
ast.RShift: ">>",
ast.Sub: "-",
ast.UAdd: "+",
ast.USub: "-",
ast.Add: '+',
ast.And: 'and',
ast.BitAnd: '&',
ast.BitOr: '|',
ast.BitXor: '^',
ast.Div: '/',
ast.FloorDiv: '//',
ast.Invert: '~',
ast.LShift: '<<',
ast.MatMult: '@',
ast.Mult: '*',
ast.Mod: '%',
ast.Not: 'not',
ast.Pow: '**',
ast.Or: 'or',
ast.RShift: '>>',
ast.Sub: '-',
ast.UAdd: '+',
ast.USub: '-',
}
@overload
def unparse(node: None, code: str = '') -> None:
...
def unparse(node: None, code: str = '') -> None: ... # NoQA: E704
@overload
def unparse(node: ast.AST, code: str = '') -> str:
...
def unparse(node: ast.AST, code: str = '') -> str: ... # NoQA: E704
def unparse(node: ast.AST | None, code: str = '') -> str | None:
@ -54,12 +52,13 @@ class _UnparseVisitor(ast.NodeVisitor):
def _visit_op(self, node: ast.AST) -> str:
return OPERATORS[node.__class__]
for _op in OPERATORS:
locals()[f'visit_{_op.__name__}'] = _visit_op
def visit_arg(self, node: ast.arg) -> str:
if node.annotation:
return f"{node.arg}: {self.visit(node.annotation)}"
return f'{node.arg}: {self.visit(node.annotation)}'
else:
return node.arg
@ -68,9 +67,9 @@ class _UnparseVisitor(ast.NodeVisitor):
name = self.visit(arg)
if default:
if arg.annotation:
name += " = %s" % self.visit(default)
name += ' = %s' % self.visit(default)
else:
name += "=%s" % self.visit(default)
name += '=%s' % self.visit(default)
return name
def visit_arguments(self, node: ast.arguments) -> str:
@ -85,8 +84,10 @@ class _UnparseVisitor(ast.NodeVisitor):
for _ in range(len(kw_defaults), len(node.kwonlyargs)):
kw_defaults.insert(0, None)
args: list[str] = [self._visit_arg_with_default(arg, defaults[i])
for i, arg in enumerate(node.posonlyargs)]
args: list[str] = [
self._visit_arg_with_default(arg, defaults[i])
for i, arg in enumerate(node.posonlyargs)
]
if node.posonlyargs:
args.append('/')
@ -95,7 +96,7 @@ class _UnparseVisitor(ast.NodeVisitor):
args.append(self._visit_arg_with_default(arg, defaults[i + posonlyargs]))
if node.vararg:
args.append("*" + self.visit(node.vararg))
args.append('*' + self.visit(node.vararg))
if node.kwonlyargs and not node.vararg:
args.append('*')
@ -103,33 +104,33 @@ class _UnparseVisitor(ast.NodeVisitor):
args.append(self._visit_arg_with_default(arg, kw_defaults[i]))
if node.kwarg:
args.append("**" + self.visit(node.kwarg))
args.append('**' + self.visit(node.kwarg))
return ", ".join(args)
return ', '.join(args)
def visit_Attribute(self, node: ast.Attribute) -> str:
return f"{self.visit(node.value)}.{node.attr}"
return f'{self.visit(node.value)}.{node.attr}'
def visit_BinOp(self, node: ast.BinOp) -> str:
# Special case ``**`` to not have surrounding spaces.
if isinstance(node.op, ast.Pow):
return "".join(map(self.visit, (node.left, node.op, node.right)))
return " ".join(map(self.visit, (node.left, node.op, node.right)))
return ''.join(map(self.visit, (node.left, node.op, node.right)))
return ' '.join(map(self.visit, (node.left, node.op, node.right)))
def visit_BoolOp(self, node: ast.BoolOp) -> str:
op = " %s " % self.visit(node.op)
op = ' %s ' % self.visit(node.op)
return op.join(self.visit(e) for e in node.values)
def visit_Call(self, node: ast.Call) -> str:
args = ', '.join(
[self.visit(e) for e in node.args]
+ [f"{k.arg}={self.visit(k.value)}" for k in node.keywords],
+ [f'{k.arg}={self.visit(k.value)}' for k in node.keywords],
)
return f"{self.visit(node.func)}({args})"
return f'{self.visit(node.func)}({args})'
def visit_Constant(self, node: ast.Constant) -> str:
if node.value is Ellipsis:
return "..."
return '...'
elif isinstance(node.value, int | float | complex):
if self.code:
return ast.get_source_segment(self.code, node) or repr(node.value)
@ -141,34 +142,34 @@ class _UnparseVisitor(ast.NodeVisitor):
def visit_Dict(self, node: ast.Dict) -> str:
keys = (self.visit(k) for k in node.keys if k is not None)
values = (self.visit(v) for v in node.values)
items = (k + ": " + v for k, v in zip(keys, values, strict=True))
return "{" + ", ".join(items) + "}"
items = (k + ': ' + v for k, v in zip(keys, values, strict=True))
return '{' + ', '.join(items) + '}'
def visit_Lambda(self, node: ast.Lambda) -> str:
return "lambda %s: ..." % self.visit(node.args)
return 'lambda %s: ...' % self.visit(node.args)
def visit_List(self, node: ast.List) -> str:
return "[" + ", ".join(self.visit(e) for e in node.elts) + "]"
return '[' + ', '.join(self.visit(e) for e in node.elts) + ']'
def visit_Name(self, node: ast.Name) -> str:
return node.id
def visit_Set(self, node: ast.Set) -> str:
return "{" + ", ".join(self.visit(e) for e in node.elts) + "}"
return '{' + ', '.join(self.visit(e) for e in node.elts) + '}'
def visit_Slice(self, node: ast.Slice) -> str:
if not node.lower and not node.upper and not node.step:
# Empty slice with default values -> [:]
return ":"
return ':'
start = self.visit(node.lower) if node.lower else ""
stop = self.visit(node.upper) if node.upper else ""
start = self.visit(node.lower) if node.lower else ''
stop = self.visit(node.upper) if node.upper else ''
if not node.step:
# Default step size -> [start:stop]
return f"{start}:{stop}"
return f'{start}:{stop}'
step = self.visit(node.step) if node.step else ""
return f"{start}:{stop}:{step}"
step = self.visit(node.step) if node.step else ''
return f'{start}:{stop}:{step}'
def visit_Subscript(self, node: ast.Subscript) -> str:
def is_simple_tuple(value: ast.expr) -> bool:
@ -179,25 +180,24 @@ class _UnparseVisitor(ast.NodeVisitor):
)
if is_simple_tuple(node.slice):
elts = ", ".join(self.visit(e)
for e in node.slice.elts) # type: ignore[attr-defined]
return f"{self.visit(node.value)}[{elts}]"
return f"{self.visit(node.value)}[{self.visit(node.slice)}]"
elts = ', '.join(self.visit(e) for e in node.slice.elts) # type: ignore[attr-defined]
return f'{self.visit(node.value)}[{elts}]'
return f'{self.visit(node.value)}[{self.visit(node.slice)}]'
def visit_UnaryOp(self, node: ast.UnaryOp) -> str:
# UnaryOp is one of {UAdd, USub, Invert, Not}, which refer to ``+x``,
# ``-x``, ``~x``, and ``not x``. Only Not needs a space.
if isinstance(node.op, ast.Not):
return f"{self.visit(node.op)} {self.visit(node.operand)}"
return f"{self.visit(node.op)}{self.visit(node.operand)}"
return f'{self.visit(node.op)} {self.visit(node.operand)}'
return f'{self.visit(node.op)}{self.visit(node.operand)}'
def visit_Tuple(self, node: ast.Tuple) -> str:
if len(node.elts) == 0:
return "()"
return '()'
elif len(node.elts) == 1:
return "(%s,)" % self.visit(node.elts[0])
return '(%s,)' % self.visit(node.elts[0])
else:
return "(" + ", ".join(self.visit(e) for e in node.elts) + ")"
return '(' + ', '.join(self.visit(e) for e in node.elts) + ')'
def generic_visit(self, node: ast.AST) -> NoReturn:
raise NotImplementedError('Unable to parse %s object' % type(node).__name__)

View File

@ -63,11 +63,12 @@ def get_lvar_names(node: ast.AST, self: ast.arg | None = None) -> list[str]:
return members
elif node_name == 'Attribute':
if (
node.value.__class__.__name__ == 'Name' and # type: ignore[attr-defined]
self and node.value.id == self_id # type: ignore[attr-defined]
node.value.__class__.__name__ == 'Name' # type: ignore[attr-defined]
and self
and node.value.id == self_id # type: ignore[attr-defined]
):
# instance variable
return ["%s" % get_lvar_names(node.attr, self)[0]] # type: ignore[attr-defined]
return ['%s' % get_lvar_names(node.attr, self)[0]] # type: ignore[attr-defined]
else:
raise TypeError('The assignment %r is not instance variable' % node)
elif node_name == 'str':
@ -80,6 +81,7 @@ def get_lvar_names(node: ast.AST, self: ast.arg | None = None) -> list[str]:
def dedent_docstring(s: str) -> str:
"""Remove common leading indentation from docstring."""
def dummy() -> None:
# dummy function to mock `inspect.getdoc`.
pass
@ -87,16 +89,22 @@ def dedent_docstring(s: str) -> str:
dummy.__doc__ = s
docstring = inspect.getdoc(dummy)
if docstring:
return docstring.lstrip("\r\n").rstrip("\r\n")
return docstring.lstrip('\r\n').rstrip('\r\n')
else:
return ""
return ''
class Token:
"""Better token wrapper for tokenize module."""
def __init__(self, kind: int, value: Any, start: tuple[int, int], end: tuple[int, int],
source: str) -> None:
def __init__(
self,
kind: int,
value: Any,
start: tuple[int, int],
end: tuple[int, int],
source: str,
) -> None:
self.kind = kind
self.value = value
self.start = start
@ -201,7 +209,9 @@ class AfterCommentParser(TokenProcessor):
def parse(self) -> None:
"""Parse the code and obtain comment after assignment."""
# skip lvalue (or whole of AnnAssign)
while (tok := self.fetch_token()) and not tok.match([OP, '='], NEWLINE, COMMENT):
while (tok := self.fetch_token()) and not tok.match(
[OP, '='], NEWLINE, COMMENT
):
assert tok
assert tok is not None
@ -239,7 +249,7 @@ class VariableCommentPicker(ast.NodeVisitor):
def get_qualname_for(self, name: str) -> list[str] | None:
"""Get qualified name for given object as a list of string(s)."""
if self.current_function:
if self.current_classes and self.context[-1] == "__init__":
if self.current_classes and self.context[-1] == '__init__':
# store variable comments inside __init__ method of classes
return self.context[:-1] + [name]
else:
@ -250,31 +260,32 @@ class VariableCommentPicker(ast.NodeVisitor):
def add_entry(self, name: str) -> None:
qualname = self.get_qualname_for(name)
if qualname:
self.deforders[".".join(qualname)] = next(self.counter)
self.deforders['.'.join(qualname)] = next(self.counter)
def add_final_entry(self, name: str) -> None:
qualname = self.get_qualname_for(name)
if qualname:
self.finals.append(".".join(qualname))
self.finals.append('.'.join(qualname))
def add_overload_entry(self, func: ast.FunctionDef) -> None:
# avoid circular import problem
from sphinx.util.inspect import signature_from_ast
qualname = self.get_qualname_for(func.name)
if qualname:
overloads = self.overloads.setdefault(".".join(qualname), [])
overloads = self.overloads.setdefault('.'.join(qualname), [])
overloads.append(signature_from_ast(func))
def add_variable_comment(self, name: str, comment: str) -> None:
qualname = self.get_qualname_for(name)
if qualname:
basename = ".".join(qualname[:-1])
basename = '.'.join(qualname[:-1])
self.comments[(basename, name)] = comment
def add_variable_annotation(self, name: str, annotation: ast.AST) -> None:
qualname = self.get_qualname_for(name)
if qualname:
basename = ".".join(qualname[:-1])
basename = '.'.join(qualname[:-1])
self.annotations[(basename, name)] = ast_unparse(annotation)
def is_final(self, decorators: list[ast.expr]) -> bool:
@ -353,7 +364,10 @@ class VariableCommentPicker(ast.NodeVisitor):
try:
targets = get_assign_targets(node)
varnames: list[str] = functools.reduce(
operator.iadd, [get_lvar_names(t, self=self.get_self()) for t in targets], [])
operator.iadd,
[get_lvar_names(t, self=self.get_self()) for t in targets],
[],
)
current_line = self.get_line(node.lineno)
except TypeError:
return # this assignment is not new definition!
@ -364,21 +378,23 @@ class VariableCommentPicker(ast.NodeVisitor):
self.add_variable_annotation(varname, node.annotation)
elif hasattr(node, 'type_comment') and node.type_comment:
for varname in varnames:
self.add_variable_annotation(
varname, node.type_comment) # type: ignore[arg-type]
self.add_variable_annotation(varname, node.type_comment) # type: ignore[arg-type]
# check comments after assignment
parser = AfterCommentParser([current_line[node.col_offset:]] +
self.buffers[node.lineno:])
parser = AfterCommentParser(
[current_line[node.col_offset :]] + self.buffers[node.lineno :]
)
parser.parse()
if parser.comment and comment_re.match(parser.comment):
for varname in varnames:
self.add_variable_comment(varname, comment_re.sub('\\1', parser.comment))
self.add_variable_comment(
varname, comment_re.sub('\\1', parser.comment)
)
self.add_entry(varname)
return
# check comments before assignment
if indent_re.match(current_line[:node.col_offset]):
if indent_re.match(current_line[: node.col_offset]):
comment_lines = []
for i in range(node.lineno - 1):
before_line = self.get_line(node.lineno - 1 - i)
@ -404,8 +420,11 @@ class VariableCommentPicker(ast.NodeVisitor):
def visit_Expr(self, node: ast.Expr) -> None:
"""Handles Expr node and pick up a comment if string."""
if (isinstance(self.previous, ast.Assign | ast.AnnAssign) and
isinstance(node.value, ast.Constant) and isinstance(node.value.value, str)):
if (
isinstance(self.previous, ast.Assign | ast.AnnAssign)
and isinstance(node.value, ast.Constant)
and isinstance(node.value.value, str)
):
try:
targets = get_assign_targets(self.previous)
varnames = get_lvar_names(targets[0], self.get_self())
@ -446,7 +465,8 @@ class VariableCommentPicker(ast.NodeVisitor):
def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
"""Handles FunctionDef node and set context."""
if self.current_function is None:
self.add_entry(node.name) # should be called before setting self.current_function
# should be called before setting self.current_function
self.add_entry(node.name)
if self.is_final(node.decorator_list):
self.add_final_entry(node.name)
if self.is_overload(node.decorator_list):
@ -491,8 +511,10 @@ class DefinitionFinder(TokenProcessor):
break
if token == COMMENT:
pass
elif token == [OP, '@'] and (self.previous is None or
self.previous.match(NEWLINE, NL, INDENT, DEDENT)):
elif token == [OP, '@'] and (
self.previous is None
or self.previous.match(NEWLINE, NL, INDENT, DEDENT)
):
if self.decorator is None:
self.decorator = token
elif token.match([NAME, 'class']):
@ -522,8 +544,7 @@ class DefinitionFinder(TokenProcessor):
self.indents.append((typ, funcname, start_pos))
else:
# one-liner
self.add_definition(funcname,
(typ, start_pos, name.end[0])) # type: ignore[union-attr]
self.add_definition(funcname, (typ, start_pos, name.end[0])) # type: ignore[union-attr]
self.context.pop()
def finalize_block(self) -> None: