@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
JavaScript
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)
}), ";");
}