@atlaskit/eslint-plugin-design-system 4.13.5 → 4.13.7

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,17 @@
1
1
  # @atlaskit/eslint-plugin-design-system
2
2
 
3
+ ## 4.13.7
4
+
5
+ ### Patch Changes
6
+
7
+ - [`41ac6cadd32`](https://bitbucket.org/atlassian/atlassian-frontend/commits/41ac6cadd32) - Adds support to the ensure-design-token-usage-spacing rule for replacing typography values with tokens
8
+
9
+ ## 4.13.6
10
+
11
+ ### Patch Changes
12
+
13
+ - [`0518a6ab41d`](https://bitbucket.org/atlassian/atlassian-frontend/commits/0518a6ab41d) - Changes behavior of `ensure-design-token-usage-spacing` to fallback to px instead of rems when a fix is applied.
14
+
3
15
  ## 4.13.5
4
16
 
5
17
  ### Patch Changes
@@ -15,6 +15,8 @@ var _eslintCodemodUtils = require("eslint-codemod-utils");
15
15
 
16
16
  var _spacingRaw = _interopRequireDefault(require("@atlaskit/tokens/spacing-raw"));
17
17
 
18
+ var _typographyRaw = _interopRequireDefault(require("@atlaskit/tokens/typography-raw"));
19
+
18
20
  var _isNode = require("../utils/is-node");
19
21
 
20
22
  var _utils = require("./utils");
@@ -23,46 +25,50 @@ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (O
23
25
 
24
26
  function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
25
27
 
26
- var pixelsToRems = function pixelsToRems(pixelValueString) {
27
- var pixels = (0, _utils.removePixelSuffix)(pixelValueString);
28
-
29
- if (pixels === '0' || pixels === 0) {
30
- return pixels;
31
- }
32
-
33
- return "".concat(Number(pixels) / 16, "rem");
34
- };
35
28
  /**
36
29
  * Currently we have a wide range of experimental spacing tokens that we are testing.
37
30
  * We only want transforms to apply to the stable scale values, not the rest.
38
31
  * This could be removed in the future.
39
32
  */
40
-
41
-
42
33
  var onlyScaleTokens = _spacingRaw.default.filter(function (token) {
43
- return token.name.startsWith('spacing.scale.');
34
+ return token.name.startsWith('space.');
44
35
  });
45
36
 
46
37
  var spacingValueToToken = Object.fromEntries(onlyScaleTokens.map(function (token) {
47
- return [token.value, token.name];
38
+ return [token.attributes['pixelValue'], token.name];
39
+ }));
40
+ var typographyValueToToken = Object.fromEntries(_typographyRaw.default.map(function (currentToken) {
41
+ // Group tokens by property name (e.g. fontSize, fontFamily, lineHeight)
42
+ // This allows us to look up values specific to a property
43
+ // (so as not to mix tokens with overlapping values e.g. font size and line height both have tokens for 16px)
44
+ var tokenGroup = currentToken.attributes.group;
45
+ return [tokenGroup, Object.fromEntries(_typographyRaw.default.map(function (token) {
46
+ return token.attributes.group === tokenGroup ? [token.value.replaceAll("\"", "'"), token.name] : [];
47
+ }).filter(function (token) {
48
+ return token.length;
49
+ }))];
48
50
  }));
49
51
  /**
52
+ * Returns a token node for a given value including fallbacks.
53
+ * @param propertyName camelCase CSS property
54
+ * @param value string representing pixel value, or font family, or number representing font weight
50
55
  * @example
51
56
  * ```
52
- * '8px' => token('spacing.scale.100', '8px')
57
+ * propertyName: padding, value: '8px' => token('spacing.scale.100', '8px')
58
+ * propertyName: fontWeight, value: 400 => token('font.weight.regular', '400')
53
59
  * ```
54
60
  */
55
61
 
56
- function pixelValueToSpacingTokenNode(pixelValueString) {
57
- var remValueString = pixelsToRems(pixelValueString);
58
- var token = spacingValueToToken[remValueString];
62
+ function getTokenNodeForValue(propertyName, value) {
63
+ var token = (0, _utils.isTypographyProperty)(propertyName) ? typographyValueToToken[propertyName][value] : spacingValueToToken[value];
64
+ var fallbackValue = propertyName === 'fontFamily' ? "\"".concat(value, "\"") : "'".concat(value, "'");
59
65
  return (0, _eslintCodemodUtils.callExpression)({
60
66
  callee: (0, _eslintCodemodUtils.identifier)({
61
67
  name: 'token'
62
68
  }),
63
69
  arguments: [(0, _eslintCodemodUtils.literal)({
64
70
  value: "'".concat(token !== null && token !== void 0 ? token : '', "'")
65
- }), (0, _eslintCodemodUtils.literal)("'".concat(pixelValueString, "'"))],
71
+ }), (0, _eslintCodemodUtils.literal)(fallbackValue)],
66
72
  optional: false
67
73
  });
68
74
  }
@@ -144,6 +150,8 @@ var rule = {
144
150
  return;
145
151
  }
146
152
 
153
+ var propertyName = node.key.name;
154
+ var isFontFamily = /fontFamily/.test(propertyName);
147
155
  var value = (0, _utils.getValue)(node.value, context); // value is either NaN or it can't be resolved eg, em, 100% etc...
148
156
 
149
157
  if (!(value && (0, _utils.isValidSpacingValue)(value, fontSize))) {
@@ -157,35 +165,37 @@ var rule = {
157
165
  }
158
166
 
159
167
  var values = Array.isArray(value) ? value : [value]; // value is a single value so we can apply a more robust approach to our fix
168
+ // treat fontFamily as having one value
160
169
 
161
- if (values.length === 1) {
170
+ if (values.length === 1 || isFontFamily) {
162
171
  var _values = (0, _slicedToArray2.default)(values, 1),
163
172
  _value = _values[0];
164
173
 
165
- var pixelValue = (0, _utils.emToPixels)(_value, fontSize);
174
+ var pixelValue = isFontFamily ? _value : (0, _utils.emToPixels)(_value, fontSize);
166
175
  return context.report({
167
176
  node: node,
168
177
  messageId: 'noRawSpacingValues',
169
178
  data: {
170
- payload: "".concat(node.key.name, ":").concat(pixelValue)
179
+ payload: "".concat(propertyName, ":").concat(pixelValue)
171
180
  },
172
181
  fix: function fix(fixer) {
173
182
  var _node$loc;
174
183
 
175
- if (!/padding|margin|gap/.test(node.key.name)) {
184
+ if (!(0, _utils.isSpacingProperty)(propertyName)) {
176
185
  return null;
177
186
  }
178
187
 
179
188
  var pixelValueString = "".concat(pixelValue, "px");
180
- var remValueString = pixelsToRems(pixelValueString);
181
- var tokenName = spacingValueToToken[remValueString];
189
+ var lookupValue = /fontWeight|fontFamily/.test(propertyName) ? pixelValue : pixelValueString;
190
+ var tokenName = (0, _utils.isTypographyProperty)(propertyName) ? typographyValueToToken[propertyName][lookupValue] : spacingValueToToken[lookupValue];
182
191
 
183
192
  if (!tokenName) {
184
193
  return null;
185
194
  }
186
195
 
196
+ var replacementValue = getTokenNodeForValue(propertyName, lookupValue);
187
197
  return [fixer.insertTextBefore(node, "// TODO Delete this comment after verifying spacing token -> previous value `".concat((0, _eslintCodemodUtils.node)(node.value), "`\n").concat(' '.padStart(((_node$loc = node.loc) === null || _node$loc === void 0 ? void 0 : _node$loc.start.column) || 0))), fixer.replaceText(node, (0, _eslintCodemodUtils.property)(_objectSpread(_objectSpread({}, node), {}, {
188
- value: pixelValueToSpacingTokenNode(pixelValueString)
198
+ value: replacementValue
189
199
  })).toString())];
190
200
  }
191
201
  });
@@ -205,7 +215,7 @@ var rule = {
205
215
  node: node,
206
216
  messageId: 'noRawSpacingValues',
207
217
  data: {
208
- payload: "".concat(node.key.name, ":").concat(pixelValue)
218
+ payload: "".concat(propertyName, ":").concat(pixelValue)
209
219
  },
210
220
  fix: index === 0 ? function (fixer) {
211
221
  var allResolvableValues = values.every(function (value) {
@@ -219,7 +229,7 @@ var rule = {
219
229
  return fixer.replaceText(node.value, "`".concat(values.map(function (value) {
220
230
  var pixelValue = (0, _utils.emToPixels)(value, fontSize);
221
231
  var pixelValueString = "".concat(pixelValue, "px");
222
- return "${".concat(pixelValueToSpacingTokenNode(pixelValueString), "}");
232
+ return "${".concat(getTokenNodeForValue(propertyName, pixelValueString), "}");
223
233
  }).join(' '), "`"));
224
234
  } : undefined
225
235
  });
@@ -268,9 +278,9 @@ var rule = {
268
278
  rawProperty = _style$split4[0],
269
279
  value = _style$split4[1];
270
280
 
271
- var property = (0, _utils.convertHyphenatedNameToCamelCase)(rawProperty);
281
+ var propertyName = (0, _utils.convertHyphenatedNameToCamelCase)(rawProperty);
272
282
 
273
- if (!(0, _utils.isSpacingProperty)(property)) {
283
+ if (!(0, _utils.isSpacingProperty)(propertyName)) {
274
284
  return;
275
285
  } // value is either NaN or it can't be resolved eg, em, 100% etc...
276
286
 
@@ -287,16 +297,17 @@ var rule = {
287
297
 
288
298
  var values = (0, _utils.getValueFromShorthand)(value);
289
299
  values.forEach(function (val, index) {
290
- if (!val && val !== 0 || !/padding|margin|gap/.test(rawProperty)) {
300
+ if (!val && val !== 0 || !(0, _utils.isSpacingProperty)(propertyName)) {
291
301
  return;
292
302
  }
293
303
 
294
- var pixelValue = (0, _utils.emToPixels)(val, fontSize);
304
+ var isFontFamily = /fontFamily/.test(propertyName);
305
+ var pixelValue = isFontFamily ? val : (0, _utils.emToPixels)(val, fontSize);
295
306
  context.report({
296
307
  node: node,
297
308
  messageId: 'noRawSpacingValues',
298
309
  data: {
299
- payload: "".concat(property, ":").concat(pixelValue)
310
+ payload: "".concat(propertyName, ":").concat(pixelValue)
300
311
  },
301
312
  fix: index === 0 ? function (fixer) {
302
313
  var allResolvableValues = values.every(function (value) {
@@ -308,17 +319,19 @@ var rule = {
308
319
  }
309
320
 
310
321
  var replacementValue = values.map(function (value) {
311
- var pixelValue = (0, _utils.emToPixels)(value, fontSize);
322
+ var propertyValue = typeof value === 'string' ? value.trim() : value;
323
+ var pixelValue = isFontFamily ? propertyValue : (0, _utils.emToPixels)(propertyValue, fontSize);
312
324
  var pixelValueString = "".concat(pixelValue, "px");
313
- var remValueString = pixelsToRems(pixelValueString);
314
- var tokenName = spacingValueToToken[remValueString];
325
+ var lookupValue = /fontWeight|fontFamily/.test(propertyName) ? pixelValue : pixelValueString;
326
+ var tokenName = (0, _utils.isTypographyProperty)(propertyName) ? typographyValueToToken[propertyName][lookupValue] : spacingValueToToken[lookupValue];
315
327
 
316
328
  if (!tokenName) {
317
329
  return pixelValueString;
318
- } // ${token('...', '...')}
330
+ }
319
331
 
332
+ var replacementTokenValue = getTokenNodeForValue(propertyName, lookupValue); // ${token('...', '...')}
320
333
 
321
- var replacementSubValue = '${' + pixelValueToSpacingTokenNode(pixelValueString).toString() + '}';
334
+ var replacementSubValue = '${' + replacementTokenValue.toString() + '}';
322
335
  return replacementSubValue;
323
336
  }).join(' '); // get original source
324
337
 
@@ -5,13 +5,14 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.emToPixels = exports.convertHyphenatedNameToCamelCase = void 0;
7
7
  exports.findIdentifierInParentScope = findIdentifierInParentScope;
8
- exports.removePixelSuffix = exports.isValidSpacingValue = exports.isSpacingProperty = exports.getValueFromShorthand = exports.getValue = exports.findParentNodeForLine = void 0;
8
+ exports.removePixelSuffix = exports.isValidSpacingValue = exports.isTypographyProperty = exports.isSpacingProperty = exports.getValueFromShorthand = exports.getValue = exports.findParentNodeForLine = void 0;
9
9
 
10
10
  var _eslintCodemodUtils = require("eslint-codemod-utils");
11
11
 
12
- var properties = ['padding', 'paddingBlock', 'paddingInline', 'paddingLeft', 'paddingTop', 'paddingRight', 'paddingBottom', 'marginLeft', 'marginTop', 'marginRight', 'marginBottom', 'margin', 'gap', 'fontSize', 'lineHeight', // 'width', re-enable later
12
+ var typographyProperties = ['fontSize', 'fontWeight', 'fontFamily', 'lineHeight'];
13
+ var properties = ['padding', 'paddingBlock', 'paddingInline', 'paddingLeft', 'paddingTop', 'paddingRight', 'paddingBottom', 'marginLeft', 'marginTop', 'marginRight', 'marginBottom', 'margin', 'gap'].concat(typographyProperties, [// 'width', re-enable later
13
14
  // 'height', re-enable later
14
- 'rowGap', 'gridRowGap', 'columnGap', 'gridColumnGap'];
15
+ 'rowGap', 'gridRowGap', 'columnGap', 'gridColumnGap']);
15
16
 
16
17
  function findIdentifierInParentScope(_ref) {
17
18
  var scope = _ref.scope,
@@ -33,14 +34,27 @@ function findIdentifierInParentScope(_ref) {
33
34
  return null;
34
35
  }
35
36
 
36
- var isSpacingProperty = function isSpacingProperty(prop) {
37
- return properties.includes(prop);
37
+ var isSpacingProperty = function isSpacingProperty(propertyName) {
38
+ return properties.includes(propertyName);
38
39
  };
39
40
 
40
41
  exports.isSpacingProperty = isSpacingProperty;
41
42
 
43
+ var isTypographyProperty = function isTypographyProperty(propertyName) {
44
+ return typographyProperties.includes(propertyName);
45
+ };
46
+
47
+ exports.isTypographyProperty = isTypographyProperty;
48
+
42
49
  var getValueFromShorthand = function getValueFromShorthand(str) {
43
- // If we want to filter out NaN just add .filter(Boolean)
50
+ var valueString = String(str);
51
+ var fontFamily = /(sans-serif$)|(monospace$)/;
52
+
53
+ if (fontFamily.test(valueString)) {
54
+ return [valueString];
55
+ } // If we want to filter out NaN just add .filter(Boolean)
56
+
57
+
44
58
  return String(str).trim().split(' ').filter(function (val) {
45
59
  return val !== '';
46
60
  }).map(removePixelSuffix);
@@ -60,6 +74,14 @@ var isFontSizeSmall = function isFontSizeSmall(node) {
60
74
  return (0, _eslintCodemodUtils.isNodeOfType)(node, 'CallExpression') && (0, _eslintCodemodUtils.isNodeOfType)(node.callee, 'Identifier') && node.callee.name === 'fontSizeSmall';
61
75
  };
62
76
 
77
+ var isFontFamily = function isFontFamily(node) {
78
+ return (0, _eslintCodemodUtils.isNodeOfType)(node, 'CallExpression') && (0, _eslintCodemodUtils.isNodeOfType)(node.callee, 'Identifier') && (node.callee.name === 'fontFamily' || node.callee.name === 'getFontFamily');
79
+ };
80
+
81
+ var isCodeFontFamily = function isCodeFontFamily(node) {
82
+ return (0, _eslintCodemodUtils.isNodeOfType)(node, 'CallExpression') && (0, _eslintCodemodUtils.isNodeOfType)(node.callee, 'Identifier') && (node.callee.name === 'codeFontFamily' || node.callee.name === 'getCodeFontFamily');
83
+ };
84
+
63
85
  var isToken = function isToken(node) {
64
86
  return (0, _eslintCodemodUtils.isNodeOfType)(node, 'CallExpression') && (0, _eslintCodemodUtils.isNodeOfType)(node.callee, 'Identifier') && node.callee.name === 'token';
65
87
  };
@@ -81,6 +103,14 @@ var getValueFromCallExpression = function getValueFromCallExpression(node, conte
81
103
  return 11;
82
104
  }
83
105
 
106
+ if (isFontFamily(node)) {
107
+ return "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif";
108
+ }
109
+
110
+ if (isCodeFontFamily(node)) {
111
+ return "'SFMono-Medium', 'SF Mono', 'Segoe UI Mono', 'Roboto Mono', 'Ubuntu Mono', Menlo, Consolas, Courier, monospace";
112
+ }
113
+
84
114
  if (isToken(node)) {
85
115
  var args = node.arguments;
86
116
  var call = "${token(".concat(args.map(function (argNode) {
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@atlaskit/eslint-plugin-design-system",
3
- "version": "4.13.5",
3
+ "version": "4.13.7",
4
4
  "sideEffects": false
5
5
  }
@@ -1,43 +1,44 @@
1
1
  import { callExpression, identifier, isNodeOfType, literal, node as nodeFn, property } from 'eslint-codemod-utils';
2
2
  import spacingScale from '@atlaskit/tokens/spacing-raw';
3
+ import typographyTokens from '@atlaskit/tokens/typography-raw';
3
4
  import { isDecendantOfGlobalToken } from '../utils/is-node';
4
- import { convertHyphenatedNameToCamelCase, emToPixels, findParentNodeForLine, getValue, getValueFromShorthand, isSpacingProperty, isValidSpacingValue, removePixelSuffix } from './utils';
5
-
6
- const pixelsToRems = pixelValueString => {
7
- const pixels = removePixelSuffix(pixelValueString);
8
-
9
- if (pixels === '0' || pixels === 0) {
10
- return pixels;
11
- }
12
-
13
- return `${Number(pixels) / 16}rem`;
14
- };
5
+ import { convertHyphenatedNameToCamelCase, emToPixels, findParentNodeForLine, getValue, getValueFromShorthand, isSpacingProperty, isTypographyProperty, isValidSpacingValue } from './utils';
15
6
  /**
16
7
  * Currently we have a wide range of experimental spacing tokens that we are testing.
17
8
  * We only want transforms to apply to the stable scale values, not the rest.
18
9
  * This could be removed in the future.
19
10
  */
20
11
 
21
-
22
- const onlyScaleTokens = spacingScale.filter(token => token.name.startsWith('spacing.scale.'));
23
- const spacingValueToToken = Object.fromEntries(onlyScaleTokens.map(token => [token.value, token.name]));
12
+ const onlyScaleTokens = spacingScale.filter(token => token.name.startsWith('space.'));
13
+ const spacingValueToToken = Object.fromEntries(onlyScaleTokens.map(token => [token.attributes['pixelValue'], token.name]));
14
+ const typographyValueToToken = Object.fromEntries(typographyTokens.map(currentToken => {
15
+ // Group tokens by property name (e.g. fontSize, fontFamily, lineHeight)
16
+ // This allows us to look up values specific to a property
17
+ // (so as not to mix tokens with overlapping values e.g. font size and line height both have tokens for 16px)
18
+ const tokenGroup = currentToken.attributes.group;
19
+ return [tokenGroup, Object.fromEntries(typographyTokens.map(token => token.attributes.group === tokenGroup ? [token.value.replaceAll(`"`, `'`), token.name] : []).filter(token => token.length))];
20
+ }));
24
21
  /**
22
+ * Returns a token node for a given value including fallbacks.
23
+ * @param propertyName camelCase CSS property
24
+ * @param value string representing pixel value, or font family, or number representing font weight
25
25
  * @example
26
26
  * ```
27
- * '8px' => token('spacing.scale.100', '8px')
27
+ * propertyName: padding, value: '8px' => token('spacing.scale.100', '8px')
28
+ * propertyName: fontWeight, value: 400 => token('font.weight.regular', '400')
28
29
  * ```
29
30
  */
30
31
 
31
- function pixelValueToSpacingTokenNode(pixelValueString) {
32
- const remValueString = pixelsToRems(pixelValueString);
33
- const token = spacingValueToToken[remValueString];
32
+ function getTokenNodeForValue(propertyName, value) {
33
+ const token = isTypographyProperty(propertyName) ? typographyValueToToken[propertyName][value] : spacingValueToToken[value];
34
+ const fallbackValue = propertyName === 'fontFamily' ? `"${value}"` : `'${value}'`;
34
35
  return callExpression({
35
36
  callee: identifier({
36
37
  name: 'token'
37
38
  }),
38
39
  arguments: [literal({
39
40
  value: `'${token !== null && token !== void 0 ? token : ''}'`
40
- }), literal(`'${pixelValueString}'`)],
41
+ }), literal(fallbackValue)],
41
42
  optional: false
42
43
  });
43
44
  }
@@ -120,6 +121,8 @@ const rule = {
120
121
  return;
121
122
  }
122
123
 
124
+ const propertyName = node.key.name;
125
+ const isFontFamily = /fontFamily/.test(propertyName);
123
126
  const value = getValue(node.value, context); // value is either NaN or it can't be resolved eg, em, 100% etc...
124
127
 
125
128
  if (!(value && isValidSpacingValue(value, fontSize))) {
@@ -133,33 +136,35 @@ const rule = {
133
136
  }
134
137
 
135
138
  const values = Array.isArray(value) ? value : [value]; // value is a single value so we can apply a more robust approach to our fix
139
+ // treat fontFamily as having one value
136
140
 
137
- if (values.length === 1) {
141
+ if (values.length === 1 || isFontFamily) {
138
142
  const [value] = values;
139
- const pixelValue = emToPixels(value, fontSize);
143
+ const pixelValue = isFontFamily ? value : emToPixels(value, fontSize);
140
144
  return context.report({
141
145
  node,
142
146
  messageId: 'noRawSpacingValues',
143
147
  data: {
144
- payload: `${node.key.name}:${pixelValue}`
148
+ payload: `${propertyName}:${pixelValue}`
145
149
  },
146
150
  fix: fixer => {
147
151
  var _node$loc;
148
152
 
149
- if (!/padding|margin|gap/.test(node.key.name)) {
153
+ if (!isSpacingProperty(propertyName)) {
150
154
  return null;
151
155
  }
152
156
 
153
157
  const pixelValueString = `${pixelValue}px`;
154
- const remValueString = pixelsToRems(pixelValueString);
155
- const tokenName = spacingValueToToken[remValueString];
158
+ const lookupValue = /fontWeight|fontFamily/.test(propertyName) ? pixelValue : pixelValueString;
159
+ const tokenName = isTypographyProperty(propertyName) ? typographyValueToToken[propertyName][lookupValue] : spacingValueToToken[lookupValue];
156
160
 
157
161
  if (!tokenName) {
158
162
  return null;
159
163
  }
160
164
 
165
+ const replacementValue = getTokenNodeForValue(propertyName, lookupValue);
161
166
  return [fixer.insertTextBefore(node, `// TODO Delete this comment after verifying spacing token -> previous value \`${nodeFn(node.value)}\`\n${' '.padStart(((_node$loc = node.loc) === null || _node$loc === void 0 ? void 0 : _node$loc.start.column) || 0)}`), fixer.replaceText(node, property({ ...node,
162
- value: pixelValueToSpacingTokenNode(pixelValueString)
167
+ value: replacementValue
163
168
  }).toString())];
164
169
  }
165
170
  });
@@ -179,7 +184,7 @@ const rule = {
179
184
  node,
180
185
  messageId: 'noRawSpacingValues',
181
186
  data: {
182
- payload: `${node.key.name}:${pixelValue}`
187
+ payload: `${propertyName}:${pixelValue}`
183
188
  },
184
189
  fix: index === 0 ? fixer => {
185
190
  const allResolvableValues = values.every(value => !Number.isNaN(emToPixels(value, fontSize)));
@@ -191,7 +196,7 @@ const rule = {
191
196
  return fixer.replaceText(node.value, `\`${values.map(value => {
192
197
  const pixelValue = emToPixels(value, fontSize);
193
198
  const pixelValueString = `${pixelValue}px`;
194
- return `\${${pixelValueToSpacingTokenNode(pixelValueString)}}`;
199
+ return `\${${getTokenNodeForValue(propertyName, pixelValueString)}}`;
195
200
  }).join(' ')}\``);
196
201
  } : undefined
197
202
  });
@@ -228,9 +233,9 @@ const rule = {
228
233
  const fontSize = getValueFromShorthand(fontSizeNode)[0];
229
234
  cssProperties.forEach(style => {
230
235
  const [rawProperty, value] = style.split(':');
231
- const property = convertHyphenatedNameToCamelCase(rawProperty);
236
+ const propertyName = convertHyphenatedNameToCamelCase(rawProperty);
232
237
 
233
- if (!isSpacingProperty(property)) {
238
+ if (!isSpacingProperty(propertyName)) {
234
239
  return;
235
240
  } // value is either NaN or it can't be resolved eg, em, 100% etc...
236
241
 
@@ -247,16 +252,17 @@ const rule = {
247
252
 
248
253
  const values = getValueFromShorthand(value);
249
254
  values.forEach((val, index) => {
250
- if (!val && val !== 0 || !/padding|margin|gap/.test(rawProperty)) {
255
+ if (!val && val !== 0 || !isSpacingProperty(propertyName)) {
251
256
  return;
252
257
  }
253
258
 
254
- const pixelValue = emToPixels(val, fontSize);
259
+ const isFontFamily = /fontFamily/.test(propertyName);
260
+ const pixelValue = isFontFamily ? val : emToPixels(val, fontSize);
255
261
  context.report({
256
262
  node,
257
263
  messageId: 'noRawSpacingValues',
258
264
  data: {
259
- payload: `${property}:${pixelValue}`
265
+ payload: `${propertyName}:${pixelValue}`
260
266
  },
261
267
  fix: index === 0 ? fixer => {
262
268
  const allResolvableValues = values.every(value => !Number.isNaN(emToPixels(value, fontSize)));
@@ -266,17 +272,19 @@ const rule = {
266
272
  }
267
273
 
268
274
  const replacementValue = values.map(value => {
269
- const pixelValue = emToPixels(value, fontSize);
275
+ const propertyValue = typeof value === 'string' ? value.trim() : value;
276
+ const pixelValue = isFontFamily ? propertyValue : emToPixels(propertyValue, fontSize);
270
277
  const pixelValueString = `${pixelValue}px`;
271
- const remValueString = pixelsToRems(pixelValueString);
272
- const tokenName = spacingValueToToken[remValueString];
278
+ const lookupValue = /fontWeight|fontFamily/.test(propertyName) ? pixelValue : pixelValueString;
279
+ const tokenName = isTypographyProperty(propertyName) ? typographyValueToToken[propertyName][lookupValue] : spacingValueToToken[lookupValue];
273
280
 
274
281
  if (!tokenName) {
275
282
  return pixelValueString;
276
- } // ${token('...', '...')}
283
+ }
277
284
 
285
+ const replacementTokenValue = getTokenNodeForValue(propertyName, lookupValue); // ${token('...', '...')}
278
286
 
279
- const replacementSubValue = '${' + pixelValueToSpacingTokenNode(pixelValueString).toString() + '}';
287
+ const replacementSubValue = '${' + replacementTokenValue.toString() + '}';
280
288
  return replacementSubValue;
281
289
  }).join(' '); // get original source
282
290
 
@@ -1,5 +1,6 @@
1
1
  import { isNodeOfType } from 'eslint-codemod-utils';
2
- const properties = ['padding', 'paddingBlock', 'paddingInline', 'paddingLeft', 'paddingTop', 'paddingRight', 'paddingBottom', 'marginLeft', 'marginTop', 'marginRight', 'marginBottom', 'margin', 'gap', 'fontSize', 'lineHeight', // 'width', re-enable later
2
+ const typographyProperties = ['fontSize', 'fontWeight', 'fontFamily', 'lineHeight'];
3
+ const properties = ['padding', 'paddingBlock', 'paddingInline', 'paddingLeft', 'paddingTop', 'paddingRight', 'paddingBottom', 'marginLeft', 'marginTop', 'marginRight', 'marginBottom', 'margin', 'gap', ...typographyProperties, // 'width', re-enable later
3
4
  // 'height', re-enable later
4
5
  'rowGap', 'gridRowGap', 'columnGap', 'gridColumnGap'];
5
6
  export function findIdentifierInParentScope({
@@ -20,11 +21,21 @@ export function findIdentifierInParentScope({
20
21
 
21
22
  return null;
22
23
  }
23
- export const isSpacingProperty = prop => {
24
- return properties.includes(prop);
24
+ export const isSpacingProperty = propertyName => {
25
+ return properties.includes(propertyName);
26
+ };
27
+ export const isTypographyProperty = propertyName => {
28
+ return typographyProperties.includes(propertyName);
25
29
  };
26
30
  export const getValueFromShorthand = str => {
27
- // If we want to filter out NaN just add .filter(Boolean)
31
+ const valueString = String(str);
32
+ const fontFamily = /(sans-serif$)|(monospace$)/;
33
+
34
+ if (fontFamily.test(valueString)) {
35
+ return [valueString];
36
+ } // If we want to filter out NaN just add .filter(Boolean)
37
+
38
+
28
39
  return String(str).trim().split(' ').filter(val => val !== '').map(removePixelSuffix);
29
40
  };
30
41
 
@@ -34,6 +45,10 @@ const isFontSize = node => isNodeOfType(node, 'CallExpression') && isNodeOfType(
34
45
 
35
46
  const isFontSizeSmall = node => isNodeOfType(node, 'CallExpression') && isNodeOfType(node.callee, 'Identifier') && node.callee.name === 'fontSizeSmall';
36
47
 
48
+ const isFontFamily = node => isNodeOfType(node, 'CallExpression') && isNodeOfType(node.callee, 'Identifier') && (node.callee.name === 'fontFamily' || node.callee.name === 'getFontFamily');
49
+
50
+ const isCodeFontFamily = node => isNodeOfType(node, 'CallExpression') && isNodeOfType(node.callee, 'Identifier') && (node.callee.name === 'codeFontFamily' || node.callee.name === 'getCodeFontFamily');
51
+
37
52
  const isToken = node => isNodeOfType(node, 'CallExpression') && isNodeOfType(node.callee, 'Identifier') && node.callee.name === 'token';
38
53
 
39
54
  const getValueFromCallExpression = (node, context) => {
@@ -53,6 +68,14 @@ const getValueFromCallExpression = (node, context) => {
53
68
  return 11;
54
69
  }
55
70
 
71
+ if (isFontFamily(node)) {
72
+ return `-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif`;
73
+ }
74
+
75
+ if (isCodeFontFamily(node)) {
76
+ return `'SFMono-Medium', 'SF Mono', 'Segoe UI Mono', 'Roboto Mono', 'Ubuntu Mono', Menlo, Consolas, Courier, monospace`;
77
+ }
78
+
56
79
  if (isToken(node)) {
57
80
  const args = node.arguments;
58
81
  const call = `\${token(${args.map(argNode => {
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@atlaskit/eslint-plugin-design-system",
3
- "version": "4.13.5",
3
+ "version": "4.13.7",
4
4
  "sideEffects": false
5
5
  }
@@ -7,48 +7,53 @@ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { va
7
7
 
8
8
  import { callExpression, identifier, isNodeOfType, literal, node as nodeFn, property } from 'eslint-codemod-utils';
9
9
  import spacingScale from '@atlaskit/tokens/spacing-raw';
10
+ import typographyTokens from '@atlaskit/tokens/typography-raw';
10
11
  import { isDecendantOfGlobalToken } from '../utils/is-node';
11
- import { convertHyphenatedNameToCamelCase, emToPixels, findParentNodeForLine, getValue, getValueFromShorthand, isSpacingProperty, isValidSpacingValue, removePixelSuffix } from './utils';
12
-
13
- var pixelsToRems = function pixelsToRems(pixelValueString) {
14
- var pixels = removePixelSuffix(pixelValueString);
15
-
16
- if (pixels === '0' || pixels === 0) {
17
- return pixels;
18
- }
19
-
20
- return "".concat(Number(pixels) / 16, "rem");
21
- };
12
+ import { convertHyphenatedNameToCamelCase, emToPixels, findParentNodeForLine, getValue, getValueFromShorthand, isSpacingProperty, isTypographyProperty, isValidSpacingValue } from './utils';
22
13
  /**
23
14
  * Currently we have a wide range of experimental spacing tokens that we are testing.
24
15
  * We only want transforms to apply to the stable scale values, not the rest.
25
16
  * This could be removed in the future.
26
17
  */
27
18
 
28
-
29
19
  var onlyScaleTokens = spacingScale.filter(function (token) {
30
- return token.name.startsWith('spacing.scale.');
20
+ return token.name.startsWith('space.');
31
21
  });
32
22
  var spacingValueToToken = Object.fromEntries(onlyScaleTokens.map(function (token) {
33
- return [token.value, token.name];
23
+ return [token.attributes['pixelValue'], token.name];
24
+ }));
25
+ var typographyValueToToken = Object.fromEntries(typographyTokens.map(function (currentToken) {
26
+ // Group tokens by property name (e.g. fontSize, fontFamily, lineHeight)
27
+ // This allows us to look up values specific to a property
28
+ // (so as not to mix tokens with overlapping values e.g. font size and line height both have tokens for 16px)
29
+ var tokenGroup = currentToken.attributes.group;
30
+ return [tokenGroup, Object.fromEntries(typographyTokens.map(function (token) {
31
+ return token.attributes.group === tokenGroup ? [token.value.replaceAll("\"", "'"), token.name] : [];
32
+ }).filter(function (token) {
33
+ return token.length;
34
+ }))];
34
35
  }));
35
36
  /**
37
+ * Returns a token node for a given value including fallbacks.
38
+ * @param propertyName camelCase CSS property
39
+ * @param value string representing pixel value, or font family, or number representing font weight
36
40
  * @example
37
41
  * ```
38
- * '8px' => token('spacing.scale.100', '8px')
42
+ * propertyName: padding, value: '8px' => token('spacing.scale.100', '8px')
43
+ * propertyName: fontWeight, value: 400 => token('font.weight.regular', '400')
39
44
  * ```
40
45
  */
41
46
 
42
- function pixelValueToSpacingTokenNode(pixelValueString) {
43
- var remValueString = pixelsToRems(pixelValueString);
44
- var token = spacingValueToToken[remValueString];
47
+ function getTokenNodeForValue(propertyName, value) {
48
+ var token = isTypographyProperty(propertyName) ? typographyValueToToken[propertyName][value] : spacingValueToToken[value];
49
+ var fallbackValue = propertyName === 'fontFamily' ? "\"".concat(value, "\"") : "'".concat(value, "'");
45
50
  return callExpression({
46
51
  callee: identifier({
47
52
  name: 'token'
48
53
  }),
49
54
  arguments: [literal({
50
55
  value: "'".concat(token !== null && token !== void 0 ? token : '', "'")
51
- }), literal("'".concat(pixelValueString, "'"))],
56
+ }), literal(fallbackValue)],
52
57
  optional: false
53
58
  });
54
59
  }
@@ -130,6 +135,8 @@ var rule = {
130
135
  return;
131
136
  }
132
137
 
138
+ var propertyName = node.key.name;
139
+ var isFontFamily = /fontFamily/.test(propertyName);
133
140
  var value = getValue(node.value, context); // value is either NaN or it can't be resolved eg, em, 100% etc...
134
141
 
135
142
  if (!(value && isValidSpacingValue(value, fontSize))) {
@@ -143,35 +150,37 @@ var rule = {
143
150
  }
144
151
 
145
152
  var values = Array.isArray(value) ? value : [value]; // value is a single value so we can apply a more robust approach to our fix
153
+ // treat fontFamily as having one value
146
154
 
147
- if (values.length === 1) {
155
+ if (values.length === 1 || isFontFamily) {
148
156
  var _values = _slicedToArray(values, 1),
149
157
  _value = _values[0];
150
158
 
151
- var pixelValue = emToPixels(_value, fontSize);
159
+ var pixelValue = isFontFamily ? _value : emToPixels(_value, fontSize);
152
160
  return context.report({
153
161
  node: node,
154
162
  messageId: 'noRawSpacingValues',
155
163
  data: {
156
- payload: "".concat(node.key.name, ":").concat(pixelValue)
164
+ payload: "".concat(propertyName, ":").concat(pixelValue)
157
165
  },
158
166
  fix: function fix(fixer) {
159
167
  var _node$loc;
160
168
 
161
- if (!/padding|margin|gap/.test(node.key.name)) {
169
+ if (!isSpacingProperty(propertyName)) {
162
170
  return null;
163
171
  }
164
172
 
165
173
  var pixelValueString = "".concat(pixelValue, "px");
166
- var remValueString = pixelsToRems(pixelValueString);
167
- var tokenName = spacingValueToToken[remValueString];
174
+ var lookupValue = /fontWeight|fontFamily/.test(propertyName) ? pixelValue : pixelValueString;
175
+ var tokenName = isTypographyProperty(propertyName) ? typographyValueToToken[propertyName][lookupValue] : spacingValueToToken[lookupValue];
168
176
 
169
177
  if (!tokenName) {
170
178
  return null;
171
179
  }
172
180
 
181
+ var replacementValue = getTokenNodeForValue(propertyName, lookupValue);
173
182
  return [fixer.insertTextBefore(node, "// TODO Delete this comment after verifying spacing token -> previous value `".concat(nodeFn(node.value), "`\n").concat(' '.padStart(((_node$loc = node.loc) === null || _node$loc === void 0 ? void 0 : _node$loc.start.column) || 0))), fixer.replaceText(node, property(_objectSpread(_objectSpread({}, node), {}, {
174
- value: pixelValueToSpacingTokenNode(pixelValueString)
183
+ value: replacementValue
175
184
  })).toString())];
176
185
  }
177
186
  });
@@ -191,7 +200,7 @@ var rule = {
191
200
  node: node,
192
201
  messageId: 'noRawSpacingValues',
193
202
  data: {
194
- payload: "".concat(node.key.name, ":").concat(pixelValue)
203
+ payload: "".concat(propertyName, ":").concat(pixelValue)
195
204
  },
196
205
  fix: index === 0 ? function (fixer) {
197
206
  var allResolvableValues = values.every(function (value) {
@@ -205,7 +214,7 @@ var rule = {
205
214
  return fixer.replaceText(node.value, "`".concat(values.map(function (value) {
206
215
  var pixelValue = emToPixels(value, fontSize);
207
216
  var pixelValueString = "".concat(pixelValue, "px");
208
- return "${".concat(pixelValueToSpacingTokenNode(pixelValueString), "}");
217
+ return "${".concat(getTokenNodeForValue(propertyName, pixelValueString), "}");
209
218
  }).join(' '), "`"));
210
219
  } : undefined
211
220
  });
@@ -254,9 +263,9 @@ var rule = {
254
263
  rawProperty = _style$split4[0],
255
264
  value = _style$split4[1];
256
265
 
257
- var property = convertHyphenatedNameToCamelCase(rawProperty);
266
+ var propertyName = convertHyphenatedNameToCamelCase(rawProperty);
258
267
 
259
- if (!isSpacingProperty(property)) {
268
+ if (!isSpacingProperty(propertyName)) {
260
269
  return;
261
270
  } // value is either NaN or it can't be resolved eg, em, 100% etc...
262
271
 
@@ -273,16 +282,17 @@ var rule = {
273
282
 
274
283
  var values = getValueFromShorthand(value);
275
284
  values.forEach(function (val, index) {
276
- if (!val && val !== 0 || !/padding|margin|gap/.test(rawProperty)) {
285
+ if (!val && val !== 0 || !isSpacingProperty(propertyName)) {
277
286
  return;
278
287
  }
279
288
 
280
- var pixelValue = emToPixels(val, fontSize);
289
+ var isFontFamily = /fontFamily/.test(propertyName);
290
+ var pixelValue = isFontFamily ? val : emToPixels(val, fontSize);
281
291
  context.report({
282
292
  node: node,
283
293
  messageId: 'noRawSpacingValues',
284
294
  data: {
285
- payload: "".concat(property, ":").concat(pixelValue)
295
+ payload: "".concat(propertyName, ":").concat(pixelValue)
286
296
  },
287
297
  fix: index === 0 ? function (fixer) {
288
298
  var allResolvableValues = values.every(function (value) {
@@ -294,17 +304,19 @@ var rule = {
294
304
  }
295
305
 
296
306
  var replacementValue = values.map(function (value) {
297
- var pixelValue = emToPixels(value, fontSize);
307
+ var propertyValue = typeof value === 'string' ? value.trim() : value;
308
+ var pixelValue = isFontFamily ? propertyValue : emToPixels(propertyValue, fontSize);
298
309
  var pixelValueString = "".concat(pixelValue, "px");
299
- var remValueString = pixelsToRems(pixelValueString);
300
- var tokenName = spacingValueToToken[remValueString];
310
+ var lookupValue = /fontWeight|fontFamily/.test(propertyName) ? pixelValue : pixelValueString;
311
+ var tokenName = isTypographyProperty(propertyName) ? typographyValueToToken[propertyName][lookupValue] : spacingValueToToken[lookupValue];
301
312
 
302
313
  if (!tokenName) {
303
314
  return pixelValueString;
304
- } // ${token('...', '...')}
315
+ }
305
316
 
317
+ var replacementTokenValue = getTokenNodeForValue(propertyName, lookupValue); // ${token('...', '...')}
306
318
 
307
- var replacementSubValue = '${' + pixelValueToSpacingTokenNode(pixelValueString).toString() + '}';
319
+ var replacementSubValue = '${' + replacementTokenValue.toString() + '}';
308
320
  return replacementSubValue;
309
321
  }).join(' '); // get original source
310
322
 
@@ -1,7 +1,8 @@
1
1
  import { isNodeOfType } from 'eslint-codemod-utils';
2
- var properties = ['padding', 'paddingBlock', 'paddingInline', 'paddingLeft', 'paddingTop', 'paddingRight', 'paddingBottom', 'marginLeft', 'marginTop', 'marginRight', 'marginBottom', 'margin', 'gap', 'fontSize', 'lineHeight', // 'width', re-enable later
2
+ var typographyProperties = ['fontSize', 'fontWeight', 'fontFamily', 'lineHeight'];
3
+ var properties = ['padding', 'paddingBlock', 'paddingInline', 'paddingLeft', 'paddingTop', 'paddingRight', 'paddingBottom', 'marginLeft', 'marginTop', 'marginRight', 'marginBottom', 'margin', 'gap'].concat(typographyProperties, [// 'width', re-enable later
3
4
  // 'height', re-enable later
4
- 'rowGap', 'gridRowGap', 'columnGap', 'gridColumnGap'];
5
+ 'rowGap', 'gridRowGap', 'columnGap', 'gridColumnGap']);
5
6
  export function findIdentifierInParentScope(_ref) {
6
7
  var scope = _ref.scope,
7
8
  identifierName = _ref.identifierName;
@@ -21,11 +22,21 @@ export function findIdentifierInParentScope(_ref) {
21
22
 
22
23
  return null;
23
24
  }
24
- export var isSpacingProperty = function isSpacingProperty(prop) {
25
- return properties.includes(prop);
25
+ export var isSpacingProperty = function isSpacingProperty(propertyName) {
26
+ return properties.includes(propertyName);
27
+ };
28
+ export var isTypographyProperty = function isTypographyProperty(propertyName) {
29
+ return typographyProperties.includes(propertyName);
26
30
  };
27
31
  export var getValueFromShorthand = function getValueFromShorthand(str) {
28
- // If we want to filter out NaN just add .filter(Boolean)
32
+ var valueString = String(str);
33
+ var fontFamily = /(sans-serif$)|(monospace$)/;
34
+
35
+ if (fontFamily.test(valueString)) {
36
+ return [valueString];
37
+ } // If we want to filter out NaN just add .filter(Boolean)
38
+
39
+
29
40
  return String(str).trim().split(' ').filter(function (val) {
30
41
  return val !== '';
31
42
  }).map(removePixelSuffix);
@@ -43,6 +54,14 @@ var isFontSizeSmall = function isFontSizeSmall(node) {
43
54
  return isNodeOfType(node, 'CallExpression') && isNodeOfType(node.callee, 'Identifier') && node.callee.name === 'fontSizeSmall';
44
55
  };
45
56
 
57
+ var isFontFamily = function isFontFamily(node) {
58
+ return isNodeOfType(node, 'CallExpression') && isNodeOfType(node.callee, 'Identifier') && (node.callee.name === 'fontFamily' || node.callee.name === 'getFontFamily');
59
+ };
60
+
61
+ var isCodeFontFamily = function isCodeFontFamily(node) {
62
+ return isNodeOfType(node, 'CallExpression') && isNodeOfType(node.callee, 'Identifier') && (node.callee.name === 'codeFontFamily' || node.callee.name === 'getCodeFontFamily');
63
+ };
64
+
46
65
  var isToken = function isToken(node) {
47
66
  return isNodeOfType(node, 'CallExpression') && isNodeOfType(node.callee, 'Identifier') && node.callee.name === 'token';
48
67
  };
@@ -64,6 +83,14 @@ var getValueFromCallExpression = function getValueFromCallExpression(node, conte
64
83
  return 11;
65
84
  }
66
85
 
86
+ if (isFontFamily(node)) {
87
+ return "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif";
88
+ }
89
+
90
+ if (isCodeFontFamily(node)) {
91
+ return "'SFMono-Medium', 'SF Mono', 'Segoe UI Mono', 'Roboto Mono', 'Ubuntu Mono', Menlo, Consolas, Courier, monospace";
92
+ }
93
+
67
94
  if (isToken(node)) {
68
95
  var args = node.arguments;
69
96
  var call = "${token(".concat(args.map(function (argNode) {
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@atlaskit/eslint-plugin-design-system",
3
- "version": "4.13.5",
3
+ "version": "4.13.7",
4
4
  "sideEffects": false
5
5
  }
@@ -4,7 +4,8 @@ export declare function findIdentifierInParentScope({ scope, identifierName, }:
4
4
  scope: Scope.Scope;
5
5
  identifierName: string;
6
6
  }): Scope.Variable | null;
7
- export declare const isSpacingProperty: (prop: string) => boolean;
7
+ export declare const isSpacingProperty: (propertyName: string) => boolean;
8
+ export declare const isTypographyProperty: (propertyName: string) => boolean;
8
9
  export declare const getValueFromShorthand: (str: unknown) => any[];
9
10
  export declare const getValue: (node: EslintNode, context: Rule.RuleContext) => string | number | any[] | null | undefined;
10
11
  export declare const emToPixels: <T extends unknown>(value: T, fontSize: number | null | undefined) => number | T | null;
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": "4.13.5",
4
+ "version": "4.13.7",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "publishConfig": {
7
7
  "registry": "https://registry.npmjs.org/"