Repository: sindresorhus/eslint-plugin-unicorn (JavaScript)

Issue:
`escape-case` fixer bug
```js
const foo = `\ud834${foo}\ud834${foo}\ud834`;
```

fixed to

```js
const foo = `\uD834{foo}\uD834{foo}\uD834`;
```

Should be

```js
const foo = `\uD834${foo}\uD834${foo}\uD834`;
//                 ^           ^  missing
```


Discussion:

Patch:
diff --git a/rules/escape-case.js b/rules/escape-case.js
index 0c7ef6cce5..0395058e04 100644
--- a/rules/escape-case.js
+++ b/rules/escape-case.js
@@ -5,6 +5,7 @@ const {
 } = require('regexpp');
 
 const getDocumentationUrl = require('./utils/get-documentation-url');
+const replaceTemplateElement = require('./utils/replace-template-element');
 
 const escapeWithLowercase = /(?<before>(?:^|[^\\])(?:\\\\)*)\\(?<data>x[\da-f]{2}|u[\da-f]{4}|u{[\da-f]+})/;
 const escapePatternWithLowercase = /(?<before>(?:^|[^\\])(?:\\\\)*)\\(?<data>x[\da-f]{2}|u[\da-f]{4}|u{[\da-f]+}|c[a-z])/;
@@ -113,20 +114,17 @@ const create = context => {
 			}
 		},
 		TemplateElement(node) {
-			if (typeof node.value.raw !== 'string') {
-				return;
-			}
-
 			const matches = node.value.raw.match(escapeWithLowercase);
 
 			if (matches && matches[2].slice(1).match(hasLowercaseCharacter)) {
-				// Move cursor inside the head and tail apostrophe
-				const start = node.range[0] + 1;
-				const end = node.range[1] - 1;
 				context.report({
 					node,
 					message,
-					fix: fixer => fixer.replaceTextRange([start, end], fix(node.value.raw, escapeWithLowercase))
+					fix: fixer => replaceTemplateElement(
+						fixer,
+						node,
+						fix(node.value.raw, escapeWithLowercase)
+					)
 				});
 			}
 		}
diff --git a/rules/no-hex-escape.js b/rules/no-hex-escape.js
index a436892fff..dc62abc7aa 100644
--- a/rules/no-hex-escape.js
+++ b/rules/no-hex-escape.js
@@ -1,5 +1,6 @@
 'use strict';
 const getDocumentationUrl = require('./utils/get-documentation-url');
+const replaceTemplateElement = require('./utils/replace-template-element');
 
 function checkEscape(context, node, value) {
 	const fixedValue = typeof value === 'string' ?
@@ -10,16 +11,10 @@ function checkEscape(context, node, value) {
 		context.report({
 			node,
 			message: 'Use Unicode escapes instead of hexadecimal escapes.',
-			fix: fixer => {
-				let {range: [start, end], type, tail} = node;
-
-				if (type === 'TemplateElement') {
-					start += 1;
-					end -= tail ? 1 : 2;
-				}
-
-				return fixer.replaceTextRange([start, end], fixedValue);
-			}
+			fix: fixer =>
+				node.type === 'TemplateElement' ?
+					replaceTemplateElement(fixer, node, fixedValue) :
+					fixer.replaceText(node, fixedValue)
 		});
 	}
 }
diff --git a/rules/utils/replace-template-element.js b/rules/utils/replace-template-element.js
new file mode 100644
index 0000000000..c2c454e6ec
--- /dev/null
+++ b/rules/utils/replace-template-element.js
@@ -0,0 +1,9 @@
+'use strict';
+
+module.exports = (fixer, node, replacement) => {
+	const {range: [start, end], tail} = node;
+	return fixer.replaceTextRange(
+		[start + 1, end - (tail ? 1 : 2)],
+		replacement
+	);
+};


Test patch:
diff --git a/test/escape-case.js b/test/escape-case.js
index 0619962c9e..e22289d982 100644
--- a/test/escape-case.js
+++ b/test/escape-case.js
@@ -103,6 +103,11 @@ ruleTester.run('escape-case', rule, {
 			errors,
 			output: 'const foo = `${"\uD834 foo"} \\uD834`;'
 		},
+		{
+			code: 'const foo = `\\ud834${foo}\\ud834${foo}\\ud834`;',
+			errors: Array.from({length: 3}, () => errors[0]),
+			output: 'const foo = `\\uD834${foo}\\uD834${foo}\\uD834`;'
+		},
 		{
 			code: 'const foo = "\\ud834foo";',
 			errors,