UNPKG

@atlaskit/eslint-plugin-design-system

Version:

The essential plugin for use with the Atlassian Design System.

134 lines (129 loc) 4.81 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _eslintCodemodUtils = require("eslint-codemod-utils"); var _createLintRule = require("../utils/create-lint-rule"); var _findProp = require("../utils/find-prop"); /** * Entry points whose default export is an icon-only control, where the `label` * prop is the sole source of the accessible name. */ var DEFAULT_IMPORT_SOURCES = new Set(['@atlaskit/button/icon/button', '@atlaskit/button/icon/link']); /** * Deprecated entry point that still re-exports the icon-only controls by name. */ var NAMED_IMPORT_SOURCES = new Set(['@atlaskit/button/new']); var NAMED_IMPORTS = new Set(['IconButton', 'LinkIconButton']); var normalize = function normalize(value) { return value.trim(); }; /** * Resolves a `label` prop to a static string, or `undefined` when the value * cannot be determined statically. Dynamic values are intentionally skipped so * the rule never guesses at the contents of a variable, function call or * interpolated template. */ var getStaticLabel = function getStaticLabel(attribute) { var value = attribute.value; if (value === null || value === undefined) { return undefined; } if ((0, _eslintCodemodUtils.isNodeOfType)(value, 'Literal')) { if (typeof value.value === 'string') { return value.value; } return value.value === null || value.value === false ? null : undefined; } if (!(0, _eslintCodemodUtils.isNodeOfType)(value, 'JSXExpressionContainer')) { return undefined; } var expression = value.expression; if ((0, _eslintCodemodUtils.isNodeOfType)(expression, 'Literal')) { if (typeof expression.value === 'string') { return expression.value; } return expression.value === null || expression.value === false ? null : undefined; } if ((0, _eslintCodemodUtils.isNodeOfType)(expression, 'Identifier') && expression.name === 'undefined') { return null; } // Only template literals without interpolation are static. A template with // expressions is assumed to include distinguishing context. if ((0, _eslintCodemodUtils.isNodeOfType)(expression, 'TemplateLiteral')) { if (expression.expressions.length > 0) { return undefined; } return expression.quasis.map(function (quasi) { var _quasi$value$cooked; return (_quasi$value$cooked = quasi.value.cooked) !== null && _quasi$value$cooked !== void 0 ? _quasi$value$cooked : ''; }).join(''); } return undefined; }; var rule = (0, _createLintRule.createLintRule)({ meta: { name: 'no-empty-icon-button-label', type: 'problem', docs: { description: 'Ensures icon-only Atlassian Design System buttons do not have an empty accessible name.', recommended: true, severity: 'warn' }, messages: { emptyLabel: 'The `label` prop is the only accessible name for an icon-only button, so it must not be empty.' } }, create: function create(context) { var iconButtonNames = new Set(); return { ImportDeclaration: function ImportDeclaration(node) { var source = String(node.source.value); if (DEFAULT_IMPORT_SOURCES.has(source)) { node.specifiers.forEach(function (specifier) { if ((0, _eslintCodemodUtils.isNodeOfType)(specifier, 'ImportDefaultSpecifier')) { iconButtonNames.add(specifier.local.name); } }); return; } if (NAMED_IMPORT_SOURCES.has(source)) { node.specifiers.forEach(function (specifier) { if ((0, _eslintCodemodUtils.isNodeOfType)(specifier, 'ImportSpecifier') && (0, _eslintCodemodUtils.isNodeOfType)(specifier.imported, 'Identifier') && NAMED_IMPORTS.has(specifier.imported.name)) { iconButtonNames.add(specifier.local.name); } }); } }, JSXElement: function JSXElement(node) { if (!(0, _eslintCodemodUtils.isNodeOfType)(node, 'JSXElement')) { return; } if (!(0, _eslintCodemodUtils.isNodeOfType)(node.openingElement.name, 'JSXIdentifier')) { return; } if (!iconButtonNames.has(node.openingElement.name.name)) { return; } var labelProp = (0, _findProp.findProp)(node, 'label'); // A missing `label` is already a type error, so it is not reported here. if (!labelProp) { return; } var label = getStaticLabel(labelProp); if (label === undefined) { return; } if (label === null || normalize(label) === '') { context.report({ node: labelProp, messageId: 'emptyLabel' }); return; } } }; } }); var _default = exports.default = rule;