Skip to content

Commit

Permalink
Optional container support
Browse files Browse the repository at this point in the history
Fix #5.
  • Loading branch information
motet-a committed Sep 3, 2018
1 parent dceab9f commit 0abd2c2
Show file tree
Hide file tree
Showing 5 changed files with 241 additions and 47 deletions.
30 changes: 23 additions & 7 deletions jinjalint/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,29 @@ def __str__(self):
)


@attr.s(frozen=True)
class JinjaOptionalContainer(Jinja):
first_opening_if = attr.ib() # JinjaTag
opening_tag = attr.ib() # OpeningTag
first_closing_if = attr.ib() # JinjaTag
content = attr.ib() # Interpolated
second_opening_if = attr.ib() # JinjaTag
closing_tag = attr.ib() # ClosingTag
second_closing_if = attr.ib() # JinjaTag

def __str__(self):
nodes = [
self.first_opening_if,
self.opening_tag,
self.first_closing_if,
self.content,
self.second_opening_if,
self.closing_tag,
self.second_closing_if,
]
return ''.join(str(n) for n in nodes)


@attr.s(frozen=True)
class InterpolatedBase(Node):
nodes = attr.ib() # [any | Jinja]
Expand Down Expand Up @@ -225,10 +248,3 @@ def __init__(self, *args, **kwargs):
kwargs = kwargs.copy()
kwargs['nodes'] = _normalize_nodes(kwargs['nodes'])
super().__init__(**kwargs)


NODE_TYPE_LIST = [
Node, Slash, OpeningTag, ClosingTag, Element, String, Integer,
Attribute, Comment, JinjaVariable, JinjaComment, JinjaTag,
JinjaElement, JinjaElementPart, InterpolatedBase, Interpolated,
]
115 changes: 92 additions & 23 deletions jinjalint/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ def contains_exclusively(string, char):
return string.replace(char, '') == ''


def truncate(s, length=16):
return s[:length] + (s[length:] and '…')


def check_indentation(file, config):
indent_size = config.get('indent_size', 4)

Expand All @@ -33,11 +37,13 @@ def check_indentation(file, config):
def add_issue(location, msg):
issues.append(Issue.from_ast(file, location, msg))

def check_indent(expected_level, node, inline=False):
def check_indent(expected_level, node, inline=False,
allow_same_line=False):
node_level = get_indent_level(file.source, node)
if node_level is None:
if not inline and not isinstance(node, ast.Jinja):
add_issue(node.begin, 'Should be on the next line')
if not inline and not allow_same_line:
node_s = repr(truncate(str(node)))
add_issue(node.begin, node_s + ' should be on the next line')
return

if node_level != expected_level:
Expand All @@ -46,7 +52,7 @@ def check_indent(expected_level, node, inline=False):
)
add_issue(node.begin, msg)

def check_attribute(expected_level, attr, inline=False):
def check_attribute(expected_level, attr, inline=False, **_):
if not attr.value:
return

Expand All @@ -55,9 +61,14 @@ def check_attribute(expected_level, attr, inline=False):
attr.begin,
'The value must begin on line {}'.format(attr.begin.line),
)
check_content(expected_level, attr.value, inline=True)
check_content(
expected_level,
attr.value,
inline=attr.value.begin.line == attr.value.end.line,
allow_same_line=True
)

def check_opening_tag(expected_level, tag, inline=False):
def check_opening_tag(expected_level, tag, inline=False, **_):
if len(tag.attributes) and tag.begin.line != tag.end.line:
first = tag.attributes[0]
check_node(
Expand All @@ -74,21 +85,23 @@ def check_opening_tag(expected_level, tag, inline=False):
inline=isinstance(attr, ast.Attribute),
)

def check_comment(expected_level, tag, inline=False):
def check_comment(expected_level, tag, **_):
pass

def check_jinja_comment(expected_level, tag, inline=False):
def check_jinja_comment(expected_level, tag, **_):
pass

def check_jinja_tag(expected_level, tag, inline=False):
def check_jinja_tag(expected_level, tag, **_):
pass

def check_string(expected_level, string, inline=False):
def check_string(expected_level, string, inline=False,
allow_same_line=False):
if string.value.begin.line != string.value.end.line:
inline = False
check_content(string.value.begin.column, string.value, inline=inline)
check_content(string.value.begin.column, string.value, inline=inline,
allow_same_line=allow_same_line)

def check_integer(expected_level, integer, inline=False):
def check_integer(expected_level, integer, **_):
pass

def get_first_child_node(parent):
Expand All @@ -104,8 +117,10 @@ def has_jinja_element_child(parent, tag_name):
child.parts[0].tag.name == tag_name
)

