@atlaskit/eslint-plugin-design-system 13.12.0 → 13.13.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.
Files changed (75) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +2 -0
  3. package/dist/cjs/presets/all-flat.codegen.js +3 -1
  4. package/dist/cjs/presets/all.codegen.js +3 -1
  5. package/dist/cjs/presets/recommended-flat.codegen.js +3 -1
  6. package/dist/cjs/presets/recommended.codegen.js +3 -1
  7. package/dist/cjs/rules/index.codegen.js +5 -1
  8. package/dist/cjs/rules/no-html-text-input/index.js +34 -0
  9. package/dist/cjs/rules/no-html-text-input/node-types/index.js +12 -0
  10. package/dist/cjs/rules/no-html-text-input/node-types/jsx-element/index.js +115 -0
  11. package/dist/cjs/rules/no-html-text-input/node-types/supported.js +91 -0
  12. package/dist/cjs/rules/no-html-textarea/index.js +40 -0
  13. package/dist/cjs/rules/no-html-textarea/node-types/index.js +19 -0
  14. package/dist/cjs/rules/no-html-textarea/node-types/jsx-element/index.js +112 -0
  15. package/dist/cjs/rules/no-html-textarea/node-types/styled-component/index.js +42 -0
  16. package/dist/cjs/rules/no-html-textarea/node-types/supported.js +66 -0
  17. package/dist/es2019/presets/all-flat.codegen.js +3 -1
  18. package/dist/es2019/presets/all.codegen.js +3 -1
  19. package/dist/es2019/presets/recommended-flat.codegen.js +3 -1
  20. package/dist/es2019/presets/recommended.codegen.js +3 -1
  21. package/dist/es2019/rules/index.codegen.js +5 -1
  22. package/dist/es2019/rules/no-html-text-input/index.js +28 -0
  23. package/dist/es2019/rules/no-html-text-input/node-types/index.js +1 -0
  24. package/dist/es2019/rules/no-html-text-input/node-types/jsx-element/index.js +80 -0
  25. package/dist/es2019/rules/no-html-text-input/node-types/supported.js +79 -0
  26. package/dist/es2019/rules/no-html-textarea/index.js +34 -0
  27. package/dist/es2019/rules/no-html-textarea/node-types/index.js +2 -0
  28. package/dist/es2019/rules/no-html-textarea/node-types/jsx-element/index.js +79 -0
  29. package/dist/es2019/rules/no-html-textarea/node-types/styled-component/index.js +37 -0
  30. package/dist/es2019/rules/no-html-textarea/node-types/supported.js +56 -0
  31. package/dist/esm/presets/all-flat.codegen.js +3 -1
  32. package/dist/esm/presets/all.codegen.js +3 -1
  33. package/dist/esm/presets/recommended-flat.codegen.js +3 -1
  34. package/dist/esm/presets/recommended.codegen.js +3 -1
  35. package/dist/esm/rules/index.codegen.js +5 -1
  36. package/dist/esm/rules/no-html-text-input/index.js +28 -0
  37. package/dist/esm/rules/no-html-text-input/node-types/index.js +1 -0
  38. package/dist/esm/rules/no-html-text-input/node-types/jsx-element/index.js +106 -0
  39. package/dist/esm/rules/no-html-text-input/node-types/supported.js +82 -0
  40. package/dist/esm/rules/no-html-textarea/index.js +34 -0
  41. package/dist/esm/rules/no-html-textarea/node-types/index.js +2 -0
  42. package/dist/esm/rules/no-html-textarea/node-types/jsx-element/index.js +103 -0
  43. package/dist/esm/rules/no-html-textarea/node-types/styled-component/index.js +36 -0
  44. package/dist/esm/rules/no-html-textarea/node-types/supported.js +57 -0
  45. package/dist/types/index.codegen.d.ts +18 -0
  46. package/dist/types/presets/all-flat.codegen.d.ts +2 -0
  47. package/dist/types/presets/all.codegen.d.ts +2 -0
  48. package/dist/types/presets/recommended-flat.codegen.d.ts +2 -0
  49. package/dist/types/presets/recommended.codegen.d.ts +2 -0
  50. package/dist/types/rules/index.codegen.d.ts +2 -0
  51. package/dist/types/rules/no-html-text-input/index.d.ts +3 -0
  52. package/dist/types/rules/no-html-text-input/node-types/index.d.ts +1 -0
  53. package/dist/types/rules/no-html-text-input/node-types/jsx-element/index.d.ts +8 -0
  54. package/dist/types/rules/no-html-text-input/node-types/supported.d.ts +7 -0
  55. package/dist/types/rules/no-html-textarea/index.d.ts +3 -0
  56. package/dist/types/rules/no-html-textarea/node-types/index.d.ts +2 -0
  57. package/dist/types/rules/no-html-textarea/node-types/jsx-element/index.d.ts +8 -0
  58. package/dist/types/rules/no-html-textarea/node-types/styled-component/index.d.ts +8 -0
  59. package/dist/types/rules/no-html-textarea/node-types/supported.d.ts +7 -0
  60. package/dist/types-ts4.5/index.codegen.d.ts +18 -0
  61. package/dist/types-ts4.5/presets/all-flat.codegen.d.ts +2 -0
  62. package/dist/types-ts4.5/presets/all.codegen.d.ts +2 -0
  63. package/dist/types-ts4.5/presets/recommended-flat.codegen.d.ts +2 -0
  64. package/dist/types-ts4.5/presets/recommended.codegen.d.ts +2 -0
  65. package/dist/types-ts4.5/rules/index.codegen.d.ts +2 -0
  66. package/dist/types-ts4.5/rules/no-html-text-input/index.d.ts +3 -0
  67. package/dist/types-ts4.5/rules/no-html-text-input/node-types/index.d.ts +1 -0
  68. package/dist/types-ts4.5/rules/no-html-text-input/node-types/jsx-element/index.d.ts +8 -0
  69. package/dist/types-ts4.5/rules/no-html-text-input/node-types/supported.d.ts +7 -0
  70. package/dist/types-ts4.5/rules/no-html-textarea/index.d.ts +3 -0
  71. package/dist/types-ts4.5/rules/no-html-textarea/node-types/index.d.ts +2 -0
  72. package/dist/types-ts4.5/rules/no-html-textarea/node-types/jsx-element/index.d.ts +8 -0
  73. package/dist/types-ts4.5/rules/no-html-textarea/node-types/styled-component/index.d.ts +8 -0
  74. package/dist/types-ts4.5/rules/no-html-textarea/node-types/supported.d.ts +7 -0
  75. package/package.json +1 -1
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+
3
+ var _typeof = require("@babel/runtime/helpers/typeof");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.isSupportedForLint = isSupportedForLint;
8
+ var _eslintCodemodUtils = require("eslint-codemod-utils");
9
+ var ast = _interopRequireWildcard(require("../../../ast-nodes"));
10
+ function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
11
+ function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
12
+ var supportedElements = [{
13
+ name: 'textarea'
14
+ }];
15
+
16
+ /**
17
+ * Determines if the given JSX element is a supported element to lint with this rule.
18
+ */
19
+ function isSupportedForLint(jsxNode, elementName) {
20
+ if (!(0, _eslintCodemodUtils.isNodeOfType)(jsxNode, 'JSXElement')) {
21
+ return false;
22
+ }
23
+
24
+ // Allow passing in the element name because the jsxNode doesn't
25
+ // represent the element name with styled components
26
+ var elName = elementName || ast.JSXElement.getName(jsxNode);
27
+ if (!elName) {
28
+ return false;
29
+ }
30
+
31
+ // Only check native HTML elements, not components
32
+ if (elName[0] !== elName[0].toLowerCase()) {
33
+ return false;
34
+ }
35
+ var supportedElement = supportedElements.find(function (_ref) {
36
+ var name = _ref.name;
37
+ return name === elName;
38
+ });
39
+ if (!supportedElement) {
40
+ supportedElement = supportedElements.find(function (_ref2) {
41
+ var name = _ref2.name;
42
+ return name === '*';
43
+ });
44
+ }
45
+ if (!supportedElement) {
46
+ return false;
47
+ }
48
+
49
+ // Check if the element has any attributes that are not supported
50
+ var attributes = ast.JSXElement.getAttributes(jsxNode);
51
+ if (supportedElement.attributes && !supportedElement.attributes.every(function (_ref3) {
52
+ var name = _ref3.name,
53
+ values = _ref3.values;
54
+ return attributes.some(function (attribute) {
55
+ if (attribute.type === 'JSXSpreadAttribute') {
56
+ return false;
57
+ }
58
+ var isMatchingName = attribute.name.name === name;
59
+ 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));
60
+ return isMatchingName && isMatchingValues;
61
+ });
62
+ })) {
63
+ return false;
64
+ }
65
+ return true;
66
+ }
@@ -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::1602f6349577ef6ff9950bcc26efd668>>
3
+ * @codegen <<SignedSource::70743df3608ca82213ea54b79fedd264>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
 
