@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,118 @@
1
+ /* eslint-disable no-console */
2
+ import fs from 'fs/promises';
3
+ import path from 'path';
4
+ import { v4 as uuidv4 } from 'uuid';
5
+ async function writeToCsv(filePath, data) {
6
+ await fs.writeFile(filePath, data.join('\n'), 'utf-8');
7
+ }
8
+ async function readCsv(filePath) {
9
+ const data = await fs.readFile(filePath, 'utf-8');
10
+ return data.split('\n').filter(line => line.trim() !== '');
11
+ }
12
+ function escapeCsvValue(value) {
13
+ if (!value) {
14
+ return value;
15
+ }
16
+ if (value.includes('"')) {
17
+ // Escape double quotes by doubling them
18
+ value = value.replace(/"/g, '""');
19
+ }
20
+ if (value.includes(',') || value.includes('"') || value.includes('\n')) {
21
+ // Surround the value with double quotes if it contains a comma, double quotes, or newlines
22
+ value = `"${value}"`;
23
+ }
24
+ return value;
25
+ }
26
+ export async function clearFolder(reportFolder) {
27
+ console.log('Clearing report folder:', reportFolder);
28
+ const filesToDelete = await fs.readdir(reportFolder);
29
+ for (const file of filesToDelete) {
30
+ const filePath = path.join(reportFolder, file);
31
+ await fs.unlink(filePath);
32
+ }
33
+ }
34
+ async function saveFilePaths(reportFolder, files) {
35
+ const filesTxtPath = path.join(reportFolder, 'files.txt');
36
+ await fs.writeFile(filesTxtPath, Array.from(files).map(filePath => `"${filePath}"`).join(' '), 'utf-8');
37
+ }
38
+ export async function combineReports(reportFolder) {
39
+ console.log('Combining reports in folder:', reportFolder);
40
+ const files = await fs.readdir(reportFolder);
41
+ let totalReplaced = 0;
42
+ let totalNotReplaced = 0;
43
+ const combinedReplacements = [];
44
+ const combinedNonReplacements = [];
45
+ const affectedPaths = new Set();
46
+ for (const file of files) {
47
+ const filePath = path.join(reportFolder, file);
48
+ if (file.endsWith('_success.csv')) {
49
+ const replacements = await readCsv(filePath);
50
+ const codeFilePaths = replacements.map(x => x.split(',')[2]);
51
+ for (const codeFilePath of codeFilePaths) {
52
+ affectedPaths.add(codeFilePath);
53
+ }
54
+ totalReplaced += replacements.length;
55
+ combinedReplacements.push(...replacements);
56
+ } else if (file.endsWith('_failed.csv')) {
57
+ const nonReplacements = await readCsv(filePath);
58
+ totalNotReplaced += nonReplacements.length;
59
+ combinedNonReplacements.push(...nonReplacements);
60
+ }
61
+ }
62
+ const totalTokens = totalReplaced + totalNotReplaced;
63
+ const percentageReplaced = totalTokens > 0 ? totalReplaced / totalTokens * 100 : 0;
64
+ await clearFolder(reportFolder);
65
+
66
+ // Write combined summary as JSON
67
+ const combinedSummaryPath = path.join(reportFolder, 'summary.json');
68
+ const summaryData = {
69
+ totalReplaced,
70
+ totalNotReplaced,
71
+ percentageReplaced
72
+ };
73
+ await fs.writeFile(combinedSummaryPath, JSON.stringify(summaryData, null, 2), 'utf-8');
74
+
75
+ // Sort the combined arrays
76
+ const sortCsvLines = lines => {
77
+ return lines.sort((a, b) => {
78
+ const aCols = a.split(',');
79
+ const bCols = b.split(',');
80
+ for (let i = 0; i < 4; i++) {
81
+ const comparison = aCols[i].localeCompare(bCols[i]);
82
+ if (comparison !== 0) {
83
+ return comparison;
84
+ }
85
+ }
86
+ return 0;
87
+ });
88
+ };
89
+ const sortedReplacements = sortCsvLines(combinedReplacements);
90
+ const sortedNonReplacements = sortCsvLines(combinedNonReplacements);
91
+
92
+ // Write combined replacements
93
+ const combinedReplacementsPath = path.join(reportFolder, 'success.csv');
94
+ const header = 'Team,Package,File,Line Number,Raw Token Key,Raw Fallback Value,Resolved Token Value,Resolved Fallback Value,Difference %';
95
+ await fs.writeFile(combinedReplacementsPath, [header, ...sortedReplacements].join('\n'), 'utf-8');
96
+
97
+ // Write combined non-replacements
98
+ const combinedNonReplacementsPath = path.join(reportFolder, 'failed.csv');
99
+ await fs.writeFile(combinedNonReplacementsPath, [header, ...sortedNonReplacements].join('\n'), 'utf-8');
100
+ // Extract unique file paths
101
+ await saveFilePaths(reportFolder, affectedPaths);
102
+ }
103
+ function prepareCsvData(items) {
104
+ return items.map(item => {
105
+ var _item$difference$toFi, _item$difference;
106
+ return [escapeCsvValue(item.teamInfo.teamName), escapeCsvValue(item.teamInfo.packageName), escapeCsvValue(item.filePath), escapeCsvValue(String(item.lineNumber)), escapeCsvValue(item.tokenKey), escapeCsvValue(item.rawFallbackValue), escapeCsvValue(item.resolvedTokenValue), escapeCsvValue(item.resolvedFallbackValue), escapeCsvValue((_item$difference$toFi = (_item$difference = item.difference) === null || _item$difference === void 0 ? void 0 : _item$difference.toFixed(1)) !== null && _item$difference$toFi !== void 0 ? _item$difference$toFi : '')].join(',');
107
+ });
108
+ }
109
+ export async function writeReports(details, reportFolder) {
110
+ const replacementsFilePath = path.join(reportFolder, `${uuidv4()}_success.csv`);
111
+ const nonReplacementsFilePath = path.join(reportFolder, `${uuidv4()}_failed.csv`);
112
+ await fs.mkdir(reportFolder, {
113
+ recursive: true
114
+ });
115
+ const replacementData = prepareCsvData(details.replaced);
116
+ const nonReplacementData = prepareCsvData(details.notReplaced);
117
+ await Promise.all([writeToCsv(replacementsFilePath, replacementData), writeToCsv(nonReplacementsFilePath, nonReplacementData)]);
118
+ }
@@ -0,0 +1,377 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
+ /* eslint-disable no-console */
3
+ import fs from 'fs/promises';
4
+ import path from 'path';
5
+ import chalk from 'chalk';
6
+ import { normalizeValues } from './normalize-values';
7
+ import { addOrUpdateEslintIgnoreComment } from './update-comments';
8
+ export class TokenProcessor {
9
+ constructor(j, options, fileInfo, source, rootDir, details, tokenMap, teamInfo) {
10
+ _defineProperty(this, "logMessages", []);
11
+ _defineProperty(this, "possibleExtensions", ['.ts', '.tsx', '.js', '.jsx']);
12
+ this.j = j;
13
+ this.options = options;
14
+ this.fileInfo = fileInfo;
15
+ this.source = source;
16
+ this.rootDir = rootDir;
17
+ this.details = details;
18
+ this.tokenMap = tokenMap;
19
+ this.teamInfo = teamInfo;
20
+ }
21
+ async processAndLogSingleToken(callPath) {
22
+ var _callPath$node$loc;
23
+ const line = (_callPath$node$loc = callPath.node.loc) === null || _callPath$node$loc === void 0 ? void 0 : _callPath$node$loc.start.line;
24
+ const {
25
+ shouldLog,
26
+ ...rest
27
+ } = await this.processSingleToken(callPath);
28
+ if (this.options.silent || !shouldLog) {
29
+ return rest;
30
+ }
31
+ const coloredPath = chalk.blue(this.fileInfo.path);
32
+ const coloredLine = line ? `: ${chalk.green(line)}` : '';
33
+ console.log(`${coloredPath}${coloredLine}: ${this.logMessages.join(' | ')}
34
+ ----------------------------------------`);
35
+ return rest;
36
+ }
37
+ logVerbose(message) {
38
+ if (this.options.verbose) {
39
+ this.log(message);
40
+ }
41
+ }
42
+ log(message) {
43
+ this.logMessages.push(message);
44
+ }
45
+ logError(message) {
46
+ this.log(chalk.red(message));
47
+ }
48
+ async processSingleToken(callPath) {
49
+ var _callPath$node$loc2;
50
+ const args = callPath.node.arguments;
51
+ const line = (_callPath$node$loc2 = callPath.node.loc) === null || _callPath$node$loc2 === void 0 ? void 0 : _callPath$node$loc2.start.line;
52
+ if (args.length < 2) {
53
+ this.logVerbose(chalk.yellow('Skipped token call without fallback'));
54
+ return {
55
+ shouldLog: false,
56
+ fallbackRemoved: false,
57
+ resolvedImportDeclaration: undefined,
58
+ resolvedLocalVarDeclaration: undefined
59
+ };
60
+ }
61
+ const tokenKey = this.getTokenKey(args[0]);
62
+ if (!tokenKey) {
63
+ return {
64
+ shouldLog: true,
65
+ fallbackRemoved: false,
66
+ resolvedImportDeclaration: undefined,
67
+ resolvedLocalVarDeclaration: undefined
68
+ };
69
+ }
70
+ const isSkipped = tokenKey.startsWith('elevation.shadow') || tokenKey.startsWith('font.body') || tokenKey.startsWith('font.heading');
71
+ const tokenValue = isSkipped ? '' : this.tokenMap[tokenKey];
72
+ this.logVerbose(`Token value from tokenMap: ${chalk.magenta(tokenValue)} for key: ${chalk.yellow(tokenKey)}`);
73
+ const {
74
+ rawFallbackValue,
75
+ fallbackValue,
76
+ resolvedImportDeclaration,
77
+ resolvedLocalVarDeclaration
78
+ } = isSkipped ? {
79
+ rawFallbackValue: 'N/A',
80
+ fallbackValue: undefined,
81
+ resolvedImportDeclaration: undefined,
82
+ resolvedLocalVarDeclaration: undefined
83
+ } : await this.getFallbackValue(args[1]);
84
+ const {
85
+ difference,
86
+ isAcceptableDifference,
87
+ tokenLogValue,
88
+ fallbackLogValue,
89
+ normalizedTokenValue,
90
+ normalizedFallbackValue
91
+ } = normalizeValues(tokenKey, tokenValue, fallbackValue);
92
+ const areEqual = normalizedTokenValue === normalizedFallbackValue;
93
+ const logData = {
94
+ teamInfo: this.teamInfo,
95
+ filePath: this.fileInfo.path,
96
+ lineNumber: line || -1,
97
+ tokenKey,
98
+ rawFallbackValue,
99
+ resolvedTokenValue: tokenValue,
100
+ resolvedFallbackValue: fallbackValue !== null && fallbackValue !== void 0 ? fallbackValue : '',
101
+ difference
102
+ };
103
+ let fallbackRemoved = false;
104
+ let importDeclaration;
105
+ let localVarDeclaration;
106
+ if (areEqual || isAcceptableDifference || this.options.forceUpdate) {
107
+ this.log(chalk.green(areEqual ? 'Token value and fallback value are equal, removing fallback' : 'Token value and fallback value are within acceptable difference threshold, removing fallback'));
108
+ args.pop();
109
+ this.details.replaced.push(logData);
110
+ fallbackRemoved = true;
111
+ importDeclaration = resolvedImportDeclaration;
112
+ localVarDeclaration = resolvedLocalVarDeclaration;
113
+ } else {
114
+ const message = normalizedFallbackValue === undefined ? `Fallback value could not be resolved` : `Values mismatched significantly`;
115
+ this.logError(message);
116
+ if (this.options.addEslintComments) {
117
+ addOrUpdateEslintIgnoreComment(this.j, tokenValue, fallbackValue, callPath);
118
+ }
119
+ this.details.notReplaced.push(logData);
120
+ }
121
+ this.log(`Token: ${chalk.yellow(tokenKey)}, Raw fallback: ${chalk.yellow(rawFallbackValue)}, Resolved token value: ${tokenLogValue}, Resolved fallback value: ${fallbackLogValue}`);
122
+ return {
123
+ shouldLog: true,
124
+ fallbackRemoved,
125
+ resolvedImportDeclaration: importDeclaration,
126
+ resolvedLocalVarDeclaration: localVarDeclaration
127
+ };
128
+ }
129
+ getTokenKey(arg) {
130
+ if (arg.type === 'StringLiteral') {
131
+ const tokenKey = arg.value;
132
+ this.logVerbose(`Determined token key as literal: ${chalk.yellow(tokenKey)}`);
133
+ return tokenKey;
134
+ } else {
135
+ this.logError(`The first argument of token function is not a string literal`);
136
+ return undefined;
137
+ }
138
+ }
139
+ async getFallbackValue(fallbackValueNode) {
140
+ switch (fallbackValueNode.type) {
141
+ case 'StringLiteral':
142
+ return this.processFallbackAsStringLiteral(fallbackValueNode);
143
+ case 'Identifier':
144
+ return this.processFallbackAsIdentifier(fallbackValueNode);
145
+ case 'MemberExpression':
146
+ return this.processFallbackAsMemberExpression(fallbackValueNode);
147
+ case 'TemplateLiteral':
148
+ return this.processFallbackAsTemplateLiteral(fallbackValueNode);
149
+ default:
150
+ return {
151
+ fallbackValue: undefined,
152
+ rawFallbackValue: '',
153
+ resolvedImportDeclaration: undefined,
154
+ resolvedLocalVarDeclaration: undefined
155
+ };
156
+ }
157
+ }
158
+ processFallbackAsStringLiteral(fallbackValueNode) {
159
+ const fallbackValue = fallbackValueNode.value;
160
+ this.logVerbose(`Fallback value is a literal: ${chalk.yellow(fallbackValue)}`);
161
+ return {
162
+ fallbackValue,
163
+ rawFallbackValue: fallbackValue,
164
+ resolvedImportDeclaration: undefined,
165
+ resolvedLocalVarDeclaration: undefined
166
+ };
167
+ }
168
+ async processFallbackAsIdentifier(fallbackValueNode) {
169
+ const variableName = fallbackValueNode.name;
170
+ const variableNameForLog = `${chalk.yellow(variableName)}`;
171
+ let fallbackValue;
172
+ this.logVerbose(`Fallback is an identifier: ${chalk.yellow(variableName)}, attempting to resolve...`);
173
+
174
+ // Check for local variable declaration
175
+ const localVarDeclaration = this.source.find(this.j.VariableDeclarator, {
176
+ id: {
177
+ name: variableName
178
+ }
179
+ }).at(0);
180
+ let resolvedImportDeclaration;
181
+ let resolvedLocalVarDeclaration;
182
+ if (localVarDeclaration.size()) {
183
+ const init = localVarDeclaration.get().value.init;
184
+ if (init.type === 'Literal' || init.type === 'StringLiteral') {
185
+ fallbackValue = init.value;
186
+ resolvedLocalVarDeclaration = localVarDeclaration.paths()[0];
187
+ this.logVerbose(`Resolved fallback value from local variable: ${chalk.yellow(fallbackValue)} for identifier: ${variableNameForLog}`);
188
+ }
189
+ } else {
190
+ // Check for named import
191
+ const importDeclaration = this.source.find(this.j.ImportDeclaration).filter(this.createImportFilter(variableName)).at(0);
192
+ if (importDeclaration.size()) {
193
+ const importSource = importDeclaration.get().value.source.value;
194
+ fallbackValue = await this.resolveValueFromImport(this.rootDir, importSource, undefined, variableName);
195
+ if (fallbackValue !== undefined) {
196
+ this.logVerbose(`Resolved fallback value from import: ${chalk.yellow(fallbackValue)} for identifier: ${variableNameForLog}`);
197
+ resolvedImportDeclaration = importDeclaration.paths()[0];
198
+ }
199
+ } else {
200
+ this.logVerbose(chalk.red(`Could not resolve fallback value for identifier: ${variableNameForLog}: it's neither a local variable nor an import`));
201
+ }
202
+ }
203
+ return {
204
+ rawFallbackValue: variableName,
205
+ fallbackValue,
206
+ resolvedImportDeclaration,
207
+ resolvedLocalVarDeclaration
208
+ };
209
+ }
210
+ async processFallbackAsMemberExpression(fallbackValueNode) {
211
+ let objectName;
212
+ let propertyName;
213
+ let fallbackValue;
214
+ if (fallbackValueNode.object.type === 'Identifier') {
215
+ objectName = fallbackValueNode.object.name;
216
+ }
217
+ if (fallbackValueNode.property.type === 'Identifier') {
218
+ propertyName = fallbackValueNode.property.name;
219
+ } else if (fallbackValueNode.property.type === 'Literal' && typeof fallbackValueNode.property.value === 'string') {
220
+ propertyName = fallbackValueNode.property.value;
221
+ }
222
+ if (!objectName || !propertyName) {
223
+ this.logError(`Could not determine object and property names from member expression: ${chalk.yellow(fallbackValueNode)}`);
224
+ return {
225
+ rawFallbackValue: '',
226
+ fallbackValue,
227
+ resolvedImportDeclaration: undefined,
228
+ resolvedLocalVarDeclaration: undefined
229
+ };
230
+ }
231
+ const rawFallbackValue = `${objectName}.${propertyName}`;
232
+ this.logVerbose(`Fallback is a member expression: ${chalk.yellow(rawFallbackValue)}, attempting to resolve...`);
233
+ let resolvedImportDeclaration;
234
+
235
+ // Find the import statement for the object
236
+ const importDeclaration = this.source.find(this.j.ImportDeclaration).filter(this.createImportFilter(objectName)).at(0);
237
+ if (importDeclaration.size()) {
238
+ const importSource = importDeclaration.get().value.source.value;
239
+ fallbackValue = await this.resolveValueFromImport(this.rootDir, importSource, objectName, propertyName);
240
+ if (fallbackValue !== undefined) {
241
+ resolvedImportDeclaration = importDeclaration.paths()[0];
242
+ this.logVerbose(`Resolved fallback value from member expression: ${chalk.yellow(fallbackValue)}`);
243
+ }
244
+ } else {
245
+ this.logError(`Could not find import for member expression: ${chalk.yellow(rawFallbackValue)}`);
246
+ }
247
+ return {
248
+ rawFallbackValue,
249
+ fallbackValue,
250
+ resolvedImportDeclaration,
251
+ resolvedLocalVarDeclaration: undefined
252
+ };
253
+ }
254
+ async processFallbackAsTemplateLiteral(fallbackValueNode) {
255
+ const expressions = fallbackValueNode.expressions;
256
+ let rawFallbackValue = '';
257
+ let fallbackValue;
258
+ const quasis = fallbackValueNode.quasis;
259
+ if (expressions.length !== 1 || quasis.length !== 2) {
260
+ this.logError(`Unsupported template literal structure`);
261
+ return {
262
+ rawFallbackValue,
263
+ fallbackValue,
264
+ resolvedImportDeclaration: undefined,
265
+ resolvedLocalVarDeclaration: undefined
266
+ };
267
+ }
268
+ let exprValue;
269
+ const expression = expressions[0];
270
+ let resolvedImportDeclaration;
271
+ let resolvedLocalVarDeclaration;
272
+ if (expression.type === 'Identifier') {
273
+ const result = await this.processFallbackAsIdentifier(expression);
274
+ exprValue = result.fallbackValue;
275
+ resolvedImportDeclaration = result.resolvedImportDeclaration;
276
+ resolvedLocalVarDeclaration = result.resolvedLocalVarDeclaration;
277
+ } else if (expression.type === 'MemberExpression') {
278
+ const result = await this.processFallbackAsMemberExpression(expression);
279
+ exprValue = result.fallbackValue;
280
+ resolvedImportDeclaration = result.resolvedImportDeclaration;
281
+ }
282
+ if (exprValue !== undefined) {
283
+ rawFallbackValue = `${quasis[0].value.raw}\${${exprValue}}${quasis[1].value.raw}`;
284
+ fallbackValue = `${quasis[0].value.cooked}${exprValue}${quasis[1].value.cooked}`;
285
+ this.logVerbose(`Resolved fallback value from template literal: ${chalk.yellow(fallbackValue)}`);
286
+ }
287
+ return {
288
+ rawFallbackValue,
289
+ fallbackValue,
290
+ resolvedImportDeclaration,
291
+ resolvedLocalVarDeclaration
292
+ };
293
+ }
294
+ tryResolveModulePath(moduleName) {
295
+ try {
296
+ const resolvedPath = require.resolve(moduleName, {
297
+ paths: [this.rootDir]
298
+ });
299
+ this.logVerbose(`Resolved module path: ${chalk.green(resolvedPath)} for ${chalk.cyan(moduleName)}`);
300
+ return resolvedPath;
301
+ } catch (error) {
302
+ return null;
303
+ }
304
+ }
305
+ async tryResolveLocalPath(currentDir, importPath) {
306
+ for (const ext of this.possibleExtensions) {
307
+ const potentialPath = path.resolve(currentDir, `${importPath}${ext}`);
308
+ try {
309
+ await fs.access(potentialPath);
310
+ this.logVerbose(`Resolved file path locally: ${chalk.green(potentialPath)}`);
311
+ return potentialPath;
312
+ } catch {
313
+ // Continue if the file is not found
314
+ }
315
+ }
316
+ return null;
317
+ }
318
+ async resolveValueFromImport(currentDir, importPath, objectName, propertyOrVariableName) {
319
+ let filePath = this.tryResolveModulePath(importPath);
320
+ if (!filePath) {
321
+ filePath = await this.tryResolveLocalPath(currentDir, importPath);
322
+ }
323
+ if (!filePath) {
324
+ this.logError(`File not found for import path: ${chalk.cyan(importPath)} in directory: ${chalk.blue(this.rootDir)}`);
325
+ return undefined;
326
+ }
327
+ this.logVerbose(`Reading file: ${chalk.green(filePath)}`);
328
+ const fileContent = await fs.readFile(filePath, 'utf-8');
329
+ const source = this.j(fileContent);
330
+ if (objectName) {
331
+ // Check if the object is imported from another module
332
+ const imports = source.find(this.j.ImportDeclaration);
333
+ const matchingImport = imports.filter(this.createImportFilter(objectName)).at(0);
334
+ if (matchingImport.size()) {
335
+ var _importDecl$source$va;
336
+ const importDecl = matchingImport.get().node;
337
+ const newImportPath = (_importDecl$source$va = importDecl.source.value) === null || _importDecl$source$va === void 0 ? void 0 : _importDecl$source$va.toString();
338
+ return this.resolveValueFromImport(path.dirname(filePath), newImportPath, objectName, propertyOrVariableName);
339
+ }
340
+ }
341
+ // If not imported, check for variable declaration
342
+ const varDeclaration = source.find(this.j.VariableDeclarator, {
343
+ id: {
344
+ name: propertyOrVariableName
345
+ }
346
+ }).at(0);
347
+ if (!varDeclaration.size()) {
348
+ this.logError(`Variable declaration not found for ${chalk.yellow(propertyOrVariableName)} in file: ${chalk.green(filePath)}`);
349
+ return undefined;
350
+ }
351
+ const init = varDeclaration.get().value.init;
352
+ if (init.type === 'Literal' || init.type === 'StringLiteral' || init.type === 'NumericLiteral') {
353
+ return init.value;
354
+ } else {
355
+ this.logError(`Unhandled init type ${init.type} for variable: ${chalk.yellow(propertyOrVariableName)} in file: ${chalk.green(filePath)}`);
356
+ return undefined;
357
+ }
358
+ }
359
+ createImportFilter(targetName) {
360
+ return path => {
361
+ var _path$node$specifiers;
362
+ return ((_path$node$specifiers = path.node.specifiers) === null || _path$node$specifiers === void 0 ? void 0 : _path$node$specifiers.some(specifier => {
363
+ var _specifier$local, _specifier$local2, _specifier$local3;
364
+ switch (specifier.type) {
365
+ case 'ImportNamespaceSpecifier':
366
+ return ((_specifier$local = specifier.local) === null || _specifier$local === void 0 ? void 0 : _specifier$local.name) === targetName;
367
+ case 'ImportDefaultSpecifier':
368
+ return ((_specifier$local2 = specifier.local) === null || _specifier$local2 === void 0 ? void 0 : _specifier$local2.name) === targetName;
369
+ case 'ImportSpecifier':
370
+ return ((_specifier$local3 = specifier.local) === null || _specifier$local3 === void 0 ? void 0 : _specifier$local3.name) === targetName || specifier.imported.name === targetName;
371
+ default:
372
+ return false;
373
+ }
374
+ })) === true;
375
+ };
376
+ }
377
+ }
@@ -0,0 +1,46 @@
1
+ export function addOrUpdateEslintIgnoreComment(j, tokenValue, fallbackValue, callPath) {
2
+ const commentText = `eslint-disable-next-line @atlaskit/design-system/no-unsafe-design-token-usage -- The token value "${tokenValue}" and fallback "${fallbackValue}" do not match and can't be replaced automatically.`;
3
+ // first see if we can add the comment to the parent object property
4
+ const updatedCommentInObjectExpression = addOrUpdateEslintIgnoreCommentForParentInObjectExpression(j, commentText, callPath);
5
+ // if the comment was not added to the parent object property, add it to the node itself
6
+ if (!updatedCommentInObjectExpression) {
7
+ addOrUpdateEslintIgnoreCommentForNodeItself(j, commentText, callPath);
8
+ }
9
+ }
10
+ function addOrUpdateEslintIgnoreCommentForNodeItself(j, commentText, callPath) {
11
+ const leadingComments = callPath.node.leadingComments || [];
12
+ const existingCommentIndex = leadingComments.findIndex(comment => comment.value.includes('eslint-disable-next-line @atlaskit/design-system/no-unsafe-design-token-usage'));
13
+ const commentLine = j.commentLine(commentText, true);
14
+ if (existingCommentIndex !== -1) {
15
+ // Replace the existing comment
16
+ // Note: in order to modify the comment, it's fine to update it in the `leadingComments` array
17
+ leadingComments[existingCommentIndex] = commentLine;
18
+ } else {
19
+ // Add a new comment if none exists
20
+ // Note: Adding new comment to 'leadingComments' doesn't affect anything, we need to add it to 'comments' property
21
+ callPath.node.comments = [commentLine];
22
+ }
23
+ }
24
+ function addOrUpdateEslintIgnoreCommentForParentInObjectExpression(j, commentText, callPath) {
25
+ const parent = callPath.parentPath;
26
+ // Check if the parent node is an ObjectProperty
27
+ if (parent && parent.node.type === 'ObjectProperty') {
28
+ const grandparent = parent.parentPath;
29
+ // Check if the grandparent is an ObjectExpression
30
+ if (grandparent && grandparent.node.type === 'ObjectExpression') {
31
+ // Check for existing leading comments
32
+ const leadingComments = parent.node.leadingComments || [];
33
+ const existingCommentIndex = leadingComments.findIndex(comment => comment.value.includes('eslint-disable-next-line @atlaskit/design-system/no-unsafe-design-token-usage'));
34
+ const commentLine = j.commentLine(commentText, true);
35
+ if (existingCommentIndex !== -1) {
36
+ // Replace the existing comment
37
+ leadingComments[existingCommentIndex] = commentLine;
38
+ } else {
39
+ // Add a new comment if none exists
40
+ parent.node.comments = [commentLine, ...(parent.node.comments || [])];
41
+ }
42
+ return true;
43
+ }
44
+ }
45
+ return false;
46
+ }