Skip to content

Commit 4f5fd05

Browse files
committedMar 11, 2023
Adds the (string) position of tokens to the token tree.
1 parent 8d69d0a commit 4f5fd05

File tree

3 files changed

+60
-18
lines changed

3 files changed

+60
-18
lines changed
 

‎src/twig.core.js

+29-11
Original file line numberDiff line numberDiff line change
@@ -325,9 +325,8 @@ module.exports = function (Twig) {
325325
*/
326326
Twig.tokenize = function (template) {
327327
const tokens = [];
328-
// An offset for reporting errors locations in the template.
329-
let errorOffset = 0;
330-
328+
// An offset for reporting errors locations and the position of the nodes in the template.
329+
let currentPosition = 0;
331330
// The start and type of the first token found in the template.
332331
let foundToken = null;
333332
// The end position of the matched token.
@@ -343,29 +342,41 @@ module.exports = function (Twig) {
343342
// No more tokens -> add the rest of the template as a raw-type token
344343
tokens.push({
345344
type: Twig.token.type.raw,
346-
value: template
345+
value: template,
346+
position: {
347+
start: currentPosition,
348+
end: currentPosition + foundToken.position
349+
}
347350
});
348351
template = '';
349352
} else {
350353
// Add a raw type token for anything before the start of the token
351354
if (foundToken.position > 0) {
352355
tokens.push({
353356
type: Twig.token.type.raw,
354-
value: template.slice(0, Math.max(0, foundToken.position))
357+
value: template.slice(0, Math.max(0, foundToken.position)),
358+
position: {
359+
start: currentPosition,
360+
end: currentPosition + Math.max(0, foundToken.position)
361+
}
355362
});
356363
}
357364

358365
template = template.slice(foundToken.position + foundToken.def.open.length);
359-
errorOffset += foundToken.position + foundToken.def.open.length;
366+
currentPosition += foundToken.position + foundToken.def.open.length;
360367

361368
// Find the end of the token
362-
end = Twig.token.findEnd(template, foundToken.def, errorOffset);
369+
end = Twig.token.findEnd(template, foundToken.def, currentPosition);
363370

364371
Twig.log.trace('Twig.tokenize: ', 'Token ends at ', end);
365372

366373
tokens.push({
367374
type: foundToken.def.type,
368-
value: template.slice(0, Math.max(0, end)).trim()
375+
value: template.slice(0, Math.max(0, end)).trim(),
376+
position: {
377+
start: currentPosition - foundToken.def.open.length,
378+
end: currentPosition + end + foundToken.def.close.length
379+
}
369380
});
370381

371382
if (template.slice(end + foundToken.def.close.length, end + foundToken.def.close.length + 1) === '\n') {
@@ -385,7 +396,7 @@ module.exports = function (Twig) {
385396
template = template.slice(end + foundToken.def.close.length);
386397

387398
// Increment the position in the template
388-
errorOffset += end + foundToken.def.close.length;
399+
currentPosition += end + foundToken.def.close.length;
389400
}
390401
}
391402

@@ -434,6 +445,7 @@ module.exports = function (Twig) {
434445
const compileLogic = function (token) {
435446
// Compile the logic token
436447
logicToken = Twig.logic.compile.call(self, token);
448+
logicToken.position = token.position;
437449

438450
type = logicToken.type;
439451
open = Twig.logic.handler[type].open;
@@ -458,8 +470,13 @@ module.exports = function (Twig) {
458470

459471
tokOutput = {
460472
type: Twig.token.type.logic,
461-
token: prevToken
473+
token: prevToken,
474+
position: {
475+
open: prevToken.position,
476+
close: token.position
477+
}
462478
};
479+
463480
if (stack.length > 0) {
464481
intermediateOutput.push(tokOutput);
465482
} else {
@@ -486,7 +503,8 @@ module.exports = function (Twig) {
486503
} else if (open !== undefined && open) {
487504
tokOutput = {
488505
type: Twig.token.type.logic,
489-
token: logicToken
506+
token: logicToken,
507+
position: logicToken.position
490508
};
491509
// Standalone token (like {% set ... %}
492510
if (stack.length > 0) {

‎src/twig.expression.js

+13-7
Original file line numberDiff line numberDiff line change
@@ -1118,11 +1118,12 @@ module.exports = function (Twig) {
11181118
/**
11191119
* Break an expression into tokens defined in Twig.expression.definitions.
11201120
*
1121-
* @param {string} expression The string to tokenize.
1121+
* @param {Object} rawToken The string to tokenize.
11221122
*
11231123
* @return {Array} An array of tokens.
11241124
*/
1125-
Twig.expression.tokenize = function (expression) {
1125+
Twig.expression.tokenize = function (rawToken) {
1126+
let expression = rawToken.value;
11261127
const tokens = [];
11271128
// Keep an offset of the location in the expression for error messages.
11281129
let expOffset = 0;
@@ -1170,11 +1171,17 @@ module.exports = function (Twig) {
11701171

11711172
invalidMatches = [];
11721173

1173-
tokens.push({
1174+
const token = {
11741175
type,
11751176
value: match[0],
11761177
match
1177-
});
1178+
};
1179+
1180+
if (rawToken.position) {
1181+
token.position = rawToken.position;
1182+
}
1183+
1184+
tokens.push(token);
11781185

11791186
matchFound = true;
11801187
next = tokenNext;
@@ -1240,15 +1247,14 @@ module.exports = function (Twig) {
12401247
* @return {Object} The compiled token.
12411248
*/
12421249
Twig.expression.compile = function (rawToken) {
1243-
const expression = rawToken.value;
12441250
// Tokenize expression
1245-
const tokens = Twig.expression.tokenize(expression);
1251+
const tokens = Twig.expression.tokenize(rawToken);
12461252
let token = null;
12471253
const output = [];
12481254
const stack = [];
12491255
let tokenTemplate = null;
12501256

1251-
Twig.log.trace('Twig.expression.compile: ', 'Compiling ', expression);
1257+
Twig.log.trace('Twig.expression.compile: ', 'Compiling ', rawToken.value);
12521258

12531259
// Push tokens into RPN stack using the Shunting-yard algorithm
12541260
// See http://en.wikipedia.org/wiki/Shunting_yard_algorithm

‎test/test.core.js

+18
Original file line numberDiff line numberDiff line change
@@ -508,4 +508,22 @@ describe('Twig.js Core ->', function () {
508508
}).should.throw(/Unable to find template file/);
509509
});
510510
});
511+
512+
describe('tokens should have the correct positions in template', () => {
513+
it('should show the correct token positions in a simple template', () => {
514+
const tokens = twig({data: '{{ unit }}'}).tokens;
515+
tokens[0].position.should.eql({start: 0, end: 10});
516+
});
517+
518+
it('should show the correct token positions in a advanced template', () => {
519+
const tokens = twig({data:'I want to {{ try }} a more {% if advanced | length > 3 %}{{ variable }}{% endif %} template {% set unit = 2 %}{# This is a comment #}{{ variable_after_comment }}'}).tokens;
520+
tokens[0].position.should.eql({start: 0, end: 10});
521+
tokens[1].position.should.eql({start: 10, end: 19});
522+
tokens[2].position.should.eql({start: 19, end: 27});
523+
tokens[3].position.should.eql({open: {start: 27, end: 57}, close: {start: 71, end: 82}});
524+
tokens[3].token.output[0].position.should.eql({start: 57, end: 71});
525+
tokens[5].position.should.eql({start: 92, end: 110});
526+
tokens[6].position.should.eql({start: 133, end: 161});
527+
});
528+
});
511529
});

0 commit comments

Comments
 (0)
Please sign in to comment.