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)


  1. CaptainFlint
    16.05.2016 17:01
    +1

    Одинарные кавычки в Перле — это не полное отключение эскейпинга.

    print 'c:\\x.txt';
    
    напечатает c:\x.txt


    1. vmb
      16.05.2016 17:55

      Ну, это как раз, выходит, очень продуманная особенность. С нею можно написать


      print 'c:\\';

      и получить


      c:\


      1. CaptainFlint
        16.05.2016 19:00
        +1

        Разумеется, просто я имел в виду, что одинарные кавычки не означают стопроцентно литеральный текст.


  1. lihtenshtein
    16.05.2016 17:41
    +2

    ` встречается в MySQL для экранирования имен. Это печально.


    1. vmb
      18.05.2016 18:16

      Я мало знаю про MySQL, но, кажется, там можно обойтись без String.raw (см. ответ в соответствующей библиотеке).


  1. Holix
    16.05.2016 17:41
    +2

    Во всех, или почти во всех, примерах проблемы из-за неудачно выбранного в окнах разделителе файловых путей или ключей реестра. Может попробуете использовать более родной разделитель — '/'?


    1. vmb
      16.05.2016 17:50

      Это вы мне так вежливо предлагаете ОС сменить?) На самом деле, некоторые библиотеки мирятся с использованием простой косой черты вместо обратной для определения путей в Windows, но ведь не все, не совсем универсально выходит. Но повод вспомнить о пресловутом неудачном выборе разделителя создателями Windows опять всплывает, это правда.


      1. vintage
        16.05.2016 22:55
        +1

        По секрету скажу, что windows понимает оба варианта. А если какая-либо библиотека велосипедит свою валидацию — пишите багрепорты.


        1. CaptainFlint
          16.05.2016 23:12
          +1

          По ещё большему секрету скажу, что Windows перестаёт понимать прямой слэш, если используется префикс \\?\, необходимый для работы с длинными путями и «запрещёнными» именами.


          1. tehqux
            17.05.2016 22:02
            +1

            Обратный слеш нужен только в префиксе. Дальше все нормально воспринимается как минимум со времен XP. Оба варианта ниже работоспособны:


            \\./C:/boot.ini
            \\?/C:/boot.ini

            Единственная причина использовать обратные слеши в Windows — это то, что всякие встроенные команды типа dir воспринимают прямой слеш, как начало параметра.


            1. CaptainFlint
              18.05.2016 01:45
              +1

              Эти варианты работоспособны ровно в той степени, в какой работоспособны обычные пути без префиксов. Длинный путь, превышающий MAX_PATH, с любым из этих двух префиксов выдаст ошибку. Чтобы обработать длинный путь, необходимо задавать именно \\?\ (с обратными слэшами) и обратные слэши в пути.

              Вот объяснение (немного про другие префиксы, но схема должна быть та же):
              http://stackoverflow.com/a/25099634/2182162


        1. vmb
          16.05.2016 23:37

          Почему же тогда в руководствах советуют воздерживаться от использования конкретных разделителей для кросс-платформенности? Можно было бы во всех случаях использовать простую косую черту.


          1. vintage
            17.05.2016 07:40

            Да, можно и нужно использовать слеш. А если в руководстве даются рекомендации без пояснений, значит автор и сам не в курсе что и зачем.


            @CaptainFlint а если в редких кейсах и требуется обратный слеш, то именно в этих местах делать .replace( /\//g , '\\' ), а не рассовывать по всем библиотекам костыли вида "Hello" + require('os').EOL + "World".


            1. CaptainFlint
              17.05.2016 12:32
              +1

              Не уверен, что это удобно. В большом проекте замучаешься отслеживать все такие места. Одно пропустишь — потом фиг отловишь. Уж проще обёртки использовать, которые всё приводят к единому системному виду.

              Кроме того, в проекте может использоваться сторонняя библиотека, которая все пути автоматом приводит к префиксному виду (скажем, автор решил, что надо поддерживать длинные пути). И в эту библиотеку надо передавать обратный слэш, а во всех остальных местах программы использовать прямой? Такой подход только усугубит путаницу. В общем, всё зависит от конкретной ситуации. Просто надо иметь в виду эту особенность работы в Windows.


              1. vintage
                18.05.2016 09:28
                +1

                Именно в обёртках и надо писать все эти OS-специфичные штуки типа .replace( /\//g , '\\' ), чтобы абстрагировать пользовательский код от конкретной OS.


  1. k12th
    16.05.2016 23:47
    +1

    Недавно наткнулся на такую библиотеку: https://github.com/declandewet/common-tags. Может, что-нибудь в этом роде позволит отказаться от вещей типа ${'\\'}.


    1. vmb
      16.05.2016 23:55
      +2

      Возможно, хотя именно эта библиотека имеет несколько более крупную цель (на днях о ней вышла статья на авторитетном ресурсе, а не так давно я тоже пробовал соорудить что-то совсем простое в этом направлении). Но описанная проблема слишком низкоуровневая. Единственное, что мне приходит в голову, это сделать вариант String.raw с добавлением trim(), чтобы можно было использовать конечную косую черту с пробелом.


      1. k12th
        17.05.2016 00:00
        +1

        Да, именно там я ее и увидел.
        Я к тому, что у таких трюков большой потенциал. Я-то вряд ли порох изобрету, а вас или автора библиотеки, может, вдохновит на что-то интересное.


        1. vmb
          17.05.2016 00:04
          +1

          Ну, у меня, честно говорят, нет ни опыта, ни знаний писать универсальные библиотеки, я просто программист-любитель. Но будем надеяться, что и правда кто-то всё упростит)


  1. EviGL
    17.05.2016 00:06
    +1

    Глядя с точки зрения человека, потенциально поддерживающего данный код, лучше не используйте это вообще. Так и представляю, меняется путь до файла, код перестаёт парситься посреди строки и счастливой отладки. Лучше бы он был со всем экранированием: некрасиво, но предсказуемо.

    Ну и ограничения, очевидно, не обойти, раз они на уровне парсера. Можно перейти на что-то транслируемое в JS, где есть нужная функциональность, но это, опять же, дикий оверкилл. Зато не хак.


    1. vmb
      17.05.2016 00:09
      +1

      Ну, я бы вообще не рисковал зашивать пути во что-то, отдаваемое в общее пользование. Я это скорее для личных локальных утилит применяю. Но вот с регулярками вроде бы безопасно использовать и в общих проектах.