@atlaskit/eslint-plugin-platform 2.1.2 → 2.2.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/src/index.tsx CHANGED
@@ -1,5 +1,6 @@
1
1
  // eslint-disable-next-line import/no-extraneous-dependencies
2
- import type { Linter } from 'eslint';
2
+ import compiledPlugin from '@compiled/eslint-plugin';
3
+ import type { ESLint, Linter } from 'eslint';
3
4
  import ensureFeatureFlagRegistration from './rules/ensure-feature-flag-registration';
4
5
  import noPreAndPostInstallScripts from './rules/no-pre-post-installs';
5
6
  import ensureTestRunnerArguments from './rules/ensure-test-runner-arguments';
@@ -27,7 +28,13 @@ import useRecommendedUtils from './rules/feature-gating/use-recommended-utils';
27
28
  import expandBackgroundShorthand from './rules/compiled/expand-background-shorthand';
28
29
  import expandSpacingShorthand from './rules/compiled/expand-spacing-shorthand';
29
30
 
30
- export const rules = {
31
+ const packageJson: {
32
+ name: string;
33
+ version: string;
34
+ // eslint-disable-next-line import/no-extraneous-dependencies
35
+ } = require('@atlaskit/eslint-plugin-platform/package.json');
36
+
37
+ const rules = {
31
38
  'ensure-feature-flag-registration': ensureFeatureFlagRegistration,
32
39
  'ensure-feature-flag-prefix': ensureFeatureFlagPrefix,
33
40
  'ensure-test-runner-arguments': ensureTestRunnerArguments,
@@ -75,36 +82,27 @@ const commonConfig = {
75
82
  runtime: 'classic',
76
83
  },
77
84
  ],
78
- };
85
+ } satisfies Linter.RulesRecord;
79
86
 
80
- export const configs = {
81
- recommended: {
82
- plugins: ['@atlaskit/platform', '@compiled'],
83
- rules: {
84
- ...commonConfig,
85
- // See platform/packages/platform/eslint-plugin/src/rules/feature-gating/README.md
86
- // These rules are specific to `platform` and seem a WIP; jira and confluence currently have their own rules
87
- '@atlaskit/platform/no-module-level-eval': 'error',
88
- '@atlaskit/platform/static-feature-flags': 'error',
89
- '@atlaskit/platform/no-preconditioning': 'error',
90
- '@atlaskit/platform/inline-usage': 'error',
91
- '@atlaskit/platform/prefer-fg': 'error',
92
- '@atlaskit/platform/no-alias': 'error',
93
- // end: feature-gating rules
94
- '@atlaskit/platform/ensure-feature-flag-registration': 'error',
95
- '@atlaskit/platform/ensure-feature-flag-prefix': [
96
- 'warn',
97
- { allowedPrefixes: ['platform.', 'platform_'] },
98
- ],
99
- },
100
- },
101
- jira: {
102
- plugins: ['@atlaskit/platform', '@compiled'],
103
- rules: {
104
- ...commonConfig,
105
- },
106
- },
107
- };
87
+ const recommendedRules = {
88
+ ...commonConfig,
89
+ // See platform/packages/platform/eslint-plugin/src/rules/feature-gating/README.md
90
+ // These rules are specific to `platform` and seem a WIP; jira and confluence currently have their own rules
91
+ '@atlaskit/platform/no-module-level-eval': 'error',
92
+ '@atlaskit/platform/static-feature-flags': 'error',
93
+ '@atlaskit/platform/no-preconditioning': 'error',
94
+ '@atlaskit/platform/inline-usage': 'error',
95
+ '@atlaskit/platform/prefer-fg': 'error',
96
+ '@atlaskit/platform/no-alias': 'error',
97
+ // end: feature-gating rules
98
+ '@atlaskit/platform/ensure-feature-flag-registration': 'error',
99
+ '@atlaskit/platform/ensure-feature-flag-prefix': [
100
+ 'warn',
101
+ { allowedPrefixes: ['platform.', 'platform_'] },
102
+ ],
103
+ } satisfies Linter.RulesRecord;
104
+
105
+ const jiraRules = commonConfig;
108
106
 
109
107
  const jsonPrefix =
110
108
  '/* eslint-disable quote-props, comma-dangle, quotes, semi, eol-last, @typescript-eslint/semi, no-template-curly-in-string */ module.exports = ';
