mirror of
https://github.com/Instagram/LibCST.git
synced 2025-12-23 10:35:53 +00:00
* Keep old exception messages (avoid breaking-changes for users relying on exception messages) * Move ``get_expected_str`` out of _exceptions.py, where it does not belong, to its own file in _parser/_parsing_check.py
1381 lines
46 KiB
Python
1381 lines
46 KiB
Python
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
#
|
|
# This source code is licensed under the MIT license found in the
|
|
# LICENSE file in the root directory of this source tree.
|
|
# pyre-unsafe
|
|
|
|
from typing import Any, Dict, List, Optional, Sequence, Tuple, Type
|
|
|
|
from libcst import CSTLogicError
|
|
from libcst._exceptions import ParserSyntaxError, PartialParserSyntaxError
|
|
from libcst._maybe_sentinel import MaybeSentinel
|
|
from libcst._nodes.expression import (
|
|
Annotation,
|
|
Arg,
|
|
Asynchronous,
|
|
Attribute,
|
|
Call,
|
|
From,
|
|
LeftParen,
|
|
Name,
|
|
Param,
|
|
Parameters,
|
|
RightParen,
|
|
)
|
|
from libcst._nodes.op import (
|
|
AddAssign,
|
|
AssignEqual,
|
|
BaseAugOp,
|
|
BitAndAssign,
|
|
BitOrAssign,
|
|
BitXorAssign,
|
|
Comma,
|
|
DivideAssign,
|
|
Dot,
|
|
FloorDivideAssign,
|
|
ImportStar,
|
|
LeftShiftAssign,
|
|
MatrixMultiplyAssign,
|
|
ModuloAssign,
|
|
MultiplyAssign,
|
|
PowerAssign,
|
|
RightShiftAssign,
|
|
Semicolon,
|
|
SubtractAssign,
|
|
)
|
|
from libcst._nodes.statement import (
|
|
AnnAssign,
|
|
AsName,
|
|
Assert,
|
|
Assign,
|
|
AssignTarget,
|
|
AugAssign,
|
|
Break,
|
|
ClassDef,
|
|
Continue,
|
|
Decorator,
|
|
Del,
|
|
Else,
|
|
ExceptHandler,
|
|
Expr,
|
|
Finally,
|
|
For,
|
|
FunctionDef,
|
|
Global,
|
|
If,
|
|
Import,
|
|
ImportAlias,
|
|
ImportFrom,
|
|
IndentedBlock,
|
|
NameItem,
|
|
Nonlocal,
|
|
Pass,
|
|
Raise,
|
|
Return,
|
|
SimpleStatementLine,
|
|
SimpleStatementSuite,
|
|
Try,
|
|
While,
|
|
With,
|
|
WithItem,
|
|
)
|
|
from libcst._nodes.whitespace import EmptyLine, SimpleWhitespace
|
|
from libcst._parser.custom_itertools import grouper
|
|
from libcst._parser.production_decorator import with_production
|
|
from libcst._parser.types.config import ParserConfig
|
|
from libcst._parser.types.partials import (
|
|
AnnAssignPartial,
|
|
AssignPartial,
|
|
AugAssignPartial,
|
|
DecoratorPartial,
|
|
ExceptClausePartial,
|
|
FuncdefPartial,
|
|
ImportPartial,
|
|
ImportRelativePartial,
|
|
SimpleStatementPartial,
|
|
WithLeadingWhitespace,
|
|
)
|
|
from libcst._parser.types.token import Token
|
|
from libcst._parser.whitespace_parser import (
|
|
parse_empty_lines,
|
|
parse_parenthesizable_whitespace,
|
|
parse_simple_whitespace,
|
|
)
|
|
|
|
AUGOP_TOKEN_LUT: Dict[str, Type[BaseAugOp]] = {
|
|
"+=": AddAssign,
|
|
"-=": SubtractAssign,
|
|
"*=": MultiplyAssign,
|
|
"@=": MatrixMultiplyAssign,
|
|
"/=": DivideAssign,
|
|
"%=": ModuloAssign,
|
|
"&=": BitAndAssign,
|
|
"|=": BitOrAssign,
|
|
"^=": BitXorAssign,
|
|
"<<=": LeftShiftAssign,
|
|
">>=": RightShiftAssign,
|
|
"**=": PowerAssign,
|
|
"//=": FloorDivideAssign,
|
|
}
|
|
|
|
|
|
@with_production("stmt_input", "stmt ENDMARKER")
|
|
def convert_stmt_input(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
(child, endmarker) = children
|
|
return child
|
|
|
|
|
|
@with_production("stmt", "simple_stmt_line | compound_stmt")
|
|
def convert_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
(child,) = children
|
|
return child
|
|
|
|
|
|
@with_production("simple_stmt_partial", "small_stmt (';' small_stmt)* [';'] NEWLINE")
|
|
def convert_simple_stmt_partial(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
*statements, trailing_whitespace = children
|
|
|
|
last_stmt = len(statements) / 2
|
|
body = []
|
|
for i, (stmt_body, semi) in enumerate(grouper(statements, 2)):
|
|
if semi is not None:
|
|
if i == (last_stmt - 1):
|
|
# Trailing semicolons only own the whitespace before.
|
|
semi = Semicolon(
|
|
whitespace_before=parse_simple_whitespace(
|
|
config, semi.whitespace_before
|
|
),
|
|
whitespace_after=SimpleWhitespace(""),
|
|
)
|
|
else:
|
|
# Middle semicolons own the whitespace before and after.
|
|
semi = Semicolon(
|
|
whitespace_before=parse_simple_whitespace(
|
|
config, semi.whitespace_before
|
|
),
|
|
whitespace_after=parse_simple_whitespace(
|
|
config, semi.whitespace_after
|
|
),
|
|
)
|
|
else:
|
|
semi = MaybeSentinel.DEFAULT
|
|
body.append(stmt_body.value.with_changes(semicolon=semi))
|
|
return SimpleStatementPartial(
|
|
body,
|
|
whitespace_before=statements[0].whitespace_before,
|
|
trailing_whitespace=trailing_whitespace,
|
|
)
|
|
|
|
|
|
@with_production("simple_stmt_line", "simple_stmt_partial")
|
|
def convert_simple_stmt_line(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
"""
|
|
This function is similar to convert_simple_stmt_suite, but yields a different type
|
|
"""
|
|
(partial,) = children
|
|
return SimpleStatementLine(
|
|
partial.body,
|
|
leading_lines=parse_empty_lines(config, partial.whitespace_before),
|
|
trailing_whitespace=partial.trailing_whitespace,
|
|
)
|
|
|
|
|
|
@with_production("simple_stmt_suite", "simple_stmt_partial")
|
|
def convert_simple_stmt_suite(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
"""
|
|
This function is similar to convert_simple_stmt_line, but yields a different type
|
|
"""
|
|
(partial,) = children
|
|
return SimpleStatementSuite(
|
|
partial.body,
|
|
leading_whitespace=parse_simple_whitespace(config, partial.whitespace_before),
|
|
trailing_whitespace=partial.trailing_whitespace,
|
|
)
|
|
|
|
|
|
@with_production(
|
|
"small_stmt",
|
|
(
|
|
"expr_stmt | del_stmt | pass_stmt | break_stmt | continue_stmt | return_stmt"
|
|
+ "| raise_stmt | yield_stmt | import_stmt | global_stmt | nonlocal_stmt"
|
|
+ "| assert_stmt"
|
|
),
|
|
)
|
|
def convert_small_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
# Doesn't construct SmallStatement, because we don't know about semicolons yet.
|
|
# convert_simple_stmt will construct the SmallStatement nodes.
|
|
(small_stmt_body,) = children
|
|
return small_stmt_body
|
|
|
|
|
|
@with_production(
|
|
"expr_stmt",
|
|
"testlist_star_expr (annassign | augassign | assign* )",
|
|
version=">=3.6",
|
|
)
|
|
@with_production(
|
|
"expr_stmt", "testlist_star_expr (augassign | assign* )", version="<=3.5"
|
|
)
|
|
@with_production("yield_stmt", "yield_expr")
|
|
def convert_expr_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
if len(children) == 1:
|
|
# This is an unassigned expr statement (like a function call)
|
|
(test_node,) = children
|
|
return WithLeadingWhitespace(
|
|
Expr(value=test_node.value), test_node.whitespace_before
|
|
)
|
|
elif len(children) == 2:
|
|
lhs, rhs = children
|
|
if isinstance(rhs, AnnAssignPartial):
|
|
return WithLeadingWhitespace(
|
|
AnnAssign(
|
|
target=lhs.value,
|
|
annotation=rhs.annotation,
|
|
equal=MaybeSentinel.DEFAULT if rhs.equal is None else rhs.equal,
|
|
value=rhs.value,
|
|
),
|
|
lhs.whitespace_before,
|
|
)
|
|
elif isinstance(rhs, AugAssignPartial):
|
|
return WithLeadingWhitespace(
|
|
AugAssign(target=lhs.value, operator=rhs.operator, value=rhs.value),
|
|
lhs.whitespace_before,
|
|
)
|
|
# The only thing it could be at this point is an assign with one or more targets.
|
|
# So, walk the children moving the equals ownership back one and constructing a
|
|
# list of AssignTargets.
|
|
targets = []
|
|
for i in range(len(children) - 1):
|
|
target = children[i].value
|
|
equal = children[i + 1].equal
|
|
|
|
targets.append(
|
|
AssignTarget(
|
|
target=target,
|
|
whitespace_before_equal=equal.whitespace_before,
|
|
whitespace_after_equal=equal.whitespace_after,
|
|
)
|
|
)
|
|
|
|
return WithLeadingWhitespace(
|
|
Assign(targets=tuple(targets), value=children[-1].value),
|
|
children[0].whitespace_before,
|
|
)
|
|
|
|
|
|
@with_production("annassign", "':' test ['=' test]", version=">=3.6,<3.8")
|
|
@with_production(
|
|
"annassign", "':' test ['=' (yield_expr|testlist_star_expr)]", version=">=3.8"
|
|
)
|
|
def convert_annassign(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
if len(children) == 2:
|
|
# Variable annotation only
|
|
colon, annotation = children
|
|
annotation = annotation.value
|
|
equal = None
|
|
value = None
|
|
elif len(children) == 4:
|
|
# Variable annotation and assignment
|
|
colon, annotation, equal, value = children
|
|
annotation = annotation.value
|
|
value = value.value
|
|
equal = AssignEqual(
|
|
whitespace_before=parse_simple_whitespace(config, equal.whitespace_before),
|
|
whitespace_after=parse_simple_whitespace(config, equal.whitespace_after),
|
|
)
|
|
else:
|
|
raise ParserSyntaxError(
|
|
"Invalid parser state!", lines=config.lines, raw_line=0, raw_column=0
|
|
)
|
|
|
|
return AnnAssignPartial(
|
|
annotation=Annotation(
|
|
whitespace_before_indicator=parse_simple_whitespace(
|
|
config, colon.whitespace_before
|
|
),
|
|
whitespace_after_indicator=parse_simple_whitespace(
|
|
config, colon.whitespace_after
|
|
),
|
|
annotation=annotation,
|
|
),
|
|
equal=equal,
|
|
value=value,
|
|
)
|
|
|
|
|
|
@with_production(
|
|
"augassign",
|
|
(
|
|
"('+=' | '-=' | '*=' | '@=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<=' | "
|
|
+ "'>>=' | '**=' | '//=') (yield_expr | testlist)"
|
|
),
|
|
version=">=3.5",
|
|
)
|
|
@with_production(
|
|
"augassign",
|
|
(
|
|
"('+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' | '<<=' | "
|
|
+ "'>>=' | '**=' | '//=') (yield_expr | testlist)"
|
|
),
|
|
version="<3.5",
|
|
)
|
|
def convert_augassign(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
op, expr = children
|
|
if op.string not in AUGOP_TOKEN_LUT:
|
|
raise ParserSyntaxError(
|
|
f"Unexpected token '{op.string}'!",
|
|
lines=config.lines,
|
|
raw_line=0,
|
|
raw_column=0,
|
|
)
|
|
|
|
return AugAssignPartial(
|
|
# pyre-ignore Pyre seems to think that the value of this LUT is CSTNode
|
|
operator=AUGOP_TOKEN_LUT[op.string](
|
|
whitespace_before=parse_simple_whitespace(config, op.whitespace_before),
|
|
whitespace_after=parse_simple_whitespace(config, op.whitespace_after),
|
|
),
|
|
value=expr.value,
|
|
)
|
|
|
|
|
|
@with_production("assign", "'=' (yield_expr|testlist_star_expr)")
|
|
def convert_assign(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
equal, expr = children
|
|
return AssignPartial(
|
|
equal=AssignEqual(
|
|
whitespace_before=parse_simple_whitespace(config, equal.whitespace_before),
|
|
whitespace_after=parse_simple_whitespace(config, equal.whitespace_after),
|
|
),
|
|
value=expr.value,
|
|
)
|
|
|
|
|
|
@with_production("pass_stmt", "'pass'")
|
|
def convert_pass_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
(name,) = children
|
|
return WithLeadingWhitespace(Pass(), name.whitespace_before)
|
|
|
|
|
|
@with_production("del_stmt", "'del' exprlist")
|
|
def convert_del_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
(del_name, exprlist) = children
|
|
return WithLeadingWhitespace(
|
|
Del(
|
|
target=exprlist.value,
|
|
whitespace_after_del=parse_simple_whitespace(
|
|
config, del_name.whitespace_after
|
|
),
|
|
),
|
|
del_name.whitespace_before,
|
|
)
|
|
|
|
|
|
@with_production("continue_stmt", "'continue'")
|
|
def convert_continue_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
(name,) = children
|
|
return WithLeadingWhitespace(Continue(), name.whitespace_before)
|
|
|
|
|
|
@with_production("break_stmt", "'break'")
|
|
def convert_break_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
(name,) = children
|
|
return WithLeadingWhitespace(Break(), name.whitespace_before)
|
|
|
|
|
|
@with_production("return_stmt", "'return' [testlist]", version="<=3.7")
|
|
@with_production("return_stmt", "'return' [testlist_star_expr]", version=">=3.8")
|
|
def convert_return_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
if len(children) == 1:
|
|
(keyword,) = children
|
|
return WithLeadingWhitespace(
|
|
Return(whitespace_after_return=SimpleWhitespace("")),
|
|
keyword.whitespace_before,
|
|
)
|
|
else:
|
|
(keyword, testlist) = children
|
|
return WithLeadingWhitespace(
|
|
Return(
|
|
value=testlist.value,
|
|
whitespace_after_return=parse_simple_whitespace(
|
|
config, keyword.whitespace_after
|
|
),
|
|
),
|
|
keyword.whitespace_before,
|
|
)
|
|
|
|
|
|
@with_production("import_stmt", "import_name | import_from")
|
|
def convert_import_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
(child,) = children
|
|
return child
|
|
|
|
|
|
@with_production("import_name", "'import' dotted_as_names")
|
|
def convert_import_name(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
importtoken, names = children
|
|
return WithLeadingWhitespace(
|
|
Import(
|
|
names=names.names,
|
|
whitespace_after_import=parse_simple_whitespace(
|
|
config, importtoken.whitespace_after
|
|
),
|
|
),
|
|
importtoken.whitespace_before,
|
|
)
|
|
|
|
|
|
@with_production("import_relative", "('.' | '...')* dotted_name | ('.' | '...')+")
|
|
def convert_import_relative(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
dots = []
|
|
dotted_name = None
|
|
for child in children:
|
|
if isinstance(child, Token):
|
|
# Special case for "...", which is part of the grammar
|
|
if child.string == "...":
|
|
dots.extend(
|
|
[
|
|
Dot(),
|
|
Dot(),
|
|
Dot(
|
|
whitespace_after=parse_simple_whitespace(
|
|
config, child.whitespace_after
|
|
)
|
|
),
|
|
]
|
|
)
|
|
else:
|
|
dots.append(
|
|
Dot(
|
|
whitespace_after=parse_simple_whitespace(
|
|
config, child.whitespace_after
|
|
)
|
|
)
|
|
)
|
|
else:
|
|
# This should be the dotted name, and we can't get more than
|
|
# one, but lets be sure anyway
|
|
if dotted_name is not None:
|
|
raise CSTLogicError()
|
|
dotted_name = child
|
|
|
|
return ImportRelativePartial(relative=tuple(dots), module=dotted_name)
|
|
|
|
|
|
@with_production(
|
|
"import_from",
|
|
"'from' import_relative 'import' ('*' | '(' import_as_names ')' | import_as_names)",
|
|
)
|
|
def convert_import_from(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
fromtoken, import_relative, importtoken, *importlist = children
|
|
|
|
if len(importlist) == 1:
|
|
(possible_star,) = importlist
|
|
if isinstance(possible_star, Token):
|
|
# Its a "*" import, so we must construct this node.
|
|
names = ImportStar()
|
|
else:
|
|
# Its an import as names partial, grab the names from that.
|
|
names = possible_star.names
|
|
lpar = None
|
|
rpar = None
|
|
else:
|
|
# Its an import as names partial with parens
|
|
lpartoken, namespartial, rpartoken = importlist
|
|
lpar = LeftParen(
|
|
whitespace_after=parse_parenthesizable_whitespace(
|
|
config, lpartoken.whitespace_after
|
|
)
|
|
)
|
|
names = namespartial.names
|
|
rpar = RightParen(
|
|
whitespace_before=parse_parenthesizable_whitespace(
|
|
config, rpartoken.whitespace_before
|
|
)
|
|
)
|
|
|
|
# If we have a relative-only import, then we need to relocate the space
|
|
# after the final dot to be owned by the import token.
|
|
if len(import_relative.relative) > 0 and import_relative.module is None:
|
|
whitespace_before_import = import_relative.relative[-1].whitespace_after
|
|
relative = (
|
|
*import_relative.relative[:-1],
|
|
import_relative.relative[-1].with_changes(
|
|
whitespace_after=SimpleWhitespace("")
|
|
),
|
|
)
|
|
else:
|
|
whitespace_before_import = parse_simple_whitespace(
|
|
config, importtoken.whitespace_before
|
|
)
|
|
relative = import_relative.relative
|
|
|
|
return WithLeadingWhitespace(
|
|
ImportFrom(
|
|
whitespace_after_from=parse_simple_whitespace(
|
|
config, fromtoken.whitespace_after
|
|
),
|
|
relative=relative,
|
|
module=import_relative.module,
|
|
whitespace_before_import=whitespace_before_import,
|
|
whitespace_after_import=parse_simple_whitespace(
|
|
config, importtoken.whitespace_after
|
|
),
|
|
lpar=lpar,
|
|
names=names,
|
|
rpar=rpar,
|
|
),
|
|
fromtoken.whitespace_before,
|
|
)
|
|
|
|
|
|
@with_production("import_as_name", "NAME ['as' NAME]")
|
|
def convert_import_as_name(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
if len(children) == 1:
|
|
(dotted_name,) = children
|
|
return ImportAlias(name=Name(dotted_name.string), asname=None)
|
|
else:
|
|
dotted_name, astoken, name = children
|
|
return ImportAlias(
|
|
name=Name(dotted_name.string),
|
|
asname=AsName(
|
|
whitespace_before_as=parse_simple_whitespace(
|
|
config, astoken.whitespace_before
|
|
),
|
|
whitespace_after_as=parse_simple_whitespace(
|
|
config, astoken.whitespace_after
|
|
),
|
|
name=Name(name.string),
|
|
),
|
|
)
|
|
|
|
|
|
@with_production("dotted_as_name", "dotted_name ['as' NAME]")
|
|
def convert_dotted_as_name(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
if len(children) == 1:
|
|
(dotted_name,) = children
|
|
return ImportAlias(name=dotted_name, asname=None)
|
|
else:
|
|
dotted_name, astoken, name = children
|
|
return ImportAlias(
|
|
name=dotted_name,
|
|
asname=AsName(
|
|
whitespace_before_as=parse_parenthesizable_whitespace(
|
|
config, astoken.whitespace_before
|
|
),
|
|
whitespace_after_as=parse_parenthesizable_whitespace(
|
|
config, astoken.whitespace_after
|
|
),
|
|
name=Name(name.string),
|
|
),
|
|
)
|
|
|
|
|
|
@with_production("import_as_names", "import_as_name (',' import_as_name)* [',']")
|
|
def convert_import_as_names(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
return _gather_import_names(config, children)
|
|
|
|
|
|
@with_production("dotted_as_names", "dotted_as_name (',' dotted_as_name)*")
|
|
def convert_dotted_as_names(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
return _gather_import_names(config, children)
|
|
|
|
|
|
def _gather_import_names(
|
|
config: ParserConfig, children: Sequence[Any]
|
|
) -> ImportPartial:
|
|
names = []
|
|
for name, comma in grouper(children, 2):
|
|
if comma is None:
|
|
names.append(name)
|
|
else:
|
|
names.append(
|
|
name.with_changes(
|
|
comma=Comma(
|
|
whitespace_before=parse_parenthesizable_whitespace(
|
|
config, comma.whitespace_before
|
|
),
|
|
whitespace_after=parse_parenthesizable_whitespace(
|
|
config, comma.whitespace_after
|
|
),
|
|
)
|
|
)
|
|
)
|
|
|
|
return ImportPartial(names=names)
|
|
|
|
|
|
@with_production("dotted_name", "NAME ('.' NAME)*")
|
|
def convert_dotted_name(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
left, *rest = children
|
|
node = Name(left.string)
|
|
|
|
for dot, right in grouper(rest, 2):
|
|
node = Attribute(
|
|
value=node,
|
|
dot=Dot(
|
|
whitespace_before=parse_parenthesizable_whitespace(
|
|
config, dot.whitespace_before
|
|
),
|
|
whitespace_after=parse_parenthesizable_whitespace(
|
|
config, dot.whitespace_after
|
|
),
|
|
),
|
|
attr=Name(right.string),
|
|
)
|
|
|
|
return node
|
|
|
|
|
|
@with_production("raise_stmt", "'raise' [test ['from' test]]")
|
|
def convert_raise_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
if len(children) == 1:
|
|
(raise_token,) = children
|
|
whitespace_after_raise = MaybeSentinel.DEFAULT
|
|
exc = None
|
|
cause = None
|
|
elif len(children) == 2:
|
|
(raise_token, test) = children
|
|
whitespace_after_raise = parse_simple_whitespace(config, test.whitespace_before)
|
|
exc = test.value
|
|
cause = None
|
|
elif len(children) == 4:
|
|
(raise_token, test, from_token, source) = children
|
|
whitespace_after_raise = parse_simple_whitespace(config, test.whitespace_before)
|
|
exc = test.value
|
|
cause = From(
|
|
whitespace_before_from=parse_simple_whitespace(
|
|
config, from_token.whitespace_before
|
|
),
|
|
whitespace_after_from=parse_simple_whitespace(
|
|
config, source.whitespace_before
|
|
),
|
|
item=source.value,
|
|
)
|
|
else:
|
|
raise CSTLogicError()
|
|
|
|
return WithLeadingWhitespace(
|
|
Raise(whitespace_after_raise=whitespace_after_raise, exc=exc, cause=cause),
|
|
raise_token.whitespace_before,
|
|
)
|
|
|
|
|
|
def _construct_nameitems(config: ParserConfig, names: Sequence[Any]) -> List[NameItem]:
|
|
nameitems: List[NameItem] = []
|
|
for name, maybe_comma in grouper(names, 2):
|
|
if maybe_comma is None:
|
|
nameitems.append(NameItem(Name(name.string)))
|
|
else:
|
|
nameitems.append(
|
|
NameItem(
|
|
Name(name.string),
|
|
comma=Comma(
|
|
whitespace_before=parse_simple_whitespace(
|
|
config, maybe_comma.whitespace_before
|
|
),
|
|
whitespace_after=parse_simple_whitespace(
|
|
config, maybe_comma.whitespace_after
|
|
),
|
|
),
|
|
)
|
|
)
|
|
return nameitems
|
|
|
|
|
|
@with_production("global_stmt", "'global' NAME (',' NAME)*")
|
|
def convert_global_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
(global_token, *names) = children
|
|
return WithLeadingWhitespace(
|
|
Global(
|
|
names=tuple(_construct_nameitems(config, names)),
|
|
whitespace_after_global=parse_simple_whitespace(
|
|
config, names[0].whitespace_before
|
|
),
|
|
),
|
|
global_token.whitespace_before,
|
|
)
|
|
|
|
|
|
@with_production("nonlocal_stmt", "'nonlocal' NAME (',' NAME)*")
|
|
def convert_nonlocal_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
(nonlocal_token, *names) = children
|
|
return WithLeadingWhitespace(
|
|
Nonlocal(
|
|
names=tuple(_construct_nameitems(config, names)),
|
|
whitespace_after_nonlocal=parse_simple_whitespace(
|
|
config, names[0].whitespace_before
|
|
),
|
|
),
|
|
nonlocal_token.whitespace_before,
|
|
)
|
|
|
|
|
|
@with_production("assert_stmt", "'assert' test [',' test]")
|
|
def convert_assert_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
if len(children) == 2:
|
|
(assert_token, test) = children
|
|
assert_node = Assert(
|
|
whitespace_after_assert=parse_simple_whitespace(
|
|
config, test.whitespace_before
|
|
),
|
|
test=test.value,
|
|
msg=None,
|
|
)
|
|
else:
|
|
(assert_token, test, comma_token, msg) = children
|
|
assert_node = Assert(
|
|
whitespace_after_assert=parse_simple_whitespace(
|
|
config, test.whitespace_before
|
|
),
|
|
test=test.value,
|
|
comma=Comma(
|
|
whitespace_before=parse_simple_whitespace(
|
|
config, comma_token.whitespace_before
|
|
),
|
|
whitespace_after=parse_simple_whitespace(config, msg.whitespace_before),
|
|
),
|
|
msg=msg.value,
|
|
)
|
|
|
|
return WithLeadingWhitespace(assert_node, assert_token.whitespace_before)
|
|
|
|
|
|
@with_production(
|
|
"compound_stmt",
|
|
("if_stmt | while_stmt | asyncable_stmt | try_stmt | classdef | decorated"),
|
|
)
|
|
def convert_compound_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
(stmt,) = children
|
|
return stmt
|
|
|
|
|
|
@with_production(
|
|
"if_stmt", "'if' test ':' suite [if_stmt_elif|if_stmt_else]", version="<=3.7"
|
|
)
|
|
@with_production(
|
|
"if_stmt",
|
|
"'if' namedexpr_test ':' suite [if_stmt_elif|if_stmt_else]",
|
|
version=">=3.8",
|
|
)
|
|
def convert_if_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
if_tok, test, colon_tok, suite, *tail = children
|
|
|
|
if len(tail) > 0:
|
|
(orelse,) = tail
|
|
else:
|
|
orelse = None
|
|
|
|
return If(
|
|
leading_lines=parse_empty_lines(config, if_tok.whitespace_before),
|
|
whitespace_before_test=parse_simple_whitespace(config, if_tok.whitespace_after),
|
|
test=test.value,
|
|
whitespace_after_test=parse_simple_whitespace(
|
|
config, colon_tok.whitespace_before
|
|
),
|
|
body=suite,
|
|
orelse=orelse,
|
|
)
|
|
|
|
|
|
@with_production(
|
|
"if_stmt_elif", "'elif' test ':' suite [if_stmt_elif|if_stmt_else]", version="<=3.7"
|
|
)
|
|
@with_production(
|
|
"if_stmt_elif",
|
|
"'elif' namedexpr_test ':' suite [if_stmt_elif|if_stmt_else]",
|
|
version=">=3.8",
|
|
)
|
|
def convert_if_stmt_elif(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
# this behaves exactly the same as `convert_if_stmt`, except that the leading token
|
|
# has a different string value.
|
|
return convert_if_stmt(config, children)
|
|
|
|
|
|
@with_production("if_stmt_else", "'else' ':' suite")
|
|
def convert_if_stmt_else(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
else_tok, colon_tok, suite = children
|
|
return Else(
|
|
leading_lines=parse_empty_lines(config, else_tok.whitespace_before),
|
|
whitespace_before_colon=parse_simple_whitespace(
|
|
config, colon_tok.whitespace_before
|
|
),
|
|
body=suite,
|
|
)
|
|
|
|
|
|
@with_production(
|
|
"while_stmt", "'while' test ':' suite ['else' ':' suite]", version="<=3.7"
|
|
)
|
|
@with_production(
|
|
"while_stmt", "'while' namedexpr_test ':' suite ['else' ':' suite]", version=">=3.8"
|
|
)
|
|
def convert_while_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
while_token, test, while_colon_token, while_suite, *else_block = children
|
|
|
|
if len(else_block) > 0:
|
|
(else_token, else_colon_token, else_suite) = else_block
|
|
orelse = Else(
|
|
leading_lines=parse_empty_lines(config, else_token.whitespace_before),
|
|
whitespace_before_colon=parse_simple_whitespace(
|
|
config, else_colon_token.whitespace_before
|
|
),
|
|
body=else_suite,
|
|
)
|
|
else:
|
|
orelse = None
|
|
|
|
return While(
|
|
leading_lines=parse_empty_lines(config, while_token.whitespace_before),
|
|
whitespace_after_while=parse_simple_whitespace(
|
|
config, while_token.whitespace_after
|
|
),
|
|
test=test.value,
|
|
whitespace_before_colon=parse_simple_whitespace(
|
|
config, while_colon_token.whitespace_before
|
|
),
|
|
body=while_suite,
|
|
orelse=orelse,
|
|
)
|
|
|
|
|
|
@with_production(
|
|
"for_stmt", "'for' exprlist 'in' testlist ':' suite ['else' ':' suite]"
|
|
)
|
|
def convert_for_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
(
|
|
for_token,
|
|
expr,
|
|
in_token,
|
|
test,
|
|
for_colon_token,
|
|
for_suite,
|
|
*else_block,
|
|
) = children
|
|
|
|
if len(else_block) > 0:
|
|
(else_token, else_colon_token, else_suite) = else_block
|
|
orelse = Else(
|
|
leading_lines=parse_empty_lines(config, else_token.whitespace_before),
|
|
whitespace_before_colon=parse_simple_whitespace(
|
|
config, else_colon_token.whitespace_before
|
|
),
|
|
body=else_suite,
|
|
)
|
|
else:
|
|
orelse = None
|
|
|
|
return WithLeadingWhitespace(
|
|
For(
|
|
whitespace_after_for=parse_simple_whitespace(
|
|
config, for_token.whitespace_after
|
|
),
|
|
target=expr.value,
|
|
whitespace_before_in=parse_simple_whitespace(
|
|
config, in_token.whitespace_before
|
|
),
|
|
whitespace_after_in=parse_simple_whitespace(
|
|
config, in_token.whitespace_after
|
|
),
|
|
iter=test.value,
|
|
whitespace_before_colon=parse_simple_whitespace(
|
|
config, for_colon_token.whitespace_before
|
|
),
|
|
body=for_suite,
|
|
orelse=orelse,
|
|
),
|
|
for_token.whitespace_before,
|
|
)
|
|
|
|
|
|
@with_production(
|
|
"try_stmt",
|
|
"('try' ':' suite ((except_clause ':' suite)+ ['else' ':' suite] ['finally' ':' suite] | 'finally' ':' suite))",
|
|
)
|
|
def convert_try_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
trytoken, try_colon_token, try_suite, *rest = children
|
|
handlers: List[ExceptHandler] = []
|
|
orelse: Optional[Else] = None
|
|
finalbody: Optional[Finally] = None
|
|
|
|
for clause, colon_token, suite in grouper(rest, 3):
|
|
if isinstance(clause, Token):
|
|
if clause.string == "else":
|
|
if orelse is not None:
|
|
raise CSTLogicError("Logic error!")
|
|
orelse = Else(
|
|
leading_lines=parse_empty_lines(config, clause.whitespace_before),
|
|
whitespace_before_colon=parse_simple_whitespace(
|
|
config, colon_token.whitespace_before
|
|
),
|
|
body=suite,
|
|
)
|
|
elif clause.string == "finally":
|
|
if finalbody is not None:
|
|
raise CSTLogicError("Logic error!")
|
|
finalbody = Finally(
|
|
leading_lines=parse_empty_lines(config, clause.whitespace_before),
|
|
whitespace_before_colon=parse_simple_whitespace(
|
|
config, colon_token.whitespace_before
|
|
),
|
|
body=suite,
|
|
)
|
|
else:
|
|
raise CSTLogicError("Logic error!")
|
|
elif isinstance(clause, ExceptClausePartial):
|
|
handlers.append(
|
|
ExceptHandler(
|
|
body=suite,
|
|
type=clause.type,
|
|
name=clause.name,
|
|
leading_lines=clause.leading_lines,
|
|
whitespace_after_except=clause.whitespace_after_except,
|
|
whitespace_before_colon=parse_simple_whitespace(
|
|
config, colon_token.whitespace_before
|
|
),
|
|
)
|
|
)
|
|
else:
|
|
raise CSTLogicError("Logic error!")
|
|
|
|
return Try(
|
|
leading_lines=parse_empty_lines(config, trytoken.whitespace_before),
|
|
whitespace_before_colon=parse_simple_whitespace(
|
|
config, try_colon_token.whitespace_before
|
|
),
|
|
body=try_suite,
|
|
handlers=tuple(handlers),
|
|
orelse=orelse,
|
|
finalbody=finalbody,
|
|
)
|
|
|
|
|
|
@with_production("except_clause", "'except' [test ['as' NAME]]")
|
|
def convert_except_clause(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
if len(children) == 1:
|
|
(except_token,) = children
|
|
whitespace_after_except = SimpleWhitespace("")
|
|
test = None
|
|
name = None
|
|
elif len(children) == 2:
|
|
(except_token, test_node) = children
|
|
whitespace_after_except = parse_simple_whitespace(
|
|
config, except_token.whitespace_after
|
|
)
|
|
test = test_node.value
|
|
name = None
|
|
else:
|
|
(except_token, test_node, as_token, name_token) = children
|
|
whitespace_after_except = parse_simple_whitespace(
|
|
config, except_token.whitespace_after
|
|
)
|
|
test = test_node.value
|
|
name = AsName(
|
|
whitespace_before_as=parse_simple_whitespace(
|
|
config, as_token.whitespace_before
|
|
),
|
|
whitespace_after_as=parse_simple_whitespace(
|
|
config, as_token.whitespace_after
|
|
),
|
|
name=Name(name_token.string),
|
|
)
|
|
|
|
return ExceptClausePartial(
|
|
leading_lines=parse_empty_lines(config, except_token.whitespace_before),
|
|
whitespace_after_except=whitespace_after_except,
|
|
type=test,
|
|
name=name,
|
|
)
|
|
|
|
|
|
@with_production(
|
|
"with_stmt", "'with' with_item (',' with_item)* ':' suite", version=">=3.1"
|
|
)
|
|
@with_production("with_stmt", "'with' with_item ':' suite", version="<3.1")
|
|
def convert_with_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
(with_token, *items, colon_token, suite) = children
|
|
item_nodes: List[WithItem] = []
|
|
|
|
for with_item, maybe_comma in grouper(items, 2):
|
|
if maybe_comma is not None:
|
|
item_nodes.append(
|
|
with_item.with_changes(
|
|
comma=Comma(
|
|
whitespace_before=parse_parenthesizable_whitespace(
|
|
config, maybe_comma.whitespace_before
|
|
),
|
|
whitespace_after=parse_parenthesizable_whitespace(
|
|
config, maybe_comma.whitespace_after
|
|
),
|
|
)
|
|
)
|
|
)
|
|
else:
|
|
item_nodes.append(with_item)
|
|
|
|
return WithLeadingWhitespace(
|
|
With(
|
|
whitespace_after_with=parse_simple_whitespace(
|
|
config, with_token.whitespace_after
|
|
),
|
|
items=tuple(item_nodes),
|
|
whitespace_before_colon=parse_simple_whitespace(
|
|
config, colon_token.whitespace_before
|
|
),
|
|
body=suite,
|
|
),
|
|
with_token.whitespace_before,
|
|
)
|
|
|
|
|
|
@with_production("with_item", "test ['as' expr]")
|
|
def convert_with_item(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
if len(children) == 3:
|
|
(test, as_token, expr_node) = children
|
|
test_node = test.value
|
|
asname = AsName(
|
|
whitespace_before_as=parse_simple_whitespace(
|
|
config, as_token.whitespace_before
|
|
),
|
|
whitespace_after_as=parse_simple_whitespace(
|
|
config, as_token.whitespace_after
|
|
),
|
|
name=expr_node.value,
|
|
)
|
|
else:
|
|
(test,) = children
|
|
test_node = test.value
|
|
asname = None
|
|
|
|
return WithItem(item=test_node, asname=asname)
|
|
|
|
|
|
def _extract_async(
|
|
config: ParserConfig, children: Sequence[Any]
|
|
) -> Tuple[List[EmptyLine], Optional[Asynchronous], Any]:
|
|
if len(children) == 1:
|
|
(stmt,) = children
|
|
|
|
whitespace_before = stmt.whitespace_before
|
|
asyncnode = None
|
|
else:
|
|
asynctoken, stmt = children
|
|
|
|
whitespace_before = asynctoken.whitespace_before
|
|
asyncnode = Asynchronous(
|
|
whitespace_after=parse_simple_whitespace(
|
|
config, asynctoken.whitespace_after
|
|
)
|
|
)
|
|
|
|
return (parse_empty_lines(config, whitespace_before), asyncnode, stmt.value)
|
|
|
|
|
|
@with_production("asyncable_funcdef", "[ASYNC] funcdef", version=">=3.5")
|
|
@with_production("asyncable_funcdef", "funcdef", version="<3.5")
|
|
def convert_asyncable_funcdef(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
leading_lines, asyncnode, funcdef = _extract_async(config, children)
|
|
|
|
return funcdef.with_changes(
|
|
asynchronous=asyncnode, leading_lines=leading_lines, lines_after_decorators=()
|
|
)
|
|
|
|
|
|
@with_production("funcdef", "'def' NAME parameters [funcdef_annotation] ':' suite")
|
|
def convert_funcdef(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
defnode, namenode, param_partial, *annotation, colon, suite = children
|
|
|
|
# If the trailing paremeter doesn't have a comma, then it owns the trailing
|
|
# whitespace before the rpar. Otherwise, the comma owns it (and will have
|
|
# already parsed it). We don't check/update ParamStar because if it exists
|
|
# then we are guaranteed have at least one kwonly_param.
|
|
parameters = param_partial.params
|
|
if parameters.star_kwarg is not None:
|
|
if parameters.star_kwarg.comma == MaybeSentinel.DEFAULT:
|
|
parameters = parameters.with_changes(
|
|
star_kwarg=parameters.star_kwarg.with_changes(
|
|
whitespace_after_param=param_partial.rpar.whitespace_before
|
|
)
|
|
)
|
|
elif parameters.kwonly_params:
|
|
if parameters.kwonly_params[-1].comma == MaybeSentinel.DEFAULT:
|
|
parameters = parameters.with_changes(
|
|
kwonly_params=(
|
|
*parameters.kwonly_params[:-1],
|
|
parameters.kwonly_params[-1].with_changes(
|
|
whitespace_after_param=param_partial.rpar.whitespace_before
|
|
),
|
|
)
|
|
)
|
|
elif isinstance(parameters.star_arg, Param):
|
|
if parameters.star_arg.comma == MaybeSentinel.DEFAULT:
|
|
parameters = parameters.with_changes(
|
|
star_arg=parameters.star_arg.with_changes(
|
|
whitespace_after_param=param_partial.rpar.whitespace_before
|
|
)
|
|
)
|
|
elif parameters.params:
|
|
if parameters.params[-1].comma == MaybeSentinel.DEFAULT:
|
|
parameters = parameters.with_changes(
|
|
params=(
|
|
*parameters.params[:-1],
|
|
parameters.params[-1].with_changes(
|
|
whitespace_after_param=param_partial.rpar.whitespace_before
|
|
),
|
|
)
|
|
)
|
|
|
|
return WithLeadingWhitespace(
|
|
FunctionDef(
|
|
whitespace_after_def=parse_simple_whitespace(
|
|
config, defnode.whitespace_after
|
|
),
|
|
name=Name(namenode.string),
|
|
whitespace_after_name=parse_simple_whitespace(
|
|
config, namenode.whitespace_after
|
|
),
|
|
whitespace_before_params=param_partial.lpar.whitespace_after,
|
|
params=parameters,
|
|
returns=None if not annotation else annotation[0],
|
|
whitespace_before_colon=parse_simple_whitespace(
|
|
config, colon.whitespace_before
|
|
),
|
|
body=suite,
|
|
),
|
|
defnode.whitespace_before,
|
|
)
|
|
|
|
|
|
@with_production("parameters", "'(' [typedargslist] ')'")
|
|
def convert_parameters(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
lpar, *paramlist, rpar = children
|
|
return FuncdefPartial(
|
|
lpar=LeftParen(
|
|
whitespace_after=parse_parenthesizable_whitespace(
|
|
config, lpar.whitespace_after
|
|
)
|
|
),
|
|
params=Parameters() if not paramlist else paramlist[0],
|
|
rpar=RightParen(
|
|
whitespace_before=parse_parenthesizable_whitespace(
|
|
config, rpar.whitespace_before
|
|
)
|
|
),
|
|
)
|
|
|
|
|
|
@with_production("funcdef_annotation", "'->' test")
|
|
def convert_funcdef_annotation(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
arrow, typehint = children
|
|
return Annotation(
|
|
whitespace_before_indicator=parse_parenthesizable_whitespace(
|
|
config, arrow.whitespace_before
|
|
),
|
|
whitespace_after_indicator=parse_parenthesizable_whitespace(
|
|
config, arrow.whitespace_after
|
|
),
|
|
annotation=typehint.value,
|
|
)
|
|
|
|
|
|
@with_production("classdef", "'class' NAME ['(' [arglist] ')'] ':' suite")
|
|
def convert_classdef(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
classdef, name, *arglist, colon, suite = children
|
|
|
|
# First, parse out the comments and empty lines before the statement.
|
|
leading_lines = parse_empty_lines(config, classdef.whitespace_before)
|
|
|
|
# Compute common whitespace and nodes
|
|
whitespace_after_class = parse_simple_whitespace(config, classdef.whitespace_after)
|
|
namenode = Name(name.string)
|
|
whitespace_after_name = parse_simple_whitespace(config, name.whitespace_after)
|
|
|
|
# Now, construct the classdef node itself
|
|
if not arglist:
|
|
# No arglist, so no arguments to this class
|
|
return ClassDef(
|
|
leading_lines=leading_lines,
|
|
lines_after_decorators=(),
|
|
whitespace_after_class=whitespace_after_class,
|
|
name=namenode,
|
|
whitespace_after_name=whitespace_after_name,
|
|
body=suite,
|
|
)
|
|
else:
|
|
# Unwrap arglist partial, because its valid to not have any
|
|
lpar, *args, rpar = arglist
|
|
args = args[0].args if args else []
|
|
|
|
bases: List[Arg] = []
|
|
keywords: List[Arg] = []
|
|
|
|
current_arg = bases
|
|
for arg in args:
|
|
if arg.star == "**" or arg.keyword is not None:
|
|
current_arg = keywords
|
|
# Some quick validation
|
|
if current_arg is keywords and (
|
|
arg.star == "*" or (arg.star == "" and arg.keyword is None)
|
|
):
|
|
raise PartialParserSyntaxError(
|
|
"Positional argument follows keyword argument."
|
|
)
|
|
current_arg.append(arg)
|
|
|
|
return ClassDef(
|
|
leading_lines=leading_lines,
|
|
lines_after_decorators=(),
|
|
whitespace_after_class=whitespace_after_class,
|
|
name=namenode,
|
|
whitespace_after_name=whitespace_after_name,
|
|
lpar=LeftParen(
|
|
whitespace_after=parse_parenthesizable_whitespace(
|
|
config, lpar.whitespace_after
|
|
)
|
|
),
|
|
bases=bases,
|
|
keywords=keywords,
|
|
rpar=RightParen(
|
|
whitespace_before=parse_parenthesizable_whitespace(
|
|
config, rpar.whitespace_before
|
|
)
|
|
),
|
|
whitespace_before_colon=parse_simple_whitespace(
|
|
config, colon.whitespace_before
|
|
),
|
|
body=suite,
|
|
)
|
|
|
|
|
|
@with_production("decorator", "'@' dotted_name [ '(' [arglist] ')' ] NEWLINE")
|
|
def convert_decorator(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
atsign, name, *arglist, newline = children
|
|
if not arglist:
|
|
# This is either a name or an attribute node, so just extract it.
|
|
decoratornode = name
|
|
else:
|
|
# This needs to be converted into a call node, and we have the
|
|
# arglist partial.
|
|
lpar, *args, rpar = arglist
|
|
args = args[0].args if args else []
|
|
|
|
# If the trailing argument doesn't have a comma, then it owns the
|
|
# trailing whitespace before the rpar. Otherwise, the comma owns
|
|
# it.
|
|
if len(args) > 0 and args[-1].comma == MaybeSentinel.DEFAULT:
|
|
args[-1] = args[-1].with_changes(
|
|
whitespace_after_arg=parse_parenthesizable_whitespace(
|
|
config, rpar.whitespace_before
|
|
)
|
|
)
|
|
|
|
decoratornode = Call(
|
|
func=name,
|
|
whitespace_after_func=parse_simple_whitespace(
|
|
config, lpar.whitespace_before
|
|
),
|
|
whitespace_before_args=parse_parenthesizable_whitespace(
|
|
config, lpar.whitespace_after
|
|
),
|
|
args=tuple(args),
|
|
)
|
|
|
|
return Decorator(
|
|
leading_lines=parse_empty_lines(config, atsign.whitespace_before),
|
|
whitespace_after_at=parse_simple_whitespace(config, atsign.whitespace_after),
|
|
decorator=decoratornode,
|
|
trailing_whitespace=newline,
|
|
)
|
|
|
|
|
|
@with_production("decorators", "decorator+")
|
|
def convert_decorators(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
return DecoratorPartial(decorators=children)
|
|
|
|
|
|
@with_production("decorated", "decorators (classdef | asyncable_funcdef)")
|
|
def convert_decorated(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
partial, class_or_func = children
|
|
|
|
# First, split up the spacing on the first decorator
|
|
leading_lines = partial.decorators[0].leading_lines
|
|
|
|
# Now, redistribute ownership of the whitespace
|
|
decorators = (
|
|
partial.decorators[0].with_changes(leading_lines=()),
|
|
*partial.decorators[1:],
|
|
)
|
|
|
|
# Now, modify the original function or class to add the decorators.
|
|
return class_or_func.with_changes(
|
|
leading_lines=leading_lines,
|
|
# pyre-fixme[60]: Concatenation not yet support for multiple variadic
|
|
# tuples: `*class_or_func.leading_lines,
|
|
# *class_or_func.lines_after_decorators`.
|
|
# pyre-fixme[60]: Expected to unpack an iterable, but got `unknown`.
|
|
lines_after_decorators=(
|
|
*class_or_func.leading_lines,
|
|
*class_or_func.lines_after_decorators,
|
|
),
|
|
decorators=decorators,
|
|
)
|
|
|
|
|
|
@with_production(
|
|
"asyncable_stmt", "[ASYNC] (funcdef | with_stmt | for_stmt)", version=">=3.5"
|
|
)
|
|
@with_production("asyncable_stmt", "funcdef | with_stmt | for_stmt", version="<3.5")
|
|
def convert_asyncable_stmt(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
leading_lines, asyncnode, stmtnode = _extract_async(config, children)
|
|
if isinstance(stmtnode, FunctionDef):
|
|
return stmtnode.with_changes(
|
|
asynchronous=asyncnode,
|
|
leading_lines=leading_lines,
|
|
lines_after_decorators=(),
|
|
)
|
|
elif isinstance(stmtnode, With):
|
|
return stmtnode.with_changes(
|
|
asynchronous=asyncnode, leading_lines=leading_lines
|
|
)
|
|
elif isinstance(stmtnode, For):
|
|
return stmtnode.with_changes(
|
|
asynchronous=asyncnode, leading_lines=leading_lines
|
|
)
|
|
else:
|
|
raise CSTLogicError("Logic error!")
|
|
|
|
|
|
@with_production("suite", "simple_stmt_suite | indented_suite")
|
|
def convert_suite(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
(suite,) = children
|
|
return suite
|
|
|
|
|
|
@with_production("indented_suite", "NEWLINE INDENT stmt+ DEDENT")
|
|
def convert_indented_suite(config: ParserConfig, children: Sequence[Any]) -> Any:
|
|
newline, indent, *stmts, dedent = children
|
|
return IndentedBlock(
|
|
header=newline,
|
|
indent=(
|
|
None
|
|
if indent.relative_indent == config.default_indent
|
|
else indent.relative_indent
|
|
),
|
|
body=stmts,
|
|
# We want to be able to only keep comments in the footer that are actually for
|
|
# this IndentedBlock. We do so by assuming that lines which are indented to the
|
|
# same level as the block itself are comments that go at the footer of the
|
|
# block. Comments that are indented to less than this indent are assumed to
|
|
# belong to the next line of code. We override the indent here because the
|
|
# dedent node's absolute indent is the resulting indentation after the dedent
|
|
# is performed. Its this way because the whitespace state for both the dedent's
|
|
# whitespace_after and the next BaseCompoundStatement's whitespace_before is
|
|
# shared. This allows us to partially parse here and parse the rest of the
|
|
# whitespace and comments on the next line, effectively making sure that
|
|
# comments are attached to the correct node.
|
|
footer=parse_empty_lines(
|
|
config,
|
|
dedent.whitespace_after,
|
|
override_absolute_indent=indent.whitespace_before.absolute_indent,
|
|
),
|
|
)
|