diff --git a/templates.js b/templates.js index 6cdddea..fe94642 100644 --- a/templates.js +++ b/templates.js @@ -1,8 +1,8 @@ 'use strict'; -const TEMPLATE_REGEX = /(?:\\(u[a-f\d]{4}|x[a-f\d]{2}|.))|(?:\{(~)?(\w+(?:\([^)]*\))?(?:\.\w+(?:\([^)]*\))?)*)(?:[ \t]|(?=\r?\n)))|(\})|((?:.|[\r\n\f])+?)/gi; +const TEMPLATE_REGEX = /(?:\\(u(?:[a-f\d]{4}|\{[a-f\d]{1,6}\})|x[a-f\d]{2}|.))|(?:\{(~)?(\w+(?:\([^)]*\))?(?:\.\w+(?:\([^)]*\))?)*)(?:[ \t]|(?=\r?\n)))|(\})|((?:.|[\r\n\f])+?)/gi; const STYLE_REGEX = /(?:^|\.)(\w+)(?:\(([^)]*)\))?/g; const STRING_REGEX = /^(['"])((?:\\.|(?!\1)[^\\])*)\1$/; -const ESCAPE_REGEX = /\\(u[a-f\d]{4}|x[a-f\d]{2}|.)|([^\\])/gi; +const ESCAPE_REGEX = /\\(u(?:[a-f\d]{4}|\{[a-f\d]{1,6}\})|x[a-f\d]{2}|.)|([^\\])/gi; const ESCAPES = new Map([ ['n', '\n'], @@ -18,10 +18,17 @@ const ESCAPES = new Map([ ]); function unescape(c) { - if ((c[0] === 'u' && c.length === 5) || (c[0] === 'x' && c.length === 3)) { + const u = c[0] === 'u'; + const bracket = c[1] === '{'; + + if ((u && !bracket && c.length === 5) || (c[0] === 'x' && c.length === 3)) { return String.fromCharCode(parseInt(c.slice(1), 16)); } + if (u && bracket) { + return String.fromCodePoint(parseInt(c.slice(2, -1), 16)); + } + return ESCAPES.get(c) || c; } diff --git a/test/template-literal.js b/test/template-literal.js index 1f388c8..ae4a0bc 100644 --- a/test/template-literal.js +++ b/test/template-literal.js @@ -168,3 +168,10 @@ test('should properly handle undefined template interpolated values', t => { t.is(instance`hello ${undefined}`, 'hello undefined'); t.is(instance`hello ${null}`, 'hello null'); }); + +test('should allow bracketed Unicode escapes', t => { + const instance = new chalk.Instance({level: 3}); + t.is(instance`\u{AB}`, '\u{AB}'); + t.is(instance`This is a {bold \u{AB681}} test`, 'This is a \u001B[1m\u{AB681}\u001B[22m test'); + t.is(instance`This is a {bold \u{10FFFF}} test`, 'This is a \u001B[1m\u{10FFFF}\u001B[22m test'); +});