@atlaskit/eslint-plugin-design-system
Version:
The essential plugin for use with the Atlassian Design System.
229 lines (226 loc) • 10.4 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _eslintCodemodUtils = require("eslint-codemod-utils");
var _renameMapping = _interopRequireDefault(require("@atlaskit/tokens/rename-mapping"));
var _tokenDefaultValues = _interopRequireDefault(require("@atlaskit/tokens/token-default-values"));
var _tokenNames = _interopRequireDefault(require("@atlaskit/tokens/token-names"));
var _getTokenId = require("@atlaskit/tokens/utils/get-token-id");
var _createLintRule = require("../utils/create-lint-rule");
var _isDecendantOfStyleBlock = require("../utils/is-decendant-of-style-block");
var _isDecendantOfStyleJsxAttribute = require("../utils/is-decendant-of-style-jsx-attribute");
var _isToken = require("../utils/is-token");
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) { (0, _defineProperty2.default)(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; }
var rule = (0, _createLintRule.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 (!(0, _eslintCodemodUtils.isNodeOfType)(node, 'TaggedTemplateExpression')) {
return;
}
var value = node.quasi.quasis.map(function (q) {
return q.value.raw;
}).join('');
var tokenKey = (0, _isToken.isToken)(value, _tokenNames.default);
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 (!(0, _isDecendantOfStyleBlock.isDecendantOfStyleBlock)(node) && !(0, _isDecendantOfStyleJsxAttribute.isDecendantOfStyleJsxAttribute)(node)) {
return;
}
var tokenKey = (0, _isToken.isToken)(node.value, _tokenNames.default);
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 (!(0, _eslintCodemodUtils.isNodeOfType)(node, 'CallExpression')) {
return;
}
var isGetTokenValueCall = (0, _eslintCodemodUtils.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.default[tokenName] || null;
if (fallbackValue) {
fix = function fix(fixer) {
return fixer.replaceText(node, "".concat((0, _eslintCodemodUtils.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((0, _eslintCodemodUtils.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.default.filter(function (t) {
return t.state === 'deleted';
}).find(function (t) {
return (0, _getTokenId.getTokenId)(t.path) === tokenKey;
});
if (typeof tokenKey === 'string' && deletedMigrationMeta && deletedMigrationMeta.replacement) {
var cleanTokenKey = (0, _getTokenId.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.default.filter(function (t) {
return t.state === 'experimental';
}).find(function (t) {
return (0, _getTokenId.getTokenId)(t.path) === tokenKey;
});
var tokenNames = Object.keys(_tokenNames.default);
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' && !_tokenNames.default[tokenKey] && !UNSAFE_ignoreTokens.has(tokenKey)) {
context.report({
messageId: 'invalidToken',
node: node,
data: {
name: tokenKey.toString()
}
});
return;
}
}
};
}
});
var _default = exports.default = rule;