@@ -15,120 +15,6 @@ const util = require('../util');
15
15
// Rule Definition
16
16
//------------------------------------------------------------------------------
17
17
18
- /**
19
- * Sometimes tuple types don't have ObjectFlags.Tuple set, like when they're being matched against an inferred type.
20
- * So, in addition, check if there are integer properties 0..n and no other numeric keys
21
- * @param {ts.ObjectType } type type
22
- * @returns {boolean } true if type could be a tuple type
23
- */
24
- function couldBeTupleType ( type ) {
25
- const properties = type . getProperties ( ) ;
26
-
27
- if ( properties . length === 0 ) {
28
- return false ;
29
- }
30
- let i = 0 ;
31
-
32
- for ( ; i < properties . length ; ++ i ) {
33
- const name = properties [ i ] . name ;
34
-
35
- if ( String ( i ) !== name ) {
36
- if ( i === 0 ) {
37
- // if there are no integer properties, this is not a tuple
38
- return false ;
39
- }
40
- break ;
41
- }
42
- }
43
- for ( ; i < properties . length ; ++ i ) {
44
- if ( String ( + properties [ i ] . name ) === properties [ i ] . name ) {
45
- return false ; // if there are any other numeric properties, this is not a tuple
46
- }
47
- }
48
- return true ;
49
- }
50
-
51
- /**
52
- *
53
- * @param {Node } node node being linted
54
- * @param {Context } context linting context
55
- * @param {ts.TypeChecker } checker TypeScript typechecker
56
- * @returns {void }
57
- */
58
- function checkNonNullAssertion ( node , context , checker ) {
59
- /**
60
- * Corresponding TSNode is guaranteed to be in map
61
- * @type {ts.NonNullExpression }
62
- */
63
- const originalNode = context . parserServices . esTreeNodeToTSNodeMap . get ( node ) ;
64
- const type = checker . getTypeAtLocation ( originalNode . expression ) ;
65
-
66
- if ( type === checker . getNonNullableType ( type ) ) {
67
- context . report ( {
68
- node,
69
- messageId : 'unnecessaryAssertion' ,
70
- fix ( fixer ) {
71
- return fixer . removeRange ( [
72
- originalNode . expression . end ,
73
- originalNode . end
74
- ] ) ;
75
- }
76
- } ) ;
77
- }
78
- }
79
-
80
- /**
81
- * @param {Node } node node being linted
82
- * @param {Context } context linting context
83
- * @param {ts.TypeChecker } checker TypeScript typechecker
84
- * @returns {void }
85
- */
86
- function verifyCast ( node , context , checker ) {
87
- /**
88
- * * Corresponding TSNode is guaranteed to be in map
89
- * @type {ts.AssertionExpression }
90
- */
91
- const originalNode = context . parserServices . esTreeNodeToTSNodeMap . get ( node ) ;
92
- const options = context . options [ 0 ] ;
93
-
94
- if (
95
- options &&
96
- options . typesToIgnore &&
97
- options . typesToIgnore . indexOf ( originalNode . type . getText ( ) ) !== - 1
98
- ) {
99
- return ;
100
- }
101
- const castType = checker . getTypeAtLocation ( originalNode ) ;
102
-
103
- if (
104
- tsutils . isTypeFlagSet ( castType , ts . TypeFlags . Literal ) ||
105
- ( tsutils . isObjectType ( castType ) &&
106
- ( tsutils . isObjectFlagSet ( castType , ts . ObjectFlags . Tuple ) ||
107
- couldBeTupleType ( castType ) ) )
108
- ) {
109
- // It's not always safe to remove a cast to a literal type or tuple
110
- // type, as those types are sometimes widened without the cast.
111
- return ;
112
- }
113
-
114
- const uncastType = checker . getTypeAtLocation ( originalNode . expression ) ;
115
-
116
- if ( uncastType === castType ) {
117
- context . report ( {
118
- node,
119
- messageId : 'unnecessaryAssertion' ,
120
- fix ( fixer ) {
121
- return originalNode . kind === ts . SyntaxKind . TypeAssertionExpression
122
- ? fixer . removeRange ( [
123
- originalNode . getStart ( ) ,
124
- originalNode . expression . getStart ( )
125
- ] )
126
- : fixer . removeRange ( [ originalNode . expression . end , originalNode . end ] ) ;
127
- }
128
- } ) ;
129
- }
130
- }
131
-
132
18
/** @type {import("eslint").Rule.RuleModule } */
133
19
module . exports = {
134
20
meta : {
@@ -162,17 +48,137 @@ module.exports = {
162
48
} ,
163
49
164
50
create ( context ) {
51
+ const sourceCode = context . getSourceCode ( ) ;
165
52
const checker = util . getParserServices ( context ) . program . getTypeChecker ( ) ;
166
53
54
+ /**
55
+ * Sometimes tuple types don't have ObjectFlags.Tuple set, like when they're being matched against an inferred type.
56
+ * So, in addition, check if there are integer properties 0..n and no other numeric keys
57
+ * @param {ts.ObjectType } type type
58
+ * @returns {boolean } true if type could be a tuple type
59
+ */
60
+ function couldBeTupleType ( type ) {
61
+ const properties = type . getProperties ( ) ;
62
+
63
+ if ( properties . length === 0 ) {
64
+ return false ;
65
+ }
66
+ let i = 0 ;
67
+
68
+ for ( ; i < properties . length ; ++ i ) {
69
+ const name = properties [ i ] . name ;
70
+
71
+ if ( String ( i ) !== name ) {
72
+ if ( i === 0 ) {
73
+ // if there are no integer properties, this is not a tuple
74
+ return false ;
75
+ }
76
+ break ;
77
+ }
78
+ }
79
+ for ( ; i < properties . length ; ++ i ) {
80
+ if ( String ( + properties [ i ] . name ) === properties [ i ] . name ) {
81
+ return false ; // if there are any other numeric properties, this is not a tuple
82
+ }
83
+ }
84
+ return true ;
85
+ }
86
+
87
+ /**
88
+ * @param {Node } node node being linted
89
+ * @returns {void }
90
+ */
91
+ function checkNonNullAssertion ( node ) {
92
+ /**
93
+ * Corresponding TSNode is guaranteed to be in map
94
+ * @type {ts.NonNullExpression }
95
+ */
96
+ const originalNode = context . parserServices . esTreeNodeToTSNodeMap . get (
97
+ node
98
+ ) ;
99
+ const type = checker . getTypeAtLocation ( originalNode . expression ) ;
100
+
101
+ if ( type === checker . getNonNullableType ( type ) ) {
102
+ context . report ( {
103
+ node,
104
+ messageId : 'unnecessaryAssertion' ,
105
+ fix ( fixer ) {
106
+ return fixer . removeRange ( [
107
+ originalNode . expression . end ,
108
+ originalNode . end
109
+ ] ) ;
110
+ }
111
+ } ) ;
112
+ }
113
+ }
114
+
115
+ /**
116
+ * @param {Node } node node being linted
117
+ * @returns {void }
118
+ */
119
+ function verifyCast ( node ) {
120
+ const options = context . options [ 0 ] ;
121
+
122
+ if (
123
+ options &&
124
+ options . typesToIgnore &&
125
+ options . typesToIgnore . indexOf (
126
+ sourceCode . getText ( node . typeAnnotation )
127
+ ) !== - 1
128
+ ) {
129
+ return ;
130
+ }
131
+
132
+ /**
133
+ * Corresponding TSNode is guaranteed to be in map
134
+ * @type {ts.AssertionExpression }
135
+ */
136
+ const originalNode = context . parserServices . esTreeNodeToTSNodeMap . get (
137
+ node
138
+ ) ;
139
+ const castType = checker . getTypeAtLocation ( originalNode ) ;
140
+
141
+ if (
142
+ tsutils . isTypeFlagSet ( castType , ts . TypeFlags . Literal ) ||
143
+ ( tsutils . isObjectType ( castType ) &&
144
+ ( tsutils . isObjectFlagSet ( castType , ts . ObjectFlags . Tuple ) ||
145
+ couldBeTupleType ( castType ) ) )
146
+ ) {
147
+ // It's not always safe to remove a cast to a literal type or tuple
148
+ // type, as those types are sometimes widened without the cast.
149
+ return ;
150
+ }
151
+
152
+ const uncastType = checker . getTypeAtLocation ( originalNode . expression ) ;
153
+
154
+ if ( uncastType === castType ) {
155
+ context . report ( {
156
+ node,
157
+ messageId : 'unnecessaryAssertion' ,
158
+ fix ( fixer ) {
159
+ return originalNode . kind === ts . SyntaxKind . TypeAssertionExpression
160
+ ? fixer . removeRange ( [
161
+ originalNode . getStart ( ) ,
162
+ originalNode . expression . getStart ( )
163
+ ] )
164
+ : fixer . removeRange ( [
165
+ originalNode . expression . end ,
166
+ originalNode . end
167
+ ] ) ;
168
+ }
169
+ } ) ;
170
+ }
171
+ }
172
+
167
173
return {
168
174
TSNonNullExpression ( node ) {
169
- checkNonNullAssertion ( node , context , checker ) ;
175
+ checkNonNullAssertion ( node ) ;
170
176
} ,
171
177
TSTypeAssertion ( node ) {
172
- verifyCast ( node , context , checker ) ;
178
+ verifyCast ( node ) ;
173
179
} ,
174
180
TSAsExpression ( node ) {
175
- verifyCast ( node , context , checker ) ;
181
+ verifyCast ( node ) ;
176
182
}
177
183
} ;
178
184
}
0 commit comments