UNPKG

@rushstack/eslint-plugin

Version:

An ESLint plugin providing supplementary rules for use with the @rushstack/eslint-config package

93 lines 5.05 kB
"use strict"; // 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