@atlaskit/eslint-plugin-design-system 9.2.0 → 9.2.2

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 CHANGED
@@ -1,5 +1,21 @@
1
1
  # @atlaskit/eslint-plugin-design-system
2
2
 
3
+ ## 9.2.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [#86321](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/86321) [`b353b26e22b6`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/b353b26e22b6) - Improvements for `no-*-tagged-template-expression` rules:
8
+
9
+ - Fixed a bug that could produce syntax errors when mixins were present in nested selectors.
10
+ - Disabled autofixing styled components usages with mixins in nested selectors, as there is no general equivalent.
11
+ - Disabled autofixing function interpolations with non-expression bodies.
12
+
13
+ ## 9.2.1
14
+
15
+ ### Patch Changes
16
+
17
+ - [#85899](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/pull-requests/85899) [`4ee3baaad3b7`](https://stash.atlassian.com/projects/CONFCLOUD/repos/confluence-frontend/commits/4ee3baaad3b7) - Loosen our final autofix check to just ignore all interpolated keys or properties in general for all `no-*-tagged-template-expression` rules as they may result in broken code in some edge-cases.
18
+
3
19
  ## 9.2.0
4
20
 
5
21
  ### Minor Changes
@@ -83,7 +83,7 @@ var generateBlock = function generateBlock(blocks, offset, level) {
83
83
  };
84
84
  var generateArguments = function generateArguments(args, offset, level) {
85
85
  var chars = '';
86
- if (level > 1 && args.length > 1) {
86
+ if (level >= 1 && args.length > 1) {
87
87
  chars += '[';
88
88
  }
89
89
  var _iterator2 = _createForOfIteratorHelper(args.entries()),
@@ -124,7 +124,7 @@ var generateArguments = function generateArguments(args, offset, level) {
124
124
  } finally {
125
125
  _iterator2.f();
126
126
  }
127
- if (level > 1 && args.length > 1) {
127
+ if (level >= 1 && args.length > 1) {
128
128
  chars += '\n';
129
129
  chars += indent(offset, level);
130
130
  chars += ']';
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  });
7
7
  exports.noTaggedTemplateExpressionRuleSchema = exports.createNoTaggedTemplateExpressionRule = void 0;
8
8
  var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
9
+ var _esquery = _interopRequireDefault(require("esquery"));
9
10
  var _isSupportedImport = require("../is-supported-import");
10
11
  var _generate = require("./generate");
11
12
  var _getTaggedTemplateExpressionOffset = require("./get-tagged-template-expression-offset");
@@ -47,7 +48,7 @@ var createNoTaggedTemplateExpressionRule = exports.createNoTaggedTemplateExpress
47
48
  messageId: messageId,
48
49
  node: node,
49
50
  fix: /*#__PURE__*/_regenerator.default.mark(function fix(fixer) {
50
- var quasi, source, args, oldCode, withoutQuasi, newCode, usesEmotion;
51
+ var quasi, source, matches, args, oldCode, withoutQuasi, newCode, usesEmotion;
51
52
  return _regenerator.default.wrap(function fix$(_context) {
52
53
  while (1) switch (_context.prev = _context.next) {
53
54
  case 0:
@@ -62,22 +63,35 @@ var createNoTaggedTemplateExpressionRule = exports.createNoTaggedTemplateExpress
62
63
  }
63
64
  return _context.abrupt("return");
64
65
  case 4:
66
+ matches = (0, _esquery.default)(node, 'ArrowFunctionExpression > BlockStatement');
67
+ if (!matches.length) {
68
+ _context.next = 7;
69
+ break;
70
+ }
71
+ return _context.abrupt("return");
72
+ case 7:
65
73
  if (!(!quasi.expressions.length && quasi.quasis.length === 1 && !quasi.quasis[0].value.raw.trim())) {
66
- _context.next = 8;
74
+ _context.next = 11;
67
75
  break;
68
76
  }
69
- _context.next = 7;
77
+ _context.next = 10;
70
78
  return fixer.replaceText(quasi, '({})');
71
- case 7:
79
+ case 10:
72
80
  return _context.abrupt("return");
73
- case 8:
74
- args = (0, _toArguments.toArguments)(source, quasi); // Skip invalid CSS
81
+ case 11:
82
+ args = (0, _toArguments.toArguments)(source, quasi);
83
+ if (!((0, _isSupportedImport.isStyledComponents)(node.tag, references, importSources) && args.some(hasMixinInsideNestedSelector))) {
84
+ _context.next = 14;
85
+ break;
86
+ }
87
+ return _context.abrupt("return");
88
+ case 14:
75
89
  if (!(args.length < 1)) {
76
- _context.next = 11;
90
+ _context.next = 16;
77
91
  break;
78
92
  }
79
93
  return _context.abrupt("return");
80
- case 11:
94
+ case 16:
81
95
  oldCode = source.getText(node); // Remove quasi:
82
96
  // styled.div<Props>`
83
97
  // color: red;
@@ -89,33 +103,33 @@ var createNoTaggedTemplateExpressionRule = exports.createNoTaggedTemplateExpress
89
103
  // Indent the arguments after the tagged template expression range
90
104
  (0, _generate.generate)(args, (0, _getTaggedTemplateExpressionOffset.getTaggedTemplateExpressionOffset)(node));
91
105
  if (!(oldCode === newCode)) {
92
- _context.next = 16;
106
+ _context.next = 21;
93
107
  break;
94
108
  }
95
109
  return _context.abrupt("return");
96
- case 16:
110
+ case 21:
97
111
  // For styles like `position: initial !important`,
98
112
  // Emotion can give typechecking errors when using object syntax
99
113
  // due to csstype being overly strict
100
114
  usesEmotion = (0, _isSupportedImport.isEmotion)(node.tag, references, importSources);
101
115
  if (!(usesEmotion && !!newCode.match(/!\s*important/gm))) {
102
- _context.next = 19;
116
+ _context.next = 24;
103
117
  break;
104
118
  }
105
119
  return _context.abrupt("return");
106
- case 19:
107
- if (!/\$\{.*:[\s]*\{/.test(newCode)) {
108
- _context.next = 21;
120
+ case 24:
121
+ if (!/\$\{.*:/.test(newCode)) {
122
+ _context.next = 26;
109
123
  break;
110
124
  }
111
125
  return _context.abrupt("return");
112
- case 21:
113
- _context.next = 23;
126
+ case 26:
127
+ _context.next = 28;
114
128
  return fixer.insertTextBefore(node, newCode);
115
- case 23:
116
- _context.next = 25;
129
+ case 28:
130
+ _context.next = 30;
117
131
  return fixer.remove(node);
118
- case 25:
132
+ case 30:
119
133
  case "end":
120
134
  return _context.stop();
121
135
  }
@@ -125,4 +139,20 @@ var createNoTaggedTemplateExpressionRule = exports.createNoTaggedTemplateExpress
125
139
  }
126
140
  };
127
141
  };
128
- };
142
+ };
143
+ function hasMixinInsideNestedSelector(arg) {
144
+ if (arg.type === 'literal' || arg.type === 'expression' || arg.type === 'declaration') {
145
+ return false;
146
+ }
147
+ if (arg.type === 'rule' && arg.declarations.length > 1 && arg.declarations.some(function (node) {
148
+ return node.type === 'expression';
149
+ })) {
150
+ return true;
151
+ }
152
+ if (arg.type === 'block') {
153
+ return arg.blocks.some(hasMixinInsideNestedSelector);
154
+ }
155
+ if (arg.type === 'rule') {
156
+ return arg.declarations.some(hasMixinInsideNestedSelector);
157
+ }
158
+ }
@@ -61,7 +61,7 @@ const generateBlock = (blocks, offset, level) => {
61
61
  };
62
62
  const generateArguments = (args, offset, level) => {
63
63
  let chars = '';
64
- if (level > 1 && args.length > 1) {
64
+ if (level >= 1 && args.length > 1) {
65
65
  chars += '[';
66
66
  }
67
67
  for (const [i, arg] of args.entries()) {
@@ -91,7 +91,7 @@ const generateArguments = (args, offset, level) => {
91
91
  chars += ',';
92
92
  }
93
93
  }
94
- if (level > 1 && args.length > 1) {
94
+ if (level >= 1 && args.length > 1) {
95
95
  chars += '\n';
96
96
  chars += indent(offset, level);
97
97
  chars += ']';
@@ -1,7 +1,8 @@
1
1
  // Original source from Compiled https://github.com/atlassian-labs/compiled/blob/master/packages/eslint-plugin/src/utils/create-no-tagged-template-expression-rule/index.ts
2
2
  // eslint-disable-next-line import/no-extraneous-dependencies
3
3
 
4
- import { getImportSources, isEmotion } from '../is-supported-import';
4
+ import esquery from 'esquery';
5
+ import { getImportSources, isEmotion, isStyledComponents } from '../is-supported-import';
5
6
  import { generate } from './generate';
6
7
  import { getTaggedTemplateExpressionOffset } from './get-tagged-template-expression-offset';
7
8
  import { toArguments } from './to-arguments';
@@ -49,13 +50,24 @@ export const createNoTaggedTemplateExpressionRule = (isUsage, messageId) => cont
49
50
  if (shouldSkipMultilineComments && quasi.quasis.map(q => q.value.raw).join('').match(/\/\*[\s\S]*\*\//g)) {
50
51
  return;
51
52
  }
52
-
53
- // Replace empty tagged template expression with the equivalent object call expression
53
+ const matches = esquery(node, 'ArrowFunctionExpression > BlockStatement');
54
+ if (matches.length) {
55
+ return;
56
+ }
54
57
  if (!quasi.expressions.length && quasi.quasis.length === 1 && !quasi.quasis[0].value.raw.trim()) {
58
+ // Replace empty tagged template expression with the equivalent object call expression
55
59
  yield fixer.replaceText(quasi, '({})');
56
60
  return;
57
61
  }
58
62
  const args = toArguments(source, quasi);
63
+ if (isStyledComponents(node.tag, references, importSources) && args.some(hasMixinInsideNestedSelector)) {
64
+ /**
65
+ * Styled components doesn't support arrays as style object values,
66
+ * so we cannot autofix mixins as we cannot combine them with the other
67
+ * properties in the object.
68
+ */
69
+ return;
70
+ }
59
71
 
60
72
  // Skip invalid CSS
61
73
  if (args.length < 1) {
@@ -83,15 +95,14 @@ export const createNoTaggedTemplateExpressionRule = (isUsage, messageId) => cont
83
95
  if (usesEmotion && !!newCode.match(/!\s*important/gm)) {
84
96
  return;
85
97
  }
86
-
87
- // For styled-components, we might also want to similarly disallow or autofix `styled.div({ color: props => props.color })` as it's broken too (both type and functionality). This is tracked in https://product-fabric.atlassian.net/browse/USS-26.
88
- if (/\$\{.*:[\s]*\{/.test(newCode)) {
98
+ if (/\$\{.*:/.test(newCode)) {
89
99
  /**
90
- * If we find a variable in a selector, we skip it. There are two reasons:
91
- *
100
+ * If we find a variable in a property at all, we skip it. There are two reasons:
92
101
  * - `styled-components@3.x` does not support variables in a selector (see the first example).
102
+ * - We cannot guarantee that the contents of a mixin will ever be valid as a property or selector (which in tagged template expressions don't have to even be called).
103
+ * - It's not uncommon we just get this parsing wrong altogether…
93
104
  *
94
- * - We cannot guarantee that the contents of an function call is actually a selector, and not a CSS block (see the third example).
105
+ * // TODO: In this case, we _might_ want to convert this into a suggestion to support manual remediation, some of those code isn't bad, or it can be manually made safe…
95
106
  *
96
107
  * @examples
97
108
  * ```tsx
@@ -103,6 +114,7 @@ export const createNoTaggedTemplateExpressionRule = (isUsage, messageId) => cont
103
114
  * ```tsx
104
115
  * const Component = styled.div`
105
116
  * ${mixin()} button { color: red; }
117
+ * ${mixin} button { color: red; }
106
118
  * `;
107
119
  * ```
108
120
  *
@@ -123,4 +135,18 @@ export const createNoTaggedTemplateExpressionRule = (isUsage, messageId) => cont
123
135
  });
124
136
  }
125
137
  };
126
- };
138
+ };
139
+ function hasMixinInsideNestedSelector(arg) {
140
+ if (arg.type === 'literal' || arg.type === 'expression' || arg.type === 'declaration') {
141
+ return false;
142
+ }
143
+ if (arg.type === 'rule' && arg.declarations.length > 1 && arg.declarations.some(node => node.type === 'expression')) {
144
+ return true;
145
+ }
146
+ if (arg.type === 'block') {
147
+ return arg.blocks.some(hasMixinInsideNestedSelector);
148
+ }
149
+ if (arg.type === 'rule') {
150
+ return arg.declarations.some(hasMixinInsideNestedSelector);
151
+ }
152
+ }
@@ -76,7 +76,7 @@ var generateBlock = function generateBlock(blocks, offset, level) {
76
76
  };
77
77
  var generateArguments = function generateArguments(args, offset, level) {
78
78
  var chars = '';
79
- if (level > 1 && args.length > 1) {
79
+ if (level >= 1 && args.length > 1) {
80
80
  chars += '[';
81
81
  }
82
82
  var _iterator2 = _createForOfIteratorHelper(args.entries()),
@@ -117,7 +117,7 @@ var generateArguments = function generateArguments(args, offset, level) {
117
117
  } finally {
118
118
  _iterator2.f();
119
119
  }
120
- if (level > 1 && args.length > 1) {
120
+ if (level >= 1 && args.length > 1) {
121
121
  chars += '\n';
122
122
  chars += indent(offset, level);
123
123
  chars += ']';
@@ -2,7 +2,8 @@ import _regeneratorRuntime from "@babel/runtime/regenerator";
2
2
  // Original source from Compiled https://github.com/atlassian-labs/compiled/blob/master/packages/eslint-plugin/src/utils/create-no-tagged-template-expression-rule/index.ts
3
3
  // eslint-disable-next-line import/no-extraneous-dependencies
4
4
 
5
- import { getImportSources, isEmotion } from '../is-supported-import';
5
+ import esquery from 'esquery';
6
+ import { getImportSources, isEmotion, isStyledComponents } from '../is-supported-import';
6
7
  import { generate } from './generate';
7
8
  import { getTaggedTemplateExpressionOffset } from './get-tagged-template-expression-offset';
8
9
  import { toArguments } from './to-arguments';
@@ -40,7 +41,7 @@ export var createNoTaggedTemplateExpressionRule = function createNoTaggedTemplat
40
41
  messageId: messageId,
41
42
  node: node,
42
43
  fix: /*#__PURE__*/_regeneratorRuntime.mark(function fix(fixer) {
43
- var quasi, source, args, oldCode, withoutQuasi, newCode, usesEmotion;
44
+ var quasi, source, matches, args, oldCode, withoutQuasi, newCode, usesEmotion;
44
45
  return _regeneratorRuntime.wrap(function fix$(_context) {
45
46
  while (1) switch (_context.prev = _context.next) {
46
47
  case 0:
@@ -55,22 +56,35 @@ export var createNoTaggedTemplateExpressionRule = function createNoTaggedTemplat
55
56
  }
56
57
  return _context.abrupt("return");
57
58
  case 4:
59
+ matches = esquery(node, 'ArrowFunctionExpression > BlockStatement');
60
+ if (!matches.length) {
61
+ _context.next = 7;
62
+ break;
63
+ }
64
+ return _context.abrupt("return");
65
+ case 7:
58
66
  if (!(!quasi.expressions.length && quasi.quasis.length === 1 && !quasi.quasis[0].value.raw.trim())) {
59
- _context.next = 8;
67
+ _context.next = 11;
60
68
  break;
61
69
  }
62
- _context.next = 7;
70
+ _context.next = 10;
63
71
  return fixer.replaceText(quasi, '({})');
64
- case 7:
72
+ case 10:
65
73
  return _context.abrupt("return");
66
- case 8:
67
- args = toArguments(source, quasi); // Skip invalid CSS
74
+ case 11:
75
+ args = toArguments(source, quasi);
76
+ if (!(isStyledComponents(node.tag, references, importSources) && args.some(hasMixinInsideNestedSelector))) {
77
+ _context.next = 14;
78
+ break;
79
+ }
80
+ return _context.abrupt("return");
81
+ case 14:
68
82
  if (!(args.length < 1)) {
69
- _context.next = 11;
83
+ _context.next = 16;
70
84
  break;
71
85
  }
72
86
  return _context.abrupt("return");
73
- case 11:
87
+ case 16:
74
88
  oldCode = source.getText(node); // Remove quasi:
75
89
  // styled.div<Props>`
76
90
  // color: red;
@@ -82,33 +96,33 @@ export var createNoTaggedTemplateExpressionRule = function createNoTaggedTemplat
82
96
  // Indent the arguments after the tagged template expression range
83
97
  generate(args, getTaggedTemplateExpressionOffset(node));
84
98
  if (!(oldCode === newCode)) {
85
- _context.next = 16;
99
+ _context.next = 21;
86
100
  break;
87
101
  }
88
102
  return _context.abrupt("return");
89
- case 16:
103
+ case 21:
90
104
  // For styles like `position: initial !important`,
91
105
  // Emotion can give typechecking errors when using object syntax
92
106
  // due to csstype being overly strict
93
107
  usesEmotion = isEmotion(node.tag, references, importSources);
94
108
  if (!(usesEmotion && !!newCode.match(/!\s*important/gm))) {
95
- _context.next = 19;
109
+ _context.next = 24;
96
110
  break;
97
111
  }
98
112
  return _context.abrupt("return");
99
- case 19:
100
- if (!/\$\{.*:[\s]*\{/.test(newCode)) {
101
- _context.next = 21;
113
+ case 24:
114
+ if (!/\$\{.*:/.test(newCode)) {
115
+ _context.next = 26;
102
116
  break;
103
117
  }
104
118
  return _context.abrupt("return");
105
- case 21:
106
- _context.next = 23;
119
+ case 26:
120
+ _context.next = 28;
107
121
  return fixer.insertTextBefore(node, newCode);
108
- case 23:
109
- _context.next = 25;
122
+ case 28:
123
+ _context.next = 30;
110
124
  return fixer.remove(node);
111
- case 25:
125
+ case 30:
112
126
  case "end":
113
127
  return _context.stop();
114
128
  }
@@ -118,4 +132,20 @@ export var createNoTaggedTemplateExpressionRule = function createNoTaggedTemplat
118
132
  }
119
133
  };
120
134
  };
121
- };
135
+ };
136
+ function hasMixinInsideNestedSelector(arg) {
137
+ if (arg.type === 'literal' || arg.type === 'expression' || arg.type === 'declaration') {
138
+ return false;
139
+ }
140
+ if (arg.type === 'rule' && arg.declarations.length > 1 && arg.declarations.some(function (node) {
141
+ return node.type === 'expression';
142
+ })) {
143
+ return true;
144
+ }
145
+ if (arg.type === 'block') {
146
+ return arg.blocks.some(hasMixinInsideNestedSelector);
147
+ }
148
+ if (arg.type === 'rule') {
149
+ return arg.declarations.some(hasMixinInsideNestedSelector);
150
+ }
151
+ }
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": "9.2.0",
4
+ "version": "9.2.2",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
7
7
  "publishConfig": {
@@ -42,6 +42,7 @@
42
42
  "@typescript-eslint/utils": "^5.48.1",
43
43
  "ajv": "^6.12.6",
44
44
  "eslint-codemod-utils": "^1.8.6",
45
+ "esquery": "^1.5.0",
45
46
  "estraverse": "^5.3.0",
46
47
  "lodash": "^4.17.21",
47
48
  "semver": "^7.5.2"
@@ -55,6 +56,7 @@
55
56
  "@emotion/react": "^11.7.1",
56
57
  "@emotion/styled": "^11.0.0",
57
58
  "@types/eslint": "^8.56.6",
59
+ "@types/esquery": "^1.5.3",
58
60
  "@types/estraverse": "^5.1.7",
59
61
  "eslint": "^8.49.0",
60
62
  "jscodeshift": "^0.13.0",