@atlaskit/codemod-cli 0.13.3 → 0.14.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.
Files changed (61) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +117 -3
  3. package/dist/cjs/cli.js +70 -75
  4. package/dist/cjs/filepath.js +65 -29
  5. package/dist/cjs/index.js +3 -3
  6. package/dist/cjs/main.js +329 -167
  7. package/dist/cjs/presets/css-to-design-tokens/css-to-design-tokens.js +86 -34
  8. package/dist/cjs/presets/css-to-design-tokens/utils/legacy-colors.js +3 -3
  9. package/dist/cjs/presets/css-to-design-tokens/utils/meta.js +19 -6
  10. package/dist/cjs/presets/index.js +3 -1
  11. package/dist/cjs/presets/styled-to-emotion/styled-to-emotion.js +19 -12
  12. package/dist/cjs/presets/theme-remove-deprecated-mixins/theme-remove-deprecated-mixins.js +51 -36
  13. package/dist/cjs/presets/theme-remove-deprecated-mixins/utils/replacements.js +25 -25
  14. package/dist/cjs/presets/theme-to-design-tokens/theme-to-design-tokens.js +378 -114
  15. package/dist/cjs/presets/theme-to-design-tokens/utils/ast-meta.js +33 -18
  16. package/dist/cjs/presets/theme-to-design-tokens/utils/ast.js +1 -1
  17. package/dist/cjs/presets/theme-to-design-tokens/utils/color.js +25 -17
  18. package/dist/cjs/presets/theme-to-design-tokens/utils/css-utils.js +38 -0
  19. package/dist/cjs/presets/theme-to-design-tokens/utils/fuzzy-search.js +10 -6
  20. package/dist/cjs/presets/theme-to-design-tokens/utils/legacy-colors.js +3 -3
  21. package/dist/cjs/presets/theme-to-design-tokens/utils/named-colors.js +1 -1
  22. package/dist/cjs/presets/theme-to-design-tokens/utils/string-utils.js +26 -0
  23. package/dist/cjs/presets/theme-to-design-tokens/utils/tokens.js +16 -2
  24. package/dist/cjs/sinceRef.js +69 -35
  25. package/dist/cjs/transforms.js +44 -26
  26. package/dist/cjs/types.js +27 -3
  27. package/dist/cjs/utils.js +6 -6
  28. package/dist/es2019/cli.js +4 -0
  29. package/dist/es2019/main.js +2 -0
  30. package/dist/es2019/presets/css-to-design-tokens/css-to-design-tokens.js +19 -8
  31. package/dist/es2019/presets/theme-to-design-tokens/theme-to-design-tokens.js +191 -32
  32. package/dist/es2019/presets/theme-to-design-tokens/utils/ast.js +1 -1
  33. package/dist/es2019/presets/theme-to-design-tokens/utils/color.js +12 -10
  34. package/dist/es2019/presets/theme-to-design-tokens/utils/css-utils.js +31 -0
  35. package/dist/es2019/presets/theme-to-design-tokens/utils/string-utils.js +13 -0
  36. package/dist/es2019/sinceRef.js +1 -0
  37. package/dist/esm/cli.js +4 -0
  38. package/dist/esm/main.js +3 -1
  39. package/dist/esm/presets/css-to-design-tokens/css-to-design-tokens.js +23 -12
  40. package/dist/esm/presets/theme-to-design-tokens/theme-to-design-tokens.js +346 -100
  41. package/dist/esm/presets/theme-to-design-tokens/utils/ast.js +1 -1
  42. package/dist/esm/presets/theme-to-design-tokens/utils/color.js +12 -10
  43. package/dist/esm/presets/theme-to-design-tokens/utils/css-utils.js +31 -0
  44. package/dist/esm/presets/theme-to-design-tokens/utils/string-utils.js +17 -0
  45. package/dist/esm/sinceRef.js +1 -0
  46. package/dist/types/presets/css-to-design-tokens/css-to-design-tokens.d.ts +2 -1
  47. package/dist/types/presets/theme-to-design-tokens/theme-to-design-tokens.d.ts +1 -1
  48. package/dist/types/presets/theme-to-design-tokens/utils/ast.d.ts +1 -1
  49. package/dist/types/presets/theme-to-design-tokens/utils/color.d.ts +2 -1
  50. package/dist/types/presets/theme-to-design-tokens/utils/css-utils.d.ts +2 -0
  51. package/dist/types/presets/theme-to-design-tokens/utils/string-utils.d.ts +3 -0
  52. package/dist/types-ts4.5/presets/css-to-design-tokens/css-to-design-tokens.d.ts +2 -1
  53. package/dist/types-ts4.5/presets/theme-to-design-tokens/theme-to-design-tokens.d.ts +1 -1
  54. package/dist/types-ts4.5/presets/theme-to-design-tokens/utils/ast.d.ts +1 -1
  55. package/dist/types-ts4.5/presets/theme-to-design-tokens/utils/color.d.ts +2 -1
  56. package/dist/types-ts4.5/presets/theme-to-design-tokens/utils/css-utils.d.ts +2 -0
  57. package/dist/types-ts4.5/presets/theme-to-design-tokens/utils/string-utils.d.ts +6 -0
  58. package/package.json +2 -2
  59. package/dist/cjs/version.json +0 -4
  60. package/dist/es2019/version.json +0 -4
  61. package/dist/esm/version.json +0 -4
