UNPKG

eslint-plugin-functional

Version:
1,478 lines (1,463 loc) 139 kB
'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$i = { 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$i, 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$h = { 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$h, 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$g = { 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.