/** * @fileoverview HTML special characters should be escaped. * @author Patrick Hayes */ 'use strict'; const docsUrl = require('../util/docsUrl'); const jsxUtil = require('../util/jsx'); // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ // 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 = [{ char: '>', alternatives: ['>'] }, { char: '"', alternatives: ['"', '“', '"', '”'] }, { char: '\'', alternatives: [''', '‘', ''', '’'] }, { char: '}', alternatives: ['}'] }]; module.exports = { meta: { docs: { description: 'Detect unescaped HTML entities, which might represent malformed tags', category: 'Possible Errors', recommended: true, url: docsUrl('no-unescaped-entities') }, messages: { unescapedEntity: 'HTML entity, `{{entity}}` , must be escaped.', unescapedEntityAlts: '`{{entity}}` can be escaped with {{alts}}.' }, schema: [{ type: 'object', properties: { forbid: { type: 'array', items: { oneOf: [{ type: 'string' }, { type: 'object', properties: { char: { type: 'string' }, alternatives: { type: 'array', uniqueItems: true, items: { type: 'string' } } } }] } } }, additionalProperties: false }] }, create(context) { function reportInvalidEntity(node) { const configuration = context.options[0] || {}; const entities = configuration.forbid || DEFAULTS; // HTML entites are already escaped in node.value (as well as node.raw), // so pull the raw text from context.getSourceCode() for (let i = node.loc.start.line; i <= node.loc.end.line; i++) { let rawLine = context.getSourceCode().lines[i - 1]; let start = 0; let end = rawLine.length; if (i === node.loc.start.line) { start = node.loc.start.column; } if (i === node.loc.end.line) { end = node.loc.end.column; } rawLine = rawLine.substring(start, end); for (let j = 0; j < entities.length; j++) { for (let index = 0; index < rawLine.length; index++) { const c = rawLine[index]; if (typeof entities[j] === 'string') { if (c === entities[j]) { context.report({ node, loc: {line: i, column: start + index}, messageId: 'unescapedEntity', data: { entity: entities[j] } }); } } else if (c === entities[j].char) { context.report({ node, loc: {line: i, column: start + index}, messageId: 'unescapedEntityAlts', data: { entity: entities[j].char, alts: entities[j].alternatives.map((alt) => `\`${alt}\``).join(', ') } }); } } } } } return { 'Literal, JSXText'(node) { if (jsxUtil.isJSX(node.parent)) { reportInvalidEntity(node); } } }; } };