UNPKG

@atlaskit/eslint-plugin-design-system

Version:

The essential plugin for use with the Atlassian Design System.

163 lines (161 loc) 6.44 kB
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 }; };