@@ -31,6 +31,8 @@ export default {
31
31
  '@atlaskit/design-system/no-html-radio': 'warn',
32
32
  '@atlaskit/design-system/no-html-range': 'warn',
33
33
  '@atlaskit/design-system/no-html-select': 'warn',
34
+ '@atlaskit/design-system/no-html-text-input': 'warn',
35
+ '@atlaskit/design-system/no-html-textarea': 'warn',
34
36
  '@atlaskit/design-system/no-invalid-css-map': ['error', {
35
37
  allowedFunctionCalls: [['@atlaskit/tokens', 'token']]
36
38
  }],
@@ -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::5d725e14bffb8d9a0fef48552880717c>>
3
+ * @codegen <<SignedSource::a0aebeced02b3d5507a0aa23fe8ff1b2>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
 
@@ -30,6 +30,8 @@ export default {
30
30
  '@atlaskit/design-system/no-html-radio': 'warn',
31
31
  '@atlaskit/design-system/no-html-range': 'warn',
32
32
  '@atlaskit/design-system/no-html-select': 'warn',
33
+ '@atlaskit/design-system/no-html-text-input': 'warn',
34
+ '@atlaskit/design-system/no-html-textarea': 'warn',
33
35
  '@atlaskit/design-system/no-invalid-css-map': ['error', {
34
36
  allowedFunctionCalls: [['@atlaskit/tokens', 'token']]
35
37
  }],
@@ -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::6dd8cd1a75ab5e45935592d62f64875e>>
3
+ * @codegen <<SignedSource::af96131434aa77c7882ceb200e4e2cf1>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
 
@@ -26,6 +26,8 @@ export default {
26
26
  '@atlaskit/design-system/no-html-radio': 'warn',
27
27
  '@atlaskit/design-system/no-html-range': 'warn',
28
28
  '@atlaskit/design-system/no-html-select': 'warn',
29
+ '@atlaskit/design-system/no-html-text-input': 'warn',
30
+ '@atlaskit/design-system/no-html-textarea': 'warn',
29
31
  '@atlaskit/design-system/no-invalid-css-map': ['error', {
30
32
  allowedFunctionCalls: [['@atlaskit/tokens', 'token']]
31
33
  }],
@@ -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::fc8d439406a5af0011226104bfcb66b5>>
3
+ * @codegen <<SignedSource::4bb65ab8e5c1cad968c6e74d7179e58b>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
 
@@ -25,6 +25,8 @@ export default {
25
25
  '@atlaskit/design-system/no-html-radio': 'warn',
26
26
  '@atlaskit/design-system/no-html-range': 'warn',
27
27
  '@atlaskit/design-system/no-html-select': 'warn',
28
+ '@atlaskit/design-system/no-html-text-input': 'warn',
29
+ '@atlaskit/design-system/no-html-textarea': 'warn',
28
30
  '@atlaskit/design-system/no-invalid-css-map': ['error', {
29
31
  allowedFunctionCalls: [['@atlaskit/tokens', 'token']]
30
32
  }],
@@ -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::c2467b3710c61a46d8227d23aaa8b5cf>>
3
+ * @codegen <<SignedSource::be1696f2cf03313e918f93d6414aafd3>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
  import consistentCssPropUsage from './consistent-css-prop-usage';
@@ -29,6 +29,8 @@ import noHtmlImage from './no-html-image';
29
29
  import noHtmlRadio from './no-html-radio';
30
30
  import noHtmlRange from './no-html-range';
31
31
  import noHtmlSelect from './no-html-select';
32
+ import noHtmlTextInput from './no-html-text-input';
33
+ import noHtmlTextarea from './no-html-textarea';
32
34
  import noInvalidCssMap from './no-invalid-css-map';
33
35
  import noKeyframesTaggedTemplateExpression from './no-keyframes-tagged-template-expression';
34
36
  import noLegacyIcons from './no-legacy-icons';
@@ -88,6 +90,8 @@ export const rules = {
88
90
  'no-html-radio': noHtmlRadio,
89
91
  'no-html-range': noHtmlRange,
90
92
  'no-html-select': noHtmlSelect,
93
+ 'no-html-text-input': noHtmlTextInput,
94
+ 'no-html-textarea': noHtmlTextarea,
91
95
  'no-invalid-css-map': noInvalidCssMap,
92
96
  'no-keyframes-tagged-template-expression': noKeyframesTaggedTemplateExpression,
93
97
  'no-legacy-icons': noLegacyIcons,
@@ -0,0 +1,28 @@
1
+ import { createLintRule } from '../utils/create-rule';
2
+ import { JSXElement } from './node-types';
3
+ const rule = createLintRule({
4
+ meta: {
5
+ name: 'no-html-text-input',
6
+ type: 'suggestion',
7
+ hasSuggestions: true,
8
+ docs: {
9
+ description: 'Discourage direct usage of HTML text input elements in favor of the Atlassian Design System textfield component.',
10
+ recommended: true,
11
+ severity: 'warn'
12
+ },
13
+ messages: {
14
+ noHtmlTextInput: `This <{{ name }}> should be replaced with a textfield component from the Atlassian Design System. ADS components include event tracking, ensure accessible implementations, and provide access to ADS styling features like design tokens.`
15
+ }
16
+ },
17
+ create(context) {
18
+ return {
19
+ // transforms <input type="range" css={...}> usages
20
+ JSXElement(node) {
21
+ JSXElement.lint(node, {
22
+ context
23
+ });
24
+ }
25
+ };
26
+ }
27
+ });
28
+ export default rule;
@@ -0,0 +1 @@
1
+ export { JSXElement } from './jsx-element';
@@ -0,0 +1,80 @@
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 existingTextfieldName = 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/textfield') {
27
+ const defaultSpecifier = declaration.specifiers.find(specifier => specifier.type === 'ImportDefaultSpecifier');
28
+ if (defaultSpecifier) {
29
+ existingTextfieldName = 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 textfieldName = existingTextfieldName || generateUniqueName('Textfield');
43
+ context.report({
44
+ node: node.openingElement,
45
+ messageId: 'noHtmlTextInput',
46
+ data: {
47
+ name: nodeName
48
+ },
49
+ suggest: [{
50
+ desc: 'Replace with Textfield component from @atlaskit/textfield',
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 attributesText = node.openingElement.attributes.filter(attr => !isNodeOfType(attr, 'JSXAttribute') || attr.name.name !== 'type').map(attr => sourceCode.getText(attr)).join(' ');
56
+ const fixers = [];
57
+
58
+ // Replace <img> with <textfield> and retain attributes
59
+ if (openingTagRange) {
60
+ if (node.openingElement.selfClosing) {
61
+ fixers.push(fixer.replaceTextRange([openingTagRange[0] + 1, openingTagRange[1] - 1], `${textfieldName}${attributesText ? ` ${attributesText}` : ''} /`));
62
+ } else {
63
+ fixers.push(fixer.replaceTextRange([openingTagRange[0] + 1, openingTagRange[1] - 1], `${textfieldName}${attributesText ? ` ${attributesText}` : ''}`));
64
+ }
65
+ }
66
+ if (closingTagRange && !node.openingElement.selfClosing) {
67
+ fixers.push(fixer.replaceTextRange([closingTagRange[0] + 2, closingTagRange[1] - 1], textfieldName));
68
+ }
69
+
70
+ // Add import if not present
71
+ if (!existingTextfieldName) {
72
+ const importStatement = `import ${textfieldName} from '@atlaskit/textfield';\n`;
73
+ fixers.push(fixer.insertTextBefore(sourceCode.ast, importStatement));
74
+ }
75
+ return fixers;
76
+ }
77
+ }]
78
+ });
79
+ }
80
+ };
@@ -0,0 +1,79 @@
1
+ import { isNodeOfType } from 'eslint-codemod-utils';
2
+ import * as ast from '../../../ast-nodes';
3
+ const supportedElements = [{
4
+ name: 'input',
5
+ attributes: [{
6
+ name: 'type',
7
+ values: ['text'],
8
+ canBeUndefined: true
9
+ }]
10
+ }];
11
+
12
+ /**
13
+ * Determines if the given JSX element is a supported element to lint with this rule.
14
+ */
15
+ export function isSupportedForLint(jsxNode, elementName) {
16
+ if (!isNodeOfType(jsxNode, 'JSXElement')) {
17
+ return false;
18
+ }
19
+
20
+ // Allow passing in the element name because the jsxNode doesn't
21
+ // represent the element name with styled components
22
+ const elName = elementName || ast.JSXElement.getName(jsxNode);
23
+ if (!elName) {
24
+ return false;
25
+ }
26
+
27
+ // Only check native HTML elements, not components
28
+ if (elName[0] !== elName[0].toLowerCase()) {
29
+ return false;
30
+ }
31
+ let supportedElement = supportedElements.find(({
32
+ name
33
+ }) => name === elName);
34
+ if (!supportedElement) {
35
+ supportedElement = supportedElements.find(({
36
+ name
37
+ }) => name === '*');
38
+ }
39
+ if (!supportedElement) {
40
+ return false;
41
+ }
42
+
43
+ // Check if the element has any attributes that are not supported
44
+ const attributes = ast.JSXElement.getAttributes(jsxNode);
45
+ if (supportedElement.attributes &&
46
+ // If not every attribute resolves to `true`
47
+ !supportedElement.attributes.every(({
48
+ name,
49
+ values,
50
+ canBeUndefined
51
+ }) => {
52
+ // If it can be an undefined prop
53
+ if (canBeUndefined) {
54
+ // Search attributes for `name`
55
+ const foundAttribute = attributes.find(attr => isNodeOfType(attr, 'JSXAttribute') && attr.name.name === name);
56
+ // If we didn't find that attribute, then it should be linted
57
+ if (!foundAttribute) {
58
+ return true;
59
+ }
60
+ }
61
+
62
+ // If we did find the attribute with a matching name, then check if one of
63
+ // it's values matches
64
+ return attributes.some(attribute => {
65
+ // Don't lint spreads
66
+ if (attribute.type === 'JSXSpreadAttribute') {
67
+ return false;
68
+ }
69
+ const isMatchingName = attribute.name.name === name;
70
+ 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));
71
+
72
+ // If one of our values match the attribute's value, lint it
73
+ return isMatchingName && isMatchingValues;
74
+ });
75
+ })) {
76
+ return false;
77
+ }
78
+ return true;
79
+ }
@@ -0,0 +1,34 @@
1
+ import { createLintRule } from '../utils/create-rule';
2
+ import { JSXElement, StyledComponent } from './node-types';
3
+ const rule = createLintRule({
4
+ meta: {
5
+ name: 'no-html-textarea',
6
+ type: 'suggestion',
7
+ hasSuggestions: true,
8
+ docs: {
9
+ description: 'Discourage direct usage of HTML textarea elements in favor of the Atlassian Design System textarea component.',
10
+ recommended: true,
11
+ severity: 'warn'
12
+ },
13
+ messages: {
14
+ noHtmlTextarea: `This <{{ name }}> should be replaced with a textarea component from the Atlassian Design System. ADS components include event tracking, ensure accessible implementations, and provide access to ADS styling features like design tokens.`
15
+ }
16
+ },
17
+ create(context) {
18
+ return {
19
+ // transforms styled.<textarea>(...) usages
20
+ CallExpression(node) {
21
+ StyledComponent.lint(node, {
22
+ context
23
+ });
24
+ },
25
+ // transforms <textarea css={...}> usages
26
+ JSXElement(node) {
27
+ JSXElement.lint(node, {
28
+ context
29
+ });
30
+ }
31
+ };
32
+ }
33
+ });
34
+ export default rule;
@@ -0,0 +1,2 @@
1
+ export { StyledComponent } from './styled-component';
2
+ export { JSXElement } from './jsx-element';
@@ -0,0 +1,79 @@
1
+ import { getSourceCode } from '@atlaskit/eslint-utils/context-compat';
2
+ import * as ast from '../../../../ast-nodes';
3
+ import { isSupportedForLint } from '../supported';
4
+ function isImportDeclaration(node) {
5
+ return node.type === 'ImportDeclaration';
6
+ }
7
+ export const JSXElement = {
8
+ lint(node, {
9
+ context
10
+ }) {
11
+ if (!isSupportedForLint(node)) {
12
+ return;
13
+ }
14
+ const nodeName = ast.JSXElement.getName(node);
15
+ const sourceCode = getSourceCode(context);
16
+ const importDeclarations = sourceCode.ast.body.filter(isImportDeclaration);
17
+ let existingTextareaName = null;
18
+ const usedNames = new Set();
19
+
20
+ // Check for existing imports
21
+ for (const declaration of importDeclarations) {
22
+ for (const specifier of declaration.specifiers) {
23
+ usedNames.add(specifier.local.name);
24
+ }
25
+ if (declaration.source.value === '@atlaskit/textarea') {
26
+ const defaultSpecifier = declaration.specifiers.find(specifier => specifier.type === 'ImportDefaultSpecifier');
27
+ if (defaultSpecifier) {
28
+ existingTextareaName = defaultSpecifier.local.name;
29
+ }
30
+ }
31
+ }
32
+ const generateUniqueName = baseName => {
33
+ let index = 1;
34
+ let newName = baseName;
35
+ while (usedNames.has(newName)) {
36
+ newName = `${baseName}${index}`;
37
+ index++;
38
+ }
39
+ return newName;
40
+ };
41
+ const textareaName = existingTextareaName || generateUniqueName('Textarea');
42
+ context.report({
43
+ node: node.openingElement,
44
+ messageId: 'noHtmlTextarea',
45
+ data: {
46
+ name: nodeName
47
+ },
48
+ suggest: [{
49
+ desc: 'Replace with Textarea component from @atlaskit/textarea',
50
+ fix(fixer) {
51
+ var _node$closingElement;
52
+ const openingTagRange = node.openingElement.range;
53
+ const closingTagRange = (_node$closingElement = node.closingElement) === null || _node$closingElement === void 0 ? void 0 : _node$closingElement.range;
54
+ const attributesText = node.openingElement.attributes.map(attr => sourceCode.getText(attr)).join(' ');
55
+ const fixers = [];
56
+
57
+ // Replace <textarea> with <Textarea> and retain attributes
58
+ if (openingTagRange) {
59
+ if (node.openingElement.selfClosing) {
60
+ fixers.push(fixer.replaceTextRange([openingTagRange[0] + 1, openingTagRange[1] - 1], `${textareaName}${attributesText ? ` ${attributesText}` : ''} /`));
61
+ } else {
62
+ fixers.push(fixer.replaceTextRange([openingTagRange[0] + 1, openingTagRange[1] - 1], `${textareaName}${attributesText ? ` ${attributesText}` : ''}`));
63
+ }
64
+ }
65
+ if (closingTagRange && !node.openingElement.selfClosing) {
66
+ fixers.push(fixer.replaceTextRange([closingTagRange[0] + 2, closingTagRange[1] - 1], textareaName));
67
+ }
68
+
69
+ // Add import if not present
70
+ if (!existingTextareaName) {
71
+ const importStatement = `import ${textareaName} from '@atlaskit/textarea';\n`;
72
+ fixers.push(fixer.insertTextBefore(sourceCode.ast, importStatement));
73
+ }
74
+ return fixers;
75
+ }
76
+ }]
77
+ });
78
+ }
79
+ };
@@ -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 textarea or not
24
+ return;
25
+ }
26
+ if (jsxElement && !isSupportedForLint(jsxElement, elementName)) {
27
+ return;
28
+ }
29
+ context.report({
30
+ node: styles,
31
+ messageId: 'noHtmlTextarea',
32
+ data: {
33
+ name: node.callee.property.name
34
+ }
35
+ });
36
+ }
37
+ };
@@ -0,0 +1,56 @@
1
+ import { isNodeOfType } from 'eslint-codemod-utils';
2
+ import * as ast from '../../../ast-nodes';
3
+ const supportedElements = [{
4
+ name: 'textarea'
5
+ }];
6
+
7
+ /**
8
+ * Determines if the given JSX element is a supported element to lint with this rule.
9
+ */
10
+ export function isSupportedForLint(jsxNode, elementName) {
11
+ if (!isNodeOfType(jsxNode, 'JSXElement')) {
12
+ return false;
13
+ }
14
+
15
+ // Allow passing in the element name because the jsxNode doesn't
16
+ // represent the element name with styled components
17
+ const elName = elementName || ast.JSXElement.getName(jsxNode);
18
+ if (!elName) {
19
+ return false;
20
+ }
21
+
22
+ // Only check native HTML elements, not components
23
+ if (elName[0] !== elName[0].toLowerCase()) {
24
+ return false;
25
+ }
26
+ let supportedElement = supportedElements.find(({
27
+ name
28
+ }) => name === elName);
29
+ if (!supportedElement) {
30
+ supportedElement = supportedElements.find(({
31
+ name
32
+ }) => name === '*');
33
+ }
34
+ if (!supportedElement) {
35
+ return false;
36
+ }
37
+
38
+ // Check if the element has any attributes that are not supported
39
+ const attributes = ast.JSXElement.getAttributes(jsxNode);
40
+ if (supportedElement.attributes && !supportedElement.attributes.every(({
41
+ name,
42
+ values
43
+ }) => {
44
+ return attributes.some(attribute => {
45
+ if (attribute.type === 'JSXSpreadAttribute') {
46
+ return false;
47
+ }
48
+ const isMatchingName = attribute.name.name === name;
49
+ 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));
50
+ return isMatchingName && isMatchingValues;
51
+ });
52
+ })) {
53
+ return false;
54
+ }
55
+ return true;
56
+ }
@@ -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::1602f6349577ef6ff9950bcc26efd668>>
3
+ * @codegen <<SignedSource::70743df3608ca82213ea54b79fedd264>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
 
