@atlaskit/codemod-cli 0.27.2 → 0.27.4

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 (65) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/cjs/main.js +85 -33
  3. package/dist/cjs/presets/index.js +2 -1
  4. package/dist/cjs/presets/remove-token-fallbacks/remove-token-fallbacks.js +225 -0
  5. package/dist/cjs/presets/remove-token-fallbacks/types.js +5 -0
  6. package/dist/cjs/presets/remove-token-fallbacks/utils/all-tokens.js +44 -0
  7. package/dist/cjs/presets/remove-token-fallbacks/utils/color-utils.js +93 -0
  8. package/dist/cjs/presets/remove-token-fallbacks/utils/get-team-info.js +51 -0
  9. package/dist/cjs/presets/remove-token-fallbacks/utils/normalize-values.js +113 -0
  10. package/dist/cjs/presets/remove-token-fallbacks/utils/remove-unused-imports.js +61 -0
  11. package/dist/cjs/presets/remove-token-fallbacks/utils/remove-unused-variables.js +37 -0
  12. package/dist/cjs/presets/remove-token-fallbacks/utils/reporter.js +310 -0
  13. package/dist/cjs/presets/remove-token-fallbacks/utils/token-processor.js +632 -0
  14. package/dist/cjs/presets/remove-token-fallbacks/utils/update-comments.js +58 -0
  15. package/dist/es2019/main.js +24 -0
  16. package/dist/es2019/presets/index.js +2 -1
  17. package/dist/es2019/presets/remove-token-fallbacks/remove-token-fallbacks.js +130 -0
  18. package/dist/es2019/presets/remove-token-fallbacks/types.js +1 -0
  19. package/dist/es2019/presets/remove-token-fallbacks/utils/all-tokens.js +30 -0
  20. package/dist/es2019/presets/remove-token-fallbacks/utils/color-utils.js +84 -0
  21. package/dist/es2019/presets/remove-token-fallbacks/utils/get-team-info.js +22 -0
  22. package/dist/es2019/presets/remove-token-fallbacks/utils/normalize-values.js +104 -0
  23. package/dist/es2019/presets/remove-token-fallbacks/utils/remove-unused-imports.js +51 -0
  24. package/dist/es2019/presets/remove-token-fallbacks/utils/remove-unused-variables.js +31 -0
  25. package/dist/es2019/presets/remove-token-fallbacks/utils/reporter.js +118 -0
  26. package/dist/es2019/presets/remove-token-fallbacks/utils/token-processor.js +377 -0
  27. package/dist/es2019/presets/remove-token-fallbacks/utils/update-comments.js +46 -0
  28. package/dist/esm/main.js +85 -33
  29. package/dist/esm/presets/index.js +2 -1
  30. package/dist/esm/presets/remove-token-fallbacks/remove-token-fallbacks.js +215 -0
  31. package/dist/esm/presets/remove-token-fallbacks/types.js +1 -0
  32. package/dist/esm/presets/remove-token-fallbacks/utils/all-tokens.js +38 -0
  33. package/dist/esm/presets/remove-token-fallbacks/utils/color-utils.js +86 -0
  34. package/dist/esm/presets/remove-token-fallbacks/utils/get-team-info.js +44 -0
  35. package/dist/esm/presets/remove-token-fallbacks/utils/normalize-values.js +107 -0
  36. package/dist/esm/presets/remove-token-fallbacks/utils/remove-unused-imports.js +55 -0
  37. package/dist/esm/presets/remove-token-fallbacks/utils/remove-unused-variables.js +31 -0
  38. package/dist/esm/presets/remove-token-fallbacks/utils/reporter.js +302 -0
  39. package/dist/esm/presets/remove-token-fallbacks/utils/token-processor.js +625 -0
  40. package/dist/esm/presets/remove-token-fallbacks/utils/update-comments.js +51 -0
  41. package/dist/types/presets/index.d.ts +1 -0
  42. package/dist/types/presets/remove-token-fallbacks/remove-token-fallbacks.d.ts +29 -0
  43. package/dist/types/presets/remove-token-fallbacks/types.d.ts +39 -0
  44. package/dist/types/presets/remove-token-fallbacks/utils/all-tokens.d.ts +1 -0
  45. package/dist/types/presets/remove-token-fallbacks/utils/color-utils.d.ts +3 -0
  46. package/dist/types/presets/remove-token-fallbacks/utils/get-team-info.d.ts +8 -0
  47. package/dist/types/presets/remove-token-fallbacks/utils/normalize-values.d.ts +8 -0
  48. package/dist/types/presets/remove-token-fallbacks/utils/remove-unused-imports.d.ts +2 -0
  49. package/dist/types/presets/remove-token-fallbacks/utils/remove-unused-variables.d.ts +2 -0
  50. package/dist/types/presets/remove-token-fallbacks/utils/reporter.d.ts +4 -0
  51. package/dist/types/presets/remove-token-fallbacks/utils/token-processor.d.ts +30 -0
  52. package/dist/types/presets/remove-token-fallbacks/utils/update-comments.d.ts +2 -0
  53. package/dist/types-ts4.5/presets/index.d.ts +1 -0
  54. package/dist/types-ts4.5/presets/remove-token-fallbacks/remove-token-fallbacks.d.ts +29 -0
  55. package/dist/types-ts4.5/presets/remove-token-fallbacks/types.d.ts +39 -0
  56. package/dist/types-ts4.5/presets/remove-token-fallbacks/utils/all-tokens.d.ts +1 -0
  57. package/dist/types-ts4.5/presets/remove-token-fallbacks/utils/color-utils.d.ts +3 -0
  58. package/dist/types-ts4.5/presets/remove-token-fallbacks/utils/get-team-info.d.ts +8 -0
  59. package/dist/types-ts4.5/presets/remove-token-fallbacks/utils/normalize-values.d.ts +8 -0
  60. package/dist/types-ts4.5/presets/remove-token-fallbacks/utils/remove-unused-imports.d.ts +2 -0
  61. package/dist/types-ts4.5/presets/remove-token-fallbacks/utils/remove-unused-variables.d.ts +2 -0
  62. package/dist/types-ts4.5/presets/remove-token-fallbacks/utils/reporter.d.ts +4 -0
  63. package/dist/types-ts4.5/presets/remove-token-fallbacks/utils/token-processor.d.ts +30 -0
  64. package/dist/types-ts4.5/presets/remove-token-fallbacks/utils/update-comments.d.ts +2 -0
  65. package/package.json +10 -5
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
+ Object.defineProperty(exports, "__esModule", {
5
+ value: true
6
+ });
7
+ exports.addOrUpdateEslintIgnoreComment = addOrUpdateEslintIgnoreComment;
8
+ var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
9
+ function addOrUpdateEslintIgnoreComment(j, tokenValue, fallbackValue, callPath) {
10
+ var commentText = "eslint-disable-next-line @atlaskit/design-system/no-unsafe-design-token-usage -- The token value \"".concat(tokenValue, "\" and fallback \"").concat(fallbackValue, "\" do not match and can't be replaced automatically.");
11
+ // first see if we can add the comment to the parent object property
12
+ var updatedCommentInObjectExpression = addOrUpdateEslintIgnoreCommentForParentInObjectExpression(j, commentText, callPath);
13
+ // if the comment was not added to the parent object property, add it to the node itself
14
+ if (!updatedCommentInObjectExpression) {
15
+ addOrUpdateEslintIgnoreCommentForNodeItself(j, commentText, callPath);
16
+ }
17
+ }
18
+ function addOrUpdateEslintIgnoreCommentForNodeItself(j, commentText, callPath) {
19
+ var leadingComments = callPath.node.leadingComments || [];
20
+ var existingCommentIndex = leadingComments.findIndex(function (comment) {
21
+ return comment.value.includes('eslint-disable-next-line @atlaskit/design-system/no-unsafe-design-token-usage');
22
+ });
23
+ var commentLine = j.commentLine(commentText, true);
24
+ if (existingCommentIndex !== -1) {
25
+ // Replace the existing comment
26
+ // Note: in order to modify the comment, it's fine to update it in the `leadingComments` array
27
+ leadingComments[existingCommentIndex] = commentLine;
28
+ } else {
29
+ // Add a new comment if none exists
30
+ // Note: Adding new comment to 'leadingComments' doesn't affect anything, we need to add it to 'comments' property
31
+ callPath.node.comments = [commentLine];
32
+ }
33
+ }
34
+ function addOrUpdateEslintIgnoreCommentForParentInObjectExpression(j, commentText, callPath) {
35
+ var parent = callPath.parentPath;
36
+ // Check if the parent node is an ObjectProperty
37
+ if (parent && parent.node.type === 'ObjectProperty') {
38
+ var grandparent = parent.parentPath;
39
+ // Check if the grandparent is an ObjectExpression
40
+ if (grandparent && grandparent.node.type === 'ObjectExpression') {
41
+ // Check for existing leading comments
42
+ var leadingComments = parent.node.leadingComments || [];
43
+ var existingCommentIndex = leadingComments.findIndex(function (comment) {
44
+ return comment.value.includes('eslint-disable-next-line @atlaskit/design-system/no-unsafe-design-token-usage');
45
+ });
46
+ var commentLine = j.commentLine(commentText, true);
47
+ if (existingCommentIndex !== -1) {
48
+ // Replace the existing comment
49
+ leadingComments[existingCommentIndex] = commentLine;
50
+ } else {
51
+ // Add a new comment if none exists
52
+ parent.node.comments = [commentLine].concat((0, _toConsumableArray2.default)(parent.node.comments || []));
53
+ }
54
+ return true;
55
+ }
56
+ }
57
+ return false;
58
+ }
@@ -106,9 +106,22 @@ const runTransform = async (filePaths, transform, flags) => {
106
106
  const jscodeshiftContent = fs.readFileSync(jscodeshift, 'utf8');
107
107
  const jscodeshiftContentNew = fixLineEnding(jscodeshiftContent, 'LF');
108
108
  fs.writeFileSync(jscodeshift, jscodeshiftContentNew);
109
+ let transformModule;
110
+ try {
111
+ transformModule = require(transformPath);
112
+ } catch (error) {
113
+ // eslint-disable-next-line no-console
114
+ console.warn(`Error loading transform module: ${transformPath}. Skipping lifecycle hooks.`);
115
+ }
116
+ if (transformModule) {
117
+ await processLifecycleHook('beforeAll', transformModule, logger, transform, flags);
118
+ }
109
119
  await spawn(jscodeshift, args, {
110
120
  stdio: 'inherit'
111
121
  });
122
+ if (transformModule) {
123
+ await processLifecycleHook('afterAll', transformModule, logger, transform, flags);
124
+ }
112
125
  };
