Skip to content

Commit afb23c0

Browse files
committed
refactor: begin splitting up large utils file
1 parent 26e7357 commit afb23c0

File tree

2 files changed

+358
-349
lines changed

2 files changed

+358
-349
lines changed

src/jsdocUtils.js

Lines changed: 4 additions & 349 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ import {
77
closureTags,
88
typeScriptTags,
99
} from './tagNames';
10+
import {
11+
hasReturnValue,
12+
hasValueOrExecutorHasNonEmptyResolveValue,
13+
} from './utils/hasReturnValue';
1014

1115
/**
1216
* @typedef {"jsdoc"|"typescript"|"closure"} ParserMode
@@ -669,355 +673,6 @@ const tagMissingRequiredTypeOrNamepath = (tag, tagMap = tagStructure) => {
669673
return mustHaveEither && !hasEither && !mustHaveTypePosition;
670674
};
671675

672-
/**
673-
* Checks if a node is a promise but has no resolve value or an empty value.
674-
* An `undefined` resolve does not count.
675-
*
676-
* @param {object} node
677-
* @returns {boolean}
678-
*/
679-
const isNewPromiseExpression = (node) => {
680-
return node && node.type === 'NewExpression' && node.callee.type === 'Identifier' &&
681-
node.callee.name === 'Promise';
682-
};
683-
684-
const isVoidPromise = (node) => {
685-
return node?.typeParameters?.params?.[0]?.type === 'TSVoidKeyword';
686-
};
687-
688-
/**
689-
* @callback PromiseFilter
690-
* @param {object} node
691-
* @returns {boolean}
692-
*/
693-
694-
/**
695-
* Checks if a node has a return statement. Void return does not count.
696-
*
697-
* @param {object} node
698-
* @param {PromiseFilter} promFilter
699-
* @returns {boolean|Node}
700-
*/
701-
// eslint-disable-next-line complexity
702-
const hasReturnValue = (node, promFilter) => {
703-
if (!node) {
704-
return false;
705-
}
706-
707-
switch (node.type) {
708-
case 'TSFunctionType':
709-
case 'TSMethodSignature':
710-
return ![
711-
'TSVoidKeyword',
712-
'TSUndefinedKeyword',
713-
].includes(node?.returnType?.typeAnnotation?.type);
714-
case 'MethodDefinition':
715-
return hasReturnValue(node.value, promFilter);
716-
case 'FunctionExpression':
717-
case 'FunctionDeclaration':
718-
case 'ArrowFunctionExpression': {
719-
return node.expression && (!isNewPromiseExpression(node.body) || !isVoidPromise(node.body)) ||
720-
hasReturnValue(node.body, promFilter);
721-
}
722-
723-
case 'BlockStatement': {
724-
return node.body.some((bodyNode) => {
725-
return bodyNode.type !== 'FunctionDeclaration' && hasReturnValue(bodyNode, promFilter);
726-
});
727-
}
728-
729-
case 'LabeledStatement':
730-
case 'WhileStatement':
731-
case 'DoWhileStatement':
732-
case 'ForStatement':
733-
case 'ForInStatement':
734-
case 'ForOfStatement':
735-
case 'WithStatement': {
736-
return hasReturnValue(node.body, promFilter);
737-
}
738-
739-
case 'IfStatement': {
740-
return hasReturnValue(node.consequent, promFilter) || hasReturnValue(node.alternate, promFilter);
741-
}
742-
743-
case 'TryStatement': {
744-
return hasReturnValue(node.block, promFilter) ||
745-
hasReturnValue(node.handler && node.handler.body, promFilter) ||
746-
hasReturnValue(node.finalizer, promFilter);
747-
}
748-
749-
case 'SwitchStatement': {
750-
return node.cases.some(
751-
(someCase) => {
752-
return someCase.consequent.some((nde) => {
753-
return hasReturnValue(nde, promFilter);
754-
});
755-
},
756-
);
757-
}
758-
759-
case 'ReturnStatement': {
760-
// void return does not count.
761-
if (node.argument === null) {
762-
return false;
763-
}
764-
765-
if (promFilter && isNewPromiseExpression(node.argument)) {
766-
// Let caller decide how to filter, but this is, at the least,
767-
// a return of sorts and truthy
768-
return promFilter(node.argument);
769-
}
770-
771-
return true;
772-
}
773-
774-
default: {
775-
return false;
776-
}
777-
}
778-
};
779-
780-
/**
781-
* Avoids further checking child nodes if a nested function shadows the
782-
* resolver, but otherwise, if name is used (by call or passed in as an
783-
* argument to another function), will be considered as non-empty.
784-
*
785-
* This could check for redeclaration of the resolver, but as such is
786-
* unlikely, we avoid the performance cost of checking everywhere for
787-
* (re)declarations or assignments.
788-
*
789-
* @param {AST} node
790-
* @param {string} resolverName
791-
* @returns {boolean}
792-
*/
793-
// eslint-disable-next-line complexity
794-
const hasNonEmptyResolverCall = (node, resolverName) => {
795-
if (!node) {
796-
return false;
797-
}
798-
799-
// Arrow function without block
800-
switch (node.type) {
801-
// istanbul ignore next -- In Babel?
802-
case 'OptionalCallExpression':
803-
case 'CallExpression':
804-
return node.callee.name === resolverName && (
805-
806-
// Implicit or explicit undefined
807-
node.arguments.length > 1 || node.arguments[0] !== undefined
808-
) ||
809-
node.arguments.some((nde) => {
810-
// Being passed in to another function (which might invoke it)
811-
return nde.type === 'Identifier' && nde.name === resolverName ||
812-
813-
// Handle nested items
814-
hasNonEmptyResolverCall(nde, resolverName);
815-
});
816-
case 'ChainExpression':
817-
case 'Decorator':
818-
case 'ExpressionStatement':
819-
return hasNonEmptyResolverCall(node.expression, resolverName);
820-
case 'ClassBody':
821-
case 'BlockStatement':
822-
return node.body.some((bodyNode) => {
823-
return hasNonEmptyResolverCall(bodyNode, resolverName);
824-
});
825-
case 'FunctionExpression':
826-
case 'FunctionDeclaration':
827-
case 'ArrowFunctionExpression': {
828-
// Shadowing
829-
if (node.params[0]?.name === resolverName) {
830-
return false;
831-
}
832-
833-
return hasNonEmptyResolverCall(node.body, resolverName);
834-
}
835-
836-
case 'LabeledStatement':
837-
case 'WhileStatement':
838-
case 'DoWhileStatement':
839-
case 'ForStatement':
840-
case 'ForInStatement':
841-
case 'ForOfStatement':
842-
case 'WithStatement': {
843-
return hasNonEmptyResolverCall(node.body, resolverName);
844-
}
845-
846-
case 'ConditionalExpression':
847-
case 'IfStatement': {
848-
return hasNonEmptyResolverCall(node.test, resolverName) ||
849-
hasNonEmptyResolverCall(node.consequent, resolverName) ||
850-
hasNonEmptyResolverCall(node.alternate, resolverName);
851-
}
852-
853-
case 'TryStatement': {
854-
return hasNonEmptyResolverCall(node.block, resolverName) ||
855-
hasNonEmptyResolverCall(node.handler && node.handler.body, resolverName) ||
856-
hasNonEmptyResolverCall(node.finalizer, resolverName);
857-
}
858-
859-
case 'SwitchStatement': {
860-
return node.cases.some(
861-
(someCase) => {
862-
return someCase.consequent.some((nde) => {
863-
return hasNonEmptyResolverCall(nde, resolverName);
864-
});
865-
},
866-
);
867-
}
868-
869-
case 'ArrayPattern':
870-
case 'ArrayExpression':
871-
return node.elements.some((element) => {
872-
return hasNonEmptyResolverCall(element, resolverName);
873-
});
874-
875-
case 'AssignmentPattern':
876-
return hasNonEmptyResolverCall(node.right, resolverName);
877-
878-
case 'AssignmentExpression':
879-
case 'BinaryExpression':
880-
case 'LogicalExpression': {
881-
return hasNonEmptyResolverCall(node.left, resolverName) ||
882-
hasNonEmptyResolverCall(node.right, resolverName);
883-
}
884-
885-
// Comma
886-
case 'SequenceExpression':
887-
case 'TemplateLiteral':
888-
return node.expressions.some((subExpression) => {
889-
return hasNonEmptyResolverCall(subExpression, resolverName);
890-
});
891-
892-
case 'ObjectPattern':
893-
case 'ObjectExpression':
894-
return node.properties.some((property) => {
895-
return hasNonEmptyResolverCall(property, resolverName);
896-
});
897-
// istanbul ignore next -- In Babel?
898-
case 'ClassMethod':
899-
case 'MethodDefinition':
900-
return node.decorators && node.decorators.some((decorator) => {
901-
return hasNonEmptyResolverCall(decorator, resolverName);
902-
}) ||
903-
node.computed && hasNonEmptyResolverCall(node.key, resolverName) ||
904-
hasNonEmptyResolverCall(node.value, resolverName);
905-
906-
// istanbul ignore next -- In Babel?
907-
case 'ObjectProperty':
908-
/* eslint-disable no-fallthrough */
909-
// istanbul ignore next -- In Babel?
910-
case 'PropertyDefinition':
911-
// istanbul ignore next -- In Babel?
912-
case 'ClassProperty':
913-
/* eslint-enable no-fallthrough */
914-
case 'Property':
915-
return node.computed && hasNonEmptyResolverCall(node.key, resolverName) ||
916-
hasNonEmptyResolverCall(node.value, resolverName);
917-
// istanbul ignore next -- In Babel?
918-
case 'ObjectMethod':
919-
// istanbul ignore next -- In Babel?
920-
return node.computed && hasNonEmptyResolverCall(node.key, resolverName) ||
921-
node.arguments.some((nde) => {
922-
return hasNonEmptyResolverCall(nde, resolverName);
923-
});
924-
925-
case 'ClassExpression':
926-
case 'ClassDeclaration':
927-
return hasNonEmptyResolverCall(node.body, resolverName);
928-
929-
case 'AwaitExpression':
930-
case 'SpreadElement':
931-
case 'UnaryExpression':
932-
case 'YieldExpression':
933-
return hasNonEmptyResolverCall(node.argument, resolverName);
934-
935-
case 'VariableDeclaration': {
936-
return node.declarations.some((nde) => {
937-
return hasNonEmptyResolverCall(nde, resolverName);
938-
});
939-
}
940-
941-
case 'VariableDeclarator': {
942-
return hasNonEmptyResolverCall(node.id, resolverName) ||
943-
hasNonEmptyResolverCall(node.init, resolverName);
944-
}
945-
946-
case 'TaggedTemplateExpression':
947-
return hasNonEmptyResolverCall(node.quasi, resolverName);
948-
949-
// ?.
950-
// istanbul ignore next -- In Babel?
951-
case 'OptionalMemberExpression':
952-
case 'MemberExpression':
953-
return hasNonEmptyResolverCall(node.object, resolverName) ||
954-
hasNonEmptyResolverCall(node.property, resolverName);
955-
956-
// istanbul ignore next -- In Babel?
957-
case 'Import':
958-
case 'ImportExpression':
959-
return hasNonEmptyResolverCall(node.source, resolverName);
960-
961-
case 'ReturnStatement': {
962-
if (node.argument === null) {
963-
return false;
964-
}
965-
966-
return hasNonEmptyResolverCall(node.argument, resolverName);
967-
}
968-
969-
/*
970-
// Shouldn't need to parse literals/literal components, etc.
971-
972-
case 'Identifier':
973-
case 'TemplateElement':
974-
case 'Super':
975-
// Exports not relevant in this context
976-
*/
977-
default:
978-
return false;
979-
}
980-
};
981-
982-
/**
983-
* Checks if a Promise executor has no resolve value or an empty value.
984-
* An `undefined` resolve does not count.
985-
*
986-
* @param {object} node
987-
* @param {boolean} anyPromiseAsReturn
988-
* @returns {boolean}
989-
*/
990-
const hasValueOrExecutorHasNonEmptyResolveValue = (node, anyPromiseAsReturn) => {
991-
return hasReturnValue(node, (prom) => {
992-
if (anyPromiseAsReturn) {
993-
return true;
994-
}
995-
996-
if (isVoidPromise(prom)) {
997-
return false;
998-
}
999-
1000-
const [
1001-
{
1002-
params,
1003-
body,
1004-
} = {},
1005-
] = prom.arguments;
1006-
1007-
if (!params?.length) {
1008-
return false;
1009-
}
1010-
1011-
const [
1012-
{
1013-
name: resolverName,
1014-
},
1015-
] = params;
1016-
1017-
return hasNonEmptyResolverCall(body, resolverName);
1018-
});
1019-
};
1020-
1021676
// eslint-disable-next-line complexity
1022677
const hasNonFunctionYield = (node, checkYieldReturnValue) => {
1023678
if (!node) {

0 commit comments

Comments
 (0)