@@ -112,55 +110,98 @@ const jsonPrefix =
112
110
  const jsonPrefixForFlatConfig =
113
111
  '/* eslint-disable quote-props, comma-dangle, quotes, semi, eol-last, no-template-curly-in-string */ module.exports = ';
114
112
 
115
- export const processors = {
116
- 'package-json-processor': {
117
- preprocess: (source: string) => {
118
- // augment the json into a js file
119
- return [jsonPrefix + source.trim()];
113
+ const { name, version } = packageJson;
114
+ const plugin = {
115
+ meta: {
116
+ name,
117
+ version,
118
+ },
119
+ rules,
120
+ configs: {
121
+ recommended: {
122
+ plugins: ['@atlaskit/platform', '@compiled'],
123
+ rules: recommendedRules,
120
124
  },
121
- postprocess: (messages) => {
122
- return messages[0].map((message) => {
123
- const { fix } = message;
124
- if (!fix) {
125
- return message;
126
- }
127
-
128
- const offset = jsonPrefix.length;
129
- return {
130
- ...message,
131
- fix: {
132
- ...fix,
133
- range: [fix.range[0] - offset, fix.range[1] - offset],
134
- },
135
- };
136
- });
125
+ 'recommended/flat': {
126
+ plugins: {
127
+ get '@atlaskit/platform'(): ESLint.Plugin {
128
+ return plugin;
129
+ },
130
+ // @ts-expect-error there's an issue with the types for @compiled/eslint-plugin ('no-css-prop-without-css-function' specifically)
131
+ '@compiled': { meta: compiledPlugin.meta, rules: compiledPlugin.rules } as ESLint.Plugin,
132
+ },
133
+ rules: recommendedRules,
137
134
  },
138
- supportsAutofix: true,
139
- } as Linter.Processor,
140
- // This processor is used for ESLint FlatConfig,
141
- // once we roll out FlatConfig, we can remove the above processor
142
- 'package-json-processor-for-flat-config': {
143
- preprocess: (source: string) => {
144
- // augment the json into a js file
145
- return [jsonPrefixForFlatConfig + source.trim()];
135
+ jira: {
136
+ plugins: ['@atlaskit/platform', '@compiled'],
137
+ rules: jiraRules,
146
138
  },
147
- postprocess: (messages) => {
148
- return messages[0].map((message) => {
149
- const { fix } = message;
150
- if (!fix) {
151
- return message;
152
- }
153
-
154
- const offset = jsonPrefixForFlatConfig.length;
155
- return {
156
- ...message,
157
- fix: {
158
- ...fix,
159
- range: [fix.range[0] - offset, fix.range[1] - offset],
160
- },
161
- };
162
- });
139
+ 'jira/flat': {
140
+ plugins: {
141
+ get '@atlaskit/platform'(): ESLint.Plugin {
142
+ return plugin;
143
+ },
144
+ // @ts-expect-error there's an issue with the types for @compiled/eslint-plugin ('no-css-prop-without-css-function' specifically)
145
+ '@compiled': { meta: compiledPlugin.meta, rules: compiledPlugin.rules } as ESLint.Plugin,
146
+ },
147
+ rules: jiraRules,
163
148
  },
164
- supportsAutofix: true,
165
- } as Linter.Processor,
166
- };
149
+ },
150
+ processors: {
151
+ 'package-json-processor': {
152
+ preprocess: (source: string) => {
153
+ // augment the json into a js file
154
+ return [jsonPrefix + source.trim()];
155
+ },
156
+ postprocess: (messages) => {
157
+ return messages[0].map((message) => {
158
+ const { fix } = message;
159
+ if (!fix) {
160
+ return message;
161
+ }
162
+
163
+ const offset = jsonPrefix.length;
164
+ return {
165
+ ...message,
166
+ fix: {
167
+ ...fix,
168
+ range: [fix.range[0] - offset, fix.range[1] - offset],
169
+ },
170
+ };
171
+ });
172
+ },
173
+ supportsAutofix: true,
174
+ } as Linter.Processor,
175
+ // This processor is used for ESLint FlatConfig,
176
+ // once we roll out FlatConfig, we can remove the above processor
177
+ 'package-json-processor-for-flat-config': {
178
+ preprocess: (source: string) => {
179
+ // augment the json into a js file
180
+ return [jsonPrefixForFlatConfig + source.trim()];
181
+ },
182
+ postprocess: (messages) => {
183
+ return messages[0].map((message) => {
184
+ const { fix } = message;
185
+ if (!fix) {
186
+ return message;
187
+ }
188
+
189
+ const offset = jsonPrefixForFlatConfig.length;
190
+ return {
191
+ ...message,
192
+ fix: {
193
+ ...fix,
194
+ range: [fix.range[0] - offset, fix.range[1] - offset],
195
+ },
196
+ };
197
+ });
198
+ },
199
+ supportsAutofix: true,
200
+ } as Linter.Processor,
201
+ },
202
+ } satisfies ESLint.Plugin;
203
+ const configs = plugin.configs;
204
+ const processors = plugin.processors;
205
+
206
+ export { configs, plugin, processors, rules };
207
+ export default plugin;
@@ -272,6 +272,36 @@ const invalidTestCases = (property: string) => {
272
272
  `,
273
273
  errors: Array.from(Array(3), () => ({ messageId: 'expandSpacingShorthand' })),
274
274
  },
275
+ // Strings that are not valid property values should not be autofixed (e.g. !important)
276
+ {
277
+ name: `${property}: Don't autofix if not able to handle all the string values, e.g !important`,
278
+ code: outdent`
279
+ import {css} from '@compiled/react';
280
+ const styles = css({
281
+ ${property}: \`0 \${token('space.200', '16px')} !important\`,
282
+ });
283
+ `,
284
+ errors: [{ messageId: 'expandSpacingShorthand' }],
285
+ },
286
+ {
287
+ name: `${property}: Autofix if able to handle all string values`,
288
+ code: outdent`
289
+ import {css} from '@compiled/react';
290
+ const styles = css({
291
+ ${property}: \`0 auto \${token('space.300', '24px')}\`,
292
+ });
293
+ `,
294
+ output: outdent`
295
+ import {css} from '@compiled/react';
296
+ const styles = css({
297
+ ${property}Top: 0,
298
+ ${property}Right: 'auto',
299
+ ${property}Bottom: token('space.300', '24px'),
300
+ ${property}Left: 'auto',
301
+ });
302
+ `,
303
+ errors: [{ messageId: 'expandSpacingShorthand' }],
304
+ },
275
305
  // Miscellaneous
276
306
  {
277
307
  name: `${property}: new property should not be created if existing property already exists`,
@@ -383,7 +413,7 @@ const invalidTestCases = (property: string) => {
383
413
  `,
384
414
  errors: Array.from(Array(2), () => ({ messageId: 'expandSpacingShorthand' })),
385
415
  },
386
- // TODO (AFB-1022): Resolve this failing test
416
+ // // TODO (AFB-1022): Resolve this failing test
387
417
  // {
388
418
  // name: `${property}: styled components with prop input`,
389
419
  // code: outdent`
@@ -34,7 +34,7 @@ const parseTemplateLiteral = (templateLiteral: TemplateLiteral, context: Rule.Ru
34
34
  if (cookedQuasi) {
35
35
  const splitQuasis = cookedQuasi.split(' ');
36
36
  splitQuasis.forEach((str) => {
37
- str = str.trim().replace("\'", "");
37
+ str = str.trim().replace("'", '');
38
38
  if (str.length > 0) {
39
39
  propertyValues.push(isNaN(Number(str)) ? `'${str}'` : str);
40
40
  }
@@ -49,26 +49,52 @@ const parseTemplateLiteral = (templateLiteral: TemplateLiteral, context: Rule.Ru
49
49
  return propertyValues;
50
50
  };
51
51
 
52
- const checkValidPropertyValues = (propertyValues: string[]) => {
52
+ /**
53
+ * Checks if the parsed property values are valid (i.e. no rule violation will be thrown). Cases are, the property values:
54
+ * 1. Do not contain a token
55
+ * 2. Have length that are not in the range [1, 4]
56
+ * 3. Includes `calc(...)`
57
+ * Then, the rule will return with no error
58
+ * @param propertyValues property values parsed as list of strings
59
+ * @returns boolean
60
+ */
61
+ const isPropertyValueExempted = (propertyValues: string[]) => {
53
62
  if (!propertyValues.some((str) => str.includes('token('))) {
54
- return false;
63
+ return true;
55
64
  }
56
65
  if (propertyValues.length < 1 || propertyValues.length > 4) {
57
- return false;
66
+ return true;
58
67
  }
59
68
  if (propertyValues.some((str) => str.includes('calc('))) {
60
- return false;
69
+ return true;
61
70
  }
62
- return true;
71
+ return false;
63
72
  };
64
73
 
65
- // Check that all expressions in TemplateLiteral are token expressions
66
- // If true: create an autofix
67
- // If false: report violation without autofix
68
- const hasOnlyTokens = (templateLiteral: TemplateLiteral) => {
74
+ /**
75
+ * Checks if the parsed property values are invalid (i.e. rule violation thrown) and autofix required. Cases are when:
76
+ * 1. All expressions in TemplateLiteral are token expressions
77
+ * 2. Property values must have a format which includes -> e.g. 2, '2(rem|em|px)', auto, initial, inherit, token(...)
78
+ * The rule will return with error and provide a fix
79
+ * @param templateLiteral TemplateLiteral AST Node
80
+ * @param propertyValues property values parsed as list of strings
81
+ * @returns boolean
82
+ */
83
+ const isPropertyValuesInvalidFix = (templateLiteral: TemplateLiteral, propertyValues: string[]) => {
69
84
  const expressions = templateLiteral.expressions;
70
- return expressions.every((expr) => expr.type === 'CallExpression' && isTokenCallExpression(expr));
71
- };
85
+ if (!expressions.every((expr) => expr.type === 'CallExpression' && isTokenCallExpression(expr))) {
86
+ return false;
87
+ }
88
+
89
+ for (const propValue of propertyValues) {
90
+ if (propValue === '0') { continue; }
91
+ if (['auto', 'initial', 'inherit'].includes(propValue.slice(1, -1))) { continue; }
92
+ if ((/^token\(.*\)$/).test(propValue)) { continue; }
93
+ if ((/^['"]\d+(\.\d+)?((rem)|(em)|(px))['"]$/).test(propValue)) { continue; }
94
+ return false;
95
+ }
96
+ return true;
97
+ }
72
98
 
73
99
  // To fix spacing shorthands, given a list of spacing property values, expands the spacing property and adds autofix fixes
74
100
  const expandSpacingProperties = ({
@@ -130,8 +156,24 @@ const executeExpandSpacingRule = (
130
156
  return;
131
157
  }
132
158
  if (node.value.type === 'TemplateLiteral') {
133
- // Value of spacing property is a TemplateLiteral type that contains a token, e.g. padding: `0 token('a')`
134
- if (!hasOnlyTokens(node.value)) {
159
+ const propertyValues = parseTemplateLiteral(node.value, context);
160
+ if (isPropertyValueExempted(propertyValues)) {
161
+ // Valid, so no error should be thrown
162
+ return;
163
+ }
164
+ if (isPropertyValuesInvalidFix(node.value, propertyValues)) {
165
+ // Invalid, so error should be thrown and fix provided
166
+ context.report({
167
+ node,
168
+ messageId: 'expandSpacingShorthand',
169
+ data: {
170
+ property: propertyShorthand,
171
+ },
172
+ fix(fixer) {
173
+ return expandSpacingProperties({ context, node, propertyValues, fixer, propertyShorthand });
174
+ },
175
+ });
176
+ } else {
135
177
  context.report({
136
178
  node,
137
179
  messageId: 'expandSpacingShorthand',
@@ -141,20 +183,6 @@ const executeExpandSpacingRule = (
141
183
  });
142
184
  return;
143
185
  }
144
- const propertyValues = parseTemplateLiteral(node.value, context);
145
- if (!checkValidPropertyValues(propertyValues)) {
146
- return;
147
- }
148
- context.report({
149
- node,
150
- messageId: 'expandSpacingShorthand',
151
- data: {
152
- property: propertyShorthand,
153
- },
154
- fix(fixer) {
155
- return expandSpacingProperties({ context, node, propertyValues, fixer, propertyShorthand });
156
- },
157
- });
158
186
  } else if (node.value.type === 'CallExpression' && isTokenCallExpression(node.value)) {
159
187
  // Value of spacing property is a token CallExpression type, e.g. margin: token('space.100', '8px')
160
188
  const propertyValues = [getSourceCode(context).getText(node.value)];