@atlaskit/eslint-plugin-design-system
Version:
The essential plugin for use with the Atlassian Design System.
222 lines (220 loc) • 9.64 kB
JavaScript
import _defineProperty from "@babel/runtime/helpers/defineProperty";
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
import { isNodeOfType } from 'eslint-codemod-utils';
import renameMapping from '@atlaskit/tokens/rename-mapping';
import tokenDefaultValues from '@atlaskit/tokens/token-default-values';
import tokens from '@atlaskit/tokens/token-names';
import { getTokenId } from '@atlaskit/tokens/utils/get-token-id';
import { createLintRule } from '../utils/create-lint-rule';
import { isDecendantOfStyleBlock } from '../utils/is-decendant-of-style-block';
import { isDecendantOfStyleJsxAttribute } from '../utils/is-decendant-of-style-jsx-attribute';
import { isToken } from '../utils/is-token';
var rule = createLintRule({
meta: {
name: 'no-unsafe-design-token-usage',
docs: {
description: 'Enforces design token usage is statically and locally analyzable.',
recommended: true,
severity: 'error'
},
fixable: 'code',
type: 'problem',
messages: {
directTokenUsage: "Access the global theme using the token function.\n\n```\nimport { token } from '@atlaskit/tokens';\n\ntoken('{{tokenKey}}');\n```\n",
staticToken: "Token string should be inlined directly into the function call.\n\n```\ntoken('color.background.blanket');\n```\n",
invalidToken: 'The token "{{name}}" does not exist.',
tokenRemoved: 'The token "{{name}}" is removed in favour of "{{replacement}}".',
tokenIsExperimental: 'The token "{{name}}" is experimental and should not be used directly at this time. It should be replaced by "{{replacement}}".',
tokenFallbackEnforced: "Token function requires a fallback, preferably something that best matches the light/default theme in case tokens aren't present.\n\n```\ntoken('color.background.blanket', N500A);\n```\n ",
tokenFallbackRestricted: "Token function must not use a fallback.\n\n```\ntoken('color.background.blanket');\n```\n "
},
schema: [{
type: 'object',
properties: {
shouldEnforceFallbacks: {
type: 'boolean'
},
fallbackUsage: {
enum: ['forced', 'optional', 'none']
},
UNSAFE_ignoreTokens: {
type: 'array',
items: {
type: 'string'
}
}
}
}]
},
create: function create(context) {
// TODO: JFP-2823 - this type cast was added due to Jira's ESLint v9 migration
var config = _objectSpread({}, context.options[0]);
if (!config.fallbackUsage) {
config.fallbackUsage = config.shouldEnforceFallbacks ? 'forced' : 'none';
}
var UNSAFE_ignoreTokens = new Set(config.UNSAFE_ignoreTokens);
return {
'TaggedTemplateExpression[tag.name="css"],TaggedTemplateExpression[tag.object.name="styled"]': function TaggedTemplateExpressionTagNameCssTaggedTemplateExpressionTagObjectNameStyled(node) {
if (!isNodeOfType(node, 'TaggedTemplateExpression')) {
return;
}
var value = node.quasi.quasis.map(function (q) {
return q.value.raw;
}).join('');
var tokenKey = isToken(value, tokens);
if (tokenKey) {
context.report({
messageId: 'directTokenUsage',
node: node,
data: {
tokenKey: tokenKey
}
});
return;
}
},
'ObjectExpression > Property > Literal': function ObjectExpression__Property__Literal(node) {
if (node.type !== 'Literal') {
return;
}
if (typeof node.value !== 'string') {
return;
}
if (!isDecendantOfStyleBlock(node) && !isDecendantOfStyleJsxAttribute(node)) {
return;
}
var tokenKey = isToken(node.value, tokens);
var isCSSVar = node.value.startsWith('var(');
if (tokenKey) {
context.report({
messageId: 'directTokenUsage',
node: node,
data: {
tokenKey: tokenKey
},
fix: function fix(fixer) {
return isCSSVar ? fixer.replaceText(node, "token('".concat(tokenKey, "')")) : null;
}
});
return;
}
},
'CallExpression:matches([callee.name="token"], [callee.name="getTokenValue"])': function CallExpressionMatchesCalleeNameToken_CalleeNameGetTokenValue(node) {
if (!isNodeOfType(node, 'CallExpression')) {
return;
}
var isGetTokenValueCall = isNodeOfType(node.callee, 'Identifier') && node.callee.name === 'getTokenValue';
// Skip processing if it's a `getTokenValue` call and config.fallbackUsage is `none`
if (isGetTokenValueCall && config.fallbackUsage === 'none') {
return;
}
if (node.arguments.length < 2 && config.fallbackUsage === 'forced') {
var fix;
if (node.arguments[0].type === 'Literal') {
var value = node.arguments[0].value;
var tokenName = value;
var fallbackValue = tokenDefaultValues[tokenName] || null;
if (fallbackValue) {
fix = function fix(fixer) {
return fixer.replaceText(node, "".concat(isNodeOfType(node.callee, 'Identifier') ? node.callee.name : 'token', "('").concat(tokenName, "', '").concat(fallbackValue, "')"));
};
}
}
context.report({
messageId: 'tokenFallbackEnforced',
node: node,
fix: fix
});
} else if (node.arguments.length > 1 && config.fallbackUsage === 'none') {
if (node.arguments[0].type === 'Literal') {
var _value = node.arguments[0].value;
/**
* This check allows ADS tokens to be passed in via UNSAFE_ignoreTokens and allow fallbacks even if `fallbackUsage` is set to 'none'.
* Temporary solution introduced to allow fallbacks for shape tokens in Jira during migration
*/
if (_value && UNSAFE_ignoreTokens.has(_value)) {
return;
}
context.report({
messageId: 'tokenFallbackRestricted',
node: node.arguments[1],
fix: function fix(fixer) {
return fixer.replaceText(node, "".concat(isNodeOfType(node.callee, 'Identifier') ? node.callee.name : 'token', "('").concat(_value, "')"));
}
});
} else {
context.report({
messageId: 'tokenFallbackRestricted',
node: node.arguments[1]
});
}
}
if (node.arguments[0] && node.arguments[0].type !== 'Literal') {
context.report({
messageId: 'staticToken',
node: node
});
return;
}
var tokenKey = node.arguments[0].value;
if (!tokenKey) {
return;
}
var deletedMigrationMeta = renameMapping.filter(function (t) {
return t.state === 'deleted';
}).find(function (t) {
return getTokenId(t.path) === tokenKey;
});
if (typeof tokenKey === 'string' && deletedMigrationMeta && deletedMigrationMeta.replacement) {
var cleanTokenKey = getTokenId(deletedMigrationMeta.replacement);
context.report({
messageId: 'tokenRemoved',
node: node,
data: {
name: tokenKey,
replacement: cleanTokenKey
},
fix: function fix(fixer) {
return fixer.replaceText(node.arguments[0], "'".concat(cleanTokenKey, "'"));
}
});
return;
}
var tokenMeta = renameMapping.filter(function (t) {
return t.state === 'experimental';
}).find(function (t) {
return getTokenId(t.path) === tokenKey;
});
var tokenNames = Object.keys(tokens);
if (typeof tokenKey === 'string' && tokenMeta && tokenMeta.replacement) {
var replacementValue = tokenMeta.replacement;
var isReplacementAToken = tokenNames.includes(replacementValue);
context.report({
messageId: 'tokenIsExperimental',
node: node,
data: {
name: tokenKey,
replacement: replacementValue
},
fix: function fix(fixer) {
return isReplacementAToken ? fixer.replaceText(node.arguments[0], "'".concat(replacementValue, "'")) : fixer.replaceText(node, "'".concat(replacementValue, "'"));
}
});
return;
}
if (typeof tokenKey !== 'string' || typeof tokenKey === 'string' && !tokens[tokenKey] && !UNSAFE_ignoreTokens.has(tokenKey)) {
context.report({
messageId: 'invalidToken',
node: node,
data: {
name: tokenKey.toString()
}
});
return;
}
}
};
}
});
export default rule;