UNPKG

@atlaskit/eslint-plugin-design-system

Version:

The essential plugin for use with the Atlassian Design System.

229 lines (226 loc) 10.4 kB
"use strict"; 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;