diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 07f1739018..6901c8f5ad 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -449,6 +449,17 @@ export default class Element extends Node { const href_attribute = attribute_map.get('href') || attribute_map.get('xlink:href'); const id_attribute = attribute_map.get('id'); const name_attribute = attribute_map.get('name'); + const target_attribute = attribute_map.get('target'); + const rel_attribute = attribute_map.get('rel'); + + if (target_attribute && target_attribute.get_static_value() === '_blank' && + (!rel_attribute || !/noopener|noreferrer/.test(rel_attribute.get_static_value()))) { + // https://web.dev/external-anchors-use-rel-noopener/ + component.warn(target_attribute, { + code: 'missing-attribute', + message: 'target="_blank" links should have a rel="noopener" attribute' + }); + } if (href_attribute) { const href_value = href_attribute.get_static_value(); diff --git a/test/validator/samples/missing-rel-cross-origin-link/input.svelte b/test/validator/samples/missing-rel-cross-origin-link/input.svelte new file mode 100644 index 0000000000..b33ef2b655 --- /dev/null +++ b/test/validator/samples/missing-rel-cross-origin-link/input.svelte @@ -0,0 +1 @@ +Click here diff --git a/test/validator/samples/missing-rel-cross-origin-link/warnings.json b/test/validator/samples/missing-rel-cross-origin-link/warnings.json new file mode 100644 index 0000000000..d09ac02230 --- /dev/null +++ b/test/validator/samples/missing-rel-cross-origin-link/warnings.json @@ -0,0 +1,17 @@ +[ + { + "code": "missing-attribute", + "end": { + "character": 45, + "column": 45, + "line": 1 + }, + "message": "target=\"_blank\" links should have a rel=\"noopener\" attribute", + "pos": 30, + "start": { + "character": 30, + "column": 30, + "line": 1 + } + } +]