113
126
  const parsePkg = pkg => {
114
127
  if (!pkg.startsWith('@')) {
@@ -179,6 +192,17 @@ const defaultFlags = {
179
192
  ignorePattern: 'node_modules',
180
193
  logger: console
181
194
  };
195
+ async function processLifecycleHook(hookName, transformModule, logger, transform, flags) {
196
+ if (typeof transformModule[hookName] === 'function') {
197
+ try {
198
+ logger.log(chalk.green(`Executing ${hookName} for transform '${transform.name}'...`));
199
+ await transformModule[hookName](flags);
200
+ } catch (error) {
201
+ logger.log(chalk.red(`Error in ${hookName} for transform '${transform.name}': ${error}`));
202
+ throw error; // Re-throw error for beforeAll to halt execution
203
+ }
204
+ }
205
+ }
182
206
  export default async function main(input, userFlags) {
183
207
  const flags = {
184
208
  ...defaultFlags,
@@ -10,5 +10,6 @@ import './migrate-to-link/migrate-to-link';
10
10
  import './migrate-to-new-buttons/migrate-to-new-buttons';
11
11
  import './upgrade-pragmatic-drag-and-drop-to-stable/upgrade-pragmatic-drag-and-drop-to-stable';
12
12
  import './remove-dark-theme-vr-options/remove-dark-theme-vr-options';
13
- const presets = ['styled-to-emotion', 'theme-remove-deprecated-mixins', 'migrate-to-link', 'migrate-to-new-buttons', 'upgrade-pragmatic-drag-and-drop-to-stable', 'remove-dark-theme-vr-options'].map(preset => path.join(__dirname, preset, `${preset}.@(ts|js|tsx)`));
13
+ import './remove-token-fallbacks/remove-token-fallbacks';
14
+ const presets = ['styled-to-emotion', 'theme-remove-deprecated-mixins', 'migrate-to-link', 'migrate-to-new-buttons', 'upgrade-pragmatic-drag-and-drop-to-stable', 'remove-dark-theme-vr-options', 'remove-token-fallbacks'].map(preset => path.join(__dirname, preset, `${preset}.@(ts|js|tsx)`));
14
15
  export default presets;
@@ -0,0 +1,130 @@
1
+ /* eslint-disable no-console */
2
+ import { exec } from 'child_process';
3
+ import fs from 'fs/promises';
4
+ import path from 'path';
5
+ import { promisify } from 'util';
6
+ import { hasImportDeclaration } from '@hypermod/utils';
7
+ import { findRoot } from '@manypkg/find-root';
8
+ import chalk from 'chalk';
9
+ import { getTokenMap } from './utils/all-tokens';
10
+ import { getTeamInfo } from './utils/get-team-info';
11
+ import { removeUnusedImports } from './utils/remove-unused-imports';
12
+ import { removeUnusedVariables } from './utils/remove-unused-variables';
13
+ import { clearFolder, combineReports, writeReports } from './utils/reporter';
14
+ import { TokenProcessor } from './utils/token-processor';
15
+ const execAsync = promisify(exec);
16
+
17
+ /**
18
+ * Transforms the source code of a file by removing fallback values from the @atlaskit/tokens/token functions.
19
+ * By default removes only the fallbacks that have the same values as the tokens.
20
+ *
21
+ * @param {FileInfo} fileInfo - Information about the file to be transformed.
22
+ * @param {API} api - The jscodeshift API, providing utilities for AST transformations.
23
+ * @param {RemoveTokenFallbackOptions} options - Options for the transformation, including:
24
+ * @param {boolean} [options.verbose] - If true, enables verbose logging.
25
+ * @param {boolean} [options.forceUpdate] - If true, removes the fallbacks regardless of the difference between token and fallback. Otherwise removes only the fallbacks that are equal to the token values.
26
+ * @param {boolean} [options.addEslintComments] - If true, adds the eslint ignore comment for the rule @atlaskit/design-system/no-unsafe-design-token-usage for the fallbacks that weren't removed.
27
+ * @param {boolean} [options.useLegacyColorTheme] - If true, uses the legacy theme for color token mapping.
28
+ * @param {string} [options.reportFolder] - Directory path to output transformation reports. Reports will be generated only if this option is provided.
29
+ * @param {boolean} [options.dry] - If true, performs a dry run without modifying the files.
30
+ *
31
+ * @returns {Promise<string>} A promise that resolves to the transformed source code as a string.
32
+ */
33
+ export default async function transformer(fileInfo, {
34
+ jscodeshift: j
35
+ }, options) {
36
+ var _options$useLegacyCol;
37
+ const rootDir = await findRoot(path.dirname(fileInfo.path));
38
+ const source = j(fileInfo.source);
39
+ if (!hasImportDeclaration(j, source, '@atlaskit/tokens')) {
40
+ return fileInfo.source;
41
+ }
42
+ const details = {
43
+ replaced: [],
44
+ notReplaced: []
45
+ };
46
+ if (options.verbose) {
47
+ console.log(chalk.yellow(`Using ${options.useLegacyColorTheme ? 'legacy light' : 'light'} theme.`));
48
+ }
49
+ const tokenMap = getTokenMap((_options$useLegacyCol = options.useLegacyColorTheme) !== null && _options$useLegacyCol !== void 0 ? _options$useLegacyCol : false);
50
+ const teamInfo = await getTeamInfo(fileInfo.path);
51
+ const transformPromises = source.find(j.CallExpression, {
52
+ callee: {
53
+ type: 'Identifier',
54
+ name: 'token'
55
+ }
56
+ }).paths().map(callPath => {
57
+ const processor = new TokenProcessor(j, options, fileInfo, source, rootDir, details, tokenMap, teamInfo);
58
+ return processor.processAndLogSingleToken(callPath);
59
+ });
60
+ const results = await Promise.all(transformPromises);
61
+ const unusedVars = [];
62
+ if (results.some(result => result.fallbackRemoved)) {
63
+ const allImports = results.flatMap(result => {
64
+ var _result$resolvedImpor;
65
+ return (_result$resolvedImpor = result.resolvedImportDeclaration) !== null && _result$resolvedImpor !== void 0 ? _result$resolvedImpor : [];
66
+ });
67
+ const allVars = results.flatMap(result => {
68
+ var _result$resolvedLocal;
69
+ return (_result$resolvedLocal = result.resolvedLocalVarDeclaration) !== null && _result$resolvedLocal !== void 0 ? _result$resolvedLocal : [];
70
+ });
71
+ unusedVars.push(...allVars);
72
+ if (allImports.length) {
73
+ if (options.verbose) {
74
+ console.log(chalk.green(`${fileInfo.path}: Some fallbacks were removed. Cleaning up ${allImports.length} imports.`));
75
+ }
76
+ removeUnusedImports(allImports, j);
77
+ }
78
+ }
79
+ removeUnusedVariables(unusedVars, j);
80
+ if (options.reportFolder) {
81
+ await writeReports(details, options.reportFolder);
82
+ }
83
+ if (options.dry) {
84
+ if (options.verbose) {
85
+ console.log(chalk.cyan(`${fileInfo.path}: dry run mode active. Source was not modified.`));
86
+ }
87
+ // Return the unmodified source if dryRun is true
88
+ return fileInfo.source;
89
+ } else {
90
+ // Return the transformed source
91
+ return source.toSource();
92
+ }
93
+ }
94
+ export const parser = 'tsx';
95
+
96
+ /**
97
+ * Function executed before all transformations to prepare the environment by clearing the report folder.
98
+ */
99
+ export async function beforeAll(options) {
100
+ if (options.reportFolder) {
101
+ await clearFolder(options.reportFolder);
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Function executed after all transformations to combine individual file reports into a comprehensive transformation report.
107
+ * It also applies prettier to the affected files.
108
+ */
109
+ export async function afterAll(options) {
110
+ if (options.reportFolder) {
111
+ await combineReports(options.reportFolder);
112
+ }
113
+ if (options.reportFolder && !options.dry) {
114
+ try {
115
+ const filesTxtPath = path.join(options.reportFolder, 'files.txt');
116
+ const fileContent = await fs.readFile(filesTxtPath, 'utf-8');
117
+ if (fileContent.length > 0) {
118
+ console.log(`Running prettier on files: ${chalk.magenta(fileContent)}`);
119
+ await execAsync(`yarn prettier --write ${fileContent}`);
120
+ console.log(chalk.green(`Prettier was run successfully`));
121
+ }
122
+ } catch (error) {
123
+ if (error instanceof Error) {
124
+ console.error(chalk.red(`Unexpected error running Prettier: ${error.message}`));
125
+ } else {
126
+ console.error(chalk.red('An unknown error occurred while running Prettier.'));
127
+ }
128
+ }
129
+ }
130
+ }
@@ -0,0 +1,30 @@
1
+ import { legacyLightTokens as legacyLightTheme, light as lightTheme, shape as shapeTheme, spacing as spacingTheme, typographyAdg3 as typographyAdg3Theme } from '@atlaskit/tokens/tokens-raw';
2
+ const typographyGroups = ['typography', 'fontWeight', 'fontFamily'];
3
+ function buildCombinedMap(...arrays) {
4
+ const combinedMap = {};
5
+ arrays.forEach(array => {
6
+ array.forEach(token => {
7
+ combinedMap[token.cleanName] = token.value.toString();
8
+ });
9
+ });
10
+ return combinedMap;
11
+ }
12
+
13
+ // Filter the typography tokens based on predefined groups and exclusions
14
+ const typographyAdg3ThemeFiltered = typographyAdg3Theme.filter(token => typographyGroups.includes(token.attributes.group)).filter(token => token.cleanName !== 'font.body.UNSAFE_small');
15
+
16
+ // Cache array: [0] for light theme, [1] for legacy light theme
17
+ const tokenMapCache = [null, null];
18
+
19
+ // Function to get the token map with the desired theme.
20
+ // This should be used with the same value as the @atlaskit/tokens/babel-plugin is cofigured with for the given path.
21
+ // When there is no theme, the babel plugin would use the given theme to provide default token fallbacks. Therefore it's safe to remove fallbacks only when they match the fallbacks that would be generated by the babel plugin with the given theme.
22
+ // The default value is set to use the Light theme to match the behavior of the babel plugin (packages/design-system/tokens/src/babel-plugin/plugin.tsx).
23
+ export function getTokenMap(useLegacyTheme = false) {
24
+ const themeIndex = useLegacyTheme ? 1 : 0;
25
+ if (!tokenMapCache[themeIndex]) {
26
+ const selectedTheme = useLegacyTheme ? legacyLightTheme : lightTheme;
27
+ tokenMapCache[themeIndex] = buildCombinedMap(selectedTheme, spacingTheme, shapeTheme, typographyAdg3ThemeFiltered);
28
+ }
29
+ return tokenMapCache[themeIndex];
30
+ }
@@ -0,0 +1,84 @@
1
+ import { diff, rgb_to_lab } from 'color-diff';
2
+
3
+ // Compare hex values using a CIEDE2000 color difference algorithm
4
+ export const compareHex = (hex, hex2) => diff(rgb_to_lab(hexToRgbA(hex)), rgb_to_lab(hexToRgbA(hex2)));
5
+ function hexToRgbA(hex) {
6
+ // Remove the leading '#' if present
7
+ hex = hex.replace(/^#/, '');
8
+ // Parse the hex string
9
+ const r = parseInt(hex.substring(0, 2), 16);
10
+ const g = parseInt(hex.substring(2, 4), 16);
11
+ const b = parseInt(hex.substring(4, 6), 16);
12
+ const a = parseInt(hex.substring(6, 8), 16) / 255;
13
+ return {
14
+ R: r,
15
+ G: g,
16
+ B: b,
17
+ A: a
18
+ };
19
+ }
20
+ const namedColors = {
21
+ black: '#000000',
22
+ silver: '#C0C0C0',
23
+ gray: '#808080',
24
+ grey: '#808080',
25
+ pink: '#FFC0CB',
26
+ white: '#FFFFFF',
27
+ maroon: '#800000',
28
+ red: '#FF0000',
29
+ purple: '#800080',
30
+ fuchsia: '#FF00FF',
31
+ green: '#008000',
32
+ lime: '#00FF00',
33
+ olive: '#808000',
34
+ yellow: '#FFFF00',
35
+ navy: '#000080',
36
+ blue: '#0000FF',
37
+ teal: '#008080',
38
+ aqua: '#00FFFF'
39
+ };
40
+ export function isValidColor(color) {
41
+ // Check if it's a named color
42
+ if (namedColors[color.toLowerCase()]) {
43
+ return true;
44
+ }
45
+ // Check for hex colors (including those with alpha)
46
+ if (/^#([0-9A-F]{3}){1,2}([0-9A-F]{2})?$/i.test(color)) {
47
+ return true;
48
+ }
49
+ // Check for rgba() values
50
+ if (/^rgba?\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*(?:,\s*(?:0?\.)?\d+\s*)?\)$/i.test(color)) {
51
+ return true;
52
+ }
53
+ return false;
54
+ }
55
+ export function colorToHex(color) {
56
+ // Handle named colors
57
+ if (namedColors[color.toLowerCase()]) {
58
+ return namedColors[color.toLowerCase()].toUpperCase() + 'FF'; // Add full opacity
59
+ }
60
+ if (color.startsWith('#')) {
61
+ // If it's already a hex color
62
+ if (color.length === 7) {
63
+ // #RRGGBB format, add full opacity
64
+ return (color + 'FF').toUpperCase();
65
+ } else if (color.length === 9) {
66
+ // #RRGGBBAA format, return as is
67
+ return color.toUpperCase();
68
+ } else if (color.length === 4) {
69
+ // #RGB format, expand to #RRGGBBFF
70
+ return ('#' + color[1] + color[1] + color[2] + color[2] + color[3] + color[3] + 'FF').toUpperCase();
71
+ }
72
+ }
73
+ // For rgb() and rgba(), convert to hex
74
+ const match = color.match(/^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d*\.?\d+)\s*)?\)$/i);
75
+ if (match) {
76
+ const r = parseInt(match[1], 10);
77
+ const g = parseInt(match[2], 10);
78
+ const b = parseInt(match[3], 10);
79
+ const a = match[4] !== undefined ? Math.round(parseFloat(match[4]) * 255) : 255;
80
+ return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).padStart(6, '0')}${a.toString(16).padStart(2, '0')}`.toUpperCase();
81
+ }
82
+ // If conversion is not possible, return the original color
83
+ return color;
84
+ }
@@ -0,0 +1,22 @@
1
+ import readPkgUp from 'read-pkg-up';
2
+ /**
3
+ * Looks for the closest package.json and gets team and package information from it.
4
+ *
5
+ * @param {string} filePath
6
+ * @returns {Promise<AtlassianTeamInfo>}
7
+ */
8
+ export const getTeamInfo = async filePath => {
9
+ var _pkgJson$packageJson$;
10
+ const pkgJson = await readPkgUp({
11
+ cwd: filePath
12
+ });
13
+ if (!pkgJson || !pkgJson.packageJson) {
14
+ throw new Error(`Closest package.json file could not be found or is invalid for ${filePath}.`);
15
+ }
16
+ const packageName = pkgJson.packageJson.name || '';
17
+ const teamName = ((_pkgJson$packageJson$ = pkgJson.packageJson.atlassian) === null || _pkgJson$packageJson$ === void 0 ? void 0 : _pkgJson$packageJson$.team) || '';
18
+ return {
19
+ packageName,
20
+ teamName
21
+ };
22
+ };
@@ -0,0 +1,104 @@
1
+ import chalk from 'chalk';
2
+ import { colorToHex, compareHex, isValidColor } from './color-utils';
3
+
4
+ // so far allowing to remove only exact matches in values. Can be increased to auto-remove fallbacks with similar values
5
+ const ACCEPTABLE_COLOR_DIFFERENCE = 0;
6
+ const ACCEPTABLE_SPACE_DIFFERENCE = 0;
7
+ const ACCEPTABLE_NUMERIC_DIFFERENCE = 0;
8
+ export function normalizeValues(tokenKey, tokenValue, fallbackValue) {
9
+ let tokenLogValue;
10
+ let fallbackLogValue;
11
+ let normalizedTokenValue = tokenValue;
12
+ let normalizedFallbackValue = fallbackValue;
13
+ const lowerCaseTokenKey = tokenKey === null || tokenKey === void 0 ? void 0 : tokenKey.toLowerCase();
14
+ let difference;
15
+ let isAcceptableDifference;
16
+ if (lowerCaseTokenKey.startsWith('color') || lowerCaseTokenKey.startsWith('elevation')) {
17
+ if (tokenValue && isValidColor(tokenValue)) {
18
+ const normalizedHex = colorToHex(tokenValue);
19
+ tokenLogValue = chalk.bgHex(normalizedHex)(tokenValue);
20
+ normalizedTokenValue = normalizedHex;
21
+ }
22
+ if (fallbackValue && isValidColor(fallbackValue)) {
23
+ const normalizedHex = colorToHex(fallbackValue);
24
+ fallbackLogValue = chalk.bgHex(normalizedHex)(fallbackValue);
25
+ normalizedFallbackValue = normalizedHex;
26
+ }
27
+ if (normalizedTokenValue && normalizedFallbackValue) {
28
+ difference = compareHex(normalizedTokenValue, normalizedFallbackValue);
29
+ isAcceptableDifference = difference <= ACCEPTABLE_COLOR_DIFFERENCE;
30
+ }
31
+ } else if (lowerCaseTokenKey.startsWith('space')) {
32
+ const tokenValueInPx = tokenValue ? convertToPx(tokenValue) : undefined;
33
+ const fallbackValueInPx = fallbackValue ? convertToPx(fallbackValue) : undefined;
34
+ if (tokenValueInPx !== undefined && fallbackValueInPx !== undefined) {
35
+ const maxVal = Math.max(tokenValueInPx, fallbackValueInPx);
36
+ difference = Math.abs(tokenValueInPx - fallbackValueInPx) / maxVal * 100;
37
+ isAcceptableDifference = difference <= ACCEPTABLE_SPACE_DIFFERENCE;
38
+ }
39
+ // Log the normalized values
40
+ normalizedTokenValue = tokenValue;
41
+ normalizedFallbackValue = fallbackValue;
42
+ tokenLogValue = tokenValue;
43
+ fallbackLogValue = fallbackValue;
44
+ } else {
45
+ // Handle other numeric comparisons
46
+ const tokenValueNumber = parseFloat(tokenValue !== null && tokenValue !== void 0 ? tokenValue : '');
47
+ const fallbackValueNumber = parseFloat(fallbackValue !== null && fallbackValue !== void 0 ? fallbackValue : '');
48
+ if (!isNaN(tokenValueNumber) && !isNaN(fallbackValueNumber)) {
49
+ const maxVal = Math.max(tokenValueNumber, fallbackValueNumber);
50
+ difference = Math.abs(tokenValueNumber - fallbackValueNumber) / maxVal * 100;
51
+ isAcceptableDifference = difference <= ACCEPTABLE_NUMERIC_DIFFERENCE;
52
+ }
53
+ // Log the normalized values
54
+ normalizedTokenValue = tokenValue;
55
+ normalizedFallbackValue = fallbackValue;
56
+ tokenLogValue = tokenValue;
57
+ fallbackLogValue = fallbackValue;
58
+ }
59
+ if (tokenLogValue === undefined) {
60
+ tokenLogValue = chalk.magenta(tokenValue || '');
61
+ }
62
+ if (fallbackLogValue === undefined) {
63
+ fallbackLogValue = chalk.yellow(fallbackValue || '');
64
+ }
65
+ return {
66
+ difference,
67
+ isAcceptableDifference,
68
+ tokenLogValue,
69
+ fallbackLogValue,
70
+ normalizedTokenValue,
71
+ normalizedFallbackValue
72
+ };
73
+ }
74
+ function convertToPx(value) {
75
+ // If the value is a number, return it directly
76
+ if (typeof value === 'number') {
77
+ return value;
78
+ }
79
+ // Check if the string is a plain number (without units)
80
+ const plainNumberRegex = /^-?\d+(\.\d+)?$/;
81
+ if (plainNumberRegex.test(value)) {
82
+ return parseFloat(value);
83
+ }
84
+ // Regular expression to match CSS units
85
+ const unitRegex = /^(-?\d+(\.\d+)?)(px|rem|em|%)$/;
86
+ const match = value.match(unitRegex);
87
+ if (!match) {
88
+ return undefined;
89
+ }
90
+ const [, num,, unit] = match;
91
+ const numericValue = parseFloat(num);
92
+ switch (unit) {
93
+ case 'px':
94
+ return numericValue;
95
+ case 'rem':
96
+ return numericValue * 16;
97
+ // Assuming 1rem = 16px
98
+ case 'em':
99
+ return numericValue * 16;
100
+ // Assuming 1em = 16px
101
+ default:
102
+ return undefined;
103
+ }
104
+ }
@@ -0,0 +1,51 @@
1
+ export function removeUnusedImports(importDeclarations, j) {
2
+ const removeIfUnused = (importSpecifier, importDeclaration) => {
3
+ var _importSpecifier$valu;
4
+ const varName = (_importSpecifier$valu = importSpecifier.value.local) === null || _importSpecifier$valu === void 0 ? void 0 : _importSpecifier$valu.name;
5
+ if (varName === 'React' || varName === 'jsx') {
6
+ return false;
7
+ }
8
+ const isUsedInScopes = () => {
9
+ return j(importDeclaration).closestScope().find(j.Identifier, {
10
+ name: varName
11
+ }).filter(p => {
12
+ var _importSpecifier$valu2;
13
+ if (p.value.start === ((_importSpecifier$valu2 = importSpecifier.value.local) === null || _importSpecifier$valu2 === void 0 ? void 0 : _importSpecifier$valu2.start)) {
14
+ return false;
15
+ }
16
+ if (p.parentPath.value.type === 'Property' && p.name === 'key') {
17
+ return false;
18
+ }
19
+ if (p.name === 'property') {
20
+ return false;
21
+ }
22
+ return true;
23
+ }).size() > 0;
24
+ };
25
+ if (!isUsedInScopes()) {
26
+ j(importSpecifier).remove();
27
+ return true;
28
+ }
29
+ return false;
30
+ };
31
+ const removeUnusedDefaultImport = importDeclaration => {
32
+ return j(importDeclaration).find(j.ImportDefaultSpecifier).filter(s => removeIfUnused(s, importDeclaration)).size() > 0;
33
+ };
34
+ const removeUnusedNonDefaultImports = importDeclaration => {
35
+ return j(importDeclaration).find(j.ImportSpecifier).filter(s => removeIfUnused(s, importDeclaration)).size() > 0;
36
+ };
37
+ const processImportDeclaration = importDeclaration => {
38
+ var _importDeclaration$va, _importDeclaration$va2;
39
+ if (((_importDeclaration$va = importDeclaration.value.specifiers) === null || _importDeclaration$va === void 0 ? void 0 : _importDeclaration$va.length) === 0) {
40
+ return false;
41
+ }
42
+ const hadUnusedDefaultImport = removeUnusedDefaultImport(importDeclaration);
43
+ const hadUnusedNonDefaultImports = removeUnusedNonDefaultImports(importDeclaration);
44
+ if (((_importDeclaration$va2 = importDeclaration.value.specifiers) === null || _importDeclaration$va2 === void 0 ? void 0 : _importDeclaration$va2.length) === 0) {
45
+ j(importDeclaration).remove();
46
+ return true;
47
+ }
48
+ return hadUnusedDefaultImport || hadUnusedNonDefaultImports;
49
+ };
50
+ importDeclarations.forEach(processImportDeclaration);
51
+ }
@@ -0,0 +1,31 @@
1
+ export function removeUnusedVariables(variableDeclarations, j) {
2
+ const removeIfUnused = varDeclarator => {
3
+ var _varDeclarator$value;
4
+ if (((_varDeclarator$value = varDeclarator.value) === null || _varDeclarator$value === void 0 ? void 0 : _varDeclarator$value.id.type) !== 'Identifier') {
5
+ return false;
6
+ }
7
+ const varName = varDeclarator.value.id.name;
8
+ const isUsedInScopes = () => {
9
+ return j(varDeclarator).closestScope().find(j.Identifier, {
10
+ name: varName
11
+ }).filter(p => {
12
+ if (p.value.start === varDeclarator.value.id.start) {
13
+ return false;
14
+ }
15
+ if (p.parentPath.value.type === 'Property' && p.name === 'key') {
16
+ return false;
17
+ }
18
+ if (p.name === 'property') {
19
+ return false;
20
+ }
21
+ return true;
22
+ }).size() > 0;
23
+ };
24
+ if (!isUsedInScopes()) {
25
+ j(varDeclarator).remove();
26
+ return true;
27
+ }
28
+ return false;
29
+ };
30
+ variableDeclarations.forEach(removeIfUnused);
31
+ }