UNPKG

8.1 kBJavaScriptView Raw
1'use strict';
2
3const object = {};
4const hasOwnProperty = object.hasOwnProperty;
5const forOwn = (object, callback) => {
6 for (const key in object) {
7 if (hasOwnProperty.call(object, key)) {
8 callback(key, object[key]);
9 }
10 }
11};
12
13const extend = (destination, source) => {
14 if (!source) {
15 return destination;
16 }
17 forOwn(source, (key, value) => {
18 destination[key] = value;
19 });
20 return destination;
21};
22
23const forEach = (array, callback) => {
24 const length = array.length;
25 let index = -1;
26 while (++index < length) {
27 callback(array[index]);
28 }
29};
30
31const fourHexEscape = (hex) => {
32 return '\\u' + ('0000' + hex).slice(-4);
33}
34
35const hexadecimal = (code, lowercase) => {
36 let hexadecimal = code.toString(16);
37 if (lowercase) return hexadecimal;
38 return hexadecimal.toUpperCase();
39};
40
41const toString = object.toString;
42const isArray = Array.isArray;
43const isBuffer = (value) => {
44 return typeof Buffer === 'function' && Buffer.isBuffer(value);
45};
46const isObject = (value) => {
47 // This is a very simple check, but it’s good enough for what we need.
48 return toString.call(value) == '[object Object]';
49};
50const isString = (value) => {
51 return typeof value == 'string' ||
52 toString.call(value) == '[object String]';
53};
54const isNumber = (value) => {
55 return typeof value == 'number' ||
56 toString.call(value) == '[object Number]';
57};
58const isFunction = (value) => {
59 return typeof value == 'function';
60};
61const isMap = (value) => {
62 return toString.call(value) == '[object Map]';
63};
64const isSet = (value) => {
65 return toString.call(value) == '[object Set]';
66};
67
68/*--------------------------------------------------------------------------*/
69
70// https://mathiasbynens.be/notes/javascript-escapes#single
71const singleEscapes = {
72 '\\': '\\\\',
73 '\b': '\\b',
74 '\f': '\\f',
75 '\n': '\\n',
76 '\r': '\\r',
77 '\t': '\\t'
78 // `\v` is omitted intentionally, because in IE < 9, '\v' == 'v'.
79 // '\v': '\\x0B'
80};
81const regexSingleEscape = /[\\\b\f\n\r\t]/;
82
83const regexDigit = /[0-9]/;
84const regexWhitespace = /[\xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]/;
85
86const escapeEverythingRegex = /([\uD800-\uDBFF][\uDC00-\uDFFF])|([\uD800-\uDFFF])|(['"`])|[^]/g;
87const escapeNonAsciiRegex = /([\uD800-\uDBFF][\uDC00-\uDFFF])|([\uD800-\uDFFF])|(['"`])|[^ !#-&\(-\[\]-_a-~]/g;
88
89const jsesc = (argument, options) => {
90 const increaseIndentation = () => {
91 oldIndent = indent;
92 ++options.indentLevel;
93 indent = options.indent.repeat(options.indentLevel)
94 };
95 // Handle options
96 const defaults = {
97 'escapeEverything': false,
98 'minimal': false,
99 'isScriptContext': false,
100 'quotes': 'single',
101 'wrap': false,
102 'es6': false,
103 'json': false,
104 'compact': true,
105 'lowercaseHex': false,
106 'numbers': 'decimal',
107 'indent': '\t',
108 'indentLevel': 0,
109 '__inline1__': false,
110 '__inline2__': false
111 };
112 const json = options && options.json;
113 if (json) {
114 defaults.quotes = 'double';
115 defaults.wrap = true;
116 }
117 options = extend(defaults, options);
118 if (
119 options.quotes != 'single' &&
120 options.quotes != 'double' &&
121 options.quotes != 'backtick'
122 ) {
123 options.quotes = 'single';
124 }
125 const quote = options.quotes == 'double' ?
126 '"' :
127 (options.quotes == 'backtick' ?
128 '`' :
129 '\''
130 );
131 const compact = options.compact;
132 const lowercaseHex = options.lowercaseHex;
133 let indent = options.indent.repeat(options.indentLevel);
134 let oldIndent = '';
135 const inline1 = options.__inline1__;
136 const inline2 = options.__inline2__;
137 const newLine = compact ? '' : '\n';
138 let result;
139 let isEmpty = true;
140 const useBinNumbers = options.numbers == 'binary';
141 const useOctNumbers = options.numbers == 'octal';
142 const useDecNumbers = options.numbers == 'decimal';
143 const useHexNumbers = options.numbers == 'hexadecimal';
144
145 if (json && argument && isFunction(argument.toJSON)) {
146 argument = argument.toJSON();
147 }
148
149 if (!isString(argument)) {
150 if (isMap(argument)) {
151 if (argument.size == 0) {
152 return 'new Map()';
153 }
154 if (!compact) {
155 options.__inline1__ = true;
156 options.__inline2__ = false;
157 }
158 return 'new Map(' + jsesc(Array.from(argument), options) + ')';
159 }
160 if (isSet(argument)) {
161 if (argument.size == 0) {
162 return 'new Set()';
163 }
164 return 'new Set(' + jsesc(Array.from(argument), options) + ')';
165 }
166 if (isBuffer(argument)) {
167 if (argument.length == 0) {
168 return 'Buffer.from([])';
169 }
170 return 'Buffer.from(' + jsesc(Array.from(argument), options) + ')';
171 }
172 if (isArray(argument)) {
173 result = [];
174 options.wrap = true;
175 if (inline1) {
176 options.__inline1__ = false;
177 options.__inline2__ = true;
178 }
179 if (!inline2) {
180 increaseIndentation();
181 }
182 forEach(argument, (value) => {
183 isEmpty = false;
184 if (inline2) {
185 options.__inline2__ = false;
186 }
187 result.push(
188 (compact || inline2 ? '' : indent) +
189 jsesc(value, options)
190 );
191 });
192 if (isEmpty) {
193 return '[]';
194 }
195 if (inline2) {
196 return '[' + result.join(', ') + ']';
197 }
198 return '[' + newLine + result.join(',' + newLine) + newLine +
199 (compact ? '' : oldIndent) + ']';
200 } else if (isNumber(argument)) {
201 if (json) {
202 // Some number values (e.g. `Infinity`) cannot be represented in JSON.
203 return JSON.stringify(argument);
204 }
205 if (useDecNumbers) {
206 return String(argument);
207 }
208 if (useHexNumbers) {
209 let hexadecimal = argument.toString(16);
210 if (!lowercaseHex) {
211 hexadecimal = hexadecimal.toUpperCase();
212 }
213 return '0x' + hexadecimal;
214 }
215 if (useBinNumbers) {
216 return '0b' + argument.toString(2);
217 }
218 if (useOctNumbers) {
219 return '0o' + argument.toString(8);
220 }
221 } else if (!isObject(argument)) {
222 if (json) {
223 // For some values (e.g. `undefined`, `function` objects),
224 // `JSON.stringify(value)` returns `undefined` (which isn’t valid
225 // JSON) instead of `'null'`.
226 return JSON.stringify(argument) || 'null';
227 }
228 return String(argument);
229 } else { // it’s an object
230 result = [];
231 options.wrap = true;
232 increaseIndentation();
233 forOwn(argument, (key, value) => {
234 isEmpty = false;
235 result.push(
236 (compact ? '' : indent) +
237 jsesc(key, options) + ':' +
238 (compact ? '' : ' ') +
239 jsesc(value, options)
240 );
241 });
242 if (isEmpty) {
243 return '{}';
244 }
245 return '{' + newLine + result.join(',' + newLine) + newLine +
246 (compact ? '' : oldIndent) + '}';
247 }
248 }
249
250 const regex = options.escapeEverything ? escapeEverythingRegex : escapeNonAsciiRegex;
251 result = argument.replace(regex, (char, pair, lone, quoteChar, index, string) => {
252 if (pair) {
253 if (options.minimal) return pair;
254 const first = pair.charCodeAt(0);
255 const second = pair.charCodeAt(1);
256 if (options.es6) {
257 // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
258 const codePoint = (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
259 const hex = hexadecimal(codePoint, lowercaseHex);
260 return '\\u{' + hex + '}';
261 }
262 return fourHexEscape(hexadecimal(first, lowercaseHex)) + fourHexEscape(hexadecimal(second, lowercaseHex));
263 }
264
265 if (lone) {
266 return fourHexEscape(hexadecimal(lone.charCodeAt(0), lowercaseHex));
267 }
268
269 if (
270 char == '\0' &&
271 !json &&
272 !regexDigit.test(string.charAt(index + 1))
273 ) {
274 return '\\0';
275 }
276
277 if (quoteChar) {
278 if (quoteChar == quote || options.escapeEverything) {
279 return '\\' + quoteChar;
280 }
281 return quoteChar;
282 }
283
284 if (regexSingleEscape.test(char)) {
285 // no need for a `hasOwnProperty` check here
286 return singleEscapes[char];
287 }
288
289 if (options.minimal && !regexWhitespace.test(char)) {
290 return char;
291 }
292
293 const hex = hexadecimal(char.charCodeAt(0), lowercaseHex);
294 if (json || hex.length > 2) {
295 return fourHexEscape(hex);
296 }
297
298 return '\\x' + ('00' + hex).slice(-2);
299 });
300
301 if (quote == '`') {
302 result = result.replace(/\$\{/g, '\\${');
303 }
304 if (options.isScriptContext) {
305 // https://mathiasbynens.be/notes/etago
306 result = result
307 .replace(/<\/(script|style)/gi, '<\\/$1')
308 .replace(/<!--/g, json ? '\\u003C!--' : '\\x3C!--');
309 }
310 if (options.wrap) {
311 result = quote + result + quote;
312 }
313 return result;
314};
315
316jsesc.version = '3.0.2';
317
318module.exports = jsesc;