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