@atlaskit/eslint-plugin-design-system 13.28.0 → 13.29.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 (33) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +1 -0
  3. package/dist/cjs/presets/all-flat.codegen.js +2 -1
  4. package/dist/cjs/presets/all.codegen.js +2 -1
  5. package/dist/cjs/presets/recommended-flat.codegen.js +2 -1
  6. package/dist/cjs/presets/recommended.codegen.js +2 -1
  7. package/dist/cjs/rules/index.codegen.js +3 -1
  8. package/dist/cjs/rules/use-character-counter-field/index.js +139 -0
  9. package/dist/es2019/presets/all-flat.codegen.js +2 -1
  10. package/dist/es2019/presets/all.codegen.js +2 -1
  11. package/dist/es2019/presets/recommended-flat.codegen.js +2 -1
  12. package/dist/es2019/presets/recommended.codegen.js +2 -1
  13. package/dist/es2019/rules/index.codegen.js +3 -1
  14. package/dist/es2019/rules/use-character-counter-field/index.js +131 -0
  15. package/dist/esm/presets/all-flat.codegen.js +2 -1
  16. package/dist/esm/presets/all.codegen.js +2 -1
  17. package/dist/esm/presets/recommended-flat.codegen.js +2 -1
  18. package/dist/esm/presets/recommended.codegen.js +2 -1
  19. package/dist/esm/rules/index.codegen.js +3 -1
  20. package/dist/esm/rules/use-character-counter-field/index.js +133 -0
  21. package/dist/types/presets/all-flat.codegen.d.ts +1 -1
  22. package/dist/types/presets/all.codegen.d.ts +1 -1
  23. package/dist/types/presets/recommended-flat.codegen.d.ts +1 -1
  24. package/dist/types/presets/recommended.codegen.d.ts +1 -1
  25. package/dist/types/rules/index.codegen.d.ts +1 -1
  26. package/dist/types/rules/use-character-counter-field/index.d.ts +5 -0
  27. package/dist/types-ts4.5/presets/all-flat.codegen.d.ts +1 -1
  28. package/dist/types-ts4.5/presets/all.codegen.d.ts +1 -1
  29. package/dist/types-ts4.5/presets/recommended-flat.codegen.d.ts +1 -1
  30. package/dist/types-ts4.5/presets/recommended.codegen.d.ts +1 -1
  31. package/dist/types-ts4.5/rules/index.codegen.d.ts +1 -1
  32. package/dist/types-ts4.5/rules/use-character-counter-field/index.d.ts +5 -0
  33. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @atlaskit/eslint-plugin-design-system
2
2
 
3
+ ## 13.29.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`3e6f7b52a2689`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/3e6f7b52a2689) -
8
+ Add rule to suggest usage of CharacterCounterField when using Textfield or Textarea with
9
+ minLength/maxLength properties
10
+
3
11
  ## 13.28.0
4
12
 
5
13
  ### Minor Changes
