eslint-plugin-functional
Version:
ESLint rules to promote functional programming in TypeScript.
1,478 lines (1,463 loc) • 139 kB
JavaScript
'use strict';
var deepmergeTs = require('deepmerge-ts');
var utils = require('@typescript-eslint/utils');
var eslintUtils = require('@typescript-eslint/utils/eslint-utils');
var isImmutableType = require('is-immutable-type');
var escapeRegExp = require('escape-string-regexp');
var semver = require('semver');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var semver__namespace = /*#__PURE__*/_interopNamespaceDefault(semver);
var ts = (() => {
try {
return require("typescript");
}
catch {
return undefined;
}
})();
/**
* @file Functions that type guard the given node/type.
*/
/*
* Node type guards.
*/
function isArrayExpression(node) {
return node.type === utils.AST_NODE_TYPES.ArrayExpression;
}
function isArrayPattern(node) {
return node.type === utils.AST_NODE_TYPES.ArrayPattern;
}
function isAssignmentExpression(node) {
return node.type === utils.AST_NODE_TYPES.AssignmentExpression;
}
function isAssignmentPattern(node) {
return node.type === utils.AST_NODE_TYPES.AssignmentPattern;
}
function isBlockStatement(node) {
return node.type === utils.AST_NODE_TYPES.BlockStatement;
}
function isBreakStatement(node) {
return node.type === utils.AST_NODE_TYPES.BreakStatement;
}
function isCallExpression(node) {
return node.type === utils.AST_NODE_TYPES.CallExpression;
}
function isPropertyDefinition(node) {
return node.type === utils.AST_NODE_TYPES.PropertyDefinition;
}
/**
* Is the given node a class node?
*
* It doesn't matter what type of class.
*/
function isClassLike(node) {
return (node.type === utils.AST_NODE_TYPES.ClassDeclaration ||
node.type === utils.AST_NODE_TYPES.ClassExpression);
}
function isContinueStatement(node) {
return node.type === utils.AST_NODE_TYPES.ContinueStatement;
}
function isExpressionStatement(node) {
return node.type === utils.AST_NODE_TYPES.ExpressionStatement;
}
function isForStatement(node) {
return node.type === utils.AST_NODE_TYPES.ForStatement;
}
function isFunctionDeclaration(node) {
return node.type === utils.AST_NODE_TYPES.FunctionDeclaration;
}
/**
* Is the given node a function expression node?
*
* It doesn't matter what type of function expression.
*/
function isFunctionExpressionLike(node) {
return (node.type === utils.AST_NODE_TYPES.FunctionExpression ||
node.type === utils.AST_NODE_TYPES.ArrowFunctionExpression);
}
/**
* Is the given node a function node?
*
* It doesn't matter what type of function.
*/
function isFunctionLike(node) {
return isFunctionDeclaration(node) || isFunctionExpressionLike(node);
}
function isIdentifier(node) {
return node.type === utils.AST_NODE_TYPES.Identifier;
}
function isIfStatement(node) {
return node.type === utils.AST_NODE_TYPES.IfStatement;
}
function isMemberExpression(node) {
return node.type === utils.AST_NODE_TYPES.MemberExpression;
}
function isMethodDefinition(node) {
return node.type === utils.AST_NODE_TYPES.MethodDefinition;
}
function isNewExpression(node) {
return node.type === utils.AST_NODE_TYPES.NewExpression;
}
function isObjectExpression(node) {
return node.type === utils.AST_NODE_TYPES.ObjectExpression;
}
function isObjectPattern(node) {
return node.type === utils.AST_NODE_TYPES.ObjectPattern;
}
function isPrivateIdentifier(node) {
return node.type === utils.AST_NODE_TYPES.PrivateIdentifier;
}
function isProgram(node) {
return node.type === utils.AST_NODE_TYPES.Program;
}
function isProperty(node) {
return node.type === utils.AST_NODE_TYPES.Property;
}
function isRestElement(node) {
return node.type === utils.AST_NODE_TYPES.RestElement;
}
function isReturnStatement(node) {
return node.type === utils.AST_NODE_TYPES.ReturnStatement;
}
function isSwitchStatement(node) {
return node.type === utils.AST_NODE_TYPES.SwitchStatement;
}
function isThisExpression(node) {
return node.type === utils.AST_NODE_TYPES.ThisExpression;
}
function isThrowStatement(node) {
return node.type === utils.AST_NODE_TYPES.ThrowStatement;
}
function isTSArrayType(node) {
return node.type === utils.AST_NODE_TYPES.TSArrayType;
}
function isTSAsExpression(node) {
return node.type === utils.AST_NODE_TYPES.TSAsExpression;
}
function isTSFunctionType(node) {
return node.type === utils.AST_NODE_TYPES.TSFunctionType;
}
function isTSIndexSignature(node) {
return node.type === utils.AST_NODE_TYPES.TSIndexSignature;
}
function isTSMethodSignature(node) {
return node.type === utils.AST_NODE_TYPES.TSMethodSignature;
}
function isTSCallSignatureDeclaration(node) {
return node.type === utils.AST_NODE_TYPES.TSCallSignatureDeclaration;
}
function isTSConstructSignatureDeclaration(node) {
return node.type === utils.AST_NODE_TYPES.TSConstructSignatureDeclaration;
}
function isTSInterfaceBody(node) {
return node.type === utils.AST_NODE_TYPES.TSInterfaceBody;
}
function isTSInterfaceDeclaration(node) {
return node.type === utils.AST_NODE_TYPES.TSInterfaceDeclaration;
}
function isTSInterfaceHeritage(node) {
return node.type === utils.AST_NODE_TYPES.TSInterfaceHeritage;
}
function isTSNullKeyword(node) {
return node.type === utils.AST_NODE_TYPES.TSNullKeyword;
}
function isTSParameterProperty(node) {
return node.type === utils.AST_NODE_TYPES.TSParameterProperty;
}
function isTSPropertySignature(node) {
return node.type === utils.AST_NODE_TYPES.TSPropertySignature;
}
function isTSTupleType(node) {
return node.type === utils.AST_NODE_TYPES.TSTupleType;
}
function isTSTypeAnnotation(node) {
return node.type === utils.AST_NODE_TYPES.TSTypeAnnotation;
}
function isTSTypeLiteral(node) {
return node.type === utils.AST_NODE_TYPES.TSTypeLiteral;
}
function isTSTypeOperator(node) {
return node.type === utils.AST_NODE_TYPES.TSTypeOperator;
}
function isTSTypePredicate(node) {
return node.type === utils.AST_NODE_TYPES.TSTypePredicate;
}
function isTSTypeReference(node) {
return node.type === utils.AST_NODE_TYPES.TSTypeReference;
}
function isTSUndefinedKeyword(node) {
return node.type === utils.AST_NODE_TYPES.TSUndefinedKeyword;
}
function isTSVoidKeyword(node) {
return node.type === utils.AST_NODE_TYPES.TSVoidKeyword;
}
function isUnaryExpression(node) {
return node.type === utils.AST_NODE_TYPES.UnaryExpression;
}
function isVariableDeclaration(node) {
return node.type === utils.AST_NODE_TYPES.VariableDeclaration;
}
function isYieldExpression(node) {
return node.type === utils.AST_NODE_TYPES.YieldExpression;
}
function hasID(node) {
return Object.hasOwn(node, "id");
}
function hasKey(node) {
return Object.hasOwn(node, "key");
}
function isDefined(value) {
return value !== null && value !== undefined;
}
/*
* TS types type guards.
*/
function isUnionType(type) {
return ts !== undefined && type.flags === ts.TypeFlags.Union;
}
function isArrayType(type) {
return (type !== null &&
((type.symbol !== undefined && type.symbol.name === "Array") ||
(isUnionType(type) && type.types.some(isArrayType))));
}
function isArrayConstructorType(type) {
return (type !== null &&
((type.symbol !== undefined &&
type.symbol.name === "ArrayConstructor") ||
(isUnionType(type) && type.types.some(isArrayConstructorType))));
}
function isObjectConstructorType(type) {
return (type !== null &&
((type.symbol !== undefined &&
type.symbol.name === "ObjectConstructor") ||
(isUnionType(type) && type.types.some(isObjectConstructorType))));
}
function isFunctionLikeType(type) {
return type !== null && type.getCallSignatures().length > 0;
}
/**
* Return the first ancestor that meets the given check criteria.
*/
function getAncestorOfType(checker, node, child = null) {
return checker(node, child)
? node
: isDefined(node.parent)
? getAncestorOfType(checker, node.parent, node)
: null;
}
/**
* Test if the given node is in a function's body.
*
* @param node - The node to test.
* @param async - Whether the function must be async or sync. Use `undefined` for either.
*/
function isInFunctionBody(node, async) {
const functionNode = getAncestorOfType((n, c) => isFunctionLike(n) && n.body === c, node);
return (functionNode !== null &&
(async === undefined || functionNode.async === async));
}
/**
* Test if the given node is in a class.
*/
function isInClass(node) {
return getAncestorOfType(isClassLike, node) !== null;
}
/**
* Test if the given node is in a for loop initializer.
*/
function isInForLoopInitializer(node) {
return (getAncestorOfType((n, c) => isForStatement(n) && n.init === c, node) !== null);
}
/**
* Test if the given node is shallowly inside a `Readonly<{...}>`.
*/
function isInReadonly(node) {
return getReadonly(node) !== null;
}
/**
* Test if the given node is shallowly inside a `Readonly<{...}>`.
*/
function getReadonly(node) {
// For nested cases, we shouldn't look for any parent, but the immediate parent.
if (isDefined(node.parent) &&
isTSTypeLiteral(node.parent) &&
isDefined(node.parent.parent) &&
isTSTypeAnnotation(node.parent.parent)) {
return null;
}
const typeRef = getAncestorOfType(isTSTypeReference, node);
const intHeritage = getAncestorOfType(isTSInterfaceHeritage, node);
const expressionOrTypeName = typeRef?.typeName ?? intHeritage?.expression;
return expressionOrTypeName !== undefined &&
isIdentifier(expressionOrTypeName) &&
expressionOrTypeName.name === "Readonly"
? typeRef ?? intHeritage
: null;
}
/**
* Test if the given node is in a TS Property Signature.
*/
function isInInterface(node) {
return getAncestorOfType(isTSInterfaceBody, node) !== null;
}
/**
* Test if the given node is in a Constructor.
*/
function isInConstructor(node) {
const methodDefinition = getAncestorOfType(isMethodDefinition, node);
return (methodDefinition !== null &&
isIdentifier(methodDefinition.key) &&
methodDefinition.key.name === "constructor");
}
/**
* Is the given node in the return type.
*/
function isInReturnType(node) {
return (getAncestorOfType((n) => isDefined(n.parent) &&
isFunctionLike(n.parent) &&
n.parent.returnType === n, node) !== null);
}
/**
* Test if the given node is nested inside another statement.
*/
function isNested(node) {
return (node.parent !== undefined &&
!(isProgram(node.parent) || isBlockStatement(node.parent)));
}
/**
* Is the given identifier a property of an object?
*/
function isPropertyAccess(node) {
return (node.parent !== undefined &&
isMemberExpression(node.parent) &&
node.parent.property === node);
}
/**
* Is the given identifier a property name?
*/
function isPropertyName(node) {
return (node.parent !== undefined &&
isProperty(node.parent) &&
node.parent.key === node);
}
/**
* Is the given function an IIFE?
*/
function isIIFE(node) {
return (isFunctionExpressionLike(node) &&
node.parent !== undefined &&
isCallExpression(node.parent) &&
node.parent.callee === node);
}
/**
* Is the given node being passed as an argument?
*/
function isArgument(node) {
return (node.parent !== undefined &&
isCallExpression(node.parent) &&
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
node.parent.arguments.includes(node));
}
/**
* Is the given node a parameter?
*/
function isParameter(node) {
return (node.parent !== undefined &&
isFunctionLike(node.parent) &&
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
node.parent.params.includes(node));
}
/**
* Is the given node a getter function?
*/
function isGetter(node) {
return (node.parent !== undefined &&
isProperty(node.parent) &&
node.parent.kind === "get");
}
/**
* Is the given node a setter function?
*/
function isSetter(node) {
return (node.parent !== undefined &&
isProperty(node.parent) &&
node.parent.kind === "set");
}
/**
* Get the key the given node is assigned to in its parent ObjectExpression.
*/
function getKeyOfValueInObjectExpression(node) {
if (!isDefined(node.parent)) {
return null;
}
const objectExpression = getAncestorOfType(isObjectExpression, node);
if (objectExpression === null) {
return null;
}
const objectExpressionProps = objectExpression.properties.filter((prop) => isProperty(prop) && prop.value === node);
if (objectExpressionProps.length !== 1) {
return null;
}
const objectExpressionProp = objectExpressionProps[0];
if (!isProperty(objectExpressionProp) ||
!isIdentifier(objectExpressionProp.key)) {
return null;
}
return objectExpressionProp.key.name;
}
/**
* Is the given identifier defined by a mutable variable (let or var)?
*/
function isDefinedByMutableVariable(node, context, treatParametersAsMutable) {
const services = eslintUtils.getParserServices(context);
const symbol = services.getSymbolAtLocation(node);
const variableDeclaration = symbol?.valueDeclaration;
if (variableDeclaration === undefined) {
return true;
}
const variableDeclarationNode = services.tsNodeToESTreeNodeMap.get(variableDeclaration);
if (variableDeclarationNode !== undefined &&
isParameter(variableDeclarationNode)) {
return treatParametersAsMutable(variableDeclarationNode);
}
if (!ts.isVariableDeclaration(variableDeclaration)) {
return true;
}
const variableDeclarator = context.parserServices?.tsNodeToESTreeNodeMap.get(variableDeclaration);
if (variableDeclarator?.parent === undefined ||
!isVariableDeclaration(variableDeclarator.parent)) {
return true;
}
return variableDeclarator.parent.kind !== "const";
}
/**
* Get the root identifier of an expression.
*/
function findRootIdentifier(node) {
if (isIdentifier(node)) {
return node;
}
if (isMemberExpression(node)) {
return findRootIdentifier(node.object);
}
return undefined;
}
const ruleNameScope = "functional";
/**
* Higher order function to check if the two given values are the same.
*/
function isExpected(expected) {
return (actual) => actual === expected;
}
/**
* Does the given ExpressionStatement specify directive prologues.
*/
function isDirectivePrologue(node) {
return (node.expression.type === utils.AST_NODE_TYPES.Literal &&
typeof node.expression.value === "string" &&
node.expression.value.startsWith("use "));
}
/**
* Get the identifier text of the given node.
*/
function getNodeIdentifierText(node, context) {
if (!isDefined(node)) {
return undefined;
}
const identifierText = isIdentifier(node) || isPrivateIdentifier(node)
? node.name
: hasID(node) && isDefined(node.id)
? getNodeIdentifierText(node.id, context)
: hasKey(node) && isDefined(node.key)
? getNodeIdentifierText(node.key, context)
: isAssignmentExpression(node)
? getNodeIdentifierText(node.left, context)
: isMemberExpression(node)
? `${getNodeIdentifierText(node.object, context)}.${getNodeIdentifierText(node.property, context)}`
: isThisExpression(node)
? "this"
: isUnaryExpression(node)
? getNodeIdentifierText(node.argument, context)
: isTSTypeAnnotation(node)
? context.sourceCode
.getText(node.typeAnnotation)
.replaceAll(/\s+/gmu, "")
: isTSAsExpression(node)
? getNodeIdentifierText(node.expression, context)
: null;
if (identifierText !== null) {
return identifierText;
}
const keyInObjectExpression = getKeyOfValueInObjectExpression(node);
if (keyInObjectExpression !== null) {
return keyInObjectExpression;
}
return undefined;
}
/**
* Get the code of the given node.
*/
function getNodeCode(node, context) {
return context.sourceCode.getText(node);
}
/**
* Get all the identifier texts of the given node.
*/
function getNodeIdentifierTexts(node, context) {
return (isVariableDeclaration(node)
? node.declarations.flatMap((declarator) => getNodeIdentifierText(declarator, context))
: [getNodeIdentifierText(node, context)]).filter((text) => text !== undefined);
}
/**
* The schema for the option to ignore patterns.
*/
const ignoreIdentifierPatternOptionSchema = {
ignoreIdentifierPattern: {
type: ["string", "array"],
items: {
type: "string",
},
},
};
/**
* The schema for the option to ignore patterns.
*/
const ignoreCodePatternOptionSchema = {
ignoreCodePattern: {
type: ["string", "array"],
items: {
type: "string",
},
},
};
/**
* The schema for the option to ignore accessor patterns.
*/
const ignoreAccessorPatternOptionSchema = {
ignoreAccessorPattern: {
type: ["string", "array"],
items: {
type: "string",
},
},
};
/**
* The schema for the option to ignore classes.
*/
const ignoreClassesOptionSchema = {
ignoreClasses: {
oneOf: [
{
type: "boolean",
},
{
type: "string",
enum: ["fieldsOnly"],
},
],
},
};
/**
* The schema for the option to ignore prefix selector.
*/
const ignorePrefixSelectorOptionSchema = {
ignorePrefixSelector: {
type: ["string", "array"],
items: {
type: "string",
},
},
};
/**
* Should the given text be allowed?
*
* Test using the given pattern(s).
*/
function shouldIgnoreViaPattern(text, pattern) {
const patterns = Array.isArray(pattern) ? pattern : [pattern];
// One or more patterns match?
return patterns.some((p) => new RegExp(p, "u").test(text));
}
/**
* Recursive callback of `shouldIgnoreViaAccessorPattern`.
*
* This function not be called from anywhere else.
*
* Does the given text match the given pattern.
*/
function accessorPatternMatch([pattern, ...remainingPatternParts], textParts, allowExtra = false) {
return pattern === undefined
? allowExtra || textParts.length === 0
: // Match any depth (including 0)?
pattern === "**"
? textParts.length === 0
? accessorPatternMatch(remainingPatternParts, [], allowExtra)
: Array.from({ length: textParts.length })
.map((element, index) => index)
.some((offset) => accessorPatternMatch(remainingPatternParts, textParts.slice(offset), true))
: // Match anything?
pattern === "*"
? textParts.length > 0 &&
accessorPatternMatch(remainingPatternParts, textParts.slice(1), allowExtra)
: // Text matches pattern?
new RegExp(`^${escapeRegExp(pattern).replaceAll("\\*", ".*")}$`, "u").test(textParts[0]) &&
accessorPatternMatch(remainingPatternParts, textParts.slice(1), allowExtra);
}
/**
* Should the given text be allowed?
*
* Test using the given accessor pattern(s).
*/
function shouldIgnoreViaAccessorPattern(text, pattern) {
const patterns = Array.isArray(pattern) ? pattern : [pattern];
// One or more patterns match?
return patterns.some((p) => accessorPatternMatch(p.split("."), text.split(".")));
}
/**
* Should the given node be allowed base off the following rule options?
*
* - AllowInFunctionOption.
*/
function shouldIgnoreInFunction(node, context, allowInFunction) {
return allowInFunction === true && isInFunctionBody(node);
}
/**
* Should the given node be allowed base off the following rule options?
*
* - IgnoreClassesOption.
*/
function shouldIgnoreClasses(node, context, ignoreClasses) {
return ((ignoreClasses === true && (isClassLike(node) || isInClass(node))) ||
(ignoreClasses === "fieldsOnly" &&
(isPropertyDefinition(node) ||
(isAssignmentExpression(node) &&
isInClass(node) &&
isMemberExpression(node.left) &&
isThisExpression(node.left.object)))));
}
/**
* Should the given node be allowed base off the following rule options?
*
* - IgnoreAccessorPatternOption.
* - IgnoreIdentifierPatternOption.
*/
function shouldIgnorePattern(node, context, ignoreIdentifierPattern, ignoreAccessorPattern, ignoreCodePattern) {
const texts = getNodeIdentifierTexts(node, context);
if (texts.length === 0) {
return (ignoreCodePattern !== undefined &&
shouldIgnoreViaPattern(getNodeCode(node, context), ignoreCodePattern));
}
return (
// Ignore if ignoreIdentifierPattern is set and a pattern matches.
(ignoreIdentifierPattern !== undefined &&
texts.every((text) => shouldIgnoreViaPattern(text, ignoreIdentifierPattern))) ||
// Ignore if ignoreAccessorPattern is set and an accessor pattern matches.
(ignoreAccessorPattern !== undefined &&
texts.every((text) => shouldIgnoreViaAccessorPattern(text, ignoreAccessorPattern))) ||
// Ignore if ignoreCodePattern is set and a code pattern matches.
(ignoreCodePattern !== undefined &&
shouldIgnoreViaPattern(getNodeCode(node, context), ignoreCodePattern)));
}
/**
* The settings that have been loaded - so we don't have to reload them.
*/
const cachedSettings = new WeakMap();
/**
* Get the immutability overrides defined in the settings.
*/
function getImmutabilityOverrides({ immutability, }) {
if (immutability === undefined) {
return undefined;
}
if (!cachedSettings.has(immutability)) {
const overrides = loadImmutabilityOverrides(immutability);
// eslint-disable-next-line functional/no-expression-statements
cachedSettings.set(immutability, overrides);
return overrides;
}
return cachedSettings.get(immutability);
}
/**
* Get all the overrides and upgrade them.
*/
function loadImmutabilityOverrides(immutabilitySettings) {
const overridesSetting = immutabilitySettings?.overrides;
if (overridesSetting === undefined) {
return undefined;
}
const raw = Array.isArray(overridesSetting)
? overridesSetting
: overridesSetting.values ?? [];
const upgraded = raw.map((rawValue) => {
const { type, to, from, ...rest } = rawValue;
const value = {
type,
to: typeof to === "string" ? isImmutableType.Immutability[to] : to,
from: from === undefined
? undefined
: typeof from === "string"
? isImmutableType.Immutability[from]
: from,
};
/* c8 ignore start */
if (value.type === undefined) {
// eslint-disable-next-line functional/no-throw-statements
throw new Error(`Override is missing required "type" property. Value: "${JSON.stringify(rawValue)}"`);
}
if (value.to === undefined) {
// eslint-disable-next-line functional/no-throw-statements
throw new Error(`Override is missing required "to" property. Value: "${JSON.stringify(rawValue)}"`);
}
const restKeys = Object.keys(rest);
if (restKeys.length > 0) {
// eslint-disable-next-line functional/no-throw-statements
throw new Error(`Override is contains unknown property(s) "${restKeys.join(", ")}". Value: "${JSON.stringify(rawValue)}"`);
}
/* c8 ignore stop */
return value;
});
const keepDefault = Array.isArray(overridesSetting) || overridesSetting.keepDefault !== false;
return keepDefault
? [...isImmutableType.getDefaultOverrides(), ...upgraded]
: upgraded;
}
// eslint-disable-next-line @typescript-eslint/naming-convention -- This is a special var.
const __VERSION__ = "0.0.0-development";
// This function can't be functional as it needs to interact with 3rd-party
// libraries that aren't functional.
/* eslint-disable functional/no-return-void, functional/no-expression-statements */
/**
* Create a function that processes common options and then runs the given
* check.
*/
function checkNode(check, context, options) {
return (node) => {
const result = check(node, context, options);
// eslint-disable-next-line functional/no-loop-statements -- can't really be avoided.
for (const descriptor of result.descriptors) {
result.context.report(descriptor);
}
};
}
/* eslint-enable functional/no-return-void, functional/no-expression-statements */
/**
* Create a rule.
*/
function createRule(name, meta, defaultOptions, ruleFunctionsMap) {
return createRuleUsingFunction(name, meta, defaultOptions, () => ruleFunctionsMap);
}
/**
* Create a rule.
*/
function createRuleUsingFunction(name, meta, defaultOptions, createFunction) {
const ruleCreator = eslintUtils.RuleCreator((ruleName) => `https://github.com/eslint-functional/eslint-plugin-functional/blob/v6.5.1/docs/rules/${ruleName}.md`);
return ruleCreator({
name,
meta: meta,
defaultOptions,
create: (context, options) => {
const ruleFunctionsMap = createFunction(context, options);
return Object.fromEntries(Object.entries(ruleFunctionsMap).map(([nodeSelector, ruleFunction]) => [
nodeSelector,
// prettier-ignore
checkNode(ruleFunction, context, options),
]));
},
});
}
/**
* Get the type of the the given node.
*/
function getTypeOfNode(node, context) {
const { esTreeNodeToTSNodeMap } = eslintUtils.getParserServices(context);
const tsNode = esTreeNodeToTSNodeMap.get(node);
return getTypeOfTSNode(tsNode, context);
}
/**
* Get the type of the the given ts node.
*/
function getTypeOfTSNode(node, context) {
const { program } = eslintUtils.getParserServices(context);
const checker = program.getTypeChecker();
const nodeType = checker.getTypeAtLocation(node);
const constrained = checker.getBaseConstraintOfType(nodeType);
return constrained ?? nodeType;
}
/**
* Get the return type of the the given function node.
*/
function getReturnTypesOfFunction(node, context) {
if (ts === undefined) {
return null;
}
const parserServices = eslintUtils.getParserServices(context);
const checker = parserServices.program.getTypeChecker();
const type = getTypeOfNode(node, context);
const signatures = checker.getSignaturesOfType(type, ts.SignatureKind.Call);
return signatures.map((signature) => checker.getReturnTypeOfSignature(signature));
}
/**
* Does the given function have overloads?
*/
function isImplementationOfOverload(func, context) {
if (ts === undefined) {
return false;
}
const parserServices = eslintUtils.getParserServices(context);
const checker = parserServices.program.getTypeChecker();
const signature = parserServices.esTreeNodeToTSNodeMap.get(func);
return checker.isImplementationOfOverload(signature) === true;
}
/**
* Get the type immutability of the the given node or type.
*/
function getTypeImmutabilityOfNode(node, context, maxImmutability, explicitOverrides) {
if (ts === undefined) {
return isImmutableType.Immutability.Unknown;
}
const parserServices = eslintUtils.getParserServices(context);
const overrides = explicitOverrides ?? getImmutabilityOverrides(context.settings);
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
const typedNode = ts.isIdentifier(tsNode) ? tsNode.parent : tsNode;
const typeLike = typedNode.type ??
getTypeOfNode(parserServices.tsNodeToESTreeNodeMap.get(typedNode), context);
return isImmutableType.getTypeImmutability(parserServices.program, typeLike, overrides,
// Don't use the global cache in testing environments as it may cause errors when switching between different config options.
process.env["NODE_ENV"] !== "test", maxImmutability);
}
/**
* Get the type immutability of the the given type.
*/
function getTypeImmutabilityOfType(typeOrTypeNode, context, maxImmutability, explicitOverrides) {
const parserServices = eslintUtils.getParserServices(context);
const overrides = explicitOverrides ?? getImmutabilityOverrides(context.settings);
return isImmutableType.getTypeImmutability(parserServices.program, typeOrTypeNode, overrides,
// Don't use the global cache in testing environments as it may cause errors when switching between different config options.
process.env["NODE_ENV"] !== "test", maxImmutability);
}
/**
* The name of this rule.
*/
const name$i = "functional-parameters";
/**
* The full name of this rule.
*/
const fullName$9 = `${ruleNameScope}/${name$i}`;
/**
* The schema for the rule options.
*/
const schema$i = [
{
type: "object",
properties: deepmergeTs.deepmerge(ignoreIdentifierPatternOptionSchema, ignorePrefixSelectorOptionSchema, {
allowRestParameter: {
type: "boolean",
},
allowArgumentsKeyword: {
type: "boolean",
},
enforceParameterCount: {
oneOf: [
{
type: "boolean",
enum: [false],
},
{
type: "string",
enum: ["atLeastOne", "exactlyOne"],
},
{
type: "object",
properties: {
count: {
type: "string",
enum: ["atLeastOne", "exactlyOne"],
},
ignoreGettersAndSetters: {
type: "boolean",
},
ignoreLambdaExpression: {
type: "boolean",
},
ignoreIIFE: {
type: "boolean",
},
},
additionalProperties: false,
},
],
},
}),
additionalProperties: false,
},
];
/**
* The default options for the rule.
*/
const defaultOptions$i = [
{
allowRestParameter: false,
allowArgumentsKeyword: false,
enforceParameterCount: {
count: "atLeastOne",
ignoreLambdaExpression: false,
ignoreIIFE: true,
ignoreGettersAndSetters: true,
},
},
];
/**
* The possible error messages.
*/
const errorMessages$i = {
restParam: "Unexpected rest parameter. Use a regular parameter of type array instead.",
arguments: "Unexpected use of `arguments`. Use regular function arguments instead.",
paramCountAtLeastOne: "Functions must have at least one parameter.",
paramCountExactlyOne: "Functions must have exactly one parameter.",
};
/**
* The meta data for this rule.
*/
const meta$j = {
type: "suggestion",
docs: {
category: "Currying",
description: "Enforce functional parameters.",
recommended: "recommended",
recommendedSeverity: "error",
},
messages: errorMessages$i,
schema: schema$i,
};
/**
* Get the rest parameter violations.
*/
function getRestParamViolations([{ allowRestParameter }], node) {
return !allowRestParameter &&
node.params.length > 0 &&
isRestElement(node.params.at(-1))
? [
{
node: node.params.at(-1),
messageId: "restParam",
},
]
: [];
}
/**
* Get the parameter count violations.
*/
function getParamCountViolations([{ enforceParameterCount }], node) {
if (enforceParameterCount === false ||
(node.params.length === 0 &&
typeof enforceParameterCount === "object" &&
((enforceParameterCount.ignoreIIFE && isIIFE(node)) ||
(enforceParameterCount.ignoreLambdaExpression && isArgument(node)) ||
(enforceParameterCount.ignoreGettersAndSetters &&
(isGetter(node) || isSetter(node)))))) {
return [];
}
if (node.params.length === 0 &&
(enforceParameterCount === "atLeastOne" ||
(typeof enforceParameterCount === "object" &&
enforceParameterCount.count === "atLeastOne"))) {
return [
{
node,
messageId: "paramCountAtLeastOne",
},
];
}
if (node.params.length !== 1 &&
(enforceParameterCount === "exactlyOne" ||
(typeof enforceParameterCount === "object" &&
enforceParameterCount.count === "exactlyOne"))) {
return [
{
node,
messageId: "paramCountExactlyOne",
},
];
}
return [];
}
/**
* Check if the given function node has a reset parameter this rule.
*/
function checkFunction$3(node, context, options) {
const [optionsObject] = options;
const { ignoreIdentifierPattern } = optionsObject;
if (shouldIgnorePattern(node, context, ignoreIdentifierPattern)) {
return {
context,
descriptors: [],
};
}
return {
context,
descriptors: [
...getRestParamViolations(options, node),
...getParamCountViolations(options, node),
],
};
}
/**
* Check if the given identifier is for the "arguments" keyword.
*/
function checkIdentifier(node, context, options) {
const [optionsObject] = options;
const { ignoreIdentifierPattern } = optionsObject;
if (shouldIgnorePattern(node, context, ignoreIdentifierPattern)) {
return {
context,
descriptors: [],
};
}
const { allowArgumentsKeyword } = optionsObject;
return {
context,
descriptors: !allowArgumentsKeyword &&
node.name === "arguments" &&
!isPropertyName(node) &&
!isPropertyAccess(node)
? [
{
node,
messageId: "arguments",
},
]
: [],
};
}
// Create the rule.
const rule$i = createRuleUsingFunction(name$i, meta$j, defaultOptions$i, (context, options) => {
const [optionsObject] = options;
const { ignorePrefixSelector } = optionsObject;
const baseFunctionSelectors = [
"ArrowFunctionExpression",
"FunctionDeclaration",
"FunctionExpression",
];
const ignoreSelectors = ignorePrefixSelector === undefined
? undefined
: Array.isArray(ignorePrefixSelector)
? ignorePrefixSelector
: [ignorePrefixSelector];
const fullFunctionSelectors = baseFunctionSelectors.flatMap((baseSelector) => ignoreSelectors === undefined
? [baseSelector]
: `:not(:matches(${ignoreSelectors.join(",")})) > ${baseSelector}`);
return {
...Object.fromEntries(fullFunctionSelectors.map((selector) => [selector, checkFunction$3])),
Identifier: checkIdentifier,
};
});
/**
* The name of this rule.
*/
const name$h = "immutable-data";
/**
* The full name of this rule.
*/
const fullName$8 = `${ruleNameScope}/${name$h}`;
/**
* The schema for the rule options.
*/
const schema$h = [
{
type: "object",
properties: deepmergeTs.deepmerge(ignoreIdentifierPatternOptionSchema, ignoreAccessorPatternOptionSchema, ignoreClassesOptionSchema, {
ignoreImmediateMutation: {
type: "boolean",
},
ignoreNonConstDeclarations: {
oneOf: [
{
type: "boolean",
},
{
type: "object",
properties: {
treatParametersAsConst: {
type: "boolean",
},
},
additionalProperties: false,
},
],
},
}),
additionalProperties: false,
},
];
/**
* The default options for the rule.
*/
const defaultOptions$h = [
{
ignoreClasses: false,
ignoreImmediateMutation: true,
ignoreNonConstDeclarations: false,
},
];
/**
* The possible error messages.
*/
const errorMessages$h = {
generic: "Modifying an existing object/array is not allowed.",
object: "Modifying properties of existing object not allowed.",
array: "Modifying an array is not allowed.",
};
/**
* The meta data for this rule.
*/
const meta$i = {
type: "suggestion",
docs: {
category: "No Mutations",
description: "Enforce treating data as immutable.",
recommended: "recommended",
recommendedSeverity: "error",
requiresTypeChecking: true,
},
messages: errorMessages$h,
schema: schema$h,
};
/**
* Array methods that mutate an array.
*
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/prototype#Methods#Mutator_methods
*/
const arrayMutatorMethods = new Set([
"copyWithin",
"fill",
"pop",
"push",
"reverse",
"shift",
"sort",
"splice",
"unshift",
]);
/**
* Array methods that return a new object (or array) without mutating the original.
*
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/prototype#Methods#Accessor_methods
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/prototype#Iteration_methods
*/
const arrayNewObjectReturningMethods = [
"concat",
"slice",
"filter",
"map",
"reduce",
"reduceRight",
];
/**
* Array constructor functions that create a new array.
*
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array#Methods
*/
const arrayConstructorFunctions = ["from", "of"];
/**
* Object constructor functions that mutate an object.
*
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object#Methods_of_the_Object_constructor
*/
const objectConstructorMutatorFunctions = new Set([
"assign",
"defineProperties",
"defineProperty",
"setPrototypeOf",
]);
/**
* Object constructor functions that return new objects.
*/
const objectConstructorNewObjectReturningMethods = [
"create",
"entries",
"fromEntries",
"getOwnPropertyDescriptor",
"getOwnPropertyDescriptors",
"getOwnPropertyNames",
"getOwnPropertySymbols",
"groupBy",
"keys",
"values",
];
/**
* String constructor functions that return new objects.
*
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#Methods
*/
const stringConstructorNewObjectReturningMethods = ["split"];
/**
* Check if the given assignment expression violates this rule.
*/
function checkAssignmentExpression(node, context, options) {
const [optionsObject] = options;
const { ignoreIdentifierPattern, ignoreAccessorPattern, ignoreNonConstDeclarations, ignoreClasses, } = optionsObject;
if (!isMemberExpression(node.left) ||
shouldIgnoreClasses(node, context, ignoreClasses) ||
shouldIgnorePattern(node, context, ignoreIdentifierPattern, ignoreAccessorPattern)) {
return {
context,
descriptors: [],
};
}
if (ignoreNonConstDeclarations !== false) {
const rootIdentifier = findRootIdentifier(node.left.object);
if (rootIdentifier !== undefined &&
isDefinedByMutableVariable(rootIdentifier, context, (variableNode) => ignoreNonConstDeclarations === true ||
!ignoreNonConstDeclarations.treatParametersAsConst ||
shouldIgnorePattern(variableNode, context, ignoreIdentifierPattern, ignoreAccessorPattern))) {
return {
context,
descriptors: [],
};
}
}
return {
context,
descriptors:
// Allow if in a constructor - allow for field initialization.
isInConstructor(node) ? [] : [{ node, messageId: "generic" }],
};
}
/**
* Check if the given node violates this rule.
*/
function checkUnaryExpression(node, context, options) {
const [optionsObject] = options;
const { ignoreIdentifierPattern, ignoreAccessorPattern, ignoreNonConstDeclarations, ignoreClasses, } = optionsObject;
if (!isMemberExpression(node.argument) ||
shouldIgnoreClasses(node, context, ignoreClasses) ||
shouldIgnorePattern(node, context, ignoreIdentifierPattern, ignoreAccessorPattern)) {
return {
context,
descriptors: [],
};
}
if (ignoreNonConstDeclarations !== false) {
const rootIdentifier = findRootIdentifier(node.argument.object);
if (rootIdentifier !== undefined &&
isDefinedByMutableVariable(rootIdentifier, context, (variableNode) => ignoreNonConstDeclarations === true ||
!ignoreNonConstDeclarations.treatParametersAsConst ||
shouldIgnorePattern(variableNode, context, ignoreIdentifierPattern, ignoreAccessorPattern))) {
return {
context,
descriptors: [],
};
}
}
return {
context,
descriptors: node.operator === "delete" ? [{ node, messageId: "generic" }] : [],
};
}
/**
* Check if the given node violates this rule.
*/
function checkUpdateExpression(node, context, options) {
const [optionsObject] = options;
const { ignoreIdentifierPattern, ignoreAccessorPattern, ignoreNonConstDeclarations, ignoreClasses, } = optionsObject;
if (!isMemberExpression(node.argument) ||
shouldIgnoreClasses(node.argument, context, ignoreClasses) ||
shouldIgnorePattern(node.argument, context, ignoreIdentifierPattern, ignoreAccessorPattern)) {
return {
context,
descriptors: [],
};
}
if (ignoreNonConstDeclarations !== false) {
const rootIdentifier = findRootIdentifier(node.argument.object);
if (rootIdentifier !== undefined &&
isDefinedByMutableVariable(rootIdentifier, context, (variableNode) => ignoreNonConstDeclarations === true ||
!ignoreNonConstDeclarations.treatParametersAsConst ||
shouldIgnorePattern(variableNode, context, ignoreIdentifierPattern, ignoreAccessorPattern))) {
return {
context,
descriptors: [],
};
}
}
return {
context,
descriptors: [{ node, messageId: "generic" }],
};
}
/**
* Check if the given the given MemberExpression is part of a chain and
* immediately follows a method/function call that returns a new array.
*
* If this is the case, then the given MemberExpression is allowed to be
* a mutator method call.
*/
function isInChainCallAndFollowsNew(node, context) {
if (isMemberExpression(node)) {
return isInChainCallAndFollowsNew(node.object, context);
}
if (isTSAsExpression(node)) {
return isInChainCallAndFollowsNew(node.expression, context);
}
// Check for: [0, 1, 2]
if (isArrayExpression(node)) {
return true;
}
// Check for: new Array()
if (isNewExpression(node) &&
isArrayConstructorType(getTypeOfNode(node.callee, context))) {
return true;
}
if (isCallExpression(node) &&
isMemberExpression(node.callee) &&
isIdentifier(node.callee.property)) {
// Check for: Array.from(iterable)
if (arrayConstructorFunctions.some(isExpected(node.callee.property.name)) &&
isArrayConstructorType(getTypeOfNode(node.callee.object, context))) {
return true;
}
// Check for: array.slice(0)
if (arrayNewObjectReturningMethods.some(isExpected(node.callee.property.name))) {
return true;
}
// Check for: Object.entries(object)
if (objectConstructorNewObjectReturningMethods.some(isExpected(node.callee.property.name)) &&
isObjectConstructorType(getTypeOfNode(node.callee.object, context))) {
return true;
}
// Check for: "".split("")
if (stringConstructorNewObjectReturningMethods.some(isExpected(node.callee.property.name)) &&
getTypeOfNode(node.callee.object, context).isStringLiteral()) {
return true;
}
}
return false;
}
/**
* Check if the given node violates this rule.
*/
function checkCallExpression$1(node, context, options) {
const [optionsObject] = options;
const { ignoreIdentifierPattern, ignoreAccessorPattern, ignoreNonConstDeclarations, ignoreClasses, } = optionsObject;
// Not potential object mutation?
if (!isMemberExpression(node.callee) ||
!isIdentifier(node.callee.property) ||
shouldIgnoreClasses(node.callee.object, context, ignoreClasses) ||
shouldIgnorePattern(node.callee.object, context, ignoreIdentifierPattern, ignoreAccessorPattern)) {
return {
context,
descriptors: [],
};
}
const { ignoreImmediateMutation } = optionsObject;
// Array mutation?
if (arrayMutatorMethods.has(node.callee.property.name) &&
(!ignoreImmediateMutation ||
!isInChainCallAndFollowsNew(node.callee, context)) &&
isArrayType(getTypeOfNode(node.callee.object, context))) {
if (ignoreNonConstDeclarations === false) {
return {
context,
descriptors: [{ node, messageId: "array" }],
};
}
const rootIdentifier = findRootIdentifier(node.callee.object);
if (rootIdentifier === undefined ||
!isDefinedByMutableVariable(rootIdentifier, context, (variableNode) => ignoreNonConstDeclarations === true ||
!ignoreNonConstDeclarations.treatParametersAsConst ||
shouldIgnorePattern(variableNode, context, ignoreIdentifierPattern, ignoreAccessorPattern))) {
return {
context,
descriptors: [{ node, messageId: "array" }],
};
}
}
// Non-array object mutation (ex. Object.assign on identifier)?
if (objectConstructorMutatorFunctions.has(node.callee.property.name) &&
node.arguments.length >= 2 &&
(isIdentifier(node.arguments[0]) ||
isMemberExpression(node.arguments[0])) &&
!shouldIgnoreClasses(node.arguments[0], context, ignoreClasses) &&
!shouldIgnorePattern(node.arguments[0], context, ignoreIdentifierPattern, ignoreAccessorPattern) &&
isObjectConstructorType(getTypeOfNode(node.callee.object, context))) {
if (ignoreNonConstDeclarations === false) {
return {
context,
descriptors: [{ node, messageId: "object" }],
};
}
const rootIdentifier = findRootIdentifier(node.callee.object);
if (rootIdentifier === undefined ||
!isDefinedByMutableVariable(rootIdentifier, context, (variableNode) => ignoreNonConstDeclarations === true ||
!ignoreNonConstDeclarations.treatParametersAsConst ||
shouldIgnorePattern(variableNode, context, ignoreIdentifierPattern, ignoreAccessorPattern))) {
return {
context,
descriptors: [{ node, messageId: "object" }],
};
}
}
return {
context,
descriptors: [],
};
}
// Create the rule.
const rule$h = createRule(name$h, meta$i, defaultOptions$h, {
AssignmentExpression: checkAssignmentExpression,
UnaryExpression: checkUnaryExpression,
UpdateExpression: checkUpdateExpression,
CallExpression: checkCallExpression$1,
});
/**
* The name of this rule.
*/
const name$g = "no-classes";
/**
* The schema for the rule options.
*/
const schema$g = [];
/**
* The default options for the rule.
*/
const defaultOptions$g = [{}];
/**
* The possible error messages.
*/
const errorMessages$g = {
generic: "Unexpected class, use functions not classes.",
};
/**
* The meta data for this rule.
*/
const meta$h = {
type: "suggestion",
docs: {
category: "No Other Paradigms",
description: "Disallow classes.",
recommended: "recommended",
recommendedSeverity: "error",
},
messages: errorMessages$g,
schema: schema$g,
};
/**
* Check if the given class node violates this rule.
*/
function checkClass(node, context) {
// All class nodes violate this rule.