@atlaskit/eslint-plugin-design-system
Version:
The essential plugin for use with the Atlassian Design System.
97 lines (96 loc) • 3.65 kB
JavaScript
import { getAllowedFunctionCalls, isAllowListedVariable } from '@atlaskit/eslint-utils/allowed-function-calls';
import { getSourceCode } from '@atlaskit/eslint-utils/context-compat';
import { findVariable } from '@atlaskit/eslint-utils/find-variable';
export class CssMapObjectChecker {
constructor(cssMapObject, context) {
this.allowedFunctionCalls = getAllowedFunctionCalls(context.options);
this.cssMapObject = cssMapObject;
this.report = context.report;
this.context = context;
}
checkCssMapObjectValue(value) {
if (value.type === 'CallExpression' && value.callee.type === 'Identifier') {
// object value is a function call in the style
// {
// key: functionCall(), ...
// }
const variable = findVariable({
identifier: value.callee,
sourceCode: getSourceCode(this.context)
});
if (!variable || !isAllowListedVariable({
allowList: this.allowedFunctionCalls,
variable
})) {
this.report({
node: value,
messageId: 'noFunctionCalls'
});
}
} else if (value.type === 'ArrowFunctionExpression' || value.type === 'FunctionExpression') {
// object value is a function call in the style
// {
// key: (prop) => prop.color, // ArrowFunctionExpression
// get danger() { return { ... } }, // FunctionExpression
// }
this.report({
node: value,
messageId: 'noInlineFunctions'
});
} else if (value.type === 'BinaryExpression' || value.type === 'LogicalExpression') {
// @ts-ignore -- this needs to be `ts-ignore` because this only errors in jira's typechecking due to update, not platform's
this.checkCssMapObjectValue(value.left);
this.checkCssMapObjectValue(value.right);
} else if (value.type === 'Identifier') {
const variable = findVariable({
identifier: value,
sourceCode: getSourceCode(this.context)
});
// Get the variable's definition when initialised, and check the first definition that we find.
//
// Ideally we would try to get the variable's value at the point at which
// cssMap() is run, but ESLint doesn't seem to give us an easy way to
// do that...
const definitions = variable === null || variable === void 0 ? void 0 : variable.defs;
if (!definitions || definitions.length === 0) {
// Variable is not defined :thinking:
return;
}
for (const definition of definitions) {
if (definition.type === 'Variable' && definition.node.init) {
return this.checkCssMapObjectValue(definition.node.init);
}
}
} else if (value.type === 'ObjectExpression') {
// Object inside another object
this.checkCssMapObject(value);
} else if (value.type === 'TemplateLiteral') {
// object value is a template literal, something like
// `hello world`
// `hello ${functionCall()} world`
// `hello ${someVariable} world`
// etc.
//
// where the expressions are the parts enclosed within the
// ${ ... }
for (const expression of value.expressions) {
this.checkCssMapObjectValue(expression);
}
}
}
checkCssMapObject(cssMapObject) {
for (const property of cssMapObject.properties) {
if (property.type === 'SpreadElement') {
this.report({
node: property,
messageId: 'noSpreadElement'
});
continue;
}
this.checkCssMapObjectValue(property.value);
}
}
run() {
this.checkCssMapObject(this.cssMapObject);
}
}