@hero-design/eslint-plugin 9.2.0-rc.1 → 9.2.1-alpha.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.
@@ -1 +0,0 @@
1
- $ eslint .
@@ -1 +1,13 @@
1
- $ jest --runInBand
1
+ PASS tests/lib/rules/react-no-text-outside-typography.js
2
+ PASS tests/lib/rules/no-deprecated-component-prop-value.js
3
+ PASS tests/lib/rules/no-deprecated-component-prop.js
4
+ PASS tests/lib/rules/no-deprecated-theme-key.js
5
+ PASS tests/lib/rules/not-recommended-import.js
6
+ PASS tests/lib/rules/banning-snowflake-approve-comment.js
7
+ PASS tests/lib/rules/no-direct-color-palette-access.js
8
+
9
+ Test Suites: 7 passed, 7 total
10
+ Tests: 90 passed, 90 total
11
+ Snapshots: 0 total
12
+ Time: 12.224 s
13
+ Ran all test suites.
package/CHANGELOG.md ADDED
@@ -0,0 +1,37 @@
1
+ # @hero-design/eslint-plugin
2
+
3
+ ## 9.2.1-alpha.0
4
+
5
+ ### Patch Changes
6
+
7
+ - test release workflow
8
+
9
+ ## 9.2.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [#3327](https://github.com/Thinkei/hero-design/pull/3327) [`a405687ba571a1dffd92cb23d3d6ae6159fdf904`](https://github.com/Thinkei/hero-design/commit/a405687ba571a1dffd92cb23d3d6ae6159fdf904) Thanks [@luanlai2201](https://github.com/luanlai2201)! - Add banning-snowflake-approve-comment rule
14
+
15
+ ## 9.1.0
16
+
17
+ ### Minor Changes
18
+
19
+ - [#3197](https://github.com/Thinkei/hero-design/pull/3197) [`36c71a4a7`](https://github.com/Thinkei/hero-design/commit/36c71a4a7719bd5f09925a9d88e197135696d0c9) Thanks [@vinhphan-eh](https://github.com/vinhphan-eh)! - Add react-no-text-outside-typography rule for react
20
+
21
+ ## 9.0.1
22
+
23
+ ### Patch Changes
24
+
25
+ - [#3187](https://github.com/Thinkei/hero-design/pull/3187) [`c580bccd6`](https://github.com/Thinkei/hero-design/commit/c580bccd6958dd5accc6f7c17192a822630e6536) Thanks [@vinhphan-eh](https://github.com/vinhphan-eh)! - Implement no-direct-color-palette-access rule
26
+
27
+ ## 9.0.0
28
+
29
+ ### Major Changes
30
+
31
+ - [#2563](https://github.com/Thinkei/hero-design/pull/2563) [`0254e47a8`](https://github.com/Thinkei/hero-design/commit/0254e47a838f0f431c3d72306b945247786ea7d1) Thanks [@luanlai2201](https://github.com/luanlai2201)! - [Internal] Remove eslint no-invalid-access-theme rule
32
+
33
+ ## 8.42.5
34
+
35
+ ### Patch Changes
36
+
37
+ - [#2302](https://github.com/Thinkei/hero-design/pull/2302) [`2293190d1`](https://github.com/Thinkei/hero-design/commit/2293190d105fb97dc699e39990c3148f3509fafb) Thanks [@vinhphan-eh](https://github.com/vinhphan-eh)! - Upgrade expo sdk and react-native version
@@ -0,0 +1,31 @@
1
+ const { FlatCompat } = require('@eslint/eslintrc');
2
+ const js = require('@eslint/js');
3
+
4
+ const compat = new FlatCompat({
5
+ baseDirectory: __dirname,
6
+ recommendedConfig: js.configs.recommended,
7
+ });
8
+
9
+ module.exports = [
10
+ ...compat.extends(
11
+ 'eslint:recommended',
12
+ 'plugin:eslint-plugin/recommended',
13
+ 'plugin:node/recommended'
14
+ ),
15
+ {
16
+ files: ['**/*.js', '**/*.ts'],
17
+ languageOptions: {
18
+ globals: {
19
+ node: true,
20
+ },
21
+ },
22
+ },
23
+ {
24
+ files: ['tests/**/*.js'],
25
+ languageOptions: {
26
+ globals: {
27
+ jest: true,
28
+ },
29
+ },
30
+ },
31
+ ];
package/lib/index.js CHANGED
@@ -14,213 +14,6 @@ const requireIndex = require('requireindex');
14
14
  // Plugin Definition
15
15
  //------------------------------------------------------------------------------
16
16
 
17
- const themeKeysMap = [
18
- {
19
- old: 'colors.globalPrimary',
20
- new: 'colors.onDefaultGlobalSurface',
21
- },
22
- {
23
- old: 'colors.globalPrimaryLight',
24
- new: 'colors.globalPrimaryLight',
25
- },
26
- {
27
- old: 'colors.globalPrimaryBackground',
28
- new: 'colors.defaultGlobalSurface',
29
- },
30
- {
31
- old: 'colors.primaryLight',
32
- new: 'colors.secondary',
33
- },
34
- {
35
- old: 'colors.primaryDark',
36
- new: 'colors.primary',
37
- },
38
- {
39
- old: 'colors.primaryBackground',
40
- new: 'colors.highlightedSurface',
41
- },
42
- {
43
- old: 'colors.primaryBackgroundDark',
44
- new: 'colors.mutedOnDefaultGlobalSurface',
45
- },
46
- {
47
- old: 'colors.secondaryLight',
48
- new: 'colors.secondaryLight',
49
- },
50
- {
51
- old: 'colors.secondaryBackground',
52
- new: 'colors.secondaryBackground',
53
- },
54
- {
55
- old: 'colors.infoMediumLight',
56
- new: 'colors.mutedInfo',
57
- },
58
- {
59
- old: 'colors.infoLight',
60
- new: 'colors.mutedInfo',
61
- },
62
- {
63
- old: 'colors.infoBackground',
64
- new: 'colors.infoSurface',
65
- },
66
- {
67
- old: 'colors.successLight',
68
- new: 'colors.mutedSuccess',
69
- },
70
- {
71
- old: 'colors.successDark',
72
- new: 'colors.success',
73
- },
74
- {
75
- old: 'colors.successBackground',
76
- new: 'colors.successSurface',
77
- },
78
- {
79
- old: 'colors.danger',
80
- new: 'colors.error',
81
- },
82
- {
83
- old: 'colors.dangerMediumLight',
84
- new: 'colors.mutedError',
85
- },
86
- {
87
- old: 'colors.dangerLight',
88
- new: 'colors.mutedError',
89
- },
90
- {
91
- old: 'colors.dangerBackground',
92
- new: 'colors.errorSurface',
93
- },
94
- {
95
- old: 'colors.warningLight',
96
- new: 'colors.mutedWarning',
97
- },
98
- {
99
- old: 'colors.warningDark',
100
- new: 'colors.warning',
101
- },
102
- {
103
- old: 'colors.warningBackground',
104
- new: 'colors.warningSurface',
105
- },
106
- {
107
- old: 'colors.platformBackground',
108
- new: 'colors.defaultGlobalSurface',
109
- },
110
- {
111
- old: 'colors.backgroundLight',
112
- new: 'colors.neutralGlobalSurface',
113
- },
114
- {
115
- old: 'colors.backgroundDark',
116
- new: 'colors.darkGlobalSurface',
117
- },
118
- {
119
- old: 'colors.text',
120
- new: 'colors.onDefaultGlobalSurface',
121
- },
122
- {
123
- old: 'colors.subduedText',
124
- new: 'colors.mutedOnDefaultGlobalSurface',
125
- },
126
- {
127
- old: 'colors.disabledText',
128
- new: 'colors.inactiveOnDefaultGlobalSurface',
129
- },
130
- {
131
- old: 'colors.disabledLightText',
132
- new: 'colors.disabledOnDefaultGlobalSurface',
133
- },
134
- {
135
- old: 'colors.invertedText',
136
- new: 'colors.onDarkGlobalSurface',
137
- },
138
- {
139
- old: 'colors.outline',
140
- new: 'colors.secondaryOutline',
141
- },
142
- {
143
- old: 'colors.archivedLight',
144
- new: 'colors.disabledOnDefaultGlobalSurface',
145
- },
146
- {
147
- old: 'colors.archivedDark',
148
- new: 'colors.archived',
149
- },
150
- {
151
- old: 'colors.archivedBackground',
152
- new: 'colors.archivedSurface',
153
- },
154
- {
155
- old: 'colors.black',
156
- new: 'colors.onDefaultGlobalSurface',
157
- },
158
- {
159
- old: 'colors.inactiveBackground',
160
- new: 'colors.inactiveOnDefaultGlobalSurface',
161
- },
162
- {
163
- old: 'colors.shadow',
164
- new: 'colors.secondaryOutline',
165
- },
166
- {
167
- old: 'colors.mutedGlobalPrimary',
168
- new: 'colors.mutedOnDefaultGlobalSurface',
169
- },
170
- {
171
- old: 'colors.onGlobalPrimary',
172
- new: 'colors.onDefaultGlobalSurface',
173
- },
174
- {
175
- old: 'colors.globalSecondary',
176
- new: 'colors.secondary',
177
- },
178
- {
179
- old: 'colors.globalPrimaryOutline',
180
- new: 'colors.primaryOutline',
181
- },
182
- {
183
- old: 'colors.globalSecondaryOutline',
184
- new: 'colors.secondaryOutline',
185
- },
186
- {
187
- old: 'colors.mutedPrimary',
188
- new: 'colors.mutedPrimary',
189
- },
190
- {
191
- old: 'colors.highlightedSecondarySurface',
192
- new: 'colors.highlightedSecondarySurface',
193
- },
194
- {
195
- old: 'colors.mutedSecondary',
196
- new: 'colors.mutedSecondary',
197
- },
198
- {
199
- old: 'colors.disabledSecondary',
200
- new: 'colors.disabledSecondary',
201
- },
202
- {
203
- old: 'colors.lightHighlightedSurface',
204
- new: 'colors.highlightedSurface',
205
- },
206
- ];
207
- const deprecatedThemeColors = themeKeysMap.map((c) => c.old.split('.')[1]);
208
- const boxColorProps = [
209
- 'backgroundColor',
210
- 'bgColor',
211
- 'borderColor',
212
- 'borderTopColor',
213
- 'borderBottomColor',
214
- 'borderStartColor',
215
- 'borderEndColor',
216
- 'borderLeftColor',
217
- 'borderRightColor',
218
- ];
219
- const deprecatedBoxPropValues = boxColorProps.map((prop) => ({
220
- name: prop,
221
- values: deprecatedThemeColors,
222
- }));
223
-
224
17
  // import all rules in lib/rules
225
18
  module.exports = {
226
19
  rules: requireIndex(__dirname + '/rules'),
@@ -228,101 +21,18 @@ module.exports = {
228
21
  internalRn: {
229
22
  plugins: ['@hero-design'],
230
23
  rules: {
231
- '@hero-design/no-deprecated-theme-key': [
232
- 'error',
233
- {
234
- keys: themeKeysMap,
235
- },
236
- ],
237
- '@hero-design/no-deprecated-component-prop': [
238
- 'error',
239
- {
240
- package: '@hero-design/rn',
241
- components: [
242
- { name: 'Card', props: ['variant'] },
243
- { name: 'Switch', props: ['size'] },
244
- {
245
- name: 'Select',
246
- props: ['onDimiss', 'numberOfLines', 'inputProps.required'],
247
- },
248
- {
249
- name: 'Select.Multi',
250
- props: ['onDimiss', 'numberOfLines', 'inputProps.required'],
251
- },
252
- { name: 'Toast.Provider', props: ['position'] },
253
- ],
254
- },
255
- ],
256
- '@hero-design/no-deprecated-component-prop-value': [
257
- 'error',
24
+ // Rules for the next major version
25
+ '@hero-design/no-deprecated-component-prop-next': [
26
+ 'warn',
258
27
  {
259
28
  package: '@hero-design/rn',
260
29
  components: [
30
+ { name: 'Tag', props: ['variant'] },
31
+ { name: 'FAB.ActionGroup', props: ['headerTitle'] },
32
+ { name: 'Checkbox', props: ['withBorder'] },
261
33
  {
262
- name: 'Tag',
263
- props: [{ name: 'intent', values: ['default'] }],
264
- },
265
- {
266
- name: 'Button',
267
- props: [{ name: 'variant', values: ['basic-transparent'] }],
268
- },
269
- {
270
- name: 'Button.Icon',
271
- props: [
272
- {
273
- name: 'icon',
274
- values: [
275
- 'carat-down-small',
276
- 'carat-down',
277
- 'carat-left-small',
278
- 'carat-left',
279
- 'carat-right-small',
280
- 'carat-right',
281
- 'carat-up-small',
282
- 'carat-up',
283
- ],
284
- },
285
- ],
286
- },
287
- {
288
- name: 'Button.Utility',
289
- props: [
290
- {
291
- name: 'icon',
292
- values: [
293
- 'carat-down-small',
294
- 'carat-down',
295
- 'carat-left-small',
296
- 'carat-left',
297
- 'carat-right-small',
298
- 'carat-right',
299
- 'carat-up-small',
300
- 'carat-up',
301
- ],
302
- },
303
- ],
304
- },
305
- {
306
- name: 'Icon',
307
- props: [
308
- {
309
- name: 'icon',
310
- values: [
311
- 'carat-down-small',
312
- 'carat-down',
313
- 'carat-left-small',
314
- 'carat-left',
315
- 'carat-right-small',
316
- 'carat-right',
317
- 'carat-up-small',
318
- 'carat-up',
319
- ],
320
- },
321
- ],
322
- },
323
- {
324
- name: 'Box',
325
- props: deprecatedBoxPropValues,
34
+ name: 'SectionHeading',
35
+ props: ['fontSize', 'fontWeight'],
326
36
  },
327
37
  ],
328
38
  },
@@ -408,5 +118,12 @@ module.exports = {
408
118
  ],
409
119
  },
410
120
  },
121
+ recommendedReact: {
122
+ plugins: ['@hero-design'],
123
+ rules: {
124
+ '@hero-design/no-direct-color-palette-access': 'error',
125
+ '@hero-design/react-no-text-outside-typography': 'warn',
126
+ }
127
+ }
411
128
  },
412
129
  };
@@ -0,0 +1,32 @@
1
+ module.exports = {
2
+ meta: {
3
+ type: 'problem',
4
+ docs: {
5
+ description:
6
+ 'Disallow the use of comments that include @snowflake-guard/',
7
+ },
8
+ messages: {
9
+ noSnowflakeGuard:
10
+ 'Comments including @snowflake-guard/ are not allowed. Please contact Andromeda team for the approval.',
11
+ },
12
+ schema: [],
13
+ },
14
+ create(context) {
15
+ return {
16
+ Program() {
17
+ const sourceCode = context.getSourceCode();
18
+ const comments = sourceCode.getAllComments();
19
+
20
+ comments.forEach((comment) => {
21
+ // Check if the comment includes @snowflake-guard/
22
+ if (comment.value.includes('@snowflake-guard/')) {
23
+ context.report({
24
+ node: comment,
25
+ messageId: 'noSnowflakeGuard',
26
+ });
27
+ }
28
+ });
29
+ },
30
+ };
31
+ },
32
+ };
@@ -0,0 +1,4 @@
1
+
2
+ const rule = require('./no-deprecated-component-prop');
3
+
4
+ module.exports = rule;
@@ -4,6 +4,8 @@
4
4
  */
5
5
  'use strict';
6
6
 
7
+ const { findIndentifierPath } = require('../utils');
8
+
7
9
  //------------------------------------------------------------------------------
8
10
  // Rule Definition
9
11
  //------------------------------------------------------------------------------
@@ -46,27 +48,13 @@ module.exports = {
46
48
  const oldKeys = context.options[0].keys.map((k) => k.old);
47
49
  const newKeys = context.options[0].keys.map((k) => k.new);
48
50
 
49
- const findPath = (node, identifiers) => {
50
- const property = node.property;
51
-
52
- if (
53
- property != null &&
54
- property.type === 'Identifier' &&
55
- property.name != null
56
- ) {
57
- return findPath(node.parent, [...identifiers, property.name]);
58
- }
59
-
60
- return identifiers.join('.');
61
- };
62
-
63
51
  return {
64
52
  MemberExpression(node) {
65
53
  const object = node.object;
66
54
  if (object == null) return;
67
55
 
68
56
  if (object.type === 'Identifier' && object.name === 'theme') {
69
- const path = findPath(node, []);
57
+ const path = findIndentifierPath(node, []);
70
58
  const deprecatedKeyIndex = oldKeys.findIndex((k) => k === path);
71
59
 
72
60
  if (deprecatedKeyIndex >= 0) {
@@ -0,0 +1,56 @@
1
+ /**
2
+ * @fileoverview Disallow direct access of color palette.
3
+ */
4
+ 'use strict';
5
+
6
+ const { findIndentifierPath } = require('../utils');
7
+
8
+ //------------------------------------------------------------------------------
9
+ // Rule Definition
10
+ //------------------------------------------------------------------------------
11
+
12
+ /** @type {import('eslint').Rule.RuleModule} */
13
+ module.exports = {
14
+ meta: {
15
+ type: 'problem',
16
+ docs: {
17
+ description: 'Disallow direct access of color palette.',
18
+ },
19
+ messages: {
20
+ invalidColorAccess: 'Unexpected direct access of palette colors, use semantic colors instead.',
21
+ },
22
+ schema: [],
23
+ },
24
+
25
+ create(context) {
26
+ return {
27
+ MemberExpression(node) {
28
+ const object = node.object;
29
+ if (object == null) return;
30
+
31
+ // For example: theme.colors.pallete.redLight30
32
+ if (object.type === 'Identifier' && object.name === 'theme') {
33
+ const path = findIndentifierPath(node, []);
34
+
35
+ if (path.includes('colors.palette')) {
36
+ context.report({
37
+ node,
38
+ messageId: 'invalidColorAccess',
39
+ });
40
+ }
41
+ }
42
+ // For example: colors.pallete.redLight30
43
+ else if (object.type === 'Identifier' && object.name === 'colors') {
44
+ const path = findIndentifierPath(node, []);
45
+
46
+ if (path.includes('palette')) {
47
+ context.report({
48
+ node,
49
+ messageId: 'invalidColorAccess',
50
+ });
51
+ }
52
+ }
53
+ },
54
+ };
55
+ },
56
+ };
@@ -0,0 +1,216 @@
1
+ const notRecommendedList = [
2
+ 'div',
3
+ 'p',
4
+ 'span',
5
+ 'h1',
6
+ 'h2',
7
+ 'h3',
8
+ 'h4',
9
+ 'h5',
10
+ 'h6',
11
+ 'a',
12
+ 'strong',
13
+ 'em',
14
+ 'b',
15
+ 'i',
16
+ 'u',
17
+ ];
18
+
19
+ // Get the parent tag name of the node
20
+ const getParentTagName = (node) => {
21
+ let parent = node.parent;
22
+ while (parent) {
23
+ if (parent.type === 'JSXElement') {
24
+ return parent.openingElement.name.name;
25
+ }
26
+ parent = parent.parent;
27
+ }
28
+ return null;
29
+ };
30
+
31
+ // Find the Typography component in the parent hierarchy
32
+ // and return the value of tagName prop and the level of nested component
33
+ const findTypography = (node, typographyNames) => {
34
+ let parent = node.parent;
35
+ let level = 0;
36
+ let tagName = null;
37
+ let found = false;
38
+
39
+ while (parent) {
40
+ if (
41
+ parent.type === 'JSXElement' &&
42
+ parent.openingElement.name.type === 'JSXMemberExpression' &&
43
+ typographyNames.includes(parent.openingElement.name.object.name)
44
+ ) {
45
+ found = true;
46
+ const attributes = parent.openingElement.attributes;
47
+ for (let attr of attributes) {
48
+ if (attr.name && attr.name.name === 'tagName') {
49
+ tagName = attr.value.value;
50
+ }
51
+ }
52
+ break;
53
+ }
54
+ parent = parent.parent;
55
+ level++;
56
+ }
57
+ return { tagName, level, found };
58
+ };
59
+
60
+ // Check if the direct parent element is in the notRecommendedList
61
+ const isParentNotRecommended = (node) => {
62
+ let parentElement = node.parent;
63
+ while (parentElement && parentElement.type !== 'JSXElement') {
64
+ parentElement = parentElement.parent;
65
+ }
66
+
67
+ return (
68
+ parentElement &&
69
+ parentElement.openingElement.name.type === 'JSXIdentifier' &&
70
+ notRecommendedList.includes(parentElement.openingElement.name.name)
71
+ );
72
+ };
73
+
74
+ const checkNode = (node, context, typographyNames) => {
75
+ const {
76
+ tagName,
77
+ level,
78
+ found: foundTypography,
79
+ } = findTypography(node, typographyNames);
80
+
81
+ // Typography not found, check if the direct parent is in the notRecommendedList
82
+ if (!foundTypography && isParentNotRecommended(node)) {
83
+ context.report({
84
+ node,
85
+ messageId: 'textNodeOutsideTypography',
86
+ });
87
+ }
88
+
89
+ // Typography found, handle edge cases as documented in
90
+ // https://design.employmenthero.com/web/Components/Typography/#typographytext
91
+ if (foundTypography) {
92
+ switch (tagName) {
93
+ case 'p':
94
+ case '':
95
+ case 'span':
96
+ case 'label': {
97
+ if (level > 1) {
98
+ // Not allowing nested of nested nodes
99
+ context.report({
100
+ node,
101
+ messageId: 'textNodeOutsideTypography',
102
+ });
103
+ }
104
+ break;
105
+ }
106
+ case 'div': {
107
+ // Allowed cases:
108
+ // 2 level nested with unordered list
109
+ // 2 level nested elements with p
110
+ const parentTagName = getParentTagName(node);
111
+ const grandParentTagName = getParentTagName(node.parent);
112
+
113
+ const isAllowedUnorderedList =
114
+ level === 2 && parentTagName === 'li' && grandParentTagName === 'ul';
115
+
116
+ const isAllowedParagraph = level === 2 && grandParentTagName === 'p';
117
+
118
+ if (!isAllowedParagraph && !isAllowedUnorderedList && level > 1) {
119
+ context.report({
120
+ node,
121
+ messageId: 'textNodeOutsideTypography',
122
+ });
123
+ }
124
+ break;
125
+ }
126
+ }
127
+ }
128
+ };
129
+
130
+ function containsIntlFormatMessage(expression) {
131
+ if (expression === null || expression === undefined) {
132
+ return false;
133
+ }
134
+
135
+ if (
136
+ expression.type === 'CallExpression' &&
137
+ expression.callee.type === 'MemberExpression' &&
138
+ expression.callee.object.name === 'Intl' &&
139
+ expression.callee.property.name === 'formatMessage'
140
+ ) {
141
+ return true;
142
+ }
143
+
144
+ // Example: {condition1 && Intl.formatMessage({ id: 'someId' })}
145
+ // Also works for multiple logical expressions
146
+ if (expression.type === 'LogicalExpression') {
147
+ return (
148
+ containsIntlFormatMessage(expression.left) ||
149
+ containsIntlFormatMessage(expression.right)
150
+ );
151
+ }
152
+
153
+ if (expression.type === 'ConditionalExpression') {
154
+ return (
155
+ containsIntlFormatMessage(expression.consequent) ||
156
+ containsIntlFormatMessage(expression.alternate)
157
+ );
158
+ }
159
+
160
+ return false;
161
+ }
162
+
163
+ module.exports = {
164
+ meta: {
165
+ type: 'problem',
166
+ docs: {
167
+ description: 'Recommend using text inside Typography component.',
168
+ recommended: false,
169
+ },
170
+ schema: [],
171
+ messages: {
172
+ textNodeOutsideTypography:
173
+ 'Text nodes should be inside Typography component.',
174
+ },
175
+ },
176
+ create(context) {
177
+ let typographyNames = ['Typography'];
178
+
179
+ return {
180
+ ImportDeclaration(node) {
181
+ if (node.source.value === '@hero-design/react') {
182
+ node.specifiers.forEach((specifier) => {
183
+ if (
184
+ specifier.imported &&
185
+ specifier.imported.name === 'Typography'
186
+ ) {
187
+ specifier.local.name !== 'Typography' &&
188
+ typographyNames.push(specifier.local.name);
189
+ }
190
+ });
191
+ }
192
+ },
193
+ VariableDeclarator(node) {
194
+ // Handle local assignments
195
+ if (
196
+ node.init &&
197
+ node.init.type === 'Identifier' &&
198
+ typographyNames.includes(node.init.name)
199
+ ) {
200
+ node.id.name !== 'Typography' && typographyNames.push(node.id.name);
201
+ }
202
+ },
203
+ JSXText(node) {
204
+ const trimmedValue = node.value.trim();
205
+ if (trimmedValue) {
206
+ checkNode(node, context, typographyNames);
207
+ }
208
+ },
209
+ JSXExpressionContainer(node) {
210
+ if (containsIntlFormatMessage(node.expression)) {
211
+ checkNode(node, context, typographyNames);
212
+ }
213
+ },
214
+ };
215
+ },
216
+ };
package/lib/utils.js ADDED
@@ -0,0 +1,17 @@
1
+ const findIndentifierPath = (node, identifiers) => {
2
+ const property = node.property;
3
+
4
+ if (
5
+ property != null &&
6
+ property.type === 'Identifier' &&
7
+ property.name != null
8
+ ) {
9
+ return findIndentifierPath(node.parent, [...identifiers, property.name]);
10
+ }
11
+
12
+ return identifiers.join('.');
13
+ };
14
+
15
+ module.exports = {
16
+ findIndentifierPath,
17
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hero-design/eslint-plugin",
3
- "version": "9.2.0-rc.1",
3
+ "version": "9.2.1-alpha.0",
4
4
  "description": "Hero Design's eslint plugin",
5
5
  "keywords": [
6
6
  "eslint",
@@ -12,7 +12,7 @@
12
12
  "exports": "./lib/index.js",
13
13
  "prettier": "prettier-config-hd",
14
14
  "scripts": {
15
- "lint": "eslint .",
15
+ "lint": "eslint lib tests",
16
16
  "test": "jest --runInBand",
17
17
  "test:watch": "jest --runInBand --watch",
18
18
  "test:ci": "jest --runInBand --logHeapUsage",
@@ -22,14 +22,17 @@
22
22
  "requireindex": "^1.2.0"
23
23
  },
24
24
  "devDependencies": {
25
- "eslint": "^8.10.0",
25
+ "@eslint/compat": "^1.1.1",
26
+ "@eslint/eslintrc": "^3.1.0",
27
+ "@eslint/js": "^9.8.0",
28
+ "eslint": "^8.56.0",
26
29
  "eslint-plugin-eslint-plugin": "^5.0.0",
27
30
  "eslint-plugin-node": "^11.1.0",
28
- "jest": "^27.3.1",
29
- "prettier-config-hd": "9.2.0-rc.1"
31
+ "jest": "^29.2.1",
32
+ "prettier-config-hd": "8.42.5-alpha.0"
30
33
  },
31
34
  "engines": {
32
- "node": "^14.17.0 || ^16.0.0 || >= 18.0.0"
35
+ "node": "^14.17.0 || ^16.0.0 || ^18.0.0 || >=20.0.0"
33
36
  },
34
37
  "peerDependencies": {
35
38
  "eslint": ">=7"
@@ -0,0 +1,46 @@
1
+ const { RuleTester } = require('eslint');
2
+ const rule = require('../../../lib/rules/banning-snowflake-approve-comment');
3
+
4
+ const ruleTester = new RuleTester();
5
+
6
+ // Define valid and invalid test cases
7
+ ruleTester.run('no-snowflake-guard', rule, {
8
+ // Valid cases (should not trigger the rule)
9
+ valid: [
10
+ '// A normal comment without snowflake-guard',
11
+ '// Another valid comment',
12
+ '/* @no-snowflake-guard */',
13
+ 'console.log("no issues here");',
14
+ ],
15
+
16
+ // Invalid cases (should trigger the rule)
17
+ invalid: [
18
+ {
19
+ code: '// @snowflake-guard/none-css-classname', // Triggers the rule
20
+ errors: [
21
+ {
22
+ message:
23
+ 'Comments including @snowflake-guard/ are not allowed. Please contact Andromeda team for the approval.',
24
+ },
25
+ ],
26
+ },
27
+ {
28
+ code: '// @snowflake-guard/another-pattern', // Triggers the rule
29
+ errors: [
30
+ {
31
+ message:
32
+ 'Comments including @snowflake-guard/ are not allowed. Please contact Andromeda team for the approval.',
33
+ },
34
+ ],
35
+ },
36
+ {
37
+ code: '/* @snowflake-guard/custom-pattern */', // Triggers the rule for block comments
38
+ errors: [
39
+ {
40
+ message:
41
+ 'Comments including @snowflake-guard/ are not allowed. Please contact Andromeda team for the approval.',
42
+ },
43
+ ],
44
+ },
45
+ ],
46
+ });
@@ -0,0 +1,45 @@
1
+ const rule = require('../../../lib/rules/no-direct-color-palette-access');
2
+ const RuleTester = require('eslint').RuleTester;
3
+
4
+ //------------------------------------------------------------------------------
5
+ // Tests
6
+ //------------------------------------------------------------------------------
7
+
8
+ const config = {
9
+ parserOptions: {
10
+ sourceType: 'module',
11
+ ecmaVersion: 6,
12
+ ecmaFeatures: { jsx: true },
13
+ },
14
+ };
15
+
16
+ const ruleTester = new RuleTester();
17
+ ruleTester.run('no-direct-color-palette-access', rule, {
18
+ valid: [
19
+ {
20
+ code: 'const color = theme.colors.hoverDanger',
21
+ },
22
+ {
23
+ code: '<Box sx={{ backgroundColor: theme.colors.hoverDanger }} />',
24
+ },
25
+ ].map((test) => ({ ...test, ...config })),
26
+ invalid: [
27
+ {
28
+ code: 'const color = theme.colors.palette.redLight30',
29
+ errors: [{ messageId: 'invalidColorAccess' }],
30
+ },
31
+ {
32
+ code: 'const color = theme.colors.palette',
33
+ errors: [{ messageId: 'invalidColorAccess' }],
34
+ },
35
+ {
36
+ code: '<Box sx={{ backgroundColor: theme.colors.palette.redLight30 }} />',
37
+ errors: [{ messageId: 'invalidColorAccess' }],
38
+ },
39
+ {
40
+ code: `const colors = theme.colors;
41
+ const color = colors.palette.redLight30`,
42
+ errors: [{ messageId: 'invalidColorAccess' }],
43
+ }
44
+ ].map((test) => ({ ...test, ...config })),
45
+ });
@@ -0,0 +1,248 @@
1
+ const rule = require('../../../lib/rules/react-no-text-outside-typography');
2
+ const RuleTester = require('eslint').RuleTester;
3
+
4
+ //------------------------------------------------------------------------------
5
+ // Tests
6
+ //------------------------------------------------------------------------------
7
+
8
+ const config = {
9
+ parserOptions: {
10
+ sourceType: 'module',
11
+ ecmaVersion: 6,
12
+ ecmaFeatures: { jsx: true },
13
+ },
14
+ };
15
+
16
+ const ruleTester = new RuleTester();
17
+ ruleTester.run('react-no-text-outside-typography', rule, {
18
+ valid: [
19
+ // Common cases
20
+ {
21
+ code: '<Typography.Text>Content</Typography.Text>',
22
+ },
23
+ {
24
+ code: '<Typography.Title>Content</Typography.Title>',
25
+ },
26
+ {
27
+ code: `<Typography.Text>
28
+ Click <a href='..'>here</a>
29
+ </Typography.Text>`,
30
+ },
31
+ {
32
+ code: `<Typography.Text>
33
+ <Box>Content</Box>
34
+ </Typography.Text>
35
+ `,
36
+ },
37
+ // With Intl.formatMessage and conditions
38
+ {
39
+ code: `<Typography.Text>
40
+ <Box>{Intl.formatMessage({ id: 'someId' })}</Box>
41
+ </Typography.Text>
42
+ `,
43
+ },
44
+ {
45
+ code: `<Typography.Text>
46
+ <Box>{condition1 && Intl.formatMessage({ id: 'someId' })}</Box>
47
+ </Typography.Text>
48
+ `,
49
+ },
50
+ {
51
+ code: `<Typography.Text>
52
+ <Box>{condition1 ? Intl.formatMessage({ id: 'someId' }) : null}</Box>
53
+ </Typography.Text>
54
+ `,
55
+ },
56
+ {
57
+ code: `
58
+ <Typography.Text>
59
+ {Intl.formatMessage({ id: 'someId' })}
60
+ </Typography.Text>`,
61
+ },
62
+ {
63
+ code: `
64
+ <Typography.Text>
65
+ {condition1 && Intl.formatMessage({ id: 'someId' })}
66
+ </Typography.Text>`,
67
+ },
68
+ {
69
+ code: `
70
+ <Typography.Text>
71
+ {condition1 ? Intl.formatMessage({ id: 'someId' }) : null}
72
+ </Typography.Text>`,
73
+ },
74
+ {
75
+ code: `
76
+ <Typography.Text>
77
+ {condition1 ? Intl.formatMessage({ id: 'someId' }) : Intl.formatMessage({ id: 'someId2' })}
78
+ </Typography.Text>`,
79
+ },
80
+ // Custom namings
81
+ {
82
+ code: `
83
+ import { Typography as HDTypography } from '@hero-design/react';
84
+ <HDTypography.Text>Content</HDTypography.Text>
85
+ `,
86
+ },
87
+ {
88
+ code: `
89
+ import { Typography as HDTypography } from '@hero-design/react';
90
+ const { Text } = HDTypography;
91
+ <Text>Content</Text>
92
+ `,
93
+ },
94
+ {
95
+ code: `
96
+ import { Typography } from '@hero-design/react';
97
+ const { Title } = Typography;
98
+ <Title>Content</Title>
99
+ `,
100
+ },
101
+ {
102
+ code: `
103
+ import { Typography } from '@hero-design/react';
104
+ const { Title: HDTypographyTitle } = Typography;
105
+ <HDTypographyTitle>Content</HDTypographyTitle>
106
+ `,
107
+ },
108
+ {
109
+ code: `
110
+ import { Typography } from '@hero-design/react';
111
+ const Title = Typography.Title;
112
+ <Title>Content</Title>
113
+ `,
114
+ },
115
+ // Exceptions with tagName prop
116
+ {
117
+ code: `
118
+ <Typography.Text tagName='p'>
119
+ <span>Content</span>
120
+ </Typography.Text>
121
+ `,
122
+ },
123
+ {
124
+ code: `
125
+ <Typography.Text tagName='div'>
126
+ <ul>
127
+ <li>Content</li>
128
+ </ul>
129
+ </Typography.Text>
130
+ `,
131
+ },
132
+ {
133
+ code: `
134
+ <Typography.Text tagName='div'>
135
+ <p>
136
+ <a>Content</a>
137
+ </p>
138
+ </Typography.Text>
139
+ `,
140
+ },
141
+ {
142
+ code: `
143
+ <Typography.Text tagName='span'>
144
+ <span>Content</span>
145
+ </Typography.Text>
146
+ `,
147
+ },
148
+ {
149
+ code: `
150
+ <Typography.Text tagName='label'>
151
+ <span>Content</span>
152
+ </Typography.Text>
153
+ `,
154
+ },
155
+ ].map((test) => ({ ...test, ...config })),
156
+ invalid: [
157
+ // Common cases
158
+ {
159
+ code: '<div>Content</div>',
160
+ errors: [{ messageId: 'textNodeOutsideTypography' }],
161
+ },
162
+ {
163
+ code: '<p>Content</p>',
164
+ errors: [{ messageId: 'textNodeOutsideTypography' }],
165
+ },
166
+ {
167
+ code: '<h1>Content</h1>',
168
+ errors: [{ messageId: 'textNodeOutsideTypography' }],
169
+ },
170
+ // With Intl.formatMessage and conditions
171
+ {
172
+ code: `
173
+ <div>
174
+ {Intl.formatMessage({ id: 'someId' })}
175
+ </div>`,
176
+ errors: [{ messageId: 'textNodeOutsideTypography' }],
177
+ },
178
+ {
179
+ code: `
180
+ <div>
181
+ { condition && Intl.formatMessage({ id: 'someId' })}
182
+ </div>`,
183
+ errors: [{ messageId: 'textNodeOutsideTypography' }],
184
+ },
185
+ {
186
+ code: `
187
+ <div>
188
+ { condition && condition2 && Intl.formatMessage({ id: 'someId' })}
189
+ </div>`,
190
+ errors: [{ messageId: 'textNodeOutsideTypography' }],
191
+ },
192
+ {
193
+ code: `
194
+ <div>
195
+ { condition && condition2 || condition3 && Intl.formatMessage({ id: 'someId' })}
196
+ </div>`,
197
+ errors: [{ messageId: 'textNodeOutsideTypography' }],
198
+ },
199
+ {
200
+ code: `
201
+ <div>
202
+ {condition1 ? Intl.formatMessage({ id: 'someId' }) : null}
203
+ </div>`,
204
+ errors: [{ messageId: 'textNodeOutsideTypography' }],
205
+ },
206
+ // Exceptions with tagName prop
207
+ {
208
+ code: `
209
+ <Typography.Text tagName='p'>
210
+ <span>
211
+ <span>Content</span>
212
+ </span>
213
+ </Typography.Text>
214
+ `,
215
+ errors: [{ messageId: 'textNodeOutsideTypography' }],
216
+ },
217
+ {
218
+ code: `
219
+ <Typography.Text tagName='div'>
220
+ <span>
221
+ <li>Content</li>
222
+ </span>
223
+ </Typography.Text>
224
+ `,
225
+ errors: [{ messageId: 'textNodeOutsideTypography' }],
226
+ },
227
+ {
228
+ code: `
229
+ <Typography.Text tagName='span'>
230
+ <span>
231
+ <span>Content</span>
232
+ </span>
233
+ </Typography.Text>
234
+ `,
235
+ errors: [{ messageId: 'textNodeOutsideTypography' }],
236
+ },
237
+ {
238
+ code: `
239
+ <Typography.Text tagName='label'>
240
+ <span>
241
+ <span>Content</span>
242
+ </span>
243
+ </Typography.Text>
244
+ `,
245
+ errors: [{ messageId: 'textNodeOutsideTypography' }],
246
+ },
247
+ ].map((test) => ({ ...test, ...config })),
248
+ });
package/.eslintrc.js DELETED
@@ -1,19 +0,0 @@
1
- 'use strict';
2
-
3
- module.exports = {
4
- root: true,
5
- extends: [
6
- 'eslint:recommended',
7
- 'plugin:eslint-plugin/recommended',
8
- 'plugin:node/recommended',
9
- ],
10
- env: {
11
- node: true,
12
- },
13
- overrides: [
14
- {
15
- files: ['tests/**/*.js'],
16
- env: { jest: true },
17
- },
18
- ],
19
- };
File without changes