@atlaskit/eslint-plugin-design-system
Version:
The essential plugin for use with the Atlassian Design System.
121 lines (119 loc) • 5.09 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _eslintCodemodUtils = require("eslint-codemod-utils");
var _createLintRule = require("../utils/create-lint-rule");
var MODAL_DIALOG_IMPORT_SOURCES = new Set(['@atlaskit/modal-dialog', '@atlaskit/modal-dialog/modal-title', '@atlaskit/modal-dialog/modal-header']);
var rule = (0, _createLintRule.createLintRule)({
meta: {
name: 'use-modal-title',
type: 'suggestion',
docs: {
description: 'Encourages makers to include `ModalTitle` within `ModalHeader` when using Atlassian Design System modal dialog.',
recommended: true,
severity: 'error'
},
messages: {
modalHeaderMissingModalTitle: '`ModalHeader` should include `ModalTitle`.'
}
},
create: function create(context) {
var localImports = {
modalHeader: new Set(),
modalTitle: new Set()
};
var defaultImports = {
modalHeader: new Set(),
modalTitle: new Set()
};
/**
* Stack of open ModalHeader elements awaiting a ModalTitle descendant.
*
* Each frame records whether a ModalTitle has been found anywhere inside
* the corresponding ModalHeader's subtree. We use ESLint's own DFS traversal
* rather than a hand-rolled recursive search, which avoids the O(subtree)
* double-traversal that `containsModalTitle` caused.
*
* Invariant: frames are pushed in DFS enter-order and popped in exit-order,
* so the array is always a valid ancestor chain (outermost frame at index 0).
*/
var modalHeaderStack = [];
return {
// Keeping this for barrel imports, though we are moving away from them.
// This is mostly for non-internal usage.
ImportDeclaration: function ImportDeclaration(node) {
var importSource = String(node.source.value);
if (!MODAL_DIALOG_IMPORT_SOURCES.has(importSource)) {
return;
}
node.specifiers.forEach(function (specifier) {
if ((0, _eslintCodemodUtils.isNodeOfType)(specifier, 'ImportSpecifier') && 'name' in specifier.imported) {
if (specifier.imported.name === 'ModalHeader') {
localImports.modalHeader.add(specifier.local.name);
}
if (specifier.imported.name === 'ModalTitle') {
localImports.modalTitle.add(specifier.local.name);
}
}
if ((0, _eslintCodemodUtils.isNodeOfType)(specifier, 'ImportDefaultSpecifier')) {
if (importSource === '@atlaskit/modal-dialog/modal-header') {
defaultImports.modalHeader.add(specifier.local.name);
}
if (importSource === '@atlaskit/modal-dialog/modal-title') {
defaultImports.modalTitle.add(specifier.local.name);
}
}
});
},
JSXElement: function JSXElement(node) {
if (!(0, _eslintCodemodUtils.isNodeOfType)(node, 'JSXElement')) {
return;
}
if (!(0, _eslintCodemodUtils.isNodeOfType)(node.openingElement.name, 'JSXIdentifier')) {
return;
}
var elementName = node.openingElement.name.name;
if (localImports.modalHeader.has(elementName) || defaultImports.modalHeader.has(elementName)) {
// Push a new frame when entering a ModalHeader. It starts unsatisfied
// and will be marked satisfied if a ModalTitle is found in its subtree.
modalHeaderStack.push({
satisfied: false
});
return;
}
if (modalHeaderStack.length > 0 && (localImports.modalTitle.has(elementName) || defaultImports.modalTitle.has(elementName))) {
// A ModalTitle anywhere inside the current ModalHeader's subtree satisfies
// the requirement. Mark all enclosing ModalHeaders satisfied so that
// ModalTitle inside a nested ModalHeader also satisfies outer ones — matching
// the original containsModalTitle full-subtree-search semantics.
for (var _i = 0, _modalHeaderStack = modalHeaderStack; _i < _modalHeaderStack.length; _i++) {
var entry = _modalHeaderStack[_i];
entry.satisfied = true;
}
}
},
'JSXElement:exit': function JSXElementExit(node) {
if (!(0, _eslintCodemodUtils.isNodeOfType)(node, 'JSXElement')) {
return;
}
if (!(0, _eslintCodemodUtils.isNodeOfType)(node.openingElement.name, 'JSXIdentifier')) {
return;
}
var elementName = node.openingElement.name.name;
if (localImports.modalHeader.has(elementName) || defaultImports.modalHeader.has(elementName)) {
// Pop on exit (handles both regular and self-closing ModalHeader elements).
var entry = modalHeaderStack.pop();
if (entry && !entry.satisfied) {
context.report({
node: node.openingElement,
messageId: 'modalHeaderMissingModalTitle'
});
}
}
}
};
}
});
var _default = exports.default = rule;