move parser tolerance setting to Environment and tolerate more issues
This commit is contained in:
parent
05ddbbda8e
commit
42345cc1c4
@ -252,6 +252,10 @@ class Environment:
|
||||
will reload the template. For higher performance it's possible to
|
||||
disable that.
|
||||
|
||||
`parser_tolerate_faults`
|
||||
Instruct the parser to tolerate some invalid constructs that don't cause much semantic uncertainty, useful for linters and LSP to provide output on incomplete templates.
|
||||
Defaults to False.
|
||||
|
||||
`bytecode_cache`
|
||||
If set to a bytecode cache object, this object will provide a
|
||||
cache for the internal Jinja bytecode so that templates don't
|
||||
@ -316,6 +320,7 @@ class Environment:
|
||||
auto_reload: bool = True,
|
||||
bytecode_cache: t.Optional["BytecodeCache"] = None,
|
||||
enable_async: bool = False,
|
||||
parser_tolerate_faults: bool = False,
|
||||
):
|
||||
# !!Important notice!!
|
||||
# The constructor accepts quite a few arguments that should be
|
||||
@ -360,6 +365,7 @@ class Environment:
|
||||
self.auto_reload = auto_reload
|
||||
|
||||
# configurable policies
|
||||
self.parser_tolerate_faults = parser_tolerate_faults
|
||||
self.policies = DEFAULT_POLICIES.copy()
|
||||
|
||||
# load extensions
|
||||
|
||||
@ -125,6 +125,7 @@ class Node(metaclass=NodeType):
|
||||
|
||||
lineno: int
|
||||
environment: t.Optional["Environment"]
|
||||
issues: list[t.Union["ParserIssue", "ExprIssue"]]
|
||||
|
||||
def __init__(self, *fields: t.Any, **attributes: t.Any) -> None:
|
||||
if self.abstract:
|
||||
@ -279,6 +280,13 @@ class Node(metaclass=NodeType):
|
||||
return "".join(buf)
|
||||
|
||||
|
||||
class ParserIssue(Node):
|
||||
attributes: tuple[str, ...] = ("message", "lineno_end")
|
||||
|
||||
message: str
|
||||
lineno_end: int | None
|
||||
|
||||
|
||||
class Stmt(Node):
|
||||
"""Base node for all statements."""
|
||||
|
||||
@ -493,13 +501,26 @@ class Expr(Node):
|
||||
return False
|
||||
|
||||
|
||||
class EmptyExpression(Expr):
|
||||
class ExprIssue(Expr):
|
||||
attributes: tuple[str, ...] = ("message", "lineno_end")
|
||||
message: str
|
||||
lineno_end: int | None
|
||||
|
||||
|
||||
class EmptyExpression(ExprIssue):
|
||||
"""Node where an expression should be but an empty expression was given.
|
||||
Returned in Fault-tolerant Mode only
|
||||
"""
|
||||
|
||||
comment: str
|
||||
attributes: t.Tuple[str, ...] = ("comment",)
|
||||
|
||||
class InvalidExpression(ExprIssue):
|
||||
"""Node where an expression should be but an unparsable expression was given.
|
||||
Returned in Fault-tolerant Mode only
|
||||
"""
|
||||
|
||||
attributes: tuple[str, ...] = ("original_str",)
|
||||
|
||||
original_str: str
|
||||
|
||||
|
||||
class BinExpr(Expr):
|
||||
|
||||
@ -57,9 +57,7 @@ class Parser:
|
||||
name: str | None = None,
|
||||
filename: str | None = None,
|
||||
state: str | None = None,
|
||||
fault_tolerant: bool = False,
|
||||
) -> None:
|
||||
self.fault_tolerant = fault_tolerant
|
||||
self.environment = environment
|
||||
self.stream = environment._tokenize(source, name, filename, state)
|
||||
self.name = name
|
||||
@ -401,9 +399,17 @@ class Parser:
|
||||
node.with_context = False
|
||||
return node
|
||||
|
||||
def parse_signature(self, node: _MacroCall) -> None:
|
||||
def parse_signature(self, node: _MacroCall) -> None | nodes.EmptyExpression | nodes.InvalidExpression:
|
||||
args = node.args = []
|
||||
defaults = node.defaults = []
|
||||
if self.environment.parser_tolerate_faults and self.stream.current.type != "lparen":
|
||||
if self.stream.current.type == "block_end":
|
||||
return nodes.EmptyExpression( # type: ignore[assignment]
|
||||
lineno=node.lineno,
|
||||
lineno_end=self.stream.current.lineno,
|
||||
message="Empty signature",
|
||||
)
|
||||
|
||||
self.stream.expect("lparen")
|
||||
while self.stream.current.type != "rparen":
|
||||
if args:
|
||||
@ -413,14 +419,27 @@ class Parser:
|
||||
if self.stream.skip_if("assign"):
|
||||
defaults.append(self.parse_expression())
|
||||
elif defaults:
|
||||
self.fail("non-default argument follows default argument")
|
||||
msg = "non-default argument follows default argument"
|
||||
if not self.environment.parser_tolerate_faults:
|
||||
self.fail(msg)
|
||||
err = nodes.InvalidExpression(
|
||||
lineno=arg.lineno,
|
||||
lineno_end=self.stream.current.lineno,
|
||||
message=msg,
|
||||
original_str=arg.name,
|
||||
)
|
||||
arg.issues = [err]
|
||||
args.append(arg)
|
||||
self.stream.expect("rparen")
|
||||
return None
|
||||
|
||||
def parse_call_block(self) -> nodes.CallBlock:
|
||||
node = nodes.CallBlock(lineno=next(self.stream).lineno)
|
||||
if self.stream.current.type == "lparen":
|
||||
self.parse_signature(node)
|
||||
signature_issue = self.parse_signature(node)
|
||||
if signature_issue:
|
||||
assert self.environment.parser_tolerate_faults
|
||||
node.args = signature_issue # type: ignore[assignment]
|
||||
else:
|
||||
node.args = []
|
||||
node.defaults = []
|
||||
@ -439,9 +458,15 @@ class Parser:
|
||||
return node
|
||||
|
||||
def parse_macro(self) -> nodes.Macro:
|
||||
node = nodes.Macro(lineno=next(self.stream).lineno)
|
||||
node = nodes.Macro(lineno=next(self.stream).lineno, issues=None)
|
||||
node.name = self.parse_assign_target(name_only=True).name
|
||||
self.parse_signature(node)
|
||||
signature_issue = self.parse_signature(node)
|
||||
if signature_issue:
|
||||
assert self.environment.parser_tolerate_faults
|
||||
node.args = signature_issue # type: ignore[assignment]
|
||||
if node.issues is None:
|
||||
node.issues = []
|
||||
node.issues.append(signature_issue)
|
||||
node.body = self.parse_statements(("name:endmacro",), drop_needle=True)
|
||||
return node
|
||||
|
||||
@ -744,7 +769,9 @@ class Parser:
|
||||
# tuple.
|
||||
if not explicit_parentheses:
|
||||
if allow_empty:
|
||||
return nodes.EmptyExpression(lineno=lineno, comment="")
|
||||
empty = nodes.EmptyExpression(lineno=lineno, message="Expected an expression")
|
||||
empty.issues = [empty]
|
||||
return empty
|
||||
self.fail(
|
||||
"Expected an expression,"
|
||||
f" got {describe_token(self.stream.current)!r}"
|
||||
@ -1014,9 +1041,9 @@ class Parser:
|
||||
next(self.stream)
|
||||
elif token.type == "variable_begin":
|
||||
next(self.stream)
|
||||
data = self.parse_tuple(with_condexpr=True, allow_empty=self.fault_tolerant)
|
||||
data = self.parse_tuple(with_condexpr=True, allow_empty=self.environment.parser_tolerate_faults)
|
||||
if isinstance(data, nodes.EmptyExpression):
|
||||
data.comment = "Empty expression inside print statement"
|
||||
data.message = "Empty expression inside print statement"
|
||||
add_data(data)
|
||||
self.stream.expect("variable_end")
|
||||
elif token.type == "block_begin":
|
||||
|
||||
Loading…
Reference in New Issue
Block a user