UNPKG

@atlaskit/eslint-plugin-ui-styling-standard

Version:

The eslint plugin to enforce and educate on Atlassian's UI Styling Standard

253 lines (252 loc) • 9.3 kB
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray"; import _slicedToArray from "@babel/runtime/helpers/slicedToArray"; import _regeneratorRuntime from "@babel/runtime/regenerator"; import { identifier, importDeclaration, importSpecifier, literal } from 'eslint-codemod-utils'; import { getAllComments } from '@atlaskit/eslint-utils/context-compat'; import { findVariable } from '@atlaskit/eslint-utils/find-variable'; import { createLintRule } from '../utils/create-rule'; import { isImportDefaultSpecifier } from './is-import-default-specifier'; import { isImportNamespaceSpecifier } from './is-import-namespace-specifier'; import { isImportSpecifier } from './is-import-specifier'; import { isSafeStyled } from './is-safe-styled'; import { isSafeUsage } from './is-safe-usage'; import { getFirstImportFromSource } from './utils'; var SUPPORTED_EMOTION_IMPORTS = new Set(['css', 'keyframes', 'ClassNames', 'jsx']); var SUPPORTED_IMPORT_MAP = { '@emotion/core': SUPPORTED_EMOTION_IMPORTS, '@emotion/react': SUPPORTED_EMOTION_IMPORTS, '@emotion/styled': new Set(['default']), 'styled-components': new Set(['default', 'css', 'keyframes']) }; function isSupportedImportSource(importSource) { return importSource in SUPPORTED_IMPORT_MAP; } var schema = { type: 'array', items: [{ type: 'object', properties: { canAutoFix: { type: 'boolean' } }, additionalProperties: false }] }; var defaultConfig = { canAutoFix: false }; function readConfig(context) { var _ref = context.options, _ref2 = _slicedToArray(_ref, 1), config = _ref2[0]; return Object.assign({}, defaultConfig, config); } var rule = createLintRule({ meta: { name: 'use-compiled', type: 'problem', fixable: 'code', docs: { description: 'Ensures usage of `@compiled/react` or `@atlaskit/css` instead of other CSS-in-JS libraries', recommended: true, severity: 'error' }, messages: { 'use-compiled': "Migrate '{{importSource}}' styling to @atlaskit/css or @compiled/react, updating imports and usages together. Verify the component rather than mixing styling engines on it." }, schema: schema }, create: function create(context) { var config = readConfig(context); return { Program: function Program() { var pragma = getAllComments(context).find(function (n) { return n.value.includes('@jsxImportSource @emotion/react'); }); if (!pragma) { return; } context.report({ messageId: 'use-compiled', data: { importSource: '@emotion/react' }, loc: pragma.loc, fix: /*#__PURE__*/_regeneratorRuntime.mark(function fix(fixer) { var _context$sourceCode; var importDeclarations, hasEmotionImport, hasCompiledImport; return _regeneratorRuntime.wrap(function (_context) { while (1) switch (_context.prev = _context.next) { case 0: if (config.canAutoFix) { _context.next = 1; break; } return _context.abrupt("return"); case 1: // @ts-ignore - Jira's ESLint v10 types expose sourceCode, platform still checks with ESLint v9. importDeclarations = ((_context$sourceCode = context.sourceCode) !== null && _context$sourceCode !== void 0 ? _context$sourceCode : context.getSourceCode()).ast.body.filter(function (node) { return node.type === 'ImportDeclaration'; }); hasEmotionImport = importDeclarations.some(function (importDeclaration) { var importSource = importDeclaration.source.value; return importSource.startsWith('@emotion/'); }); hasCompiledImport = importDeclarations.some(function (importDeclaration) { return importDeclaration.source.value === '@compiled/react'; }); if (!(hasEmotionImport || !hasCompiledImport)) { _context.next = 2; break; } return _context.abrupt("return"); case 2: _context.next = 3; return fixer.replaceText( // TODO: JFP-2823 - this type cast was added due to Jira's ESLint v9 migration // @ts-ignore - types don't accept comment nodes pragma, "/** @jsxImportSource @compiled/react */"); case 3: case "end": return _context.stop(); } }, fix); }) }); }, ImportDeclaration: function ImportDeclaration(node) { var importSource = node.source.value; if (!isSupportedImportSource(importSource)) { return; } context.report({ node: node.source, messageId: 'use-compiled', data: { importSource: importSource }, fix: /*#__PURE__*/_regeneratorRuntime.mark(function fix(fixer) { var specifiers; return _regeneratorRuntime.wrap(function (_context2) { while (1) switch (_context2.prev = _context2.next) { case 0: if (config.canAutoFix) { _context2.next = 1; break; } return _context2.abrupt("return"); case 1: specifiers = getSpecifiers({ context: context, node: node, importSource: importSource }); if (specifiers) { _context2.next = 2; break; } return _context2.abrupt("return"); case 2: return _context2.delegateYield(fixImport({ context: context, fixer: fixer, node: node, specifiers: specifiers }), "t0", 3); case 3: case "end": return _context2.stop(); } }, fix); }) }); } }; } }); export default rule; function getSpecifiers(_ref3) { var context = _ref3.context, node = _ref3.node, importSource = _ref3.importSource; var supportedImports = SUPPORTED_IMPORT_MAP[importSource]; // We aren't supporting namespace imports if (node.specifiers.some(isImportNamespaceSpecifier)) { return; } var defaultSpecifier = node.specifiers.find(isImportDefaultSpecifier); if (defaultSpecifier) { var _context$sourceCode2; if (!supportedImports.has('default')) { return; } var styled = findVariable({ identifier: defaultSpecifier.local, // @ts-ignore - Jira's ESLint v10 types expose sourceCode, platform still checks with ESLint v9. sourceCode: (_context$sourceCode2 = context.sourceCode) !== null && _context$sourceCode2 !== void 0 ? _context$sourceCode2 : context.getSourceCode() }); if (!styled || !isSafeStyled(styled)) { return; } } var namedSpecifiers = node.specifiers.filter(isImportSpecifier); if (!namedSpecifiers.every(function (specifier) { return isSafeUsage({ context: context, specifier: specifier, supportedImports: supportedImports }); })) { return; } if (defaultSpecifier) { return [].concat(_toConsumableArray(namedSpecifiers), [ // ASSUMPTION: the default import, if allowed, is for `styled` importSpecifier({ imported: identifier('styled'), local: node.specifiers[0].local })]); } return namedSpecifiers; } function fixImport(_ref4) { var context = _ref4.context, fixer = _ref4.fixer, node = _ref4.node, specifiers = _ref4.specifiers; return /*#__PURE__*/_regeneratorRuntime.mark(function _callee() { var compiledImport; return _regeneratorRuntime.wrap(function (_context3) { while (1) switch (_context3.prev = _context3.next) { case 0: compiledImport = getFirstImportFromSource(context, '@compiled/react'); if (!compiledImport) { _context3.next = 3; break; } _context3.next = 1; return fixer.remove(node); case 1: _context3.next = 2; return fixer.replaceText(compiledImport, buildImportDeclaration(compiledImport.specifiers.concat(specifiers), '@compiled/react')); case 2: _context3.next = 4; break; case 3: _context3.next = 4; return fixer.replaceText(node, buildImportDeclaration(specifiers, '@compiled/react')); case 4: case "end": return _context3.stop(); } }, _callee); })(); } function buildImportDeclaration(specifiers, source) { // @ts-ignore - this needs to be `ts-ignore` because this only errors in jira's typechecking due to update, not platform's return "".concat(importDeclaration({ specifiers: specifiers, source: literal(source) }), ";"); }