@atlaskit/codemod-cli 0.27.2 → 0.27.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/cjs/main.js +85 -33
- package/dist/cjs/presets/index.js +2 -1
- package/dist/cjs/presets/remove-token-fallbacks/remove-token-fallbacks.js +225 -0
- package/dist/cjs/presets/remove-token-fallbacks/types.js +5 -0
- package/dist/cjs/presets/remove-token-fallbacks/utils/all-tokens.js +44 -0
- package/dist/cjs/presets/remove-token-fallbacks/utils/color-utils.js +93 -0
- package/dist/cjs/presets/remove-token-fallbacks/utils/get-team-info.js +51 -0
- package/dist/cjs/presets/remove-token-fallbacks/utils/normalize-values.js +113 -0
- package/dist/cjs/presets/remove-token-fallbacks/utils/remove-unused-imports.js +61 -0
- package/dist/cjs/presets/remove-token-fallbacks/utils/remove-unused-variables.js +37 -0
- package/dist/cjs/presets/remove-token-fallbacks/utils/reporter.js +310 -0
- package/dist/cjs/presets/remove-token-fallbacks/utils/token-processor.js +632 -0
- package/dist/cjs/presets/remove-token-fallbacks/utils/update-comments.js +58 -0
- package/dist/es2019/main.js +24 -0
- package/dist/es2019/presets/index.js +2 -1
- package/dist/es2019/presets/remove-token-fallbacks/remove-token-fallbacks.js +130 -0
- package/dist/es2019/presets/remove-token-fallbacks/types.js +1 -0
- package/dist/es2019/presets/remove-token-fallbacks/utils/all-tokens.js +30 -0
- package/dist/es2019/presets/remove-token-fallbacks/utils/color-utils.js +84 -0
- package/dist/es2019/presets/remove-token-fallbacks/utils/get-team-info.js +22 -0
- package/dist/es2019/presets/remove-token-fallbacks/utils/normalize-values.js +104 -0
- package/dist/es2019/presets/remove-token-fallbacks/utils/remove-unused-imports.js +51 -0
- package/dist/es2019/presets/remove-token-fallbacks/utils/remove-unused-variables.js +31 -0
- package/dist/es2019/presets/remove-token-fallbacks/utils/reporter.js +118 -0
- package/dist/es2019/presets/remove-token-fallbacks/utils/token-processor.js +377 -0
- package/dist/es2019/presets/remove-token-fallbacks/utils/update-comments.js +46 -0
- package/dist/esm/main.js +85 -33
- package/dist/esm/presets/index.js +2 -1
- package/dist/esm/presets/remove-token-fallbacks/remove-token-fallbacks.js +215 -0
- package/dist/esm/presets/remove-token-fallbacks/types.js +1 -0
- package/dist/esm/presets/remove-token-fallbacks/utils/all-tokens.js +38 -0
- package/dist/esm/presets/remove-token-fallbacks/utils/color-utils.js +86 -0
- package/dist/esm/presets/remove-token-fallbacks/utils/get-team-info.js +44 -0
- package/dist/esm/presets/remove-token-fallbacks/utils/normalize-values.js +107 -0
- package/dist/esm/presets/remove-token-fallbacks/utils/remove-unused-imports.js +55 -0
- package/dist/esm/presets/remove-token-fallbacks/utils/remove-unused-variables.js +31 -0
- package/dist/esm/presets/remove-token-fallbacks/utils/reporter.js +302 -0
- package/dist/esm/presets/remove-token-fallbacks/utils/token-processor.js +625 -0
- package/dist/esm/presets/remove-token-fallbacks/utils/update-comments.js +51 -0
- package/dist/types/presets/index.d.ts +1 -0
- package/dist/types/presets/remove-token-fallbacks/remove-token-fallbacks.d.ts +29 -0
- package/dist/types/presets/remove-token-fallbacks/types.d.ts +39 -0
- package/dist/types/presets/remove-token-fallbacks/utils/all-tokens.d.ts +1 -0
- package/dist/types/presets/remove-token-fallbacks/utils/color-utils.d.ts +3 -0
- package/dist/types/presets/remove-token-fallbacks/utils/get-team-info.d.ts +8 -0
- package/dist/types/presets/remove-token-fallbacks/utils/normalize-values.d.ts +8 -0
- package/dist/types/presets/remove-token-fallbacks/utils/remove-unused-imports.d.ts +2 -0
- package/dist/types/presets/remove-token-fallbacks/utils/remove-unused-variables.d.ts +2 -0
- package/dist/types/presets/remove-token-fallbacks/utils/reporter.d.ts +4 -0
- package/dist/types/presets/remove-token-fallbacks/utils/token-processor.d.ts +30 -0
- package/dist/types/presets/remove-token-fallbacks/utils/update-comments.d.ts +2 -0
- package/dist/types-ts4.5/presets/index.d.ts +1 -0
- package/dist/types-ts4.5/presets/remove-token-fallbacks/remove-token-fallbacks.d.ts +29 -0
- package/dist/types-ts4.5/presets/remove-token-fallbacks/types.d.ts +39 -0
- package/dist/types-ts4.5/presets/remove-token-fallbacks/utils/all-tokens.d.ts +1 -0
- package/dist/types-ts4.5/presets/remove-token-fallbacks/utils/color-utils.d.ts +3 -0
- package/dist/types-ts4.5/presets/remove-token-fallbacks/utils/get-team-info.d.ts +8 -0
- package/dist/types-ts4.5/presets/remove-token-fallbacks/utils/normalize-values.d.ts +8 -0
- package/dist/types-ts4.5/presets/remove-token-fallbacks/utils/remove-unused-imports.d.ts +2 -0
- package/dist/types-ts4.5/presets/remove-token-fallbacks/utils/remove-unused-variables.d.ts +2 -0
- package/dist/types-ts4.5/presets/remove-token-fallbacks/utils/reporter.d.ts +4 -0
- package/dist/types-ts4.5/presets/remove-token-fallbacks/utils/token-processor.d.ts +30 -0
- package/dist/types-ts4.5/presets/remove-token-fallbacks/utils/update-comments.d.ts +2 -0
- package/package.json +8 -3
|
@@ -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
|
+
}
|