UNPKG

@atlaskit/eslint-plugin-design-system

Version:

The essential plugin for use with the Atlassian Design System.

276 lines (258 loc) 14.6 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.StyleObject = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _eslintCodemodUtils = require("eslint-codemod-utils"); var _contextCompat = require("@atlaskit/eslint-utils/context-compat"); var _object = require("../../../ast-nodes/object"); var _objectEntry = require("../../../ast-nodes/object-entry"); var _root = require("../../../ast-nodes/root"); var _getValueForPropertyNode = require("../../ensure-design-token-usage/get-value-for-property-node"); var _normaliseValue = require("../../ensure-design-token-usage/normalise-value"); var _isDecendantOfGlobalToken = require("../../utils/is-decendant-of-global-token"); var _isDecendantOfStyleBlock = require("../../utils/is-decendant-of-style-block"); var _isDecendantOfType = require("../../utils/is-decendant-of-type"); var _convertPropertyNodeToStringableNode = require("../convert-property-node-to-stringable-node"); var _defaultFontWeight = require("../default-font-weight"); var _findFontWeightTokenForValue = require("../find-font-weight-token-for-value"); var _findTypographyTokenForValues = require("../find-typography-token-for-values"); var _fontWeightMap = require("../font-weight-map"); var _getLiteralProperty = require("../get-literal-property"); var _getTokenProperty = require("../get-token-property"); var _insertTokensImport = require("../insert-tokens-import"); var _isValidPropertyNode = require("../is-valid-property-node"); var _isValidTypographyToken = require("../is-valid-typography-token"); var _notUndefined = require("../not-undefined"); 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; } /* eslint-disable @repo/internal/react/require-jsdoc */ var StyleObject = exports.StyleObject = { lint: function lint(node, _ref) { var context = _ref.context, config = _ref.config; // To force the correct node type if (!(0, _eslintCodemodUtils.isNodeOfType)(node, 'ObjectExpression')) { return { success: false }; } // Check whether all criteria needed to make a transformation are met var _StyleObject$_check = StyleObject._check(node, { context: context, config: config }), success = _StyleObject$_check.success, refs = _StyleObject$_check.refs; if (!success || !refs) { return; } var fontSizeNode = refs.fontSizeNode, fontSizeRaw = refs.fontSizeRaw, tokensImportNode = refs.tokensImportNode; var fontSizeValue = (0, _normaliseValue.normaliseValue)('fontSize', fontSizeRaw); // -- Font weight -- var fontWeightNode = _object.Object.getEntryByPropertyName(node, 'fontWeight'); var fontWeightRaw = fontWeightNode && (0, _getValueForPropertyNode.getValueForPropertyNode)(fontWeightNode, context); // If fontWeightRaw is a token we find the token name and treat it like a raw value for simplicity. // e.g. token('font.weight.bold', '700') ends up as '700' after this if-block. // That way the token matching logic still runs and the font weight declaration can be removed and re-added after the main font token. if (fontWeightRaw && typeof fontWeightRaw === 'string' && fontWeightRaw.includes('font.weight.')) { var _fontWeightRaw$match; var fontWeightTokenSuffix = ((_fontWeightRaw$match = fontWeightRaw.match(/font\.weight\.(\w*)/)) === null || _fontWeightRaw$match === void 0 ? void 0 : _fontWeightRaw$match[1]) || 'regular'; // ${token('font.weight.bold', '700')} -> 'bold' if (Object.keys(_fontWeightMap.fontWeightMap).includes(fontWeightTokenSuffix)) { fontWeightRaw = _fontWeightMap.fontWeightMap[fontWeightTokenSuffix]; } } // If no fontWeight value exists, default to 400 to avoid matching with a bolder token resulting in a visual change var fontWeightValue = fontWeightRaw && (0, _normaliseValue.normaliseValue)('fontWeight', fontWeightRaw) || _defaultFontWeight.defaultFontWeight; fontWeightValue = fontWeightValue.length === 3 ? fontWeightValue : _fontWeightMap.fontWeightMap[fontWeightValue] || _defaultFontWeight.defaultFontWeight; // -- Line height -- var lineHeightNode = _object.Object.getEntryByPropertyName(node, 'lineHeight'); var lineHeightRaw = lineHeightNode && (0, _getValueForPropertyNode.getValueForPropertyNode)(lineHeightNode, context); var shouldAddFontWeight = false; var lineHeightValue = lineHeightRaw && (0, _normaliseValue.normaliseValue)('lineHeight', lineHeightRaw) || undefined; if (lineHeightValue === fontSizeValue) { lineHeightValue = '1'; } // -- Match tokens -- // Check if fontSize is a token (this is invalid syntax but unfortunately a common occurence) // We may as well auto-fix `fontSize` to `font` and keep the token. // Other tokens like `fontSize: token('space.100')` will not autofix, but still report var matchingTokens = []; var isFontSizeAToken = (0, _isDecendantOfGlobalToken.isDecendantOfGlobalToken)(fontSizeNode.value); if (isFontSizeAToken) { // Specifically match for valid, non-deprecated font.heading|body|code tokens var match = fontSizeValue.match(/font.(body|heading|code)[^']*/); if (match) { var matchedTokenName = match[0]; // This is really just a double check to be 100% certain the token exists // and that we're not trying to apply a deprecated fontSize token to the font property if ((0, _isValidTypographyToken.isValidTypographyToken)(matchedTokenName)) { matchingTokens = [{ tokenName: matchedTokenName }]; } } } else { // Standard matching against fontSize/lineHeight values matchingTokens = (0, _findTypographyTokenForValues.findTypographyTokenForValues)(fontSizeValue, lineHeightValue); if (matchingTokens.length) { // If we have multiple matching tokens, try matching fontWeight var matchingTokenWithWeight = (0, _findFontWeightTokenForValue.findFontWeightTokenForValue)(fontWeightValue, matchingTokens); if (matchingTokenWithWeight) { // Possibly narrowed down tokens matchingTokens = [matchingTokenWithWeight]; } else { // Ended up with 0 matches by matching fontWeight // return body token and add fontWeight manually matchingTokens = matchingTokens.filter(function (token) { return token.tokenName.includes('.body'); }); shouldAddFontWeight = true; } } } // Get other font-* nodes that we can replace/remove. // These aren't needed for token matching. // -- Font family -- var fontFamilyNode = _object.Object.getEntryByPropertyName(node, 'fontFamily'); var fontFamilyRaw = fontFamilyNode && (0, _getValueForPropertyNode.getValueForPropertyNode)(fontFamilyNode, context); var fontFamilyValue = fontFamilyRaw && (0, _normaliseValue.normaliseValue)('fontFamily', fontFamilyRaw) || undefined; // If font family is already a token, we remove and re-add it // Only need to do this for non-default font stacks as the defaults can be safely removed if (fontFamilyValue && fontFamilyValue.includes('font.family.') && !(fontFamilyValue.includes('font.family.heading') || fontFamilyValue.includes('font.family.body'))) { fontFamilyValue = undefined; } var fontFamilyToAdd; // If font family uses the Charlie font we can't replace; exit if (fontFamilyValue) { if (fontFamilyValue.toLowerCase().includes('charlie display')) { fontFamilyToAdd = 'heading'; } else if (fontFamilyValue.toLowerCase().includes('charlie text')) { fontFamilyToAdd = 'body'; } } else { // Font family node exists but we can't resolve its value // Will need to re-add it below the font property to ensure it still applies fontFamilyToAdd = fontFamilyNode ? 'original' : undefined; } // -- Font style -- var fontStyleNode = _object.Object.getEntryByPropertyName(node, 'fontStyle'); var fontStyleRaw = fontStyleNode && (0, _getValueForPropertyNode.getValueForPropertyNode)(fontStyleNode, context); var fontStyleValue = fontStyleRaw && (0, _normaliseValue.normaliseValue)('fontStyle', fontStyleRaw) || undefined; var fontStyleToAdd; if (fontStyleValue === 'italic') { fontStyleToAdd = 'italic'; } // -- Letter spacing -- var letterSpacingNode = _object.Object.getEntryByPropertyName(node, 'letterSpacing'); // A single matching token // TOOD: Maybe suggest options if > 1 matching token if (matchingTokens.length === 1) { var matchingToken = matchingTokens[0]; // fontSize node is always first var nodesToReplace = [fontSizeNode, fontWeightNode, lineHeightNode, fontFamilyNode, fontStyleNode, letterSpacingNode].filter(_notUndefined.notUndefined); var fontFamilyTokenName = fontFamilyToAdd ? "font.family.brand.".concat(fontFamilyToAdd) : ''; var fontWeightReplacementToken = shouldAddFontWeight ? (0, _findFontWeightTokenForValue.findFontWeightTokenForValue)(fontWeightValue) : undefined; var fontWeightReplacement = fontWeightReplacementToken && (0, _getTokenProperty.getTokenProperty)('fontWeight', fontWeightReplacementToken.tokenName); var fontFamilyReplacement = fontFamilyToAdd && (fontFamilyToAdd === 'original' ? (0, _convertPropertyNodeToStringableNode.convertPropertyNodeToStringableNode)( // This will always exist if fontFamilyToAdd === 'original', TS can't figure that out. fontFamilyNode) : (0, _getTokenProperty.getTokenProperty)('fontFamily', fontFamilyTokenName)); var fontStyleReplacement = fontStyleToAdd && (0, _getLiteralProperty.getLiteralProperty)('fontStyle', fontStyleToAdd); var fixerRefs = { matchingToken: matchingToken, nodesToReplace: nodesToReplace, tokensImportNode: tokensImportNode, fontWeightReplacement: fontWeightReplacement, fontFamilyReplacement: fontFamilyReplacement, fontStyleReplacement: fontStyleReplacement }; var fix = StyleObject._fix(fixerRefs, context); context.report(_objectSpread({ node: fontSizeNode, messageId: isFontSizeAToken ? 'noFontSizeTypographyToken' : 'noRawTypographyValues' }, config.enableUnsafeAutofix ? { fix: fix } : { suggest: [{ desc: "Convert to font token", fix: fix }] })); } else if (!matchingTokens.length) { context.report({ node: fontSizeNode, messageId: 'noRawTypographyValues' }); } return; }, _check: function _check(node, _ref2) { var context = _ref2.context, config = _ref2.config; if (!config.patterns.includes('style-object')) { return { success: false }; } if (!(0, _isDecendantOfStyleBlock.isDecendantOfStyleBlock)(node) && !(0, _isDecendantOfType.isDecendantOfType)(node, 'JSXExpressionContainer')) { return { success: false }; } // -- Font size -- var fontSizeNode = _object.Object.getEntryByPropertyName(node, 'fontSize'); if (!fontSizeNode || !(0, _isValidPropertyNode.isValidPropertyNode)(fontSizeNode)) { return { success: false }; } var fontSizeRaw = (0, _getValueForPropertyNode.getValueForPropertyNode)(fontSizeNode, context); // Without a valid fontSize value we can't be certain what token should be used; exit if (fontSizeRaw === undefined || fontSizeRaw === null) { return { success: false }; } var tokensImportDeclaration = _root.Root.findImportsByModule((0, _contextCompat.getSourceCode)(context).ast.body, '@atlaskit/tokens'); // If there is more than one `@atlaskit/tokens` import, then it becomes difficult to determine which import to transform if (tokensImportDeclaration.length > 1) { return { success: false }; } return { success: true, refs: { fontSizeNode: fontSizeNode, fontSizeRaw: fontSizeRaw, tokensImportNode: tokensImportDeclaration[0] } }; }, _fix: function _fix(refs, context) { return function (fixer) { var matchingToken = refs.matchingToken, nodesToReplace = refs.nodesToReplace, tokensImportNode = refs.tokensImportNode, fontWeightReplacement = refs.fontWeightReplacement, fontFamilyReplacement = refs.fontFamilyReplacement, fontStyleReplacement = refs.fontStyleReplacement; var fontSizeNode = nodesToReplace[0]; var root = (0, _contextCompat.getSourceCode)(context).ast.body; return (!tokensImportNode ? [(0, _insertTokensImport.insertTokensImport)(root, fixer)] : []).concat(nodesToReplace.map(function (node, index) { // Replace first node with token, delete remaining nodes. Guaranteed to be fontSize if (index === 0) { return fixer.replaceText(node, "".concat((0, _getTokenProperty.getTokenProperty)('font', matchingToken.tokenName, undefined, true))); } // We don't replace fontWeight/fontFamily/fontStyle here in case it occurs before the font property. // Instead delete the original property and add below return _objectEntry.ObjectEntry.deleteEntry(node, context, fixer); }), // Make sure font weight/family/style properties are added AFTER font property to ensure they override corectly fontWeightReplacement ? [fixer.insertTextAfter(fontSizeNode, ",\n".concat(fontWeightReplacement))] : [], fontFamilyReplacement ? [fixer.insertTextAfter(fontSizeNode, ",\n".concat(fontFamilyReplacement))] : [], fontStyleReplacement ? [fixer.insertTextAfter(fontSizeNode, ",\n".concat(fontStyleReplacement))] : []); }; } };