UNPKG

eslint-plugin-mocha

Version:

Eslint rules for mocha.

256 lines 9.82 kB
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