package/dist/cjs/types.js CHANGED
@@ -1,14 +1,38 @@
1
1
  "use strict";
2
2
 
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
3
4
  Object.defineProperty(exports, "__esModule", {
4
5
  value: true
5
6
  });
6
7
  exports.ValidationError = exports.NoTransformsExistError = void 0;
8
+ var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
9
+ var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
10
+ var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
11
+ var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));
12
+ var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));
13
+ var _wrapNativeSuper2 = _interopRequireDefault(require("@babel/runtime/helpers/wrapNativeSuper"));
14
+ function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = (0, _getPrototypeOf2.default)(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = (0, _getPrototypeOf2.default)(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return (0, _possibleConstructorReturn2.default)(this, result); }; }
15
+ function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
7
16
  /** Converts required args to optional if they have a default
8
17
  * Example: export type UserFlags = Default<Flags, keyof typeof defaultFlags>;
9
18
  */
10
-
11
- class ValidationError extends Error {}
19
+ var ValidationError = /*#__PURE__*/function (_Error) {
20
+ (0, _inherits2.default)(ValidationError, _Error);
21
+ var _super = _createSuper(ValidationError);
22
+ function ValidationError() {
23
+ (0, _classCallCheck2.default)(this, ValidationError);
24
+ return _super.apply(this, arguments);
25
+ }
26
+ return (0, _createClass2.default)(ValidationError);
27
+ }( /*#__PURE__*/(0, _wrapNativeSuper2.default)(Error));
12
28
  exports.ValidationError = ValidationError;
13
- class NoTransformsExistError extends Error {}
29
+ var NoTransformsExistError = /*#__PURE__*/function (_Error2) {
30
+ (0, _inherits2.default)(NoTransformsExistError, _Error2);
31
+ var _super2 = _createSuper(NoTransformsExistError);
32
+ function NoTransformsExistError() {
33
+ (0, _classCallCheck2.default)(this, NoTransformsExistError);
34
+ return _super2.apply(this, arguments);
35
+ }
36
+ return (0, _createClass2.default)(NoTransformsExistError);
37
+ }( /*#__PURE__*/(0, _wrapNativeSuper2.default)(Error));
14
38
  exports.NoTransformsExistError = NoTransformsExistError;
package/dist/cjs/utils.js CHANGED
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.fixLineEnding = void 0;
7
7
  /* Utility functions to be used in codemod-cli. */
8
8
 
