diff --git a/src/djlint/formatter/indent.py b/src/djlint/formatter/indent.py index c73553427..d486a3bb0 100644 --- a/src/djlint/formatter/indent.py +++ b/src/djlint/formatter/indent.py @@ -382,21 +382,51 @@ def format_set(config: Config, html: str, match: re.Match) -> str: return f"{leading_space}{open_bracket} {tag} {contents} {close_bracket}" def format_function(config: Config, html: str, match: re.Match) -> str: - if inside_ignored_block(config, html, match): + # will accept stuff like ` url("foo").foo().bar[1] ` + if inside_ignored_block(config, html, match) or not match.group(3): return match.group() + leading_space = match.group(1) open_bracket = match.group(2) tag = match.group(3).strip() - index = (match.group(5) or "").strip() - close_bracket = match.group(6) - contents = format_data( - config, - match.group(4).strip()[1:-1], - len(f"{open_bracket} {tag}() {close_bracket}"), - leading_space, - ) + close_bracket = match.group(4) - return f"{leading_space}{open_bracket} {tag}({contents}){index} {close_bracket}" + functions = "" + for function in tag.split("."): + parts = re.search( + r""" + ((?:\w|\|)*) # function name including pipe: id|default() + (?: + ( + \((?:\"[^\"]*\"|'[^']*'|[^\)])*?\) # () + | \[(?:\"[^\"]*\"|'[^']*'|[^\]])*?\] # [] + ) + (\[(?:\"[^\"]*\"|'[^']*'|[^\]])*?\])? # [] trailing + )? + """, + function, + re.I | re.DOTALL | re.VERBOSE, + ) + if functions != "": + functions += "." + functions += parts.group(1) + + if parts.group(2): + functions += ( + parts.group(2)[0] + + format_data( + config, + parts.group(2).strip()[1:-1], + len(f"{open_bracket} {tag} {close_bracket}"), + leading_space, + ) + + parts.group(2)[-1] + ) + + if parts.group(3): + functions += parts.group(3) + + return f"{leading_space}{open_bracket} {functions} {close_bracket}" if config.no_set_formatting is False: func = partial(format_set, config, beautified_code) @@ -415,7 +445,29 @@ def format_function(config: Config, html: str, match: re.Match) -> str: # format function contents beautified_code = re.sub( re.compile( - r"([ ]*)({{-?\+?)[ ]*?((?:(?!}}).)*?\w)(\((?:\"[^\"]*\"|'[^']*'|[^\)])*?\)[ ]*)((?:\[[^\]]*?\]|\.\d+)[ ]*)?((?:(?!}}).)*?-?\+?}})", + r""" + ([ ]*) + ({{-?\+?) + [ ]*? + ( + (?: + (?: + (?:(?:(?!}}).)*?\w) # function name + (?: + (?:\((?:\"[^\"]*\"|'[^']*'|[^\)])*?\)) # (stuff) + | (?:\[(?:\"[^\"]*\"|'[^']*'|[^\]])*?\]) # [stuff] + ) + ) + \.?)+ + (?: + (?:\[(?:\"[^\"]*\"|'[^']*'|[^\]])*?\]) # [] following () + | [a-z\d]* # .stuff + + )? + [ ]* + )? + ((?:(?!}}).)*?-?\+?}}) + """, flags=re.IGNORECASE | re.MULTILINE | re.VERBOSE | re.DOTALL, ), func, diff --git a/tests/test_nunjucks/test_functions.py b/tests/test_nunjucks/test_functions.py index cf5fede77..d3e0ac7ee 100644 --- a/tests/test_nunjucks/test_functions.py +++ b/tests/test_nunjucks/test_functions.py @@ -29,11 +29,31 @@ id="test quoting", ), pytest.param( - ('{{ item.split("/")[1] }}\n' '{{ item.split("/").123 }}'), - ('{{ item.split("/")[1] }}\n' '{{ item.split("/").123 }}\n'), + ( + '{{ item.split("/")[1] }}\n' + '{{ item.split("/").123 }}\n' + '{{ item.split("/").bar }}' + ), + ( + '{{ item.split("/")[1] }}\n' + '{{ item.split("/").123 }}\n' + '{{ item.split("/").bar }}\n' + ), ({}), id="test index", ), + pytest.param( + ("{{ url('foo').foo }}"), + ('{{ url("foo").foo }}\n'), + ({}), + id="function_call_attribute_access", + ), + pytest.param( + ("{{ url('foo').foo().bar[1] }}"), + ('{{ url("foo").foo().bar[1] }}\n'), + ({}), + id="function_call_attribute_access_multiple", + ), pytest.param( ( "{{ myfunc({\n"