OK, I've tried to answer in the comment but it wasn't pleasant to read.
The issue is split in two major problems:
- counting raw chars
- counting ASCII length instead of UTF-16 one
I will answer to both issues with examples.
counting raw chars
The only way to consider '\n' string as two chars with one backslash and one n letter, is to use a function tag and a template literal.
const rawlength = tpl => tpl.raw.join('').length;
`a\nb`.length; // 3
rawlength`a\nb`; // 4
You can copy and paste above code and read the two different results. Bear in mind, not using parenthesis with rawlength is not a typo, but how template literals work.
Also bear in mind if you use a template literal like the following one
`a
b`
its length will still be 3 because there is indeed no backslash in there, so the \n char is considered one char as it should be.
In Python, that would be equivalent
len("""a
b""")
That' a 3.
Edit: the Python r in JavaScript
The equivalent of r in JavaScript would be:
const r = (t, ...v) => {
const result = [t.raw[0]];
const length = t.length;
for (let i = 1; i < length; i++)
result.push(v[i - 1], t.raw[i]);
return result.join('');
};
So that:
r`a\nb`
Would produce what you expect.
You can add the following trick around result.join('') to also have the length as ASCII/bytes instead.
counting ASCII length instead of UTF-16 one
This is an old trick to always count bytes:
unescape(encodeURIComponent('a“b')).length;
That's a 5, because encodeURIComponent would return an UTF-8 url friendly version of the text, and unescape will create a char per each %XX encountered.
In this case 'a“b' becomes a%E2%80%9Cb which is ab plus 3 url encoded chars.
\tit is compiled identically to a string with a literal tab character, for example.“is three bytes long but 1 character long.String.raw`a\nb`.length(same way like Python's r)