@atlaskit/eslint-plugin-design-system
Version:
The essential plugin for use with the Atlassian Design System.
239 lines (227 loc) • 9.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _eslintCodemodUtils = require("eslint-codemod-utils");
var _createLintRule = require("../utils/create-lint-rule");
var _errorBoundary = require("../utils/error-boundary");
var _isImportFromPackage = require("../utils/is-import-from-package");
var rule = (0, _createLintRule.createLintRule)({
meta: {
name: 'ensure-proper-xcss-usage',
docs: {
description: 'Enforces proper xcss usage: migrate from xcss() to cssMap() and use cssMap objects with specific keys.',
recommended: false,
severity: 'error'
},
messages: {
missingCssMapKey: 'xcss prop should use a specific key from cssMap (e.g., {{identifier}}.root) instead of the entire cssMap object.',
noXcssWithCompiled: 'Cannot use `xcss()` function with `@atlaskit/primitives/compiled`. Use `cssMap()` from `@atlaskit/css or `@compiled/react` instead.'
}
},
create: function create(context) {
var tracker = {
// Components tracking
compiledComponents: new Set(),
// Function tracking
cssMapFunction: new Set(),
xcssFunction: new Set(),
// Variables tracking
cssMapVariables: new Map(),
xcssVariables: new Set()
};
// Store potential violations to check after all declarations are collected
var potentialViolations = [];
return (0, _errorBoundary.errorBoundary)({
// Track all imports in a single handler
ImportDeclaration: function ImportDeclaration(node) {
var source = node.source.value;
node.specifiers.forEach(function (specifier) {
if (specifier.type !== 'ImportSpecifier') {
return;
}
// Handle different import sources
if ((0, _isImportFromPackage.isImportFromPackage)(source, '@atlaskit/primitives/compiled') && specifier.imported.type === 'Identifier') {
// Track the local name (alias), not the imported name
// e.g., import { Box as CompiledBox } -> track "CompiledBox"
tracker.compiledComponents.add(specifier.local.name);
return;
}
if ((source === '@atlaskit/primitives' || source === '@atlaskit/primitives/xcss') && specifier.imported.type === 'Identifier' && specifier.imported.name === 'xcss') {
tracker.xcssFunction.add(specifier.local.name);
return;
}
if ((source === '@atlaskit/css' || source === '@compiled/react') && specifier.imported.type === 'Identifier' && specifier.imported.name === 'cssMap') {
tracker.cssMapFunction.add(specifier.local.name);
}
});
},
// Track variable declarations
VariableDeclarator: function VariableDeclarator(node) {
if (!node.init || node.init.type !== 'CallExpression' || node.init.callee.type !== 'Identifier' || node.id.type !== 'Identifier') {
return;
}
var calleeName = node.init.callee.name;
var variableName = node.id.name;
// Track cssMap variables and extract their keys
if (tracker.cssMapFunction.has(calleeName)) {
var keys = new Set();
// Extract keys from the cssMap object argument
if (node.init.arguments.length > 0 && node.init.arguments[0].type === 'ObjectExpression') {
node.init.arguments[0].properties.forEach(function (prop) {
if (prop.type === 'Property' && prop.key.type === 'Identifier') {
keys.add(prop.key.name);
}
});
}
tracker.cssMapVariables.set(variableName, keys);
// Check if this variable was used before it was declared
var violationsToRemove = [];
potentialViolations.forEach(function (violation, index) {
if (violation.identifierName === variableName) {
if (violation.type === 'identifier') {
// Identifier usage of cssMap variable is invalid (e.g., styles instead of styles.root)
context.report({
node: violation.node,
messageId: 'missingCssMapKey',
data: {
identifier: variableName
}
});
violationsToRemove.push(index);
}
// Member expressions with cssMap are valid (e.g., styles.root) - just remove from list
// No need to report or keep in list
if (violation.type === 'memberExpression') {
violationsToRemove.push(index);
}
}
});
// Remove violations in reverse order to maintain indices
violationsToRemove.reverse().forEach(function (index) {
potentialViolations.splice(index, 1);
});
}
// Track xcss variables
if (tracker.xcssFunction.has(calleeName)) {
tracker.xcssVariables.add(variableName);
// Check if this variable was used before it was declared
var _violationsToRemove = [];
potentialViolations.forEach(function (violation, index) {
if (violation.identifierName === variableName) {
context.report({
node: violation.node,
messageId: 'noXcssWithCompiled'
});
_violationsToRemove.push(index);
}
});
// Remove violations in reverse order to maintain indices
_violationsToRemove.reverse().forEach(function (index) {
potentialViolations.splice(index, 1);
});
}
},
// Check JSX elements for xcss prop usage
JSXElement: function JSXElement(node) {
var _xcssAttribute$value;
if (!(0, _eslintCodemodUtils.isNodeOfType)(node, 'JSXElement')) {
return;
}
var elementName = node.openingElement.name;
if (elementName.type !== 'JSXIdentifier') {
return;
}
var componentName = elementName.name;
if (!tracker.compiledComponents.has(componentName)) {
return;
}
// Find xcss attribute
var xcssAttribute = node.openingElement.attributes.find(function (attr) {
return attr.type === 'JSXAttribute' && attr.name.name === 'xcss';
});
if ((xcssAttribute === null || xcssAttribute === void 0 || (_xcssAttribute$value = xcssAttribute.value) === null || _xcssAttribute$value === void 0 ? void 0 : _xcssAttribute$value.type) !== 'JSXExpressionContainer') {
return;
}
var expression = xcssAttribute.value.expression;
// Check for direct xcss function calls
if (expression.type === 'CallExpression' && expression.callee.type === 'Identifier' && tracker.xcssFunction.has(expression.callee.name)) {
context.report({
node: expression,
messageId: 'noXcssWithCompiled'
});
return;
}
// Check for variables
if (expression.type === 'Identifier') {
var identifierName = expression.name;
if (tracker.xcssVariables.has(identifierName)) {
context.report({
node: expression,
messageId: 'noXcssWithCompiled'
});
} else if (tracker.cssMapVariables.has(identifierName)) {
context.report({
node: expression,
messageId: 'missingCssMapKey',
data: {
identifier: identifierName
}
});
} else {
// Variable not yet declared - store for later check
potentialViolations.push({
node: expression,
identifierName: identifierName,
type: 'identifier'
});
}
return;
}
// Check member expressions (e.g., styles.root)
if (expression.type === 'MemberExpression' && expression.object.type === 'Identifier') {
var objectName = expression.object.name;
if (tracker.xcssVariables.has(objectName)) {
context.report({
node: expression,
messageId: 'noXcssWithCompiled'
});
} else if (!tracker.cssMapVariables.has(objectName)) {
// Variable not yet declared - store for later check
// Note: If it's already a cssMap variable, member expressions are valid (e.g., styles.root)
potentialViolations.push({
node: expression,
identifierName: objectName,
type: 'memberExpression'
});
}
// If it's a cssMap variable, member expressions are valid - no action needed
}
},
// Final check after all declarations are processed
'Program:exit': function ProgramExit() {
// Check remaining potential violations against all collected declarations
potentialViolations.forEach(function (violation) {
if (tracker.xcssVariables.has(violation.identifierName)) {
context.report({
node: violation.node,
messageId: 'noXcssWithCompiled'
});
} else if (tracker.cssMapVariables.has(violation.identifierName) && violation.type === 'identifier') {
// Only report cssMap violations for identifier usage (not member expressions)
// Member expressions like stylesMap.root are valid
context.report({
node: violation.node,
messageId: 'missingCssMapKey',
data: {
identifier: violation.identifierName
}
});
}
});
}
});
}
});
var _default = exports.default = rule;