@rhinostone/swig-django 2.9.13 → 2.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -33,6 +33,7 @@ Renders real Django templates — everything below was cross-checked against Dja
33
33
  * **Variable resolution** — Django-faithful attribute / dictionary / index lookup. Callable attributes are auto-called (honoring `alters_data` / `do_not_call_in_templates`), numeric indexing works (`{{ list.0 }}`), and dicts iterate via `.keys` / `.values` / `.items`.
34
34
  * **Filter arguments** — Django colon syntax (`{{ value|date:"Y-m-d" }}`).
35
35
  * **Filters** — 42 built-ins (`add`, `addslashes`, `capfirst`, `center`, `cut`, `date`, `default`, `default_if_none`, `divisibleby`, `escape` / `e`, `escapejs`, `filesizeformat`, `first`, `floatformat`, `force_escape`, `get_digit`, `iriencode`, `join`, `last`, `length`, `linebreaks`, `linebreaksbr`, `linenumbers`, `ljust`, `lower`, `make_list`, `phone2numeric`, `pluralize`, `rjust`, `safe`, `slice`, `slugify`, `striptags`, `time`, `title`, `truncatechars`, `truncatewords`, `upper`, `urlencode`, `wordcount`, `wordwrap`, `yesno`). See `lib/filters.js` for the full list.
36
+ * **Unknown names** — a filter name the instance does not hold is a compile error wherever it stands, as in Django. Register custom filters before the compile that uses them.
36
37
  * **Async** — `renderFile(path, locals, cb)` against a loader with `async === true`, or `compileFile` with `codegenMode: 'async'`; `renderFileAsync` / `compileFileAsync` still work but are deprecated.
37
38
 
38
39
  Explicitly unsupported (parse-time throw or absent)
package/lib/index.js CHANGED
@@ -32,7 +32,7 @@ exports.name = 'django';
32
32
  *
33
33
  * @type {String}
34
34
  */
35
- exports.version = "2.9.13";
35
+ exports.version = "2.10.0";
36
36
 
37
37
  /**
38
38
  * Expression-level parser — Pratt-style recursive descent that consumes
package/lib/lexer.js CHANGED
@@ -243,9 +243,9 @@ exports.types = TYPES;
243
243
  /**
244
244
  * Match the next token at the start of `str`.
245
245
  *
246
- * Throws via utils.throwError when no rule matches. The throw is opaque (no
247
- * line / file info); the Django frontend's onCompileError callback attaches
248
- * filename + line per the swig-core / frontend seam rule.
246
+ * Throws when no rule matches. This module does not know the line or the
247
+ * file, so the error is flagged `unplaced` and the Django frontend's
248
+ * `exports.parse` names them.
249
249
  *
250
250
  * @param {string} str Input slice starting at the unconsumed offset.
251
251
  * @return {LexerToken} Matched token.
@@ -277,7 +277,9 @@ function reader(str) {
277
277
  });
278
278
 
279
279
  if (!matched) {
280
- utils.throwError('Unexpected token "' + str.charAt(0) + '" in Django expression');
280
+ var err = new Error('Unexpected token "' + str.charAt(0) + '" in Django expression.');
281
+ err.unplaced = true;
282
+ throw err;
281
283
  }
282
284
 
283
285
  return matched;
package/lib/parser.js CHANGED
@@ -5,6 +5,10 @@ var ir = require('@rhinostone/swig-core/lib/ir'),
5
5
  var lexer = require('./lexer');
6
6
  var _t = require('./tokentypes');
7
7
 
8
+ // A catalog entry is read as an own property: a filter can be registered under
9
+ // the name hasOwnProperty, and a name like toString is not a filter.
10
+ var hasOwn = Object.prototype.hasOwnProperty;
11
+
8
12
  /**
9
13
  * Make a string safe for embedding into a regular expression.
10
14
  * @param {string} str
@@ -154,9 +158,13 @@ exports.missingAsEmpty = function (expr) {
154
158
  * @param {object} [_posOut] Optional out-param; final cursor stored on
155
159
  * `_posOut.pos` to let callers detect partial
156
160
  * consumption.
161
+ * @param {object} [_scope] Set by `exports.parse`: the `line` and the
162
+ * `filename` an error names, and the instance's
163
+ * `extensions`, which make a filter name the
164
+ * catalog does not hold an error.
157
165
  * @return {object} IRExpr tree.
158
166
  */
