Module refinery.lib.scripts.js.precedence
JavaScript operator precedence and parenthesization rules.
The synthesizer does not rely on JsParenthesizedExpression nodes for
correctness: it consults needs_parens() to decide, from operator precedence, whether a child
expression must be wrapped in its parent context. The
JsSimplifications paren-removal pass uses the same
rules (via parens_required()) so it never strips a paren that is actually required.
Expand source code Browse git
"""
JavaScript operator precedence and parenthesization rules.
The synthesizer does not rely on `refinery.lib.scripts.js.model.JsParenthesizedExpression` nodes for
correctness: it consults `needs_parens` to decide, from operator precedence, whether a child
expression must be wrapped in its parent context. The
`refinery.lib.scripts.js.deobfuscation.simplify.JsSimplifications` paren-removal pass uses the same
rules (via `parens_required`) so it never strips a paren that is actually required.
"""
from __future__ import annotations
from typing import Generator
from refinery.lib.scripts import Node
from refinery.lib.scripts.js.model import (
JsArrayExpression,
JsArrowFunctionExpression,
JsAssignmentExpression,
JsAwaitExpression,
JsBinaryExpression,
JsCallExpression,
JsClassDeclaration,
JsClassExpression,
JsConditionalExpression,
JsFunctionExpression,
JsIdentifier,
JsLogicalExpression,
JsMemberExpression,
JsNewExpression,
JsNumericLiteral,
JsObjectExpression,
JsObjectPattern,
JsParenthesizedExpression,
JsSequenceExpression,
JsTaggedTemplateExpression,
JsThisExpression,
JsUnaryExpression,
JsUpdateExpression,
JsYieldExpression,
)
_BINARY_PRECEDENCE = {
'||' : 3, # noqa
'??' : 3, # noqa
'&&' : 4, # noqa
'|' : 5, # noqa
'^' : 6, # noqa
'&' : 7, # noqa
'==' : 8, # noqa
'!=' : 8, # noqa
'===': 8,
'!==': 8,
'<' : 9, # noqa
'>' : 9, # noqa
'<=' : 9, # noqa
'>=' : 9, # noqa
'in' : 9, # noqa
'instanceof': 9,
'<<' : 10, # noqa
'>>' : 10, # noqa
'>>>': 10,
'+' : 11, # noqa
'-' : 11, # noqa
'*' : 12, # noqa
'/' : 12, # noqa
'%' : 12, # noqa
'**' : 13, # noqa
}
_SEQUENCE_PRECEDENCE = 1
_ASSIGN_PRECEDENCE = 2
_CONDITIONAL_PRECEDENCE = 2
_UNARY_PRECEDENCE = 14
_PRIMARY_PRECEDENCE = 100
def _expression_precedence(node: Node) -> int:
"""
Return the operator precedence of *node* on the JavaScript scale used by `parens_required`.
Primary expressions (literals, identifiers, member access, calls, etc.) return a value high
enough that they never need parenthesisation.
"""
if isinstance(node, (JsBinaryExpression, JsLogicalExpression)):
return _BINARY_PRECEDENCE.get(node.operator, 0)
if isinstance(node, JsSequenceExpression):
return _SEQUENCE_PRECEDENCE
if isinstance(node, (JsAssignmentExpression, JsArrowFunctionExpression, JsYieldExpression)):
return _ASSIGN_PRECEDENCE
if isinstance(node, JsConditionalExpression):
return _CONDITIONAL_PRECEDENCE
if isinstance(node, (JsUnaryExpression, JsAwaitExpression, JsUpdateExpression)):
return _UNARY_PRECEDENCE
if _is_negated_literal(node):
return _UNARY_PRECEDENCE
return _PRIMARY_PRECEDENCE
def _is_negated_literal(node: Node) -> bool:
"""
Whether *node* is a numeric literal spelled as a negation rather than as a single token.
Negative zero has no literal form and is written `-0`, so a literal's class does not by itself
settle how the node binds; the spelling does, and that is what this reads.
"""
return isinstance(node, JsNumericLiteral) and node.raw.startswith('-')
def _leading_sign(node: Node) -> str | None:
"""
Return `+` or `-` if *node* is synthesized starting with that sign character, otherwise `None`.
Such a node cannot directly follow a prefix `+`/`-` operator without an intervening paren,
since the synthesizer emits no separator and the two would merge into a `++`/`--` token.
"""
if isinstance(node, JsUnaryExpression) and node.operator in ('+', '-'):
return node.operator
if isinstance(node, JsUpdateExpression) and node.prefix and node.operator in ('++', '--'):
return node.operator[0]
if _is_negated_literal(node):
return '-'
return None
def _has_optional_in_spine(node: Node) -> bool:
"""
Return whether the left spine of *node* contains an optional link (`?.`). Such a node forms an
optional chain whose short-circuit scope is delimited by parentheses: stripping the parentheses
around it in an outer member/call/new position would extend the short-circuit and change meaning
(or, for `new`, produce invalid syntax).
"""
while True:
if isinstance(node, JsMemberExpression):
if node.optional:
return True
if node.object is None:
return False
node = node.object
elif isinstance(node, JsCallExpression):
if node.optional:
return True
if node.callee is None:
return False
node = node.callee
elif isinstance(node, JsTaggedTemplateExpression):
if node.tag is None:
return False
node = node.tag
else:
return False
def _safe_new_callee(node: Node) -> bool:
"""
Return whether *node* may serve as the callee of a `new` expression without parentheses. The
callee of `new` is a member-access chain, so a call anywhere in its left spine (or any operator
expression) would re-bind the `new` and must keep its parentheses. An optional link (`?.`)
anywhere in the chain is never a valid `new` callee and likewise requires parentheses.
"""
while isinstance(node, JsMemberExpression):
if node.optional or node.object is None:
return False
node = node.object
return isinstance(node, (JsIdentifier, JsThisExpression))
def _nullish_logical_conflict(outer_op: str, inner: Node) -> bool:
"""
Return whether nesting *inner* directly under a binary operator *outer_op* would put the nullish
coalescing operator `??` immediately adjacent to `&&` or `||`. The grammar forbids that
combination without parentheses regardless of precedence, so such a nesting must stay wrapped.
"""
if not isinstance(inner, (JsBinaryExpression, JsLogicalExpression)):
return False
pair = {outer_op, inner.operator}
return '??' in pair and ('||' in pair or '&&' in pair)
def parens_required(inner: Node, parent: Node | None, paren_node: Node) -> bool:
"""
Return whether *paren_node* (positioned within *parent*, wrapping *inner*) must be parenthesized
to preserve the program's meaning. *paren_node* is the node actually occupying the slot in
*parent*; *inner* is the expression whose precedence is examined. When the two are identical the
question is simply whether *inner* needs parentheses in *parent* — see `needs_parens`.
"""
if parent is None:
return False
inner_p = _expression_precedence(inner)
if isinstance(parent, (JsBinaryExpression, JsLogicalExpression)):
outer_p = _BINARY_PRECEDENCE.get(parent.operator, 0)
if (
parent.operator == '**'
and parent.left is paren_node
and (
isinstance(inner, (JsUnaryExpression, JsAwaitExpression))
or (isinstance(inner, JsUpdateExpression) and inner.prefix)
or _is_negated_literal(inner)
)
):
return True
if _nullish_logical_conflict(parent.operator, inner):
return True
if inner_p > outer_p:
return False
if inner_p < outer_p:
return True
right_associative = parent.operator == '**'
if right_associative:
return parent.left is paren_node
return parent.right is paren_node
if isinstance(parent, JsUnaryExpression):
if parent.operator in ('+', '-') and _leading_sign(inner) == parent.operator:
return True
return inner_p < _UNARY_PRECEDENCE
if isinstance(parent, JsAwaitExpression):
return inner_p < _UNARY_PRECEDENCE
if isinstance(parent, JsConditionalExpression):
if parent.test is paren_node:
return inner_p <= _CONDITIONAL_PRECEDENCE
return inner_p < _ASSIGN_PRECEDENCE
if isinstance(parent, JsAssignmentExpression):
if parent.left is paren_node:
return False
return inner_p < _ASSIGN_PRECEDENCE
if isinstance(parent, JsMemberExpression):
if parent.object is paren_node:
if paren_node is not inner and _has_optional_in_spine(inner):
return True
if isinstance(inner, JsNumericLiteral):
return _is_negated_literal(inner) or not parent.computed
if not isinstance(inner, (
JsIdentifier,
JsMemberExpression,
JsCallExpression,
JsArrayExpression,
)):
return inner_p < _PRIMARY_PRECEDENCE
return False
if isinstance(parent, JsCallExpression):
if parent.callee is paren_node:
if paren_node is not inner and _has_optional_in_spine(inner):
return True
return inner_p < _PRIMARY_PRECEDENCE
return False
if isinstance(parent, JsTaggedTemplateExpression):
if parent.tag is paren_node:
if paren_node is not inner and _has_optional_in_spine(inner):
return True
return inner_p < _PRIMARY_PRECEDENCE
return False
if isinstance(parent, JsNewExpression):
if parent.callee is paren_node:
return not _safe_new_callee(inner)
return False
if isinstance(parent, (JsClassExpression, JsClassDeclaration)):
if parent.super_class is paren_node:
return inner_p < _PRIMARY_PRECEDENCE
return False
return False
def needs_parens(child: Node, parent: Node | None) -> bool:
"""
Return whether *child*, which currently occupies a slot in *parent*, must be wrapped in
parentheses to preserve precedence. Used by the synthesizer at print time so that correctness
does not depend on `refinery.lib.scripts.js.model.JsParenthesizedExpression` nodes being present
in the tree.
"""
if isinstance(child, JsParenthesizedExpression):
return False
return parens_required(child, parent, child)
def left_spine(expr: Node) -> Generator[Node, None, None]:
"""
The nodes whose first token is also the first token of *expr*, outermost first. Every rule about
where a bracket is needed to keep a position from being misread is a rule about that one token,
so each of them walks this chain: `({}).x` and `(function(){})()` are bracketed for what stands
at the far end of it, not for what stands at the top.
"""
node: Node | None = expr
while node is not None:
yield node
if isinstance(node, JsMemberExpression):
node = node.object
elif isinstance(node, JsCallExpression):
node = node.callee
elif isinstance(node, JsTaggedTemplateExpression):
node = node.tag
elif isinstance(node, (JsBinaryExpression, JsLogicalExpression, JsAssignmentExpression)):
node = node.left
elif isinstance(node, JsConditionalExpression):
node = node.test
elif isinstance(node, JsSequenceExpression):
node = node.expressions[0] if node.expressions else None
elif isinstance(node, JsUpdateExpression) and not node.prefix:
node = node.argument
else:
return
def _opens_a_let_declaration(node: Node) -> bool:
return (
isinstance(node, JsMemberExpression)
and node.computed
and isinstance(node.object, JsIdentifier)
and node.object.name == 'let'
)
def opens_a_let_declaration(expr: Node) -> bool:
"""
Whether *expr* begins with the two tokens `let [`. That is the one spelling a statement may not
read as an expression: `let` is an ordinary name everywhere else, so `let.a` and `let(1)` need
nothing, while `let[0]` written bare is a destructuring declaration and not the index the tree
holds. The same two tokens are refused at the head of a `for` and of a `for ... in`.
"""
return any(map(_opens_a_let_declaration, left_spine(expr)))
def opens_with_let(expr: Node) -> bool:
"""
Whether *expr* begins with the name `let` at all. A `for ... of` head refuses that much: where a
statement asks what follows the name, this position does not, and `for (let.a of x)` is no
program even though `let.a;` is one.
"""
return any(
isinstance(node, JsIdentifier) and node.name == 'let'
for node in left_spine(expr)
)
def is_the_name_async(expr: Node) -> bool:
"""
Whether *expr* is the name `async` and nothing more. A `for ... of` head refuses those two words
in a row, so `for (async of x)` is no program — but the refusal is of the pair and not of the
name, so `for (async.a of x)` and `for (async[0] of x)` need nothing: the word behind `async` is
then not `of`.
"""
return isinstance(expr, JsIdentifier) and expr.name == 'async'
def reads_an_in_operator(expr: Node | None) -> bool:
"""
Whether *expr* holds an `in` that the head of a `for` would read as its own. The initializer is
the one expression position the grammar denies that operator, because the head has already
spent the word on `for ... in`, and the denial reaches as far as a bracket does not: through the
operands of an operator, the arms of a conditional, the members of a sequence, and the body an
arrow spells without braces.
A bracket ends it, so nothing inside a call, an index, an array, an object, a template hole or a
braced body is asked. Answering for text a bracket already covers would refuse where nothing
needs refusing — and a bracket around the whole initializer is never wrong, so the reach may
be over-stated but never under-stated.
"""
if isinstance(expr, JsBinaryExpression) and expr.operator == 'in':
return True
if isinstance(expr, (JsAssignmentExpression, JsBinaryExpression, JsLogicalExpression)):
return reads_an_in_operator(expr.left) or reads_an_in_operator(expr.right)
if isinstance(expr, JsConditionalExpression):
return (
reads_an_in_operator(expr.test)
or reads_an_in_operator(expr.consequent)
or reads_an_in_operator(expr.alternate)
)
if isinstance(expr, JsSequenceExpression):
return any(map(reads_an_in_operator, expr.expressions))
if isinstance(expr, (JsAwaitExpression, JsUpdateExpression, JsYieldExpression)):
return reads_an_in_operator(expr.argument)
if isinstance(expr, JsUnaryExpression):
return reads_an_in_operator(expr.operand)
if isinstance(expr, (JsCallExpression, JsNewExpression)):
return reads_an_in_operator(expr.callee)
if isinstance(expr, JsTaggedTemplateExpression):
return reads_an_in_operator(expr.tag)
if isinstance(expr, JsMemberExpression):
return reads_an_in_operator(expr.object)
if isinstance(expr, JsArrowFunctionExpression):
return reads_an_in_operator(expr.body)
return False
def statement_needs_parens(expr: Node) -> bool:
"""
Whether an expression statement made of *expr* has to be bracketed, because its first token
would otherwise open something a statement reads as its own: a block, a function or class
declaration, or the declaration that `let [` begins.
"""
return any(
isinstance(node, (JsObjectExpression, JsFunctionExpression, JsClassExpression, JsObjectPattern))
or _opens_a_let_declaration(node)
for node in left_spine(expr)
)
def for_initializer_needs_parens(expr: Node) -> bool:
"""
Whether the initializer of a `for` has to be bracketed. It refuses the `let [` a statement
refuses, and the `in` that would end the head early.
"""
return opens_a_let_declaration(expr) or reads_an_in_operator(expr)
def for_in_target_needs_parens(expr: Node) -> bool:
"""
Whether the assignment target of a `for ... in` has to be bracketed. The `in` behind it ends the
target, so the operator needs no refusing here; the `let [` that opens a declaration does.
"""
return opens_a_let_declaration(expr)
def for_of_target_needs_parens(expr: Node) -> bool:
"""
Whether the assignment target of a `for ... of` has to be bracketed. This head refuses two bare
names outright, where the others refuse only what `let` opens.
"""
return opens_with_let(expr) or is_the_name_async(expr)
Functions
def parens_required(inner, parent, paren_node)-
Return whether paren_node (positioned within parent, wrapping inner) must be parenthesized to preserve the program's meaning. paren_node is the node actually occupying the slot in parent; inner is the expression whose precedence is examined. When the two are identical the question is simply whether inner needs parentheses in parent — see
needs_parens().Expand source code Browse git
def parens_required(inner: Node, parent: Node | None, paren_node: Node) -> bool: """ Return whether *paren_node* (positioned within *parent*, wrapping *inner*) must be parenthesized to preserve the program's meaning. *paren_node* is the node actually occupying the slot in *parent*; *inner* is the expression whose precedence is examined. When the two are identical the question is simply whether *inner* needs parentheses in *parent* — see `needs_parens`. """ if parent is None: return False inner_p = _expression_precedence(inner) if isinstance(parent, (JsBinaryExpression, JsLogicalExpression)): outer_p = _BINARY_PRECEDENCE.get(parent.operator, 0) if ( parent.operator == '**' and parent.left is paren_node and ( isinstance(inner, (JsUnaryExpression, JsAwaitExpression)) or (isinstance(inner, JsUpdateExpression) and inner.prefix) or _is_negated_literal(inner) ) ): return True if _nullish_logical_conflict(parent.operator, inner): return True if inner_p > outer_p: return False if inner_p < outer_p: return True right_associative = parent.operator == '**' if right_associative: return parent.left is paren_node return parent.right is paren_node if isinstance(parent, JsUnaryExpression): if parent.operator in ('+', '-') and _leading_sign(inner) == parent.operator: return True return inner_p < _UNARY_PRECEDENCE if isinstance(parent, JsAwaitExpression): return inner_p < _UNARY_PRECEDENCE if isinstance(parent, JsConditionalExpression): if parent.test is paren_node: return inner_p <= _CONDITIONAL_PRECEDENCE return inner_p < _ASSIGN_PRECEDENCE if isinstance(parent, JsAssignmentExpression): if parent.left is paren_node: return False return inner_p < _ASSIGN_PRECEDENCE if isinstance(parent, JsMemberExpression): if parent.object is paren_node: if paren_node is not inner and _has_optional_in_spine(inner): return True if isinstance(inner, JsNumericLiteral): return _is_negated_literal(inner) or not parent.computed if not isinstance(inner, ( JsIdentifier, JsMemberExpression, JsCallExpression, JsArrayExpression, )): return inner_p < _PRIMARY_PRECEDENCE return False if isinstance(parent, JsCallExpression): if parent.callee is paren_node: if paren_node is not inner and _has_optional_in_spine(inner): return True return inner_p < _PRIMARY_PRECEDENCE return False if isinstance(parent, JsTaggedTemplateExpression): if parent.tag is paren_node: if paren_node is not inner and _has_optional_in_spine(inner): return True return inner_p < _PRIMARY_PRECEDENCE return False if isinstance(parent, JsNewExpression): if parent.callee is paren_node: return not _safe_new_callee(inner) return False if isinstance(parent, (JsClassExpression, JsClassDeclaration)): if parent.super_class is paren_node: return inner_p < _PRIMARY_PRECEDENCE return False return False def needs_parens(child, parent)-
Return whether child, which currently occupies a slot in parent, must be wrapped in parentheses to preserve precedence. Used by the synthesizer at print time so that correctness does not depend on
JsParenthesizedExpressionnodes being present in the tree.Expand source code Browse git
def needs_parens(child: Node, parent: Node | None) -> bool: """ Return whether *child*, which currently occupies a slot in *parent*, must be wrapped in parentheses to preserve precedence. Used by the synthesizer at print time so that correctness does not depend on `refinery.lib.scripts.js.model.JsParenthesizedExpression` nodes being present in the tree. """ if isinstance(child, JsParenthesizedExpression): return False return parens_required(child, parent, child) def left_spine(expr)-
The nodes whose first token is also the first token of expr, outermost first. Every rule about where a bracket is needed to keep a position from being misread is a rule about that one token, so each of them walks this chain:
({}).xand(function(){})()are bracketed for what stands at the far end of it, not for what stands at the top.Expand source code Browse git
def left_spine(expr: Node) -> Generator[Node, None, None]: """ The nodes whose first token is also the first token of *expr*, outermost first. Every rule about where a bracket is needed to keep a position from being misread is a rule about that one token, so each of them walks this chain: `({}).x` and `(function(){})()` are bracketed for what stands at the far end of it, not for what stands at the top. """ node: Node | None = expr while node is not None: yield node if isinstance(node, JsMemberExpression): node = node.object elif isinstance(node, JsCallExpression): node = node.callee elif isinstance(node, JsTaggedTemplateExpression): node = node.tag elif isinstance(node, (JsBinaryExpression, JsLogicalExpression, JsAssignmentExpression)): node = node.left elif isinstance(node, JsConditionalExpression): node = node.test elif isinstance(node, JsSequenceExpression): node = node.expressions[0] if node.expressions else None elif isinstance(node, JsUpdateExpression) and not node.prefix: node = node.argument else: return def opens_a_let_declaration(expr)-
Whether expr begins with the two tokens
let [. That is the one spelling a statement may not read as an expression:letis an ordinary name everywhere else, solet.aandlet(1)need nothing, whilelet[0]written bare is a destructuring declaration and not the index the tree holds. The same two tokens are refused at the head of aforand of afor … in.Expand source code Browse git
def opens_a_let_declaration(expr: Node) -> bool: """ Whether *expr* begins with the two tokens `let [`. That is the one spelling a statement may not read as an expression: `let` is an ordinary name everywhere else, so `let.a` and `let(1)` need nothing, while `let[0]` written bare is a destructuring declaration and not the index the tree holds. The same two tokens are refused at the head of a `for` and of a `for ... in`. """ return any(map(_opens_a_let_declaration, left_spine(expr))) def opens_with_let(expr)-
Whether expr begins with the name
letat all. Afor … ofhead refuses that much: where a statement asks what follows the name, this position does not, andfor (let.a of x)is no program even thoughlet.a;is one.Expand source code Browse git
def opens_with_let(expr: Node) -> bool: """ Whether *expr* begins with the name `let` at all. A `for ... of` head refuses that much: where a statement asks what follows the name, this position does not, and `for (let.a of x)` is no program even though `let.a;` is one. """ return any( isinstance(node, JsIdentifier) and node.name == 'let' for node in left_spine(expr) ) def is_the_name_async(expr)-
Whether expr is the name
asyncand nothing more. Afor … ofhead refuses those two words in a row, sofor (async of x)is no program — but the refusal is of the pair and not of the name, sofor (async.a of x)andfor (async[0] of x)need nothing: the word behindasyncis then notof.Expand source code Browse git
def is_the_name_async(expr: Node) -> bool: """ Whether *expr* is the name `async` and nothing more. A `for ... of` head refuses those two words in a row, so `for (async of x)` is no program — but the refusal is of the pair and not of the name, so `for (async.a of x)` and `for (async[0] of x)` need nothing: the word behind `async` is then not `of`. """ return isinstance(expr, JsIdentifier) and expr.name == 'async' def reads_an_in_operator(expr)-
Whether expr holds an
inthat the head of aforwould read as its own. The initializer is the one expression position the grammar denies that operator, because the head has already spent the word onfor … in, and the denial reaches as far as a bracket does not: through the operands of an operator, the arms of a conditional, the members of a sequence, and the body an arrow spells without braces.A bracket ends it, so nothing inside a call, an index, an array, an object, a template hole or a braced body is asked. Answering for text a bracket already covers would refuse where nothing needs refusing — and a bracket around the whole initializer is never wrong, so the reach may be over-stated but never under-stated.
Expand source code Browse git
def reads_an_in_operator(expr: Node | None) -> bool: """ Whether *expr* holds an `in` that the head of a `for` would read as its own. The initializer is the one expression position the grammar denies that operator, because the head has already spent the word on `for ... in`, and the denial reaches as far as a bracket does not: through the operands of an operator, the arms of a conditional, the members of a sequence, and the body an arrow spells without braces. A bracket ends it, so nothing inside a call, an index, an array, an object, a template hole or a braced body is asked. Answering for text a bracket already covers would refuse where nothing needs refusing — and a bracket around the whole initializer is never wrong, so the reach may be over-stated but never under-stated. """ if isinstance(expr, JsBinaryExpression) and expr.operator == 'in': return True if isinstance(expr, (JsAssignmentExpression, JsBinaryExpression, JsLogicalExpression)): return reads_an_in_operator(expr.left) or reads_an_in_operator(expr.right) if isinstance(expr, JsConditionalExpression): return ( reads_an_in_operator(expr.test) or reads_an_in_operator(expr.consequent) or reads_an_in_operator(expr.alternate) ) if isinstance(expr, JsSequenceExpression): return any(map(reads_an_in_operator, expr.expressions)) if isinstance(expr, (JsAwaitExpression, JsUpdateExpression, JsYieldExpression)): return reads_an_in_operator(expr.argument) if isinstance(expr, JsUnaryExpression): return reads_an_in_operator(expr.operand) if isinstance(expr, (JsCallExpression, JsNewExpression)): return reads_an_in_operator(expr.callee) if isinstance(expr, JsTaggedTemplateExpression): return reads_an_in_operator(expr.tag) if isinstance(expr, JsMemberExpression): return reads_an_in_operator(expr.object) if isinstance(expr, JsArrowFunctionExpression): return reads_an_in_operator(expr.body) return False def statement_needs_parens(expr)-
Whether an expression statement made of expr has to be bracketed, because its first token would otherwise open something a statement reads as its own: a block, a function or class declaration, or the declaration that
let [begins.Expand source code Browse git
def statement_needs_parens(expr: Node) -> bool: """ Whether an expression statement made of *expr* has to be bracketed, because its first token would otherwise open something a statement reads as its own: a block, a function or class declaration, or the declaration that `let [` begins. """ return any( isinstance(node, (JsObjectExpression, JsFunctionExpression, JsClassExpression, JsObjectPattern)) or _opens_a_let_declaration(node) for node in left_spine(expr) ) def for_initializer_needs_parens(expr)-
Whether the initializer of a
forhas to be bracketed. It refuses thelet [a statement refuses, and theinthat would end the head early.Expand source code Browse git
def for_initializer_needs_parens(expr: Node) -> bool: """ Whether the initializer of a `for` has to be bracketed. It refuses the `let [` a statement refuses, and the `in` that would end the head early. """ return opens_a_let_declaration(expr) or reads_an_in_operator(expr) def for_in_target_needs_parens(expr)-
Whether the assignment target of a
for … inhas to be bracketed. Theinbehind it ends the target, so the operator needs no refusing here; thelet [that opens a declaration does.Expand source code Browse git
def for_in_target_needs_parens(expr: Node) -> bool: """ Whether the assignment target of a `for ... in` has to be bracketed. The `in` behind it ends the target, so the operator needs no refusing here; the `let [` that opens a declaration does. """ return opens_a_let_declaration(expr) def for_of_target_needs_parens(expr)-
Whether the assignment target of a
for … ofhas to be bracketed. This head refuses two bare names outright, where the others refuse only whatletopens.Expand source code Browse git
def for_of_target_needs_parens(expr: Node) -> bool: """ Whether the assignment target of a `for ... of` has to be bracketed. This head refuses two bare names outright, where the others refuse only what `let` opens. """ return opens_with_let(expr) or is_the_name_async(expr)