I. Возможности
Когда я прочитал на MDN: «The static
String.raw()
method is a tag function of template literals, similar to the r
prefix in Python or the @
prefix in C# for string literals» — я здорово обрадовался, потому что мне часто не хватало в JavaScript чего-то вроде одиночных кавычек в Perl.Я сразу придумал несколько видов использования и стал активно применять их в скриптах.
1. Определение путей к файлам Windows без двойного экранирования.
const r = String.raw;
const test_module = require(r`e:\DOC\prg\js\node\-lib\test.js`);
2. Определение путей к ключам реестра Windows.
const r = String.raw;
const Winreg = require('winreg');
const regKey = new Winreg({
hive: Winreg.HKCU,
key: r`\Software\MPC-HC\MPC-HC\Settings`
});
3. Создание сложных регулярных выражений из составных литералов.
См. пример кода в одной из недавних статей.
II. Ограничения
Однако со временем я стал натыкаться на неожиданные ограничения. Написав об одном из них в багтрекер V8, я получил отрезвляющее объяснение. Оказывается, хоть
String.raw
и выдаёт строку без интерпретации экранированных литералов, на стадии парсинга кода анализатор всё равно требует, чтобы литералы соответствовали правилам. Из этого следуют неочевидные ограничения для упомянутых случаев применения. 1. После обратной косой черты не могут следовать символы
x
или u
без соответствующих hex-последовательностей.Следующий код выдаёт ошибку
Uncaught SyntaxError: Invalid hexadecimal escape sequence
:console.log(String.raw`с:\x.js`);
Следующий код выдаёт ошибку
Uncaught SyntaxError: Invalid Unicode escape sequence
:console.log(String.raw`с:\u.js`);
Притом что правильные сочетания всё равно сериализируются неинтерпретированными:
console.log(String.raw`\x61`);
//\x61
console.log(String.raw`\u0061`);
//\u0061
Простых способов решить проблему я не нашёл:
console.log(String.raw`с:\\x.js`);
// с:\\x.js
console.log(String.raw`с:\\x78.js`);
// с:\\x78.js
console.log(String.raw`с:\${'x'}.js`);
// с:\${'x'}.js
console.log(String.raw`с:\\${'x'}.js`);
// с:\\x.js
console.log(String.raw`с:\\u.js`);
// с:\\u.js
console.log(String.raw`с:\\x75.js`);
// с:\\x75.js
console.log(String.raw`с:\${'u'}.js`);
// с:\${'u'}.js
console.log(String.raw`с:\\${'u'}.js`);
// с:\\u.js
Работающие решения настолько сложны, что проще будет вернуться к использованию обычных кавычек с двойным экранированием:
console.log(String.raw`с:${'\\'}x.js`);
// с:\x.js
console.log(String.raw`с:${'\\'}u.js`);
// с:\u.js
Остальные совпадения с экранированными литералами трудностей не вызывают и отображаются буквально:
console.log(String.raw`с:\ab\0 cd`);
console.log(String.raw`с:\ab\' cd`);
console.log(String.raw`с:\ab\" cd`);
console.log(String.raw`с:\ab\\ cd`);
console.log(String.raw`с:\ab\n cd`);
console.log(String.raw`с:\ab\r cd`);
console.log(String.raw`с:\ab\v cd`);
console.log(String.raw`с:\ab\t cd`);
console.log(String.raw`с:\ab\b cd`);
console.log(String.raw`с:\ab\f cd`);
2. Нет простого способа включить в строку сам символ
`
.Этот символ иногда заменяет английский апостроф, а также используется в некоторых системах транслитерации.
Ожидаемая ошибка
Uncaught SyntaxError: missing ) after argument list
:console.log(String.raw`с:\John`s.js`);
Неработающие решения:
console.log(String.raw`с:\John\`s.js`);
// с:\John\`s.js
console.log(String.raw`с:\John\x60s.js`);
// с:\John\x60s.js
Неоправданная сложность:
console.log(String.raw`с:\John${'`'}s.js`);
// с:\John`s.js
3. Нет простого способа создать строку с обратной косой чертой в самом конце.
Ожидаемая ошибка
Uncaught SyntaxError: Unterminated template literal
:console.log(String.raw`с:\`);
Неработающие решения:
console.log(String.raw`с:\\`);
// с:\console.log(String.raw`с:\x5c`);
// с:\x5c
Неоправданная сложность:
console.log(String.raw`с:${'\\'}`);
// с:
III. Сравнение с другими языками
Соответствующие средства упомянутых в начале заметки языков работают, как и ожидалось, что можно проверить, например, здесь.
C#:
Perl:
Python:
Если вы нашли другие интересные способы применения
String.raw
, столкнулись с другими неожиданными ограничениями или придумали элегантные способы их обхода, поделитесь, пожалуйста.Комментарии (21)
lihtenshtein
16.05.2016 17:41+2` встречается в MySQL для экранирования имен. Это печально.
vmb
18.05.2016 18:16Я мало знаю про MySQL, но, кажется, там можно обойтись без
String.raw
(см. ответ в соответствующей библиотеке).
Holix
16.05.2016 17:41+2Во всех, или почти во всех, примерах проблемы из-за неудачно выбранного в окнах разделителе файловых путей или ключей реестра. Может попробуете использовать более родной разделитель — '/'?
vmb
16.05.2016 17:50Это вы мне так вежливо предлагаете ОС сменить?) На самом деле, некоторые библиотеки мирятся с использованием простой косой черты вместо обратной для определения путей в Windows, но ведь не все, не совсем универсально выходит. Но повод вспомнить о пресловутом неудачном выборе разделителя создателями Windows опять всплывает, это правда.
vintage
16.05.2016 22:55+1По секрету скажу, что windows понимает оба варианта. А если какая-либо библиотека велосипедит свою валидацию — пишите багрепорты.
CaptainFlint
16.05.2016 23:12+1По ещё большему секрету скажу, что Windows перестаёт понимать прямой слэш, если используется префикс \\?\, необходимый для работы с длинными путями и «запрещёнными» именами.
tehqux
17.05.2016 22:02+1Обратный слеш нужен только в префиксе. Дальше все нормально воспринимается как минимум со времен XP. Оба варианта ниже работоспособны:
\\./C:/boot.ini \\?/C:/boot.ini
Единственная причина использовать обратные слеши в Windows — это то, что всякие встроенные команды типа
dir
воспринимают прямой слеш, как начало параметра.CaptainFlint
18.05.2016 01:45+1Эти варианты работоспособны ровно в той степени, в какой работоспособны обычные пути без префиксов. Длинный путь, превышающий MAX_PATH, с любым из этих двух префиксов выдаст ошибку. Чтобы обработать длинный путь, необходимо задавать именно \\?\ (с обратными слэшами) и обратные слэши в пути.
Вот объяснение (немного про другие префиксы, но схема должна быть та же):
http://stackoverflow.com/a/25099634/2182162
vmb
16.05.2016 23:37Почему же тогда в руководствах советуют воздерживаться от использования конкретных разделителей для кросс-платформенности? Можно было бы во всех случаях использовать простую косую черту.
vintage
17.05.2016 07:40Да, можно и нужно использовать слеш. А если в руководстве даются рекомендации без пояснений, значит автор и сам не в курсе что и зачем.
@CaptainFlint а если в редких кейсах и требуется обратный слеш, то именно в этих местах делать
.replace( /\//g , '\\' )
, а не рассовывать по всем библиотекам костыли вида"Hello" + require('os').EOL + "World"
.CaptainFlint
17.05.2016 12:32+1Не уверен, что это удобно. В большом проекте замучаешься отслеживать все такие места. Одно пропустишь — потом фиг отловишь. Уж проще обёртки использовать, которые всё приводят к единому системному виду.
Кроме того, в проекте может использоваться сторонняя библиотека, которая все пути автоматом приводит к префиксному виду (скажем, автор решил, что надо поддерживать длинные пути). И в эту библиотеку надо передавать обратный слэш, а во всех остальных местах программы использовать прямой? Такой подход только усугубит путаницу. В общем, всё зависит от конкретной ситуации. Просто надо иметь в виду эту особенность работы в Windows.vintage
18.05.2016 09:28+1Именно в обёртках и надо писать все эти OS-специфичные штуки типа
.replace( /\//g , '\\' )
, чтобы абстрагировать пользовательский код от конкретной OS.
k12th
16.05.2016 23:47+1Недавно наткнулся на такую библиотеку: https://github.com/declandewet/common-tags. Может, что-нибудь в этом роде позволит отказаться от вещей типа
${'\\'}
.vmb
16.05.2016 23:55+2Возможно, хотя именно эта библиотека имеет несколько более крупную цель (на днях о ней вышла статья на авторитетном ресурсе, а не так давно я тоже пробовал соорудить что-то совсем простое в этом направлении). Но описанная проблема слишком низкоуровневая. Единственное, что мне приходит в голову, это сделать вариант
String.raw
с добавлениемtrim()
, чтобы можно было использовать конечную косую черту с пробелом.k12th
17.05.2016 00:00+1Да, именно там я ее и увидел.
Я к тому, что у таких трюков большой потенциал. Я-то вряд ли порох изобрету, а вас или автора библиотеки, может, вдохновит на что-то интересное.vmb
17.05.2016 00:04+1Ну, у меня, честно говорят, нет ни опыта, ни знаний писать универсальные библиотеки, я просто программист-любитель. Но будем надеяться, что и правда кто-то всё упростит)
EviGL
17.05.2016 00:06+1Глядя с точки зрения человека, потенциально поддерживающего данный код, лучше не используйте это вообще. Так и представляю, меняется путь до файла, код перестаёт парситься посреди строки и счастливой отладки. Лучше бы он был со всем экранированием: некрасиво, но предсказуемо.
Ну и ограничения, очевидно, не обойти, раз они на уровне парсера. Можно перейти на что-то транслируемое в JS, где есть нужная функциональность, но это, опять же, дикий оверкилл. Зато не хак.vmb
17.05.2016 00:09+1Ну, я бы вообще не рисковал зашивать пути во что-то, отдаваемое в общее пользование. Я это скорее для личных локальных утилит применяю. Но вот с регулярками вроде бы безопасно использовать и в общих проектах.
CaptainFlint
Одинарные кавычки в Перле — это не полное отключение эскейпинга.
напечатает c:\x.txtvmb
Ну, это как раз, выходит, очень продуманная особенность. С нею можно написать
и получить
CaptainFlint
Разумеется, просто я имел в виду, что одинарные кавычки не означают стопроцентно литеральный текст.