package/README.md CHANGED
@@ -98,6 +98,7 @@ module.exports = {
98
98
  | <a href="./packages/design-system/eslint-plugin/src/rules/no-utility-icons/README.md">no-utility-icons</a> | Disallow use of deprecated utility icons, in favor of core icons with `size="small"`. | Yes | Yes | Yes |
99
99
  | <a href="./packages/design-system/eslint-plugin/src/rules/prefer-primitives/README.md">prefer-primitives</a> | Increase awareness of primitive components via code hints. Strictly used for education purposes and discoverability. To enforce usage please refer to the `use-primitives` rule. | | | |
100
100
  | <a href="./packages/design-system/eslint-plugin/src/rules/use-button-group-label/README.md">use-button-group-label</a> | Ensures button groups are described to assistive technology by a direct label or by another element. | Yes | | Yes |
101
+ | <a href="./packages/design-system/eslint-plugin/src/rules/use-character-counter-field/README.md">use-character-counter-field</a> | Suggests using CharacterCounterField when Textfield or Textarea components have maxLength or minLength props within a Form. | Yes | | |
101
102
  | <a href="./packages/design-system/eslint-plugin/src/rules/use-correct-field/README.md">use-correct-field</a> | Ensure makers use appropriate field component for their respective form elements. | Yes | Yes | Yes |
102
103
  | <a href="./packages/design-system/eslint-plugin/src/rules/use-cx-function-in-xcss/README.md">use-cx-function-in-xcss</a> | Enforces cx function use to combine styles in xcss. | Yes | Yes | Yes |
103
104
  | <a href="./packages/design-system/eslint-plugin/src/rules/use-datetime-picker-calendar-button/README.md">use-datetime-picker-calendar-button</a> | Encourages makers to use calendar button in Atlassian Design System's date picker and datetime picker components. | Yes | Yes | Yes |
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.default = void 0;
7
7
  /**
8
8
  * THIS FILE WAS CREATED VIA CODEGEN DO NOT MODIFY {@see http://go/af-codegen}
9
- * @codegen <<SignedSource::8ba8f062c0c8a49b3776a111d41fa3bf>>
9
+ * @codegen <<SignedSource::131350ebc877f42247042c6e7327fe18>>
10
10
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
11
11
  */
12
12
 
@@ -62,6 +62,7 @@ var rules = {
62
62
  '@atlaskit/design-system/no-utility-icons': 'warn',
63
63
  '@atlaskit/design-system/prefer-primitives': 'warn',
64
64
  '@atlaskit/design-system/use-button-group-label': 'warn',
65
+ '@atlaskit/design-system/use-character-counter-field': 'warn',
65
66
  '@atlaskit/design-system/use-correct-field': 'warn',
66
67
  '@atlaskit/design-system/use-cx-function-in-xcss': 'error',
67
68
  '@atlaskit/design-system/use-datetime-picker-calendar-button': 'warn',
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.default = void 0;
7
7
  /**
8
8
  * THIS FILE WAS CREATED VIA CODEGEN DO NOT MODIFY {@see http://go/af-codegen}
9
- * @codegen <<SignedSource::e83747a5a69e6ae7443c198f4cc990b0>>
9
+ * @codegen <<SignedSource::b3ddf9ee0a27aaf8a318a6579d48ce99>>
10
10
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
11
11
  */
12
12
 
@@ -61,6 +61,7 @@ var rules = {
61
61
  '@atlaskit/design-system/no-utility-icons': 'warn',
62
62
  '@atlaskit/design-system/prefer-primitives': 'warn',
63
63
  '@atlaskit/design-system/use-button-group-label': 'warn',
64
+ '@atlaskit/design-system/use-character-counter-field': 'warn',
64
65
  '@atlaskit/design-system/use-correct-field': 'warn',
65
66
  '@atlaskit/design-system/use-cx-function-in-xcss': 'error',
66
67
  '@atlaskit/design-system/use-datetime-picker-calendar-button': 'warn',
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.default = void 0;
7
7
  /**
8
8
  * THIS FILE WAS CREATED VIA CODEGEN DO NOT MODIFY {@see http://go/af-codegen}
9
- * @codegen <<SignedSource::ee679166d3c39a30db50d8cf45cd3cb7>>
9
+ * @codegen <<SignedSource::ba71ef89188bdc20d0ec948cc00f6436>>
10
10
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
11
11
  */
12
12
 
@@ -45,6 +45,7 @@ var rules = {
45
45
  '@atlaskit/design-system/no-unused-css-map': 'warn',
46
46
  '@atlaskit/design-system/no-utility-icons': 'warn',
47
47
  '@atlaskit/design-system/use-button-group-label': 'warn',
48
+ '@atlaskit/design-system/use-character-counter-field': 'warn',
48
49
  '@atlaskit/design-system/use-correct-field': 'warn',
49
50
  '@atlaskit/design-system/use-cx-function-in-xcss': 'error',
50
51
  '@atlaskit/design-system/use-datetime-picker-calendar-button': 'warn',
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.default = void 0;
7
7
  /**
8
8
  * THIS FILE WAS CREATED VIA CODEGEN DO NOT MODIFY {@see http://go/af-codegen}
9
- * @codegen <<SignedSource::11c250f8fdbf2787edb36243db5e3e07>>
9
+ * @codegen <<SignedSource::5c119e0ab08e1f39d471b2b827bbb4de>>
10
10
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
11
11
  */
12
12
 
@@ -44,6 +44,7 @@ var rules = {
44
44
  '@atlaskit/design-system/no-unused-css-map': 'warn',
45
45
  '@atlaskit/design-system/no-utility-icons': 'warn',
46
46
  '@atlaskit/design-system/use-button-group-label': 'warn',
47
+ '@atlaskit/design-system/use-character-counter-field': 'warn',
47
48
  '@atlaskit/design-system/use-correct-field': 'warn',
48
49
  '@atlaskit/design-system/use-cx-function-in-xcss': 'error',
49
50
  '@atlaskit/design-system/use-datetime-picker-calendar-button': 'warn',
@@ -54,6 +54,7 @@ var _noUnusedCssMap = _interopRequireDefault(require("./no-unused-css-map"));
54
54
  var _noUtilityIcons = _interopRequireDefault(require("./no-utility-icons"));
55
55
  var _preferPrimitives = _interopRequireDefault(require("./prefer-primitives"));
56
56
  var _useButtonGroupLabel = _interopRequireDefault(require("./use-button-group-label"));
57
+ var _useCharacterCounterField = _interopRequireDefault(require("./use-character-counter-field"));
57
58
  var _useCorrectField = _interopRequireDefault(require("./use-correct-field"));
58
59
  var _useCxFunctionInXcss = _interopRequireDefault(require("./use-cx-function-in-xcss"));
59
60
  var _useDatetimePickerCalendarButton = _interopRequireDefault(require("./use-datetime-picker-calendar-button"));
@@ -79,7 +80,7 @@ var _useTokensTypography = _interopRequireDefault(require("./use-tokens-typograp
79
80
  var _useVisuallyHidden = _interopRequireDefault(require("./use-visually-hidden"));
80
81
  /**
81
82
  * THIS FILE WAS CREATED VIA CODEGEN DO NOT MODIFY {@see http://go/af-codegen}
82
- * @codegen <<SignedSource::3ae0af16a58563d27e580593a73961d4>>
83
+ * @codegen <<SignedSource::02be73e0813b6f22038f0f3f2ad6865d>>
83
84
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
84
85
  */
85
86
 
@@ -133,6 +134,7 @@ var rules = exports.rules = {
133
134
  'no-utility-icons': _noUtilityIcons.default,
134
135
  'prefer-primitives': _preferPrimitives.default,
135
136
  'use-button-group-label': _useButtonGroupLabel.default,
137
+ 'use-character-counter-field': _useCharacterCounterField.default,
136
138
  'use-correct-field': _useCorrectField.default,
137
139
  'use-cx-function-in-xcss': _useCxFunctionInXcss.default,
138
140
  'use-datetime-picker-calendar-button': _useDatetimePickerCalendarButton.default,
@@ -0,0 +1,139 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.ruleName = exports.message = exports.default = void 0;
7
+ var _eslintCodemodUtils = require("eslint-codemod-utils");
8
+ var _jsxElement = require("../../ast-nodes/jsx-element");
9
+ var _createRule = require("../utils/create-rule");
10
+ var TEXTFIELD_PACKAGE = '@atlaskit/textfield';
11
+ var TEXTAREA_PACKAGE = '@atlaskit/textarea';
12
+ var FORM_PACKAGE = '@atlaskit/form';
13
+ var message = exports.message = 'When using `maxLength` or `minLength` props with Textfield/Textarea inside a Form, use `CharacterCounterField` from `@atlaskit/form` instead of Field and remove the props from the Textfield/Textarea. This ensures accessibility through real time feedback and aligns with the design system. Read more about [character counter fields](https://atlassian.design/components/form/examples#character-counter-field)';
14
+ var ruleName = exports.ruleName = __dirname.split('/').slice(-1)[0];
15
+
16
+ /**
17
+ * Check if a node is inside a Form component or Field component
18
+ */
19
+ function isInsideFormOrField(node, formComponentNames) {
20
+ var current = node;
21
+
22
+ // Traverse up the AST to find parent JSX elements
23
+ while (current) {
24
+ var parent = current.parent;
25
+ if (!parent) {
26
+ break;
27
+ }
28
+
29
+ // Check if parent is a JSXElement with a name we're tracking
30
+ if ((0, _eslintCodemodUtils.isNodeOfType)(parent, 'JSXElement') && (0, _eslintCodemodUtils.isNodeOfType)(parent.openingElement.name, 'JSXIdentifier')) {
31
+ var parentName = parent.openingElement.name.name;
32
+
33
+ // Check if this is a Form component or Field component
34
+ if (formComponentNames.has(parentName)) {
35
+ return true;
36
+ }
37
+ }
38
+
39
+ // Check if parent is inside a JSXExpressionContainer (render prop pattern)
40
+ // This handles: <Field>{({ fieldProps }) => <Textfield {...fieldProps} />}</Field>
41
+ if ((0, _eslintCodemodUtils.isNodeOfType)(parent, 'JSXExpressionContainer')) {
42
+ // Continue traversing up to find the Field/Form
43
+ current = parent;
44
+ continue;
45
+ }
46
+
47
+ // Check if parent is an arrow function (render prop)
48
+ if ((0, _eslintCodemodUtils.isNodeOfType)(parent, 'ArrowFunctionExpression')) {
49
+ current = parent;
50
+ continue;
51
+ }
52
+ current = parent;
53
+ }
54
+ return false;
55
+ }
56
+ var rule = (0, _createRule.createLintRule)({
57
+ meta: {
58
+ name: ruleName,
59
+ type: 'suggestion',
60
+ docs: {
61
+ description: 'Suggests using CharacterCounterField when Textfield or Textarea components have maxLength or minLength props within a Form.',
62
+ recommended: true,
63
+ severity: 'warn'
64
+ },
65
+ messages: {
66
+ useCharacterCounterField: message
67
+ }
68
+ },
69
+ create: function create(context) {
70
+ // Track imported component names
71
+ var importedComponents = new Map();
72
+ var formComponentNames = new Set();
73
+ return {
74
+ ImportDeclaration: function ImportDeclaration(node) {
75
+ var source = node.source.value;
76
+ if (typeof source !== 'string') {
77
+ return;
78
+ }
79
+
80
+ // Track Form package imports (Form, Field, etc.)
81
+ if (source === FORM_PACKAGE) {
82
+ node.specifiers.forEach(function (spec) {
83
+ if ((0, _eslintCodemodUtils.isNodeOfType)(spec, 'ImportDefaultSpecifier')) {
84
+ // Default import: import Form from '@atlaskit/form'
85
+ formComponentNames.add(spec.local.name);
86
+ } else if ((0, _eslintCodemodUtils.isNodeOfType)(spec, 'ImportSpecifier')) {
87
+ // Named import: import { Form, Field } from '@atlaskit/form'
88
+ formComponentNames.add(spec.local.name);
89
+ }
90
+ });
91
+ return;
92
+ }
93
+
94
+ // Track Textfield and Textarea imports
95
+ if (source === TEXTFIELD_PACKAGE || source === TEXTAREA_PACKAGE) {
96
+ var defaultImport = node.specifiers.find(function (spec) {
97
+ return (0, _eslintCodemodUtils.isNodeOfType)(spec, 'ImportDefaultSpecifier');
98
+ });
99
+ if (defaultImport && (0, _eslintCodemodUtils.isNodeOfType)(defaultImport, 'ImportDefaultSpecifier')) {
100
+ var localName = defaultImport.local.name;
101
+ importedComponents.set(localName, {
102
+ localName: localName,
103
+ package: source
104
+ });
105
+ }
106
+ }
107
+ },
108
+ JSXElement: function JSXElement(node) {
109
+ if (!(0, _eslintCodemodUtils.isNodeOfType)(node, 'JSXElement')) {
110
+ return;
111
+ }
112
+ if (!(0, _eslintCodemodUtils.isNodeOfType)(node.openingElement.name, 'JSXIdentifier')) {
113
+ return;
114
+ }
115
+ var elementName = node.openingElement.name.name;
116
+
117
+ // Check if this element is one of our tracked components
118
+ var componentInfo = importedComponents.get(elementName);
119
+ if (!componentInfo) {
120
+ return;
121
+ }
122
+
123
+ // Check if the component has maxLength or minLength props
124
+ var hasMaxLength = _jsxElement.JSXElementHelper.getAttributeByName(node, 'maxLength');
125
+ var hasMinLength = _jsxElement.JSXElementHelper.getAttributeByName(node, 'minLength');
126
+ if (hasMaxLength || hasMinLength) {
127
+ // Only warn if inside a Form or Field component
128
+ if (isInsideFormOrField(node, formComponentNames)) {
129
+ context.report({
130
+ node: node,
131
+ messageId: 'useCharacterCounterField'
132
+ });
133
+ }
134
+ }
135
+ }
136
+ };
137
+ }
138
+ });
139
+ var _default = exports.default = rule;
@@ -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::8ba8f062c0c8a49b3776a111d41fa3bf>>
3
+ * @codegen <<SignedSource::131350ebc877f42247042c6e7327fe18>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
 
@@ -56,6 +56,7 @@ const rules = {
56
56
  '@atlaskit/design-system/no-utility-icons': 'warn',
57
57
  '@atlaskit/design-system/prefer-primitives': 'warn',
58
58
  '@atlaskit/design-system/use-button-group-label': 'warn',
59
+ '@atlaskit/design-system/use-character-counter-field': 'warn',
59
60
  '@atlaskit/design-system/use-correct-field': 'warn',
60
61
  '@atlaskit/design-system/use-cx-function-in-xcss': 'error',
61
62
  '@atlaskit/design-system/use-datetime-picker-calendar-button': '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::e83747a5a69e6ae7443c198f4cc990b0>>
3
+ * @codegen <<SignedSource::b3ddf9ee0a27aaf8a318a6579d48ce99>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
 
@@ -55,6 +55,7 @@ const rules = {
55
55
  '@atlaskit/design-system/no-utility-icons': 'warn',
56
56
  '@atlaskit/design-system/prefer-primitives': 'warn',
57
57
  '@atlaskit/design-system/use-button-group-label': 'warn',
58
+ '@atlaskit/design-system/use-character-counter-field': 'warn',
58
59
  '@atlaskit/design-system/use-correct-field': 'warn',
59
60
  '@atlaskit/design-system/use-cx-function-in-xcss': 'error',
60
61
  '@atlaskit/design-system/use-datetime-picker-calendar-button': '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::ee679166d3c39a30db50d8cf45cd3cb7>>
3
+ * @codegen <<SignedSource::ba71ef89188bdc20d0ec948cc00f6436>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
 
@@ -39,6 +39,7 @@ const rules = {
39
39
  '@atlaskit/design-system/no-unused-css-map': 'warn',
40
40
  '@atlaskit/design-system/no-utility-icons': 'warn',
41
41
  '@atlaskit/design-system/use-button-group-label': 'warn',
42
+ '@atlaskit/design-system/use-character-counter-field': 'warn',
42
43
  '@atlaskit/design-system/use-correct-field': 'warn',
43
44
  '@atlaskit/design-system/use-cx-function-in-xcss': 'error',
44
45
  '@atlaskit/design-system/use-datetime-picker-calendar-button': '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::11c250f8fdbf2787edb36243db5e3e07>>
3
+ * @codegen <<SignedSource::5c119e0ab08e1f39d471b2b827bbb4de>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
 
@@ -38,6 +38,7 @@ const rules = {
38
38
  '@atlaskit/design-system/no-unused-css-map': 'warn',
39
39
  '@atlaskit/design-system/no-utility-icons': 'warn',
40
40
  '@atlaskit/design-system/use-button-group-label': 'warn',
41
+ '@atlaskit/design-system/use-character-counter-field': 'warn',
41
42
  '@atlaskit/design-system/use-correct-field': 'warn',
42
43
  '@atlaskit/design-system/use-cx-function-in-xcss': 'error',
43
44
  '@atlaskit/design-system/use-datetime-picker-calendar-button': '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::3ae0af16a58563d27e580593a73961d4>>
3
+ * @codegen <<SignedSource::02be73e0813b6f22038f0f3f2ad6865d>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
 
@@ -53,6 +53,7 @@ import noUnusedCssMap from './no-unused-css-map';
53
53
  import noUtilityIcons from './no-utility-icons';
54
54
  import preferPrimitives from './prefer-primitives';
55
55
  import useButtonGroupLabel from './use-button-group-label';
56
+ import useCharacterCounterField from './use-character-counter-field';
56
57
  import useCorrectField from './use-correct-field';
57
58
  import useCxFunctionInXcss from './use-cx-function-in-xcss';
58
59
  import useDatetimePickerCalendarButton from './use-datetime-picker-calendar-button';
@@ -126,6 +127,7 @@ export const rules = {
126
127
  'no-utility-icons': noUtilityIcons,
127
128
  'prefer-primitives': preferPrimitives,
128
129
  'use-button-group-label': useButtonGroupLabel,
130
+ 'use-character-counter-field': useCharacterCounterField,
129
131
  'use-correct-field': useCorrectField,
130
132
  'use-cx-function-in-xcss': useCxFunctionInXcss,
131
133
  'use-datetime-picker-calendar-button': useDatetimePickerCalendarButton,
@@ -0,0 +1,131 @@
1
+ import { isNodeOfType } from 'eslint-codemod-utils';
2
+ import { JSXElementHelper } from '../../ast-nodes/jsx-element';
3
+ import { createLintRule } from '../utils/create-rule';
4
+ const TEXTFIELD_PACKAGE = '@atlaskit/textfield';
5
+ const TEXTAREA_PACKAGE = '@atlaskit/textarea';
6
+ const FORM_PACKAGE = '@atlaskit/form';
7
+ export const message = 'When using `maxLength` or `minLength` props with Textfield/Textarea inside a Form, use `CharacterCounterField` from `@atlaskit/form` instead of Field and remove the props from the Textfield/Textarea. This ensures accessibility through real time feedback and aligns with the design system. Read more about [character counter fields](https://atlassian.design/components/form/examples#character-counter-field)';
8
+ export const ruleName = __dirname.split('/').slice(-1)[0];
9
+
10
+ /**
11
+ * Check if a node is inside a Form component or Field component
12
+ */
13
+ function isInsideFormOrField(node, formComponentNames) {
14
+ let current = node;
15
+
16
+ // Traverse up the AST to find parent JSX elements
17
+ while (current) {
18
+ const parent = current.parent;
19
+ if (!parent) {
20
+ break;
21
+ }
22
+
23
+ // Check if parent is a JSXElement with a name we're tracking
24
+ if (isNodeOfType(parent, 'JSXElement') && isNodeOfType(parent.openingElement.name, 'JSXIdentifier')) {
25
+ const parentName = parent.openingElement.name.name;
26
+
27
+ // Check if this is a Form component or Field component
28
+ if (formComponentNames.has(parentName)) {
29
+ return true;
30
+ }
31
+ }
32
+
33
+ // Check if parent is inside a JSXExpressionContainer (render prop pattern)
34
+ // This handles: <Field>{({ fieldProps }) => <Textfield {...fieldProps} />}</Field>
35
+ if (isNodeOfType(parent, 'JSXExpressionContainer')) {
36
+ // Continue traversing up to find the Field/Form
37
+ current = parent;
38
+ continue;
39
+ }
40
+
41
+ // Check if parent is an arrow function (render prop)
42
+ if (isNodeOfType(parent, 'ArrowFunctionExpression')) {
43
+ current = parent;
44
+ continue;
45
+ }
46
+ current = parent;
47
+ }
48
+ return false;
49
+ }
50
+ const rule = createLintRule({
51
+ meta: {
52
+ name: ruleName,
53
+ type: 'suggestion',
54
+ docs: {
55
+ description: 'Suggests using CharacterCounterField when Textfield or Textarea components have maxLength or minLength props within a Form.',
56
+ recommended: true,
57
+ severity: 'warn'
58
+ },
59
+ messages: {
60
+ useCharacterCounterField: message
61
+ }
62
+ },
63
+ create(context) {
64
+ // Track imported component names
65
+ const importedComponents = new Map();
66
+ const formComponentNames = new Set();
67
+ return {
68
+ ImportDeclaration(node) {
69
+ const source = node.source.value;
70
+ if (typeof source !== 'string') {
71
+ return;
72
+ }
73
+
74
+ // Track Form package imports (Form, Field, etc.)
75
+ if (source === FORM_PACKAGE) {
76
+ node.specifiers.forEach(spec => {
77
+ if (isNodeOfType(spec, 'ImportDefaultSpecifier')) {
78
+ // Default import: import Form from '@atlaskit/form'
79
+ formComponentNames.add(spec.local.name);
80
+ } else if (isNodeOfType(spec, 'ImportSpecifier')) {
81
+ // Named import: import { Form, Field } from '@atlaskit/form'
82
+ formComponentNames.add(spec.local.name);
83
+ }
84
+ });
85
+ return;
86
+ }
87
+
88
+ // Track Textfield and Textarea imports
89
+ if (source === TEXTFIELD_PACKAGE || source === TEXTAREA_PACKAGE) {
90
+ const defaultImport = node.specifiers.find(spec => isNodeOfType(spec, 'ImportDefaultSpecifier'));
91
+ if (defaultImport && isNodeOfType(defaultImport, 'ImportDefaultSpecifier')) {
92
+ const localName = defaultImport.local.name;
93
+ importedComponents.set(localName, {
94
+ localName,
95
+ package: source
96
+ });
97
+ }
98
+ }
99
+ },
100
+ JSXElement(node) {
101
+ if (!isNodeOfType(node, 'JSXElement')) {
102
+ return;
103
+ }
104
+ if (!isNodeOfType(node.openingElement.name, 'JSXIdentifier')) {
105
+ return;
106
+ }
107
+ const elementName = node.openingElement.name.name;
108
+
109
+ // Check if this element is one of our tracked components
110
+ const componentInfo = importedComponents.get(elementName);
111
+ if (!componentInfo) {
112
+ return;
113
+ }
114
+
115
+ // Check if the component has maxLength or minLength props
116
+ const hasMaxLength = JSXElementHelper.getAttributeByName(node, 'maxLength');
117
+ const hasMinLength = JSXElementHelper.getAttributeByName(node, 'minLength');
118
+ if (hasMaxLength || hasMinLength) {
119
+ // Only warn if inside a Form or Field component
120
+ if (isInsideFormOrField(node, formComponentNames)) {
121
+ context.report({
122
+ node,
123
+ messageId: 'useCharacterCounterField'
124
+ });
125
+ }
126
+ }
127
+ }
128
+ };
129
+ }
130
+ });
131
+ export default rule;
@@ -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::8ba8f062c0c8a49b3776a111d41fa3bf>>
3
+ * @codegen <<SignedSource::131350ebc877f42247042c6e7327fe18>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
 
@@ -56,6 +56,7 @@ var rules = {
56
56
  '@atlaskit/design-system/no-utility-icons': 'warn',
57
57
  '@atlaskit/design-system/prefer-primitives': 'warn',
58
58
  '@atlaskit/design-system/use-button-group-label': 'warn',
59
+ '@atlaskit/design-system/use-character-counter-field': 'warn',
59
60
  '@atlaskit/design-system/use-correct-field': 'warn',
60
61
  '@atlaskit/design-system/use-cx-function-in-xcss': 'error',
61
62
  '@atlaskit/design-system/use-datetime-picker-calendar-button': '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::e83747a5a69e6ae7443c198f4cc990b0>>
3
+ * @codegen <<SignedSource::b3ddf9ee0a27aaf8a318a6579d48ce99>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
 
@@ -55,6 +55,7 @@ var rules = {
55
55
  '@atlaskit/design-system/no-utility-icons': 'warn',
56
56
  '@atlaskit/design-system/prefer-primitives': 'warn',
57
57
  '@atlaskit/design-system/use-button-group-label': 'warn',
58
+ '@atlaskit/design-system/use-character-counter-field': 'warn',
58
59
  '@atlaskit/design-system/use-correct-field': 'warn',
59
60
  '@atlaskit/design-system/use-cx-function-in-xcss': 'error',
60
61
  '@atlaskit/design-system/use-datetime-picker-calendar-button': '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::ee679166d3c39a30db50d8cf45cd3cb7>>
3
+ * @codegen <<SignedSource::ba71ef89188bdc20d0ec948cc00f6436>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
 
@@ -39,6 +39,7 @@ var rules = {
39
39
  '@atlaskit/design-system/no-unused-css-map': 'warn',
40
40
  '@atlaskit/design-system/no-utility-icons': 'warn',
41
41
  '@atlaskit/design-system/use-button-group-label': 'warn',
42
+ '@atlaskit/design-system/use-character-counter-field': 'warn',
42
43
  '@atlaskit/design-system/use-correct-field': 'warn',
43
44
  '@atlaskit/design-system/use-cx-function-in-xcss': 'error',
44
45
  '@atlaskit/design-system/use-datetime-picker-calendar-button': '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::11c250f8fdbf2787edb36243db5e3e07>>
3
+ * @codegen <<SignedSource::5c119e0ab08e1f39d471b2b827bbb4de>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
 
@@ -38,6 +38,7 @@ var rules = {
38
38
  '@atlaskit/design-system/no-unused-css-map': 'warn',
39
39
  '@atlaskit/design-system/no-utility-icons': 'warn',
40
40
  '@atlaskit/design-system/use-button-group-label': 'warn',
41
+ '@atlaskit/design-system/use-character-counter-field': 'warn',
41
42
  '@atlaskit/design-system/use-correct-field': 'warn',
42
43
  '@atlaskit/design-system/use-cx-function-in-xcss': 'error',
43
44
  '@atlaskit/design-system/use-datetime-picker-calendar-button': '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::3ae0af16a58563d27e580593a73961d4>>
3
+ * @codegen <<SignedSource::02be73e0813b6f22038f0f3f2ad6865d>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
 
@@ -53,6 +53,7 @@ import noUnusedCssMap from './no-unused-css-map';
53
53
  import noUtilityIcons from './no-utility-icons';
54
54
  import preferPrimitives from './prefer-primitives';
55
55
  import useButtonGroupLabel from './use-button-group-label';
56
+ import useCharacterCounterField from './use-character-counter-field';
56
57
  import useCorrectField from './use-correct-field';
57
58
  import useCxFunctionInXcss from './use-cx-function-in-xcss';
58
59
  import useDatetimePickerCalendarButton from './use-datetime-picker-calendar-button';
@@ -126,6 +127,7 @@ export var rules = {
126
127
  'no-utility-icons': noUtilityIcons,
127
128
  'prefer-primitives': preferPrimitives,
128
129
  'use-button-group-label': useButtonGroupLabel,
130
+ 'use-character-counter-field': useCharacterCounterField,
129
131
  'use-correct-field': useCorrectField,
130
132
  'use-cx-function-in-xcss': useCxFunctionInXcss,
131
133
  'use-datetime-picker-calendar-button': useDatetimePickerCalendarButton,
@@ -0,0 +1,133 @@
1
+ import { isNodeOfType } from 'eslint-codemod-utils';
2
+ import { JSXElementHelper } from '../../ast-nodes/jsx-element';
3
+ import { createLintRule } from '../utils/create-rule';
4
+ var TEXTFIELD_PACKAGE = '@atlaskit/textfield';
5
+ var TEXTAREA_PACKAGE = '@atlaskit/textarea';
6
+ var FORM_PACKAGE = '@atlaskit/form';
7
+ export var message = 'When using `maxLength` or `minLength` props with Textfield/Textarea inside a Form, use `CharacterCounterField` from `@atlaskit/form` instead of Field and remove the props from the Textfield/Textarea. This ensures accessibility through real time feedback and aligns with the design system. Read more about [character counter fields](https://atlassian.design/components/form/examples#character-counter-field)';
8
+ export var ruleName = __dirname.split('/').slice(-1)[0];
9
+
10
+ /**
11
+ * Check if a node is inside a Form component or Field component
12
+ */
13
+ function isInsideFormOrField(node, formComponentNames) {
14
+ var current = node;
15
+
16
+ // Traverse up the AST to find parent JSX elements
17
+ while (current) {
18
+ var parent = current.parent;
19
+ if (!parent) {
20
+ break;
21
+ }
22
+
23
+ // Check if parent is a JSXElement with a name we're tracking
24
+ if (isNodeOfType(parent, 'JSXElement') && isNodeOfType(parent.openingElement.name, 'JSXIdentifier')) {
25
+ var parentName = parent.openingElement.name.name;
26
+
27
+ // Check if this is a Form component or Field component
28
+ if (formComponentNames.has(parentName)) {
29
+ return true;
30
+ }
31
+ }
32
+
33
+ // Check if parent is inside a JSXExpressionContainer (render prop pattern)
34
+ // This handles: <Field>{({ fieldProps }) => <Textfield {...fieldProps} />}</Field>
35
+ if (isNodeOfType(parent, 'JSXExpressionContainer')) {
36
+ // Continue traversing up to find the Field/Form
37
+ current = parent;
38
+ continue;
39
+ }
40
+
41
+ // Check if parent is an arrow function (render prop)
42
+ if (isNodeOfType(parent, 'ArrowFunctionExpression')) {
43
+ current = parent;
44
+ continue;
45
+ }
46
+ current = parent;
47
+ }
48
+ return false;
49
+ }
50
+ var rule = createLintRule({
51
+ meta: {
52
+ name: ruleName,
53
+ type: 'suggestion',
54
+ docs: {
55
+ description: 'Suggests using CharacterCounterField when Textfield or Textarea components have maxLength or minLength props within a Form.',
56
+ recommended: true,
57
+ severity: 'warn'
58
+ },
59
+ messages: {
60
+ useCharacterCounterField: message
61
+ }
62
+ },
63
+ create: function create(context) {
64
+ // Track imported component names
65
+ var importedComponents = new Map();
66
+ var formComponentNames = new Set();
67
+ return {
68
+ ImportDeclaration: function ImportDeclaration(node) {
69
+ var source = node.source.value;
70
+ if (typeof source !== 'string') {
71
+ return;
72
+ }
73
+
74
+ // Track Form package imports (Form, Field, etc.)
75
+ if (source === FORM_PACKAGE) {
76
+ node.specifiers.forEach(function (spec) {
77
+ if (isNodeOfType(spec, 'ImportDefaultSpecifier')) {
78
+ // Default import: import Form from '@atlaskit/form'
79
+ formComponentNames.add(spec.local.name);
80
+ } else if (isNodeOfType(spec, 'ImportSpecifier')) {
81
+ // Named import: import { Form, Field } from '@atlaskit/form'
82
+ formComponentNames.add(spec.local.name);
83
+ }
84
+ });
85
+ return;
86
+ }
87
+
88
+ // Track Textfield and Textarea imports
89
+ if (source === TEXTFIELD_PACKAGE || source === TEXTAREA_PACKAGE) {
90
+ var defaultImport = node.specifiers.find(function (spec) {
91
+ return isNodeOfType(spec, 'ImportDefaultSpecifier');
92
+ });
93
+ if (defaultImport && isNodeOfType(defaultImport, 'ImportDefaultSpecifier')) {
94
+ var localName = defaultImport.local.name;
95
+ importedComponents.set(localName, {
96
+ localName: localName,
97
+ package: source
98
+ });
99
+ }
100
+ }
101
+ },
102
+ JSXElement: function JSXElement(node) {
103
+ if (!isNodeOfType(node, 'JSXElement')) {
104
+ return;
105
+ }
106
+ if (!isNodeOfType(node.openingElement.name, 'JSXIdentifier')) {
107
+ return;
108
+ }
109
+ var elementName = node.openingElement.name.name;
110
+
111
+ // Check if this element is one of our tracked components
112
+ var componentInfo = importedComponents.get(elementName);
113
+ if (!componentInfo) {
114
+ return;
115
+ }
116
+
117
+ // Check if the component has maxLength or minLength props
118
+ var hasMaxLength = JSXElementHelper.getAttributeByName(node, 'maxLength');
119
+ var hasMinLength = JSXElementHelper.getAttributeByName(node, 'minLength');
120
+ if (hasMaxLength || hasMinLength) {
121
+ // Only warn if inside a Form or Field component
122
+ if (isInsideFormOrField(node, formComponentNames)) {
123
+ context.report({
124
+ node: node,
125
+ messageId: 'useCharacterCounterField'
126
+ });
127
+ }
128
+ }
129
+ }
130
+ };
131
+ }
132
+ });
133
+ export default rule;
@@ -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::8ba8f062c0c8a49b3776a111d41fa3bf>>
3
+ * @codegen <<SignedSource::131350ebc877f42247042c6e7327fe18>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
  import type { Linter } from 'eslint';
@@ -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::e83747a5a69e6ae7443c198f4cc990b0>>
3
+ * @codegen <<SignedSource::b3ddf9ee0a27aaf8a318a6579d48ce99>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
  import type { ESLint } from 'eslint';
@@ -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::ee679166d3c39a30db50d8cf45cd3cb7>>
3
+ * @codegen <<SignedSource::ba71ef89188bdc20d0ec948cc00f6436>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
  import type { Linter } from 'eslint';
@@ -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::11c250f8fdbf2787edb36243db5e3e07>>
3
+ * @codegen <<SignedSource::5c119e0ab08e1f39d471b2b827bbb4de>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
  import type { ESLint } from 'eslint';
@@ -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::3ae0af16a58563d27e580593a73961d4>>
3
+ * @codegen <<SignedSource::02be73e0813b6f22038f0f3f2ad6865d>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
  import type { Rule } from 'eslint';
@@ -0,0 +1,5 @@
1
+ import type { Rule } from 'eslint';
2
+ export declare const message = "When using `maxLength` or `minLength` props with Textfield/Textarea inside a Form, use `CharacterCounterField` from `@atlaskit/form` instead of Field and remove the props from the Textfield/Textarea. This ensures accessibility through real time feedback and aligns with the design system. Read more about [character counter fields](https://atlassian.design/components/form/examples#character-counter-field)";
3
+ export declare const ruleName: string;
4
+ declare const rule: Rule.RuleModule;
5
+ export default rule;
@@ -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::8ba8f062c0c8a49b3776a111d41fa3bf>>
3
+ * @codegen <<SignedSource::131350ebc877f42247042c6e7327fe18>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
  import type { Linter } from 'eslint';
@@ -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::e83747a5a69e6ae7443c198f4cc990b0>>
3
+ * @codegen <<SignedSource::b3ddf9ee0a27aaf8a318a6579d48ce99>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
  import type { ESLint } from 'eslint';
@@ -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::ee679166d3c39a30db50d8cf45cd3cb7>>
3
+ * @codegen <<SignedSource::ba71ef89188bdc20d0ec948cc00f6436>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
  import type { Linter } from 'eslint';
@@ -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::11c250f8fdbf2787edb36243db5e3e07>>
3
+ * @codegen <<SignedSource::5c119e0ab08e1f39d471b2b827bbb4de>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
  import type { ESLint } from 'eslint';
@@ -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::3ae0af16a58563d27e580593a73961d4>>
3
+ * @codegen <<SignedSource::02be73e0813b6f22038f0f3f2ad6865d>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
  import type { Rule } from 'eslint';
@@ -0,0 +1,5 @@
1
+ import type { Rule } from 'eslint';
2
+ export declare const message = "When using `maxLength` or `minLength` props with Textfield/Textarea inside a Form, use `CharacterCounterField` from `@atlaskit/form` instead of Field and remove the props from the Textfield/Textarea. This ensures accessibility through real time feedback and aligns with the design system. Read more about [character counter fields](https://atlassian.design/components/form/examples#character-counter-field)";
3
+ export declare const ruleName: string;
4
+ declare const rule: Rule.RuleModule;
5
+ export default rule;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@atlaskit/eslint-plugin-design-system",
3
3
  "description": "The essential plugin for use with the Atlassian Design System.",
4
- "version": "13.28.0",
4
+ "version": "13.29.0",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
7
7
  "publishConfig": {