@atlaskit/eslint-plugin-ui-styling-standard
Version:
The eslint plugin to enforce and educate on Atlassian's UI Styling Standard
192 lines (175 loc) • 6.87 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _contextCompat = require("@atlaskit/eslint-utils/context-compat");
var _isSupportedImport = require("@atlaskit/eslint-utils/is-supported-import");
var _createRule = require("../utils/create-rule");
var IMPORT_SOURCES = [_isSupportedImport.CSS_IN_JS_IMPORTS.compiled, _isSupportedImport.CSS_IN_JS_IMPORTS.atlaskitCss];
/**
* Gets the key name from a property node
*/
function getPropertyKeyName(property) {
if (property.key.type === 'Identifier') {
return property.key.name;
}
if (property.key.type === 'Literal' && typeof property.key.value === 'string') {
return property.key.value;
}
return null;
}
/**
* Checks if a variable is exported from the module
*/
function isExported(node, context) {
var parent = node.parent;
// Walk up to find VariableDeclaration
while (parent && parent.type !== 'VariableDeclaration') {
parent = parent.parent;
}
if (!parent) {
return false;
}
// Check if the VariableDeclaration is an ExportNamedDeclaration
var grandParent = parent.parent;
if (grandParent && grandParent.type === 'ExportNamedDeclaration') {
return true;
}
// Check if the variable is exported elsewhere in the file via `export { styles }`
if (node.id.type === 'Identifier') {
var scope = (0, _contextCompat.getScope)(context, node);
var variableName = node.id.name;
var variable = scope.variables.find(function (v) {
return v.name === variableName;
});
if (variable) {
// Check if any reference is a direct export specifier (e.g., `export { styles }`)
return variable.references.some(function (ref) {
var refParent = ref.identifier.parent;
// Check for direct export: `export { styles }` or `export { styles as something }`
// In this case, the identifier's parent is an ExportSpecifier
if (refParent && refParent.type === 'ExportSpecifier') {
return true;
}
// Check for default export: `export default styles`
if (refParent && refParent.type === 'ExportDefaultDeclaration') {
return true;
}
return false;
});
}
}
return false;
}
var rule = (0, _createRule.createLintRule)({
meta: {
name: 'no-unused-cssmap-properties',
docs: {
description: 'Detects unused properties in cssMap style objects that are not exported. Helps maintain clean code by identifying style variants that are defined but never used.',
recommended: true,
severity: 'warn'
},
messages: {
'unused-property': "The \"{{propertyName}}\" style is never used. Remove it from cssMap, or apply it to the intended element if the style is still required."
},
type: 'problem'
},
create: function create(context) {
return {
VariableDeclarator: function VariableDeclarator(node) {
var _getSourceCode$scopeM;
if (node.type !== 'VariableDeclarator') {
return;
}
// Only process if the initializer is a cssMap call
if (!node.init || node.init.type !== 'CallExpression' || !(0, _isSupportedImport.isCssMap)(node.init.callee, (0, _contextCompat.getScope)(context, node).references, IMPORT_SOURCES)) {
return;
}
// Skip if the cssMap is exported
if (isExported(node, context)) {
return;
}
// Get the cssMap argument (should be an object expression)
var cssMapArg = node.init.arguments[0];
if (!cssMapArg || cssMapArg.type !== 'ObjectExpression') {
return;
}
// Get the variable name
if (node.id.type !== 'Identifier') {
return;
}
var variableName = node.id.name;
// Use getDeclaredVariables to get the variable with all its references
// across all scopes (including nested function components)
var declaredVariables = (_getSourceCode$scopeM = (0, _contextCompat.getSourceCode)(context).scopeManager) === null || _getSourceCode$scopeM === void 0 ? void 0 : _getSourceCode$scopeM.getDeclaredVariables(node);
var variable = declaredVariables === null || declaredVariables === void 0 ? void 0 : declaredVariables.find(function (v) {
return v.name === variableName;
});
if (!variable) {
return;
}
// Track which properties are defined
var definedProperties = new Set();
cssMapArg.properties.forEach(function (prop) {
if (prop.type === 'Property') {
var keyName = getPropertyKeyName(prop);
if (keyName) {
definedProperties.add(keyName);
}
}
});
// Track which properties are used
var usedProperties = new Set();
var hasNonPropertyAccess = false;
variable.references.forEach(function (reference) {
// Skip the declaration itself
if (reference.init) {
return;
}
var identifier = reference.identifier;
var parent = identifier.parent;
// Check if it's a member access (e.g., styles.root)
if (parent && parent.type === 'MemberExpression' && parent.object === identifier) {
// Check if it's computed (dynamic) property access like styles[dynamicKey]
if (parent.computed) {
// Dynamic access - we can't determine which properties are used
hasNonPropertyAccess = true;
return;
}
// Static property access
if (parent.property.type === 'Identifier') {
usedProperties.add(parent.property.name);
} else if (parent.property.type === 'Literal' && typeof parent.property.value === 'string') {
usedProperties.add(parent.property.value);
}
} else {
// The cssMap object is used as a whole (e.g., spread, passed to function, etc.)
// In this case, we can't determine which properties are used, so mark all as used
hasNonPropertyAccess = true;
}
});
// If the object is used as a whole (not just property access), skip checks
if (hasNonPropertyAccess) {
return;
}
// Report unused properties
cssMapArg.properties.forEach(function (prop) {
if (prop.type === 'Property') {
var keyName = getPropertyKeyName(prop);
if (keyName && !usedProperties.has(keyName)) {
context.report({
node: prop.key,
messageId: 'unused-property',
data: {
propertyName: keyName
}
});
}
}
});
}
};
}
});
var _default = exports.default = rule;