@atlaskit/eslint-plugin-design-system
Version:
The essential plugin for use with the Atlassian Design System.
163 lines (161 loc) • 6.44 kB
JavaScript
import { isNodeOfType, literal } from 'eslint-codemod-utils';
import coreIconLabMetadata from '@atlaskit/icon-lab/metadata';
import coreIconMetadata from '@atlaskit/icon/metadata-core';
import { pathWithCustomMessageId } from '../constants';
/**
* __Deprecation icon handler__
*
* A deprecation icon handler which is responsible for displaying an error for deprecated icons.
* It also includes a fixer to replace the deprecated icon with the new icon if a replacement exists.
*/
export const getDeprecationIconHandler = context => {
const jsxElements = new Map();
const identifiers = new Map();
const importErrors = {};
const exportErrors = {};
const iconNameToImportSource = new Map();
const getConfigFlag = (key, defaultValue) => {
if (context.options && context.options.length > 0 && context.options[0] && context.options[0].hasOwnProperty(key)) {
return context.options[0][key] === !defaultValue ? !defaultValue : defaultValue;
}
return defaultValue;
};
const getIconComponentName = name => {
return name.split(/\W/).map(part => `${part[0].toUpperCase()}${part.slice(1)}`).join('').concat('Icon');
};
const createImportError = ({
node,
importSource,
config
}) => {
if (config.message) {
var _node$specifiers$, _node$specifiers$$loc;
const myError = {
node,
messageId: pathWithCustomMessageId,
data: {
importSource,
customMessage: config.message,
unfixable: config.unfixable ? 'true' : 'false'
}
};
importErrors[node.source.value] = myError;
const importIconName = (_node$specifiers$ = node.specifiers[0]) === null || _node$specifiers$ === void 0 ? void 0 : (_node$specifiers$$loc = _node$specifiers$.local) === null || _node$specifiers$$loc === void 0 ? void 0 : _node$specifiers$$loc.name;
if (importIconName) {
iconNameToImportSource.set(importIconName, importSource);
}
}
};
const createExportError = ({
node,
importSource,
config
}) => {
if (config.message) {
const myError = {
node,
messageId: pathWithCustomMessageId,
data: {
importSource,
customMessage: config.message
}
};
exportErrors[importSource] = myError;
}
};
const throwErrors = () => {
const shouldTurnOffAutoFixer = getConfigFlag('turnOffAutoFixer', false);
for (const [importSource, error] of Object.entries(importErrors)) {
if (importSource.includes('/migration/')) {
var _coreIconMetadata$dep, _error$data;
const [_location, _, _migration, name] = importSource.split('/').slice(1);
const [deprecatedIconName, legacyIconName] = name.split('--');
const replacement = (_coreIconMetadata$dep = coreIconMetadata[deprecatedIconName]) === null || _coreIconMetadata$dep === void 0 ? void 0 : _coreIconMetadata$dep.replacement;
if (replacement && ((_error$data = error.data) === null || _error$data === void 0 ? void 0 : _error$data.unfixable) === 'false') {
const newIconName = getIconComponentName(replacement.name);
if (!shouldTurnOffAutoFixer) {
addAutoFix(error, importSource, `${replacement.location}/core/migration/${replacement.name}--${legacyIconName}`, newIconName);
}
}
} else {
var _metadata, _metadata$name;
const [location, _, name] = importSource.split('/').slice(1);
let metadata;
if (location === 'icon') {
metadata = coreIconMetadata;
} else if (location === 'icon-lab') {
metadata = coreIconLabMetadata;
}
const replacement = (_metadata = metadata) === null || _metadata === void 0 ? void 0 : (_metadata$name = _metadata[name]) === null || _metadata$name === void 0 ? void 0 : _metadata$name.replacement;
if (replacement) {
const newIconName = getIconComponentName(replacement.name);
if (!shouldTurnOffAutoFixer) {
addAutoFix(error, importSource, `${replacement.location}/core/${replacement.name}`, newIconName);
}
}
}
context.report(error);
}
for (const error of Object.values(exportErrors)) {
context.report(error);
}
};
const addAutoFix = (error, importSource, newImportSource, newIconName) => {
error.fix = fixer => {
const fixes = [];
//Find and update all usages of this icon in JSX with the replacement icon
const jsxUsageNodes = jsxElements.get(importSource);
if (jsxUsageNodes) {
for (const usageNode of jsxUsageNodes) {
fixes.push(fixer.replaceText(usageNode.openingElement.name, newIconName));
}
}
//Find and update all usages of this icon in identifiers with the replacement icon
const usageNodes = identifiers.get(importSource);
if (usageNodes) {
for (const usageNode of usageNodes) {
fixes.push(fixer.replaceText(usageNode.parent, `{${newIconName}}`));
}
}
fixes.push(fixer.replaceText(error.node, `${literal(`import ${newIconName} from '${newImportSource}'`)};`));
return fixes;
};
};
const checkJSXElement = node => {
if (!('openingElement' in node) || !isNodeOfType(node.openingElement.name, 'JSXIdentifier')) {
return;
}
const iconName = node.openingElement.name.name;
const importSource = iconNameToImportSource.get(iconName);
if (importSource) {
if (jsxElements.has(importSource)) {
var _jsxElements$get;
(_jsxElements$get = jsxElements.get(importSource)) === null || _jsxElements$get === void 0 ? void 0 : _jsxElements$get.push(node);
} else {
jsxElements.set(importSource, [node]);
}
}
};
const checkIdentifier = node => {
if (node.type !== 'Identifier' || node.parent.type !== 'JSXExpressionContainer') {
return;
}
const iconName = node.name;
const importSource = iconNameToImportSource.get(iconName);
if (importSource) {
if (identifiers.has(importSource)) {
var _identifiers$get;
(_identifiers$get = identifiers.get(importSource)) === null || _identifiers$get === void 0 ? void 0 : _identifiers$get.push(node);
} else {
identifiers.set(importSource, [node]);
}
}
};
return {
createImportError,
createExportError,
checkJSXElement,
checkIdentifier,
throwErrors
};
};