@@ -31,6 +31,8 @@ export default {
31
31
  '@atlaskit/design-system/no-html-radio': 'warn',
32
32
  '@atlaskit/design-system/no-html-range': 'warn',
33
33
  '@atlaskit/design-system/no-html-select': 'warn',
34
+ '@atlaskit/design-system/no-html-text-input': 'warn',
35
+ '@atlaskit/design-system/no-html-textarea': 'warn',
34
36
  '@atlaskit/design-system/no-invalid-css-map': ['error', {
35
37
  allowedFunctionCalls: [['@atlaskit/tokens', 'token']]
36
38
  }],
@@ -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::5d725e14bffb8d9a0fef48552880717c>>
3
+ * @codegen <<SignedSource::a0aebeced02b3d5507a0aa23fe8ff1b2>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
 
@@ -30,6 +30,8 @@ export default {
30
30
  '@atlaskit/design-system/no-html-radio': 'warn',
31
31
  '@atlaskit/design-system/no-html-range': 'warn',
32
32
  '@atlaskit/design-system/no-html-select': 'warn',
33
+ '@atlaskit/design-system/no-html-text-input': 'warn',
34
+ '@atlaskit/design-system/no-html-textarea': 'warn',
33
35
  '@atlaskit/design-system/no-invalid-css-map': ['error', {
34
36
  allowedFunctionCalls: [['@atlaskit/tokens', 'token']]
35
37
  }],
@@ -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::6dd8cd1a75ab5e45935592d62f64875e>>
3
+ * @codegen <<SignedSource::af96131434aa77c7882ceb200e4e2cf1>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
 
@@ -26,6 +26,8 @@ export default {
26
26
  '@atlaskit/design-system/no-html-radio': 'warn',
27
27
  '@atlaskit/design-system/no-html-range': 'warn',
28
28
  '@atlaskit/design-system/no-html-select': 'warn',
29
+ '@atlaskit/design-system/no-html-text-input': 'warn',
30
+ '@atlaskit/design-system/no-html-textarea': 'warn',
29
31
  '@atlaskit/design-system/no-invalid-css-map': ['error', {
30
32
  allowedFunctionCalls: [['@atlaskit/tokens', 'token']]
31
33
  }],
@@ -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::fc8d439406a5af0011226104bfcb66b5>>
3
+ * @codegen <<SignedSource::4bb65ab8e5c1cad968c6e74d7179e58b>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
 
@@ -25,6 +25,8 @@ export default {
25
25
  '@atlaskit/design-system/no-html-radio': 'warn',
26
26
  '@atlaskit/design-system/no-html-range': 'warn',
27
27
  '@atlaskit/design-system/no-html-select': 'warn',
28
+ '@atlaskit/design-system/no-html-text-input': 'warn',
29
+ '@atlaskit/design-system/no-html-textarea': 'warn',
28
30
  '@atlaskit/design-system/no-invalid-css-map': ['error', {
29
31
  allowedFunctionCalls: [['@atlaskit/tokens', 'token']]
30
32
  }],
@@ -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::c2467b3710c61a46d8227d23aaa8b5cf>>
3
+ * @codegen <<SignedSource::be1696f2cf03313e918f93d6414aafd3>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
  import consistentCssPropUsage from './consistent-css-prop-usage';
@@ -29,6 +29,8 @@ import noHtmlImage from './no-html-image';
29
29
  import noHtmlRadio from './no-html-radio';
30
30
  import noHtmlRange from './no-html-range';
31
31
  import noHtmlSelect from './no-html-select';
32
+ import noHtmlTextInput from './no-html-text-input';
33
+ import noHtmlTextarea from './no-html-textarea';
32
34
  import noInvalidCssMap from './no-invalid-css-map';
33
35
  import noKeyframesTaggedTemplateExpression from './no-keyframes-tagged-template-expression';
34
36
  import noLegacyIcons from './no-legacy-icons';
@@ -88,6 +90,8 @@ export var rules = {
88
90
  'no-html-radio': noHtmlRadio,
89
91
  'no-html-range': noHtmlRange,
90
92
  'no-html-select': noHtmlSelect,
93
+ 'no-html-text-input': noHtmlTextInput,
94
+ 'no-html-textarea': noHtmlTextarea,
91
95
  'no-invalid-css-map': noInvalidCssMap,
92
96
  'no-keyframes-tagged-template-expression': noKeyframesTaggedTemplateExpression,
93
97
  'no-legacy-icons': noLegacyIcons,