use tclint as parser / formatter
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
import re
|
||||
|
||||
from tclint.commands import get_commands
|
||||
from tclint.violations import Rule, Violation
|
||||
|
||||
from tclint.syntax_tree import (
|
||||
Visitor,
|
||||
BracedExpression,
|
||||
Expression,
|
||||
BracedWord,
|
||||
QuotedWord,
|
||||
CommandSub,
|
||||
)
|
||||
|
||||
|
||||
class LineLengthChecker:
|
||||
"""Ensures lines aren't too long.
|
||||
|
||||
Reports 'line-length' violations.
|
||||
"""
|
||||
|
||||
# ref: https://github.com/eslint/eslint/blob/b29a16b22f234f6134475efb6c7be5ac946556ee/lib/rules/max-len.js#L101 # noqa: E501
|
||||
# ^ ironic lint waiver...
|
||||
URL_RE = re.compile(r"[^:/?#]:\/\/[^?#]")
|
||||
|
||||
def check(self, input, _, config):
|
||||
violations = []
|
||||
for i, line in enumerate(input.split("\n")):
|
||||
if self.URL_RE.search(line) is not None:
|
||||
# ignore URLs
|
||||
continue
|
||||
|
||||
lineno = i + 1
|
||||
if len(line) > config.style_line_length:
|
||||
start = (lineno, 1)
|
||||
end = (lineno, len(line) + 1)
|
||||
violations.append(
|
||||
Violation(
|
||||
Rule.LINE_LENGTH,
|
||||
f"line length is {len(line)}, maximum allowed is"
|
||||
f" {config.style_line_length}",
|
||||
start,
|
||||
end,
|
||||
)
|
||||
)
|
||||
|
||||
return violations
|
||||
|
||||
|
||||
class TrailingWhitespaceChecker:
|
||||
"""Ensures lines don't include trailing whitespace.
|
||||
|
||||
Reports 'trailing-whitespace' violations.
|
||||
"""
|
||||
|
||||
def check(self, input, _, config):
|
||||
violations = []
|
||||
for i, line in enumerate(input.split("\n")):
|
||||
lineno = i + 1
|
||||
|
||||
WHITESPACE = (" ", "\t")
|
||||
if line.endswith(WHITESPACE):
|
||||
start_col = len(line.rstrip("".join(WHITESPACE)))
|
||||
start = (lineno, start_col + 1)
|
||||
end = (lineno, len(line) + 1)
|
||||
violations.append(
|
||||
Violation(
|
||||
Rule.TRAILING_WHITESPACE,
|
||||
"line has trailing whitespace",
|
||||
start,
|
||||
end,
|
||||
)
|
||||
)
|
||||
|
||||
return violations
|
||||
|
||||
|
||||
class RedefinedBuiltinChecker(Visitor):
|
||||
"""Ensures names of built-in commands aren't reused by proc definitions.
|
||||
|
||||
Reports 'redefined-builtin' violations.
|
||||
"""
|
||||
|
||||
def check(self, _, tree, config):
|
||||
self._violations = []
|
||||
|
||||
plugins = [config.commands] if config.commands is not None else []
|
||||
commands = get_commands(plugins)
|
||||
self._commands = commands.keys()
|
||||
|
||||
tree.accept(self, recurse=True)
|
||||
|
||||
return self._violations
|
||||
|
||||
def visit_command(self, command):
|
||||
if command.routine.contents != "proc":
|
||||
return
|
||||
|
||||
if len(command.args) == 0:
|
||||
# This is a syntax error, but should already be caught as a command-args
|
||||
# error by the parser's `proc` command handling.
|
||||
return
|
||||
|
||||
name = command.args[0].contents
|
||||
|
||||
if name in self._commands:
|
||||
self._violations.append(
|
||||
Violation(
|
||||
Rule.REDEFINED_BUILTIN,
|
||||
f"redefinition of built-in command '{name}'",
|
||||
command.pos,
|
||||
command.args[1].end_pos,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class UnbracedExprChecker(Visitor):
|
||||
def check(self, _, tree, __):
|
||||
self._violations = []
|
||||
tree.accept(self, recurse=True)
|
||||
return self._violations
|
||||
|
||||
def visit_command(self, command):
|
||||
if command.routine.contents != "expr":
|
||||
return
|
||||
|
||||
if len(command.args) == 0:
|
||||
# This is a syntax error, but should already be caught as a command-args
|
||||
# error by the parser's `expr` command handling.
|
||||
return
|
||||
|
||||
if len(command.args) == 1 and isinstance(
|
||||
command.args[0], (BracedExpression, Expression)
|
||||
):
|
||||
return
|
||||
|
||||
# If we got here, tclint had trouble parsing the expression due to one of the
|
||||
# two following cases.
|
||||
|
||||
for child in command.args:
|
||||
if child.contents is None:
|
||||
self._violations.append(
|
||||
Violation(
|
||||
Rule.UNBRACED_EXPR,
|
||||
"expression with substitutions should be enclosed by braces",
|
||||
command.args[0].pos,
|
||||
command.args[-1].end_pos,
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
for child in command.args:
|
||||
if isinstance(child, (BracedWord, QuotedWord)):
|
||||
self._violations.append(
|
||||
Violation(
|
||||
Rule.UNBRACED_EXPR,
|
||||
"expression containing braced or quoted words should be"
|
||||
" enclosed by braces",
|
||||
command.args[0].pos,
|
||||
command.args[-1].end_pos,
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
# If we reach here, there's probably a bug in expr parsing logic.
|
||||
assert False, (
|
||||
"Children of expr node were different than expected, please file a bug"
|
||||
" report"
|
||||
)
|
||||
|
||||
|
||||
class RedundantExprChecker(Visitor):
|
||||
def check(self, _, tree, __):
|
||||
self._violations = []
|
||||
tree.accept(self, recurse=True)
|
||||
return self._violations
|
||||
|
||||
def _check_operand(self, operand):
|
||||
if not isinstance(operand, CommandSub) or len(operand.children) != 1:
|
||||
return
|
||||
|
||||
command = operand.children[0]
|
||||
if command.routine.contents == "expr":
|
||||
self._violations.append(
|
||||
Violation(
|
||||
Rule.REDUNDANT_EXPR,
|
||||
"unnecessary command substitution within expression",
|
||||
operand.pos,
|
||||
operand.end_pos,
|
||||
)
|
||||
)
|
||||
|
||||
def visit_braced_expression(self, expression):
|
||||
if len(expression.children) == 1:
|
||||
self._check_operand(expression.children[0])
|
||||
|
||||
def visit_expression(self, expression):
|
||||
if len(expression.children) == 1:
|
||||
self._check_operand(expression.children[0])
|
||||
|
||||
def visit_unary_op(self, expr):
|
||||
self._check_operand(expr.children[1])
|
||||
|
||||
def visit_binary_op(self, expr):
|
||||
self._check_operand(expr.children[0])
|
||||
self._check_operand(expr.children[2])
|
||||
|
||||
def visit_ternary_op(self, expr):
|
||||
self._check_operand(expr.children[0])
|
||||
self._check_operand(expr.children[2])
|
||||
self._check_operand(expr.children[4])
|
||||
|
||||
def visit_function(self, function):
|
||||
for arg in function.children[1:]:
|
||||
self._check_operand(arg)
|
||||
|
||||
|
||||
def get_checkers():
|
||||
checkers = (
|
||||
RedefinedBuiltinChecker(),
|
||||
UnbracedExprChecker(),
|
||||
RedundantExprChecker(),
|
||||
LineLengthChecker(),
|
||||
TrailingWhitespaceChecker(),
|
||||
)
|
||||
|
||||
return checkers
|
||||
Reference in New Issue
Block a user