ykmnkmi / jinja.dart

Jinja2 template engine port for Dart.
https://pub.dev/packages/jinja
MIT License
51 stars 11 forks source link

NoSuchMethodError: 'The getter 'call' was called on null' with 'With' tag #13

Closed lindeer closed 2 years ago

lindeer commented 2 years ago

I got this error when I tried to load a 'with' template, I am not sure my usage is correct: jinjia version 0.4.0-dev.31

  const raw = """
{% with _articles=part.articles, file=file, config=config %}
    {% include "aaa.html" %}
{% endwith %}
  """;
  final content = File('aaa.html').readAsStringSync();
  final tpl = Environment(
    filters: filters,
    loader: MapLoader({
      'aaa.html': content,
    }),
  ).fromString(raw);
  final r = tpl.render(<String, dynamic>{
      'file': {
      },
      'part': {
        'articles': [
          {
            'path': '/user/to/file',
            'anchor': "#nice",
            'url': 'https://www.github.com',
            'level': '0',
            'title': 'That is Why You Go Away!',
            'articles': [],
          },
        ],
      }
    });

but code throw exception as below:

Unhandled exception:
NoSuchMethodError: The getter 'call' was called on null.
Receiver: null
Tried calling: call
#0      Object.noSuchMethod (dart:core-patch/object_patch.dart:38:5)
#1      Context.call (package:jinja/src/context.dart:27:27)

aaa.html:

    {% for article in _articles %}
        <li class="chapter {% if article.path == file.path and not article.anchor %}active{% endif %}" data-level="{{ article.level }}" {% if article.path %}data-path="{{ article.path|resolveFile }}"{% endif %}>
            {% if article.path and getPageByPath(article.path) %}
                <a href="{{ article.path|resolveFile }}{{ article.anchor }}">
            {% elif article.url %}
                <a target="_blank" href="{{ article.url }}">
            {% else %}
                <span>
            {% endif %}
                    {% if article.level != "0" and config.pluginsConfig['theme-default'].showLevel %}
                        <b>{{ article.level }}.</b>
                    {% endif %}
                    {{ article.title }}
            {% if article.path  or article.url %}
                </a>
            {% else %}
                </span>
            {% endif %}

            {% if article.articles.length > 0 %}
            <ul class="articles">
                {{ articles(article.articles, file, config) }}
            </ul>
            {% endif %}
        </li>
    {% endfor %}

How could I fix it? My mock data should be right, 'case other templates could run without error. Thanks.

ykmnkmi commented 2 years ago

Can you write full stack trace. For recursive loop use {% for article in _articles recursive %} and loop(article.articles).

My stack trace after running your example without getPageByPath function and filters:

Tried calling: call
#0      Object.noSuchMethod (dart:core-patch/object_patch.dart:38:5)
#1      Context.call (package:jinja/src/context.dart:27:27)
...
#5      Binary.resolve (package:jinja/src/nodes/expressions.dart:1143:56) < article...
#7      If.accept (package:jinja/src/nodes/statements.dart:113:20) < {% if article....
#13     For.accept (package:jinja/src/nodes/statements.dart:70:20) < {% for article...
#18     Include.accept (package:jinja/src/nodes/statements.dart:236:20) < {% includ...
#23     With.accept (package:jinja/src/nodes/statements.dart:167:20) < {% with _art...

There is one if statement with binary expression and one calling: {% if article.path and getPageByPath(article.path) %}.

lindeer commented 2 years ago

Can you write full stack trace. For recursive loop use {% for article in _articles recursive %} and loop(article.articles).

My stack trace after running your example without getPageByPath function and filters:

Tried calling: call
#0      Object.noSuchMethod (dart:core-patch/object_patch.dart:38:5)
#1      Context.call (package:jinja/src/context.dart:27:27)
...
#5      Binary.resolve (package:jinja/src/nodes/expressions.dart:1143:56) < article...
#7      If.accept (package:jinja/src/nodes/statements.dart:113:20) < {% if article....
#13     For.accept (package:jinja/src/nodes/statements.dart:70:20) < {% for article...
#18     Include.accept (package:jinja/src/nodes/statements.dart:236:20) < {% includ...
#23     With.accept (package:jinja/src/nodes/statements.dart:167:20) < {% with _art...

There is one if statement with binary expression and one calling: {% if article.path and getPageByPath(article.path) %}.

Thanks @ykmnkmi ,it almost works! changed to {% for article in _articles recursive %} and loop(article.articles), another exception was thrown:

Unhandled exception:
type 'String' is not a subtype of type 'int' of 'index'
#0      Environment.getAttribute (package:jinja/src/environment.dart:297:20)
#1      Attribute.resolve (package:jinja/src/nodes/expressions.dart:852:32)
#2      Compare.resolve (package:jinja/src/nodes/expressions.dart:1002:22)
#3      StringSinkRenderer.visitIf (package:jinja/src/renderer.dart:544:27)
#4      If.accept (package:jinja/src/nodes/statements.dart:113:20)
...
#10     For.accept (package:jinja/src/nodes/statements.dart:70:20)
#15     Include.accept (package:jinja/src/nodes/statements.dart:236:20)
#18     Output.accept (package:jinja/src/nodes.dart:55:20)
#20     With.accept (package:jinja/src/nodes/statements.dart:167:20)

