From 76b6742309d260a45d25855f07ccdeba05c0da41 Mon Sep 17 00:00:00 2001 From: Steve Mao Date: Sat, 13 Oct 2018 21:53:51 +1100 Subject: [PATCH] [New] `no-unescaped-entities`: more friendly error message; add config to adjust --- docs/rules/no-unescaped-entities.md | 10 ++++ lib/rules/no-unescaped-entities.js | 44 +++++++++++++++-- tests/lib/rules/no-unescaped-entities.js | 60 ++++++++++++++++++++---- 3 files changed, 100 insertions(+), 14 deletions(-) diff --git a/docs/rules/no-unescaped-entities.md b/docs/rules/no-unescaped-entities.md index 34088d4395..c9fd93660b 100644 --- a/docs/rules/no-unescaped-entities.md +++ b/docs/rules/no-unescaped-entities.md @@ -83,4 +83,14 @@ Overwrite the default forbidden entities array `['>', '"', '\'', '}']` with your ```js "react/no-unescaped-entities": ["error", {"forbid": [">", "}"]}], +// or +"react/no-unescaped-entities": ["error", {"forbid": [{ + char: ">", + alternatives: ['>'] +}, { + char: "}", + alternatives: ['}'] +}]}], ``` + +Where `char` is a special character and `alternatives` is the correct escapes. diff --git a/lib/rules/no-unescaped-entities.js b/lib/rules/no-unescaped-entities.js index 1fa88d1f5e..3a2f263c2d 100644 --- a/lib/rules/no-unescaped-entities.js +++ b/lib/rules/no-unescaped-entities.js @@ -14,7 +14,19 @@ const jsxUtil = require('../util/jsx'); // NOTE: '<' and '{' are also problematic characters, but they do not need // to be included here because it is a syntax error when these characters are // included accidentally. -const DEFAULTS = ['>', '"', '\'', '}']; +const DEFAULTS = [{ + char: '>', + alternatives: ['>'] +}, { + char: '"', + alternatives: ['"', '“', '"', '”'] +}, { + char: '\'', + alternatives: [''', '‘', ''', '’'] +}, { + char: '}', + alternatives: ['}'] +}]; module.exports = { meta: { @@ -30,7 +42,23 @@ module.exports = { forbid: { type: 'array', items: { - type: 'string' + oneOf: [{ + type: 'string' + }, { + type: 'object', + properties: { + char: { + type: 'string' + }, + alternatives: { + type: 'array', + uniqueItems: true, + items: { + type: 'string' + } + } + } + }] } } }, @@ -59,10 +87,18 @@ module.exports = { for (let j = 0; j < entities.length; j++) { for (let index = 0; index < rawLine.length; index++) { const c = rawLine[index]; - if (c === entities[j]) { + if (typeof entities[j] === 'string') { + if (c === entities[j]) { + context.report({ + loc: {line: i, column: start + index}, + message: `HTML entity, \`${entities[j]}\` , must be escaped.`, + node: node + }); + } + } else if (c === entities[j].char) { context.report({ loc: {line: i, column: start + index}, - message: 'HTML entities must be escaped.', + message: `\`${entities[j].char}\` can be escaped with ${entities[j].alternatives.map(alt => `\`${alt}\``).join(', ')}.`, node: node }); } diff --git a/tests/lib/rules/no-unescaped-entities.js b/tests/lib/rules/no-unescaped-entities.js index aff01dca5b..92cefa62e7 100644 --- a/tests/lib/rules/no-unescaped-entities.js +++ b/tests/lib/rules/no-unescaped-entities.js @@ -112,7 +112,7 @@ ruleTester.run('no-unescaped-entities', rule, { } }); `, - errors: [{message: 'HTML entities must be escaped.'}] + errors: [{message: '`>` can be escaped with `>`.'}] }, { code: ` var Hello = createReactClass({ @@ -122,7 +122,7 @@ ruleTester.run('no-unescaped-entities', rule, { }); `, parser: 'babel-eslint', - errors: [{message: 'HTML entities must be escaped.'}] + errors: [{message: '`>` can be escaped with `>`.'}] }, { code: ` var Hello = createReactClass({ @@ -133,7 +133,7 @@ ruleTester.run('no-unescaped-entities', rule, { } }); `, - errors: [{message: 'HTML entities must be escaped.'}] + errors: [{message: '`>` can be escaped with `>`.'}] }, { code: ` var Hello = createReactClass({ @@ -145,7 +145,7 @@ ruleTester.run('no-unescaped-entities', rule, { }); `, parser: 'babel-eslint', - errors: [{message: 'HTML entities must be escaped.'}] + errors: [{message: '`>` can be escaped with `>`.'}] }, { code: ` var Hello = createReactClass({ @@ -154,7 +154,7 @@ ruleTester.run('no-unescaped-entities', rule, { } }); `, - errors: [{message: 'HTML entities must be escaped.'}] + errors: [{message: '`\'` can be escaped with `'`, `‘`, `'`, `’`.'}] }, { code: ` var Hello = createReactClass({ @@ -164,9 +164,9 @@ ruleTester.run('no-unescaped-entities', rule, { }); `, errors: [ - {message: 'HTML entities must be escaped.'}, - {message: 'HTML entities must be escaped.'}, - {message: 'HTML entities must be escaped.'} + {message: '`\'` can be escaped with `'`, `‘`, `'`, `’`.'}, + {message: '`>` can be escaped with `>`.'}, + {message: '`>` can be escaped with `>`.'} ] }, { code: ` @@ -176,7 +176,7 @@ ruleTester.run('no-unescaped-entities', rule, { } }); `, - errors: [{message: 'HTML entities must be escaped.'}] + errors: [{message: '`}` can be escaped with `}`.'}] }, { code: ` var Hello = createReactClass({ @@ -186,7 +186,47 @@ ruleTester.run('no-unescaped-entities', rule, { }); `, parser: 'babel-eslint', - errors: [{message: 'HTML entities must be escaped.'}] + errors: [{message: '`}` can be escaped with `}`.'}] + }, { + code: ` + var Hello = createReactClass({ + render: function() { + return <>foo & bar; + } + }); + `, + parser: 'babel-eslint', + errors: [{message: 'HTML entity, \`&\` , must be escaped.'}], + options: [{ + forbid: ['&'] + }] + }, { + code: ` + var Hello = createReactClass({ + render: function() { + return foo & bar; + } + }); + `, + errors: [{message: 'HTML entity, \`&\` , must be escaped.'}], + options: [{ + forbid: ['&'] + }] + }, { + code: ` + var Hello = createReactClass({ + render: function() { + return foo & bar; + } + }); + `, + errors: [{message: '`&` can be escaped with `&`.'}], + options: [{ + forbid: [{ + char: '&', + alternatives: ['&'] + }] + }] } ] });