astro-ghostcms/.pnpm-store/v3/files/1c/26962d97b273af384316e367420...

318 lines
15 KiB
Plaintext
Raw Normal View History

2024-02-14 14:10:47 +00:00
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.compareNodes = void 0;
const utils_1 = require("@typescript-eslint/utils");
const visitor_keys_1 = require("@typescript-eslint/visitor-keys");
function compareArrays(arrayA, arrayB) {
if (arrayA.length !== arrayB.length) {
return "Invalid" /* NodeComparisonResult.Invalid */;
}
const result = arrayA.every((elA, idx) => {
const elB = arrayB[idx];
if (elA == null || elB == null) {
return elA === elB;
}
return compareUnknownValues(elA, elB) === "Equal" /* NodeComparisonResult.Equal */;
});
if (result) {
return "Equal" /* NodeComparisonResult.Equal */;
}
return "Invalid" /* NodeComparisonResult.Invalid */;
}
function isValidNode(x) {
return (typeof x === 'object' &&
x != null &&
'type' in x &&
typeof x.type === 'string');
}
function isValidChainExpressionToLookThrough(node) {
return (!(node.parent?.type === utils_1.AST_NODE_TYPES.MemberExpression &&
node.parent.object === node) &&
!(node.parent?.type === utils_1.AST_NODE_TYPES.CallExpression &&
node.parent.callee === node) &&
node.type === utils_1.AST_NODE_TYPES.ChainExpression);
}
function compareUnknownValues(valueA, valueB) {
/* istanbul ignore if -- not possible for us to test this - it's just a sanity safeguard */
if (valueA == null || valueB == null) {
if (valueA !== valueB) {
return "Invalid" /* NodeComparisonResult.Invalid */;
}
return "Equal" /* NodeComparisonResult.Equal */;
}
/* istanbul ignore if -- not possible for us to test this - it's just a sanity safeguard */
if (!isValidNode(valueA) || !isValidNode(valueB)) {
return "Invalid" /* NodeComparisonResult.Invalid */;
}
return compareNodes(valueA, valueB);
}
function compareByVisiting(nodeA, nodeB) {
const currentVisitorKeys = visitor_keys_1.visitorKeys[nodeA.type];
/* istanbul ignore if -- not possible for us to test this - it's just a sanity safeguard */
if (currentVisitorKeys == null) {
// we don't know how to visit this node, so assume it's invalid to avoid false-positives / broken fixers
return "Invalid" /* NodeComparisonResult.Invalid */;
}
if (currentVisitorKeys.length === 0) {
// assume nodes with no keys are constant things like keywords
return "Equal" /* NodeComparisonResult.Equal */;
}
for (const key of currentVisitorKeys) {
// @ts-expect-error - dynamic access but it's safe
const nodeAChildOrChildren = nodeA[key];
// @ts-expect-error - dynamic access but it's safe
const nodeBChildOrChildren = nodeB[key];
if (Array.isArray(nodeAChildOrChildren)) {
const arrayA = nodeAChildOrChildren;
const arrayB = nodeBChildOrChildren;
const result = compareArrays(arrayA, arrayB);
if (result !== "Equal" /* NodeComparisonResult.Equal */) {
return "Invalid" /* NodeComparisonResult.Invalid */;
}
// fallthrough to the next key as the key was "equal"
}
else {
const result = compareUnknownValues(nodeAChildOrChildren, nodeBChildOrChildren);
if (result !== "Equal" /* NodeComparisonResult.Equal */) {
return "Invalid" /* NodeComparisonResult.Invalid */;
}
// fallthrough to the next key as the key was "equal"
}
}
return "Equal" /* NodeComparisonResult.Equal */;
}
function compareNodesUncached(nodeA, nodeB) {
if (nodeA.type !== nodeB.type) {
// special cases where nodes are allowed to be non-equal
// look through a chain expression node at the top-level because it only
// exists to delimit the end of an optional chain
//
// a?.b && a.b.c
// ^^^^ ChainExpression, MemberExpression
// ^^^^^ MemberExpression
//
// except for in this class of cases
// (a?.b).c && a.b.c
// because the parentheses have runtime meaning (sad face)
if (isValidChainExpressionToLookThrough(nodeA)) {
return compareNodes(nodeA.expression, nodeB);
}
if (isValidChainExpressionToLookThrough(nodeB)) {
return compareNodes(nodeA, nodeB.expression);
}
// look through the type-only non-null assertion because its existence could
// possibly be replaced by an optional chain instead
//
// a.b! && a.b.c
// ^^^^ TSNonNullExpression
if (nodeA.type === utils_1.AST_NODE_TYPES.TSNonNullExpression) {
return compareNodes(nodeA.expression, nodeB);
}
if (nodeB.type === utils_1.AST_NODE_TYPES.TSNonNullExpression) {
return compareNodes(nodeA, nodeB.expression);
}
// special case for subset optional chains where the node types don't match,
// but we want to try comparing by discarding the "extra" code
//
// a && a.b
// ^ compare this
// a && a()
// ^ compare this
// a.b && a.b()
// ^^^ compare this
// a() && a().b
// ^^^ compare this
// import.meta && import.meta.b
// ^^^^^^^^^^^ compare this
if (nodeA.type === utils_1.AST_NODE_TYPES.CallExpression ||
nodeA.type === utils_1.AST_NODE_TYPES.Identifier ||
nodeA.type === utils_1.AST_NODE_TYPES.MemberExpression ||
nodeA.type === utils_1.AST_NODE_TYPES.MetaProperty) {
switch (nodeB.type) {
case utils_1.AST_NODE_TYPES.MemberExpression:
if (nodeB.property.type === utils_1.AST_NODE_TYPES.PrivateIdentifier) {
// Private identifiers in optional chaining is not currently allowed
// TODO - handle this once TS supports it (https://github.com/microsoft/TypeScript/issues/42734)
return "Invalid" /* NodeComparisonResult.Invalid */;
}
if (compareNodes(nodeA, nodeB.object) !== "Invalid" /* NodeComparisonResult.Invalid */) {
return "Subset" /* NodeComparisonResult.Subset */;
}
return "Invalid" /* NodeComparisonResult.Invalid */;
case utils_1.AST_NODE_TYPES.CallExpression:
if (compareNodes(nodeA, nodeB.callee) !== "Invalid" /* NodeComparisonResult.Invalid */) {
return "Subset" /* NodeComparisonResult.Subset */;
}
return "Invalid" /* NodeComparisonResult.Invalid */;
default:
return "Invalid" /* NodeComparisonResult.Invalid */;
}
}
return "Invalid" /* NodeComparisonResult.Invalid */;
}
switch (nodeA.type) {
// these expressions create a new instance each time - so it makes no sense to compare the chain
case utils_1.AST_NODE_TYPES.ArrayExpression:
case utils_1.AST_NODE_TYPES.ArrowFunctionExpression:
case utils_1.AST_NODE_TYPES.ClassExpression:
case utils_1.AST_NODE_TYPES.FunctionExpression:
case utils_1.AST_NODE_TYPES.JSXElement:
case utils_1.AST_NODE_TYPES.JSXFragment:
case utils_1.AST_NODE_TYPES.NewExpression:
case utils_1.AST_NODE_TYPES.ObjectExpression:
return "Invalid" /* NodeComparisonResult.Invalid */;
// chaining from assignments could change the value irrevocably - so it makes no sense to compare the chain
case utils_1.AST_NODE_TYPES.AssignmentExpression:
return "Invalid" /* NodeComparisonResult.Invalid */;
case utils_1.AST_NODE_TYPES.CallExpression: {
const nodeBCall = nodeB;
// check for cases like
// foo() && foo()(bar)
// ^^^^^ nodeA
// ^^^^^^^^^^ nodeB
// we don't want to check the arguments in this case
const aSubsetOfB = compareNodes(nodeA, nodeBCall.callee);
if (aSubsetOfB !== "Invalid" /* NodeComparisonResult.Invalid */) {
return "Subset" /* NodeComparisonResult.Subset */;
}
const calleeCompare = compareNodes(nodeA.callee, nodeBCall.callee);
if (calleeCompare !== "Equal" /* NodeComparisonResult.Equal */) {
return "Invalid" /* NodeComparisonResult.Invalid */;
}
// NOTE - we purposely ignore optional flag because for our purposes
// foo?.bar() && foo.bar?.()?.baz
// or
// foo.bar() && foo?.bar?.()?.baz
// are going to be exactly the same
const argumentCompare = compareArrays(nodeA.arguments, nodeBCall.arguments);
if (argumentCompare !== "Equal" /* NodeComparisonResult.Equal */) {
return "Invalid" /* NodeComparisonResult.Invalid */;
}
const typeParamCompare = compareNodes(nodeA.typeArguments, nodeBCall.typeArguments);
if (typeParamCompare === "Equal" /* NodeComparisonResult.Equal */) {
return "Equal" /* NodeComparisonResult.Equal */;
}
return "Invalid" /* NodeComparisonResult.Invalid */;
}
case utils_1.AST_NODE_TYPES.ChainExpression:
// special case handling for ChainExpression because it's allowed to be a subset
return compareNodes(nodeA, nodeB.expression);
case utils_1.AST_NODE_TYPES.Identifier:
case utils_1.AST_NODE_TYPES.PrivateIdentifier:
if (nodeA.name === nodeB.name) {
return "Equal" /* NodeComparisonResult.Equal */;
}
return "Invalid" /* NodeComparisonResult.Invalid */;
case utils_1.AST_NODE_TYPES.Literal: {
const nodeBLiteral = nodeB;
if (nodeA.raw === nodeBLiteral.raw &&
nodeA.value === nodeBLiteral.value) {
return "Equal" /* NodeComparisonResult.Equal */;
}
return "Invalid" /* NodeComparisonResult.Invalid */;
}
case utils_1.AST_NODE_TYPES.MemberExpression: {
const nodeBMember = nodeB;
if (nodeBMember.property.type === utils_1.AST_NODE_TYPES.PrivateIdentifier) {
// Private identifiers in optional chaining is not currently allowed
// TODO - handle this once TS supports it (https://github.com/microsoft/TypeScript/issues/42734)
return "Invalid" /* NodeComparisonResult.Invalid */;
}
// check for cases like
// foo.bar && foo.bar.baz
// ^^^^^^^ nodeA
// ^^^^^^^^^^^ nodeB
// result === Equal
//
// foo.bar && foo.bar.baz.bam
// ^^^^^^^ nodeA
// ^^^^^^^^^^^^^^^ nodeB
// result === Subset
//
// we don't want to check the property in this case
const aSubsetOfB = compareNodes(nodeA, nodeBMember.object);
if (aSubsetOfB !== "Invalid" /* NodeComparisonResult.Invalid */) {
return "Subset" /* NodeComparisonResult.Subset */;
}
if (nodeA.computed !== nodeBMember.computed) {
return "Invalid" /* NodeComparisonResult.Invalid */;
}
// NOTE - we purposely ignore optional flag because for our purposes
// foo?.bar && foo.bar?.baz
// or
// foo.bar && foo?.bar?.baz
// are going to be exactly the same
const objectCompare = compareNodes(nodeA.object, nodeBMember.object);
if (objectCompare !== "Equal" /* NodeComparisonResult.Equal */) {
return "Invalid" /* NodeComparisonResult.Invalid */;
}
return compareNodes(nodeA.property, nodeBMember.property);
}
case utils_1.AST_NODE_TYPES.TSTemplateLiteralType:
case utils_1.AST_NODE_TYPES.TemplateLiteral: {
const nodeBTemplate = nodeB;
const areQuasisEqual = nodeA.quasis.length === nodeBTemplate.quasis.length &&
nodeA.quasis.every((elA, idx) => {
const elB = nodeBTemplate.quasis[idx];
return elA.value.cooked === elB.value.cooked;
});
if (!areQuasisEqual) {
return "Invalid" /* NodeComparisonResult.Invalid */;
}
return "Equal" /* NodeComparisonResult.Equal */;
}
case utils_1.AST_NODE_TYPES.TemplateElement: {
const nodeBElement = nodeB;
if (nodeA.value.cooked === nodeBElement.value.cooked) {
return "Equal" /* NodeComparisonResult.Equal */;
}
return "Invalid" /* NodeComparisonResult.Invalid */;
}
// these aren't actually valid expressions.
// https://github.com/typescript-eslint/typescript-eslint/blob/20d7caee35ab84ae6381fdf04338c9e2b9e2bc48/packages/ast-spec/src/unions/Expression.ts#L37-L43
case utils_1.AST_NODE_TYPES.ArrayPattern:
case utils_1.AST_NODE_TYPES.ObjectPattern:
/* istanbul ignore next */
return "Invalid" /* NodeComparisonResult.Invalid */;
// update expression returns a number and also changes the value each time - so it makes no sense to compare the chain
case utils_1.AST_NODE_TYPES.UpdateExpression:
return "Invalid" /* NodeComparisonResult.Invalid */;
// yield returns the value passed to the `next` function, so it may not be the same each time - so it makes no sense to compare the chain
case utils_1.AST_NODE_TYPES.YieldExpression:
return "Invalid" /* NodeComparisonResult.Invalid */;
// general-case automatic handling of nodes to save us implementing every
// single case by hand. This just iterates the visitor keys to recursively
// check the children.
//
// Any specific logic cases or short-circuits should be listed as separate
// cases so that they don't fall into this generic handling
default:
return compareByVisiting(nodeA, nodeB);
}
}
const COMPARE_NODES_CACHE = new WeakMap();
/**
* Compares two nodes' ASTs to determine if the A is equal to or a subset of B
*/
function compareNodes(nodeA, nodeB) {
if (nodeA == null || nodeB == null) {
if (nodeA !== nodeB) {
return "Invalid" /* NodeComparisonResult.Invalid */;
}
return "Equal" /* NodeComparisonResult.Equal */;
}
const cached = COMPARE_NODES_CACHE.get(nodeA)?.get(nodeB);
if (cached) {
return cached;
}
const result = compareNodesUncached(nodeA, nodeB);
let mapA = COMPARE_NODES_CACHE.get(nodeA);
if (mapA == null) {
mapA = new WeakMap();
COMPARE_NODES_CACHE.set(nodeA, mapA);
}
mapA.set(nodeB, result);
return result;
}
exports.compareNodes = compareNodes;
//# sourceMappingURL=compareNodes.js.map