mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Enable automatic formatting for `sphinx/pycode/
` (#12965)
This commit is contained in:
parent
c1d192e2c2
commit
a44293a3a8
@ -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/*",
|
||||
|
@ -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:
|
||||
|
@ -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__)
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user