UNPKG

@atlaskit/eslint-plugin-design-system

Version:

The essential plugin for use with the Atlassian Design System.

185 lines (178 loc) 8.15 kB
import { isNodeOfType } from 'eslint-codemod-utils'; import { JSXAttribute } from '../../ast-nodes/jsx-attribute'; import { JSXElementHelper } from '../../ast-nodes/jsx-element-helper'; import { createLintRule } from '../utils/create-lint-rule'; var DATE_PICKER = 'DatePicker'; var DATETIME_PICKER = 'DateTimePicker'; var PROP_NAME = 'shouldShowCalendarButton'; var DATETIME_PICKER_PACKAGE = '@atlaskit/datetime-picker'; var DATE_PICKER_IMPORT_SOURCE = '@atlaskit/datetime-picker/date-picker'; var DATE_TIME_PICKER_IMPORT_SOURCE = '@atlaskit/datetime-picker/date-time-picker'; // Lint rule message var message = '`shouldShowCalendarButton` should be set to `true` to make date picker accessible.'; // Fix messages export var addCalendarButtonProp = 'Add `shouldShowCalendarButton` prop.'; export var setCalendarButtonPropToTrue = 'Set `shouldShowCalendarButton` prop to `true`.'; export var addCalendarButtonProperty = 'Add `shouldShowCalendarButton: true` to `datePickerProps`.'; export var setCalendarButtonPropertyToTrue = 'Set `shouldShowCalendarButton` property in `datePickerProps` to `true`.'; var datePickerJSXElement = function datePickerJSXElement(node, context) { var prop = JSXElementHelper.getAttributeByName(node, PROP_NAME); // If the prop exists if (prop) { var attrValue = JSXAttribute.getValue(prop); // If the value is a boolean with value `false` if ((attrValue === null || attrValue === void 0 ? void 0 : attrValue.type) === 'ExpressionStatement Literal' && (attrValue === null || attrValue === void 0 ? void 0 : attrValue.value) === false) { return context.report({ node: prop, messageId: 'datePickerCalendarButtonShouldBeShown', suggest: [{ desc: setCalendarButtonPropToTrue, fix: function fix(fixer) { return [fixer.replaceText(prop, PROP_NAME)]; } }] }); } // If the prop does not exist } else { return context.report({ node: node.openingElement, messageId: 'datePickerMissingCalendarButtonProp', suggest: [{ desc: addCalendarButtonProp, fix: function fix(fixer) { return [fixer.insertTextAfter(node.openingElement.name, " ".concat(PROP_NAME))]; } }] }); } }; var dateTimePickerJSXElement = function dateTimePickerJSXElement(node, context) { var _datePickerProp$value, _expression$propertie; var datePickerProp = JSXElementHelper.getAttributeByName(node, 'datePickerProps'); // If the `datePickerProps` prop does not exist or is not an expression // container (the latter being essentially unprecedented, against type // guidelines, and useless) if (!datePickerProp || ((_datePickerProp$value = datePickerProp.value) === null || _datePickerProp$value === void 0 ? void 0 : _datePickerProp$value.type) !== 'JSXExpressionContainer') { return context.report({ node: node.openingElement, messageId: 'dateTimePickerMissingCalendarButtonProp', suggest: [{ desc: addCalendarButtonProperty, fix: function fix(fixer) { return [fixer.insertTextAfter(node.openingElement.name, " datePickerProps={{ ".concat(PROP_NAME, ": true }}"))]; } }] }); } // Had to cast all these things because ESLint can't hang here. The // types are just abjectly wrong from what I can log. var expression = datePickerProp.value.expression; // If it is not an analyzable expression, like a variable or something, skip // it. if (!isNodeOfType(datePickerProp.value.expression, 'ObjectExpression')) { return; } var prop = expression === null || expression === void 0 || (_expression$propertie = expression.properties) === null || _expression$propertie === void 0 ? void 0 : _expression$propertie.find(function (property) { return property.type === 'Property' && property.key.name === PROP_NAME; }); // If the `shouldShowCalendarButton` prop does not exist in `datePickerProps` if (!prop) { return context.report({ node: datePickerProp, messageId: 'dateTimePickerMissingCalendarButtonProp', suggest: [{ desc: addCalendarButtonProperty, fix: function fix(fixer) { // If it has existing properties if (expression.properties.length > 0) { // Needs following comma to not disrupt existing properties inside `datePickerProps` return [fixer.insertTextBefore(expression.properties[0], "".concat(PROP_NAME, ": true,"))]; // Else it's an empty object } else { return [fixer.replaceText(expression, "{ ".concat(PROP_NAME, ": true }"))]; } } }] }); // If the `shouldShowCalendarButton` property exists and it's value is `false` } else if (isNodeOfType(prop.value, 'Literal') && prop.value.value !== true) { return context.report({ node: datePickerProp, messageId: 'dateTimePickerCalendarButtonShouldBeShown', suggest: [{ desc: setCalendarButtonPropertyToTrue, // Needs following comma to not disrupt existing properties inside `datePickerProps` fix: function fix(fixer) { return [fixer.replaceText(prop, "".concat(PROP_NAME, ": true,"))]; } }] }); } }; var rule = createLintRule({ meta: { name: 'use-datetime-picker-calendar-button', type: 'suggestion', fixable: 'code', hasSuggestions: true, docs: { description: "Encourages makers to use calendar button in Atlassian Design System's date picker and datetime picker components.", recommended: true, severity: 'warn' }, messages: { dateTimePickerMissingCalendarButtonProp: "In `datePickerProps`, ".concat(message), datePickerMissingCalendarButtonProp: message, dateTimePickerCalendarButtonShouldBeShown: "In `datePickerProps`, ".concat(message), datePickerCalendarButtonShouldBeShown: message } }, create: function create(context) { // List of component's locally imported names that match var contextLocalIdentifier = []; var contextImportedIdentifier = []; return { // Only run rule in files where the package is imported ImportDeclaration: function ImportDeclaration(node) { var _node$specifiers; (_node$specifiers = node.specifiers) === null || _node$specifiers === void 0 || _node$specifiers.forEach(function (spec) { var _spec$imported; if (node.source.value === DATETIME_PICKER_PACKAGE && isNodeOfType(spec, 'ImportSpecifier') && 'name' in spec.imported && [DATE_PICKER, DATETIME_PICKER].includes((_spec$imported = spec.imported) === null || _spec$imported === void 0 ? void 0 : _spec$imported.name)) { contextLocalIdentifier.push(spec.local.name); contextImportedIdentifier.push(spec.imported.name); } if (node.source.value === DATE_PICKER_IMPORT_SOURCE && isNodeOfType(spec, 'ImportDefaultSpecifier')) { contextLocalIdentifier.push(spec.local.name); contextImportedIdentifier.push(DATE_PICKER); } if (node.source.value === DATE_TIME_PICKER_IMPORT_SOURCE && isNodeOfType(spec, 'ImportDefaultSpecifier')) { contextLocalIdentifier.push(spec.local.name); contextImportedIdentifier.push(DATETIME_PICKER); } }); }, JSXElement: function JSXElement(node) { if (!isNodeOfType(node, 'JSXElement')) { return; } if (!isNodeOfType(node.openingElement.name, 'JSXIdentifier')) { return; } var name = node.openingElement.name.name; var localIndex = contextLocalIdentifier.indexOf(name); // If this component does not match what we're looking for, quit early if (localIndex === -1) { return; } var importedName = contextImportedIdentifier[localIndex]; if (importedName === DATE_PICKER) { return datePickerJSXElement(node, context); } else if (importedName === DATETIME_PICKER) { return dateTimePickerJSXElement(node, context); } } }; } }); export default rule;