UNPKG

eslint-plugin-ember

Version:
74 lines (69 loc) 2.7 kB
/** @type {import('eslint').Rule.RuleModule} */ module.exports = { meta: { type: 'problem', docs: { description: 'require rel="noopener noreferrer" on links with target="_blank"', category: 'Security', url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-link-rel-noopener.md', }, fixable: 'code', schema: [], messages: { missingRel: 'links with target="_blank" must have rel="noopener noreferrer"', }, originallyFrom: { name: 'ember-template-lint', rule: 'lib/rules/link-rel-noopener.js', docs: 'docs/rule/link-rel-noopener.md', tests: 'test/unit/rules/link-rel-noopener-test.js', }, }, create(context) { return { GlimmerElementNode(node) { if (node.tag !== 'a') { return; } const targetAttr = node.attributes?.find((a) => a.name === 'target'); if (!targetAttr?.value || targetAttr.value.type !== 'GlimmerTextNode') { return; } if (targetAttr.value.chars !== '_blank') { return; } const relAttr = node.attributes?.find((a) => a.name === 'rel'); const relValue = relAttr?.value?.type === 'GlimmerTextNode' ? relAttr.value.chars : ''; const hasNoopener = /(?:^|\s)noopener(?:\s|$)/.test(relValue); const hasNoreferrer = /(?:^|\s)noreferrer(?:\s|$)/.test(relValue); const hasProperRel = hasNoopener && hasNoreferrer; if (!hasProperRel) { context.report({ node: targetAttr, messageId: 'missingRel', fix(fixer) { if (relAttr && relAttr.value?.type === 'GlimmerTextNode') { // Strip existing noopener/noreferrer tokens, then re-add in canonical order const oldValue = relAttr.value.chars.trim().replaceAll(/\s+/g, ' '); const filtered = oldValue .split(' ') .filter((t) => t !== 'noopener' && t !== 'noreferrer') .join(' '); const newValue = `${filtered} noopener noreferrer`.trim(); return fixer.replaceText(relAttr.value, `"${newValue}"`); } // No rel attribute — insert one before the closing > const sourceCode = context.sourceCode; const openTag = sourceCode.getText(node).match(/^<a[^>]*/)[0]; const insertPos = node.range[0] + openTag.length; return fixer.insertTextBeforeRange( [insertPos, insertPos], ' rel="noopener noreferrer"' ); }, }); } }, }; }, };