def check_jinja_element_part(expected_level, part, inline=False):
check_node(expected_level, part.tag, inline=inline)
def check_jinja_element_part(expected_level, part, inline=False,
allow_same_line=False):
check_node(expected_level, part.tag, inline=inline,
allow_same_line=allow_same_line)
element_names_to_not_indent = (
config.get('jinja_element_names_to_not_indent', [])
)
Expand All @@ -118,18 +133,58 @@ def check_jinja_element_part(expected_level, part, inline=False):
if part.content is not None:
check_content(content_level, part.content, inline=inline)

def check_jinja_element(expected_level, element, inline=False):
def check_jinja_optional_container_if(expected_level, o_if, html_tag, c_if,
inline=False):
check_indent(expected_level, o_if, inline=inline)
shift = 0 if inline else indent_size
if isinstance(html_tag, ast.OpeningTag):
check_opening_tag(expected_level + shift, html_tag, inline=inline)
elif isinstance(html_tag, ast.ClosingTag):
check_indent(expected_level + shift, html_tag, inline=inline)
else:
raise AssertionError('invalid tag')
check_indent(expected_level, c_if, inline=inline)
return inline

def check_jinja_optional_container(expected_level, element,
inline=False, **_):
if element.first_opening_if.begin.line == \
element.second_opening_if.end.line:
inline = True

inline = check_jinja_optional_container_if(
expected_level,
element.first_opening_if,
element.opening_tag,
element.first_closing_if,
inline=inline)

check_content(expected_level, element.content, inline=inline)

check_jinja_optional_container_if(
expected_level,
element.second_opening_if,
element.closing_tag,
element.second_closing_if,
inline=inline)

def check_jinja_element(expected_level, element, inline=False,
allow_same_line=False):
if element.begin.line == element.end.line:
inline = True
for part in element.parts:
check_node(expected_level, part, inline=inline)
check_node(
expected_level,
part,
inline=inline,
allow_same_line=allow_same_line)
if element.closing_tag is not None:
check_indent(expected_level, element.closing_tag, inline=inline)

def check_jinja_variable(expected_level, var, inline=False):
def check_jinja_variable(expected_level, var, **_):
pass

def check_element(expected_level, element, inline=False):
def check_element(expected_level, element, inline=False, **_):
opening_tag = element.opening_tag
closing_tag = element.closing_tag
check_opening_tag(expected_level, opening_tag, inline=inline)
Expand All @@ -144,8 +199,14 @@ def check_element(expected_level, element, inline=False):
)
check_indent(expected_level, closing_tag)

def check_node(expected_level, node, inline=False):
check_indent(expected_level, node, inline=inline)
def check_node(expected_level, node, inline=False,
allow_same_line=False, **_):
check_indent(
expected_level,
node,
inline=inline,
allow_same_line=allow_same_line
)

types_to_functions = {
ast.Attribute: check_attribute,
Expand All @@ -155,6 +216,7 @@ def check_node(expected_level, node, inline=False):
ast.JinjaComment: check_jinja_comment,
ast.JinjaElement: check_jinja_element,
ast.JinjaElementPart: check_jinja_element_part,
ast.JinjaOptionalContainer: check_jinja_optional_container,
ast.JinjaTag: check_jinja_tag,
ast.JinjaVariable: check_jinja_variable,
ast.String: check_string,
Expand All @@ -166,7 +228,8 @@ def check_node(expected_level, node, inline=False):
type(node), node.begin,
))

func(expected_level, node, inline=inline)
func(expected_level, node, inline=inline,
allow_same_line=allow_same_line)

def check_content_str(expected_level, string, parent_node):
lines = string.split('\n')
Expand All @@ -192,7 +255,8 @@ def check_content_str(expected_level, string, parent_node):
)
add_issue(parent_node.begin, msg)

def check_content(expected_level, parent_node, inline=False):
def check_content(expected_level, parent_node, inline=False,
allow_same_line=False):
inline_parent = inline
for i, child in enumerate(parent_node):
next_child = get_first_child_node(parent_node[i + 1:])
Expand Down Expand Up @@ -220,7 +284,12 @@ def check_content(expected_level, parent_node, inline=False):
if isinstance(child, ast.Node):
if next_child and child.begin.line == next_child.end.line:
inline = True
check_node(expected_level, child, inline=inline)
check_node(
expected_level,
child,
inline=inline,
allow_same_line=allow_same_line
)
continue

raise Exception()
Expand Down
6 changes: 5 additions & 1 deletion jinjalint/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@
def print_issues(issues, config):
sorted_issues = sorted(
issues,
key=lambda i: (i.location.file_path, i.location.line),
key=lambda i: (
i.location.file_path,
i.location.line,
i.location.column
),
)

for issue in sorted_issues:
Expand Down
Loading

0 comments on commit 0abd2c2

Please sign in to comment.