Skip to content

Commit

Permalink
feat(prefer-immutable-types): improve ignore options
Browse files Browse the repository at this point in the history
  • Loading branch information
RebeccaStevens committed Jan 28, 2023
1 parent e81124d commit 8a35e52
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 57 deletions.
10 changes: 8 additions & 2 deletions docs/rules/prefer-immutable-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,12 @@ If true, the rule will not flag any variables that are inside of function bodies

See the [allowLocalMutation](./options/allow-local-mutation.md) docs for more information.

### `ignorePattern`
### `ignoreNamePattern`

See the [ignorePattern](./options/ignore-pattern.md) docs.
This option takes a `RegExp` string or an array of `RegExp` strings.
It allows for the ability to ignore violations based on the identifier (name) of node in question.

### `ignoreTypePattern`

This option takes a `RegExp` string or an array of `RegExp` strings.
It allows for the ability to ignore violations based on the type (as written, with whitespace removed) of the node in question.
101 changes: 70 additions & 31 deletions src/rules/prefer-immutable-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,9 @@ import { deepmerge } from "deepmerge-ts";
import { Immutability } from "is-immutable-type";
import type { JSONSchema4 } from "json-schema";

import type {
IgnoreClassesOption,
IgnorePatternOption,
} from "~/common/ignore-options";
import type { IgnoreClassesOption } from "~/common/ignore-options";
import {
ignoreClassesOptionSchema,
ignorePatternOptionSchema,
shouldIgnoreClasses,
shouldIgnoreInFunction,
shouldIgnorePattern,
Expand Down Expand Up @@ -43,11 +39,12 @@ type RawEnforcement =
| "None"
| false;

type Option = IgnoreClassesOption &
IgnorePatternOption & {
enforcement: RawEnforcement;
ignoreInferredTypes: boolean;
};
type Option = IgnoreClassesOption & {
enforcement: RawEnforcement;
ignoreInferredTypes: boolean;
ignoreNamePattern?: string[] | string;
ignoreTypePattern?: string[] | string;
};

/**
* The options this rule can take.
Expand Down Expand Up @@ -84,19 +81,27 @@ const enforcementEnumOptions = [
/**
* The non-shorthand schema for each option.
*/
const optionExpandedSchema: JSONSchema4 = deepmerge(
ignoreClassesOptionSchema,
ignorePatternOptionSchema,
{
enforcement: {
type: ["string", "number", "boolean"],
enum: enforcementEnumOptions,
const optionExpandedSchema: JSONSchema4 = deepmerge(ignoreClassesOptionSchema, {
enforcement: {
type: ["string", "number", "boolean"],
enum: enforcementEnumOptions,
},
ignoreInferredTypes: {
type: "boolean",
},
ignoreNamePattern: {
type: ["string", "array"],
items: {
type: "string",
},
ignoreInferredTypes: {
type: "boolean",
},
ignoreTypePattern: {
type: ["string", "array"],
items: {
type: "string",
},
}
);
},
});

/**
* The schema for each option.
Expand Down Expand Up @@ -216,23 +221,24 @@ function getParameterTypeViolations(
enforcement: rawEnforcement,
ignoreInferredTypes: rawIgnoreInferredTypes,
ignoreClasses,
ignorePattern,
ignoreNamePattern,
ignoreTypePattern,
} = typeof rawOption === "object"
? rawOption
: {
enforcement: rawOption,
ignoreInferredTypes: optionsObject.ignoreInferredTypes,
ignoreClasses: optionsObject.ignoreClasses,
ignorePattern: optionsObject.ignorePattern,
ignoreNamePattern: optionsObject.ignoreNamePattern,
ignoreTypePattern: optionsObject.ignoreTypePattern,
};

const enforcement = parseEnforcement(
rawEnforcement ?? optionsObject.enforcement
);
if (
enforcement === false ||
shouldIgnoreClasses(node, context, ignoreClasses) ||
shouldIgnorePattern(node, context, ignorePattern)
shouldIgnoreClasses(node, context, ignoreClasses)
) {
return [];
}
Expand All @@ -242,6 +248,10 @@ function getParameterTypeViolations(

return node.params
.map((param): Descriptor | undefined => {
if (shouldIgnorePattern(param, context, ignoreNamePattern)) {
return undefined;
}

if (isTSParameterProperty(param) && param.readonly !== true) {
return {
node: param,
Expand All @@ -256,6 +266,13 @@ function getParameterTypeViolations(
if (
// inferred types
(ignoreInferredTypes && actualParam.typeAnnotation === undefined) ||
// ignored
(actualParam.typeAnnotation !== undefined &&
shouldIgnorePattern(
actualParam.typeAnnotation,
context,
ignoreTypePattern
)) ||
// type guard
(node.returnType !== undefined &&
isTSTypePredicate(node.returnType.typeAnnotation) &&
Expand Down Expand Up @@ -301,14 +318,16 @@ function getReturnTypeViolations(
enforcement: rawEnforcement,
ignoreInferredTypes: rawIgnoreInferredTypes,
ignoreClasses,
ignorePattern,
ignoreNamePattern,
ignoreTypePattern,
} = typeof rawOption === "object"
? rawOption
: {
enforcement: rawOption,
ignoreInferredTypes: optionsObject.ignoreInferredTypes,
ignoreClasses: optionsObject.ignoreClasses,
ignorePattern: optionsObject.ignorePattern,
ignoreNamePattern: optionsObject.ignoreNamePattern,
ignoreTypePattern: optionsObject.ignoreTypePattern,
};

const enforcement = parseEnforcement(
Expand All @@ -322,7 +341,7 @@ function getReturnTypeViolations(
enforcement === false ||
(ignoreInferredTypes && node.returnType?.typeAnnotation === undefined) ||
shouldIgnoreClasses(node, context, ignoreClasses) ||
shouldIgnorePattern(node, context, ignorePattern)
shouldIgnorePattern(node, context, ignoreNamePattern)
) {
return [];
}
Expand All @@ -331,6 +350,10 @@ function getReturnTypeViolations(
node.returnType?.typeAnnotation !== undefined &&
!isTSTypePredicate(node.returnType.typeAnnotation)
) {
if (shouldIgnorePattern(node.returnType, context, ignoreTypePattern)) {
return [];
}

const immutability = getTypeImmutabilityOfNode(
node.returnType.typeAnnotation,
context,
Expand Down Expand Up @@ -420,15 +443,17 @@ function checkVarible(
enforcement: rawEnforcement,
ignoreInferredTypes: rawIgnoreInferredTypes,
ignoreClasses,
ignorePattern,
ignoreNamePattern,
ignoreTypePattern,
ignoreInFunctions: rawIgnoreInFunctions,
} = typeof rawOption === "object"
? rawOption
: {
enforcement: rawOption,
ignoreInferredTypes: optionsObject.ignoreInferredTypes,
ignoreClasses: optionsObject.ignoreClasses,
ignorePattern: optionsObject.ignorePattern,
ignoreNamePattern: optionsObject.ignoreNamePattern,
ignoreTypePattern: optionsObject.ignoreTypePattern,
ignoreInFunctions: false,
};

Expand All @@ -441,7 +466,7 @@ function checkVarible(
enforcement === false ||
shouldIgnoreClasses(node, context, ignoreClasses) ||
shouldIgnoreInFunction(node, context, ignoreInFunctions) ||
shouldIgnorePattern(node, context, ignorePattern)
shouldIgnorePattern(node, context, ignoreNamePattern)
) {
return {
context,
Expand Down Expand Up @@ -477,6 +502,20 @@ function checkVarible(
};
}

if (
nodeWithTypeAnnotation.typeAnnotation !== undefined &&
shouldIgnorePattern(
nodeWithTypeAnnotation.typeAnnotation,
context,
ignoreTypePattern
)
) {
return {
context,
descriptors: [],
};
}

const immutability = getTypeImmutabilityOfNode(
nodeWithTypeAnnotation,
context,
Expand Down
50 changes: 44 additions & 6 deletions src/rules/prefer-readonly-type.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import type { ESLintUtils, TSESLint, TSESTree } from "@typescript-eslint/utils";
import type { JSONSchema4 } from "json-schema";

import type {
IgnorePatternOption,
IgnoreAccessorPatternOption,
} from "~/common/ignore-options";
import {
shouldIgnoreInFunction,
shouldIgnoreClasses,
shouldIgnorePattern,
} from "~/common/ignore-options";
import type { ESArrayTupleType } from "~/src/util/node-types";
import type { RuleResult } from "~/util/rule";
import type { BaseOptions, RuleResult } from "~/util/rule";
import { createRule, getTypeOfNode } from "~/util/rule";
import { inInterface, isInReturnType } from "~/util/tree";
import {
Expand All @@ -20,7 +24,10 @@ import {
isTSParameterProperty,
isTSPropertySignature,
isTSTupleType,
isTSTypeAnnotation,
isTSTypeLiteral,
isTSTypeOperator,
isTSTypeReference,
} from "~/util/typeguard";

/**
Expand Down Expand Up @@ -141,6 +148,37 @@ const mutableTypeRegex = new RegExp(
"u"
);

function shouldIgnorePattern2(
node: TSESTree.Node,
context: TSESLint.RuleContext<string, BaseOptions>,
ignorePattern: Partial<IgnorePatternOption>["ignorePattern"],
ignoreAccessorPattern?: Partial<IgnoreAccessorPatternOption>["ignoreAccessorPattern"]
): boolean {
const isTypeNode =
isTSArrayType(node) ||
isTSIndexSignature(node) ||
isTSTupleType(node) ||
isTSTypeAnnotation(node) ||
isTSTypeLiteral(node) ||
isTSTypeReference(node);

if (isTypeNode) {
return shouldIgnorePattern2(
node.parent!,
context,
ignorePattern,
ignoreAccessorPattern
);
}

return shouldIgnorePattern(
node,
context,
ignorePattern,
ignoreAccessorPattern
);
}

/**
* Check if the given ArrayType or TupleType violates this rule.
*/
Expand All @@ -163,7 +201,7 @@ function checkArrayOrTupleType(
shouldIgnoreClasses(node, context, ignoreClass) ||
(ignoreInterface === true && inInterface(node)) ||
shouldIgnoreInFunction(node, context, allowLocalMutation) ||
shouldIgnorePattern(node, context, ignorePattern) ||
shouldIgnorePattern2(node, context, ignorePattern) ||
ignoreCollections
) {
return {
Expand Down Expand Up @@ -219,7 +257,7 @@ function checkMappedType(
shouldIgnoreClasses(node, context, ignoreClass) ||
(ignoreInterface === true && inInterface(node)) ||
shouldIgnoreInFunction(node, context, allowLocalMutation) ||
shouldIgnorePattern(node, context, ignorePattern)
shouldIgnorePattern2(node, context, ignorePattern)
) {
return {
context,
Expand Down Expand Up @@ -268,7 +306,7 @@ function checkTypeReference(
shouldIgnoreClasses(node, context, ignoreClass) ||
(ignoreInterface === true && inInterface(node)) ||
shouldIgnoreInFunction(node, context, allowLocalMutation) ||
shouldIgnorePattern(node, context, ignorePattern)
shouldIgnorePattern2(node, context, ignorePattern)
) {
return {
context,
Expand Down Expand Up @@ -335,7 +373,7 @@ function checkProperty(
shouldIgnoreClasses(node, context, ignoreClass) ||
(ignoreInterface === true && inInterface(node)) ||
shouldIgnoreInFunction(node, context, allowLocalMutation) ||
shouldIgnorePattern(node, context, ignorePattern)
shouldIgnorePattern2(node, context, ignorePattern)
) {
return {
context,
Expand Down Expand Up @@ -400,7 +438,7 @@ function checkImplicitType(
shouldIgnoreClasses(node, context, ignoreClass) ||
(ignoreInterface === true && inInterface(node)) ||
shouldIgnoreInFunction(node, context, allowLocalMutation) ||
shouldIgnorePattern(node, context, ignorePattern)
shouldIgnorePattern2(node, context, ignorePattern)
) {
return {
context,
Expand Down
17 changes: 5 additions & 12 deletions src/util/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,7 @@ import {
isIdentifier,
isMemberExpression,
isThisExpression,
isTSArrayType,
isTSIndexSignature,
isTSTupleType,
isTSTypeAnnotation,
isTSTypeLiteral,
isTSTypeReference,
isUnaryExpression,
isVariableDeclaration,
} from "~/util/typeguard";
Expand Down Expand Up @@ -72,13 +67,11 @@ function getNodeIdentifierText(
? getNodeIdentifierText(node.argument, context)
: isExpressionStatement(node)
? context.getSourceCode().getText(node as TSESTree.Node)
: isTSArrayType(node) ||
isTSIndexSignature(node) ||
isTSTupleType(node) ||
isTSTypeAnnotation(node) ||
isTSTypeLiteral(node) ||
isTSTypeReference(node)
? getNodeIdentifierText(node.parent, context)
: isTSTypeAnnotation(node)
? context
.getSourceCode()
.getText(node.typeAnnotation as TSESTree.Node)
.replace(/\s+/gu, "")
: null;

if (identifierText !== null) {
Expand Down
Loading

0 comments on commit 8a35e52

Please sign in to comment.