From ff98741f98deeda8630591f8ed16ea84ef81b663 Mon Sep 17 00:00:00 2001 From: Kate Higa Date: Mon, 18 Jul 2022 17:07:18 -0700 Subject: [PATCH 01/13] add new accessibility rule --- lib/rules/no-generic-link-text.js | 55 +++++ package-lock.json | 353 ++++++++++++++++++++---------- package.json | 3 +- tests/no-generic-link-text.js | 31 +++ 4 files changed, 329 insertions(+), 113 deletions(-) create mode 100644 lib/rules/no-generic-link-text.js create mode 100644 tests/no-generic-link-text.js diff --git a/lib/rules/no-generic-link-text.js b/lib/rules/no-generic-link-text.js new file mode 100644 index 00000000..5f899858 --- /dev/null +++ b/lib/rules/no-generic-link-text.js @@ -0,0 +1,55 @@ +const {elementType, getProp, getPropValue} = require('jsx-ast-utils') + +const bannedLinkText = ['read more', 'here', 'click here', 'learn more', 'more', 'here'] + +/* Downcase and strip extra whitespaces and punctuation */ +const stripAndDowncaseText = text => { + return text + .toLowerCase() + .replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g, '') + .replace(/\s{2,}/g, ' ') + .trim() +} + +module.exports = { + meta: { + docs: { + description: 'disallow generic link text', + url: require('../url')(module) + }, + schema: [] + }, + + create(context) { + return { + JSXOpeningElement: node => { + if (elementType(node) !== 'a') return + if (getProp(node.attributes, 'aria-labelledby')) return + + const ariaLabel = getPropValue(getProp(node.attributes, 'aria-label')) + const cleanAriaLabelValue = ariaLabel && stripAndDowncaseText(ariaLabel) + + if (ariaLabel) { + if (bannedLinkText.includes(cleanAriaLabelValue)) { + context.report({ + node, + message: + 'Avoid setting generic link text like `Here`, `Click here`, `Read more`. Make sure that your link text is both descriptive and concise.' + }) + } + } else { + const parent = node.parent + if (parent.children && parent.children.length === 1 && parent.children[0].type === 'JSXText') { + const textContent = stripAndDowncaseText(parent.children[0].value) + if (!bannedLinkText.includes(textContent)) return + context.report({ + node, + message: + 'Avoid setting generic link text like `Here`, `Click here`, `Read more`. Make sure that your link text is both descriptive and concise.' + }) + } + } + } + } + } +} diff --git a/package-lock.json b/package-lock.json index a09e1774..b18c0650 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "eslint-plugin-no-only-tests": "^2.6.0", "eslint-plugin-prettier": "^4.0.0", "eslint-rule-documentation": ">=1.0.0", + "jsx-ast-utils": "^3.3.2", "prettier": "^2.2.1", "svg-element-attributes": "^1.3.1" }, @@ -473,13 +474,13 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/array-includes": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz", - "integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", + "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5", "get-intrinsic": "^1.1.1", "is-string": "^1.0.7" }, @@ -784,14 +785,18 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" }, "node_modules/define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", "dependencies": { - "object-keys": "^1.0.12" + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/diff": { @@ -837,30 +842,33 @@ "dev": true }, "node_modules/es-abstract": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", - "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz", + "integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==", "dependencies": { "call-bind": "^1.0.2", "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", "get-intrinsic": "^1.1.1", "get-symbol-description": "^1.0.0", "has": "^1.0.3", - "has-symbols": "^1.0.2", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", "internal-slot": "^1.0.3", "is-callable": "^1.2.4", - "is-negative-zero": "^2.0.1", + "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.1", + "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", - "is-weakref": "^1.0.1", - "object-inspect": "^1.11.0", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.0", "object-keys": "^1.1.1", "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.1" + "regexp.prototype.flags": "^1.4.3", + "string.prototype.trimend": "^1.0.5", + "string.prototype.trimstart": "^1.0.5", + "unbox-primitive": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -1450,11 +1458,36 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -1567,9 +1600,9 @@ } }, "node_modules/has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -1582,10 +1615,21 @@ "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", "engines": { "node": ">= 0.4" }, @@ -1768,9 +1812,9 @@ } }, "node_modules/is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", "engines": { "node": ">= 0.4" }, @@ -1787,9 +1831,9 @@ } }, "node_modules/is-number-object": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", - "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", "dependencies": { "has-tostringtag": "^1.0.0" }, @@ -1825,9 +1869,12 @@ } }, "node_modules/is-shared-array-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz", - "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dependencies": { + "call-bind": "^1.0.2" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -1873,11 +1920,11 @@ } }, "node_modules/is-weakref": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.1.tgz", - "integrity": "sha512-b2jKc2pQZjaeFYWEf7ScFj+Be1I+PXmlu572Q8coTXZ+LD/QQZ7ShPMst8h16riVgyXTQwUsFEl74mDvc/3MHQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", "dependencies": { - "call-bind": "^1.0.0" + "call-bind": "^1.0.2" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1920,6 +1967,18 @@ "json5": "lib/cli.js" } }, + "node_modules/jsx-ast-utils": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.2.tgz", + "integrity": "sha512-4ZCADZHRkno244xlNnn4AOG6sRQ7iBZ5BbgZ4vW4y5IZw7cVUD1PPeblm1xx/nfmMxPdt/LHsXZW8z/j58+l9Q==", + "dependencies": { + "array-includes": "^3.1.5", + "object.assign": "^4.1.2" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -2222,9 +2281,9 @@ } }, "node_modules/object-inspect": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", - "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -2469,6 +2528,22 @@ "node": ">=8.10.0" } }, + "node_modules/regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", @@ -2642,24 +2717,26 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", + "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", + "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2808,13 +2885,13 @@ } }, "node_modules/unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", "dependencies": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", "which-boxed-primitive": "^1.0.2" }, "funding": { @@ -3311,13 +3388,13 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "array-includes": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz", - "integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", + "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5", "get-intrinsic": "^1.1.1", "is-string": "^1.0.7" } @@ -3521,11 +3598,12 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" }, "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", "requires": { - "object-keys": "^1.0.12" + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" } }, "diff": { @@ -3562,30 +3640,33 @@ "dev": true }, "es-abstract": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", - "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz", + "integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==", "requires": { "call-bind": "^1.0.2", "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", "get-intrinsic": "^1.1.1", "get-symbol-description": "^1.0.0", "has": "^1.0.3", - "has-symbols": "^1.0.2", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", "internal-slot": "^1.0.3", "is-callable": "^1.2.4", - "is-negative-zero": "^2.0.1", + "is-negative-zero": "^2.0.2", "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.1", + "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", - "is-weakref": "^1.0.1", - "object-inspect": "^1.11.0", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.0", "object-keys": "^1.1.1", "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.4", - "string.prototype.trimstart": "^1.0.4", - "unbox-primitive": "^1.0.1" + "regexp.prototype.flags": "^1.4.3", + "string.prototype.trimend": "^1.0.5", + "string.prototype.trimstart": "^1.0.5", + "unbox-primitive": "^1.0.2" } }, "es-to-primitive": { @@ -4025,11 +4106,27 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, + "function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + } + }, "functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" + }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -4106,19 +4203,27 @@ } }, "has-bigints": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", - "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==" }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "requires": { + "get-intrinsic": "^1.1.1" + } + }, "has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" }, "has-tostringtag": { "version": "1.0.0", @@ -4238,9 +4343,9 @@ } }, "is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==" }, "is-number": { "version": "7.0.0", @@ -4248,9 +4353,9 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, "is-number-object": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", - "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", "requires": { "has-tostringtag": "^1.0.0" } @@ -4271,9 +4376,12 @@ } }, "is-shared-array-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz", - "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "requires": { + "call-bind": "^1.0.2" + } }, "is-string": { "version": "1.0.7", @@ -4298,11 +4406,11 @@ "dev": true }, "is-weakref": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.1.tgz", - "integrity": "sha512-b2jKc2pQZjaeFYWEf7ScFj+Be1I+PXmlu572Q8coTXZ+LD/QQZ7ShPMst8h16riVgyXTQwUsFEl74mDvc/3MHQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", "requires": { - "call-bind": "^1.0.0" + "call-bind": "^1.0.2" } }, "isexe": { @@ -4336,6 +4444,15 @@ "minimist": "^1.2.0" } }, + "jsx-ast-utils": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.2.tgz", + "integrity": "sha512-4ZCADZHRkno244xlNnn4AOG6sRQ7iBZ5BbgZ4vW4y5IZw7cVUD1PPeblm1xx/nfmMxPdt/LHsXZW8z/j58+l9Q==", + "requires": { + "array-includes": "^3.1.5", + "object.assign": "^4.1.2" + } + }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -4560,9 +4677,9 @@ "dev": true }, "object-inspect": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.11.0.tgz", - "integrity": "sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==" + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==" }, "object-keys": { "version": "1.1.1", @@ -4721,6 +4838,16 @@ "picomatch": "^2.2.1" } }, + "regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + } + }, "regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", @@ -4820,21 +4947,23 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" }, "string.prototype.trimend": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", - "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", + "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" } }, "string.prototype.trimstart": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", - "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", + "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3" + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" } }, "strip-ansi": { @@ -4930,13 +5059,13 @@ "peer": true }, "unbox-primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", - "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", "requires": { - "function-bind": "^1.1.1", - "has-bigints": "^1.0.1", - "has-symbols": "^1.0.2", + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", "which-boxed-primitive": "^1.0.2" } }, diff --git a/package.json b/package.json index 65a608c0..233c3d44 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "scripts": { "pretest": "mkdir -p node_modules/ && ln -fs $(pwd) node_modules/", "eslint-check": "eslint-config-prettier .eslintrc.js", - "test": "npm run eslint-check && eslint . && mocha tests/" + "test": "mocha tests/no-generic-link-text.js" }, "repository": { "type": "git", @@ -36,6 +36,7 @@ "eslint-plugin-no-only-tests": "^2.6.0", "eslint-plugin-prettier": "^4.0.0", "eslint-rule-documentation": ">=1.0.0", + "jsx-ast-utils": "^3.3.2", "prettier": "^2.2.1", "svg-element-attributes": "^1.3.1" }, diff --git a/tests/no-generic-link-text.js b/tests/no-generic-link-text.js new file mode 100644 index 00000000..eb4d608f --- /dev/null +++ b/tests/no-generic-link-text.js @@ -0,0 +1,31 @@ +const rule = require('../lib/rules/no-generic-link-text') +const RuleTester = require('eslint').RuleTester + +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + ecmaFeatures: { + jsx: true + } + } +}) + +const errorMessage = + 'Avoid setting generic link text like `Here`, `Click here`, `Read more`. Make sure that your link text is both descriptive and concise.' + +ruleTester.run('no-generic-link-text', rule, { + valid: [ + {code: "GitHub Home;"}, + {code: "GitHub Home;"}, + {code: "Read more;"}, + {code: "Read more;"} + ], + invalid: [ + {code: 'Click here!;', errors: [{message: errorMessage}]}, + {code: 'Learn more.;', errors: [{message: errorMessage}]}, + {code: ";", errors: [{message: errorMessage}]}, + {code: ";", errors: [{message: errorMessage}]}, + {code: ";", errors: [{message: errorMessage}]} + ] +}) From fa7dd7e7a1a4baba1ae4b1fbdd0152db935f40f8 Mon Sep 17 00:00:00 2001 From: Kate Higa Date: Mon, 18 Jul 2022 17:07:35 -0700 Subject: [PATCH 02/13] basically copy over docs from erblint-github --- docs/rules/no-generic-link-text.md | 83 ++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 docs/rules/no-generic-link-text.md diff --git a/docs/rules/no-generic-link-text.md b/docs/rules/no-generic-link-text.md new file mode 100644 index 00000000..88e1089e --- /dev/null +++ b/docs/rules/no-generic-link-text.md @@ -0,0 +1,83 @@ +# No Generic Link Text + +## Rule Details + +Avoid setting generic link text like, "Click here", "Read more", and "Learn more" which do not make sense when read out of context. + +Screen reader users often tab through links on a page to quickly find content without needing to listen to the full page. When link text is too generic, it becomes difficult to quickly identify the destination of the link. While it is possible to provide a more specific link text by setting the `aria-label`, this results in divergence between the label and the text and is not an ideal, future-proof solution. + +Additionally, generic link text can also problematic for heavy zoom users where the link context is out of view. + +Ensure that your link text is descriptive and the purpose of the link is clear even when read out of context of surrounding text. +Learn more about how to write descriptive link text at [Access Guide: Write descriptive link text](https://www.accessguide.io/guide/descriptive-link-text) + +### Use of ARIA attributes + +If you _must_ use ARIA to replace the visible link text, include the visible text at the beginning. + +For example, on a pricing plans page, the following are good: + +- Visible text: `Learn more` +- Accessible label: `Learn more about GitHub pricing plans` + +Accessible ✅ + +```html +Learn more +``` + +Inaccessible 🚫 + +```html +Learn more +``` + +Including the visible text in the ARIA label satisfies [SC 2.5.3: Label in Name](https://www.w3.org/WAI/WCAG21/Understanding/label-in-name.html). + +#### False negatives + +Caution: because of the restrictions of static code analysis, we may not catch all violations. + +Please perform browser tests and spot checks: + +- when `aria-label` is set dynamically +- when using `aria-labelledby` + +## Resources + +- [Primer: Links](https://primer.style/design/accessibility/links) +- [Understanding Success Criterion 2.4.4: Link Purpose (In Context)](https://www.w3.org/WAI/WCAG21/Understanding/link-purpose-in-context.html) +- [WebAim: Links and Hypertext](https://webaim.org/techniques/hypertext/) +- [Deque: Use link text that make sense when read out of context](https://dequeuniversity.com/tips/link-text) + +## Examples + +### **Incorrect** code for this rule 👎 + +```jsx +Learn more +``` + +```jsx +Read more +``` + +```jsx +Read more +``` + +```jsx +Read more +``` + +### **Correct** code for this rule 👍 + +```jsx +Learn more about GitHub +``` + +```jsx +Create a new repository +``` + +## Version From b69e44ff092a5cc0574259a10d389965553a04a8 Mon Sep 17 00:00:00 2001 From: Kate Higa Date: Mon, 18 Jul 2022 17:12:09 -0700 Subject: [PATCH 03/13] fix lints --- lib/rules/no-generic-link-text.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/rules/no-generic-link-text.js b/lib/rules/no-generic-link-text.js index 5f899858..c09fc5ad 100644 --- a/lib/rules/no-generic-link-text.js +++ b/lib/rules/no-generic-link-text.js @@ -6,7 +6,7 @@ const bannedLinkText = ['read more', 'here', 'click here', 'learn more', 'more', const stripAndDowncaseText = text => { return text .toLowerCase() - .replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g, '') + .replace(/[.,/#!$%^&*;:{}=\-_`~()]/g, '') .replace(/\s{2,}/g, ' ') .trim() } diff --git a/package.json b/package.json index 233c3d44..78b8e8d6 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "scripts": { "pretest": "mkdir -p node_modules/ && ln -fs $(pwd) node_modules/", "eslint-check": "eslint-config-prettier .eslintrc.js", - "test": "mocha tests/no-generic-link-text.js" + "test": "npm run eslint-check && eslint . && mocha tests/" }, "repository": { "type": "git", From ac1edd7c620a37aeabfaee254837e25d2a0b74b5 Mon Sep 17 00:00:00 2001 From: Kate Higa Date: Mon, 18 Jul 2022 17:16:03 -0700 Subject: [PATCH 04/13] set up react config --- lib/configs/react.js | 10 ++ lib/index.js | 4 +- package-lock.json | 227 +++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 4 files changed, 241 insertions(+), 1 deletion(-) create mode 100644 lib/configs/react.js diff --git a/lib/configs/react.js b/lib/configs/react.js new file mode 100644 index 00000000..6bb307d4 --- /dev/null +++ b/lib/configs/react.js @@ -0,0 +1,10 @@ +module.exports = { + env: { + browser: true + }, + plugins: ['github', 'jsx-a11y'], + extends: ['plugin:jsx-a11y/recommended'], + rules: { + 'github/no-generic-link-text': 'error' + } +} diff --git a/lib/index.js b/lib/index.js index 838ec673..c0ca4828 100644 --- a/lib/index.js +++ b/lib/index.js @@ -9,6 +9,7 @@ module.exports = { 'no-blur': require('./rules/no-blur'), 'no-d-none': require('./rules/no-d-none'), 'no-dataset': require('./rules/no-dataset'), + 'no-generic-link-text': require('./rules/no-generic-link-text'), 'no-implicit-buggy-globals': require('./rules/no-implicit-buggy-globals'), 'no-inner-html': require('./rules/no-inner-html'), 'no-innerText': require('./rules/no-innerText'), @@ -23,6 +24,7 @@ module.exports = { browser: require('./configs/browser'), internal: require('./configs/internal'), recommended: require('./configs/recommended'), - typescript: require('./configs/typescript') + typescript: require('./configs/typescript'), + react: require('./configs/react') } } diff --git a/package-lock.json b/package-lock.json index b18c0650..ccab3919 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "eslint-plugin-filenames": "^1.3.2", "eslint-plugin-i18n-text": "^1.0.1", "eslint-plugin-import": "^2.25.2", + "eslint-plugin-jsx-a11y": "^6.6.0", "eslint-plugin-no-only-tests": "^2.6.0", "eslint-plugin-prettier": "^4.0.0", "eslint-rule-documentation": ">=1.0.0", @@ -37,6 +38,29 @@ "eslint": "^8.0.1" } }, + "node_modules/@babel/runtime": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz", + "integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==", + "dependencies": { + "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime-corejs3": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.18.9.tgz", + "integrity": "sha512-qZEWeccZCrHA2Au4/X05QW5CMdm4VjUDCrGq5gf1ZDcM4hRqreKrtwAn7yci9zfgAS9apvnsFXiGBHBAxZdK9A==", + "dependencies": { + "core-js-pure": "^3.20.2", + "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@eslint/eslintrc": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", @@ -473,6 +497,18 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, + "node_modules/aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "dependencies": { + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" + }, + "engines": { + "node": ">=6.0" + } + }, "node_modules/array-includes": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", @@ -515,6 +551,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==" + }, + "node_modules/axe-core": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.4.3.tgz", + "integrity": "sha512-32+ub6kkdhhWick/UjvEwRchgoetXqTK14INLqbGm5U2TzBkBNF3nQtLYm8ovxSkQWArjEQvftCKryjZaATu3w==", + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", + "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -738,6 +792,16 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, + "node_modules/core-js-pure": { + "version": "3.23.5", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.23.5.tgz", + "integrity": "sha512-8t78LdpKSuCq4pJYCYk8hl7XEkAX+BP16yRIwL3AanTksxuEf7CM83vRyctmiEL8NDZ3jpUcv56fk9/zG3aIuw==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -751,6 +815,11 @@ "node": ">= 8" } }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -1145,6 +1214,45 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.6.0.tgz", + "integrity": "sha512-kTeLuIzpNhXL2CwLlc8AHI0aFRwWHcg483yepO9VQiHzM9bZwJdzTkzBszbuPrbgGmq2rlX/FaT2fJQsjUSHsw==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "aria-query": "^4.2.2", + "array-includes": "^3.1.5", + "ast-types-flow": "^0.0.7", + "axe-core": "^4.4.2", + "axobject-query": "^2.2.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "has": "^1.0.3", + "jsx-ast-utils": "^3.3.1", + "language-tags": "^1.0.5", + "minimatch": "^3.1.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/eslint-plugin-no-only-tests": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/eslint-plugin-no-only-tests/-/eslint-plugin-no-only-tests-2.6.0.tgz", @@ -1979,6 +2087,19 @@ "node": ">=4.0" } }, + "node_modules/language-subtag-registry": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", + "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==" + }, + "node_modules/language-tags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", + "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", + "dependencies": { + "language-subtag-registry": "~0.3.2" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -2528,6 +2649,11 @@ "node": ">=8.10.0" } }, + "node_modules/regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + }, "node_modules/regexp.prototype.flags": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", @@ -3117,6 +3243,23 @@ } }, "dependencies": { + "@babel/runtime": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.9.tgz", + "integrity": "sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@babel/runtime-corejs3": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.18.9.tgz", + "integrity": "sha512-qZEWeccZCrHA2Au4/X05QW5CMdm4VjUDCrGq5gf1ZDcM4hRqreKrtwAn7yci9zfgAS9apvnsFXiGBHBAxZdK9A==", + "requires": { + "core-js-pure": "^3.20.2", + "regenerator-runtime": "^0.13.4" + } + }, "@eslint/eslintrc": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", @@ -3387,6 +3530,15 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, + "aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "requires": { + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" + } + }, "array-includes": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", @@ -3414,6 +3566,21 @@ "es-abstract": "^1.19.0" } }, + "ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==" + }, + "axe-core": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.4.3.tgz", + "integrity": "sha512-32+ub6kkdhhWick/UjvEwRchgoetXqTK14INLqbGm5U2TzBkBNF3nQtLYm8ovxSkQWArjEQvftCKryjZaATu3w==" + }, + "axobject-query": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", + "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==" + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3568,6 +3735,11 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, + "core-js-pure": { + "version": "3.23.5", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.23.5.tgz", + "integrity": "sha512-8t78LdpKSuCq4pJYCYk8hl7XEkAX+BP16yRIwL3AanTksxuEf7CM83vRyctmiEL8NDZ3jpUcv56fk9/zG3aIuw==" + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -3578,6 +3750,11 @@ "which": "^2.0.1" } }, + "damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==" + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -3898,6 +4075,38 @@ } } }, + "eslint-plugin-jsx-a11y": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.6.0.tgz", + "integrity": "sha512-kTeLuIzpNhXL2CwLlc8AHI0aFRwWHcg483yepO9VQiHzM9bZwJdzTkzBszbuPrbgGmq2rlX/FaT2fJQsjUSHsw==", + "requires": { + "@babel/runtime": "^7.18.3", + "aria-query": "^4.2.2", + "array-includes": "^3.1.5", + "ast-types-flow": "^0.0.7", + "axe-core": "^4.4.2", + "axobject-query": "^2.2.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "has": "^1.0.3", + "jsx-ast-utils": "^3.3.1", + "language-tags": "^1.0.5", + "minimatch": "^3.1.2", + "semver": "^6.3.0" + }, + "dependencies": { + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, "eslint-plugin-no-only-tests": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/eslint-plugin-no-only-tests/-/eslint-plugin-no-only-tests-2.6.0.tgz", @@ -4453,6 +4662,19 @@ "object.assign": "^4.1.2" } }, + "language-subtag-registry": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", + "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==" + }, + "language-tags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", + "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", + "requires": { + "language-subtag-registry": "~0.3.2" + } + }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -4838,6 +5060,11 @@ "picomatch": "^2.2.1" } }, + "regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + }, "regexp.prototype.flags": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", diff --git a/package.json b/package.json index 78b8e8d6..29837104 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "eslint-plugin-filenames": "^1.3.2", "eslint-plugin-i18n-text": "^1.0.1", "eslint-plugin-import": "^2.25.2", + "eslint-plugin-jsx-a11y": "^6.6.0", "eslint-plugin-no-only-tests": "^2.6.0", "eslint-plugin-prettier": "^4.0.0", "eslint-rule-documentation": ">=1.0.0", From de77ce7828b67137a86c78f18dee19d9cf505226 Mon Sep 17 00:00:00 2001 From: Kate Higa Date: Mon, 18 Jul 2022 17:16:13 -0700 Subject: [PATCH 05/13] update test --- tests/no-generic-link-text.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/no-generic-link-text.js b/tests/no-generic-link-text.js index eb4d608f..2a6afbe2 100644 --- a/tests/no-generic-link-text.js +++ b/tests/no-generic-link-text.js @@ -22,7 +22,7 @@ ruleTester.run('no-generic-link-text', rule, { {code: "Read more;"} ], invalid: [ - {code: 'Click here!;', errors: [{message: errorMessage}]}, + {code: 'Click here*;', errors: [{message: errorMessage}]}, {code: 'Learn more.;', errors: [{message: errorMessage}]}, {code: ";", errors: [{message: errorMessage}]}, {code: ";", errors: [{message: errorMessage}]}, From 57dab5d107214e1785b7b6d1ddbe71f2498287fa Mon Sep 17 00:00:00 2001 From: Kate Higa Date: Mon, 18 Jul 2022 17:54:08 -0700 Subject: [PATCH 06/13] Add additional check to make sure accessible name fully contains visible label --- lib/rules/no-generic-link-text.js | 19 +++++++++++++++---- tests/no-generic-link-text.js | 11 ++++++++++- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/lib/rules/no-generic-link-text.js b/lib/rules/no-generic-link-text.js index c09fc5ad..14bc41bd 100644 --- a/lib/rules/no-generic-link-text.js +++ b/lib/rules/no-generic-link-text.js @@ -26,6 +26,13 @@ module.exports = { if (elementType(node) !== 'a') return if (getProp(node.attributes, 'aria-labelledby')) return + let cleanTextContent // text content we can reliably fetch + + const parent = node.parent + if (parent.children && parent.children.length > 0 && parent.children[0].type === 'JSXText') { + cleanTextContent = stripAndDowncaseText(parent.children[0].value) + } + const ariaLabel = getPropValue(getProp(node.attributes, 'aria-label')) const cleanAriaLabelValue = ariaLabel && stripAndDowncaseText(ariaLabel) @@ -37,11 +44,15 @@ module.exports = { 'Avoid setting generic link text like `Here`, `Click here`, `Read more`. Make sure that your link text is both descriptive and concise.' }) } + if (cleanTextContent && !cleanAriaLabelValue.includes(cleanTextContent)) { + context.report({ + node, + message: 'When using ARIA to set a more descriptive text, it must fully contain the visible label.' + }) + } } else { - const parent = node.parent - if (parent.children && parent.children.length === 1 && parent.children[0].type === 'JSXText') { - const textContent = stripAndDowncaseText(parent.children[0].value) - if (!bannedLinkText.includes(textContent)) return + if (cleanTextContent) { + if (!bannedLinkText.includes(cleanTextContent)) return context.report({ node, message: diff --git a/tests/no-generic-link-text.js b/tests/no-generic-link-text.js index 2a6afbe2..1c1f5347 100644 --- a/tests/no-generic-link-text.js +++ b/tests/no-generic-link-text.js @@ -26,6 +26,15 @@ ruleTester.run('no-generic-link-text', rule, { {code: 'Learn more.;', errors: [{message: errorMessage}]}, {code: ";", errors: [{message: errorMessage}]}, {code: ";", errors: [{message: errorMessage}]}, - {code: ";", errors: [{message: errorMessage}]} + {code: ";", errors: [{message: errorMessage}]}, + { + code: "Read more!;", + errors: [ + { + message: + 'When setting ARIA to provide a more descriptive accessible name, it must fully include the visible label content.' + } + ] + } ] }) From 19eca61c5a581fa0116b0b3a7684e635c9de651f Mon Sep 17 00:00:00 2001 From: Kate Higa Date: Mon, 18 Jul 2022 17:57:02 -0700 Subject: [PATCH 07/13] Update test --- tests/no-generic-link-text.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/no-generic-link-text.js b/tests/no-generic-link-text.js index 1c1f5347..5c227f7b 100644 --- a/tests/no-generic-link-text.js +++ b/tests/no-generic-link-text.js @@ -32,7 +32,7 @@ ruleTester.run('no-generic-link-text', rule, { errors: [ { message: - 'When setting ARIA to provide a more descriptive accessible name, it must fully include the visible label content.' + 'When using ARIA to set a more descriptive text, it must fully contain the visible label.' } ] } From e39e9c697f31472110b72837a037febf93fd1de2 Mon Sep 17 00:00:00 2001 From: Kate Higa Date: Mon, 18 Jul 2022 17:58:09 -0700 Subject: [PATCH 08/13] Run prettier --- tests/no-generic-link-text.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/no-generic-link-text.js b/tests/no-generic-link-text.js index 5c227f7b..0e04624c 100644 --- a/tests/no-generic-link-text.js +++ b/tests/no-generic-link-text.js @@ -31,8 +31,7 @@ ruleTester.run('no-generic-link-text', rule, { code: "Read more!;", errors: [ { - message: - 'When using ARIA to set a more descriptive text, it must fully contain the visible label.' + message: 'When using ARIA to set a more descriptive text, it must fully contain the visible label.' } ] } From 78b7eeb07cbdfdb5dbaff9e495b56aa88efc0e46 Mon Sep 17 00:00:00 2001 From: Kate Higa Date: Tue, 19 Jul 2022 09:22:12 -0700 Subject: [PATCH 09/13] add react rules --- .../{no-generic-link-text.md => a11y-no-generic-link-text.md} | 0 lib/configs/react.js | 2 +- lib/index.js | 2 +- .../{no-generic-link-text.js => a11y-no-generic-link-text.js} | 0 .../{no-generic-link-text.js => a11y-no-generic-link-text.js} | 4 ++-- 5 files changed, 4 insertions(+), 4 deletions(-) rename docs/rules/{no-generic-link-text.md => a11y-no-generic-link-text.md} (100%) rename lib/rules/{no-generic-link-text.js => a11y-no-generic-link-text.js} (100%) rename tests/{no-generic-link-text.js => a11y-no-generic-link-text.js} (91%) diff --git a/docs/rules/no-generic-link-text.md b/docs/rules/a11y-no-generic-link-text.md similarity index 100% rename from docs/rules/no-generic-link-text.md rename to docs/rules/a11y-no-generic-link-text.md diff --git a/lib/configs/react.js b/lib/configs/react.js index 6bb307d4..5845b799 100644 --- a/lib/configs/react.js +++ b/lib/configs/react.js @@ -5,6 +5,6 @@ module.exports = { plugins: ['github', 'jsx-a11y'], extends: ['plugin:jsx-a11y/recommended'], rules: { - 'github/no-generic-link-text': 'error' + 'github/a11y-no-generic-link-text': 'error' } } diff --git a/lib/index.js b/lib/index.js index c0ca4828..8ed725f9 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,5 +1,6 @@ module.exports = { rules: { + 'a11y-no-generic-link-text': require('./rules/a11y-no-generic-link-text'), 'array-foreach': require('./rules/array-foreach'), 'async-currenttarget': require('./rules/async-currenttarget'), 'async-preventdefault': require('./rules/async-preventdefault'), @@ -9,7 +10,6 @@ module.exports = { 'no-blur': require('./rules/no-blur'), 'no-d-none': require('./rules/no-d-none'), 'no-dataset': require('./rules/no-dataset'), - 'no-generic-link-text': require('./rules/no-generic-link-text'), 'no-implicit-buggy-globals': require('./rules/no-implicit-buggy-globals'), 'no-inner-html': require('./rules/no-inner-html'), 'no-innerText': require('./rules/no-innerText'), diff --git a/lib/rules/no-generic-link-text.js b/lib/rules/a11y-no-generic-link-text.js similarity index 100% rename from lib/rules/no-generic-link-text.js rename to lib/rules/a11y-no-generic-link-text.js diff --git a/tests/no-generic-link-text.js b/tests/a11y-no-generic-link-text.js similarity index 91% rename from tests/no-generic-link-text.js rename to tests/a11y-no-generic-link-text.js index 0e04624c..7ef4a7b4 100644 --- a/tests/no-generic-link-text.js +++ b/tests/a11y-no-generic-link-text.js @@ -1,4 +1,4 @@ -const rule = require('../lib/rules/no-generic-link-text') +const rule = require('../lib/rules/a11y-no-generic-link-text') const RuleTester = require('eslint').RuleTester const ruleTester = new RuleTester({ @@ -14,7 +14,7 @@ const ruleTester = new RuleTester({ const errorMessage = 'Avoid setting generic link text like `Here`, `Click here`, `Read more`. Make sure that your link text is both descriptive and concise.' -ruleTester.run('no-generic-link-text', rule, { +ruleTester.run('a11y-no-generic-link-text', rule, { valid: [ {code: "GitHub Home;"}, {code: "GitHub Home;"}, From c1733dc8236db0fa202eaac5068658f678f65905 Mon Sep 17 00:00:00 2001 From: Kate Higa Date: Tue, 19 Jul 2022 10:04:06 -0700 Subject: [PATCH 10/13] update README --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 4eac90e2..dec4d228 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,8 @@ The available configs are: - Rules useful for github applications. - `browser` - Useful rules when shipping your app to the browser. +- `react` + - Recommended rules for React applications. - `recommended` - Recommended rules for every application. - `typescript` @@ -59,3 +61,7 @@ The available configs are: - [Prefer Observers](./docs/rules/prefer-observers.md) - [Require Passive Events](./docs/rules/require-passive-events.md) - [Unescaped HTML Literal](./docs/rules/unescaped-html-literal.md) + +#### Accessibility + +- [No Generic Link Text](./docs/rules/a11y-no-generic-link-text.md) From af98a183ceee2a1fe0731c840d2f1c066f1f5053 Mon Sep 17 00:00:00 2001 From: Kate Higa Date: Tue, 19 Jul 2022 10:19:56 -0700 Subject: [PATCH 11/13] update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dec4d228..6cbac4bc 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,6 @@ The available configs are: - [Require Passive Events](./docs/rules/require-passive-events.md) - [Unescaped HTML Literal](./docs/rules/unescaped-html-literal.md) -#### Accessibility +#### Accessibility-focused rules (prefixed with a11y) - [No Generic Link Text](./docs/rules/a11y-no-generic-link-text.md) From cd9d7eb198da2593446e6ef68bb8f33c2ba35b4f Mon Sep 17 00:00:00 2001 From: Kate Higa Date: Tue, 19 Jul 2022 11:13:05 -0700 Subject: [PATCH 12/13] update location of report --- lib/rules/a11y-no-generic-link-text.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/rules/a11y-no-generic-link-text.js b/lib/rules/a11y-no-generic-link-text.js index 14bc41bd..a262b43e 100644 --- a/lib/rules/a11y-no-generic-link-text.js +++ b/lib/rules/a11y-no-generic-link-text.js @@ -29,7 +29,9 @@ module.exports = { let cleanTextContent // text content we can reliably fetch const parent = node.parent + let jsxTextNode if (parent.children && parent.children.length > 0 && parent.children[0].type === 'JSXText') { + jsxTextNode = parent.children[0] cleanTextContent = stripAndDowncaseText(parent.children[0].value) } @@ -54,7 +56,7 @@ module.exports = { if (cleanTextContent) { if (!bannedLinkText.includes(cleanTextContent)) return context.report({ - node, + node: jsxTextNode, message: 'Avoid setting generic link text like `Here`, `Click here`, `Read more`. Make sure that your link text is both descriptive and concise.' }) From eb5d423e8452f55520e95ad245dcc718b7137326 Mon Sep 17 00:00:00 2001 From: Kate Higa Date: Wed, 20 Jul 2022 13:32:36 -0700 Subject: [PATCH 13/13] Remove redundant word in array --- lib/rules/a11y-no-generic-link-text.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rules/a11y-no-generic-link-text.js b/lib/rules/a11y-no-generic-link-text.js index a262b43e..9b063457 100644 --- a/lib/rules/a11y-no-generic-link-text.js +++ b/lib/rules/a11y-no-generic-link-text.js @@ -1,6 +1,6 @@ const {elementType, getProp, getPropValue} = require('jsx-ast-utils') -const bannedLinkText = ['read more', 'here', 'click here', 'learn more', 'more', 'here'] +const bannedLinkText = ['read more', 'here', 'click here', 'learn more', 'more'] /* Downcase and strip extra whitespaces and punctuation */ const stripAndDowncaseText = text => {