@rushstack/eslint-plugin
Version:
An ESLint plugin providing supplementary rules for use with the @rushstack/eslint-config package
93 lines • 5.05 kB
JavaScript
;
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
exports.pairReactDomRenderUnmountRule = exports.MESSAGE_ID = void 0;
const utils_1 = require("@typescript-eslint/utils");
exports.MESSAGE_ID = 'error-pair-react-dom-render-unmount';
const pairReactDomRenderUnmountRule = {
defaultOptions: [],
meta: {
type: 'problem',
messages: {
[exports.MESSAGE_ID]: 'Pair the render and unmount calls to avoid memory leaks.'
},
schema: [],
docs: {
description: 'Pair ReactDOM "render" and "unmount" calls in one file.' +
' If a ReactDOM render tree is not unmounted when disposed, it will cause a memory leak.',
url: 'https://www.npmjs.com/package/@rushstack/eslint-plugin'
}
},
create: (context) => {
const renderCallExpressions = [];
const unmountCallExpressions = [];
let reactDomImportNamespaceName;
let reactDomRenderFunctionName;
let reactDomUnmountFunctionName;
const isFunctionCallExpression = (node, methodName) => {
return node.callee.type === utils_1.TSESTree.AST_NODE_TYPES.Identifier && node.callee.name === methodName;
};
const isNamespaceCallExpression = (node, namespaceName, methodName) => {
if (node.callee.type === utils_1.TSESTree.AST_NODE_TYPES.MemberExpression) {
const { object, property } = node.callee;
if (object.type === utils_1.TSESTree.AST_NODE_TYPES.Identifier && object.name === namespaceName) {
return ((property.type === utils_1.TSESTree.AST_NODE_TYPES.Identifier && property.name === methodName) ||
(property.type === utils_1.TSESTree.AST_NODE_TYPES.Literal && property.value === methodName));
}
}
return false;
};
return {
ImportDeclaration: (node) => {
// Extract the name for the 'react-dom' namespace import
if (node.source.value === 'react-dom') {
if (!reactDomImportNamespaceName) {
const namespaceSpecifier = node.specifiers.find((s) => s.type === utils_1.TSESTree.AST_NODE_TYPES.ImportNamespaceSpecifier);
if (namespaceSpecifier) {
reactDomImportNamespaceName = namespaceSpecifier.local.name;
}
else {
const defaultSpecifier = node.specifiers.find((s) => s.type === utils_1.TSESTree.AST_NODE_TYPES.ImportDefaultSpecifier);
if (defaultSpecifier) {
reactDomImportNamespaceName = defaultSpecifier.local.name;
}
}
}
if (!reactDomRenderFunctionName || !reactDomUnmountFunctionName) {
const importSpecifiers = node.specifiers.filter((s) => s.type === utils_1.TSESTree.AST_NODE_TYPES.ImportSpecifier);
for (const importSpecifier of importSpecifiers) {
const name = 'name' in importSpecifier.imported ? importSpecifier.imported.name : undefined;
if (name === 'render') {
reactDomRenderFunctionName = importSpecifier.local.name;
}
else if (name === 'unmountComponentAtNode') {
reactDomUnmountFunctionName = importSpecifier.local.name;
}
}
}
}
},
CallExpression: (node) => {
if (isNamespaceCallExpression(node, reactDomImportNamespaceName, 'render') ||
isFunctionCallExpression(node, reactDomRenderFunctionName)) {
renderCallExpressions.push(node);
}
else if (isNamespaceCallExpression(node, reactDomImportNamespaceName, 'unmountComponentAtNode') ||
isFunctionCallExpression(node, reactDomUnmountFunctionName)) {
unmountCallExpressions.push(node);
}
},
// eslint-disable-next-line @typescript-eslint/naming-convention
'Program:exit': (node) => {
if (renderCallExpressions.length !== unmountCallExpressions.length) {
renderCallExpressions.concat(unmountCallExpressions).forEach((callExpression) => {
context.report({ node: callExpression, messageId: exports.MESSAGE_ID });
});
}
}
};
}
};
exports.pairReactDomRenderUnmountRule = pairReactDomRenderUnmountRule;
//# sourceMappingURL=pair-react-dom-render-unmount.js.map