Skip to content

Commit 82086a1

Browse files
fix bug in BirdLoader asset scanning if None nodelist (#68)
* fix `TypeError` if nodelist is `None` when scanning for components in `BirdLoader` * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * remove commented out code * make mypy happy * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 597d71b commit 82086a1

File tree

4 files changed

+70
-11
lines changed

4 files changed

+70
-11
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ and this project attempts to adhere to [Semantic Versioning](https://semver.org/
1818

1919
## [Unreleased]
2020

21+
### Fixed
22+
23+
- Fixed a `TypeError` in the `BirdLoader` when scanning for assets if a `Template` or `Node` had a `None` nodelist. This could occur with self-closing `{% bird component / %}` components and their corresponding `BirdNode` instances.
24+
2125
## [0.6.0]
2226

2327
### Added

src/django_bird/_typing.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
from __future__ import annotations
22

33
import sys
4+
from typing import TypeGuard
5+
6+
from django.template.base import Node
7+
from django.template.base import Template
48

59
if sys.version_info >= (3, 12):
610
from typing import override as typing_override
@@ -12,3 +16,7 @@
1216
override = typing_override
1317

1418
TagBits = list[str]
19+
20+
21+
def _has_nodelist(node: Template | Node) -> TypeGuard[Template]:
22+
return hasattr(node, "nodelist")

src/django_bird/loader.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from django.template.loader_tags import IncludeNode
1414
from django.template.loaders.filesystem import Loader as FileSystemLoader
1515

16+
from ._typing import _has_nodelist
1617
from ._typing import override
1718
from .compiler import Compiler
1819
from .components import components
@@ -48,23 +49,27 @@ def get_contents(self, origin: Origin) -> str:
4849
cache.set(cache_key, compiled, timeout=None)
4950
return compiled
5051

51-
def _scan_for_components(self, template: Template | Node, context: Context) -> None:
52-
if not hasattr(template, "nodelist"):
52+
def _scan_for_components(self, node: Template | Node, context: Context) -> None:
53+
if isinstance(node, BirdNode):
54+
component = components.get_component(node.name)
55+
self.asset_registry.register(component)
56+
57+
if not _has_nodelist(node) or node.nodelist is None:
5358
return
5459

55-
for node in template.nodelist:
56-
if isinstance(node, BirdNode):
57-
component = components.get_component(node.name)
60+
for child in node.nodelist:
61+
if isinstance(child, BirdNode):
62+
component = components.get_component(child.name)
5863
self.asset_registry.register(component)
5964

60-
if isinstance(node, ExtendsNode):
61-
parent_template = node.get_parent(context)
65+
if isinstance(child, ExtendsNode):
66+
parent_template = child.get_parent(context)
6267
self._scan_for_components(parent_template, context)
6368

64-
if isinstance(node, IncludeNode):
65-
template_name = node.template.token.strip("'\"")
69+
if isinstance(child, IncludeNode):
70+
template_name = child.template.token.strip("'\"")
6671
included_template = self.engine.get_template(template_name)
6772
self._scan_for_components(included_template, context)
6873

69-
if hasattr(node, "nodelist"):
70-
self._scan_for_components(node, context)
74+
if hasattr(child, "nodelist"):
75+
self._scan_for_components(child, context)

tests/test_loader.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
from __future__ import annotations
22

33
import pytest
4+
from django.template.base import Node
5+
from django.template.base import NodeList
6+
from django.template.base import Template
7+
from django.template.context import Context
8+
from django.template.engine import Engine
49
from django.template.loader import get_template
510

611
from django_bird.loader import BIRD_TAG_PATTERN
12+
from django_bird.loader import BirdLoader
13+
from django_bird.params import Params
14+
from django_bird.templatetags.tags.bird import BirdNode
715

816

917
@pytest.mark.parametrize(
@@ -99,3 +107,37 @@ def test_asset_registry(
99107
components = loader.asset_registry.components
100108

101109
assert len(components) == 3
110+
111+
112+
@pytest.mark.parametrize(
113+
"node,expected_count",
114+
[
115+
(Template("{% bird button %}Click me{% endbird %}"), 1),
116+
(BirdNode(name="button", params=Params([]), nodelist=None), 1),
117+
(
118+
BirdNode(
119+
name="button",
120+
params=Params([]),
121+
nodelist=NodeList(
122+
[BirdNode(name="button", params=Params([]), nodelist=None)],
123+
),
124+
),
125+
1,
126+
),
127+
(type("NodeWithNoneNodelist", (Node,), {"nodelist": None})(), 0),
128+
(Template("{% bird button %}{% bird button %}{% endbird %}{% endbird %}"), 1),
129+
],
130+
)
131+
def test_scan_for_components(
132+
node, expected_count, create_bird_template, create_bird_asset
133+
):
134+
button = create_bird_template("button", "<button>Click me</button>")
135+
create_bird_asset(button, ".button { color: blue; }", "css")
136+
create_bird_asset(button, "console.log('button');", "js")
137+
138+
loader = BirdLoader(Engine.get_default())
139+
context = Context()
140+
141+
loader._scan_for_components(node, context)
142+
143+
assert len(loader.asset_registry.components) == expected_count

0 commit comments

Comments
 (0)