UNPKG

eslint-plugin-mocha

Version:

Eslint rules for mocha.

240 lines 10.5 kB
import { extractMemberExpressionPath, isConstantPath } from "./ast/member-expression.js"; import { asRuleNode } from "./ast/rule-node.js"; import { stripCallExpressions } from "./mocha/name-details.js"; import { cloneTrackedContainerProperties, collectTrackedCallbackObjectProperties, getTrackedBinding, haveSameTrackedBindings, haveSameTrackedContainerProperties, isTrackedCallbackExpression } from "./tracked-callback-reference-state.js"; const dynamicPropertyName = '<dynamic>'; const knownSingleCallbackDelegates = new Set(('setImmediate setTimeout queueMicrotask process.nextTick ' + 'global.setImmediate global.setTimeout global.queueMicrotask ' + 'globalThis.setImmediate globalThis.setTimeout globalThis.queueMicrotask ' + 'window.queueMicrotask') .split(' ')); function createEmptyReferenceState() { return { aliasBindings: new Set(), containerPropertiesByBinding: new Map() }; } function createInitialPathState(callbackBinding) { return { callbackHandled: false, handledReferences: createEmptyReferenceState(), unhandledReferences: { aliasBindings: new Set([callbackBinding]), containerPropertiesByBinding: new Map() } }; } function cloneReferenceState(state) { return { aliasBindings: new Set(state.aliasBindings), containerPropertiesByBinding: cloneTrackedContainerProperties(state.containerPropertiesByBinding) }; } export function clonePathState(state) { return { callbackHandled: state.callbackHandled, handledReferences: cloneReferenceState(state.handledReferences), unhandledReferences: cloneReferenceState(state.unhandledReferences) }; } function areReferenceStatesSame(left, right) { return haveSameTrackedBindings(left.aliasBindings, right.aliasBindings) && haveSameTrackedContainerProperties(left.containerPropertiesByBinding, right.containerPropertiesByBinding); } export function arePathStatesSame(left, right) { return left?.callbackHandled === right.callbackHandled && areReferenceStatesSame(left.handledReferences, right.handledReferences) && areReferenceStatesSame(left.unhandledReferences, right.unhandledReferences); } function getContainerPropertiesFromExpression(sourceCode, node, state) { if (node.type === 'Identifier') { const trackedProperties = state.containerPropertiesByBinding.get(getTrackedBinding(sourceCode, node)); return trackedProperties === undefined ? undefined : new Set(trackedProperties); } if (node.type !== 'ObjectExpression') { return undefined; } const trackedProperties = collectTrackedCallbackObjectProperties(sourceCode, node, state); return trackedProperties.size > 0 ? trackedProperties : undefined; } function clearBindingReferenceState(state, target) { return { aliasBindings: new Set(Array.from(state.aliasBindings).filter(function (binding) { return binding !== target; })), containerPropertiesByBinding: new Map(Array.from(state.containerPropertiesByBinding).filter(function ([binding]) { return binding !== target; })) }; } function addAliasBindingReferenceState(state, target) { return { ...state, aliasBindings: new Set([...state.aliasBindings, target]) }; } function addContainerPropertyReferenceState(state, target, trackedProperties) { return { ...state, containerPropertiesByBinding: new Map([ ...state.containerPropertiesByBinding, [target, new Set(trackedProperties)] ]) }; } function assignBindingReferenceState(sourceCode, state, source, target) { if (source === null) { return cloneReferenceState(state); } if (isTrackedCallbackExpression(sourceCode, source, state)) { return addAliasBindingReferenceState(state, target); } const trackedProperties = getContainerPropertiesFromExpression(sourceCode, source, state); return trackedProperties === undefined ? cloneReferenceState(state) : addContainerPropertyReferenceState(state, target, trackedProperties); } function updateBindingReferenceState(sourceCode, state, operation) { const clearedState = clearBindingReferenceState(state, operation.target); return assignBindingReferenceState(sourceCode, clearedState, operation.source, operation.target); } function updateContainerPropertyReferenceState(sourceCode, state, operation) { const nextState = cloneReferenceState(state); const trackedProperty = operation.propertyName ?? dynamicPropertyName; const nextProperties = new Set(nextState.containerPropertiesByBinding.get(operation.target)); nextProperties.delete(trackedProperty); if (operation.source !== null && isTrackedCallbackExpression(sourceCode, operation.source, nextState)) { nextProperties.add(trackedProperty); } if (nextProperties.size > 0) { return { ...nextState, containerPropertiesByBinding: new Map([ ...nextState.containerPropertiesByBinding, [operation.target, nextProperties] ]) }; } return { ...nextState, containerPropertiesByBinding: new Map(Array.from(nextState.containerPropertiesByBinding).filter(function ([binding]) { return binding !== operation.target; })) }; } function getNormalizedCallPath(sourceCode, node) { const callPath = extractMemberExpressionPath(sourceCode, node); if (!isConstantPath(callPath)) { return undefined; } return stripCallExpressions(callPath).join('.'); } function isKnownSingleCallbackDelegateCall(sourceCode, node, state) { const normalizedCallPath = String(getNormalizedCallPath(sourceCode, asRuleNode(node.callee))); if (!knownSingleCallbackDelegates.has(normalizedCallPath)) { return false; } return node.arguments.some(function (argument) { const candidate = argument.type === 'SpreadElement' ? argument.argument : argument; return isTrackedCallbackExpression(sourceCode, asRuleNode(candidate), state); }); } function isCallbackHandlingCall(sourceCode, operation, state) { return isTrackedCallbackExpression(sourceCode, asRuleNode(operation.node.callee), state) || isKnownSingleCallbackDelegateCall(sourceCode, operation.node, state); } function mergeAliasBindings(states) { const aliasBindings = new Set(); for (const state of states) { for (const binding of state.aliasBindings) { aliasBindings.add(binding); } } return aliasBindings; } function mergeContainerProperties(states) { const containerPropertiesByBinding = new Map(); for (const state of states) { for (const [binding, properties] of state.containerPropertiesByBinding) { const currentProperties = containerPropertiesByBinding.get(binding) ?? new Set(); for (const property of properties) { currentProperties.add(property); } containerPropertiesByBinding.set(binding, currentProperties); } } return containerPropertiesByBinding; } function mergeReferenceStates(states) { return { aliasBindings: mergeAliasBindings(states), containerPropertiesByBinding: mergeContainerProperties(states) }; } function markCallbackHandled(state) { return { callbackHandled: true, handledReferences: mergeReferenceStates([ state.handledReferences, state.unhandledReferences ]), unhandledReferences: cloneReferenceState(state.unhandledReferences) }; } export function updatePathState(sourceCode, state, operation) { if (operation.type === 'callbackHandoff') { return markCallbackHandled(state); } if (operation.type === 'bindingAssignment') { return { callbackHandled: state.callbackHandled, handledReferences: updateBindingReferenceState(sourceCode, state.handledReferences, operation), unhandledReferences: updateBindingReferenceState(sourceCode, state.unhandledReferences, operation) }; } if (operation.type === 'containerPropertyAssignment') { return { callbackHandled: state.callbackHandled, handledReferences: updateContainerPropertyReferenceState(sourceCode, state.handledReferences, operation), unhandledReferences: updateContainerPropertyReferenceState(sourceCode, state.unhandledReferences, operation) }; } if (!isCallbackHandlingCall(sourceCode, operation, state.unhandledReferences)) { return clonePathState(state); } return markCallbackHandled(state); } function isCallbackHandlingCallInAnyTrackedReferenceState(sourceCode, state, operation) { return operation.type === 'call' && (isCallbackHandlingCall(sourceCode, operation, state.handledReferences) || isCallbackHandlingCall(sourceCode, operation, state.unhandledReferences)); } export function getCodeAfterCallbackHandlingNode(sourceCode, state, operation) { if (operation.type === 'callbackHandoff') { return undefined; } return state.callbackHandled && !isCallbackHandlingCallInAnyTrackedReferenceState(sourceCode, state, operation) ? operation.node : undefined; } function mergeIncomingPathStates(callbackBinding, previousStates) { if (previousStates.length === 0) { return createInitialPathState(callbackBinding); } return { callbackHandled: previousStates.some(function (state) { return state.callbackHandled; }), handledReferences: mergeReferenceStates(previousStates.map(function (state) { return state.handledReferences; })), unhandledReferences: mergeReferenceStates(previousStates.map(function (state) { return state.unhandledReferences; })) }; } export function createEntryState(context, segment, exitStatesBySegmentId) { if (segment.id === context.codePath.initialSegment.id) { return createInitialPathState(context.callbackBinding); } return mergeIncomingPathStates(context.callbackBinding, segment.prevSegments.map(function (previousSegment) { return exitStatesBySegmentId.get(previousSegment.id) ?? createInitialPathState(context.callbackBinding); })); } //# sourceMappingURL=callback-handling-state.js.map