UNPKG

eslint-plugin-mocha

Version:

Eslint rules for mocha.

259 lines 10.7 kB
import { createMochaVisitors } from "../ast/mocha-visitors.js"; import { isFunction } from "../ast/node-types.js"; import { asRuleNode } from "../ast/rule-node.js"; import { hasUnhandledReturnPath } from "../done-callback-paths.js"; import { getIdentifierCallbackParameter } from "../mocha/callback-parameter.js"; import { getMemberExpressionBindingAndProperty, getTrackedBinding } from "../tracked-callback-reference-state.js"; function pushOperation(operationsBySegmentId, segmentId, operation) { const operations = operationsBySegmentId.get(segmentId); if (operations === undefined) { return new Map([ ...operationsBySegmentId, [segmentId, [operation]] ]); } return new Map([ ...operationsBySegmentId, [segmentId, [...operations, operation]] ]); } function createTrackedCallbackFunction(trackedCallbackFunctionContext) { const { sourceCode, trackedCallbackNodes, inheritedCallbackBinding, includeInheritedCallbackBinding, codePath, node } = trackedCallbackFunctionContext; if (!isFunction(node)) { return undefined; } const callbackParameter = trackedCallbackNodes.has(node) ? getIdentifierCallbackParameter(node) : undefined; if (callbackParameter !== undefined) { return { callbackBinding: getTrackedBinding(sourceCode, callbackParameter), callbackName: callbackParameter.name, callbackNode: asRuleNode(callbackParameter), codePath, currentSegments: new Set(), operationsBySegmentId: new Map() }; } if (!includeInheritedCallbackBinding || inheritedCallbackBinding === undefined) { return undefined; } return { callbackBinding: inheritedCallbackBinding, callbackName: undefined, callbackNode: undefined, codePath, currentSegments: new Set(), operationsBySegmentId: new Map() }; } function recordMemberPropertyAssignment(context, recordOperation, node, memberExpression) { const bindingAndProperty = getMemberExpressionBindingAndProperty(context.sourceCode, memberExpression); if (bindingAndProperty === undefined) { return; } recordOperation({ node, propertyName: bindingAndProperty.propertyName, source: null, target: bindingAndProperty.binding, type: 'containerPropertyAssignment' }); } function isDirectTrackedCallbackFunction(trackedFunction) { return trackedFunction.callbackName !== undefined; } function isInheritedTrackedCallbackFunction(trackedFunction) { return trackedFunction.callbackName === undefined; } function reportTrackedFunction(callbackTrackingOptions, trackedFunction) { if (callbackTrackingOptions.includeInheritedCallbackBinding) { const { onTrackedFunctionEnd } = callbackTrackingOptions; onTrackedFunctionEnd(trackedFunction); return; } if (isDirectTrackedCallbackFunction(trackedFunction)) { const { onTrackedFunctionEnd } = callbackTrackingOptions; onTrackedFunctionEnd(trackedFunction); } } function isHandledCallbackObjectArgument(node, handledCallbackNodes) { return node.type === 'ObjectExpression' && node.properties.some(function (property) { return property.type === 'Property' && handledCallbackNodes.has(asRuleNode(property.value)); }); } export function createTrackedCallbackVisitors(context, callbackTrackingOptions) { const trackedCallbackNodes = new WeakSet(); const handledCallbackNodes = new WeakSet(); let currentFunction = null; function recordOperation(operation) { const trackedFunction = currentFunction?.tracked; if (trackedFunction === undefined) { return; } const { operationsBySegmentId: initialOperationsBySegmentId } = trackedFunction; let operationsBySegmentId = initialOperationsBySegmentId; for (const segment of trackedFunction.currentSegments) { operationsBySegmentId = pushOperation(operationsBySegmentId, segment.id, operation); } if (currentFunction !== null) { currentFunction = { node: currentFunction.node, tracked: { ...trackedFunction, operationsBySegmentId }, upper: currentFunction.upper }; } } function updateCurrentTrackedFunction(updateTrackedFunction) { const trackedFunction = currentFunction?.tracked; if (currentFunction !== null && trackedFunction !== undefined) { currentFunction = { ...currentFunction, tracked: updateTrackedFunction(trackedFunction) }; } } function trackMochaCallback(node) { trackedCallbackNodes.add(node); } function trackHandledCallbackNode(node, trackedFunction) { if (callbackTrackingOptions.trackHandledCallbackArguments && isInheritedTrackedCallbackFunction(trackedFunction) && !hasUnhandledReturnPath({ callbackBinding: trackedFunction.callbackBinding, codePath: trackedFunction.codePath, operationsBySegmentId: trackedFunction.operationsBySegmentId, sourceCode: context.sourceCode })) { handledCallbackNodes.add(asRuleNode(node)); } } function hasHandledCallbackArgument(node) { return node.arguments.some(function (argument) { const candidate = argument.type === 'SpreadElement' ? argument.argument : argument; if (handledCallbackNodes.has(asRuleNode(candidate))) { return true; } return isHandledCallbackObjectArgument(asRuleNode(candidate), handledCallbackNodes); }); } return createMochaVisitors(context, { testCaseCallback(visitorContext) { if (visitorContext.modifier === 'pending' && callbackTrackingOptions.ignorePending) { return; } trackMochaCallback(visitorContext.node); }, hookCallback(visitorContext) { trackMochaCallback(visitorContext.node); }, onCodePathStart(codePath, node) { currentFunction = { node, tracked: createTrackedCallbackFunction({ sourceCode: context.sourceCode, trackedCallbackNodes, inheritedCallbackBinding: currentFunction?.tracked?.callbackBinding, includeInheritedCallbackBinding: callbackTrackingOptions.includeInheritedCallbackBinding, codePath, node }), upper: currentFunction }; }, onCodePathEnd() { const endedFunction = currentFunction; const trackedFunction = currentFunction?.tracked; if (trackedFunction !== undefined) { if (endedFunction !== null) { trackHandledCallbackNode(endedFunction.node, trackedFunction); } reportTrackedFunction(callbackTrackingOptions, trackedFunction); } currentFunction = currentFunction?.upper ?? null; }, onCodePathSegmentStart(segment) { updateCurrentTrackedFunction(function (trackedFunction) { return { ...trackedFunction, currentSegments: new Set([...trackedFunction.currentSegments, segment]) }; }); }, onCodePathSegmentEnd(segment) { updateCurrentTrackedFunction(function (trackedFunction) { return { ...trackedFunction, currentSegments: new Set(Array.from(trackedFunction.currentSegments).filter(function (currentSegment) { return currentSegment !== segment; })) }; }); }, 'CallExpression:exit'(node) { if (callbackTrackingOptions.trackHandledCallbackArguments && hasHandledCallbackArgument(node)) { recordOperation({ node, type: 'callbackHandoff' }); } recordOperation({ node, type: 'call' }); }, 'VariableDeclarator:exit'(node) { if (node.id.type !== 'Identifier') { return; } recordOperation({ node, source: node.init === null ? null : asRuleNode(node.init), target: getTrackedBinding(context.sourceCode, node.id), type: 'bindingAssignment' }); }, 'AssignmentExpression:exit'(node) { if (node.left.type === 'Identifier') { recordOperation({ node, source: asRuleNode(node.right), target: getTrackedBinding(context.sourceCode, node.left), type: 'bindingAssignment' }); return; } if (node.left.type !== 'MemberExpression') { return; } const bindingAndProperty = getMemberExpressionBindingAndProperty(context.sourceCode, node.left); if (bindingAndProperty === undefined) { return; } recordOperation({ node, propertyName: bindingAndProperty.propertyName, source: asRuleNode(node.right), target: bindingAndProperty.binding, type: 'containerPropertyAssignment' }); }, 'UpdateExpression:exit'(node) { if (node.argument.type === 'Identifier') { recordOperation({ node, source: null, target: getTrackedBinding(context.sourceCode, node.argument), type: 'bindingAssignment' }); return; } if (node.argument.type !== 'MemberExpression') { return; } recordMemberPropertyAssignment(context, recordOperation, node, node.argument); }, 'UnaryExpression:exit'(node) { if (node.operator !== 'delete' || node.argument.type !== 'MemberExpression') { return; } recordMemberPropertyAssignment(context, recordOperation, node, node.argument); } }); } //# sourceMappingURL=callback-tracking.js.map