Files
overleaf-cep/libraries/eslint-plugin/require-rel-for-docs-links.js
T
Rebeka DekanyandCopilot Autofix powered by AI f30c23cacc Update in-product doc links to point to docs.overleaf.com (#35184)
* update doc links to point to docs.overleaf.com instead of the old learn wiki

* fix broken link to LaTeX positioning wiki article in figure modal help

* point figure positioning help link to the docs.overleaf.com page

* point figure/table label help links to specific anchors on the captioning-and-referencing-figures doc page

* point figure modal error-help link to the graphicx-loading doc page

* normalize target/rel on docs.overleaf.com links opened in a new tab

* open figure modal and SSO help links in a new tab now that they point off-site

* add eslint rule enforcing rel=noopener noreferrer on docs.overleaf.com links

* extend require-rel-for-docs-links to also check OLButton

* add lint script catching missing rel on docs.overleaf.com links in pug templates

* fix require-rel-for-docs-links autofix crash on valueless rel attribute

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* add regression test for require-rel-for-docs-links valueless-rel autofix

* revert `target=_blank` on figure modal and SSO help links

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
GitOrigin-RevId: b6111341e1080fd7e4b42f6a1a18c27ea0fd7d8c
2026-09-17 11:18:47 +00:00

88 lines
2.6 KiB
JavaScript

const DOCS_HOST = 'docs.overleaf.com'
const REQUIRED_REL_TOKENS = ['noopener', 'noreferrer']
// Elements that forward href/target/rel straight onto a rendered <a>.
const LINK_ELEMENT_NAMES = ['a', 'OLButton']
function getStringLiteralValue(node) {
if (!node) return undefined
if (node.type === 'Literal' && typeof node.value === 'string') {
return node.value
}
if (
node.type === 'JSXExpressionContainer' &&
node.expression.type === 'Literal' &&
typeof node.expression.value === 'string'
) {
return node.expression.value
}
return undefined
}
module.exports = {
meta: {
type: 'problem',
fixable: 'code',
docs: {
description:
'Require rel="noopener noreferrer" on links to docs.overleaf.com that open in a new tab, to prevent reverse-tabnabbing',
},
schema: [],
},
create(context) {
const selector = `JSXOpeningElement[name.name=/^(${LINK_ELEMENT_NAMES.join('|')})$/]`
return {
[selector](node) {
const attributes = new Map(
node.attributes
.filter(attr => attr.type === 'JSXAttribute')
.map(attr => [attr.name.name, attr])
)
const hrefAttr = attributes.get('href')
const href = getStringLiteralValue(hrefAttr?.value)
if (!href || !href.includes(DOCS_HOST)) {
return
}
const targetAttr = attributes.get('target')
const target = getStringLiteralValue(targetAttr?.value)
if (!target || target.toLowerCase() !== '_blank') {
return
}
const relAttr = attributes.get('rel')
const rel = getStringLiteralValue(relAttr?.value) || ''
const relTokens = rel.toLowerCase().split(/\s+/).filter(Boolean)
const missingTokens = REQUIRED_REL_TOKENS.filter(
token => !relTokens.includes(token)
)
if (missingTokens.length === 0) {
return
}
context.report({
node: relAttr || node,
message:
'Links to docs.overleaf.com with target="_blank" must have rel="noopener noreferrer" to prevent reverse-tabnabbing.',
fix(fixer) {
const newRel = Array.from(
new Set([...relTokens, ...REQUIRED_REL_TOKENS])
).join(' ')
if (relAttr) {
if (!relAttr.value) {
return fixer.replaceText(relAttr, `rel="${newRel}"`)
}
return fixer.replaceText(relAttr.value, `"${newRel}"`)
}
return fixer.insertTextAfter(targetAttr, ` rel="${newRel}"`)
},
})
},
}
},
}