eslint-plugin-complete 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +9 -0
- package/README.md +114 -0
- package/dist/comments.d.ts +22 -0
- package/dist/comments.d.ts.map +1 -0
- package/dist/comments.js +66 -0
- package/dist/completeCommon.d.ts +25 -0
- package/dist/completeCommon.d.ts.map +1 -0
- package/dist/completeCommon.js +53 -0
- package/dist/completeSentence.d.ts +9 -0
- package/dist/completeSentence.d.ts.map +1 -0
- package/dist/completeSentence.js +267 -0
- package/dist/configs/recommended.d.ts +3 -0
- package/dist/configs/recommended.d.ts.map +1 -0
- package/dist/configs/recommended.js +63 -0
- package/dist/configs.d.ts +4 -0
- package/dist/configs.d.ts.map +1 -0
- package/dist/configs.js +4 -0
- package/dist/constants.d.ts +8 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +73 -0
- package/dist/format.d.ts +18 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +246 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +49 -0
- package/dist/interfaces/MyPluginDocs.d.ts +6 -0
- package/dist/interfaces/MyPluginDocs.d.ts.map +1 -0
- package/dist/interfaces/MyPluginDocs.js +1 -0
- package/dist/jsdoc.d.ts +4 -0
- package/dist/jsdoc.d.ts.map +1 -0
- package/dist/jsdoc.js +24 -0
- package/dist/leadingLineComments.d.ts +32 -0
- package/dist/leadingLineComments.d.ts.map +1 -0
- package/dist/leadingLineComments.js +77 -0
- package/dist/list.d.ts +49 -0
- package/dist/list.d.ts.map +1 -0
- package/dist/list.js +140 -0
- package/dist/rules/complete-sentences-jsdoc.d.ts +4 -0
- package/dist/rules/complete-sentences-jsdoc.d.ts.map +1 -0
- package/dist/rules/complete-sentences-jsdoc.js +48 -0
- package/dist/rules/complete-sentences-line-comments.d.ts +4 -0
- package/dist/rules/complete-sentences-line-comments.d.ts.map +1 -0
- package/dist/rules/complete-sentences-line-comments.js +88 -0
- package/dist/rules/consistent-enum-values.d.ts +2 -0
- package/dist/rules/consistent-enum-values.d.ts.map +1 -0
- package/dist/rules/consistent-enum-values.js +46 -0
- package/dist/rules/consistent-named-tuples.d.ts +2 -0
- package/dist/rules/consistent-named-tuples.d.ts.map +1 -0
- package/dist/rules/consistent-named-tuples.js +34 -0
- package/dist/rules/eqeqeq-fix.d.ts +2 -0
- package/dist/rules/eqeqeq-fix.d.ts.map +1 -0
- package/dist/rules/eqeqeq-fix.js +173 -0
- package/dist/rules/format-jsdoc-comments.d.ts +8 -0
- package/dist/rules/format-jsdoc-comments.d.ts.map +1 -0
- package/dist/rules/format-jsdoc-comments.js +117 -0
- package/dist/rules/format-line-comments.d.ts +8 -0
- package/dist/rules/format-line-comments.d.ts.map +1 -0
- package/dist/rules/format-line-comments.js +118 -0
- package/dist/rules/jsdoc-code-block-language.d.ts +2 -0
- package/dist/rules/jsdoc-code-block-language.d.ts.map +1 -0
- package/dist/rules/jsdoc-code-block-language.js +52 -0
- package/dist/rules/newline-between-switch-case.d.ts +4 -0
- package/dist/rules/newline-between-switch-case.d.ts.map +1 -0
- package/dist/rules/newline-between-switch-case.js +63 -0
- package/dist/rules/no-confusing-set-methods.d.ts +5 -0
- package/dist/rules/no-confusing-set-methods.d.ts.map +1 -0
- package/dist/rules/no-confusing-set-methods.js +51 -0
- package/dist/rules/no-empty-jsdoc.d.ts +2 -0
- package/dist/rules/no-empty-jsdoc.d.ts.map +1 -0
- package/dist/rules/no-empty-jsdoc.js +45 -0
- package/dist/rules/no-empty-line-comments.d.ts +2 -0
- package/dist/rules/no-empty-line-comments.d.ts.map +1 -0
- package/dist/rules/no-empty-line-comments.js +40 -0
- package/dist/rules/no-explicit-array-loops.d.ts +5 -0
- package/dist/rules/no-explicit-array-loops.d.ts.map +1 -0
- package/dist/rules/no-explicit-array-loops.js +114 -0
- package/dist/rules/no-explicit-map-set-loops.d.ts +5 -0
- package/dist/rules/no-explicit-map-set-loops.d.ts.map +1 -0
- package/dist/rules/no-explicit-map-set-loops.js +74 -0
- package/dist/rules/no-for-in.d.ts +2 -0
- package/dist/rules/no-for-in.d.ts.map +1 -0
- package/dist/rules/no-for-in.js +27 -0
- package/dist/rules/no-let-any.d.ts +3 -0
- package/dist/rules/no-let-any.d.ts.map +1 -0
- package/dist/rules/no-let-any.js +45 -0
- package/dist/rules/no-mutable-return.d.ts +5 -0
- package/dist/rules/no-mutable-return.d.ts.map +1 -0
- package/dist/rules/no-mutable-return.js +63 -0
- package/dist/rules/no-number-enums.d.ts +2 -0
- package/dist/rules/no-number-enums.d.ts.map +1 -0
- package/dist/rules/no-number-enums.js +27 -0
- package/dist/rules/no-object-any.d.ts +3 -0
- package/dist/rules/no-object-any.d.ts.map +1 -0
- package/dist/rules/no-object-any.js +51 -0
- package/dist/rules/no-object-methods-with-map-set.d.ts +5 -0
- package/dist/rules/no-object-methods-with-map-set.d.ts.map +1 -0
- package/dist/rules/no-object-methods-with-map-set.js +84 -0
- package/dist/rules/no-string-length-0.d.ts +3 -0
- package/dist/rules/no-string-length-0.d.ts.map +1 -0
- package/dist/rules/no-string-length-0.js +52 -0
- package/dist/rules/no-template-curly-in-string-fix.d.ts +6 -0
- package/dist/rules/no-template-curly-in-string-fix.d.ts.map +1 -0
- package/dist/rules/no-template-curly-in-string-fix.js +39 -0
- package/dist/rules/no-undefined-return-type.d.ts +3 -0
- package/dist/rules/no-undefined-return-type.d.ts.map +1 -0
- package/dist/rules/no-undefined-return-type.js +40 -0
- package/dist/rules/no-unnecessary-assignment.d.ts +5 -0
- package/dist/rules/no-unnecessary-assignment.d.ts.map +1 -0
- package/dist/rules/no-unnecessary-assignment.js +255 -0
- package/dist/rules/no-unsafe-plusplus.d.ts +2 -0
- package/dist/rules/no-unsafe-plusplus.d.ts.map +1 -0
- package/dist/rules/no-unsafe-plusplus.js +34 -0
- package/dist/rules/no-useless-return.d.ts +2 -0
- package/dist/rules/no-useless-return.d.ts.map +1 -0
- package/dist/rules/no-useless-return.js +347 -0
- package/dist/rules/no-void-return-type.d.ts +2 -0
- package/dist/rules/no-void-return-type.d.ts.map +1 -0
- package/dist/rules/no-void-return-type.js +49 -0
- package/dist/rules/prefer-const.d.ts +2 -0
- package/dist/rules/prefer-const.d.ts.map +1 -0
- package/dist/rules/prefer-const.js +426 -0
- package/dist/rules/prefer-plusplus.d.ts +5 -0
- package/dist/rules/prefer-plusplus.d.ts.map +1 -0
- package/dist/rules/prefer-plusplus.js +49 -0
- package/dist/rules/prefer-postfix-plusplus.d.ts +2 -0
- package/dist/rules/prefer-postfix-plusplus.d.ts.map +1 -0
- package/dist/rules/prefer-postfix-plusplus.js +32 -0
- package/dist/rules/prefer-readonly-parameter-types.d.ts +13 -0
- package/dist/rules/prefer-readonly-parameter-types.d.ts.map +1 -0
- package/dist/rules/prefer-readonly-parameter-types.js +140 -0
- package/dist/rules/require-break.d.ts +5 -0
- package/dist/rules/require-break.d.ts.map +1 -0
- package/dist/rules/require-break.js +76 -0
- package/dist/rules/require-capital-const-assertions.d.ts +4 -0
- package/dist/rules/require-capital-const-assertions.d.ts.map +1 -0
- package/dist/rules/require-capital-const-assertions.js +112 -0
- package/dist/rules/require-capital-read-only.d.ts +5 -0
- package/dist/rules/require-capital-read-only.d.ts.map +1 -0
- package/dist/rules/require-capital-read-only.js +111 -0
- package/dist/rules/require-unannotated-const-assertions.d.ts +2 -0
- package/dist/rules/require-unannotated-const-assertions.d.ts.map +1 -0
- package/dist/rules/require-unannotated-const-assertions.js +27 -0
- package/dist/rules/require-variadic-function-argument.d.ts +5 -0
- package/dist/rules/require-variadic-function-argument.d.ts.map +1 -0
- package/dist/rules/require-variadic-function-argument.js +86 -0
- package/dist/rules/strict-array-methods.d.ts +3 -0
- package/dist/rules/strict-array-methods.d.ts.map +1 -0
- package/dist/rules/strict-array-methods.js +83 -0
- package/dist/rules/strict-enums.d.ts +5 -0
- package/dist/rules/strict-enums.d.ts.map +1 -0
- package/dist/rules/strict-enums.js +445 -0
- package/dist/rules/strict-undefined-functions.d.ts +5 -0
- package/dist/rules/strict-undefined-functions.d.ts.map +1 -0
- package/dist/rules/strict-undefined-functions.js +49 -0
- package/dist/rules/strict-void-functions.d.ts +2 -0
- package/dist/rules/strict-void-functions.d.ts.map +1 -0
- package/dist/rules/strict-void-functions.js +43 -0
- package/dist/rules.d.ts +49 -0
- package/dist/rules.d.ts.map +1 -0
- package/dist/rules.js +85 -0
- package/dist/template.d.ts +2 -0
- package/dist/template.d.ts.map +1 -0
- package/dist/template.js +29 -0
- package/dist/typeUtils.d.ts +28 -0
- package/dist/typeUtils.d.ts.map +1 -0
- package/dist/typeUtils.js +76 -0
- package/dist/utils.d.ts +13 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +54 -0
- package/package.json +55 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { AST_NODE_TYPES, ESLintUtils } from "@typescript-eslint/utils";
|
|
2
|
+
import { getTypeName } from "../typeUtils.js";
|
|
3
|
+
import { createRule } from "../utils.js";
|
|
4
|
+
const ARRAY_METHODS_WITH_BOOLEAN_FUNCTIONS = new Set([
|
|
5
|
+
"every",
|
|
6
|
+
"filter",
|
|
7
|
+
"find",
|
|
8
|
+
"findIndex",
|
|
9
|
+
"findLast",
|
|
10
|
+
"findLastIndex",
|
|
11
|
+
"some",
|
|
12
|
+
]);
|
|
13
|
+
export const strictArrayMethods = createRule({
|
|
14
|
+
name: "strict-array-methods",
|
|
15
|
+
meta: {
|
|
16
|
+
type: "problem",
|
|
17
|
+
docs: {
|
|
18
|
+
description: "Requires boolean return types on array method functions",
|
|
19
|
+
recommended: true,
|
|
20
|
+
requiresTypeChecking: true,
|
|
21
|
+
},
|
|
22
|
+
schema: [],
|
|
23
|
+
messages: {
|
|
24
|
+
conditionError: "Unexpected value in array method. A boolean expression is required.",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
defaultOptions: [],
|
|
28
|
+
create(context) {
|
|
29
|
+
const parserServices = ESLintUtils.getParserServices(context);
|
|
30
|
+
const checker = parserServices.program.getTypeChecker();
|
|
31
|
+
function isArrayMethodInvocationWithBooleanFunction(node) {
|
|
32
|
+
const { callee } = node;
|
|
33
|
+
if (callee.type !== AST_NODE_TYPES.MemberExpression) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(callee.object);
|
|
37
|
+
const type = checker.getTypeAtLocation(tsNode);
|
|
38
|
+
const typeName = getTypeName(type);
|
|
39
|
+
if (typeName !== "Array" && typeName !== "ReadonlyArray") {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
const { property } = callee;
|
|
43
|
+
if (property.type !== AST_NODE_TYPES.Identifier) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
const methodName = property.name;
|
|
47
|
+
return ARRAY_METHODS_WITH_BOOLEAN_FUNCTIONS.has(methodName);
|
|
48
|
+
}
|
|
49
|
+
function firstArgumentHasBooleanReturnType(node) {
|
|
50
|
+
const argument = node.arguments[0];
|
|
51
|
+
if (argument === undefined) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(argument);
|
|
55
|
+
const type = checker.getTypeAtLocation(tsNode);
|
|
56
|
+
const callSignatures = type.getCallSignatures();
|
|
57
|
+
if (callSignatures.length === 0) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
for (const callSignature of callSignatures) {
|
|
61
|
+
const returnType = callSignature.getReturnType();
|
|
62
|
+
const returnTypeName = getTypeName(returnType);
|
|
63
|
+
if (returnTypeName !== "boolean" &&
|
|
64
|
+
returnTypeName !== "true" &&
|
|
65
|
+
returnTypeName !== "false") {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
CallExpression(node) {
|
|
73
|
+
if (isArrayMethodInvocationWithBooleanFunction(node) &&
|
|
74
|
+
!firstArgumentHasBooleanReturnType(node)) {
|
|
75
|
+
context.report({
|
|
76
|
+
loc: node.loc,
|
|
77
|
+
messageId: "conditionError",
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
2
|
+
export type Options = [];
|
|
3
|
+
export type MessageIds = "incorrectIncrement" | "mismatchedAssignment" | "mismatchedFunctionArgument";
|
|
4
|
+
export declare const strictEnums: ESLintUtils.RuleModule<MessageIds, [], import("../interfaces/MyPluginDocs.js").MyPluginDocs, ESLintUtils.RuleListener>;
|
|
5
|
+
//# sourceMappingURL=strict-enums.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strict-enums.d.ts","sourceRoot":"","sources":["../../src/rules/strict-enums.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAevD,MAAM,MAAM,OAAO,GAAG,EAAE,CAAC;AAEzB,MAAM,MAAM,UAAU,GAClB,oBAAoB,GACpB,sBAAsB,GACtB,4BAA4B,CAAC;AAEjC,eAAO,MAAM,WAAW,wHA4dtB,CAAC"}
|
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
import { getTypeName, isTypeReferenceType, } from "@typescript-eslint/type-utils";
|
|
2
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
3
|
+
import ts from "typescript";
|
|
4
|
+
import { isSymbolFlagSet, isTypeFlagSet, unionTypeParts, } from "../typeUtils.js";
|
|
5
|
+
import { createRule } from "../utils.js";
|
|
6
|
+
const ALLOWED_TYPES_FOR_ANY_ENUM_ARGUMENT = ts.TypeFlags.Any |
|
|
7
|
+
ts.TypeFlags.Unknown |
|
|
8
|
+
ts.TypeFlags.Number |
|
|
9
|
+
ts.TypeFlags.String;
|
|
10
|
+
export const strictEnums = createRule({
|
|
11
|
+
name: "strict-enums",
|
|
12
|
+
meta: {
|
|
13
|
+
type: "problem",
|
|
14
|
+
docs: {
|
|
15
|
+
description: "Disallows the usage of unsafe enum patterns",
|
|
16
|
+
recommended: true,
|
|
17
|
+
requiresTypeChecking: true,
|
|
18
|
+
},
|
|
19
|
+
schema: [],
|
|
20
|
+
messages: {
|
|
21
|
+
incorrectIncrement: "You cannot increment or decrement an enum type.",
|
|
22
|
+
mismatchedAssignment: "The type of the enum assignment does not match the declared enum type of the variable.",
|
|
23
|
+
mismatchedFunctionArgument: "The {{ ordinal }} argument in the function call does not match the declared enum type of the function signature.\nArgument: {{ type1 }}\nParameter: {{ type2 }}",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
defaultOptions: [],
|
|
27
|
+
create(context) {
|
|
28
|
+
const parserServices = ESLintUtils.getParserServices(context);
|
|
29
|
+
const checker = parserServices.program.getTypeChecker();
|
|
30
|
+
// ----------------
|
|
31
|
+
// Helper functions
|
|
32
|
+
// ----------------
|
|
33
|
+
/**
|
|
34
|
+
* If passed an enum member, returns the type of the parent. Otherwise, returns itself.
|
|
35
|
+
*
|
|
36
|
+
* For example:
|
|
37
|
+
* - `Fruit` --> `Fruit`
|
|
38
|
+
* - `Fruit.Apple` --> `Fruit`
|
|
39
|
+
*/
|
|
40
|
+
function getBaseEnumType(type) {
|
|
41
|
+
const symbol = type.getSymbol();
|
|
42
|
+
if (symbol === undefined) {
|
|
43
|
+
return type;
|
|
44
|
+
}
|
|
45
|
+
if (!isSymbolFlagSet(symbol, ts.SymbolFlags.EnumMember)) {
|
|
46
|
+
return type;
|
|
47
|
+
}
|
|
48
|
+
const { valueDeclaration } = symbol;
|
|
49
|
+
if (valueDeclaration === undefined) {
|
|
50
|
+
return type;
|
|
51
|
+
}
|
|
52
|
+
const parentType = getTypeFromTSNode(valueDeclaration.parent);
|
|
53
|
+
return parentType;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* A thing can have 0 or more enum types. For example:
|
|
57
|
+
* - 123 --> []
|
|
58
|
+
* - {} --> []
|
|
59
|
+
* - Fruit.Apple --> [Fruit]
|
|
60
|
+
* - Fruit.Apple | Vegetable.Lettuce --> [Fruit, Vegetable]
|
|
61
|
+
* - Fruit.Apple | Vegetable.Lettuce | 123 --> [Fruit, Vegetable]
|
|
62
|
+
* - T extends Fruit --> [Fruit]
|
|
63
|
+
*/
|
|
64
|
+
function getEnumTypes(type) {
|
|
65
|
+
/**
|
|
66
|
+
* First, we get all the parts of the union. For non-union types, this will be an array with
|
|
67
|
+
* the type in it. For example:
|
|
68
|
+
* - Fruit --> [Fruit]
|
|
69
|
+
* - Fruit | Vegetable --> [Fruit, Vegetable]
|
|
70
|
+
*/
|
|
71
|
+
const subTypes = unionTypeParts(type);
|
|
72
|
+
/**
|
|
73
|
+
* Next, we must resolve generic types with constraints. For example:
|
|
74
|
+
* - Fruit --> Fruit
|
|
75
|
+
* - T extends Fruit --> Fruit
|
|
76
|
+
*/
|
|
77
|
+
const subTypesConstraints = subTypes.map((subType) => {
|
|
78
|
+
const constraint = subType.getConstraint();
|
|
79
|
+
return constraint ?? subType;
|
|
80
|
+
});
|
|
81
|
+
const enumSubTypes = subTypesConstraints.filter((subType) => isEnum(subType));
|
|
82
|
+
const baseEnumSubTypes = enumSubTypes.map((subType) => getBaseEnumType(subType));
|
|
83
|
+
return new Set(baseEnumSubTypes);
|
|
84
|
+
}
|
|
85
|
+
function getTypeFromNode(node) {
|
|
86
|
+
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
|
|
87
|
+
return getTypeFromTSNode(tsNode);
|
|
88
|
+
}
|
|
89
|
+
function getTypeFromTSNode(tsNode) {
|
|
90
|
+
return checker.getTypeAtLocation(tsNode);
|
|
91
|
+
}
|
|
92
|
+
function hasEnumTypes(type) {
|
|
93
|
+
const enumTypes = getEnumTypes(type);
|
|
94
|
+
return enumTypes.size > 0;
|
|
95
|
+
}
|
|
96
|
+
// --------------
|
|
97
|
+
// Main functions
|
|
98
|
+
// --------------
|
|
99
|
+
function checkCallExpression(node) {
|
|
100
|
+
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
|
|
101
|
+
const signature = checker.getResolvedSignature(tsNode);
|
|
102
|
+
if (signature === undefined) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const declaration = signature.getDeclaration();
|
|
106
|
+
// The `getDeclaration` method actually returns `ts.SignatureDeclaration | undefined`, not
|
|
107
|
+
// `ts.SignatureDeclaration`.
|
|
108
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
109
|
+
if (declaration === undefined) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
// First, determine if this is a function with a `this` parameter.
|
|
113
|
+
let firstParamIsThis = false;
|
|
114
|
+
const firstParameter = declaration.parameters[0];
|
|
115
|
+
if (firstParameter !== undefined) {
|
|
116
|
+
const parameterName = firstParameter.name.getText();
|
|
117
|
+
firstParamIsThis = parameterName === "this";
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Iterate through the arguments provided to the call function and cross reference their types
|
|
121
|
+
* to the types of the "real" function parameters.
|
|
122
|
+
*/
|
|
123
|
+
for (const [i, argument] of node.arguments.entries()) {
|
|
124
|
+
const argumentType = getTypeFromNode(argument);
|
|
125
|
+
let parameterType = signature.getTypeParameterAtPosition(i);
|
|
126
|
+
/**
|
|
127
|
+
* If this function parameter is a generic type that extends another type, we want to
|
|
128
|
+
* compare the calling argument to the constraint instead.
|
|
129
|
+
*
|
|
130
|
+
* For example:
|
|
131
|
+
*
|
|
132
|
+
* ```ts
|
|
133
|
+
* function useFruit<FruitType extends Fruit>(fruitType: FruitType) {}
|
|
134
|
+
* useFruit(0)
|
|
135
|
+
* ```
|
|
136
|
+
*
|
|
137
|
+
* Here, we want to compare `Fruit.Apple` to `Fruit`, not `FruitType`, because `FruitType`
|
|
138
|
+
* would just be equal to 0 in this case (and would be unsafe).
|
|
139
|
+
*
|
|
140
|
+
* Finally, if the function has a `this` parameter, getting a constraint will mess things
|
|
141
|
+
* up, so we skip checking for a constraint if this is the case.
|
|
142
|
+
*/
|
|
143
|
+
if (!firstParamIsThis) {
|
|
144
|
+
const parameter = declaration.parameters[i];
|
|
145
|
+
if (parameter !== undefined) {
|
|
146
|
+
const parameterTSNode = getTypeFromTSNode(parameter);
|
|
147
|
+
const constraint = parameterTSNode.getConstraint();
|
|
148
|
+
if (constraint !== undefined) {
|
|
149
|
+
parameterType = constraint;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Disallow mismatched function calls, like the following:
|
|
155
|
+
*
|
|
156
|
+
* ```ts
|
|
157
|
+
* function useFruit(fruit: Fruit) {}
|
|
158
|
+
* useFruit(0);
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
if (isMismatchedEnumFunctionArgument(argumentType, parameterType)) {
|
|
162
|
+
context.report({
|
|
163
|
+
node: argument,
|
|
164
|
+
messageId: "mismatchedFunctionArgument",
|
|
165
|
+
data: {
|
|
166
|
+
ordinal: getOrdinalSuffix(i + 1), // e.g. 0 --> 1st
|
|
167
|
+
type1: getTypeName(checker, argumentType),
|
|
168
|
+
type2: getTypeName(checker, parameterType),
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function isAssigningNonEnumValueToEnumVariable(leftType, rightType) {
|
|
175
|
+
/**
|
|
176
|
+
* First, recursively check for containers like the following:
|
|
177
|
+
*
|
|
178
|
+
* ```ts
|
|
179
|
+
* declare let fruits: Fruit[];
|
|
180
|
+
* fruits = [0, 1];
|
|
181
|
+
* ```
|
|
182
|
+
*/
|
|
183
|
+
if (isTypeReferenceType(leftType) && isTypeReferenceType(rightType)) {
|
|
184
|
+
const leftTypeArguments = checker.getTypeArguments(leftType);
|
|
185
|
+
const rightTypeArguments = checker.getTypeArguments(rightType);
|
|
186
|
+
// eslint-disable-next-line unicorn/no-for-loop
|
|
187
|
+
for (let i = 0; i < leftTypeArguments.length; i++) {
|
|
188
|
+
const leftTypeArgument = leftTypeArguments[i];
|
|
189
|
+
const rightTypeArgument = rightTypeArguments[i];
|
|
190
|
+
if (leftTypeArgument === undefined ||
|
|
191
|
+
rightTypeArgument === undefined) {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (isAssigningNonEnumValueToEnumVariable(leftTypeArgument, rightTypeArgument)) {
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
const leftEnumTypes = getEnumTypes(leftType);
|
|
201
|
+
if (leftEnumTypes.size === 0) {
|
|
202
|
+
// This is not an enum assignment.
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* As a special case, allow assignment of certain types that the TypeScript compiler should
|
|
207
|
+
* handle properly.
|
|
208
|
+
*/
|
|
209
|
+
if (isNullOrUndefinedOrAnyOrUnknownOrNever(rightType)) {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
const rightEnumTypes = getEnumTypes(rightType);
|
|
213
|
+
const intersectingTypes = getIntersectingSet(leftEnumTypes, rightEnumTypes);
|
|
214
|
+
return intersectingTypes.size === 0;
|
|
215
|
+
}
|
|
216
|
+
function isMismatchedEnumFunctionArgument(argumentType, // From the function call
|
|
217
|
+
parameterType) {
|
|
218
|
+
/**
|
|
219
|
+
* First, recursively check for functions with type containers like the following:
|
|
220
|
+
*
|
|
221
|
+
* ```ts
|
|
222
|
+
* function useFruits(fruits: Fruit[]) {}
|
|
223
|
+
* useFruits([0, 1]);
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
226
|
+
if (isTypeReferenceType(argumentType)) {
|
|
227
|
+
const argumentTypeArguments = checker.getTypeArguments(argumentType);
|
|
228
|
+
const parameterSubTypes = unionTypeParts(parameterType);
|
|
229
|
+
for (const parameterSubType of parameterSubTypes) {
|
|
230
|
+
if (!isTypeReferenceType(parameterSubType)) {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
const parameterTypeArguments = checker.getTypeArguments(parameterSubType);
|
|
234
|
+
// eslint-disable-next-line unicorn/no-for-loop
|
|
235
|
+
for (let i = 0; i < argumentTypeArguments.length; i++) {
|
|
236
|
+
const argumentTypeArgument = argumentTypeArguments[i];
|
|
237
|
+
const parameterTypeArgument = parameterTypeArguments[i];
|
|
238
|
+
if (argumentTypeArgument === undefined ||
|
|
239
|
+
parameterTypeArgument === undefined) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if (isMismatchedEnumFunctionArgument(argumentTypeArgument, parameterTypeArgument)) {
|
|
243
|
+
return true;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Allow function calls that have nothing to do with enums, like the following:
|
|
251
|
+
*
|
|
252
|
+
* ```ts
|
|
253
|
+
* function useNumber(num: number) {}
|
|
254
|
+
* useNumber(0);
|
|
255
|
+
* ```
|
|
256
|
+
*/
|
|
257
|
+
const argumentEnumTypes = getEnumTypes(argumentType);
|
|
258
|
+
const parameterEnumTypes = getEnumTypes(parameterType);
|
|
259
|
+
if (parameterEnumTypes.size === 0) {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Allow passing enum values into functions that take in the "any" type and similar types that
|
|
264
|
+
* should basically match any enum, like the following:
|
|
265
|
+
*
|
|
266
|
+
* ```ts
|
|
267
|
+
* function useNumber(num: number) {}
|
|
268
|
+
* useNumber(Fruit.Apple);
|
|
269
|
+
* ```
|
|
270
|
+
*/
|
|
271
|
+
const parameterSubTypes = unionTypeParts(parameterType);
|
|
272
|
+
for (const parameterSubType of parameterSubTypes) {
|
|
273
|
+
if (isTypeFlagSet(parameterSubType, ALLOWED_TYPES_FOR_ANY_ENUM_ARGUMENT)) {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Disallow passing number literals or string literals into functions that take in an enum,
|
|
279
|
+
* like the following:
|
|
280
|
+
*
|
|
281
|
+
* ```ts
|
|
282
|
+
* function useFruit(fruit: Fruit) {}
|
|
283
|
+
* declare const fruit: Fruit.Apple | 1;
|
|
284
|
+
* useFruit(fruit)
|
|
285
|
+
* ```
|
|
286
|
+
*/
|
|
287
|
+
const argumentSubTypes = unionTypeParts(argumentType);
|
|
288
|
+
for (const argumentSubType of argumentSubTypes) {
|
|
289
|
+
if (argumentSubType.isLiteral() &&
|
|
290
|
+
!isEnum(argumentSubType) &&
|
|
291
|
+
// Allow passing number literals if there are number literals in the actual function type.
|
|
292
|
+
!parameterSubTypes.includes(argumentSubType)) {
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Allow function calls that match one of the types in a union, like the following:
|
|
298
|
+
*
|
|
299
|
+
* ```ts
|
|
300
|
+
* function useApple(fruitOrNull: Fruit | null) {}
|
|
301
|
+
* useApple(null);
|
|
302
|
+
* ```
|
|
303
|
+
*/
|
|
304
|
+
const argumentSubTypesSet = new Set(argumentSubTypes);
|
|
305
|
+
const parameterSubTypesSet = new Set(parameterSubTypes);
|
|
306
|
+
if (setHasAnyElementFromSet(argumentSubTypesSet, parameterSubTypesSet)) {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Allow function calls that have a base enum that match the function type, like the
|
|
311
|
+
* following:
|
|
312
|
+
*
|
|
313
|
+
* ```ts
|
|
314
|
+
* function useFruit(fruit: Fruit) {}
|
|
315
|
+
* useFruit(Fruit.Apple);
|
|
316
|
+
* ```
|
|
317
|
+
*/
|
|
318
|
+
if (setHasAnyElementFromSet(argumentEnumTypes, parameterEnumTypes)) {
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
return true;
|
|
322
|
+
}
|
|
323
|
+
// ------------------
|
|
324
|
+
// AST node callbacks
|
|
325
|
+
// ------------------
|
|
326
|
+
return {
|
|
327
|
+
/** When something is assigned to a variable. */
|
|
328
|
+
AssignmentExpression(node) {
|
|
329
|
+
const leftType = getTypeFromNode(node.left);
|
|
330
|
+
const rightType = getTypeFromNode(node.right);
|
|
331
|
+
if (isAssigningNonEnumValueToEnumVariable(leftType, rightType)) {
|
|
332
|
+
context.report({
|
|
333
|
+
node,
|
|
334
|
+
messageId: "mismatchedAssignment",
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
/** When a function is invoked or a class is instantiated. */
|
|
339
|
+
"CallExpression, NewExpression": function callExpressionOrNewExpression(node) {
|
|
340
|
+
checkCallExpression(node);
|
|
341
|
+
},
|
|
342
|
+
/** When a unary operator is invoked. */
|
|
343
|
+
UpdateExpression(node) {
|
|
344
|
+
const argumentType = getTypeFromNode(node.argument);
|
|
345
|
+
/**
|
|
346
|
+
* Disallow using enums with unary operators, like the following:
|
|
347
|
+
*
|
|
348
|
+
* ```ts
|
|
349
|
+
* const fruit = Fruit.Apple;
|
|
350
|
+
* fruit++;
|
|
351
|
+
* ```
|
|
352
|
+
*/
|
|
353
|
+
if (hasEnumTypes(argumentType)) {
|
|
354
|
+
context.report({
|
|
355
|
+
node,
|
|
356
|
+
messageId: "incorrectIncrement",
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
/** When a new variable is created. */
|
|
361
|
+
VariableDeclarator(node) {
|
|
362
|
+
/**
|
|
363
|
+
* Allow enum declarations without an initializer, like the following:
|
|
364
|
+
*
|
|
365
|
+
* ```ts
|
|
366
|
+
* let fruit: Fruit;
|
|
367
|
+
* if (something()) {
|
|
368
|
+
* fruit = Fruit.Apple;
|
|
369
|
+
* } else {
|
|
370
|
+
* fruit = Fruit.Banana;
|
|
371
|
+
* }
|
|
372
|
+
* ```
|
|
373
|
+
*/
|
|
374
|
+
if (node.init === null) {
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
const leftTSNode = parserServices.esTreeNodeToTSNodeMap.get(node);
|
|
378
|
+
const rightNode = leftTSNode.initializer;
|
|
379
|
+
if (rightNode === undefined) {
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* We have to use `leftTSNode.name` instead of `leftTSNode` to avoid run-time errors because
|
|
384
|
+
* the `checker.getTypeAtLocation` method expects a `ts.BindingName` instead of a
|
|
385
|
+
* `ts.VariableDeclaration`.
|
|
386
|
+
* https://github.com/microsoft/TypeScript/issues/48878
|
|
387
|
+
*/
|
|
388
|
+
const leftType = getTypeFromTSNode(leftTSNode.name);
|
|
389
|
+
const rightType = getTypeFromTSNode(rightNode);
|
|
390
|
+
if (isAssigningNonEnumValueToEnumVariable(leftType, rightType)) {
|
|
391
|
+
context.report({
|
|
392
|
+
node,
|
|
393
|
+
messageId: "mismatchedAssignment",
|
|
394
|
+
data: {
|
|
395
|
+
assignmentType: getTypeName(checker, rightType),
|
|
396
|
+
declaredType: getTypeName(checker, leftType),
|
|
397
|
+
},
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
},
|
|
401
|
+
};
|
|
402
|
+
},
|
|
403
|
+
});
|
|
404
|
+
/** Given a set A and set B, return a set that contains only elements that are in both sets. */
|
|
405
|
+
function getIntersectingSet(a, b) {
|
|
406
|
+
const intersectingValues = [...a.values()].filter((value) => b.has(value));
|
|
407
|
+
return new Set(intersectingValues);
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* From:
|
|
411
|
+
* https://stackoverflow.com/questions/13627308/add-st-nd-rd-and-th-ordinal-suffix-to-a-number
|
|
412
|
+
*/
|
|
413
|
+
function getOrdinalSuffix(i) {
|
|
414
|
+
const j = i % 10;
|
|
415
|
+
const k = i % 100;
|
|
416
|
+
if (j === 1 && k !== 11) {
|
|
417
|
+
return `${i}st`;
|
|
418
|
+
}
|
|
419
|
+
if (j === 2 && k !== 12) {
|
|
420
|
+
return `${i}nd`;
|
|
421
|
+
}
|
|
422
|
+
if (j === 3 && k !== 13) {
|
|
423
|
+
return `${i}rd`;
|
|
424
|
+
}
|
|
425
|
+
return `${i}th`;
|
|
426
|
+
}
|
|
427
|
+
function isEnum(type) {
|
|
428
|
+
/** The "EnumLiteral" flag will be set on both enum base types and enum members/values. */
|
|
429
|
+
return isTypeFlagSet(type, ts.TypeFlags.EnumLiteral);
|
|
430
|
+
}
|
|
431
|
+
function isNullOrUndefinedOrAnyOrUnknownOrNever(...types) {
|
|
432
|
+
return types.some((type) => isTypeFlagSet(type, ts.TypeFlags.Null |
|
|
433
|
+
ts.TypeFlags.Undefined |
|
|
434
|
+
ts.TypeFlags.Any |
|
|
435
|
+
ts.TypeFlags.Unknown |
|
|
436
|
+
ts.TypeFlags.Never));
|
|
437
|
+
}
|
|
438
|
+
function setHasAnyElementFromSet(set1, set2) {
|
|
439
|
+
for (const value of set2) {
|
|
440
|
+
if (set1.has(value)) {
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
2
|
+
export type Options = [];
|
|
3
|
+
export type MessageIds = "mismatchedReturnType";
|
|
4
|
+
export declare const strictUndefinedFunctions: ESLintUtils.RuleModule<"mismatchedReturnType", [], import("../interfaces/MyPluginDocs.js").MyPluginDocs, ESLintUtils.RuleListener>;
|
|
5
|
+
//# sourceMappingURL=strict-undefined-functions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strict-undefined-functions.d.ts","sourceRoot":"","sources":["../../src/rules/strict-undefined-functions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAIvD,MAAM,MAAM,OAAO,GAAG,EAAE,CAAC;AACzB,MAAM,MAAM,UAAU,GAAG,sBAAsB,CAAC;AAEhD,eAAO,MAAM,wBAAwB,oIAkDnC,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
2
|
+
import { getTypeName, unionTypeParts } from "../typeUtils.js";
|
|
3
|
+
import { createRule, getParentFunction } from "../utils.js";
|
|
4
|
+
export const strictUndefinedFunctions = createRule({
|
|
5
|
+
name: "strict-undefined-functions",
|
|
6
|
+
meta: {
|
|
7
|
+
type: "problem",
|
|
8
|
+
docs: {
|
|
9
|
+
description: "Disallows empty return statements in functions annotated as returning undefined",
|
|
10
|
+
recommended: true,
|
|
11
|
+
requiresTypeChecking: true,
|
|
12
|
+
},
|
|
13
|
+
schema: [],
|
|
14
|
+
messages: {
|
|
15
|
+
mismatchedReturnType: "You must explicitly return `undefined` in in functions that can return `undefined`.",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
defaultOptions: [],
|
|
19
|
+
create(context) {
|
|
20
|
+
const parserServices = ESLintUtils.getParserServices(context);
|
|
21
|
+
const checker = parserServices.program.getTypeChecker();
|
|
22
|
+
return {
|
|
23
|
+
ReturnStatement(node) {
|
|
24
|
+
if (node.argument !== null) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const parentFunction = getParentFunction(node);
|
|
28
|
+
if (parentFunction === undefined) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(parentFunction);
|
|
32
|
+
const type = checker.getTypeAtLocation(tsNode);
|
|
33
|
+
const signatures = type.getCallSignatures();
|
|
34
|
+
for (const signature of signatures) {
|
|
35
|
+
const returnType = signature.getReturnType();
|
|
36
|
+
for (const t of unionTypeParts(returnType)) {
|
|
37
|
+
const typeName = getTypeName(t);
|
|
38
|
+
if (typeName === "undefined") {
|
|
39
|
+
context.report({
|
|
40
|
+
loc: node.loc,
|
|
41
|
+
messageId: "mismatchedReturnType",
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
});
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const strictVoidFunctions: import("@typescript-eslint/utils/ts-eslint").RuleModule<"mismatchedReturnType", [], import("../interfaces/MyPluginDocs.js").MyPluginDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
|
|
2
|
+
//# sourceMappingURL=strict-void-functions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"strict-void-functions.d.ts","sourceRoot":"","sources":["../../src/rules/strict-void-functions.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,mBAAmB,sMA8C9B,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { AST_NODE_TYPES } from "@typescript-eslint/utils";
|
|
2
|
+
import { createRule, getParentFunction } from "../utils.js";
|
|
3
|
+
export const strictVoidFunctions = createRule({
|
|
4
|
+
name: "strict-void-functions",
|
|
5
|
+
meta: {
|
|
6
|
+
type: "problem",
|
|
7
|
+
docs: {
|
|
8
|
+
description: "Disallows non-empty return statements in functions annotated as returning void",
|
|
9
|
+
recommended: true,
|
|
10
|
+
requiresTypeChecking: false,
|
|
11
|
+
},
|
|
12
|
+
schema: [],
|
|
13
|
+
messages: {
|
|
14
|
+
mismatchedReturnType: "You cannot return values in functions annotated as returning `void`. Either remove the value after the `return` keyword or change the function return type annotation.",
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
defaultOptions: [],
|
|
18
|
+
create(context) {
|
|
19
|
+
return {
|
|
20
|
+
ReturnStatement(node) {
|
|
21
|
+
if (node.argument === null) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const parentFunction = getParentFunction(node);
|
|
25
|
+
if (parentFunction === undefined) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const { returnType } = parentFunction;
|
|
29
|
+
if (returnType === undefined) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const { typeAnnotation } = returnType;
|
|
33
|
+
if (typeAnnotation.type !== AST_NODE_TYPES.TSVoidKeyword) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
context.report({
|
|
37
|
+
loc: node.loc,
|
|
38
|
+
messageId: "mismatchedReturnType",
|
|
39
|
+
});
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
});
|