Module refinery.lib.scripts.ps1.deobfuscation.simplify
PowerShell syntax normalization transforms.
Expand source code Browse git
"""
PowerShell syntax normalization transforms.
"""
from __future__ import annotations
from refinery.lib.scripts.ps1.deobfuscation.data import (
ALL_PARAMETER_NAMES,
KNOWN_ALIAS,
KNOWN_CMDLETS,
KNOWN_PS_OPERATORS,
KNOWN_PS_SWITCHES,
PS1_KNOWN_VARIABLES,
SIMPLE_IDENTIFIER,
TYPE_ARG_COMMANDS,
)
from refinery.lib.scripts.ps1.deobfuscation.helpers import (
LocalFunctionAwareTransformer,
get_command_name,
make_string_literal,
string_value,
)
from refinery.lib.scripts.ps1.deobfuscation.typenames import canonical_type_name
from refinery.lib.scripts.ps1.model import (
Ps1BinaryExpression,
Ps1CastExpression,
Ps1ClassDefinition,
Ps1CommandArgument,
Ps1CommandArgumentKind,
Ps1CommandInvocation,
Ps1ExpandableString,
Ps1ExpressionStatement,
Ps1FunctionDefinition,
Ps1HereString,
Ps1IntegerLiteral,
Ps1InvokeMember,
Ps1MemberAccess,
Ps1ParenExpression,
Ps1RealLiteral,
Ps1ScopeModifier,
Ps1StringLiteral,
Ps1SubExpression,
Ps1TypeExpression,
Ps1UnaryExpression,
Ps1Variable,
)
from refinery.lib.scripts.ps1.token import _strip_backtick_noop
from refinery.lib.scripts.win32const import DEFAULT_ENVIRONMENT_TEMPLATE
_KNOWN_ENV_NAMES: dict[str, str] = {
name.lower(): name for name in DEFAULT_ENVIRONMENT_TEMPLATE
}
class Ps1Simplifications(LocalFunctionAwareTransformer):
def visit_Ps1Variable(self, node: Ps1Variable):
self.generic_visit(node)
if '`' in node.name:
node.name = _strip_backtick_noop(node.name)
self.mark_changed()
if node.braced and SIMPLE_IDENTIFIER.match(node.name):
node.braced = False
self.mark_changed()
canonical = PS1_KNOWN_VARIABLES.get(node.name.lower())
if canonical is not None and canonical != node.name:
node.name = canonical
self.mark_changed()
if node.scope == Ps1ScopeModifier.ENV:
canonical = _KNOWN_ENV_NAMES.get(node.name.lower())
if canonical is not None and canonical != node.name:
node.name = canonical
self.mark_changed()
return None
def visit_Ps1FunctionDefinition(self, node: Ps1FunctionDefinition):
self.generic_visit(node)
if '`' in node.name:
node.name = _strip_backtick_noop(node.name)
self.mark_changed()
return None
def visit_Ps1ClassDefinition(self, node: Ps1ClassDefinition):
self.generic_visit(node)
if '`' in node.name:
node.name = _strip_backtick_noop(node.name)
self.mark_changed()
return None
def visit_Ps1ParenExpression(self, node: Ps1ParenExpression):
self.generic_visit(node)
inner = node.expression
if isinstance(inner, (Ps1StringLiteral, Ps1HereString, Ps1IntegerLiteral, Ps1RealLiteral, Ps1TypeExpression)):
return inner
return None
def visit_Ps1SubExpression(self, node: Ps1SubExpression):
self.generic_visit(node)
if isinstance(node.parent, Ps1ExpandableString):
return None
if len(node.body) == 1:
stmt = node.body[0]
if isinstance(stmt, Ps1ExpressionStatement):
inner = stmt.expression
if isinstance(inner, (
Ps1Variable,
Ps1StringLiteral,
Ps1IntegerLiteral,
Ps1RealLiteral,
Ps1TypeExpression,
Ps1CastExpression,
)):
return inner
return None
def visit_Ps1ExpandableString(self, node: Ps1ExpandableString):
self.generic_visit(node)
parts: list[str] = []
for p in node.parts:
if isinstance(p, Ps1StringLiteral):
parts.append(p.value)
continue
if isinstance(p, Ps1SubExpression) and len(p.body) == 1:
stmt = p.body[0]
if isinstance(stmt, Ps1ExpressionStatement) and stmt.expression is not None:
sv = string_value(stmt.expression)
if sv is not None:
parts.append(sv)
continue
return None
return make_string_literal(''.join(parts))
def visit_Ps1MemberAccess(self, node: Ps1MemberAccess):
self.generic_visit(node)
self._normalize_member(node)
return None
def visit_Ps1InvokeMember(self, node: Ps1InvokeMember):
self.generic_visit(node)
self._normalize_member(node)
return None
def _normalize_member(self, node: Ps1MemberAccess | Ps1InvokeMember):
if not isinstance(node.member, Ps1StringLiteral):
return
name = node.member.value
if node.member.raw and node.member.raw[0] == '"' and '`' in node.member.raw:
name = _strip_backtick_noop(node.member.raw[1:-1])
if SIMPLE_IDENTIFIER.match(name):
node.member = name
self.mark_changed()
def visit_Ps1BinaryExpression(self, node: Ps1BinaryExpression):
self.generic_visit(node)
normalized = KNOWN_PS_OPERATORS.get(node.operator.lower(), node.operator)
if normalized != node.operator:
node.operator = normalized
self.mark_changed()
return None
def visit_Ps1UnaryExpression(self, node: Ps1UnaryExpression):
self.generic_visit(node)
normalized = KNOWN_PS_OPERATORS.get(node.operator.lower(), node.operator)
if normalized != node.operator:
node.operator = normalized
self.mark_changed()
return None
def visit_Ps1CommandArgument(self, node: Ps1CommandArgument):
self.generic_visit(node)
if node.kind in (Ps1CommandArgumentKind.SWITCH, Ps1CommandArgumentKind.NAMED):
if '`' in node.name:
node.name = _strip_backtick_noop(node.name)
self.mark_changed()
name_lower = node.name.lower()
normalized = KNOWN_PS_OPERATORS.get(name_lower)
if normalized is None:
normalized = KNOWN_PS_SWITCHES.get(name_lower)
if normalized is None:
bare = name_lower.lstrip('-')
if bare != name_lower:
canonical = ALL_PARAMETER_NAMES.get(bare)
if canonical is not None:
normalized = F'-{canonical}'
if normalized is not None and normalized != node.name:
node.name = normalized
self.mark_changed()
return None
def visit_Ps1TypeExpression(self, node: Ps1TypeExpression):
node.name = self._normalize_type_name(node.name)
return None
def visit_Ps1CastExpression(self, node: Ps1CastExpression):
self.generic_visit(node)
node.type_name = self._normalize_type_name(node.type_name)
return None
def _normalize_type_name(self, name: str) -> str:
canonical = canonical_type_name(name)
if canonical is not None and canonical != name:
self.mark_changed()
return canonical
return name
def visit_Ps1CommandInvocation(self, node: Ps1CommandInvocation):
self.generic_visit(node)
old_name = node.name
if isinstance(node.name, Ps1ParenExpression) and node.name.expression is not None:
inner = node.name.expression
if isinstance(inner, Ps1StringLiteral):
node.name = inner
elif isinstance(inner, Ps1CommandInvocation):
c = get_command_name(inner)
if c is not None and c.lower() in ('gcm', 'get-command'):
if len(inner.arguments) == 1:
arg = inner.arguments[0]
if isinstance(arg, Ps1CommandArgument):
arg = arg.value
if isinstance(arg, Ps1StringLiteral):
node.name = arg
elif isinstance(arg, Ps1ParenExpression):
if isinstance(arg.expression, Ps1StringLiteral):
node.name = arg.expression
if node.name is not old_name:
self.mark_changed()
if node.name and isinstance(node.name, Ps1StringLiteral):
if '`' in node.name.value:
stripped = _strip_backtick_noop(node.name.value)
node.name = Ps1StringLiteral(
offset=node.name.offset,
value=stripped,
raw=stripped,
)
self.mark_changed()
name_lower = node.name.value.lower()
if name_lower not in self._local_functions:
alias_target = KNOWN_ALIAS.get(name_lower)
if alias_target is not None:
new_value = alias_target
else:
new_value = KNOWN_CMDLETS.get(name_lower, node.name.value)
if new_value != node.name.value or new_value != node.name.raw:
node.name = Ps1StringLiteral(
offset=node.name.offset,
value=new_value,
raw=new_value,
)
self.mark_changed()
if node.invocation_operator in ('&', '.'):
if isinstance(node.name, Ps1StringLiteral):
name_val = node.name.value
if SIMPLE_IDENTIFIER.match(name_val) or '-' in name_val:
node.name = Ps1StringLiteral(
offset=node.name.offset,
value=name_val,
raw=name_val,
)
node.invocation_operator = ''
self.mark_changed()
if (c := get_command_name(node)) and c.lower() in TYPE_ARG_COMMANDS:
self._normalize_first_positional_type_arg(node)
return None
def _normalize_first_positional_type_arg(self, node: Ps1CommandInvocation):
for arg in node.arguments:
if isinstance(arg, Ps1CommandArgument):
if arg.kind == Ps1CommandArgumentKind.NAMED:
if arg.name.lstrip('-').lower() == 'class' and isinstance(arg.value, Ps1StringLiteral):
normalized = self._normalize_type_name(arg.value.value)
if normalized != arg.value.value:
arg.value = nl = Ps1StringLiteral(offset=arg.value.offset, value=normalized, raw=normalized)
nl.parent = arg
continue
if arg.kind != Ps1CommandArgumentKind.POSITIONAL:
continue
if isinstance(arg.value, Ps1StringLiteral):
normalized = self._normalize_type_name(arg.value.value)
if normalized != arg.value.value:
arg.value = nl = Ps1StringLiteral(offset=arg.value.offset, value=normalized, raw=normalized)
nl.parent = arg
return
if isinstance(arg, Ps1StringLiteral):
normalized = self._normalize_type_name(arg.value)
if normalized != arg.value:
nl = Ps1StringLiteral(offset=arg.offset, value=normalized, raw=normalized)
idx = node.arguments.index(arg)
node.arguments[idx] = nl
nl.parent = node
return
Classes
class Ps1Simplifications-
In-place tree rewriter. Each visit method may return a replacement node or
Noneto keep the original. Tracks whether any transformation was applied via thechangedflag.Expand source code Browse git
class Ps1Simplifications(LocalFunctionAwareTransformer): def visit_Ps1Variable(self, node: Ps1Variable): self.generic_visit(node) if '`' in node.name: node.name = _strip_backtick_noop(node.name) self.mark_changed() if node.braced and SIMPLE_IDENTIFIER.match(node.name): node.braced = False self.mark_changed() canonical = PS1_KNOWN_VARIABLES.get(node.name.lower()) if canonical is not None and canonical != node.name: node.name = canonical self.mark_changed() if node.scope == Ps1ScopeModifier.ENV: canonical = _KNOWN_ENV_NAMES.get(node.name.lower()) if canonical is not None and canonical != node.name: node.name = canonical self.mark_changed() return None def visit_Ps1FunctionDefinition(self, node: Ps1FunctionDefinition): self.generic_visit(node) if '`' in node.name: node.name = _strip_backtick_noop(node.name) self.mark_changed() return None def visit_Ps1ClassDefinition(self, node: Ps1ClassDefinition): self.generic_visit(node) if '`' in node.name: node.name = _strip_backtick_noop(node.name) self.mark_changed() return None def visit_Ps1ParenExpression(self, node: Ps1ParenExpression): self.generic_visit(node) inner = node.expression if isinstance(inner, (Ps1StringLiteral, Ps1HereString, Ps1IntegerLiteral, Ps1RealLiteral, Ps1TypeExpression)): return inner return None def visit_Ps1SubExpression(self, node: Ps1SubExpression): self.generic_visit(node) if isinstance(node.parent, Ps1ExpandableString): return None if len(node.body) == 1: stmt = node.body[0] if isinstance(stmt, Ps1ExpressionStatement): inner = stmt.expression if isinstance(inner, ( Ps1Variable, Ps1StringLiteral, Ps1IntegerLiteral, Ps1RealLiteral, Ps1TypeExpression, Ps1CastExpression, )): return inner return None def visit_Ps1ExpandableString(self, node: Ps1ExpandableString): self.generic_visit(node) parts: list[str] = [] for p in node.parts: if isinstance(p, Ps1StringLiteral): parts.append(p.value) continue if isinstance(p, Ps1SubExpression) and len(p.body) == 1: stmt = p.body[0] if isinstance(stmt, Ps1ExpressionStatement) and stmt.expression is not None: sv = string_value(stmt.expression) if sv is not None: parts.append(sv) continue return None return make_string_literal(''.join(parts)) def visit_Ps1MemberAccess(self, node: Ps1MemberAccess): self.generic_visit(node) self._normalize_member(node) return None def visit_Ps1InvokeMember(self, node: Ps1InvokeMember): self.generic_visit(node) self._normalize_member(node) return None def _normalize_member(self, node: Ps1MemberAccess | Ps1InvokeMember): if not isinstance(node.member, Ps1StringLiteral): return name = node.member.value if node.member.raw and node.member.raw[0] == '"' and '`' in node.member.raw: name = _strip_backtick_noop(node.member.raw[1:-1]) if SIMPLE_IDENTIFIER.match(name): node.member = name self.mark_changed() def visit_Ps1BinaryExpression(self, node: Ps1BinaryExpression): self.generic_visit(node) normalized = KNOWN_PS_OPERATORS.get(node.operator.lower(), node.operator) if normalized != node.operator: node.operator = normalized self.mark_changed() return None def visit_Ps1UnaryExpression(self, node: Ps1UnaryExpression): self.generic_visit(node) normalized = KNOWN_PS_OPERATORS.get(node.operator.lower(), node.operator) if normalized != node.operator: node.operator = normalized self.mark_changed() return None def visit_Ps1CommandArgument(self, node: Ps1CommandArgument): self.generic_visit(node) if node.kind in (Ps1CommandArgumentKind.SWITCH, Ps1CommandArgumentKind.NAMED): if '`' in node.name: node.name = _strip_backtick_noop(node.name) self.mark_changed() name_lower = node.name.lower() normalized = KNOWN_PS_OPERATORS.get(name_lower) if normalized is None: normalized = KNOWN_PS_SWITCHES.get(name_lower) if normalized is None: bare = name_lower.lstrip('-') if bare != name_lower: canonical = ALL_PARAMETER_NAMES.get(bare) if canonical is not None: normalized = F'-{canonical}' if normalized is not None and normalized != node.name: node.name = normalized self.mark_changed() return None def visit_Ps1TypeExpression(self, node: Ps1TypeExpression): node.name = self._normalize_type_name(node.name) return None def visit_Ps1CastExpression(self, node: Ps1CastExpression): self.generic_visit(node) node.type_name = self._normalize_type_name(node.type_name) return None def _normalize_type_name(self, name: str) -> str: canonical = canonical_type_name(name) if canonical is not None and canonical != name: self.mark_changed() return canonical return name def visit_Ps1CommandInvocation(self, node: Ps1CommandInvocation): self.generic_visit(node) old_name = node.name if isinstance(node.name, Ps1ParenExpression) and node.name.expression is not None: inner = node.name.expression if isinstance(inner, Ps1StringLiteral): node.name = inner elif isinstance(inner, Ps1CommandInvocation): c = get_command_name(inner) if c is not None and c.lower() in ('gcm', 'get-command'): if len(inner.arguments) == 1: arg = inner.arguments[0] if isinstance(arg, Ps1CommandArgument): arg = arg.value if isinstance(arg, Ps1StringLiteral): node.name = arg elif isinstance(arg, Ps1ParenExpression): if isinstance(arg.expression, Ps1StringLiteral): node.name = arg.expression if node.name is not old_name: self.mark_changed() if node.name and isinstance(node.name, Ps1StringLiteral): if '`' in node.name.value: stripped = _strip_backtick_noop(node.name.value) node.name = Ps1StringLiteral( offset=node.name.offset, value=stripped, raw=stripped, ) self.mark_changed() name_lower = node.name.value.lower() if name_lower not in self._local_functions: alias_target = KNOWN_ALIAS.get(name_lower) if alias_target is not None: new_value = alias_target else: new_value = KNOWN_CMDLETS.get(name_lower, node.name.value) if new_value != node.name.value or new_value != node.name.raw: node.name = Ps1StringLiteral( offset=node.name.offset, value=new_value, raw=new_value, ) self.mark_changed() if node.invocation_operator in ('&', '.'): if isinstance(node.name, Ps1StringLiteral): name_val = node.name.value if SIMPLE_IDENTIFIER.match(name_val) or '-' in name_val: node.name = Ps1StringLiteral( offset=node.name.offset, value=name_val, raw=name_val, ) node.invocation_operator = '' self.mark_changed() if (c := get_command_name(node)) and c.lower() in TYPE_ARG_COMMANDS: self._normalize_first_positional_type_arg(node) return None def _normalize_first_positional_type_arg(self, node: Ps1CommandInvocation): for arg in node.arguments: if isinstance(arg, Ps1CommandArgument): if arg.kind == Ps1CommandArgumentKind.NAMED: if arg.name.lstrip('-').lower() == 'class' and isinstance(arg.value, Ps1StringLiteral): normalized = self._normalize_type_name(arg.value.value) if normalized != arg.value.value: arg.value = nl = Ps1StringLiteral(offset=arg.value.offset, value=normalized, raw=normalized) nl.parent = arg continue if arg.kind != Ps1CommandArgumentKind.POSITIONAL: continue if isinstance(arg.value, Ps1StringLiteral): normalized = self._normalize_type_name(arg.value.value) if normalized != arg.value.value: arg.value = nl = Ps1StringLiteral(offset=arg.value.offset, value=normalized, raw=normalized) nl.parent = arg return if isinstance(arg, Ps1StringLiteral): normalized = self._normalize_type_name(arg.value) if normalized != arg.value: nl = Ps1StringLiteral(offset=arg.offset, value=normalized, raw=normalized) idx = node.arguments.index(arg) node.arguments[idx] = nl nl.parent = node returnAncestors
Methods
def visit_Ps1Variable(self, node)-
Expand source code Browse git
def visit_Ps1Variable(self, node: Ps1Variable): self.generic_visit(node) if '`' in node.name: node.name = _strip_backtick_noop(node.name) self.mark_changed() if node.braced and SIMPLE_IDENTIFIER.match(node.name): node.braced = False self.mark_changed() canonical = PS1_KNOWN_VARIABLES.get(node.name.lower()) if canonical is not None and canonical != node.name: node.name = canonical self.mark_changed() if node.scope == Ps1ScopeModifier.ENV: canonical = _KNOWN_ENV_NAMES.get(node.name.lower()) if canonical is not None and canonical != node.name: node.name = canonical self.mark_changed() return None def visit_Ps1FunctionDefinition(self, node)-
Expand source code Browse git
def visit_Ps1FunctionDefinition(self, node: Ps1FunctionDefinition): self.generic_visit(node) if '`' in node.name: node.name = _strip_backtick_noop(node.name) self.mark_changed() return None def visit_Ps1ClassDefinition(self, node)-
Expand source code Browse git
def visit_Ps1ClassDefinition(self, node: Ps1ClassDefinition): self.generic_visit(node) if '`' in node.name: node.name = _strip_backtick_noop(node.name) self.mark_changed() return None def visit_Ps1ParenExpression(self, node)-
Expand source code Browse git
def visit_Ps1ParenExpression(self, node: Ps1ParenExpression): self.generic_visit(node) inner = node.expression if isinstance(inner, (Ps1StringLiteral, Ps1HereString, Ps1IntegerLiteral, Ps1RealLiteral, Ps1TypeExpression)): return inner return None def visit_Ps1SubExpression(self, node)-
Expand source code Browse git
def visit_Ps1SubExpression(self, node: Ps1SubExpression): self.generic_visit(node) if isinstance(node.parent, Ps1ExpandableString): return None if len(node.body) == 1: stmt = node.body[0] if isinstance(stmt, Ps1ExpressionStatement): inner = stmt.expression if isinstance(inner, ( Ps1Variable, Ps1StringLiteral, Ps1IntegerLiteral, Ps1RealLiteral, Ps1TypeExpression, Ps1CastExpression, )): return inner return None def visit_Ps1ExpandableString(self, node)-
Expand source code Browse git
def visit_Ps1ExpandableString(self, node: Ps1ExpandableString): self.generic_visit(node) parts: list[str] = [] for p in node.parts: if isinstance(p, Ps1StringLiteral): parts.append(p.value) continue if isinstance(p, Ps1SubExpression) and len(p.body) == 1: stmt = p.body[0] if isinstance(stmt, Ps1ExpressionStatement) and stmt.expression is not None: sv = string_value(stmt.expression) if sv is not None: parts.append(sv) continue return None return make_string_literal(''.join(parts)) def visit_Ps1MemberAccess(self, node)-
Expand source code Browse git
def visit_Ps1MemberAccess(self, node: Ps1MemberAccess): self.generic_visit(node) self._normalize_member(node) return None def visit_Ps1InvokeMember(self, node)-
Expand source code Browse git
def visit_Ps1InvokeMember(self, node: Ps1InvokeMember): self.generic_visit(node) self._normalize_member(node) return None def visit_Ps1BinaryExpression(self, node)-
Expand source code Browse git
def visit_Ps1BinaryExpression(self, node: Ps1BinaryExpression): self.generic_visit(node) normalized = KNOWN_PS_OPERATORS.get(node.operator.lower(), node.operator) if normalized != node.operator: node.operator = normalized self.mark_changed() return None def visit_Ps1UnaryExpression(self, node)-
Expand source code Browse git
def visit_Ps1UnaryExpression(self, node: Ps1UnaryExpression): self.generic_visit(node) normalized = KNOWN_PS_OPERATORS.get(node.operator.lower(), node.operator) if normalized != node.operator: node.operator = normalized self.mark_changed() return None def visit_Ps1CommandArgument(self, node)-
Expand source code Browse git
def visit_Ps1CommandArgument(self, node: Ps1CommandArgument): self.generic_visit(node) if node.kind in (Ps1CommandArgumentKind.SWITCH, Ps1CommandArgumentKind.NAMED): if '`' in node.name: node.name = _strip_backtick_noop(node.name) self.mark_changed() name_lower = node.name.lower() normalized = KNOWN_PS_OPERATORS.get(name_lower) if normalized is None: normalized = KNOWN_PS_SWITCHES.get(name_lower) if normalized is None: bare = name_lower.lstrip('-') if bare != name_lower: canonical = ALL_PARAMETER_NAMES.get(bare) if canonical is not None: normalized = F'-{canonical}' if normalized is not None and normalized != node.name: node.name = normalized self.mark_changed() return None def visit_Ps1TypeExpression(self, node)-
Expand source code Browse git
def visit_Ps1TypeExpression(self, node: Ps1TypeExpression): node.name = self._normalize_type_name(node.name) return None def visit_Ps1CastExpression(self, node)-
Expand source code Browse git
def visit_Ps1CastExpression(self, node: Ps1CastExpression): self.generic_visit(node) node.type_name = self._normalize_type_name(node.type_name) return None def visit_Ps1CommandInvocation(self, node)-
Expand source code Browse git
def visit_Ps1CommandInvocation(self, node: Ps1CommandInvocation): self.generic_visit(node) old_name = node.name if isinstance(node.name, Ps1ParenExpression) and node.name.expression is not None: inner = node.name.expression if isinstance(inner, Ps1StringLiteral): node.name = inner elif isinstance(inner, Ps1CommandInvocation): c = get_command_name(inner) if c is not None and c.lower() in ('gcm', 'get-command'): if len(inner.arguments) == 1: arg = inner.arguments[0] if isinstance(arg, Ps1CommandArgument): arg = arg.value if isinstance(arg, Ps1StringLiteral): node.name = arg elif isinstance(arg, Ps1ParenExpression): if isinstance(arg.expression, Ps1StringLiteral): node.name = arg.expression if node.name is not old_name: self.mark_changed() if node.name and isinstance(node.name, Ps1StringLiteral): if '`' in node.name.value: stripped = _strip_backtick_noop(node.name.value) node.name = Ps1StringLiteral( offset=node.name.offset, value=stripped, raw=stripped, ) self.mark_changed() name_lower = node.name.value.lower() if name_lower not in self._local_functions: alias_target = KNOWN_ALIAS.get(name_lower) if alias_target is not None: new_value = alias_target else: new_value = KNOWN_CMDLETS.get(name_lower, node.name.value) if new_value != node.name.value or new_value != node.name.raw: node.name = Ps1StringLiteral( offset=node.name.offset, value=new_value, raw=new_value, ) self.mark_changed() if node.invocation_operator in ('&', '.'): if isinstance(node.name, Ps1StringLiteral): name_val = node.name.value if SIMPLE_IDENTIFIER.match(name_val) or '-' in name_val: node.name = Ps1StringLiteral( offset=node.name.offset, value=name_val, raw=name_val, ) node.invocation_operator = '' self.mark_changed() if (c := get_command_name(node)) and c.lower() in TYPE_ARG_COMMANDS: self._normalize_first_positional_type_arg(node) return None