UNPKG

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

Version:

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

260 lines (258 loc) • 9.84 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator")); var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray")); var _eslintCodemodUtils = require("eslint-codemod-utils"); var _contextCompat = require("@atlaskit/eslint-utils/context-compat"); var _findVariable = require("@atlaskit/eslint-utils/find-variable"); var _createRule = require("../utils/create-rule"); var _isImportDefaultSpecifier = require("./is-import-default-specifier"); var _isImportNamespaceSpecifier = require("./is-import-namespace-specifier"); var _isImportSpecifier = require("./is-import-specifier"); var _isSafeStyled = require("./is-safe-styled"); var _isSafeUsage = require("./is-safe-usage"); var _utils = require("./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 = (0, _slicedToArray2.default)(_ref, 1), config = _ref2[0]; return Object.assign({}, defaultConfig, config); } var rule = (0, _createRule.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 = (0, _contextCompat.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__*/_regenerator.default.mark(function fix(fixer) { var _context$sourceCode; var importDeclarations, hasEmotionImport, hasCompiledImport; return _regenerator.default.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__*/_regenerator.default.mark(function fix(fixer) { var specifiers; return _regenerator.default.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); }) }); } }; } }); var _default = exports.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.isImportNamespaceSpecifier)) { return; } var defaultSpecifier = node.specifiers.find(_isImportDefaultSpecifier.isImportDefaultSpecifier); if (defaultSpecifier) { var _context$sourceCode2; if (!supportedImports.has('default')) { return; } var styled = (0, _findVariable.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 || !(0, _isSafeStyled.isSafeStyled)(styled)) { return; } } var namedSpecifiers = node.specifiers.filter(_isImportSpecifier.isImportSpecifier); if (!namedSpecifiers.every(function (specifier) { return (0, _isSafeUsage.isSafeUsage)({ context: context, specifier: specifier, supportedImports: supportedImports }); })) { return; } if (defaultSpecifier) { return [].concat((0, _toConsumableArray2.default)(namedSpecifiers), [ // ASSUMPTION: the default import, if allowed, is for `styled` (0, _eslintCodemodUtils.importSpecifier)({ imported: (0, _eslintCodemodUtils.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__*/_regenerator.default.mark(function _callee() { var compiledImport; return _regenerator.default.wrap(function (_context3) { while (1) switch (_context3.prev = _context3.next) { case 0: compiledImport = (0, _utils.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((0, _eslintCodemodUtils.importDeclaration)({ specifiers: specifiers, source: (0, _eslintCodemodUtils.literal)(source) }), ";"); }