@rhinostone/swig 2.0.0-alpha.1 → 2.0.0-alpha.3
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/.changes/unreleased/carve-dateformatter.yaml +4 -0
- package/.changes/unreleased/swig-twig-date-filter.yaml +4 -0
- package/.changes/v2.0.0-alpha.2.md +4 -0
- package/HISTORY.md +5 -0
- package/ROADMAP.md +6 -2
- package/dist/swig.js +4133 -5133
- package/dist/swig.min.js +80 -7
- package/dist/swig.min.js.map +1 -1
- package/lib/dateformatter.js +11 -198
- package/lib/filters.js +3 -4
- package/lib/lexer.js +1 -1
- package/lib/loaders/filesystem.js +3 -5
- package/lib/loaders/index.js +3 -5
- package/lib/loaders/memory.js +4 -6
- package/lib/parser.js +16 -15
- package/lib/swig.js +3 -3
- package/lib/tags/autoescape.js +24 -1
- package/lib/tags/block.js +4 -1
- package/lib/tags/elseif.js +12 -1
- package/lib/tags/extends.js +8 -0
- package/lib/tags/filter.js +68 -9
- package/lib/tags/for.js +34 -28
- package/lib/tags/if.js +43 -4
- package/lib/tags/import.js +23 -3
- package/lib/tags/include.js +69 -9
- package/lib/tags/macro.js +23 -15
- package/lib/tags/parent.js +9 -2
- package/lib/tags/raw.js +12 -1
- package/lib/tags/set.js +58 -3
- package/lib/utils.js +6 -8
- package/package.json +4 -2
package/lib/tags/macro.js
CHANGED
|
@@ -16,27 +16,18 @@
|
|
|
16
16
|
*
|
|
17
17
|
* @param {...arguments} arguments User-defined arguments.
|
|
18
18
|
*/
|
|
19
|
+
var ir = require('@rhinostone/swig-core/lib/ir');
|
|
20
|
+
|
|
19
21
|
exports.compile = function (compiler, args, content, parents, options, blockName) {
|
|
20
22
|
var fnName = args.shift();
|
|
21
|
-
|
|
22
|
-
return '_ctx.' + fnName + ' = function (' + args.join('') + ') {\n' +
|
|
23
|
-
' var _output = "",\n' +
|
|
24
|
-
' __ctx = _utils.extend({}, _ctx);\n' +
|
|
25
|
-
' _utils.each(_ctx, function (v, k) {\n' +
|
|
26
|
-
' if (["' + args.join('","') + '"].indexOf(k) !== -1) { delete _ctx[k]; }\n' +
|
|
27
|
-
' });\n' +
|
|
28
|
-
compiler(content, parents, options, blockName) + '\n' +
|
|
29
|
-
' _ctx = _utils.extend(_ctx, __ctx);\n' +
|
|
30
|
-
' return _output;\n' +
|
|
31
|
-
'};\n' +
|
|
32
|
-
'_ctx.' + fnName + '.safe = true;\n';
|
|
23
|
+
return ir.macro(fnName, args, [ir.legacyJS(compiler(content, parents, options, blockName))]);
|
|
33
24
|
};
|
|
34
25
|
|
|
35
26
|
// CVE-2023-25345: prototype-chain properties that must not be used as macro
|
|
36
27
|
// names. The macro tag assigns the compiled function to _ctx, so dangerous
|
|
37
28
|
// names would pollute the prototype chain. Shared constant in
|
|
38
29
|
// @rhinostone/swig-core — see .claude/security.md.
|
|
39
|
-
var _dangerousProps = require('
|
|
30
|
+
var _dangerousProps = require('@rhinostone/swig-core/lib/security').dangerousProps;
|
|
40
31
|
|
|
41
32
|
exports.parse = function (str, line, parser, types) {
|
|
42
33
|
var name;
|
|
@@ -45,7 +36,24 @@ exports.parse = function (str, line, parser, types) {
|
|
|
45
36
|
if (token.match.indexOf('.') !== -1) {
|
|
46
37
|
throw new Error('Unexpected dot in macro argument "' + token.match + '" on line ' + line + '.');
|
|
47
38
|
}
|
|
48
|
-
|
|
39
|
+
if (!name) {
|
|
40
|
+
// No FUNCTION/FUNCTIONEMPTY token emitted (e.g. `{% macro foo %}` with
|
|
41
|
+
// no parens): this VAR is the macro name. CVE-2023-25345: block
|
|
42
|
+
// prototype-chain property names as macro names.
|
|
43
|
+
if (_dangerousProps.indexOf(token.match) !== -1) {
|
|
44
|
+
throw new Error('Unsafe macro name "' + token.match + '" is not allowed (CVE-2023-25345) on line ' + line + '.');
|
|
45
|
+
}
|
|
46
|
+
name = token.match;
|
|
47
|
+
this.out.push(name);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
// CVE-2023-25345: macros assign `_ctx.<paramName>` implicitly inside
|
|
51
|
+
// the IIFE scaffolding, so a prototype-chain property name as a
|
|
52
|
+
// parameter would pollute the prototype chain at invocation time.
|
|
53
|
+
if (_dangerousProps.indexOf(token.match) !== -1) {
|
|
54
|
+
throw new Error('Unsafe macro argument "' + token.match + '" is not allowed (CVE-2023-25345) on line ' + line + '.');
|
|
55
|
+
}
|
|
56
|
+
this.out.push({ name: token.match });
|
|
49
57
|
});
|
|
50
58
|
|
|
51
59
|
parser.on(types.FUNCTION, function (token) {
|
|
@@ -79,7 +87,7 @@ exports.parse = function (str, line, parser, types) {
|
|
|
79
87
|
});
|
|
80
88
|
|
|
81
89
|
parser.on(types.COMMA, function () {
|
|
82
|
-
return
|
|
90
|
+
return;
|
|
83
91
|
});
|
|
84
92
|
|
|
85
93
|
parser.on('*', function () {
|
package/lib/tags/parent.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
var ir = require('@rhinostone/swig-core/lib/ir');
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Inject the content from the parent template's block of the same name into the current block.
|
|
3
5
|
*
|
|
@@ -33,7 +35,12 @@ exports.compile = function (compiler, args, content, parents, options, blockName
|
|
|
33
35
|
// Silly JSLint "Strange Loop" requires return to be in a conditional
|
|
34
36
|
if (breaker && parentFile !== parent.name) {
|
|
35
37
|
block = parent.blocks[blockName];
|
|
36
|
-
|
|
38
|
+
// Phase 2: the block tag now returns `IRBlock { body: [...] }`.
|
|
39
|
+
// Splice the matched parent block's body into an IRParent node;
|
|
40
|
+
// the backend emits body verbatim plus a trailing newline for
|
|
41
|
+
// byte-identity with the pre-Phase-2 JS-string emission shape.
|
|
42
|
+
var blockNode = block.compile(compiler, [blockName], block.content, parents.slice(i + 1), options);
|
|
43
|
+
return ir.parent((blockNode && blockNode.body) ? blockNode.body.concat([ir.legacyJS('\n')]) : [ir.legacyJS('\n')]);
|
|
37
44
|
}
|
|
38
45
|
}
|
|
39
46
|
};
|
|
@@ -48,4 +55,4 @@ exports.parse = function (str, line, parser, types, stack, opts) {
|
|
|
48
55
|
});
|
|
49
56
|
|
|
50
57
|
return true;
|
|
51
|
-
};
|
|
58
|
+
};
|
package/lib/tags/raw.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// Magic tag, hardcoded into parser
|
|
2
2
|
|
|
3
|
+
var ir = require('@rhinostone/swig-core/lib/ir');
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* Forces the content to not be auto-escaped. All swig instructions will be ignored and the content will be rendered exactly as it was given.
|
|
5
7
|
*
|
|
@@ -12,7 +14,16 @@
|
|
|
12
14
|
*
|
|
13
15
|
*/
|
|
14
16
|
exports.compile = function (compiler, args, content, parents, options, blockName) {
|
|
15
|
-
|
|
17
|
+
var nodes = [];
|
|
18
|
+
var i;
|
|
19
|
+
for (i = 0; i < content.length; i++) {
|
|
20
|
+
if (typeof content[i] === 'string') {
|
|
21
|
+
nodes.push(ir.raw(content[i]));
|
|
22
|
+
} else {
|
|
23
|
+
nodes.push(ir.legacyJS(compiler([content[i]], parents, options, blockName)));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return nodes;
|
|
16
27
|
};
|
|
17
28
|
exports.parse = function (str, line, parser) {
|
|
18
29
|
parser.on('*', function (token) {
|
package/lib/tags/set.js
CHANGED
|
@@ -31,13 +31,68 @@
|
|
|
31
31
|
* @param {literal} assignement Any valid JavaScript assignement. <code data-language="js">=, +=, *=, /=, -=</code>
|
|
32
32
|
* @param {*} value Valid variable output.
|
|
33
33
|
*/
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
var ir = require('@rhinostone/swig-core/lib/ir'),
|
|
35
|
+
_t = require('@rhinostone/swig-core/lib/tokentypes');
|
|
36
|
+
|
|
37
|
+
// Pure-dot LHS shape: `_ctx.foo` or `_ctx.foo.bar.baz`. Bracket-touched
|
|
38
|
+
// targets (`_ctx.foo["bar"]`, `_ctx.foo[bar]`, mixed dot+bracket) fail
|
|
39
|
+
// this match and stay on the transitional string fallback per Session
|
|
40
|
+
// 14b Commit 10's narrow scope — the bracket-lvalue contract is a
|
|
41
|
+
// cross-flavor design call and is deferred to a dedicated session.
|
|
42
|
+
var _pureDotTarget = /^_ctx\.([a-zA-Z_$][\w$]*)((?:\.[a-zA-Z_$][\w$]*)*)$/;
|
|
43
|
+
|
|
44
|
+
exports.compile = function (compiler, args, content, parents, options, blockName, token) {
|
|
45
|
+
// Target migrates to structured IRVarRef when the LHS is pure-dot;
|
|
46
|
+
// otherwise stays a transitional string fragment. Value prefers the
|
|
47
|
+
// lowered IRExpr carried on the token when lowerExpr produced one;
|
|
48
|
+
// otherwise fall back to the joined JS-source fragments the parse
|
|
49
|
+
// handler emitted.
|
|
50
|
+
var target = args[0];
|
|
51
|
+
var match = typeof target === 'string' && _pureDotTarget.exec(target);
|
|
52
|
+
if (match) {
|
|
53
|
+
var path = [match[1]];
|
|
54
|
+
if (match[2]) {
|
|
55
|
+
var rest = match[2].split('.');
|
|
56
|
+
for (var pi = 1; pi < rest.length; pi++) { path.push(rest[pi]); }
|
|
57
|
+
}
|
|
58
|
+
target = ir.varRef(path);
|
|
59
|
+
}
|
|
60
|
+
var value = (token && token.irExpr) ? token.irExpr : args.slice(2).join(' ');
|
|
61
|
+
return ir.set(target, args[1], value);
|
|
36
62
|
};
|
|
37
63
|
|
|
38
64
|
// CVE-2023-25345: prototype-chain properties that must not be assignable.
|
|
39
65
|
// Shared constant in @rhinostone/swig-core — see .claude/security.md.
|
|
40
|
-
var _dangerousProps = require('
|
|
66
|
+
var _dangerousProps = require('@rhinostone/swig-core/lib/security').dangerousProps;
|
|
67
|
+
|
|
68
|
+
exports.lowerExpr = function (parser, tokens) {
|
|
69
|
+
// The set tag's parse handler consumes LHS VAR / BRACKETOPEN / STRING /
|
|
70
|
+
// BRACKETCLOSE / DOTKEY / ASSIGNMENT tokens up to (and including) the
|
|
71
|
+
// assignment operator; lowerExpr only concerns itself with the RHS.
|
|
72
|
+
// Locate the first ASSIGNMENT, slice the tail, and hand it to parseExpr.
|
|
73
|
+
// Fall back (return undefined) if the tail contains FILTER / FILTEREMPTY
|
|
74
|
+
// (filter pipes are not part of the expression grammar yet — Session
|
|
75
|
+
// 14+ Output-site work) or a nested ASSIGNMENT (parseExpr does not
|
|
76
|
+
// lower assignments, and bracket-write-as-assignment on the RHS would
|
|
77
|
+
// misparse silently).
|
|
78
|
+
var assignIdx = -1, i;
|
|
79
|
+
for (i = 0; i < tokens.length; i++) {
|
|
80
|
+
if (tokens[i].type === _t.ASSIGNMENT) {
|
|
81
|
+
assignIdx = i;
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (assignIdx === -1) { return undefined; }
|
|
86
|
+
var rhsTokens = tokens.slice(assignIdx + 1);
|
|
87
|
+
for (i = 0; i < rhsTokens.length; i++) {
|
|
88
|
+
if (rhsTokens[i].type === _t.FILTER ||
|
|
89
|
+
rhsTokens[i].type === _t.FILTEREMPTY ||
|
|
90
|
+
rhsTokens[i].type === _t.ASSIGNMENT) {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return parser.parseExpr(rhsTokens);
|
|
95
|
+
};
|
|
41
96
|
|
|
42
97
|
exports.parse = function (str, line, parser, types) {
|
|
43
98
|
var nameSet = '',
|
package/lib/utils.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Phase 1 carve bridge — utilities moved to @rhinostone/swig-core.
|
|
3
3
|
*
|
|
4
|
-
* This shim re-routes the in-repo
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* bundle. Removed when the native frontend restructures into
|
|
10
|
-
* packages/swig/ (Phase 2). See .claude/architecture/multi-flavor-ir.md.
|
|
4
|
+
* This shim re-routes the in-repo consumer requires (`require('./utils')`
|
|
5
|
+
* from lib/, `require('../utils')` from lib/tags/ and lib/loaders/) so
|
|
6
|
+
* every call site keeps resolving without churn. Removed when the native
|
|
7
|
+
* frontend restructures into packages/swig/ (Phase 2). See
|
|
8
|
+
* .claude/architecture/multi-flavor-ir.md.
|
|
11
9
|
*/
|
|
12
10
|
|
|
13
|
-
module.exports = require('
|
|
11
|
+
module.exports = require('@rhinostone/swig-core/lib/utils');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rhinostone/swig",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.3",
|
|
4
4
|
"description": "A simple, powerful, and extendable templating engine for node.js and browsers, similar to Django, Jinja2, and Twig.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"template",
|
|
@@ -21,18 +21,20 @@
|
|
|
21
21
|
"Rhinostone <contact@gina.io>"
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
|
+
"@rhinostone/swig-core": "2.0.0-alpha.3",
|
|
24
25
|
"terser": "^5.46.1",
|
|
25
26
|
"yargs": "^17.7.2"
|
|
26
27
|
},
|
|
27
28
|
"devDependencies": {
|
|
28
29
|
"blanket": "~1.1",
|
|
29
|
-
"
|
|
30
|
+
"esbuild": "^0.28.0",
|
|
30
31
|
"eslint": "^8.57.1",
|
|
31
32
|
"expect.js": "~0.2",
|
|
32
33
|
"express": "~3",
|
|
33
34
|
"lodash": "~1.3.1",
|
|
34
35
|
"mocha": "1.12.0",
|
|
35
36
|
"mocha-phantomjs": "~3.1",
|
|
37
|
+
"path-browserify": "^1.0.1",
|
|
36
38
|
"phantomjs": "~1.9.1",
|
|
37
39
|
"travis-cov": "~0.2"
|
|
38
40
|
},
|