-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: import the
check-help-links
eslint rule (#117)
- Loading branch information
1 parent
82bf23f
commit a72a1c3
Showing
7 changed files
with
240 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@sourcegraph/eslint-plugin-sourcegraph': patch | ||
--- | ||
|
||
Imported the `check-help-links` eslint-plugin rule. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
75 changes: 75 additions & 0 deletions
75
packages/eslint-plugin/src/rules/check-help-links/__tests__/check-help-links.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
// Disabled some rules in the process of migration from JS to avoid bloating lint output with warnings. | ||
// To fix these issues the `@typescript-eslint/no-explicit-any` warning in this file should be fixed. | ||
/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */ | ||
import { RuleTester } from '../../../testing/RuleTester' | ||
import { checkHelpLinks } from '../check-help-links' | ||
|
||
const ruleTester = new RuleTester({ | ||
parserOptions: { | ||
ecmaVersion: 6, | ||
ecmaFeatures: { | ||
jsx: true, | ||
}, | ||
}, | ||
parser: '@typescript-eslint/parser', | ||
}) | ||
|
||
const invalidLinkError = (path: string) => { | ||
return { message: 'Help link to non-existent page: ' + path, type: 'JSXOpeningElement' } | ||
} | ||
const options = [{ docsiteList: ['a.md', 'b/c.md', 'd/index.md'] }] | ||
|
||
// Build up the test cases given the various combinations we need to support. | ||
const cases: any = { valid: [], invalid: [] } | ||
|
||
for (const [element, attribute] of [ | ||
['a', 'href'], | ||
['Link', 'to'], | ||
]) { | ||
for (const anchor of ['', '#anchor', '#anchor#double']) { | ||
for (const content of ['', 'link content']) { | ||
const code = (target: string) => { | ||
return content | ||
? `<${element} ${attribute}="${target}${anchor}">${content}</${element}>` | ||
: `<${element} ${attribute}="${target}${anchor}" />` | ||
} | ||
|
||
cases.valid.push( | ||
...[ | ||
'/help/a', | ||
'/help/b/c', | ||
'/help/d', | ||
'/help/d/', | ||
'not-a-help-link', | ||
'help/but-not-absolute', | ||
'/help-but-not-a-directory', | ||
].map(target => { | ||
return { | ||
code: code(target), | ||
options, | ||
} | ||
}) | ||
) | ||
|
||
cases.invalid.push( | ||
...['/help/', '/help/b', '/help/does/not/exist'].map(target => { | ||
return { | ||
code: code(target), | ||
errors: [invalidLinkError(target.slice(6))], | ||
options, | ||
} | ||
}) | ||
) | ||
} | ||
} | ||
} | ||
|
||
// Every case should be valid if the options are empty. | ||
cases.valid.push( | ||
...[...cases.invalid, ...cases.valid].map(({ code }) => { | ||
return { code } | ||
}) | ||
) | ||
|
||
// Actually run the tests. | ||
ruleTester.run('check-help-links', checkHelpLinks, cases) |
22 changes: 22 additions & 0 deletions
22
packages/eslint-plugin/src/rules/check-help-links/check-help-links.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# Check help links for validity | ||
|
||
## Rule details | ||
|
||
This rule parses `Link` and `a` elements in JSX/TSX files. If a list of valid | ||
docsite pages is provided, elements that point to a `/help/*` link are checked | ||
against that list: if they don't exist, a linting error is raised. | ||
|
||
The list of docsite pages is provided either via the `DOCSITE_LIST` environment | ||
variable, which should be a newline separated list of pages as outputted by | ||
`docsite ls`, or via the `docsiteList` rule option, which is the same data as | ||
an array. | ||
|
||
If neither of these are set, then the rule will silently succeed. | ||
|
||
## How to Use | ||
|
||
```jsonc | ||
{ | ||
"@sourcegraph/check-help-links": "error" | ||
} | ||
``` |
127 changes: 127 additions & 0 deletions
127
packages/eslint-plugin/src/rules/check-help-links/check-help-links.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils' | ||
import { Literal } from '@typescript-eslint/types/dist/ast-spec' | ||
|
||
import { createRule } from '../../utils' | ||
|
||
export const messages = { | ||
invalidHelpLink: 'Help link to non-existent page: {{ destination }}', | ||
} | ||
|
||
export interface Option { | ||
docsiteList: { | ||
type: string | ||
}[] | ||
} | ||
|
||
export const checkHelpLinks = createRule<Option[], keyof typeof messages>({ | ||
name: 'check-help-links', | ||
meta: { | ||
docs: { | ||
description: 'Check that /help links point to real, non-redirected pages', | ||
recommended: false, | ||
}, | ||
messages, | ||
schema: [ | ||
{ | ||
type: 'object', | ||
properties: { | ||
docsiteList: { | ||
type: 'array', | ||
items: { | ||
type: 'string', | ||
}, | ||
}, | ||
}, | ||
additionalProperties: false, | ||
}, | ||
], | ||
type: 'problem', | ||
}, | ||
defaultOptions: [], | ||
create(context) { | ||
// Build the set of valid pages. In order, we'll try to get this from: | ||
// | ||
// 1. The DOCSITE_LIST environment variable, which should be a newline | ||
// separated list of pages, as outputted by `docsite ls`. | ||
// 2. The docsiteList rule option, which should be an array of pages. | ||
// | ||
// If neither of these are set, this rule will silently pass, so as not to | ||
// require docsite to be run when a user wants to run eslint in general. | ||
const pages = new Set() | ||
if (process.env.DOCSITE_LIST) { | ||
process.env.DOCSITE_LIST.split('\n').forEach(page => { | ||
return pages.add(page) | ||
}) | ||
} else if (context.options.length > 0) { | ||
context.options[0].docsiteList.forEach(page => { | ||
return pages.add(page) | ||
}) | ||
} | ||
|
||
// No pages were provided, so we'll return an empty object and do nothing. | ||
if (pages.size === 0) { | ||
return {} | ||
} | ||
|
||
// Return the object that will install the listeners we want. In this case, | ||
// we only need to look at JSX opening elements. | ||
// | ||
// Note that we could use AST selectors below, but the structure of the AST | ||
// makes that tricky: the identifer (Link or a) and attribute (to or href) | ||
// we use to identify an element of interest are siblings, so we'd probably | ||
// have to select on the identifier and have some ugly traversal code below | ||
// to check the attribute. It feels cleaner to do it this way with the | ||
// opening element as the context. | ||
return { | ||
JSXOpeningElement: node => { | ||
// Figure out what kind of element we have and therefore what attribute | ||
// we'd want to look for. | ||
let attributeName: string | ||
|
||
if (node.name.type === AST_NODE_TYPES.JSXIdentifier) { | ||
if (node.name.name === 'Link') { | ||
attributeName = 'to' | ||
} else if (node.name.name === 'a') { | ||
attributeName = 'href' | ||
} | ||
} else { | ||
// Anything that's not a link is uninteresting. | ||
return | ||
} | ||
|
||
// Go find the link target in the attribute array. | ||
const target = node.attributes.reduce<Literal['value']>((target, attribute) => { | ||
return ( | ||
target || | ||
(attribute.type === AST_NODE_TYPES.JSXAttribute && | ||
attribute.name && | ||
attribute.name.name === attributeName && | ||
attribute.value?.type === AST_NODE_TYPES.Literal | ||
? attribute.value.value | ||
: null) | ||
) | ||
}, null) | ||
|
||
// Make sure the target points to a help link; if not, we don't need to | ||
// go any further. | ||
if (typeof target !== 'string' || !target.startsWith('/help/')) { | ||
return | ||
} | ||
|
||
// Strip off the /help/ prefix, any anchor, and any trailing slash, then | ||
// look up the resultant page in the pages set, bearing in mind that it | ||
// might point to a directory and we also need to look for any index | ||
// page that might exist. | ||
const destination = target.slice(6).split('#')[0].replace(/\/+$/, '') | ||
|
||
if (!pages.has(destination + '.md') && !pages.has(destination + '/index.md')) { | ||
context.report({ | ||
node, | ||
messageId: 'invalidHelpLink', | ||
data: { destination }, | ||
}) | ||
} | ||
}, | ||
} | ||
}, | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './check-help-links' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,9 @@ | ||
// This file is used by `scripts/generate-configs.ts` for rules extraction. | ||
/* eslint-disable import/no-default-export */ | ||
import { checkHelpLinks } from './check-help-links' | ||
import { useButtonComponent } from './use-button-component' | ||
|
||
// eslint-disable-next-line import/no-default-export | ||
export default { | ||
'use-button-component': useButtonComponent, | ||
'check-help-links': checkHelpLinks, | ||
} |