From 32512559f5ce52ca5199f5450e814b5f57b26878 Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Tue, 8 Jul 2025 13:36:38 +1000 Subject: [PATCH 1/4] Add rule for Link to not be allowed without href --- ...nforce-button-for-link-with-nohref.test.js | 86 +++++++++++++++++++ .../enforce-button-for-link-with-nohref.js | 43 ++++++++++ 2 files changed, 129 insertions(+) create mode 100644 src/rules/__tests__/enforce-button-for-link-with-nohref.test.js create mode 100644 src/rules/enforce-button-for-link-with-nohref.js diff --git a/src/rules/__tests__/enforce-button-for-link-with-nohref.test.js b/src/rules/__tests__/enforce-button-for-link-with-nohref.test.js new file mode 100644 index 00000000..847d192c --- /dev/null +++ b/src/rules/__tests__/enforce-button-for-link-with-nohref.test.js @@ -0,0 +1,86 @@ +const rule = require('../enforce-button-for-link-with-nohref') +const {RuleTester} = require('eslint') + +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + ecmaFeatures: { + jsx: true, + }, + }, +}) + +ruleTester.run('enforce-button-for-link-with-nohref', rule, { + valid: [ + // Link with href attribute + `import {Link} from '@primer/react'; + Valid Link`, + + // Link with href and inline prop + `import {Link} from '@primer/react'; + Valid Inline Link`, + + // Link with href and className + `import {Link} from '@primer/react'; + Valid Link with Class`, + + // Link with href, inline, and className + `import {Link} from '@primer/react'; + Valid Inline Link with Class`, + + // Link with href as variable + `import {Link} from '@primer/react'; + const url = '/about'; + About`, + + // Button component (not Link) + `import {Button} from '@primer/react'; + `, + + // Regular HTML link (not Primer Link) + `Click me`, + + // Link from different package + `import {Link} from 'react-router-dom'; + About`, + ], + invalid: [ + { + code: `import {Link} from '@primer/react'; + Invalid Link without href`, + errors: [ + { + messageId: 'noLinkWithoutHref', + }, + ], + }, + { + code: `import {Link} from '@primer/react'; + Invalid Link with class but no href`, + errors: [ + { + messageId: 'noLinkWithoutHref', + }, + ], + }, + { + code: `import {Link} from '@primer/react'; + Invalid inline Link without href`, + errors: [ + { + messageId: 'noLinkWithoutHref', + }, + ], + }, + { + code: `import {Link} from '@primer/react'; + Invalid Link with onClick but no href`, + errors: [ + { + messageId: 'noLinkWithoutHref', + }, + ], + }, + ], +}) diff --git a/src/rules/enforce-button-for-link-with-nohref.js b/src/rules/enforce-button-for-link-with-nohref.js new file mode 100644 index 00000000..a95a7fb5 --- /dev/null +++ b/src/rules/enforce-button-for-link-with-nohref.js @@ -0,0 +1,43 @@ +const url = require('../url') +const {getJSXOpeningElementAttribute} = require('../utils/get-jsx-opening-element-attribute') +const {getJSXOpeningElementName} = require('../utils/get-jsx-opening-element-name') +const {isPrimerComponent} = require('../utils/is-primer-component') + +module.exports = { + meta: { + type: 'error', + docs: { + description: 'Disallow usage of Link component without href', + recommended: true, + url: url(module), + }, + messages: { + noLinkWithoutHref: + 'Links without href and other side effects are not accessible. Use a Button instead.Use a button instead', + }, + fixable: 'code', + }, + + create(context) { + const sourceCode = context.sourceCode ?? context.getSourceCode() + return { + JSXElement(node) { + const openingElement = node.openingElement + const elementName = getJSXOpeningElementName(openingElement) + + // Check if this is a Link component from @primer/react + if (elementName === 'Link' && isPrimerComponent(openingElement.name, sourceCode.getScope(node))) { + // Check if the Link has an href attribute + const hrefAttribute = getJSXOpeningElementAttribute(openingElement, 'href') + + if (!hrefAttribute) { + context.report({ + node: openingElement, + messageId: 'noLinkWithoutHref', + }) + } + } + }, + } + }, +} From 63a098b998aa36e5a001f2489a529f71bb812a40 Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Tue, 8 Jul 2025 13:38:01 +1000 Subject: [PATCH 2/4] Fix the message --- src/rules/enforce-button-for-link-with-nohref.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rules/enforce-button-for-link-with-nohref.js b/src/rules/enforce-button-for-link-with-nohref.js index a95a7fb5..f4c995ba 100644 --- a/src/rules/enforce-button-for-link-with-nohref.js +++ b/src/rules/enforce-button-for-link-with-nohref.js @@ -13,7 +13,7 @@ module.exports = { }, messages: { noLinkWithoutHref: - 'Links without href and other side effects are not accessible. Use a Button instead.Use a button instead', + 'Links without href and other side effects are not accessible. Use a Button instead.', }, fixable: 'code', }, From 4ca04933230a32a26cb60960bc7069bc1e086e20 Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Tue, 8 Jul 2025 13:43:29 +1000 Subject: [PATCH 3/4] Remove fixable property --- src/rules/enforce-button-for-link-with-nohref.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/rules/enforce-button-for-link-with-nohref.js b/src/rules/enforce-button-for-link-with-nohref.js index f4c995ba..957169de 100644 --- a/src/rules/enforce-button-for-link-with-nohref.js +++ b/src/rules/enforce-button-for-link-with-nohref.js @@ -12,10 +12,8 @@ module.exports = { url: url(module), }, messages: { - noLinkWithoutHref: - 'Links without href and other side effects are not accessible. Use a Button instead.', + noLinkWithoutHref: 'Links without href and other side effects are not accessible. Use a Button instead.', }, - fixable: 'code', }, create(context) { From 024794cb0ff985c47e31ed4a7235c2571dd3e428 Mon Sep 17 00:00:00 2001 From: Pavithra Kodmad Date: Tue, 8 Jul 2025 13:45:17 +1000 Subject: [PATCH 4/4] Create curvy-stingrays-decide.md --- .changeset/curvy-stingrays-decide.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/curvy-stingrays-decide.md diff --git a/.changeset/curvy-stingrays-decide.md b/.changeset/curvy-stingrays-decide.md new file mode 100644 index 00000000..16503a83 --- /dev/null +++ b/.changeset/curvy-stingrays-decide.md @@ -0,0 +1,5 @@ +--- +"eslint-plugin-primer-react": patch +--- + +Add rule for Link to not be allowed without href