@atlaskit/eslint-plugin-design-system 13.42.0 → 13.43.1
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 +18 -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-icon-spacing-prop/helpers.js +15 -3
- package/dist/cjs/rules/use-textfield-autocomplete/index.js +153 -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-icon-spacing-prop/helpers.js +11 -3
- package/dist/es2019/rules/use-textfield-autocomplete/index.js +147 -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-icon-spacing-prop/helpers.js +15 -3
- package/dist/esm/rules/use-textfield-autocomplete/index.js +147 -0
- package/dist/types/presets/all-flat.codegen.d.ts +1 -1
- package/dist/types/presets/all.codegen.d.ts +1 -1
- package/dist/types/presets/recommended-flat.codegen.d.ts +1 -1
- package/dist/types/presets/recommended.codegen.d.ts +1 -1
- package/dist/types/rules/index.codegen.d.ts +1 -1
- package/dist/types/rules/use-textfield-autocomplete/index.d.ts +4 -0
- package/dist/types-ts4.5/presets/all-flat.codegen.d.ts +1 -1
- package/dist/types-ts4.5/presets/all.codegen.d.ts +1 -1
- package/dist/types-ts4.5/presets/recommended-flat.codegen.d.ts +1 -1
- package/dist/types-ts4.5/presets/recommended.codegen.d.ts +1 -1
- package/dist/types-ts4.5/rules/index.codegen.d.ts +1 -1
- package/dist/types-ts4.5/rules/use-textfield-autocomplete/index.d.ts +4 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @atlaskit/eslint-plugin-design-system
|
|
2
2
|
|
|
3
|
+
## 13.43.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`08170da1fbf62`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/08170da1fbf62) -
|
|
8
|
+
Migrate spacing prop usages on icons to Flex wrapper
|
|
9
|
+
- [`08170da1fbf62`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/08170da1fbf62) -
|
|
10
|
+
Update codemod and eslint to handle different scenarios to migrate spacing props
|
|
11
|
+
- Updated dependencies
|
|
12
|
+
|
|
13
|
+
## 13.43.0
|
|
14
|
+
|
|
15
|
+
### Minor Changes
|
|
16
|
+
|
|
17
|
+
- [`7c66756e9392b`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/7c66756e9392b) -
|
|
18
|
+
Add use-textfield-autocomplete rule to flag inaccessible use of autocomplete prop for email, url
|
|
19
|
+
and tel text fields
|
|
20
|
+
|
|
3
21
|
## 13.42.0
|
|
4
22
|
|
|
5
23
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -121,6 +121,7 @@ module.exports = {
|
|
|
121
121
|
| <a href="./packages/design-system/eslint-plugin/src/rules/use-simple-form/README.md">use-simple-form</a> | Encourage use of simple form for better developer experience and accessibility. | Yes | | Yes |
|
|
122
122
|
| <a href="./packages/design-system/eslint-plugin/src/rules/use-spotlight-package/README.md">use-spotlight-package</a> | Discourage the use of @atlaskit/onboarding in favor of @atlaskit/spotlight. | | Yes | Yes |
|
|
123
123
|
| <a href="./packages/design-system/eslint-plugin/src/rules/use-tag-group-label/README.md">use-tag-group-label</a> | Ensures tag groups are described to assistive technology by a direct label or by another element. | Yes | | Yes |
|
|
124
|
+
| <a href="./packages/design-system/eslint-plugin/src/rules/use-textfield-autocomplete/README.md">use-textfield-autocomplete</a> | Enforce that Textfield components with type="email", "tel", or "url" have an appropriate autocomplete value for WCAG 2.2 SC 1.3.5 compliance (Identify Input Purpose). | Yes | Yes | |
|
|
124
125
|
| <a href="./packages/design-system/eslint-plugin/src/rules/use-tokens-shape/README.md">use-tokens-shape</a> | Enforces usage of shape design tokens rather than hard-coded values. | | Yes | Yes |
|
|
125
126
|
| <a href="./packages/design-system/eslint-plugin/src/rules/use-tokens-space/README.md">use-tokens-space</a> | Enforces usage of space design tokens rather than hard-coded values. | | Yes | Yes |
|
|
126
127
|
| <a href="./packages/design-system/eslint-plugin/src/rules/use-tokens-typography/README.md">use-tokens-typography</a> | Enforces usage of design tokens for typography properties rather than hard-coded values. | 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::
|
|
9
|
+
* @codegen <<SignedSource::d924c9b1f1f815e7bbe77b86ee42ad86>>
|
|
10
10
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
11
11
|
*/
|
|
12
12
|
|
|
@@ -85,6 +85,7 @@ var rules = {
|
|
|
85
85
|
'@atlaskit/design-system/use-simple-form': 'warn',
|
|
86
86
|
'@atlaskit/design-system/use-spotlight-package': 'warn',
|
|
87
87
|
'@atlaskit/design-system/use-tag-group-label': 'warn',
|
|
88
|
+
'@atlaskit/design-system/use-textfield-autocomplete': 'warn',
|
|
88
89
|
'@atlaskit/design-system/use-tokens-shape': 'error',
|
|
89
90
|
'@atlaskit/design-system/use-tokens-space': 'error',
|
|
90
91
|
'@atlaskit/design-system/use-tokens-typography': '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::
|
|
9
|
+
* @codegen <<SignedSource::9dbc4d90a7430699860bd67938cfd53b>>
|
|
10
10
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
11
11
|
*/
|
|
12
12
|
|
|
@@ -84,6 +84,7 @@ var rules = {
|
|
|
84
84
|
'@atlaskit/design-system/use-simple-form': 'warn',
|
|
85
85
|
'@atlaskit/design-system/use-spotlight-package': 'warn',
|
|
86
86
|
'@atlaskit/design-system/use-tag-group-label': 'warn',
|
|
87
|
+
'@atlaskit/design-system/use-textfield-autocomplete': 'warn',
|
|
87
88
|
'@atlaskit/design-system/use-tokens-shape': 'error',
|
|
88
89
|
'@atlaskit/design-system/use-tokens-space': 'error',
|
|
89
90
|
'@atlaskit/design-system/use-tokens-typography': '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::
|
|
9
|
+
* @codegen <<SignedSource::e38fe36e83da94463de0e055c45eac02>>
|
|
10
10
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
11
11
|
*/
|
|
12
12
|
|
|
@@ -67,6 +67,7 @@ var rules = {
|
|
|
67
67
|
'@atlaskit/design-system/use-simple-field': 'warn',
|
|
68
68
|
'@atlaskit/design-system/use-simple-form': 'warn',
|
|
69
69
|
'@atlaskit/design-system/use-tag-group-label': 'warn',
|
|
70
|
+
'@atlaskit/design-system/use-textfield-autocomplete': 'warn',
|
|
70
71
|
'@atlaskit/design-system/use-tokens-typography': 'warn',
|
|
71
72
|
'@atlaskit/design-system/use-visually-hidden': 'error'
|
|
72
73
|
}
|
|
@@ -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::
|
|
9
|
+
* @codegen <<SignedSource::a12b7837d2a4105c5d20b49d5b9b54ab>>
|
|
10
10
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
11
11
|
*/
|
|
12
12
|
|
|
@@ -66,6 +66,7 @@ var rules = {
|
|
|
66
66
|
'@atlaskit/design-system/use-simple-field': 'warn',
|
|
67
67
|
'@atlaskit/design-system/use-simple-form': 'warn',
|
|
68
68
|
'@atlaskit/design-system/use-tag-group-label': 'warn',
|
|
69
|
+
'@atlaskit/design-system/use-textfield-autocomplete': 'warn',
|
|
69
70
|
'@atlaskit/design-system/use-tokens-typography': 'warn',
|
|
70
71
|
'@atlaskit/design-system/use-visually-hidden': 'error'
|
|
71
72
|
}
|
|
@@ -77,13 +77,14 @@ var _useSimpleField = _interopRequireDefault(require("./use-simple-field"));
|
|
|
77
77
|
var _useSimpleForm = _interopRequireDefault(require("./use-simple-form"));
|
|
78
78
|
var _useSpotlightPackage = _interopRequireDefault(require("./use-spotlight-package"));
|
|
79
79
|
var _useTagGroupLabel = _interopRequireDefault(require("./use-tag-group-label"));
|
|
80
|
+
var _useTextfieldAutocomplete = _interopRequireDefault(require("./use-textfield-autocomplete"));
|
|
80
81
|
var _useTokensShape = _interopRequireDefault(require("./use-tokens-shape"));
|
|
81
82
|
var _useTokensSpace = _interopRequireDefault(require("./use-tokens-space"));
|
|
82
83
|
var _useTokensTypography = _interopRequireDefault(require("./use-tokens-typography"));
|
|
83
84
|
var _useVisuallyHidden = _interopRequireDefault(require("./use-visually-hidden"));
|
|
84
85
|
/**
|
|
85
86
|
* THIS FILE WAS CREATED VIA CODEGEN DO NOT MODIFY {@see http://go/af-codegen}
|
|
86
|
-
* @codegen <<SignedSource::
|
|
87
|
+
* @codegen <<SignedSource::aec1237f211398c8225cd256e75476ff>>
|
|
87
88
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
88
89
|
*/
|
|
89
90
|
|
|
@@ -160,6 +161,7 @@ var rules = exports.rules = {
|
|
|
160
161
|
'use-simple-form': _useSimpleForm.default,
|
|
161
162
|
'use-spotlight-package': _useSpotlightPackage.default,
|
|
162
163
|
'use-tag-group-label': _useTagGroupLabel.default,
|
|
164
|
+
'use-textfield-autocomplete': _useTextfieldAutocomplete.default,
|
|
163
165
|
'use-tokens-shape': _useTokensShape.default,
|
|
164
166
|
'use-tokens-space': _useTokensSpace.default,
|
|
165
167
|
'use-tokens-typography': _useTokensTypography.default,
|
|
@@ -122,6 +122,7 @@ function upsertFlexImport(context, fixer) {
|
|
|
122
122
|
return s.local.name;
|
|
123
123
|
});
|
|
124
124
|
specifiers.push('Flex');
|
|
125
|
+
specifiers.sort();
|
|
125
126
|
return fixer.replaceText(decl, "import { ".concat(specifiers.join(', '), " } from '").concat(FLEX_IMPORT_MODULE, "';"));
|
|
126
127
|
}
|
|
127
128
|
var nonCompiledImports = findExactImports(FLEX_IMPORT_MODULE_NON_COMPILED);
|
|
@@ -135,6 +136,7 @@ function upsertFlexImport(context, fixer) {
|
|
|
135
136
|
if (!_specifiers.includes('Flex')) {
|
|
136
137
|
_specifiers.push('Flex');
|
|
137
138
|
}
|
|
139
|
+
_specifiers.sort();
|
|
138
140
|
return fixer.replaceText(_decl, "import { ".concat(_specifiers.join(', '), " } from '").concat(FLEX_IMPORT_MODULE, "';"));
|
|
139
141
|
}
|
|
140
142
|
return ast.Root.upsertNamedImportDeclaration({
|
|
@@ -165,11 +167,20 @@ function upsertCssMapImport(context, fixer) {
|
|
|
165
167
|
}).map(function (s) {
|
|
166
168
|
return s.local.name;
|
|
167
169
|
});
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
170
|
+
specifiers.push('cssMap');
|
|
171
|
+
specifiers.sort();
|
|
171
172
|
return fixer.replaceText(decl, "import { ".concat(specifiers.join(', '), " } from '").concat(CSS_IMPORT_MODULE, "';"));
|
|
172
173
|
}
|
|
174
|
+
|
|
175
|
+
// If cssMap is already imported from another package (e.g. @compiled/react), don't add a duplicate
|
|
176
|
+
var cssMapAlreadyImported = root.some(function (node) {
|
|
177
|
+
return node.type === 'ImportDeclaration' && node.specifiers.some(function (s) {
|
|
178
|
+
return s.type === 'ImportSpecifier' && s.local.name === 'cssMap';
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
if (cssMapAlreadyImported) {
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
173
184
|
return ast.Root.upsertNamedImportDeclaration({
|
|
174
185
|
module: CSS_IMPORT_MODULE,
|
|
175
186
|
specifiers: ['cssMap']
|
|
@@ -198,6 +209,7 @@ function upsertTokenImport(context, fixer) {
|
|
|
198
209
|
return s.local.name;
|
|
199
210
|
});
|
|
200
211
|
specifiers.push('token');
|
|
212
|
+
specifiers.sort();
|
|
201
213
|
return fixer.replaceText(decl, "import { ".concat(specifiers.join(', '), " } from '").concat(TOKEN_IMPORT_MODULE, "';"));
|
|
202
214
|
}
|
|
203
215
|
return ast.Root.upsertNamedImportDeclaration({
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.ruleName = exports.default = void 0;
|
|
7
|
+
var _eslintCodemodUtils = require("eslint-codemod-utils");
|
|
8
|
+
var _jsxAttribute = require("../../ast-nodes/jsx-attribute");
|
|
9
|
+
var _jsxElement = require("../../ast-nodes/jsx-element");
|
|
10
|
+
var _createRule = require("../utils/create-rule");
|
|
11
|
+
var TEXTFIELD_PACKAGE = '@atlaskit/textfield';
|
|
12
|
+
var TYPE_PROP = 'type';
|
|
13
|
+
var AUTOCOMPLETE_PROP = 'autoComplete';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Mapping of input types to their corresponding autocomplete values
|
|
17
|
+
* Can be expanded in the future for other input types (tel, url, etc.)
|
|
18
|
+
*/
|
|
19
|
+
var TYPE_TO_AUTOCOMPLETE = {
|
|
20
|
+
email: 'email',
|
|
21
|
+
tel: 'tel',
|
|
22
|
+
url: 'url'
|
|
23
|
+
};
|
|
24
|
+
var ruleName = exports.ruleName = __dirname.split('/').slice(-1)[0];
|
|
25
|
+
var rule = (0, _createRule.createLintRule)({
|
|
26
|
+
meta: {
|
|
27
|
+
name: ruleName,
|
|
28
|
+
type: 'suggestion',
|
|
29
|
+
fixable: 'code',
|
|
30
|
+
docs: {
|
|
31
|
+
description: 'Enforce that Textfield components with type="email", "tel", or "url" have an appropriate autocomplete value for WCAG 2.2 SC 1.3.5 compliance (Identify Input Purpose).',
|
|
32
|
+
recommended: true,
|
|
33
|
+
severity: 'warn'
|
|
34
|
+
},
|
|
35
|
+
messages: {
|
|
36
|
+
missingAutocomplete: 'Textfield with type="email", "tel", or "url" should have an appropriate autocomplete value to comply with WCAG 2.2 SC 1.3.5 (Identify Input Purpose).',
|
|
37
|
+
noAutocompleteOff: 'Textfield with type="email", "tel", or "url" should not have autocomplete="off". Set it to an appropriate autocomplete value to comply with WCAG 2.2 SC 1.3.5 (Identify Input Purpose).'
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
create: function create(context) {
|
|
41
|
+
// Track locally imported Textfield component names
|
|
42
|
+
var textfieldImportNames = [];
|
|
43
|
+
return {
|
|
44
|
+
// Track imports from @atlaskit/textfield
|
|
45
|
+
ImportDeclaration: function ImportDeclaration(node) {
|
|
46
|
+
if (node.source.value !== TEXTFIELD_PACKAGE) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
node.specifiers.forEach(function (spec) {
|
|
50
|
+
// Handle: import Textfield from '@atlaskit/textfield'
|
|
51
|
+
if ((0, _eslintCodemodUtils.isNodeOfType)(spec, 'ImportDefaultSpecifier')) {
|
|
52
|
+
textfieldImportNames.push(spec.local.name);
|
|
53
|
+
}
|
|
54
|
+
// Handle: import { default as Textfield } from '@atlaskit/textfield' (edge case)
|
|
55
|
+
if ((0, _eslintCodemodUtils.isNodeOfType)(spec, 'ImportSpecifier') && spec.imported.name === 'default') {
|
|
56
|
+
textfieldImportNames.push(spec.local.name);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
JSXElement: function JSXElement(node) {
|
|
61
|
+
if (!(0, _eslintCodemodUtils.isNodeOfType)(node, 'JSXElement')) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Check if this is a Textfield component
|
|
66
|
+
if (!(0, _eslintCodemodUtils.isNodeOfType)(node.openingElement.name, 'JSXIdentifier')) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
var componentName = node.openingElement.name.name;
|
|
70
|
+
if (!textfieldImportNames.includes(componentName)) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Check for type prop
|
|
75
|
+
var typeProp = _jsxElement.JSXElementHelper.getAttributeByName(node, TYPE_PROP);
|
|
76
|
+
if (!typeProp) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Get the type value - only process if it's a static string literal
|
|
81
|
+
var typeValue = _jsxAttribute.JSXAttribute.getValue(typeProp);
|
|
82
|
+
if (!typeValue || typeValue.type !== 'Literal') {
|
|
83
|
+
// Skip dynamic types (e.g., type={inputType})
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
var typeString = String(typeValue.value);
|
|
87
|
+
if (!(typeString in TYPE_TO_AUTOCOMPLETE)) {
|
|
88
|
+
// Skip if type is not one we're checking
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
var expectedAutocomplete = TYPE_TO_AUTOCOMPLETE[typeString];
|
|
92
|
+
|
|
93
|
+
// Check for autoComplete prop
|
|
94
|
+
var autoCompleteProp = _jsxElement.JSXElementHelper.getAttributeByName(node, AUTOCOMPLETE_PROP);
|
|
95
|
+
if (!autoCompleteProp) {
|
|
96
|
+
// Missing autoComplete entirely
|
|
97
|
+
return context.report({
|
|
98
|
+
node: node,
|
|
99
|
+
messageId: 'missingAutocomplete',
|
|
100
|
+
fix: function fix(fixer) {
|
|
101
|
+
return _jsxElement.JSXElementHelper.addAttribute(node, AUTOCOMPLETE_PROP, expectedAutocomplete, fixer);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Check the autoComplete value
|
|
107
|
+
var autoCompleteValue = _jsxAttribute.JSXAttribute.getValue(autoCompleteProp);
|
|
108
|
+
if (!autoCompleteValue) {
|
|
109
|
+
// Boolean `autoComplete` has no value node — add the prop. `autoComplete=""` also yields no
|
|
110
|
+
// getValue() (shared helper skips falsy literals); we report but do not autofix to avoid
|
|
111
|
+
// inserting a duplicate attribute.
|
|
112
|
+
return context.report({
|
|
113
|
+
node: node,
|
|
114
|
+
messageId: 'missingAutocomplete',
|
|
115
|
+
fix: function fix(fixer) {
|
|
116
|
+
if (!autoCompleteProp.value) {
|
|
117
|
+
return _jsxElement.JSXElementHelper.addAttribute(node, AUTOCOMPLETE_PROP, expectedAutocomplete, fixer);
|
|
118
|
+
}
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Handle different value types
|
|
125
|
+
if (autoCompleteValue.type === 'Literal') {
|
|
126
|
+
var valueString = String(autoCompleteValue.value);
|
|
127
|
+
|
|
128
|
+
// Check for "off"
|
|
129
|
+
if (valueString === 'off') {
|
|
130
|
+
return context.report({
|
|
131
|
+
node: node,
|
|
132
|
+
messageId: 'noAutocompleteOff',
|
|
133
|
+
fix: function fix(fixer) {
|
|
134
|
+
if (autoCompleteProp.value && (0, _eslintCodemodUtils.isNodeOfType)(autoCompleteProp.value, 'Literal')) {
|
|
135
|
+
return fixer.replaceText(autoCompleteProp.value, "\"".concat(expectedAutocomplete, "\""));
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Valid autoComplete value (e.g. "email")
|
|
143
|
+
// If it is an invalid value, it will be caught by the jsx-a11y/autocomplete-valid rule
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// If it's a dynamic expression (e.g., autoComplete={someVar}), we can't statically analyze it
|
|
148
|
+
// so we allow it
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
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::
|
|
3
|
+
* @codegen <<SignedSource::d924c9b1f1f815e7bbe77b86ee42ad86>>
|
|
4
4
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -79,6 +79,7 @@ const rules = {
|
|
|
79
79
|
'@atlaskit/design-system/use-simple-form': 'warn',
|
|
80
80
|
'@atlaskit/design-system/use-spotlight-package': 'warn',
|
|
81
81
|
'@atlaskit/design-system/use-tag-group-label': 'warn',
|
|
82
|
+
'@atlaskit/design-system/use-textfield-autocomplete': 'warn',
|
|
82
83
|
'@atlaskit/design-system/use-tokens-shape': 'error',
|
|
83
84
|
'@atlaskit/design-system/use-tokens-space': 'error',
|
|
84
85
|
'@atlaskit/design-system/use-tokens-typography': '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::9dbc4d90a7430699860bd67938cfd53b>>
|
|
4
4
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -78,6 +78,7 @@ const rules = {
|
|
|
78
78
|
'@atlaskit/design-system/use-simple-form': 'warn',
|
|
79
79
|
'@atlaskit/design-system/use-spotlight-package': 'warn',
|
|
80
80
|
'@atlaskit/design-system/use-tag-group-label': 'warn',
|
|
81
|
+
'@atlaskit/design-system/use-textfield-autocomplete': 'warn',
|
|
81
82
|
'@atlaskit/design-system/use-tokens-shape': 'error',
|
|
82
83
|
'@atlaskit/design-system/use-tokens-space': 'error',
|
|
83
84
|
'@atlaskit/design-system/use-tokens-typography': '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::e38fe36e83da94463de0e055c45eac02>>
|
|
4
4
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -61,6 +61,7 @@ const rules = {
|
|
|
61
61
|
'@atlaskit/design-system/use-simple-field': 'warn',
|
|
62
62
|
'@atlaskit/design-system/use-simple-form': 'warn',
|
|
63
63
|
'@atlaskit/design-system/use-tag-group-label': 'warn',
|
|
64
|
+
'@atlaskit/design-system/use-textfield-autocomplete': 'warn',
|
|
64
65
|
'@atlaskit/design-system/use-tokens-typography': 'warn',
|
|
65
66
|
'@atlaskit/design-system/use-visually-hidden': 'error'
|
|
66
67
|
}
|
|
@@ -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::a12b7837d2a4105c5d20b49d5b9b54ab>>
|
|
4
4
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -60,6 +60,7 @@ const rules = {
|
|
|
60
60
|
'@atlaskit/design-system/use-simple-field': 'warn',
|
|
61
61
|
'@atlaskit/design-system/use-simple-form': 'warn',
|
|
62
62
|
'@atlaskit/design-system/use-tag-group-label': 'warn',
|
|
63
|
+
'@atlaskit/design-system/use-textfield-autocomplete': 'warn',
|
|
63
64
|
'@atlaskit/design-system/use-tokens-typography': 'warn',
|
|
64
65
|
'@atlaskit/design-system/use-visually-hidden': 'error'
|
|
65
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::
|
|
3
|
+
* @codegen <<SignedSource::aec1237f211398c8225cd256e75476ff>>
|
|
4
4
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -76,6 +76,7 @@ import useSimpleField from './use-simple-field';
|
|
|
76
76
|
import useSimpleForm from './use-simple-form';
|
|
77
77
|
import useSpotlightPackage from './use-spotlight-package';
|
|
78
78
|
import useTagGroupLabel from './use-tag-group-label';
|
|
79
|
+
import useTextfieldAutocomplete from './use-textfield-autocomplete';
|
|
79
80
|
import useTokensShape from './use-tokens-shape';
|
|
80
81
|
import useTokensSpace from './use-tokens-space';
|
|
81
82
|
import useTokensTypography from './use-tokens-typography';
|
|
@@ -153,6 +154,7 @@ export const rules = {
|
|
|
153
154
|
'use-simple-form': useSimpleForm,
|
|
154
155
|
'use-spotlight-package': useSpotlightPackage,
|
|
155
156
|
'use-tag-group-label': useTagGroupLabel,
|
|
157
|
+
'use-textfield-autocomplete': useTextfieldAutocomplete,
|
|
156
158
|
'use-tokens-shape': useTokensShape,
|
|
157
159
|
'use-tokens-space': useTokensSpace,
|
|
158
160
|
'use-tokens-typography': useTokensTypography,
|
|
@@ -89,6 +89,7 @@ export function upsertFlexImport(context, fixer) {
|
|
|
89
89
|
}
|
|
90
90
|
const specifiers = decl.specifiers.filter(s => s.type === 'ImportSpecifier').map(s => s.local.name);
|
|
91
91
|
specifiers.push('Flex');
|
|
92
|
+
specifiers.sort();
|
|
92
93
|
return fixer.replaceText(decl, `import { ${specifiers.join(', ')} } from '${FLEX_IMPORT_MODULE}';`);
|
|
93
94
|
}
|
|
94
95
|
const nonCompiledImports = findExactImports(FLEX_IMPORT_MODULE_NON_COMPILED);
|
|
@@ -98,6 +99,7 @@ export function upsertFlexImport(context, fixer) {
|
|
|
98
99
|
if (!specifiers.includes('Flex')) {
|
|
99
100
|
specifiers.push('Flex');
|
|
100
101
|
}
|
|
102
|
+
specifiers.sort();
|
|
101
103
|
return fixer.replaceText(decl, `import { ${specifiers.join(', ')} } from '${FLEX_IMPORT_MODULE}';`);
|
|
102
104
|
}
|
|
103
105
|
return ast.Root.upsertNamedImportDeclaration({
|
|
@@ -122,11 +124,16 @@ export function upsertCssMapImport(context, fixer) {
|
|
|
122
124
|
return undefined;
|
|
123
125
|
}
|
|
124
126
|
const specifiers = decl.specifiers.filter(s => s.type === 'ImportSpecifier').map(s => s.local.name);
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
127
|
+
specifiers.push('cssMap');
|
|
128
|
+
specifiers.sort();
|
|
128
129
|
return fixer.replaceText(decl, `import { ${specifiers.join(', ')} } from '${CSS_IMPORT_MODULE}';`);
|
|
129
130
|
}
|
|
131
|
+
|
|
132
|
+
// If cssMap is already imported from another package (e.g. @compiled/react), don't add a duplicate
|
|
133
|
+
const cssMapAlreadyImported = root.some(node => node.type === 'ImportDeclaration' && node.specifiers.some(s => s.type === 'ImportSpecifier' && s.local.name === 'cssMap'));
|
|
134
|
+
if (cssMapAlreadyImported) {
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
130
137
|
return ast.Root.upsertNamedImportDeclaration({
|
|
131
138
|
module: CSS_IMPORT_MODULE,
|
|
132
139
|
specifiers: ['cssMap']
|
|
@@ -149,6 +156,7 @@ export function upsertTokenImport(context, fixer) {
|
|
|
149
156
|
}
|
|
150
157
|
const specifiers = decl.specifiers.filter(s => s.type === 'ImportSpecifier').map(s => s.local.name);
|
|
151
158
|
specifiers.push('token');
|
|
159
|
+
specifiers.sort();
|
|
152
160
|
return fixer.replaceText(decl, `import { ${specifiers.join(', ')} } from '${TOKEN_IMPORT_MODULE}';`);
|
|
153
161
|
}
|
|
154
162
|
return ast.Root.upsertNamedImportDeclaration({
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { isNodeOfType } from 'eslint-codemod-utils';
|
|
2
|
+
import { JSXAttribute } from '../../ast-nodes/jsx-attribute';
|
|
3
|
+
import { JSXElementHelper } from '../../ast-nodes/jsx-element';
|
|
4
|
+
import { createLintRule } from '../utils/create-rule';
|
|
5
|
+
const TEXTFIELD_PACKAGE = '@atlaskit/textfield';
|
|
6
|
+
const TYPE_PROP = 'type';
|
|
7
|
+
const AUTOCOMPLETE_PROP = 'autoComplete';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Mapping of input types to their corresponding autocomplete values
|
|
11
|
+
* Can be expanded in the future for other input types (tel, url, etc.)
|
|
12
|
+
*/
|
|
13
|
+
const TYPE_TO_AUTOCOMPLETE = {
|
|
14
|
+
email: 'email',
|
|
15
|
+
tel: 'tel',
|
|
16
|
+
url: 'url'
|
|
17
|
+
};
|
|
18
|
+
export const ruleName = __dirname.split('/').slice(-1)[0];
|
|
19
|
+
const rule = createLintRule({
|
|
20
|
+
meta: {
|
|
21
|
+
name: ruleName,
|
|
22
|
+
type: 'suggestion',
|
|
23
|
+
fixable: 'code',
|
|
24
|
+
docs: {
|
|
25
|
+
description: 'Enforce that Textfield components with type="email", "tel", or "url" have an appropriate autocomplete value for WCAG 2.2 SC 1.3.5 compliance (Identify Input Purpose).',
|
|
26
|
+
recommended: true,
|
|
27
|
+
severity: 'warn'
|
|
28
|
+
},
|
|
29
|
+
messages: {
|
|
30
|
+
missingAutocomplete: 'Textfield with type="email", "tel", or "url" should have an appropriate autocomplete value to comply with WCAG 2.2 SC 1.3.5 (Identify Input Purpose).',
|
|
31
|
+
noAutocompleteOff: 'Textfield with type="email", "tel", or "url" should not have autocomplete="off". Set it to an appropriate autocomplete value to comply with WCAG 2.2 SC 1.3.5 (Identify Input Purpose).'
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
create(context) {
|
|
35
|
+
// Track locally imported Textfield component names
|
|
36
|
+
const textfieldImportNames = [];
|
|
37
|
+
return {
|
|
38
|
+
// Track imports from @atlaskit/textfield
|
|
39
|
+
ImportDeclaration(node) {
|
|
40
|
+
if (node.source.value !== TEXTFIELD_PACKAGE) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
node.specifiers.forEach(spec => {
|
|
44
|
+
// Handle: import Textfield from '@atlaskit/textfield'
|
|
45
|
+
if (isNodeOfType(spec, 'ImportDefaultSpecifier')) {
|
|
46
|
+
textfieldImportNames.push(spec.local.name);
|
|
47
|
+
}
|
|
48
|
+
// Handle: import { default as Textfield } from '@atlaskit/textfield' (edge case)
|
|
49
|
+
if (isNodeOfType(spec, 'ImportSpecifier') && spec.imported.name === 'default') {
|
|
50
|
+
textfieldImportNames.push(spec.local.name);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
},
|
|
54
|
+
JSXElement(node) {
|
|
55
|
+
if (!isNodeOfType(node, 'JSXElement')) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check if this is a Textfield component
|
|
60
|
+
if (!isNodeOfType(node.openingElement.name, 'JSXIdentifier')) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const componentName = node.openingElement.name.name;
|
|
64
|
+
if (!textfieldImportNames.includes(componentName)) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Check for type prop
|
|
69
|
+
const typeProp = JSXElementHelper.getAttributeByName(node, TYPE_PROP);
|
|
70
|
+
if (!typeProp) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Get the type value - only process if it's a static string literal
|
|
75
|
+
const typeValue = JSXAttribute.getValue(typeProp);
|
|
76
|
+
if (!typeValue || typeValue.type !== 'Literal') {
|
|
77
|
+
// Skip dynamic types (e.g., type={inputType})
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const typeString = String(typeValue.value);
|
|
81
|
+
if (!(typeString in TYPE_TO_AUTOCOMPLETE)) {
|
|
82
|
+
// Skip if type is not one we're checking
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const expectedAutocomplete = TYPE_TO_AUTOCOMPLETE[typeString];
|
|
86
|
+
|
|
87
|
+
// Check for autoComplete prop
|
|
88
|
+
const autoCompleteProp = JSXElementHelper.getAttributeByName(node, AUTOCOMPLETE_PROP);
|
|
89
|
+
if (!autoCompleteProp) {
|
|
90
|
+
// Missing autoComplete entirely
|
|
91
|
+
return context.report({
|
|
92
|
+
node,
|
|
93
|
+
messageId: 'missingAutocomplete',
|
|
94
|
+
fix(fixer) {
|
|
95
|
+
return JSXElementHelper.addAttribute(node, AUTOCOMPLETE_PROP, expectedAutocomplete, fixer);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Check the autoComplete value
|
|
101
|
+
const autoCompleteValue = JSXAttribute.getValue(autoCompleteProp);
|
|
102
|
+
if (!autoCompleteValue) {
|
|
103
|
+
// Boolean `autoComplete` has no value node — add the prop. `autoComplete=""` also yields no
|
|
104
|
+
// getValue() (shared helper skips falsy literals); we report but do not autofix to avoid
|
|
105
|
+
// inserting a duplicate attribute.
|
|
106
|
+
return context.report({
|
|
107
|
+
node,
|
|
108
|
+
messageId: 'missingAutocomplete',
|
|
109
|
+
fix(fixer) {
|
|
110
|
+
if (!autoCompleteProp.value) {
|
|
111
|
+
return JSXElementHelper.addAttribute(node, AUTOCOMPLETE_PROP, expectedAutocomplete, fixer);
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Handle different value types
|
|
119
|
+
if (autoCompleteValue.type === 'Literal') {
|
|
120
|
+
const valueString = String(autoCompleteValue.value);
|
|
121
|
+
|
|
122
|
+
// Check for "off"
|
|
123
|
+
if (valueString === 'off') {
|
|
124
|
+
return context.report({
|
|
125
|
+
node,
|
|
126
|
+
messageId: 'noAutocompleteOff',
|
|
127
|
+
fix(fixer) {
|
|
128
|
+
if (autoCompleteProp.value && isNodeOfType(autoCompleteProp.value, 'Literal')) {
|
|
129
|
+
return fixer.replaceText(autoCompleteProp.value, `"${expectedAutocomplete}"`);
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Valid autoComplete value (e.g. "email")
|
|
137
|
+
// If it is an invalid value, it will be caught by the jsx-a11y/autocomplete-valid rule
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// If it's a dynamic expression (e.g., autoComplete={someVar}), we can't statically analyze it
|
|
142
|
+
// so we allow it
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
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::
|
|
3
|
+
* @codegen <<SignedSource::d924c9b1f1f815e7bbe77b86ee42ad86>>
|
|
4
4
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -79,6 +79,7 @@ var rules = {
|
|
|
79
79
|
'@atlaskit/design-system/use-simple-form': 'warn',
|
|
80
80
|
'@atlaskit/design-system/use-spotlight-package': 'warn',
|
|
81
81
|
'@atlaskit/design-system/use-tag-group-label': 'warn',
|
|
82
|
+
'@atlaskit/design-system/use-textfield-autocomplete': 'warn',
|
|
82
83
|
'@atlaskit/design-system/use-tokens-shape': 'error',
|
|
83
84
|
'@atlaskit/design-system/use-tokens-space': 'error',
|
|
84
85
|
'@atlaskit/design-system/use-tokens-typography': '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::9dbc4d90a7430699860bd67938cfd53b>>
|
|
4
4
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -78,6 +78,7 @@ var rules = {
|
|
|
78
78
|
'@atlaskit/design-system/use-simple-form': 'warn',
|
|
79
79
|
'@atlaskit/design-system/use-spotlight-package': 'warn',
|
|
80
80
|
'@atlaskit/design-system/use-tag-group-label': 'warn',
|
|
81
|
+
'@atlaskit/design-system/use-textfield-autocomplete': 'warn',
|
|
81
82
|
'@atlaskit/design-system/use-tokens-shape': 'error',
|
|
82
83
|
'@atlaskit/design-system/use-tokens-space': 'error',
|
|
83
84
|
'@atlaskit/design-system/use-tokens-typography': '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::e38fe36e83da94463de0e055c45eac02>>
|
|
4
4
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -61,6 +61,7 @@ var rules = {
|
|
|
61
61
|
'@atlaskit/design-system/use-simple-field': 'warn',
|
|
62
62
|
'@atlaskit/design-system/use-simple-form': 'warn',
|
|
63
63
|
'@atlaskit/design-system/use-tag-group-label': 'warn',
|
|
64
|
+
'@atlaskit/design-system/use-textfield-autocomplete': 'warn',
|
|
64
65
|
'@atlaskit/design-system/use-tokens-typography': 'warn',
|
|
65
66
|
'@atlaskit/design-system/use-visually-hidden': 'error'
|
|
66
67
|
}
|
|
@@ -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::a12b7837d2a4105c5d20b49d5b9b54ab>>
|
|
4
4
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -60,6 +60,7 @@ var rules = {
|
|
|
60
60
|
'@atlaskit/design-system/use-simple-field': 'warn',
|
|
61
61
|
'@atlaskit/design-system/use-simple-form': 'warn',
|
|
62
62
|
'@atlaskit/design-system/use-tag-group-label': 'warn',
|
|
63
|
+
'@atlaskit/design-system/use-textfield-autocomplete': 'warn',
|
|
63
64
|
'@atlaskit/design-system/use-tokens-typography': 'warn',
|
|
64
65
|
'@atlaskit/design-system/use-visually-hidden': 'error'
|
|
65
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::
|
|
3
|
+
* @codegen <<SignedSource::aec1237f211398c8225cd256e75476ff>>
|
|
4
4
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -76,6 +76,7 @@ import useSimpleField from './use-simple-field';
|
|
|
76
76
|
import useSimpleForm from './use-simple-form';
|
|
77
77
|
import useSpotlightPackage from './use-spotlight-package';
|
|
78
78
|
import useTagGroupLabel from './use-tag-group-label';
|
|
79
|
+
import useTextfieldAutocomplete from './use-textfield-autocomplete';
|
|
79
80
|
import useTokensShape from './use-tokens-shape';
|
|
80
81
|
import useTokensSpace from './use-tokens-space';
|
|
81
82
|
import useTokensTypography from './use-tokens-typography';
|
|
@@ -153,6 +154,7 @@ export var rules = {
|
|
|
153
154
|
'use-simple-form': useSimpleForm,
|
|
154
155
|
'use-spotlight-package': useSpotlightPackage,
|
|
155
156
|
'use-tag-group-label': useTagGroupLabel,
|
|
157
|
+
'use-textfield-autocomplete': useTextfieldAutocomplete,
|
|
156
158
|
'use-tokens-shape': useTokensShape,
|
|
157
159
|
'use-tokens-space': useTokensSpace,
|
|
158
160
|
'use-tokens-typography': useTokensTypography,
|
|
@@ -104,6 +104,7 @@ export function upsertFlexImport(context, fixer) {
|
|
|
104
104
|
return s.local.name;
|
|
105
105
|
});
|
|
106
106
|
specifiers.push('Flex');
|
|
107
|
+
specifiers.sort();
|
|
107
108
|
return fixer.replaceText(decl, "import { ".concat(specifiers.join(', '), " } from '").concat(FLEX_IMPORT_MODULE, "';"));
|
|
108
109
|
}
|
|
109
110
|
var nonCompiledImports = findExactImports(FLEX_IMPORT_MODULE_NON_COMPILED);
|
|
@@ -117,6 +118,7 @@ export function upsertFlexImport(context, fixer) {
|
|
|
117
118
|
if (!_specifiers.includes('Flex')) {
|
|
118
119
|
_specifiers.push('Flex');
|
|
119
120
|
}
|
|
121
|
+
_specifiers.sort();
|
|
120
122
|
return fixer.replaceText(_decl, "import { ".concat(_specifiers.join(', '), " } from '").concat(FLEX_IMPORT_MODULE, "';"));
|
|
121
123
|
}
|
|
122
124
|
return ast.Root.upsertNamedImportDeclaration({
|
|
@@ -147,11 +149,20 @@ export function upsertCssMapImport(context, fixer) {
|
|
|
147
149
|
}).map(function (s) {
|
|
148
150
|
return s.local.name;
|
|
149
151
|
});
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
152
|
+
specifiers.push('cssMap');
|
|
153
|
+
specifiers.sort();
|
|
153
154
|
return fixer.replaceText(decl, "import { ".concat(specifiers.join(', '), " } from '").concat(CSS_IMPORT_MODULE, "';"));
|
|
154
155
|
}
|
|
156
|
+
|
|
157
|
+
// If cssMap is already imported from another package (e.g. @compiled/react), don't add a duplicate
|
|
158
|
+
var cssMapAlreadyImported = root.some(function (node) {
|
|
159
|
+
return node.type === 'ImportDeclaration' && node.specifiers.some(function (s) {
|
|
160
|
+
return s.type === 'ImportSpecifier' && s.local.name === 'cssMap';
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
if (cssMapAlreadyImported) {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
155
166
|
return ast.Root.upsertNamedImportDeclaration({
|
|
156
167
|
module: CSS_IMPORT_MODULE,
|
|
157
168
|
specifiers: ['cssMap']
|
|
@@ -180,6 +191,7 @@ export function upsertTokenImport(context, fixer) {
|
|
|
180
191
|
return s.local.name;
|
|
181
192
|
});
|
|
182
193
|
specifiers.push('token');
|
|
194
|
+
specifiers.sort();
|
|
183
195
|
return fixer.replaceText(decl, "import { ".concat(specifiers.join(', '), " } from '").concat(TOKEN_IMPORT_MODULE, "';"));
|
|
184
196
|
}
|
|
185
197
|
return ast.Root.upsertNamedImportDeclaration({
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { isNodeOfType } from 'eslint-codemod-utils';
|
|
2
|
+
import { JSXAttribute } from '../../ast-nodes/jsx-attribute';
|
|
3
|
+
import { JSXElementHelper } from '../../ast-nodes/jsx-element';
|
|
4
|
+
import { createLintRule } from '../utils/create-rule';
|
|
5
|
+
var TEXTFIELD_PACKAGE = '@atlaskit/textfield';
|
|
6
|
+
var TYPE_PROP = 'type';
|
|
7
|
+
var AUTOCOMPLETE_PROP = 'autoComplete';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Mapping of input types to their corresponding autocomplete values
|
|
11
|
+
* Can be expanded in the future for other input types (tel, url, etc.)
|
|
12
|
+
*/
|
|
13
|
+
var TYPE_TO_AUTOCOMPLETE = {
|
|
14
|
+
email: 'email',
|
|
15
|
+
tel: 'tel',
|
|
16
|
+
url: 'url'
|
|
17
|
+
};
|
|
18
|
+
export var ruleName = __dirname.split('/').slice(-1)[0];
|
|
19
|
+
var rule = createLintRule({
|
|
20
|
+
meta: {
|
|
21
|
+
name: ruleName,
|
|
22
|
+
type: 'suggestion',
|
|
23
|
+
fixable: 'code',
|
|
24
|
+
docs: {
|
|
25
|
+
description: 'Enforce that Textfield components with type="email", "tel", or "url" have an appropriate autocomplete value for WCAG 2.2 SC 1.3.5 compliance (Identify Input Purpose).',
|
|
26
|
+
recommended: true,
|
|
27
|
+
severity: 'warn'
|
|
28
|
+
},
|
|
29
|
+
messages: {
|
|
30
|
+
missingAutocomplete: 'Textfield with type="email", "tel", or "url" should have an appropriate autocomplete value to comply with WCAG 2.2 SC 1.3.5 (Identify Input Purpose).',
|
|
31
|
+
noAutocompleteOff: 'Textfield with type="email", "tel", or "url" should not have autocomplete="off". Set it to an appropriate autocomplete value to comply with WCAG 2.2 SC 1.3.5 (Identify Input Purpose).'
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
create: function create(context) {
|
|
35
|
+
// Track locally imported Textfield component names
|
|
36
|
+
var textfieldImportNames = [];
|
|
37
|
+
return {
|
|
38
|
+
// Track imports from @atlaskit/textfield
|
|
39
|
+
ImportDeclaration: function ImportDeclaration(node) {
|
|
40
|
+
if (node.source.value !== TEXTFIELD_PACKAGE) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
node.specifiers.forEach(function (spec) {
|
|
44
|
+
// Handle: import Textfield from '@atlaskit/textfield'
|
|
45
|
+
if (isNodeOfType(spec, 'ImportDefaultSpecifier')) {
|
|
46
|
+
textfieldImportNames.push(spec.local.name);
|
|
47
|
+
}
|
|
48
|
+
// Handle: import { default as Textfield } from '@atlaskit/textfield' (edge case)
|
|
49
|
+
if (isNodeOfType(spec, 'ImportSpecifier') && spec.imported.name === 'default') {
|
|
50
|
+
textfieldImportNames.push(spec.local.name);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
},
|
|
54
|
+
JSXElement: function JSXElement(node) {
|
|
55
|
+
if (!isNodeOfType(node, 'JSXElement')) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check if this is a Textfield component
|
|
60
|
+
if (!isNodeOfType(node.openingElement.name, 'JSXIdentifier')) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
var componentName = node.openingElement.name.name;
|
|
64
|
+
if (!textfieldImportNames.includes(componentName)) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Check for type prop
|
|
69
|
+
var typeProp = JSXElementHelper.getAttributeByName(node, TYPE_PROP);
|
|
70
|
+
if (!typeProp) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Get the type value - only process if it's a static string literal
|
|
75
|
+
var typeValue = JSXAttribute.getValue(typeProp);
|
|
76
|
+
if (!typeValue || typeValue.type !== 'Literal') {
|
|
77
|
+
// Skip dynamic types (e.g., type={inputType})
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
var typeString = String(typeValue.value);
|
|
81
|
+
if (!(typeString in TYPE_TO_AUTOCOMPLETE)) {
|
|
82
|
+
// Skip if type is not one we're checking
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
var expectedAutocomplete = TYPE_TO_AUTOCOMPLETE[typeString];
|
|
86
|
+
|
|
87
|
+
// Check for autoComplete prop
|
|
88
|
+
var autoCompleteProp = JSXElementHelper.getAttributeByName(node, AUTOCOMPLETE_PROP);
|
|
89
|
+
if (!autoCompleteProp) {
|
|
90
|
+
// Missing autoComplete entirely
|
|
91
|
+
return context.report({
|
|
92
|
+
node: node,
|
|
93
|
+
messageId: 'missingAutocomplete',
|
|
94
|
+
fix: function fix(fixer) {
|
|
95
|
+
return JSXElementHelper.addAttribute(node, AUTOCOMPLETE_PROP, expectedAutocomplete, fixer);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Check the autoComplete value
|
|
101
|
+
var autoCompleteValue = JSXAttribute.getValue(autoCompleteProp);
|
|
102
|
+
if (!autoCompleteValue) {
|
|
103
|
+
// Boolean `autoComplete` has no value node — add the prop. `autoComplete=""` also yields no
|
|
104
|
+
// getValue() (shared helper skips falsy literals); we report but do not autofix to avoid
|
|
105
|
+
// inserting a duplicate attribute.
|
|
106
|
+
return context.report({
|
|
107
|
+
node: node,
|
|
108
|
+
messageId: 'missingAutocomplete',
|
|
109
|
+
fix: function fix(fixer) {
|
|
110
|
+
if (!autoCompleteProp.value) {
|
|
111
|
+
return JSXElementHelper.addAttribute(node, AUTOCOMPLETE_PROP, expectedAutocomplete, fixer);
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Handle different value types
|
|
119
|
+
if (autoCompleteValue.type === 'Literal') {
|
|
120
|
+
var valueString = String(autoCompleteValue.value);
|
|
121
|
+
|
|
122
|
+
// Check for "off"
|
|
123
|
+
if (valueString === 'off') {
|
|
124
|
+
return context.report({
|
|
125
|
+
node: node,
|
|
126
|
+
messageId: 'noAutocompleteOff',
|
|
127
|
+
fix: function fix(fixer) {
|
|
128
|
+
if (autoCompleteProp.value && isNodeOfType(autoCompleteProp.value, 'Literal')) {
|
|
129
|
+
return fixer.replaceText(autoCompleteProp.value, "\"".concat(expectedAutocomplete, "\""));
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Valid autoComplete value (e.g. "email")
|
|
137
|
+
// If it is an invalid value, it will be caught by the jsx-a11y/autocomplete-valid rule
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// If it's a dynamic expression (e.g., autoComplete={someVar}), we can't statically analyze it
|
|
142
|
+
// so we allow it
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
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::
|
|
3
|
+
* @codegen <<SignedSource::d924c9b1f1f815e7bbe77b86ee42ad86>>
|
|
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::
|
|
3
|
+
* @codegen <<SignedSource::9dbc4d90a7430699860bd67938cfd53b>>
|
|
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::
|
|
3
|
+
* @codegen <<SignedSource::e38fe36e83da94463de0e055c45eac02>>
|
|
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::
|
|
3
|
+
* @codegen <<SignedSource::a12b7837d2a4105c5d20b49d5b9b54ab>>
|
|
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::
|
|
3
|
+
* @codegen <<SignedSource::aec1237f211398c8225cd256e75476ff>>
|
|
4
4
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
5
5
|
*/
|
|
6
6
|
import type { Rule } 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::
|
|
3
|
+
* @codegen <<SignedSource::d924c9b1f1f815e7bbe77b86ee42ad86>>
|
|
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::
|
|
3
|
+
* @codegen <<SignedSource::9dbc4d90a7430699860bd67938cfd53b>>
|
|
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::
|
|
3
|
+
* @codegen <<SignedSource::e38fe36e83da94463de0e055c45eac02>>
|
|
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::
|
|
3
|
+
* @codegen <<SignedSource::a12b7837d2a4105c5d20b49d5b9b54ab>>
|
|
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::
|
|
3
|
+
* @codegen <<SignedSource::aec1237f211398c8225cd256e75476ff>>
|
|
4
4
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
5
5
|
*/
|
|
6
6
|
import type { Rule } from 'eslint';
|
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.
|
|
4
|
+
"version": "13.43.1",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"publishConfig": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"@atlaskit/eslint-utils": "^2.0.0",
|
|
42
42
|
"@atlaskit/icon": "^33.1.0",
|
|
43
43
|
"@atlaskit/icon-lab": "^6.2.0",
|
|
44
|
-
"@atlaskit/tokens": "^11.
|
|
44
|
+
"@atlaskit/tokens": "^11.4.0",
|
|
45
45
|
"@babel/runtime": "^7.0.0",
|
|
46
46
|
"@typescript-eslint/utils": "^7.1.0",
|
|
47
47
|
"ajv": "^6.12.6",
|