@atlaskit/eslint-plugin-design-system 13.9.0 → 13.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +1 -0
- package/dist/cjs/presets/all-flat.codegen.js +2 -1
- package/dist/cjs/presets/all.codegen.js +2 -1
- package/dist/cjs/presets/recommended-flat.codegen.js +2 -1
- package/dist/cjs/presets/recommended.codegen.js +2 -1
- package/dist/cjs/rules/index.codegen.js +3 -1
- package/dist/cjs/rules/no-html-heading/index.js +40 -0
- package/dist/cjs/rules/no-html-heading/node-types/index.js +19 -0
- package/dist/cjs/rules/no-html-heading/node-types/jsx-element/index.js +136 -0
- package/dist/cjs/rules/no-html-heading/node-types/styled-component/index.js +42 -0
- package/dist/cjs/rules/no-html-heading/node-types/supported.js +82 -0
- package/dist/es2019/presets/all-flat.codegen.js +2 -1
- package/dist/es2019/presets/all.codegen.js +2 -1
- package/dist/es2019/presets/recommended-flat.codegen.js +2 -1
- package/dist/es2019/presets/recommended.codegen.js +2 -1
- package/dist/es2019/rules/index.codegen.js +3 -1
- package/dist/es2019/rules/no-html-heading/index.js +34 -0
- package/dist/es2019/rules/no-html-heading/node-types/index.js +2 -0
- package/dist/es2019/rules/no-html-heading/node-types/jsx-element/index.js +99 -0
- package/dist/es2019/rules/no-html-heading/node-types/styled-component/index.js +37 -0
- package/dist/es2019/rules/no-html-heading/node-types/supported.js +72 -0
- package/dist/esm/presets/all-flat.codegen.js +2 -1
- package/dist/esm/presets/all.codegen.js +2 -1
- package/dist/esm/presets/recommended-flat.codegen.js +2 -1
- package/dist/esm/presets/recommended.codegen.js +2 -1
- package/dist/esm/rules/index.codegen.js +3 -1
- package/dist/esm/rules/no-html-heading/index.js +34 -0
- package/dist/esm/rules/no-html-heading/node-types/index.js +2 -0
- package/dist/esm/rules/no-html-heading/node-types/jsx-element/index.js +127 -0
- package/dist/esm/rules/no-html-heading/node-types/styled-component/index.js +36 -0
- package/dist/esm/rules/no-html-heading/node-types/supported.js +73 -0
- package/dist/types/index.codegen.d.ts +9 -0
- package/dist/types/presets/all-flat.codegen.d.ts +1 -0
- package/dist/types/presets/all.codegen.d.ts +1 -0
- package/dist/types/presets/recommended-flat.codegen.d.ts +1 -0
- package/dist/types/presets/recommended.codegen.d.ts +1 -0
- package/dist/types/rules/index.codegen.d.ts +1 -0
- package/dist/types/rules/no-html-heading/index.d.ts +3 -0
- package/dist/types/rules/no-html-heading/node-types/index.d.ts +2 -0
- package/dist/types/rules/no-html-heading/node-types/jsx-element/index.d.ts +8 -0
- package/dist/types/rules/no-html-heading/node-types/styled-component/index.d.ts +8 -0
- package/dist/types/rules/no-html-heading/node-types/supported.d.ts +7 -0
- package/dist/types-ts4.5/index.codegen.d.ts +9 -0
- package/dist/types-ts4.5/presets/all-flat.codegen.d.ts +1 -0
- package/dist/types-ts4.5/presets/all.codegen.d.ts +1 -0
- package/dist/types-ts4.5/presets/recommended-flat.codegen.d.ts +1 -0
- package/dist/types-ts4.5/presets/recommended.codegen.d.ts +1 -0
- package/dist/types-ts4.5/rules/index.codegen.d.ts +1 -0
- package/dist/types-ts4.5/rules/no-html-heading/index.d.ts +3 -0
- package/dist/types-ts4.5/rules/no-html-heading/node-types/index.d.ts +2 -0
- package/dist/types-ts4.5/rules/no-html-heading/node-types/jsx-element/index.d.ts +8 -0
- package/dist/types-ts4.5/rules/no-html-heading/node-types/styled-component/index.d.ts +8 -0
- package/dist/types-ts4.5/rules/no-html-heading/node-types/supported.d.ts +7 -0
- package/package.json +1 -1
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { isNodeOfType } from 'eslint-codemod-utils';
|
|
2
|
+
import { getSourceCode } from '@atlaskit/eslint-utils/context-compat';
|
|
3
|
+
import * as ast from '../../../../ast-nodes';
|
|
4
|
+
import { isSupportedForLint } from '../supported';
|
|
5
|
+
function isImportDeclaration(node) {
|
|
6
|
+
return node.type === 'ImportDeclaration';
|
|
7
|
+
}
|
|
8
|
+
export const JSXElement = {
|
|
9
|
+
lint(node, {
|
|
10
|
+
context
|
|
11
|
+
}) {
|
|
12
|
+
if (!isSupportedForLint(node)) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const nodeName = ast.JSXElement.getName(node);
|
|
16
|
+
const sourceCode = getSourceCode(context);
|
|
17
|
+
const importDeclarations = sourceCode.ast.body.filter(isImportDeclaration);
|
|
18
|
+
let existingHeadingName = null;
|
|
19
|
+
const usedNames = new Set();
|
|
20
|
+
|
|
21
|
+
// Check for existing imports
|
|
22
|
+
for (const declaration of importDeclarations) {
|
|
23
|
+
for (const specifier of declaration.specifiers) {
|
|
24
|
+
usedNames.add(specifier.local.name);
|
|
25
|
+
}
|
|
26
|
+
if (declaration.source.value === '@atlaskit/heading') {
|
|
27
|
+
const defaultSpecifier = declaration.specifiers.find(specifier => specifier.type === 'ImportDefaultSpecifier');
|
|
28
|
+
if (defaultSpecifier) {
|
|
29
|
+
existingHeadingName = defaultSpecifier.local.name;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const generateUniqueName = baseName => {
|
|
34
|
+
let index = 1;
|
|
35
|
+
let newName = baseName;
|
|
36
|
+
while (usedNames.has(newName)) {
|
|
37
|
+
newName = `${baseName}${index}`;
|
|
38
|
+
index++;
|
|
39
|
+
}
|
|
40
|
+
return newName;
|
|
41
|
+
};
|
|
42
|
+
const headingName = existingHeadingName || generateUniqueName('Heading');
|
|
43
|
+
context.report({
|
|
44
|
+
node: node.openingElement,
|
|
45
|
+
messageId: 'noHtmlHeading',
|
|
46
|
+
data: {
|
|
47
|
+
name: nodeName
|
|
48
|
+
},
|
|
49
|
+
suggest: [{
|
|
50
|
+
desc: 'Replace with Heading component from @atlaskit/heading',
|
|
51
|
+
fix(fixer) {
|
|
52
|
+
var _node$closingElement;
|
|
53
|
+
const openingTagRange = node.openingElement.range;
|
|
54
|
+
const closingTagRange = (_node$closingElement = node.closingElement) === null || _node$closingElement === void 0 ? void 0 : _node$closingElement.range;
|
|
55
|
+
const elementName = isNodeOfType(node.openingElement.name, 'JSXIdentifier') ? node.openingElement.name.name : '';
|
|
56
|
+
const attributesText = node.openingElement.attributes
|
|
57
|
+
// Don't bring in the "role" or the "aria-level" because it's not needed
|
|
58
|
+
.filter(attr => !isNodeOfType(attr, 'JSXAttribute') || typeof attr.name.name === 'string' && !['role', 'aria-level'].includes(attr.name.name)).map(attr => sourceCode.getText(attr)).join(' ');
|
|
59
|
+
|
|
60
|
+
// Get the heading level
|
|
61
|
+
let headingLevel = '';
|
|
62
|
+
const ariaLevel = node.openingElement.attributes.find(attr => isNodeOfType(attr, 'JSXAttribute') && attr.name.name === 'aria-level');
|
|
63
|
+
if (ariaLevel && isNodeOfType(ariaLevel, 'JSXAttribute')) {
|
|
64
|
+
var _ariaLevel$value, _ariaLevel$value2;
|
|
65
|
+
// If it's a string
|
|
66
|
+
if (((_ariaLevel$value = ariaLevel.value) === null || _ariaLevel$value === void 0 ? void 0 : _ariaLevel$value.type) === 'Literal' && ariaLevel.value.value) {
|
|
67
|
+
headingLevel = `h${ariaLevel.value.value}`;
|
|
68
|
+
// If it's a number or some other literal in an expression container
|
|
69
|
+
} else if (((_ariaLevel$value2 = ariaLevel.value) === null || _ariaLevel$value2 === void 0 ? void 0 : _ariaLevel$value2.type) === 'JSXExpressionContainer' && ariaLevel.value.expression.type === 'Literal' && ariaLevel.value.expression.value) {
|
|
70
|
+
headingLevel = `h${ariaLevel.value.expression.value}`;
|
|
71
|
+
}
|
|
72
|
+
} else if (elementName.match(/h[1-6]/)) {
|
|
73
|
+
headingLevel = elementName;
|
|
74
|
+
}
|
|
75
|
+
const fixers = [];
|
|
76
|
+
|
|
77
|
+
// Replace <a> with <Heading> and retain attributes
|
|
78
|
+
if (openingTagRange) {
|
|
79
|
+
if (node.openingElement.selfClosing) {
|
|
80
|
+
fixers.push(fixer.replaceTextRange([openingTagRange[0] + 1, openingTagRange[1] - 1], `${headingName}${headingLevel ? ` as="${headingLevel}"` : ''}${attributesText ? ` ${attributesText}` : ''} /`));
|
|
81
|
+
} else {
|
|
82
|
+
fixers.push(fixer.replaceTextRange([openingTagRange[0] + 1, openingTagRange[1] - 1], `${headingName}${headingLevel ? ` as="${headingLevel}"` : ''}${attributesText ? ` ${attributesText}` : ''}`));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (closingTagRange && !node.openingElement.selfClosing) {
|
|
86
|
+
fixers.push(fixer.replaceTextRange([closingTagRange[0] + 2, closingTagRange[1] - 1], headingName));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Add import if not present
|
|
90
|
+
if (!existingHeadingName) {
|
|
91
|
+
const importStatement = `import ${headingName} from '@atlaskit/heading';\n`;
|
|
92
|
+
fixers.push(fixer.insertTextBefore(sourceCode.ast, importStatement));
|
|
93
|
+
}
|
|
94
|
+
return fixers;
|
|
95
|
+
}
|
|
96
|
+
}]
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/* eslint-disable @repo/internal/react/require-jsdoc */
|
|
2
|
+
|
|
3
|
+
import { isNodeOfType } from 'eslint-codemod-utils';
|
|
4
|
+
import { getScope } from '@atlaskit/eslint-utils/context-compat';
|
|
5
|
+
import { getJsxElementByName } from '../../../utils/get-jsx-element-by-name';
|
|
6
|
+
import { getStyledComponentCall } from '../../../utils/get-styled-component-call';
|
|
7
|
+
import { isSupportedForLint } from '../supported';
|
|
8
|
+
export const StyledComponent = {
|
|
9
|
+
lint(node, {
|
|
10
|
+
context
|
|
11
|
+
}) {
|
|
12
|
+
var _getJsxElementByName;
|
|
13
|
+
if (!isNodeOfType(node, 'CallExpression') || !isNodeOfType(node.callee, 'MemberExpression') || !isNodeOfType(node.callee.object, 'Identifier') || !isNodeOfType(node.callee.property, 'Identifier')) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const styles = getStyledComponentCall(node);
|
|
17
|
+
const elementName = node.callee.property.name;
|
|
18
|
+
if (!styles || !isNodeOfType(styles.id, 'Identifier')) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const jsxElement = (_getJsxElementByName = getJsxElementByName(styles.id.name, getScope(context, node))) === null || _getJsxElementByName === void 0 ? void 0 : _getJsxElementByName.parent;
|
|
22
|
+
if (!jsxElement) {
|
|
23
|
+
// If there's no JSX element, we can't determine if it's being used as an heading or not
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (jsxElement && !isSupportedForLint(jsxElement, elementName)) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
context.report({
|
|
30
|
+
node: styles,
|
|
31
|
+
messageId: 'noHtmlHeading',
|
|
32
|
+
data: {
|
|
33
|
+
name: node.callee.property.name
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { isNodeOfType } from 'eslint-codemod-utils';
|
|
2
|
+
import * as ast from '../../../ast-nodes';
|
|
3
|
+
const supportedElements = [{
|
|
4
|
+
name: 'h1'
|
|
5
|
+
}, {
|
|
6
|
+
name: 'h2'
|
|
7
|
+
}, {
|
|
8
|
+
name: 'h3'
|
|
9
|
+
}, {
|
|
10
|
+
name: 'h4'
|
|
11
|
+
}, {
|
|
12
|
+
name: 'h5'
|
|
13
|
+
}, {
|
|
14
|
+
name: 'h6'
|
|
15
|
+
}, {
|
|
16
|
+
name: '*',
|
|
17
|
+
attributes: [{
|
|
18
|
+
name: 'role',
|
|
19
|
+
values: ['heading']
|
|
20
|
+
}]
|
|
21
|
+
}];
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Determines if the given JSX element is a supported element to lint with this rule.
|
|
25
|
+
*/
|
|
26
|
+
export function isSupportedForLint(jsxNode, elementName) {
|
|
27
|
+
if (!isNodeOfType(jsxNode, 'JSXElement')) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Allow passing in the element name because the jsxNode doesn't
|
|
32
|
+
// represent the element name with styled components
|
|
33
|
+
const elName = elementName || ast.JSXElement.getName(jsxNode);
|
|
34
|
+
if (!elName) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Only check native HTML elements, not components
|
|
39
|
+
if (elName[0] !== elName[0].toLowerCase()) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
let supportedElement = supportedElements.find(({
|
|
43
|
+
name
|
|
44
|
+
}) => name === elName);
|
|
45
|
+
if (!supportedElement) {
|
|
46
|
+
supportedElement = supportedElements.find(({
|
|
47
|
+
name
|
|
48
|
+
}) => name === '*');
|
|
49
|
+
}
|
|
50
|
+
if (!supportedElement) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Check if the element has any attributes that are not supported
|
|
55
|
+
const attributes = ast.JSXElement.getAttributes(jsxNode);
|
|
56
|
+
if (supportedElement.attributes && !supportedElement.attributes.every(({
|
|
57
|
+
name,
|
|
58
|
+
values
|
|
59
|
+
}) => {
|
|
60
|
+
return attributes.some(attribute => {
|
|
61
|
+
if (attribute.type === 'JSXSpreadAttribute') {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
const isMatchingName = attribute.name.name === name;
|
|
65
|
+
const isMatchingValues = values && attribute.value && attribute.value.type === 'Literal' && typeof attribute.value.value === 'string' && (values === null || values === void 0 ? void 0 : values.includes(attribute.value.value));
|
|
66
|
+
return isMatchingName && isMatchingValues;
|
|
67
|
+
});
|
|
68
|
+
})) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* THIS FILE WAS CREATED VIA CODEGEN DO NOT MODIFY {@see http://go/af-codegen}
|
|
3
|
-
* @codegen <<SignedSource::
|
|
3
|
+
* @codegen <<SignedSource::aa1526cdf8f7bbdd8e89e584da433397>>
|
|
4
4
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -25,6 +25,7 @@ export default {
|
|
|
25
25
|
'@atlaskit/design-system/no-html-anchor': 'warn',
|
|
26
26
|
'@atlaskit/design-system/no-html-button': 'warn',
|
|
27
27
|
'@atlaskit/design-system/no-html-checkbox': 'warn',
|
|
28
|
+
'@atlaskit/design-system/no-html-heading': 'warn',
|
|
28
29
|
'@atlaskit/design-system/no-html-image': 'warn',
|
|
29
30
|
'@atlaskit/design-system/no-html-range': 'warn',
|
|
30
31
|
'@atlaskit/design-system/no-html-select': 'warn',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* THIS FILE WAS CREATED VIA CODEGEN DO NOT MODIFY {@see http://go/af-codegen}
|
|
3
|
-
* @codegen <<SignedSource::
|
|
3
|
+
* @codegen <<SignedSource::ec47b97e20d77ba1f43c2211aeeed2f6>>
|
|
4
4
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -24,6 +24,7 @@ export default {
|
|
|
24
24
|
'@atlaskit/design-system/no-html-anchor': 'warn',
|
|
25
25
|
'@atlaskit/design-system/no-html-button': 'warn',
|
|
26
26
|
'@atlaskit/design-system/no-html-checkbox': 'warn',
|
|
27
|
+
'@atlaskit/design-system/no-html-heading': 'warn',
|
|
27
28
|
'@atlaskit/design-system/no-html-image': 'warn',
|
|
28
29
|
'@atlaskit/design-system/no-html-range': 'warn',
|
|
29
30
|
'@atlaskit/design-system/no-html-select': 'warn',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* THIS FILE WAS CREATED VIA CODEGEN DO NOT MODIFY {@see http://go/af-codegen}
|
|
3
|
-
* @codegen <<SignedSource::
|
|
3
|
+
* @codegen <<SignedSource::4ad986601bfd6f0081e3bc6909694979>>
|
|
4
4
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -20,6 +20,7 @@ export default {
|
|
|
20
20
|
'@atlaskit/design-system/no-html-anchor': 'warn',
|
|
21
21
|
'@atlaskit/design-system/no-html-button': 'warn',
|
|
22
22
|
'@atlaskit/design-system/no-html-checkbox': 'warn',
|
|
23
|
+
'@atlaskit/design-system/no-html-heading': 'warn',
|
|
23
24
|
'@atlaskit/design-system/no-html-image': 'warn',
|
|
24
25
|
'@atlaskit/design-system/no-html-range': 'warn',
|
|
25
26
|
'@atlaskit/design-system/no-html-select': 'warn',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* THIS FILE WAS CREATED VIA CODEGEN DO NOT MODIFY {@see http://go/af-codegen}
|
|
3
|
-
* @codegen <<SignedSource::
|
|
3
|
+
* @codegen <<SignedSource::18dcf6fce30892e43fb5fb76339c261e>>
|
|
4
4
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -19,6 +19,7 @@ export default {
|
|
|
19
19
|
'@atlaskit/design-system/no-html-anchor': 'warn',
|
|
20
20
|
'@atlaskit/design-system/no-html-button': 'warn',
|
|
21
21
|
'@atlaskit/design-system/no-html-checkbox': 'warn',
|
|
22
|
+
'@atlaskit/design-system/no-html-heading': 'warn',
|
|
22
23
|
'@atlaskit/design-system/no-html-image': 'warn',
|
|
23
24
|
'@atlaskit/design-system/no-html-range': 'warn',
|
|
24
25
|
'@atlaskit/design-system/no-html-select': 'warn',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* THIS FILE WAS CREATED VIA CODEGEN DO NOT MODIFY {@see http://go/af-codegen}
|
|
3
|
-
* @codegen <<SignedSource::
|
|
3
|
+
* @codegen <<SignedSource::19fc297469c40b1fa9fd64c9f9313ba7>>
|
|
4
4
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
5
5
|
*/
|
|
6
6
|
import consistentCssPropUsage from './consistent-css-prop-usage';
|
|
@@ -23,6 +23,7 @@ import noExportedKeyframes from './no-exported-keyframes';
|
|
|
23
23
|
import noHtmlAnchor from './no-html-anchor';
|
|
24
24
|
import noHtmlButton from './no-html-button';
|
|
25
25
|
import noHtmlCheckbox from './no-html-checkbox';
|
|
26
|
+
import noHtmlHeading from './no-html-heading';
|
|
26
27
|
import noHtmlImage from './no-html-image';
|
|
27
28
|
import noHtmlRange from './no-html-range';
|
|
28
29
|
import noHtmlSelect from './no-html-select';
|
|
@@ -79,6 +80,7 @@ export var rules = {
|
|
|
79
80
|
'no-html-anchor': noHtmlAnchor,
|
|
80
81
|
'no-html-button': noHtmlButton,
|
|
81
82
|
'no-html-checkbox': noHtmlCheckbox,
|
|
83
|
+
'no-html-heading': noHtmlHeading,
|
|
82
84
|
'no-html-image': noHtmlImage,
|
|
83
85
|
'no-html-range': noHtmlRange,
|
|
84
86
|
'no-html-select': noHtmlSelect,
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createLintRule } from '../utils/create-rule';
|
|
2
|
+
import { JSXElement as _JSXElement, StyledComponent } from './node-types';
|
|
3
|
+
var rule = createLintRule({
|
|
4
|
+
meta: {
|
|
5
|
+
name: 'no-html-heading',
|
|
6
|
+
type: 'suggestion',
|
|
7
|
+
hasSuggestions: true,
|
|
8
|
+
docs: {
|
|
9
|
+
description: 'Discourage direct usage of HTML heading elements in favor of Atlassian Design System heading components.',
|
|
10
|
+
recommended: true,
|
|
11
|
+
severity: 'warn'
|
|
12
|
+
},
|
|
13
|
+
messages: {
|
|
14
|
+
noHtmlHeading: "This <{{ name }}> should be replaced with a heading component from the Atlassian Design System. ADS headings include ensure accessible implementations and provide access to ADS styling features like design tokens."
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
create: function create(context) {
|
|
18
|
+
return {
|
|
19
|
+
// transforms styled.<heading>(...) usages
|
|
20
|
+
CallExpression: function CallExpression(node) {
|
|
21
|
+
StyledComponent.lint(node, {
|
|
22
|
+
context: context
|
|
23
|
+
});
|
|
24
|
+
},
|
|
25
|
+
// transforms <heading css={...}> usages
|
|
26
|
+
JSXElement: function JSXElement(node) {
|
|
27
|
+
_JSXElement.lint(node, {
|
|
28
|
+
context: context
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
export default rule;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
|
|
2
|
+
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
|
|
3
|
+
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
|
|
4
|
+
import { isNodeOfType } from 'eslint-codemod-utils';
|
|
5
|
+
import { getSourceCode } from '@atlaskit/eslint-utils/context-compat';
|
|
6
|
+
import * as ast from '../../../../ast-nodes';
|
|
7
|
+
import { isSupportedForLint } from '../supported';
|
|
8
|
+
function isImportDeclaration(node) {
|
|
9
|
+
return node.type === 'ImportDeclaration';
|
|
10
|
+
}
|
|
11
|
+
export var JSXElement = {
|
|
12
|
+
lint: function lint(node, _ref) {
|
|
13
|
+
var context = _ref.context;
|
|
14
|
+
if (!isSupportedForLint(node)) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
var nodeName = ast.JSXElement.getName(node);
|
|
18
|
+
var sourceCode = getSourceCode(context);
|
|
19
|
+
var importDeclarations = sourceCode.ast.body.filter(isImportDeclaration);
|
|
20
|
+
var existingHeadingName = null;
|
|
21
|
+
var usedNames = new Set();
|
|
22
|
+
|
|
23
|
+
// Check for existing imports
|
|
24
|
+
var _iterator = _createForOfIteratorHelper(importDeclarations),
|
|
25
|
+
_step;
|
|
26
|
+
try {
|
|
27
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
28
|
+
var declaration = _step.value;
|
|
29
|
+
var _iterator2 = _createForOfIteratorHelper(declaration.specifiers),
|
|
30
|
+
_step2;
|
|
31
|
+
try {
|
|
32
|
+
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
33
|
+
var specifier = _step2.value;
|
|
34
|
+
usedNames.add(specifier.local.name);
|
|
35
|
+
}
|
|
36
|
+
} catch (err) {
|
|
37
|
+
_iterator2.e(err);
|
|
38
|
+
} finally {
|
|
39
|
+
_iterator2.f();
|
|
40
|
+
}
|
|
41
|
+
if (declaration.source.value === '@atlaskit/heading') {
|
|
42
|
+
var defaultSpecifier = declaration.specifiers.find(function (specifier) {
|
|
43
|
+
return specifier.type === 'ImportDefaultSpecifier';
|
|
44
|
+
});
|
|
45
|
+
if (defaultSpecifier) {
|
|
46
|
+
existingHeadingName = defaultSpecifier.local.name;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
} catch (err) {
|
|
51
|
+
_iterator.e(err);
|
|
52
|
+
} finally {
|
|
53
|
+
_iterator.f();
|
|
54
|
+
}
|
|
55
|
+
var generateUniqueName = function generateUniqueName(baseName) {
|
|
56
|
+
var index = 1;
|
|
57
|
+
var newName = baseName;
|
|
58
|
+
while (usedNames.has(newName)) {
|
|
59
|
+
newName = "".concat(baseName).concat(index);
|
|
60
|
+
index++;
|
|
61
|
+
}
|
|
62
|
+
return newName;
|
|
63
|
+
};
|
|
64
|
+
var headingName = existingHeadingName || generateUniqueName('Heading');
|
|
65
|
+
context.report({
|
|
66
|
+
node: node.openingElement,
|
|
67
|
+
messageId: 'noHtmlHeading',
|
|
68
|
+
data: {
|
|
69
|
+
name: nodeName
|
|
70
|
+
},
|
|
71
|
+
suggest: [{
|
|
72
|
+
desc: 'Replace with Heading component from @atlaskit/heading',
|
|
73
|
+
fix: function fix(fixer) {
|
|
74
|
+
var _node$closingElement;
|
|
75
|
+
var openingTagRange = node.openingElement.range;
|
|
76
|
+
var closingTagRange = (_node$closingElement = node.closingElement) === null || _node$closingElement === void 0 ? void 0 : _node$closingElement.range;
|
|
77
|
+
var elementName = isNodeOfType(node.openingElement.name, 'JSXIdentifier') ? node.openingElement.name.name : '';
|
|
78
|
+
var attributesText = node.openingElement.attributes
|
|
79
|
+
// Don't bring in the "role" or the "aria-level" because it's not needed
|
|
80
|
+
.filter(function (attr) {
|
|
81
|
+
return !isNodeOfType(attr, 'JSXAttribute') || typeof attr.name.name === 'string' && !['role', 'aria-level'].includes(attr.name.name);
|
|
82
|
+
}).map(function (attr) {
|
|
83
|
+
return sourceCode.getText(attr);
|
|
84
|
+
}).join(' ');
|
|
85
|
+
|
|
86
|
+
// Get the heading level
|
|
87
|
+
var headingLevel = '';
|
|
88
|
+
var ariaLevel = node.openingElement.attributes.find(function (attr) {
|
|
89
|
+
return isNodeOfType(attr, 'JSXAttribute') && attr.name.name === 'aria-level';
|
|
90
|
+
});
|
|
91
|
+
if (ariaLevel && isNodeOfType(ariaLevel, 'JSXAttribute')) {
|
|
92
|
+
var _ariaLevel$value, _ariaLevel$value2;
|
|
93
|
+
// If it's a string
|
|
94
|
+
if (((_ariaLevel$value = ariaLevel.value) === null || _ariaLevel$value === void 0 ? void 0 : _ariaLevel$value.type) === 'Literal' && ariaLevel.value.value) {
|
|
95
|
+
headingLevel = "h".concat(ariaLevel.value.value);
|
|
96
|
+
// If it's a number or some other literal in an expression container
|
|
97
|
+
} else if (((_ariaLevel$value2 = ariaLevel.value) === null || _ariaLevel$value2 === void 0 ? void 0 : _ariaLevel$value2.type) === 'JSXExpressionContainer' && ariaLevel.value.expression.type === 'Literal' && ariaLevel.value.expression.value) {
|
|
98
|
+
headingLevel = "h".concat(ariaLevel.value.expression.value);
|
|
99
|
+
}
|
|
100
|
+
} else if (elementName.match(/h[1-6]/)) {
|
|
101
|
+
headingLevel = elementName;
|
|
102
|
+
}
|
|
103
|
+
var fixers = [];
|
|
104
|
+
|
|
105
|
+
// Replace <a> with <Heading> and retain attributes
|
|
106
|
+
if (openingTagRange) {
|
|
107
|
+
if (node.openingElement.selfClosing) {
|
|
108
|
+
fixers.push(fixer.replaceTextRange([openingTagRange[0] + 1, openingTagRange[1] - 1], "".concat(headingName).concat(headingLevel ? " as=\"".concat(headingLevel, "\"") : '').concat(attributesText ? " ".concat(attributesText) : '', " /")));
|
|
109
|
+
} else {
|
|
110
|
+
fixers.push(fixer.replaceTextRange([openingTagRange[0] + 1, openingTagRange[1] - 1], "".concat(headingName).concat(headingLevel ? " as=\"".concat(headingLevel, "\"") : '').concat(attributesText ? " ".concat(attributesText) : '')));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (closingTagRange && !node.openingElement.selfClosing) {
|
|
114
|
+
fixers.push(fixer.replaceTextRange([closingTagRange[0] + 2, closingTagRange[1] - 1], headingName));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Add import if not present
|
|
118
|
+
if (!existingHeadingName) {
|
|
119
|
+
var importStatement = "import ".concat(headingName, " from '@atlaskit/heading';\n");
|
|
120
|
+
fixers.push(fixer.insertTextBefore(sourceCode.ast, importStatement));
|
|
121
|
+
}
|
|
122
|
+
return fixers;
|
|
123
|
+
}
|
|
124
|
+
}]
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/* eslint-disable @repo/internal/react/require-jsdoc */
|
|
2
|
+
|
|
3
|
+
import { isNodeOfType } from 'eslint-codemod-utils';
|
|
4
|
+
import { getScope } from '@atlaskit/eslint-utils/context-compat';
|
|
5
|
+
import { getJsxElementByName } from '../../../utils/get-jsx-element-by-name';
|
|
6
|
+
import { getStyledComponentCall } from '../../../utils/get-styled-component-call';
|
|
7
|
+
import { isSupportedForLint } from '../supported';
|
|
8
|
+
export var StyledComponent = {
|
|
9
|
+
lint: function lint(node, _ref) {
|
|
10
|
+
var _getJsxElementByName;
|
|
11
|
+
var context = _ref.context;
|
|
12
|
+
if (!isNodeOfType(node, 'CallExpression') || !isNodeOfType(node.callee, 'MemberExpression') || !isNodeOfType(node.callee.object, 'Identifier') || !isNodeOfType(node.callee.property, 'Identifier')) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
var styles = getStyledComponentCall(node);
|
|
16
|
+
var elementName = node.callee.property.name;
|
|
17
|
+
if (!styles || !isNodeOfType(styles.id, 'Identifier')) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
var jsxElement = (_getJsxElementByName = getJsxElementByName(styles.id.name, getScope(context, node))) === null || _getJsxElementByName === void 0 ? void 0 : _getJsxElementByName.parent;
|
|
21
|
+
if (!jsxElement) {
|
|
22
|
+
// If there's no JSX element, we can't determine if it's being used as an heading or not
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (jsxElement && !isSupportedForLint(jsxElement, elementName)) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
context.report({
|
|
29
|
+
node: styles,
|
|
30
|
+
messageId: 'noHtmlHeading',
|
|
31
|
+
data: {
|
|
32
|
+
name: node.callee.property.name
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { isNodeOfType } from 'eslint-codemod-utils';
|
|
2
|
+
import * as ast from '../../../ast-nodes';
|
|
3
|
+
var supportedElements = [{
|
|
4
|
+
name: 'h1'
|
|
5
|
+
}, {
|
|
6
|
+
name: 'h2'
|
|
7
|
+
}, {
|
|
8
|
+
name: 'h3'
|
|
9
|
+
}, {
|
|
10
|
+
name: 'h4'
|
|
11
|
+
}, {
|
|
12
|
+
name: 'h5'
|
|
13
|
+
}, {
|
|
14
|
+
name: 'h6'
|
|
15
|
+
}, {
|
|
16
|
+
name: '*',
|
|
17
|
+
attributes: [{
|
|
18
|
+
name: 'role',
|
|
19
|
+
values: ['heading']
|
|
20
|
+
}]
|
|
21
|
+
}];
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Determines if the given JSX element is a supported element to lint with this rule.
|
|
25
|
+
*/
|
|
26
|
+
export function isSupportedForLint(jsxNode, elementName) {
|
|
27
|
+
if (!isNodeOfType(jsxNode, 'JSXElement')) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Allow passing in the element name because the jsxNode doesn't
|
|
32
|
+
// represent the element name with styled components
|
|
33
|
+
var elName = elementName || ast.JSXElement.getName(jsxNode);
|
|
34
|
+
if (!elName) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Only check native HTML elements, not components
|
|
39
|
+
if (elName[0] !== elName[0].toLowerCase()) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
var supportedElement = supportedElements.find(function (_ref) {
|
|
43
|
+
var name = _ref.name;
|
|
44
|
+
return name === elName;
|
|
45
|
+
});
|
|
46
|
+
if (!supportedElement) {
|
|
47
|
+
supportedElement = supportedElements.find(function (_ref2) {
|
|
48
|
+
var name = _ref2.name;
|
|
49
|
+
return name === '*';
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
if (!supportedElement) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Check if the element has any attributes that are not supported
|
|
57
|
+
var attributes = ast.JSXElement.getAttributes(jsxNode);
|
|
58
|
+
if (supportedElement.attributes && !supportedElement.attributes.every(function (_ref3) {
|
|
59
|
+
var name = _ref3.name,
|
|
60
|
+
values = _ref3.values;
|
|
61
|
+
return attributes.some(function (attribute) {
|
|
62
|
+
if (attribute.type === 'JSXSpreadAttribute') {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
var isMatchingName = attribute.name.name === name;
|
|
66
|
+
var isMatchingValues = values && attribute.value && attribute.value.type === 'Literal' && typeof attribute.value.value === 'string' && (values === null || values === void 0 ? void 0 : values.includes(attribute.value.value));
|
|
67
|
+
return isMatchingName && isMatchingValues;
|
|
68
|
+
});
|
|
69
|
+
})) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
@@ -35,6 +35,7 @@ export declare const plugin: {
|
|
|
35
35
|
'no-html-anchor': import("eslint").Rule.RuleModule;
|
|
36
36
|
'no-html-button': import("eslint").Rule.RuleModule;
|
|
37
37
|
'no-html-checkbox': import("eslint").Rule.RuleModule;
|
|
38
|
+
'no-html-heading': import("eslint").Rule.RuleModule;
|
|
38
39
|
'no-html-image': import("eslint").Rule.RuleModule;
|
|
39
40
|
'no-html-range': import("eslint").Rule.RuleModule;
|
|
40
41
|
'no-html-select': import("eslint").Rule.RuleModule;
|
|
@@ -92,6 +93,7 @@ export declare const plugin: {
|
|
|
92
93
|
'@atlaskit/design-system/no-html-anchor': "warn";
|
|
93
94
|
'@atlaskit/design-system/no-html-button': "warn";
|
|
94
95
|
'@atlaskit/design-system/no-html-checkbox': "warn";
|
|
96
|
+
'@atlaskit/design-system/no-html-heading': "warn";
|
|
95
97
|
'@atlaskit/design-system/no-html-image': "warn";
|
|
96
98
|
'@atlaskit/design-system/no-html-range': "warn";
|
|
97
99
|
'@atlaskit/design-system/no-html-select': "warn";
|
|
@@ -153,6 +155,7 @@ export declare const plugin: {
|
|
|
153
155
|
'@atlaskit/design-system/no-html-anchor': "warn";
|
|
154
156
|
'@atlaskit/design-system/no-html-button': "warn";
|
|
155
157
|
'@atlaskit/design-system/no-html-checkbox': "warn";
|
|
158
|
+
'@atlaskit/design-system/no-html-heading': "warn";
|
|
156
159
|
'@atlaskit/design-system/no-html-image': "warn";
|
|
157
160
|
'@atlaskit/design-system/no-html-range': "warn";
|
|
158
161
|
'@atlaskit/design-system/no-html-select': "warn";
|
|
@@ -207,6 +210,7 @@ export declare const plugin: {
|
|
|
207
210
|
'@atlaskit/design-system/no-html-anchor': "warn";
|
|
208
211
|
'@atlaskit/design-system/no-html-button': "warn";
|
|
209
212
|
'@atlaskit/design-system/no-html-checkbox': "warn";
|
|
213
|
+
'@atlaskit/design-system/no-html-heading': "warn";
|
|
210
214
|
'@atlaskit/design-system/no-html-image': "warn";
|
|
211
215
|
'@atlaskit/design-system/no-html-range': "warn";
|
|
212
216
|
'@atlaskit/design-system/no-html-select': "warn";
|
|
@@ -252,6 +256,7 @@ export declare const plugin: {
|
|
|
252
256
|
'@atlaskit/design-system/no-html-anchor': "warn";
|
|
253
257
|
'@atlaskit/design-system/no-html-button': "warn";
|
|
254
258
|
'@atlaskit/design-system/no-html-checkbox': "warn";
|
|
259
|
+
'@atlaskit/design-system/no-html-heading': "warn";
|
|
255
260
|
'@atlaskit/design-system/no-html-image': "warn";
|
|
256
261
|
'@atlaskit/design-system/no-html-range': "warn";
|
|
257
262
|
'@atlaskit/design-system/no-html-select': "warn";
|
|
@@ -303,6 +308,7 @@ declare const configs: {
|
|
|
303
308
|
'@atlaskit/design-system/no-html-anchor': "warn";
|
|
304
309
|
'@atlaskit/design-system/no-html-button': "warn";
|
|
305
310
|
'@atlaskit/design-system/no-html-checkbox': "warn";
|
|
311
|
+
'@atlaskit/design-system/no-html-heading': "warn";
|
|
306
312
|
'@atlaskit/design-system/no-html-image': "warn";
|
|
307
313
|
'@atlaskit/design-system/no-html-range': "warn";
|
|
308
314
|
'@atlaskit/design-system/no-html-select': "warn";
|
|
@@ -364,6 +370,7 @@ declare const configs: {
|
|
|
364
370
|
'@atlaskit/design-system/no-html-anchor': "warn";
|
|
365
371
|
'@atlaskit/design-system/no-html-button': "warn";
|
|
366
372
|
'@atlaskit/design-system/no-html-checkbox': "warn";
|
|
373
|
+
'@atlaskit/design-system/no-html-heading': "warn";
|
|
367
374
|
'@atlaskit/design-system/no-html-image': "warn";
|
|
368
375
|
'@atlaskit/design-system/no-html-range': "warn";
|
|
369
376
|
'@atlaskit/design-system/no-html-select': "warn";
|
|
@@ -418,6 +425,7 @@ declare const configs: {
|
|
|
418
425
|
'@atlaskit/design-system/no-html-anchor': "warn";
|
|
419
426
|
'@atlaskit/design-system/no-html-button': "warn";
|
|
420
427
|
'@atlaskit/design-system/no-html-checkbox': "warn";
|
|
428
|
+
'@atlaskit/design-system/no-html-heading': "warn";
|
|
421
429
|
'@atlaskit/design-system/no-html-image': "warn";
|
|
422
430
|
'@atlaskit/design-system/no-html-range': "warn";
|
|
423
431
|
'@atlaskit/design-system/no-html-select': "warn";
|
|
@@ -463,6 +471,7 @@ declare const configs: {
|
|
|
463
471
|
'@atlaskit/design-system/no-html-anchor': "warn";
|
|
464
472
|
'@atlaskit/design-system/no-html-button': "warn";
|
|
465
473
|
'@atlaskit/design-system/no-html-checkbox': "warn";
|
|
474
|
+
'@atlaskit/design-system/no-html-heading': "warn";
|
|
466
475
|
'@atlaskit/design-system/no-html-image': "warn";
|
|
467
476
|
'@atlaskit/design-system/no-html-range': "warn";
|
|
468
477
|
'@atlaskit/design-system/no-html-select': "warn";
|
|
@@ -18,6 +18,7 @@ declare const _default: {
|
|
|
18
18
|
'@atlaskit/design-system/no-html-anchor': "warn";
|
|
19
19
|
'@atlaskit/design-system/no-html-button': "warn";
|
|
20
20
|
'@atlaskit/design-system/no-html-checkbox': "warn";
|
|
21
|
+
'@atlaskit/design-system/no-html-heading': "warn";
|
|
21
22
|
'@atlaskit/design-system/no-html-image': "warn";
|
|
22
23
|
'@atlaskit/design-system/no-html-range': "warn";
|
|
23
24
|
'@atlaskit/design-system/no-html-select': "warn";
|