Skip to content

Commit

Permalink
Fixes mathiasbynens#47 enable escaping to extended ascii
Browse files Browse the repository at this point in the history
  • Loading branch information
regevbr committed Dec 28, 2018
1 parent 5169a48 commit 0e6325e
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 25 deletions.
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,15 @@ jsesc('`Lorem` ipsum "dolor" sit \'amet\' etc.', {
// → `\\\`Lorem\\\` ipsum "dolor" sit 'amet' etc.`
```

If you want to disable escaping quotes, set the `quotes` option to `false`.

```js
jsesc('`Lorem` ipsum "dolor" sit \'amet\' etc.', {
'quotes': false
});
// → '`Lorem` ipsum "dolor" sit \'amet\' etc.'
```

This setting also affects the output for arrays and objects:

```js
Expand Down Expand Up @@ -212,6 +221,19 @@ jsesc('foo\u2029bar\nbaz©qux𝌆flops', {
// → 'foo\\u2029bar\\nbaz©qux𝌆flops'
```

#### `extended`

The `extended` option takes a boolean value (`true` or `false`), and defaults to `false` (disabled). When enabled, extended ASCII characters are not escaped:

* 0x80 - 0xFE

```js
jsesc('habitación𝌆', {
'extended': true
});
// → 'habitación\\uD834\\uDF06'
```

#### `isScriptContext`

The `isScriptContext` option takes a boolean value (`true` or `false`), and defaults to `false` (disabled). When enabled, occurrences of [`</script` and `</style`](https://mathiasbynens.be/notes/etago) in the output are escaped as `<\/script` and `<\/style`, and [`<!--`](https://mathiasbynens.be/notes/etago#comment-8) is escaped as `\x3C!--` (or `\u003C!--` when the `json` option is enabled). This setting is useful when jsesc’s output ends up as part of a `<script>` or `<style>` element in an HTML document.
Expand Down
11 changes: 10 additions & 1 deletion bin/jsesc
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#!/usr/bin/env node
(function() {

var fs = require('fs');
var stringEscape = require('../jsesc.js');
var strings = process.argv.splice(2);
var stdin = process.stdin;
Expand All @@ -22,9 +21,11 @@
log([
'\nUsage:\n',
'\tjsesc [string]',
'\tjsesc [-k | --skip-quotes] [string]',
'\tjsesc [-s | --single-quotes] [string]',
'\tjsesc [-d | --double-quotes] [string]',
'\tjsesc [-w | --wrap] [string]',
'\tjsesc [-x | --extended] [string]',
'\tjsesc [-e | --escape-everything] [string]',
'\tjsesc [-t | --escape-etago] [string]',
'\tjsesc [-6 | --es6] [string]',
Expand All @@ -51,6 +52,10 @@

strings.forEach(function(string) {
// Process options
if (/^(?:-k|--skip-quotes)$/.test(string)) {
options.quotes = false;
return;
}
if (/^(?:-s|--single-quotes)$/.test(string)) {
options.quotes = 'single';
return;
Expand All @@ -63,6 +68,10 @@
options.wrap = true;
return;
}
if (/^(?:-x|--extended)$/.test(string)) {
options.extended = true;
return;
}
if (/^(?:-e|--escape-everything)$/.test(string)) {
options.escapeEverything = true;
return;
Expand Down
29 changes: 21 additions & 8 deletions jsesc.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ const regexSingleEscape = /["'\\\b\f\n\r\t]/;

const regexDigit = /[0-9]/;
const regexWhitelist = /[ !#-&\(-\[\]-_a-~]/;
const regexWhitelistExtended = /[ !#-&\(-\[\]-_a-~\x80-\xFE]/;

const jsesc = (argument, options) => {
const increaseIndentation = () => {
Expand All @@ -83,6 +84,7 @@ const jsesc = (argument, options) => {
const defaults = {
'escapeEverything': false,
'minimal': false,
'extended': false,
'isScriptContext': false,
'quotes': 'single',
'wrap': false,
Expand All @@ -104,17 +106,19 @@ const jsesc = (argument, options) => {
options = extend(defaults, options);
if (
options.quotes != 'single' &&
options.quotes != 'backtick' &&
options.quotes != 'double' &&
options.quotes != 'backtick'
options.quotes != false
) {
options.quotes = 'single';
}
const quote = options.quotes == 'double' ?
const quote = options.quotes == false ? undefined :
(options.quotes == 'double' ?
'"' :
(options.quotes == 'backtick' ?
'`' :
'\''
);
));
const compact = options.compact;
const lowercaseHex = options.lowercaseHex;
let indent = options.indent.repeat(options.indentLevel);
Expand Down Expand Up @@ -262,11 +266,20 @@ const jsesc = (argument, options) => {
}
}
if (!options.escapeEverything) {
if (regexWhitelist.test(character)) {
// It’s a printable ASCII character that is not `"`, `'` or `\`,
// so don’t escape it.
result += character;
continue;
if (!options.extended) {
if (regexWhitelist.test(character)) {
// It’s a printable ASCII character that is not `"`, `'` or `\`,
// so don’t escape it.
result += character;
continue;
}
} else {
if (regexWhitelistExtended.test(character)) {
// It’s a printable extended ASCII character that is not `"`, `'` or `\`,
// so don’t escape it.
result += character;
continue;
}
}
if (character == '"') {
result += quote == character ? '\\"' : character;
Expand Down
21 changes: 14 additions & 7 deletions src/data.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
'use strict';

const regenerate = require('regenerate');
const fs = require('fs');

const set = regenerate()
.addRange(0x20, 0x7E) // printable ASCII symbols
.remove('"') // not `"`
.remove('\'') // not `'`
.remove('\\') // not `\`
.remove('`'); // not '`'
function createAsciiSet() {
return regenerate()
.addRange(0x20, 0x7E) // printable ASCII symbols
.remove('"') // not `"`
.remove('\'') // not `'`
.remove('\\') // not `\`
.remove('`'); // not '`'
}

const set = createAsciiSet();

const extendedSet = createAsciiSet()
.addRange(0x80, 0xFE); // printable extended ASCII symbols

module.exports = {
'whitelist': set.toString(),
'whitelistExtended': extendedSet.toString(),
'version': require('../package.json').version
};
29 changes: 21 additions & 8 deletions src/jsesc.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ const regexSingleEscape = /["'\\\b\f\n\r\t]/;

const regexDigit = /[0-9]/;
const regexWhitelist = /<%= whitelist %>/;
const regexWhitelistExtended = /<%= whitelistExtended %>/;

const jsesc = (argument, options) => {
const increaseIndentation = () => {
Expand All @@ -83,6 +84,7 @@ const jsesc = (argument, options) => {
const defaults = {
'escapeEverything': false,
'minimal': false,
'extended': false,
'isScriptContext': false,
'quotes': 'single',
'wrap': false,
Expand All @@ -104,17 +106,19 @@ const jsesc = (argument, options) => {
options = extend(defaults, options);
if (
options.quotes != 'single' &&
options.quotes != 'backtick' &&
options.quotes != 'double' &&
options.quotes != 'backtick'
options.quotes != false
) {
options.quotes = 'single';
}
const quote = options.quotes == 'double' ?
const quote = options.quotes == false ? undefined :
(options.quotes == 'double' ?
'"' :
(options.quotes == 'backtick' ?
'`' :
'\''
);
));
const compact = options.compact;
const lowercaseHex = options.lowercaseHex;
let indent = options.indent.repeat(options.indentLevel);
Expand Down Expand Up @@ -262,11 +266,20 @@ const jsesc = (argument, options) => {
}
}
if (!options.escapeEverything) {
if (regexWhitelist.test(character)) {
// It’s a printable ASCII character that is not `"`, `'` or `\`,
// so don’t escape it.
result += character;
continue;
if (!options.extended) {
if (regexWhitelist.test(character)) {
// It’s a printable ASCII character that is not `"`, `'` or `\`,
// so don’t escape it.
result += character;
continue;
}
} else {
if (regexWhitelistExtended.test(character)) {
// It’s a printable extended ASCII character that is not `"`, `'` or `\`,
// so don’t escape it.
result += character;
continue;
}
}
if (character == '"') {
result += quote == character ? '\\"' : character;
Expand Down
51 changes: 50 additions & 1 deletion tests/tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,55 @@ describe('common usage', function() {
'\\x001',
'`\\0` followed by `1`'
);
assert.equal(
jsesc('\0\x31', {extended: true}),
'\\x001',
'`\\0` followed by `1` extended mode'
);
assert.equal(
jsesc('\0\x38'),
'\\x008',
'`\\0` followed by `8`'
);
assert.equal(
jsesc('\0\x38', {extended: true}),
'\\x008',
'`\\0` followed by `8` extended mode'
);
assert.equal(
jsesc('\0\x39'),
'\\x009',
'`\\0` followed by `9`'
);
assert.equal(
jsesc('\0\x39', {extended: true}),
'\\x009',
'`\\0` followed by `9` extended mode'
);
assert.equal(
jsesc('\0a'),
'\\0a',
'`\\0` followed by `a`'
);
assert.equal(
jsesc('\0a', {extended: true}),
'\\0a',
'`\\0` followed by `a` extended mode'
);
assert.equal(
jsesc('foo"bar\'baz', {
'quotes': 'LOLWAT' // invalid setting
}),
'foo"bar\\\'baz',
'Invalid `quotes` setting'
);
assert.equal(
jsesc('foo"bar\'baz', {
'quotes': false
}),
'foo"bar\'baz',
'disabled `quotes` setting'
);
assert.equal(
jsesc('foo${1+1} `bar`', {
'quotes': 'backtick'
Expand Down Expand Up @@ -81,6 +108,18 @@ describe('common usage', function() {
'\\\\\\\\x00',
'`\\\\\\\\x00` shouldn’t be changed to `\\\\\\\\0`'
);
assert.equal(
jsesc('habitación𝌆', {
'extended': true
}),
'habitación\\uD834\\uDF06',
'extended mode do not escape extended ASCII chaarchters'
);
assert.equal(
jsesc('habitación𝌆'),
'habitaci\\xF3n\\uD834\\uDF06',
'escape extended ASCII chaarchters'
);
assert.equal(
jsesc('lolwat"foo\'bar', {
'escapeEverything': true
Expand Down Expand Up @@ -570,12 +609,22 @@ describe('advanced tests', function() {
eval('\'' + jsesc(allSymbols) + '\'') == allSymbols,
'All Unicode symbols, space-separated, default quote type (single quotes)'
);
assert.ok(
eval('\'' + jsesc(allSymbols) + '\'', {extended: true}) == allSymbols,
'All Unicode symbols, space-separated, default quote type (single quotes) extended mode'
);
assert.ok(
eval('\'' + jsesc(allSymbols, {
'quotes': 'single'
}) + '\'') == allSymbols,
'All Unicode symbols, space-separated, single quotes'
);
assert.ok(
eval('\'' + jsesc(allSymbols, {
'quotes': false
}) + '\'') == allSymbols,
'All Unicode symbols, space-separated, disabled quotes'
);
assert.ok(
eval('`' + jsesc(allSymbols, {
'quotes': 'backtick'
Expand Down Expand Up @@ -635,5 +684,5 @@ describe('advanced tests', function() {
'[\n\tnull,\n\tnull,\n\tnull,\n\tnull,\n\tnull,\n\t0,\n\t0,\n\t0,\n\t0,\n\t0,\n\t0,\n\tnull,\n\t"str",\n\tnull,\n\tnull,\n\ttrue,\n\ttrue,\n\tfalse,\n\tfalse,\n\t{\n\t\t"foo": 42,\n\t\t"hah": [\n\t\t\t1,\n\t\t\t2,\n\t\t\t3,\n\t\t\t{\n\t\t\t\t"foo": 42\n\t\t\t}\n\t\t]\n\t}\n]',
'Escaping a non-flat array with all kinds of values, with `json: true, compact: false`'
);
}).timeout(25000);
}).timeout(35000);
});

0 comments on commit 0e6325e

Please sign in to comment.