UNPKG

@rushstack/eslint-plugin

Version:

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

172 lines 7.73 kB
"use strict"; // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.hoistJestMock = void 0; const utils_1 = require("@typescript-eslint/utils"); const hoistJestMockPatterns = __importStar(require("./hoistJestMockPatterns")); // Jest APIs that need to be hoisted // Based on HOIST_METHODS from ts-jest const HOIST_METHODS = ['mock', 'unmock', 'enableAutomock', 'disableAutomock', 'deepUnmock']; const hoistJestMock = { defaultOptions: [], meta: { type: 'problem', messages: { 'error-unhoisted-jest-mock': "Jest's module mocking APIs must be called before regular imports. Move this call so that it precedes" + ' the import found on line {{importLine}}.' }, schema: [ { type: 'object', additionalProperties: false } ], docs: { description: 'Require Jest module mocking APIs to be called before other modules are imported.' + ' Jest module mocking APIs such as "jest.mock(\'./example\')" must be called before the associated module' + ' is imported, otherwise they will have no effect. Transpilers such as ts-jest and babel-jest automatically' + ' "hoist" these calls, however this can produce counterintuitive results. Instead, the hoist-jest-mocks' + ' lint rule requires developers to manually hoist these calls. For technical background, please read the' + ' Jest documentation here: https://jestjs.io/docs/en/es6-class-mocks', recommended: 'recommended', url: 'https://www.npmjs.com/package/@rushstack/eslint-plugin' } }, create: (context) => { // Returns true for a statement such as "jest.mock()" that needs to precede // module imports (i.e. be "hoisted"). function isHoistableJestCall(node) { if (node === undefined) { return false; } const captures = {}; if (hoistJestMockPatterns.jestCallExpression.match(node, captures)) { if (captures.methodName && HOIST_METHODS.indexOf(captures.methodName) >= 0) { return true; } } // Recurse into some common expression-combining syntaxes switch (node.type) { case utils_1.AST_NODE_TYPES.CallExpression: return isHoistableJestCall(node.callee); case utils_1.AST_NODE_TYPES.MemberExpression: return isHoistableJestCall(node.object); case utils_1.AST_NODE_TYPES.LogicalExpression: return isHoistableJestCall(node.left) || isHoistableJestCall(node.right); } return false; } // Given part of an expression, walk upwards in the tree and find the containing statement function findOuterStatement(node) { let current = node; while (current.parent) { switch (current.parent.type) { // Statements are always found inside a block: case utils_1.AST_NODE_TYPES.Program: case utils_1.AST_NODE_TYPES.BlockStatement: case utils_1.AST_NODE_TYPES.TSModuleBlock: return current; } current = current.parent; } return node; } // This tracks the first require() or import expression that we found in the file. let firstImportNode = undefined; // Avoid reporting more than one error for a given statement. // Example: jest.mock('a').mock('b'); const reportedStatements = new Set(); return { CallExpression: (node) => { if (firstImportNode === undefined) { // EXAMPLE: const x = require('x') if (hoistJestMockPatterns.requireCallExpression.match(node)) { firstImportNode = node; } } if (firstImportNode) { // EXAMPLE: jest.mock() if (isHoistableJestCall(node)) { const outerStatement = findOuterStatement(node); if (!reportedStatements.has(outerStatement)) { reportedStatements.add(outerStatement); context.report({ node, messageId: 'error-unhoisted-jest-mock', data: { importLine: firstImportNode.loc.start.line } }); } } } }, ImportExpression: (node) => { if (firstImportNode === undefined) { // EXAMPLE: const x = import('x'); if (hoistJestMockPatterns.importExpression.match(node)) { firstImportNode = node; } } }, ImportDeclaration: (node) => { if (firstImportNode === undefined) { // EXAMPLE: import { X } from "Y"; // IGNORE: import type { X } from "Y"; if (node.importKind !== 'type') { firstImportNode = node; } } }, ExportDeclaration: (node) => { if (firstImportNode === undefined) { // EXAMPLE: export * from "Y"; // IGNORE: export type { Y } from "Y"; if (node.exportKind !== 'type') { firstImportNode = node; } } }, TSImportEqualsDeclaration: (node) => { if (firstImportNode === undefined) { // EXAMPLE: import x = require("x"); firstImportNode = node; } } }; } }; exports.hoistJestMock = hoistJestMock; //# sourceMappingURL=hoist-jest-mock.js.map