159
- exports.parseExpr = function (tokens, filters, _posOut) {
167
+ exports.parseExpr = function (tokens, filters, _posOut, _scope) {
160
168
  var pos = 0;
161
169
  filters = filters || {};
162
170
 
@@ -173,7 +181,7 @@ exports.parseExpr = function (tokens, filters, _posOut) {
173
181
  return t;
174
182
  }
175
183
  function bail(msg) {
176
- utils.throwError(msg);
184
+ utils.throwError(msg, _scope && _scope.line, _scope && _scope.filename);
177
185
  }
178
186
 
179
187
  function guardSegment(segment) {
@@ -406,7 +414,7 @@ exports.parseExpr = function (tokens, filters, _posOut) {
406
414
  } else if (tok.type === _t.FILTEREMPTY || tok.type === _t.FILTER) {
407
415
  consume();
408
416
  fname = tok.match;
409
- if (filters.hasOwnProperty(fname) && typeof filters[fname] !== 'function') {
417
+ if (hasOwn.call(filters, fname) ? typeof filters[fname] !== 'function' : (_scope && _scope.extensions)) {
410
418
  bail('Invalid filter "' + fname + '"');
411
419
  }
412
420
  // Django colon-filter: an optional single positional argument
@@ -590,6 +598,25 @@ exports.parse = function (swig, source, opts, tags, filters) {
590
598
  tags = tags || {};
591
599
  filters = filters || {};
592
600
 
601
+ // Where parseExpr is working, so that an expression error can name its line
602
+ // and file, and whether it has a catalog to check a filter name against (only
603
+ // an engine instance has). A tag gets a handle on this module whose parseExpr
604
+ // works in the same scope and on this parse's filter catalog.
605
+ var scope = { line: 0, filename: opts.filename, extensions: swig && swig.extensions };
606
+ var exprParser = Object.create(exports);
607
+ exprParser.parseExpr = function (tokens, _filters, posOut) {
608
+ return exports.parseExpr(tokens, filters, posOut, scope);
609
+ };
610
+
611
+ // The lexer does not know where it is working, so the place of its error is
612
+ // named here. An error from a nested parse has its place already.
613
+ function throwPlaced(err) {
614
+ if (err.unplaced) {
615
+ utils.throwError(err.message.slice(0, -1), scope.line, scope.filename);
616
+ }
617
+ throw err;
618
+ }
619
+
593
620
  var varControls = opts.varControls || ['{{', '}}'];
594
621
  var tagControls = opts.tagControls || ['{%', '%}'];
595
622
  var cmtControls = opts.cmtControls || ['{#', '#}'];
@@ -642,16 +669,22 @@ exports.parse = function (swig, source, opts, tags, filters) {
642
669
  * @private
643
670
  */
644
671
  function parseVariable(str, _line) {
645
- var lexed = lexer.read(utils.strip(str));
672
+ scope.line = _line;
673
+ var lexed;
674
+ try {
675
+ lexed = lexer.read(utils.strip(str));
676
+ } catch (e) {
677
+ throwPlaced(e);
678
+ }
646
679
  var sawSafe = false;
647
680
  utils.each(lexed, function (tok) {
648
681
  if (tok.type === _t.FILTEREMPTY || tok.type === _t.FILTER) {
649
- if (filters.hasOwnProperty(tok.match) && filters[tok.match].safe === true) {
682
+ if (hasOwn.call(filters, tok.match) && filters[tok.match].safe === true) {
650
683
  sawSafe = true;
651
684
  }
652
685
  }
653
686
  });
654
- var expr = exports.missingAsEmpty(exports.parseExpr(lexed, filters));
687
+ var expr = exports.missingAsEmpty(exports.parseExpr(lexed, filters, undefined, scope));
655
688
  var tail;
656
689
  var esc = escapeStack[escapeStack.length - 1];
657
690
  if (esc && !sawSafe) {
@@ -734,7 +767,13 @@ exports.parse = function (swig, source, opts, tags, filters) {
734
767
  escape: escapeStack[escapeStack.length - 1]
735
768
  };
736
769
 
737
- var ok = tag.parse(tagArgs, _line, exports, _t, stack, opts, swig, token);
770
+ scope.line = _line;
771
+ var ok;
772
+ try {
773
+ ok = tag.parse(tagArgs, _line, exprParser, _t, stack, opts, swig, token);
774
+ } catch (e) {
775
+ throwPlaced(e);
776
+ }
738
777
  if (!ok) {
739
778
  utils.throwError('Unexpected tag "' + tagName + '"', _line, opts.filename);
740
779
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rhinostone/swig-django",
3
- "version": "2.9.13",
3
+ "version": "2.10.0",
4
4
  "description": "Django-syntax frontend for the @rhinostone/swig-core template engine. Part of the @rhinostone/swig multi-flavor family.",
5
5
  "keywords": [
6
6
  "template",
@@ -22,7 +22,7 @@
22
22
  "node": ">=12"
23
23
  },
24
24
  "peerDependencies": {
25
- "@rhinostone/swig-core": "2.9.13"
25
+ "@rhinostone/swig-core": "2.10.0"
26
26
  },
27
27
  "publishConfig": {
28
28
  "access": "public"