Skip to content

Commit e1ea0b9

Browse files
committed
Add explicit-heading rule
1 parent 297138f commit e1ea0b9

File tree

3 files changed

+114
-2
lines changed

3 files changed

+114
-2
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
const rule = require('../explicit-heading')
2+
const {RuleTester} = require('eslint')
3+
4+
const ruleTester = new RuleTester({
5+
parserOptions: {
6+
ecmaVersion: 'latest',
7+
sourceType: 'module',
8+
ecmaFeatures: {
9+
jsx: true
10+
}
11+
}
12+
})
13+
14+
ruleTester.run('explicit-heading', rule, {
15+
valid: [
16+
`import {Heading} from '@primer/react';
17+
<Heading as="h1">Heading level 1</Heading>
18+
`,
19+
`import {Heading} from '@primer/react';
20+
<Heading as="h2">Heading level 2</Heading>
21+
`,
22+
`import {Heading} from '@primer/react';
23+
<Heading as="H3">Heading level 2</Heading>
24+
`,
25+
],
26+
invalid: [
27+
{
28+
code: `
29+
import {Heading} from '@primer/react';
30+
31+
<Heading>Heading without "as"</Heading>
32+
`,
33+
errors: [
34+
{
35+
messageId: 'nonExplicitHeadingLevel'
36+
}
37+
]
38+
},
39+
{
40+
code: `
41+
import {Heading} from '@primer/react';
42+
43+
<Heading as="span">Heading component used as "span"</Heading>
44+
`,
45+
errors: [
46+
{
47+
messageId: 'invalidAsValue'
48+
}
49+
]
50+
},
51+
]
52+
})

src/rules/explicit-heading.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
const {isPrimerComponent} = require('../utils/is-primer-component')
2+
const {getJSXOpeningElementName} = require('../utils/get-jsx-opening-element-name')
3+
const {getJSXOpeningElementAttribute} = require('../utils/get-jsx-opening-element-attribute')
4+
5+
const validHeadings = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
6+
7+
const isHeading = elem => getJSXOpeningElementName(elem) === 'Heading'
8+
const isUsingAs = elem => {
9+
const asUsage = getJSXOpeningElementAttribute(elem, 'as');
10+
11+
if (!asUsage) return;
12+
13+
return asUsage.value;
14+
}
15+
16+
const isValidAs = value => validHeadings.includes(value.toLowerCase());
17+
const isInvalid = elem => {
18+
const elemAs = isUsingAs(elem);
19+
20+
if (!elemAs) return 'nonExplicitHeadingLevel';
21+
if(!isValidAs(elemAs.value)) return 'invalidAsValue';
22+
23+
return false;
24+
}
25+
26+
module.exports = {
27+
meta: {
28+
type: "problem",
29+
schema: [
30+
{
31+
properties: {
32+
skipImportCheck: {
33+
type: 'boolean'
34+
}
35+
}
36+
}
37+
],
38+
messages: {
39+
nonExplicitHeadingLevel: "Heading must have an explicit heading level applied through `as` prop.",
40+
invalidAsValue: "Usage of `as` must only be used for headings (h1-h6)."
41+
}
42+
},
43+
create: function(context) {
44+
return {
45+
// callback functions
46+
JSXOpeningElement(jsxNode) {
47+
if (isPrimerComponent(jsxNode.name, context.getScope(jsxNode)) && isHeading(jsxNode)) {
48+
const error = isInvalid(jsxNode);
49+
50+
if (error) {
51+
context.report({
52+
node: jsxNode,
53+
messageId: error
54+
})
55+
}
56+
}
57+
}
58+
};
59+
}
60+
};

0 commit comments

Comments
 (0)