9
- const returnLineEnding = source => {
9
+ var returnLineEnding = function returnLineEnding(source) {
10
10
  var cr = source.split('\r').length;
11
11
  var lf = source.split('\n').length;
12
12
  var crlf = source.split('\r\n').length;
@@ -22,7 +22,7 @@ const returnLineEnding = source => {
22
22
  return 'LF';
23
23
  }
24
24
  };
25
- const getLineEndingRegex = type => {
25
+ var getLineEndingRegex = function getLineEndingRegex(type) {
26
26
  if (['CR', 'LF', 'CRLF'].indexOf(type) === -1) {
27
27
  throw new Error("Line ending '" + type + "' is not supported, use CR, LF or CRLF");
28
28
  }
@@ -36,13 +36,13 @@ const getLineEndingRegex = type => {
36
36
  return '\r\n';
37
37
  }
38
38
  };
39
- const fixLineEnding = (source, lineEnding) => {
40
- const current = returnLineEnding(source);
39
+ var fixLineEnding = function fixLineEnding(source, lineEnding) {
40
+ var current = returnLineEnding(source);
41
41
  if (current === lineEnding) {
42
42
  return source;
43
43
  }
44
- const regexCurrentLineEnding = getLineEndingRegex(current);
45
- const regexLineEnding = getLineEndingRegex(lineEnding);
44
+ var regexCurrentLineEnding = getLineEndingRegex(current);
45
+ var regexLineEnding = getLineEndingRegex(lineEnding);
46
46
  if (current && regexLineEnding && regexCurrentLineEnding) {
47
47
  return source.replace(new RegExp(regexCurrentLineEnding, 'g'), regexLineEnding);
48
48
  }
@@ -67,13 +67,17 @@ Examples
67
67
  });
68
68
  main(cli.input, cli.flags).catch(e => {
69
69
  if (e instanceof ValidationError) {
70
+ // eslint-disable-next-line no-console
70
71
  console.error(cli.help);
72
+ // eslint-disable-next-line no-console
71
73
  console.error(chalk.red(e.message));
72
74
  process.exit(1);
73
75
  } else if (e instanceof NoTransformsExistError) {
76
+ // eslint-disable-next-line no-console
74
77
  console.warn(chalk.yellow(e.message));
75
78
  process.exit(0);
76
79
  } else {
80
+ // eslint-disable-next-line no-console
77
81
  console.error(chalk.red(e));
78
82
  process.exit(3);
79
83
  }
@@ -54,6 +54,7 @@ const resolveTransform = async (flags, transforms) => {
54
54
  name
55
55
  }) => name === flags.preset);
56
56
  if (!transform) {
57
+ // eslint-disable-next-line no-console
57
58
  console.warn(`No preset found for: ${chalk.bgRed(flags.preset)}`);
58
59
  } else {
59
60
  return transform; // Return only if transform found.
@@ -64,6 +65,7 @@ const resolveTransform = async (flags, transforms) => {
64
65
  return parseTransformPath(flags.transform);
65
66
  }
66
67
  if (flags.transform && !hasTransform(flags.transform)) {
68
+ // eslint-disable-next-line no-console
67
69
  console.warn(`No available transform found for: ${chalk.bgRed(flags.transform)}`);
68
70
  }
69
71
  return await getTransformPrompt(transforms);
@@ -6,6 +6,10 @@ import designTokens from '@atlaskit/tokens/token-names';
6
6
  import Search from '../theme-to-design-tokens/utils/fuzzy-search';
7
7
  import { knownVariables, knownColors, knownRawColors } from './utils/legacy-colors';
8
8
  import { cleanMeta } from './utils/meta';
9
+ const options = {
10
+ syntax: lessSyntax,
11
+ from: undefined
12
+ };
9
13
  const tokens = rawTokens.filter(t => t.attributes.state === 'active').map(t => t.name.replace(/\.\[default\]/g, '')).filter(t => !t.includes('UNSAFE') && !t.includes('interaction'));
10
14
  const search = Search(tokens, false);
11
15
  function isRule(node) {
@@ -27,8 +31,8 @@ function stripVar(prop) {
27
31
  function stripLessVar(prop) {
28
32
  return prop.substring(1);
29
33
  }
30
- function isColorProperty(prop) {
31
- return prop === 'color' || prop === 'background' || prop === 'background-color' || prop === 'box-shadow' || prop === 'border' || prop === 'border-left' || prop === 'border-right' || prop === 'border-top' || prop === 'border-bottom' || prop === 'border-color';
34
+ export function isColorProperty(prop) {
35
+ return prop === 'color' || prop === 'background' || prop === 'background-color' || prop === 'box-shadow' || prop === 'border' || prop === 'border-left' || prop === 'border-right' || prop === 'border-top' || prop === 'border-bottom' || prop === 'border-color' || prop === 'outline';
32
36
  }
33
37
  function getDeclarationMeta(decl) {
34
38
  if (decl.prop === 'color') {
@@ -77,6 +81,9 @@ const plugin = () => {
77
81
  if (!isColorProperty(decl.prop)) {
78
82
  return;
79
83
  }
84
+ if (decl.value === 'none') {
85
+ return;
86
+ }
80
87
  const searchTerms = [getDeclarationMeta(decl), ...getParentSelectors(decl).split(/\-|\.|\,|\ |\:|\&/).filter(el => !!el)];
81
88
  let match;
82
89
  const cssVarRe = /var\([^\)]+\)/g;
@@ -95,10 +102,10 @@ const plugin = () => {
95
102
  }
96
103
 
97
104
  // Less variables
98
- const lassVarMatch = decl.value.match(lessVarRe);
99
- if (lassVarMatch) {
105
+ const lessVarMatch = decl.value.match(lessVarRe);
106
+ if (lessVarMatch) {
100
107
  var _getMetaFromCssVar2;
101
- match = lassVarMatch[0];
108
+ match = lessVarMatch[0];
102
109
  searchTerms.push(...((_getMetaFromCssVar2 = getMetaFromCssVar(`--${stripLessVar(match)}`)) !== null && _getMetaFromCssVar2 !== void 0 ? _getMetaFromCssVar2 : []));
103
110
  }
104
111
 
@@ -117,6 +124,7 @@ const plugin = () => {
117
124
  searchTerms.push(...((_knownColors$decl$val = knownColors[decl.value.toLowerCase()]) !== null && _knownColors$decl$val !== void 0 ? _knownColors$decl$val : []));
118
125
  }
119
126
  if (!match) {
127
+ // eslint-disable-next-line no-console
120
128
  console.warn(`Unable to find match for declaration: ${decl.prop}: ${decl.value}`);
121
129
  return;
122
130
  }
@@ -136,7 +144,10 @@ const plugin = () => {
136
144
  };
137
145
  };
138
146
  export default async function transformer(file) {
139
- return await postcss([plugin()]).process(file.source, {
140
- syntax: lessSyntax
141
- }).css;
147
+ const processor = postcss([plugin()]);
148
+ const src = typeof file === 'string' ? file : file.source;
149
+ const {
150
+ css
151
+ } = await processor.process(src, options);
152
+ return css;
142
153
  }
@@ -3,15 +3,13 @@
3
3
  import { isDecendantOfType, hasImportDeclaration } from '@codeshift/utils';
4
4
  import { isDecendantOfToken, isParentOfToken } from './utils/ast';
5
5
  import { cleanMeta, getMetaFromAncestors } from './utils/ast-meta';
6
- import { includesHardCodedColor, isHardCodedColor, isLegacyColor, isLegacyNamedColor } from './utils/color';
6
+ import { includesHardCodedColor, isHardCodedColor, isLegacyColor, isLegacyNamedColor, isBoldColor } from './utils/color';
7
7
  import Search from './utils/fuzzy-search';
8
8
  import { legacyColorMetaMap } from './utils/legacy-colors';
9
9
  import { tokens } from './utils/tokens';
10
- const kebabize = str => str.replace(/[A-Z]+(?![a-z])|[A-Z]/g, ($, ofs) => (ofs ? '-' : '') + $.toLowerCase());
11
- function isBoldColor(color) {
12
- const number = parseInt(color.replace(/^./, ''), 10);
13
- return number > 300;
14
- }
10
+ import { kebabize, findFirstNonspaceIndexAfter, splitAtIndex } from './utils/string-utils';
11
+ import { containsReplaceableCSSDeclarations, findEndIndexOfCSSExpression } from './utils/css-utils';
12
+ import CSSTransformer from '../css-to-design-tokens/css-to-design-tokens';
15
13
  function insertTokenImport(j, source) {
16
14
  if (hasImportDeclaration(j, source, '@atlaskit/tokens')) {
17
15
  return;
@@ -23,6 +21,23 @@ function buildToken(j, tokenId, node) {
23
21
  const callExpr = j.callExpression(j.identifier('token'), [j.stringLiteral(tokenId), node].filter(Boolean));
24
22
  return callExpr;
25
23
  }
24
+
25
+ // Wrap over the j.templateElement builder to provide a more convenient API.
26
+ function buildTemplateElement(j, text, options = {
27
+ tail: false,
28
+ fromNode: null
29
+ }) {
30
+ let tail;
31
+ if (options.fromNode) {
32
+ tail = options.fromNode.tail;
33
+ } else {
34
+ tail = !!options.tail;
35
+ }
36
+ return j.templateElement({
37
+ raw: text,
38
+ cooked: null
39
+ }, tail);
40
+ }
26
41
  function getColorFromIdentifier(expression) {
27
42
  let value = '';
28
43
  if (expression.type === 'Identifier') {
@@ -126,10 +141,20 @@ function getTokenFromNode(j, path, value, propertyName) {
126
141
  }
127
142
  function parseCSSPropertyName(cssString) {
128
143
  const lastColonIndex = cssString.lastIndexOf(':');
129
- const propertyNameEndIndex = Math.max(cssString.lastIndexOf(';', lastColonIndex), cssString.lastIndexOf(' ', lastColonIndex));
130
- return cssString.slice(propertyNameEndIndex + 1, lastColonIndex).trim();
144
+ if (lastColonIndex === -1) {
145
+ return {
146
+ colonIndex: null,
147
+ cssPropertyName: null
148
+ };
149
+ }
150
+ const propertyNameEndIndex = Math.max(cssString.lastIndexOf(';', lastColonIndex), cssString.lastIndexOf(' ', lastColonIndex), -1);
151
+ const startIndex = propertyNameEndIndex + 1;
152
+ return {
153
+ cssPropertyName: cssString.slice(startIndex, lastColonIndex).trim(),
154
+ colonIndex: lastColonIndex
155
+ };
131
156
  }
132
- export default function transformer(file, api, debug = false) {
157
+ export default async function transformer(file, api, debug = false) {
133
158
  const j = api.jscodeshift;
134
159
  const source = j(file.source);
135
160
  let transformed = false;
@@ -169,29 +194,6 @@ export default function transformer(file, api, debug = false) {
169
194
  transformed = true;
170
195
  });
171
196
 
172
- // Template literals
173
- source.find(j.TemplateLiteral).forEach(path => {
174
- function replaceTemplateLiteralExpressions(j, expression, index) {
175
- if (isDecendantOfToken(j, expression)) {
176
- return;
177
- }
178
- if (index >= path.value.quasis.length) {
179
- return;
180
- }
181
- const quasi = path.value.quasis[index];
182
- const value = getColorFromIdentifier(expression.value);
183
- if (!value || !includesHardCodedColor(value) && !isHardCodedColor(value) && !isLegacyColor(value) && !isLegacyNamedColor(value)) {
184
- return;
185
- }
186
- const tokenId = getTokenFromNode(j, expression, value, parseCSSPropertyName(quasi.value.cooked || ''));
187
- insertTokenImport(j, source);
188
- expression.replace(buildToken(j, tokenId, expression.value));
189
- }
190
- j(path).find(j.Identifier).filter(expression => !isDecendantOfType(j, expression, j.MemberExpression)).forEach((expression, i) => replaceTemplateLiteralExpressions(j, expression, i));
191
- j(path).find(j.MemberExpression).forEach((expression, i) => replaceTemplateLiteralExpressions(j, expression, i));
192
- transformed = true;
193
- });
194
-
195
197
  // JSX props
196
198
  source.find(j.JSXAttribute).forEach(path => {
197
199
  var _path$value, _path$value$value;
@@ -214,5 +216,162 @@ export default function transformer(file, api, debug = false) {
214
216
  });
215
217
  transformed = true;
216
218
  });
219
+
220
+ // Strings
221
+ source.find(j.StringLiteral).forEach(path => {
222
+ j(path).filter(expression => !isDecendantOfType(j, expression, j.ObjectExpression)).forEach(path => {
223
+ const value = path.value.value;
224
+ if (replaceStringLiteralIfItConsistsOnlyOfColor(j, path, value)) {
225
+ transformed = true;
226
+ }
227
+ });
228
+ });
229
+ const templateLiteralPaths = source.find(j.TemplateLiteral).paths();
230
+ for (const path of templateLiteralPaths) {
231
+ // Background: a 'type: TemplateLiteral' Node has quasis and expressions
232
+ // (see ast-types/src/gen/namedTypes.ts), and invariant holds that
233
+ // quasis.length === expression.length + 1.
234
+ //
235
+ // eg `${foo}bar` has quasis [Node(''), Node('bar')] and expressions
236
+ // [Node('foo')]. Each quasi has type: 'TemplateElement'; expressions are
237
+ // probably safe to treat as subtypes of Expression, though ast-types
238
+ // codebase has a more involved definition.
239
+ if (path.value.expressions.length === 0) {
240
+ // A single-quasi (equivalently, no-expression) template literal is
241
+ // basically just a string literal, possibly multi-line. We handle the
242
+ // simple `#ababab` case here, and the multi-line case after.
243
+ const text = path.value.quasis[0].value.raw;
244
+ if (replaceStringLiteralIfItConsistsOnlyOfColor(j, path, text)) {
245
+ transformed = true;
246
+ }
247
+ } else {
248
+ j(path).find(j.Expression).filter(expressionPath => {
249
+ // jscodeshift walks over the whole tree; we are interested only in
250
+ // the direct children: i.e. top-level expressions appearing in ${}.
251
+ return expressionPath.parent === path;
252
+ }).forEach((expressionPath, expressionIndex) => {
253
+ if (replaceTemplateLiteralExpression(j, path, expressionPath, expressionIndex)) {
254
+ transformed = true;
255
+ }
256
+ });
257
+ }
258
+
259
+ // No matter if we have one big quasi or many small chunks between
260
+ // expressions (which potentially have been transformed), try to pass them
261
+ // through the CSS transformer; it's robust enough to understand malformed
262
+ // CSS that would result if we split e.g. this template:
263
+ //
264
+ // `${gridSize}px; color: red;`, giving `px; color: red;` as input; or
265
+ // `@media ${mobile} { color: red }`, giving `{ color: red }`.
266
+ const quasiPaths = j(path).find(j.TemplateElement).filter(quasiPath => {
267
+ return quasiPath.parent === path;
268
+ }).paths();
269
+ for (const quasiPath of quasiPaths) {
270
+ const text = quasiPath.value.value.raw;
271
+ if (includesHardCodedColor(text) && containsReplaceableCSSDeclarations(text)) {
272
+ const newCSS = await CSSTransformer(text);
273
+ j(quasiPath).replaceWith(buildTemplateElement(j, newCSS));
274
+ transformed = true;
275
+ }
276
+ }
277
+ }
278
+ function replaceStringLiteralIfItConsistsOnlyOfColor(j, path, value) {
279
+ if (isDecendantOfToken(j, path)) {
280
+ return false;
281
+ }
282
+ if (isHardCodedColor(value) && !isLegacyColor(value) && !isLegacyNamedColor(value)) {
283
+ const parent = path.parent.value;
284
+ let key = '';
285
+ if (parent.type === 'VariableDeclarator') {
286
+ key = parent.id.name;
287
+ }
288
+ const tokenId = getTokenFromNode(j, path, value, key);
289
+ insertTokenImport(j, source);
290
+ j(path).replaceWith(buildToken(j, tokenId, path.value));
291
+ return true;
292
+ }
293
+ return false;
294
+ }
295
+ function replaceTemplateLiteralExpression(j, mainPath, expressionPath, expressionIndex) {
296
+ const expression = expressionPath.value;
297
+ if (!(expression.type === 'MemberExpression' || expression.type === 'Identifier')) {
298
+ return false;
299
+ }
300
+ if (isDecendantOfToken(j, expressionPath)) {
301
+ return false;
302
+ }
303
+ const value = getColorFromIdentifier(expression);
304
+ if (!value || !includesHardCodedColor(value) && !isHardCodedColor(value) && !isLegacyColor(value) && !isLegacyNamedColor(value)) {
305
+ return false;
306
+ }
307
+ const precedingQuasi = mainPath.value.quasis[expressionIndex];
308
+ const precedingQuasiText = precedingQuasi.value.raw;
309
+ const {
310
+ cssPropertyName,
311
+ colonIndex
312
+ } = parseCSSPropertyName(precedingQuasiText);
313
+ if (!cssPropertyName) {
314
+ return false;
315
+ }
316
+ const tokenId = getTokenFromNode(j, expressionPath, value, cssPropertyName);
317
+ insertTokenImport(j, source);
318
+ const newQuasis = [...mainPath.value.quasis];
319
+ const newExpressions = [...mainPath.value.expressions];
320
+ if (cssPropertyName !== 'box-shadow') {
321
+ const tokenExpression = buildToken(j, tokenId, expressionPath.value);
322
+ newExpressions[expressionIndex] = tokenExpression;
323
+ } else {
324
+ // box-shadow is a multi-part property where the color can appear at any
325
+ // part position (even though the standard suggests that color comes
326
+ // last, browsers' CSS parsers are more lax). If we get here, then the
327
+ // color part is replaceable. Textually, it's something like:
328
+ //
329
+ // <rules before>; box-shadow: 0 1px ${colors.N50} 2rem; <rules after>
330
+ //
331
+ // the fallback value will be multipart, i.e. the token call is
332
+ //
333
+ // token(<replacedValue>, `0 1px ${colors.N50} 2rem`)
334
+ //
335
+ // and it's wrapped in a substitution like this:
336
+ //
337
+ // <rules before>; box-shadow: ${token(<...>)}; <rules after>
338
+ //
339
+ // We stich the fallback from the last part of preceding quasi (after
340
+ // colon) and the first part of the following quasi (before ';' or '}').
341
+ //
342
+ // If multiple box-shadows are comma-separated but only one of them has a
343
+ // replaceable color and others are hard-coded, this logic would still
344
+ // work. When multiple shadows have expressions, it's unfortunately not
345
+ // possible to proceed because we cannot find where the value ends from a
346
+ // single following quasi.
347
+ const valueStartIndex = findFirstNonspaceIndexAfter(precedingQuasiText, colonIndex);
348
+ const [newPrecedingQuasiText, partialValueBeginning] = splitAtIndex(precedingQuasiText, valueStartIndex);
349
+ const followingQuasi = mainPath.value.quasis[expressionIndex + 1];
350
+ const followingQuasiText = followingQuasi.value.raw;
351
+ const valueEndIndex = findEndIndexOfCSSExpression(followingQuasiText, followingQuasi.tail);
352
+ if (!valueEndIndex) {
353
+ console.warn('cannot find end of box-shadow value, please check manually');
354
+ return false;
355
+ }
356
+ const [partialValueEnd, newFollowingQuasiText] = splitAtIndex(followingQuasiText, valueEndIndex + 1);
357
+ const internalQuasis = [buildTemplateElement(j, partialValueBeginning, {
358
+ tail: false
359
+ }), buildTemplateElement(j, partialValueEnd, {
360
+ tail: true
361
+ })];
362
+ const internalExpressions = [expressionPath.value];
363
+ const newFallback = j.templateLiteral(internalQuasis, internalExpressions);
364
+ const newExpression = buildToken(j, tokenId, newFallback);
365
+ newQuasis[expressionIndex] = buildTemplateElement(j, newPrecedingQuasiText, {
366
+ fromNode: newQuasis[expressionIndex]
367
+ });
368
+ newExpressions[expressionIndex] = newExpression;
369
+ newQuasis[expressionIndex + 1] = buildTemplateElement(j, newFollowingQuasiText, {
370
+ fromNode: newQuasis[expressionIndex]
371
+ });
372
+ }
373
+ mainPath.replace(j.templateLiteral(newQuasis, newExpressions));
374
+ return true;
375
+ }
217
376
  return transformed ? source.toSource() : file.source;
218
377
  }
@@ -1,6 +1,6 @@
1
1
  import { isDecendantOfType } from '@codeshift/utils';
2
2
  export function isDecendantOfToken(j, path) {
3
- if (path.type === 'CallExpression' && path.callee.type === 'Identifier' && path.callee.name === 'token') {
3
+ if ('type' in path && path.type === 'CallExpression' && path.callee.type === 'Identifier' && path.callee.name === 'token') {
4
4
  return true;
5
5
  }
6
6
  return j(path).closest(j.CallExpression, {
@@ -2,9 +2,10 @@ import { legacyColorMixins, legacyColors } from './legacy-colors';
2
2
  import { namedColors } from './named-colors';
3
3
  export const isLegacyColor = value => legacyColors.includes(value);
4
4
  export const isLegacyNamedColor = value => legacyColorMixins.includes(value);
5
+ const colorRegexp = /#(?:[a-f0-9]{3}|[a-f0-9]{6}|[a-f0-9]{8})\b|(?:rgb|rgba|hsl|hsla|lch|lab|color)\([^\)]*\)/;
5
6
  export const includesHardCodedColor = raw => {
6
7
  const value = raw.toLowerCase();
7
- if (/#(?:[a-f0-9]{3}|[a-f0-9]{6}|[a-f0-9]{8})\b|(?:rgb|hsl)a?\([^\)]*\)/.exec(value.toLowerCase())) {
8
+ if (colorRegexp.exec(value)) {
8
9
  return true;
9
10
  }
10
11
  for (let i = 0; i < namedColors.length; i++) {
@@ -14,17 +15,18 @@ export const includesHardCodedColor = raw => {
14
15
  }
15
16
  return false;
16
17
  };
17
- export const isHardCodedColor = value => {
18
- if (namedColors.includes(value.toLowerCase())) {
19
- return true;
20
- }
21
- if (value.startsWith('rgb(') || value.startsWith('rgba(') || value.startsWith('hsl(') || value.startsWith('hsla(') || value.startsWith('lch(') || value.startsWith('lab(') || value.startsWith('color(')) {
18
+ export const isHardCodedColor = raw => {
19
+ const value = raw.toLowerCase();
20
+ if (namedColors.includes(value)) {
22
21
  return true;
23
22
  }
24
- if (value.startsWith('#') && (
25
- // short hex, hex, or hex with alpha
26
- value.length === 4 || value.length === 7 || value.length === 9)) {
23
+ const match = value.toLowerCase().match(colorRegexp);
24
+ if (match && match[0] === value) {
27
25
  return true;
28
26
  }
29
27
  return false;
30
- };
28
+ };
29
+ export function isBoldColor(color) {
30
+ const number = parseInt(color.replace(/^./, ''), 10);
31
+ return number > 300;
32
+ }
@@ -0,0 +1,31 @@
1
+ import { isColorProperty } from '../../css-to-design-tokens/css-to-design-tokens';
2
+ export function containsReplaceableCSSDeclarations(input) {
3
+ const cssPattern = /(\S+)\s*:/g;
4
+ let match;
5
+ while ((match = cssPattern.exec(input)) !== null) {
6
+ if (isColorProperty(match[1])) {
7
+ return true;
8
+ }
9
+ }
10
+ return false;
11
+ }
12
+ export function findEndIndexOfCSSExpression(text, isAtEndOfInput) {
13
+ // CSS expression can end *on* a semicolon or *before* a brace. In either
14
+ // case we treat the remaining part of the value to cover one character
15
+ // before that symbol.
16
+ const semicolonIndex = text.indexOf(';');
17
+ const braceIndex = text.indexOf('}');
18
+ if (semicolonIndex === -1 && braceIndex === -1) {
19
+ if (isAtEndOfInput) {
20
+ return text.length;
21
+ } else {
22
+ return null;
23
+ }
24
+ } else if (semicolonIndex === -1) {
25
+ return braceIndex - 1;
26
+ } else if (braceIndex === -1) {
27
+ return semicolonIndex - 1;
28
+ } else {
29
+ return Math.min(semicolonIndex, braceIndex) - 1;
30
+ }
31
+ }
@@ -0,0 +1,13 @@
1
+ export const kebabize = str => str.replace(/[A-Z]+(?![a-z])|[A-Z]/g, ($, ofs) => (ofs ? '-' : '') + $.toLowerCase());
2
+ export function findFirstNonspaceIndexAfter(text, index) {
3
+ const rest = text.slice(index + 1);
4
+ const indexInRest = rest.search(/\S/);
5
+ if (indexInRest === -1) {
6
+ return text.length;
7
+ } else {
8
+ return index + 1 + indexInRest;
9
+ }
10
+ }
11
+ export function splitAtIndex(text, index) {
12
+ return [text.slice(0, index), text.slice(index)];
13
+ }
@@ -48,5 +48,6 @@ export const getPackagesSinceRef = async ref => {
48
48
  return upgradedPackages;
49
49
  };
50
50
  if (require.main === module) {
51
+ // eslint-disable-next-line no-console
51
52
  getPackagesSinceRef(process.argv[2]).then(res => console.log(res));
52
53
  }
package/dist/esm/cli.js CHANGED
@@ -51,13 +51,17 @@ function _run() {
51
51
  });
52
52
  main(cli.input, cli.flags).catch(function (e) {
53
53
  if (e instanceof ValidationError) {
54
+ // eslint-disable-next-line no-console
54
55
  console.error(cli.help);
56
+ // eslint-disable-next-line no-console
55
57
  console.error(chalk.red(e.message));
56
58
  process.exit(1);
57
59
  } else if (e instanceof NoTransformsExistError) {
60
+ // eslint-disable-next-line no-console
58
61
  console.warn(chalk.yellow(e.message));
59
62
  process.exit(0);
60
63
  } else {
64
+ // eslint-disable-next-line no-console
61
65
  console.error(chalk.red(e));
62
66
  process.exit(3);
63
67
  }
package/dist/esm/main.js CHANGED
@@ -95,6 +95,7 @@ var resolveTransform = /*#__PURE__*/function () {
95
95
  _context2.next = 6;
96
96
  break;
97
97
  }
98
+ // eslint-disable-next-line no-console
98
99
  console.warn("No preset found for: ".concat(chalk.bgRed(flags.preset)));
99
100
  _context2.next = 7;
100
101
  break;
@@ -108,6 +109,7 @@ var resolveTransform = /*#__PURE__*/function () {
108
109
  return _context2.abrupt("return", parseTransformPath(flags.transform));
109
110
  case 9:
110
111
  if (flags.transform && !hasTransform(flags.transform)) {
112
+ // eslint-disable-next-line no-console
111
113
  console.warn("No available transform found for: ".concat(chalk.bgRed(flags.transform)));
112
114
  }
113
115
  _context2.next = 12;
@@ -289,7 +291,7 @@ function _main() {
289
291
  case 4:
290
292
  _yield$parseArgs = _context5.sent;
291
293
  packages = _yield$parseArgs.packages;
292
- _process$env$_PACKAGE = "0.13.3", _PACKAGE_VERSION_ = _process$env$_PACKAGE === void 0 ? '0.0.0-dev' : _process$env$_PACKAGE;
294
+ _process$env$_PACKAGE = "0.14.0", _PACKAGE_VERSION_ = _process$env$_PACKAGE === void 0 ? '0.0.0-dev' : _process$env$_PACKAGE;
293
295
  logger.log(chalk.bgBlue(chalk.black("\uD83D\uDCDA Atlassian-Frontend codemod library @ ".concat(_PACKAGE_VERSION_, " \uD83D\uDCDA"))));
294
296
  if (packages && packages.length > 0) {
295
297
  logger.log(chalk.gray("Searching for codemods for newer versions of the following packages: ".concat(packages.map(function (pkg) {