eslint-plugin-mocha
Version:
Eslint rules for mocha.
256 lines • 9.82 kB
JavaScript
import { findVariable, getStringIfConstant } from '@eslint-community/eslint-utils';
import { asRuleNode } from "./ast/rule-node.js";
const dynamicPropertyName = '<dynamic>';
export function getTrackedBinding(sourceCode, node) {
return findVariable(sourceCode.getScope(asRuleNode(node)), node.name) ?? node.name;
}
function hasPendingPathStates(states) {
return states.length > 0;
}
function getPropertyNode(node) {
if (node.type === 'MemberExpression') {
return node.property;
}
return node.key;
}
function getComputedPropertyName(sourceCode, node) {
return getStringIfConstant(asRuleNode(getPropertyNode(node)), sourceCode.getScope(asRuleNode(node))) ?? undefined;
}
function getNamedPropertyName(node) {
const keyNode = getPropertyNode(node);
if (keyNode.type === 'Identifier') {
return keyNode.name;
}
if (keyNode.type === 'Literal' && keyNode.value !== null) {
return String(keyNode.value);
}
return undefined;
}
function getStaticPropertyName(sourceCode, node) {
return node.computed
? getComputedPropertyName(sourceCode, node)
: getNamedPropertyName(node);
}
export function getMemberExpressionBindingAndProperty(sourceCode, node) {
if (node.object.type !== 'Identifier') {
return undefined;
}
return {
binding: getTrackedBinding(sourceCode, node.object),
propertyName: getStaticPropertyName(sourceCode, node)
};
}
export function createHandledPendingPathState() {
return {
aliasBindings: new Set(),
containerPropertiesByBinding: new Map(),
hasUnhandledPath: false
};
}
export function createInitialPendingPathState(callbackBinding) {
return {
aliasBindings: new Set([callbackBinding]),
containerPropertiesByBinding: new Map(),
hasUnhandledPath: true
};
}
export function cloneTrackedContainerProperties(containerPropertiesByBinding) {
return new Map(Array.from(containerPropertiesByBinding, function ([binding, properties]) {
return [binding, new Set(properties)];
}));
}
export function clonePendingPathState(state) {
return {
aliasBindings: new Set(state.aliasBindings),
containerPropertiesByBinding: cloneTrackedContainerProperties(state.containerPropertiesByBinding),
hasUnhandledPath: state.hasUnhandledPath
};
}
export function haveSameTrackedBindings(left, right) {
if (left.size !== right.size) {
return false;
}
return Array.from(left).every(function (binding) {
return right.has(binding);
});
}
export function haveSameTrackedContainerProperties(left, right) {
if (left.size !== right.size) {
return false;
}
return Array.from(left).every(function ([binding, properties]) {
const otherProperties = right.get(binding);
if (properties.size !== otherProperties?.size) {
return false;
}
return Array.from(properties).every(function (property) {
return otherProperties.has(property);
});
});
}
export function haveSamePendingPathStates(left, right) {
return left.hasUnhandledPath === right.hasUnhandledPath &&
haveSameTrackedBindings(left.aliasBindings, right.aliasBindings) &&
haveSameTrackedContainerProperties(left.containerPropertiesByBinding, right.containerPropertiesByBinding);
}
function intersectBindingSets(states) {
const [firstState, ...otherStates] = states;
const bindings = new Set(firstState.aliasBindings);
for (const binding of Array.from(bindings)) {
const isSharedBinding = otherStates.every(function (state) {
return state.aliasBindings.has(binding);
});
if (!isSharedBinding) {
bindings.delete(binding);
}
}
return bindings;
}
function sharedPropertiesForBinding(binding, firstProperties, otherStates) {
const sharedProperties = new Set(firstProperties);
for (const property of Array.from(sharedProperties)) {
const isSharedProperty = otherStates.every(function (state) {
return state.containerPropertiesByBinding.get(binding)?.has(property) === true;
});
if (!isSharedProperty) {
sharedProperties.delete(property);
}
}
return sharedProperties;
}
function intersectTrackedContainerPropertiesByBinding(states) {
const [firstState, ...otherStates] = states;
const sharedPropertiesByBinding = new Map();
for (const [binding, properties] of firstState.containerPropertiesByBinding) {
const sharedProperties = sharedPropertiesForBinding(binding, properties, otherStates);
if (sharedProperties.size > 0) {
sharedPropertiesByBinding.set(binding, sharedProperties);
}
}
return sharedPropertiesByBinding;
}
export function mergeIncomingPendingPathStates(previousStates) {
const unhandledStates = previousStates.filter(function (state) {
return state.hasUnhandledPath;
});
if (!hasPendingPathStates(unhandledStates)) {
return createHandledPendingPathState();
}
return {
aliasBindings: intersectBindingSets(unhandledStates),
containerPropertiesByBinding: intersectTrackedContainerPropertiesByBinding(unhandledStates),
hasUnhandledPath: true
};
}
function getContainerProperties(state, binding) {
return state.containerPropertiesByBinding.get(binding);
}
function isTrackedPropertyAccess(sourceCode, node, state) {
const bindingAndProperty = getMemberExpressionBindingAndProperty(sourceCode, node);
if (bindingAndProperty === undefined) {
return false;
}
const properties = getContainerProperties(state, bindingAndProperty.binding);
if (properties === undefined) {
return false;
}
if (bindingAndProperty.propertyName === undefined) {
return properties.has(dynamicPropertyName);
}
return properties.has(bindingAndProperty.propertyName);
}
export function isTrackedCallbackExpression(sourceCode, node, state) {
if (node.type === 'Identifier') {
return state.aliasBindings.has(getTrackedBinding(sourceCode, node));
}
return node.type === 'MemberExpression' && isTrackedPropertyAccess(sourceCode, node, state);
}
export function collectTrackedCallbackObjectProperties(sourceCode, node, state) {
const trackedProperties = new Set();
node.properties.forEach(function collectTrackedCallbackProperty(property) {
if (property.type === 'Property' && property.kind === 'init') {
const propertyName = getStaticPropertyName(sourceCode, property);
const isTrackedCallback = propertyName !== undefined &&
isTrackedCallbackExpression(sourceCode, asRuleNode(property.value), state);
if (isTrackedCallback) {
trackedProperties.add(propertyName);
}
}
});
return trackedProperties;
}
export function getTrackedContainerPropertiesFromExpression(sourceCode, node, state) {
if (node.type === 'Identifier') {
const properties = getContainerProperties(state, getTrackedBinding(sourceCode, node));
return properties === undefined ? undefined : new Set(properties);
}
if (node.type === 'ObjectExpression') {
const trackedProperties = collectTrackedCallbackObjectProperties(sourceCode, node, state);
return trackedProperties.size > 0 ? trackedProperties : undefined;
}
return undefined;
}
export function applyBindingSourceValue(sourceCode, state, source, target) {
if (source === null) {
return clonePendingPathState(state);
}
if (isTrackedCallbackExpression(sourceCode, source, state)) {
return {
...state,
aliasBindings: new Set([...state.aliasBindings, target])
};
}
const containerProperties = getTrackedContainerPropertiesFromExpression(sourceCode, source, state);
if (containerProperties !== undefined) {
return {
...state,
containerPropertiesByBinding: new Map([
...state.containerPropertiesByBinding,
[target, containerProperties]
])
};
}
return clonePendingPathState(state);
}
function nextPropertiesForAssignment(currentProperties, propertyName) {
const nextProperties = new Set(currentProperties);
if (propertyName === undefined) {
nextProperties.clear();
}
else {
nextProperties.delete(propertyName);
}
return nextProperties;
}
export function applyContainerPropertyAssignment(sourceCode, state, assignment) {
const { propertyName, source, target } = assignment;
const nextState = clonePendingPathState(state);
const nextProperties = nextPropertiesForAssignment(nextState.containerPropertiesByBinding.get(target), propertyName);
const shouldTrackProperty = source !== null &&
isTrackedCallbackExpression(sourceCode, source, nextState);
if (shouldTrackProperty) {
return {
...nextState,
containerPropertiesByBinding: new Map([
...nextState.containerPropertiesByBinding,
[target, new Set([...nextProperties, propertyName ?? dynamicPropertyName])]
])
};
}
if (nextProperties.size > 0) {
return {
...nextState,
containerPropertiesByBinding: new Map([
...nextState.containerPropertiesByBinding,
[target, nextProperties]
])
};
}
return {
...nextState,
containerPropertiesByBinding: new Map(Array.from(nextState.containerPropertiesByBinding).filter(function ([binding]) {
return binding !== target;
}))
};
}
//# sourceMappingURL=tracked-callback-reference-state.js.map