feat(tools/parser): parse uplevel bodies for formatting and linting

This commit is contained in:
2026-09-24 07:53:15 +02:00
parent 5d23df8da7
commit ca7c23a0f6
+25
View File
@@ -1,7 +1,9 @@
import io import io
import re
from typing import Optional, Tuple from typing import Optional, Tuple
from tclint.parser import Parser from tclint.parser import Parser
from tclint.commands import CommandArgError from tclint.commands import CommandArgError
from tclint.commands.checks import eval as eval_script_args
from tclint.syntax_tree import ( from tclint.syntax_tree import (
BracedWord, BracedWord,
BareWord, BareWord,
@@ -13,9 +15,32 @@ from tclint.syntax_tree import (
from tclint.lexer import TclSyntaxError, Lexer, TOK_EOF from tclint.lexer import TclSyntaxError, Lexer, TOK_EOF
_UPLEVEL_LEVEL_RE = re.compile(r"^#?\d+$")
def _uplevel(args, parser):
"""uplevel ?level? arg ?arg ...?"""
# ref: https://www.tcl.tk/man/tcl/TclCmd/uplevel.html
if len(args) == 0:
raise CommandArgError("not enough args to 'uplevel': got 0, expected at least 1")
# The level can only be omitted when the first arg doesn't look like one.
# A non-literal first arg (e.g. $level) is treated as a level as well.
level = []
if len(args) > 1:
first = args[0].contents
if first is None or _UPLEVEL_LEVEL_RE.match(first):
level = args[0:1]
return level + eval_script_args(args[len(level) :], parser, "uplevel")
class CustomParser(Parser): class CustomParser(Parser):
def __init__(self, debug=False, command_plugins=None): def __init__(self, debug=False, command_plugins=None):
super().__init__(debug, command_plugins) super().__init__(debug, command_plugins)
# tclint only checks the arg count of uplevel; parse its body as a script
# so it gets formatted and linted like eval/namespace eval bodies.
self._commands = {**self._commands, "uplevel": _uplevel}
# Used to normalize newlines consistently with open()'s universal newlines mode. # Used to normalize newlines consistently with open()'s universal newlines mode.
self._decoder = io.IncrementalNewlineDecoder(None, True) self._decoder = io.IncrementalNewlineDecoder(None, True)