"""Extract statically known variable names without evaluating Tcl substitutions.""" from tclint.syntax_tree import BareWord, CompoundBareWord, Node, QuotedWord, VarSub def variable_name(node: Node) -> str | None: contents = node.contents if isinstance(contents, str): return contents if isinstance(node, (CompoundBareWord, QuotedWord)) and node.children: first = node.children[0] last = node.children[-1] if ( isinstance(first, BareWord) and isinstance(last, BareWord) and "(" in first.value and last.value.endswith(")") ): # Substitutions in an array index do not change the array's name. return first.value.split("(", 1)[0] or None return None def array_key_parts(node: Node, *, preserve_variables: bool = False) -> tuple[str | None, ...]: """Keep comma-separated literal index components; substitutions are unknown.""" contents = node.contents if not isinstance(contents, str): if not isinstance(node, (CompoundBareWord, QuotedWord)) or not variable_name(node): return () chunks = [] for child in node.children: if isinstance(child, BareWord): chunks.append(child.value) elif preserve_variables and isinstance(child, VarSub) and not child.children: chunks.append("${" + child.value + "}" if child.braced else "$" + child.value) else: chunks.append("\0") contents = "".join(chunks) if "(" not in contents or not contents.endswith(")"): return () return tuple( part if part and not any(char in part for char in ("\0[]\\" if preserve_variables else "\0$[]\\")) else None for part in contents.split("(", 1)[1][:-1].split(",") )