1
- import { TSESLint } from '@typescript-eslint/utils' ;
1
+ import { TSESTree as es , ESLintUtils , TSESLint } from '@typescript-eslint/utils' ;
2
+ import * as tsutils from 'ts-api-utils' ;
3
+ import ts from 'typescript' ;
2
4
import { getTypeServices } from '../etc' ;
3
5
import { ruleCreator } from '../utils' ;
4
6
@@ -39,12 +41,23 @@ export const noMisusedObservablesRule = ruleCreator({
39
41
} ,
40
42
name : 'no-misused-observables' ,
41
43
create : ( context ) => {
42
- const { couldBeObservable } = getTypeServices ( context ) ;
44
+ const { program, esTreeNodeToTSNodeMap } = ESLintUtils . getParserServices ( context ) ;
45
+ const checker = program . getTypeChecker ( ) ;
46
+ const { couldBeObservable, couldReturnObservable } = getTypeServices ( context ) ;
43
47
const [ config = { } ] = context . options ;
44
48
const { checksVoidReturn = true , checksSpreads = true } = config ;
45
49
46
50
const voidReturnChecks : TSESLint . RuleListener = {
47
-
51
+ CallExpression : checkArguments ,
52
+ // NewExpression: checkArguments,
53
+ // JSXAttribute: checkJSXAttribute,
54
+ // ClassDeclaration: checkClassLikeOrInterfaceNode,
55
+ // ClassExpression: checkClassLikeOrInterfaceNode,
56
+ // TSInterfaceDeclaration: checkClassLikeOrInterfaceNode,
57
+ // Property: checkProperty,
58
+ // ReturnStatement: checkReturnStatement,
59
+ // AssignmentExpression: checkAssignment,
60
+ // VariableDeclarator: checkVariableDeclarator,
48
61
} ;
49
62
50
63
const spreadChecks : TSESLint . RuleListener = {
@@ -58,9 +71,75 @@ export const noMisusedObservablesRule = ruleCreator({
58
71
} ,
59
72
} ;
60
73
74
+ function checkArguments ( node : es . CallExpression | es . NewExpression ) : void {
75
+ const tsNode = esTreeNodeToTSNodeMap . get ( node ) ;
76
+ const voidArgs = voidFunctionArguments ( checker , tsNode ) ;
77
+ if ( ! voidArgs . size ) {
78
+ return ;
79
+ }
80
+
81
+ for ( const [ index , argument ] of node . arguments . entries ( ) ) {
82
+ if ( ! voidArgs . has ( index ) ) {
83
+ continue ;
84
+ }
85
+
86
+ if ( couldReturnObservable ( argument ) ) {
87
+ context . report ( {
88
+ messageId : 'forbiddenVoidReturnArgument' ,
89
+ node : argument ,
90
+ } ) ;
91
+ }
92
+ }
93
+ }
94
+
61
95
return {
62
96
...( checksVoidReturn ? voidReturnChecks : { } ) ,
63
97
...( checksSpreads ? spreadChecks : { } ) ,
64
98
} ;
65
99
} ,
66
100
} ) ;
101
+
102
+ function voidFunctionArguments (
103
+ checker : ts . TypeChecker ,
104
+ tsNode : ts . CallExpression | ts . NewExpression ,
105
+ ) : Set < number > {
106
+ // let b = new Object;
107
+ if ( ! tsNode . arguments ) {
108
+ return new Set < number > ( ) ;
109
+ }
110
+
111
+ const voidReturnIndices = new Set < number > ( ) ;
112
+ const type = checker . getTypeAtLocation ( tsNode . expression ) ;
113
+
114
+ for ( const subType of tsutils . unionTypeParts ( type ) ) {
115
+ const signatures = ts . isCallExpression ( tsNode )
116
+ ? subType . getCallSignatures ( )
117
+ : subType . getConstructSignatures ( ) ;
118
+ for ( const signature of signatures ) {
119
+ for ( const [ index , parameter ] of signature . parameters . entries ( ) ) {
120
+ const type = checker . getTypeOfSymbolAtLocation ( parameter , tsNode . expression ) ;
121
+ if ( isVoidReturningFunctionType ( type ) ) {
122
+ voidReturnIndices . add ( index ) ;
123
+ }
124
+ }
125
+ }
126
+ }
127
+
128
+ return voidReturnIndices ;
129
+ }
130
+
131
+ function isVoidReturningFunctionType (
132
+ type : ts . Type ,
133
+ ) : boolean {
134
+ let hasVoidReturn = false ;
135
+
136
+ for ( const subType of tsutils . unionTypeParts ( type ) ) {
137
+ for ( const signature of subType . getCallSignatures ( ) ) {
138
+ const returnType = signature . getReturnType ( ) ;
139
+
140
+ hasVoidReturn ||= tsutils . isTypeFlagSet ( returnType , ts . TypeFlags . Void ) ;
141
+ }
142
+ }
143
+
144
+ return hasVoidReturn ;
145
+ }
0 commit comments