I found the error occured when rendering mock data part.articles.articles on template {% if article.articles.length > 0 %}(aaa.html:20), trying to call return fieldGetter(object, field);(environment.dart:291), and context is object=[], field=length, type=String, so 'length' is actually a built-in method, but code run directly as object's attribute return object[field]; (environment.dart:293). I changed code locally as below:

    } on NoSuchMethodError {
      if (field == 'length') {
        return object.length;
      }
      return object[field];
    }

it works. It only help me locate the issue, but I have no idea what is correct fix.

Another question, when I append another article item to deepest children, error still occur.

{
      'file': {
      },
      'part': {
        'articles': [
          {
            'path': '/user/to/file',
            'anchor': "#nice",
            'url': 'https://www.github.com',
            'level': 0,
            'title': 'That is Why You Go Away!',
            'articles': [
              {
                'path': '/user/to/file2',
                'anchor': "#good",
                'url': 'https://www.reddit.com',
                'level': 1,
                'title': 'Title in depth 2',
                'articles': [
                ],
              },
            ],
          },
        ],
      }
    }

and

Unhandled exception:
NoSuchMethodError: Closure call with mismatched arguments: function 'LoopContext.call'
Receiver: Closure: (Object?) => String from Function 'call':.
Tried calling: LoopContext.call(Instance(length:1) of '_GrowableList', _LinkedHashMap len:0, _LinkedHashMap len:4)
Found: LoopContext.call(Object?) => String
#0      Object.noSuchMethod (dart:core-patch/object_patch.dart:38:5)
#1      _objectNoSuchMethod (dart:core-patch/object_patch.dart:85:9)
#2      Function._apply (dart:core-patch/function_patch.dart:11:73)
#3      Function.apply (dart:core-patch/function_patch.dart:34:12)
#4      Context.call (package:jinja/src/context.dart:29:21)
#5      Call.resolve.<anonymous closure> (package:jinja/src/nodes/expressions.dart:614:21)
#6      Callable.apply (package:jinja/src/nodes/expressions.dart:498:20)
#7      Call.resolve (package:jinja/src/nodes/expressions.dart:613:12)
#14     If.accept (package:jinja/src/nodes/statements.dart:113:20)
#20     For.accept (package:jinja/src/nodes/statements.dart:70:20)
ykmnkmi commented 2 years ago

try this {% if article.articles %} here is my working test code:

import 'package:jinja/jinja.dart';

void main() {
  const raw = '''
{% with _articles=part.articles, file=file, config=config %}
  {% include "aaa.html" %}
{% endwith %}''';
  final tpl = Environment(
    leftStripBlocks: true,
    trimBlocks: true,
    loader: MapLoader({
      'aaa.html': '''
{% for article in _articles recursive %}
  <li class="chapter{{ ' active' if article.path == file.path and not article.anchor }}" data-level="{{ article.level }}" {{ 'data-path="' ~ article.path ~ '"' if article.path }}>
    {% if article.path %}
      <a href="{{ article.path ~ article.anchor }}">
    {% elif article.url %}
      <a target="_blank" href="{{ article.url }}">
    {% else %}
      <span>
    {% endif %}
    {% if article.level != "0" %}
      <b>{{ article.level }}.</b>
    {% endif %}
      {{ article.title }}
    {% if article.path or article.url %}
      </a>
    {% else %}
      </span>
    {% endif %}
    {% if article.articles %}
      <ul class="articles">
        {{ loop(article.articles) }}
      </ul>
    {% endif %}
  </li>
{% endfor %}''',
    }),
  ).fromString(raw);
  final r = tpl.render(<String, dynamic>{
    'file': {},
    'part': {
      'articles': [
        {
          'path': '/user/to/file',
          'anchor': '#nice',
          'url': 'https://www.github.com',
          'level': 0,
          'title': 'That is Why You Go Away!',
          'articles': [
            {
              'path': '/user/to/file2',
              'anchor': '#good',
              'url': 'https://www.reddit.com',
              'level': 1,
              'title': 'Title in depth 2',
              'articles': [],
            },
          ],
        },
      ],
    }
  });
  print(r);
}
lindeer commented 2 years ago

thanks @ykmnkmi, when I changed {{ loop(article.articles, file, config) }} to {{ loop(article.articles) }}, everything is OK :+1: By the way, actually I was going to use 'macro' to implement this small function, but currently 'macro' is not available, so could I ask when this feature would be done~ :smile:

ykmnkmi commented 2 years ago

Of course this is case for macro. Maybe in this version.