eslint-plugin-mocha
Version:
Eslint rules for mocha.
240 lines • 10.5 kB
JavaScript
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