1
- import { anyTypeName , Types } from "./python" ;
1
+ import { anyTypeName as anyClassOrFunctionName , TypeName , Initialization , TypeCategory } from "./python" ;
2
2
3
-
4
- export interface TypeSearchResult {
5
- typeName : string | null ;
6
-
3
+ /**
4
+ * The source of a type estimation.
5
+ */
6
+ export enum EstimationSource {
7
+ ClassDefinition ,
8
+ Value ,
9
+ ValueOfOtherObject
7
10
}
8
11
9
12
/**
10
- * Detects the type of an initialized variable.
11
- *
12
- * @param src The line of code or value to detect a type for.
13
- * @param srcIsLineOfCode Determine the type from a line of code.
14
- * @returns The type or null if not found.
13
+ * The result of a type search.
15
14
*/
16
- export function detectBasicType ( src : string , srcIsLineOfCode = true ) : string | null {
15
+ export class TypeSearchResult {
16
+ public typeName : string ;
17
+ public estimationSource : EstimationSource ;
17
18
18
- for ( const typeName of typeSearchOrder ) {
19
- let r = typeSearchRegExp ( typeName , srcIsLineOfCode ? "= *" : "" ) ;
20
- if ( r . test ( src ) ) {
21
- return typeName ;
22
- }
19
+ constructor ( typeName : string , estimationSource : EstimationSource ) {
20
+ this . typeName = typeName ;
21
+ this . estimationSource = estimationSource ;
23
22
}
24
- return null ;
23
+
25
24
}
26
25
27
- /**
28
- * Detects non-basic types.
29
- *
30
- * @param lineText The line of code to detect a type for.
31
- * @param documentText The source code of the text document.
32
- * @returns The type or null if not found.
33
- */
34
- export function detectNonBasicType ( lineText : string , documentText : string ) : string | null {
35
- let regExp = new RegExp ( "= *(" + anyTypeName + ")\\(?" ) ;
36
- const match = regExp . exec ( lineText ) ;
26
+ export class CodeSearch {
37
27
38
- if ( ! match ) {
39
- return null ;
40
- }
28
+ /**
29
+ * Detects the type of an initialized variable.
30
+ *
31
+ * @param lineText The line of code to detect a type for.
32
+ * @param documentText The source code of the text document.
33
+ * @returns The type or null if not found.
34
+ */
35
+ public static async detectType ( lineText : string , documentText : string ) : Promise < TypeSearchResult | null > {
41
36
42
- if ( match [ 0 ] . endsWith ( "(" ) ) {
43
-
44
- if ( classWithSameName ( match [ 1 ] , documentText ) ) {
45
- return match [ 1 ] ;
37
+ let detectBasicType = this . detectBasicType ( lineText ) ;
38
+ const valueMatch = this . matchNonValueAssignment ( lineText , "\\(?" ) ;
39
+
40
+ let typeName = await detectBasicType ;
41
+ if ( typeName ) {
42
+ return new TypeSearchResult ( typeName , EstimationSource . Value ) ;
43
+ }
44
+ if ( ! valueMatch ) {
45
+ return null ;
46
46
}
47
47
48
- if ( isProbablyAClass ( match [ 1 ] ) ) {
49
- regExp = new RegExp ( `^[ \t]*def ${ match [ 1 ] } \\(` , "m" ) ;
50
- if ( ! regExp . test ( documentText ) ) {
51
- return match [ 1 ] ;
48
+ if ( valueMatch [ 0 ] . endsWith ( "(" ) ) {
49
+
50
+ if ( this . classWithSameName ( valueMatch [ 1 ] , documentText ) ) {
51
+ return new TypeSearchResult ( valueMatch [ 1 ] , EstimationSource . ClassDefinition ) ;
52
52
}
53
- } else {
54
- // Find the function definition and check if the return type is hinted
55
- regExp = new RegExp ( `^[ \t]*def ${ match [ 1 ] } \\([^)]*\\) *-> *(${ anyTypeName } )` , "m" ) ;
56
53
57
- const hintedCallMatch = regExp . exec ( documentText ) ;
54
+ if ( this . isProbablyAClass ( valueMatch [ 1 ] ) ) {
55
+ const regExp = new RegExp ( `^[ \t]*def ${ valueMatch [ 1 ] } \\(` , "m" ) ;
56
+ if ( ! regExp . test ( documentText ) ) {
57
+ return new TypeSearchResult ( valueMatch [ 1 ] , EstimationSource . Value ) ;
58
+ }
59
+ } else {
60
+ // Find the function definition and check if the return type is hinted
61
+ const regExp = new RegExp ( `^[ \t]*def ${ valueMatch [ 1 ] } \\([^)]*\\) *-> *(${ anyClassOrFunctionName } )` , "m" ) ;
62
+
63
+ const hintedCallMatch = regExp . exec ( documentText ) ;
58
64
59
- if ( hintedCallMatch ) {
60
- if ( hintedCallMatch . length === 2 && isType ( hintedCallMatch [ 1 ] ) ) {
61
- return hintedCallMatch [ 1 ] ;
65
+ if ( hintedCallMatch ) {
66
+ if ( hintedCallMatch . length === 2 && this . isType ( hintedCallMatch [ 1 ] ) ) {
67
+ return new TypeSearchResult ( hintedCallMatch [ 1 ] , EstimationSource . Value ) ;
68
+ }
62
69
}
63
70
}
71
+ return null ;
64
72
}
65
- return null ;
66
- }
67
- if ( importFound ( match [ 1 ] , documentText . substr ( match . index - match . length ) ) ) {
73
+
68
74
// Searching the import source document is not supported (yet?)
69
- return null ;
75
+ if ( ! this . isImported ( valueMatch [ 1 ] , documentText . substr ( valueMatch . index - valueMatch . length ) ) ) {
76
+
77
+ let objectMatch = new RegExp ( `^[ \t]*${ valueMatch [ 1 ] } *=.*` , "m" ) . exec ( documentText ) ;
78
+ if ( objectMatch ) {
79
+ const otherType = await this . detectBasicType ( objectMatch [ 0 ] ) ;
80
+ return Promise . resolve (
81
+ otherType ? new TypeSearchResult ( otherType , EstimationSource . ValueOfOtherObject ) : null
82
+ ) ;
83
+ }
84
+ }
85
+ return Promise . resolve ( null ) ;
70
86
}
71
87
72
- regExp = new RegExp ( `^[ \t]* ${ match [ 1 ] } *=.*` , "m" ) ;
73
- let varInitializationMatch = regExp . exec ( documentText ) ;
74
- if ( varInitializationMatch ) {
75
- return detectBasicType ( varInitializationMatch [ 0 ] ) ;
76
- }
77
-
78
- return null ;
79
- }
88
+ /**
89
+ * Tests if code contains a terinary operator that
90
+ * might return a type other than the type of the search result.
91
+ *
92
+ * @param lineSrc A line of code.
93
+ * @param searchResult The search result.
94
+ */
95
+ public static async invalidTernaryOperator ( lineSrc : string , searchResult : TypeSearchResult ) : Promise < boolean > {
80
96
81
- /**
82
- * Tests if a detected type is initialized using a terinary operator that
83
- * might return more than a single type.
84
- *
85
- * @param typeName The name of the detected type.
86
- * @param lineSrc The source code of the line.
87
- */
88
- export function invalidTernaryOperator ( typeName : string , lineSrc : string ) {
89
-
90
- const regExp = new RegExp ( " if +[^ ]+ +else( +[^ ]+) *$" , "m" ) ;
91
-
92
- let ternaryMatch = regExp . exec ( lineSrc ) ;
93
- while ( ternaryMatch ) {
94
- const elseVar = ternaryMatch [ 1 ] . trim ( ) ;
95
- let elseTypeName = detectBasicType ( elseVar , false ) ;
96
-
97
- if ( elseTypeName ) {
98
- ternaryMatch = regExp . exec ( elseTypeName ) ;
99
- if ( ! ternaryMatch ) {
100
- return typeName !== elseTypeName ;
101
- }
102
- } else {
97
+ if ( searchResult . estimationSource === EstimationSource . ClassDefinition ) {
103
98
return false ;
104
99
}
100
+ const regExp = new RegExp ( " if +[^ ]+ +else( +[^ ]+) *$" , "m" ) ;
101
+
102
+ let ternaryMatch = regExp . exec ( lineSrc ) ;
103
+ while ( ternaryMatch ) {
104
+ const elseVar = ternaryMatch [ 1 ] . trim ( ) ;
105
+ let elseTypeName = await this . detectBasicType ( elseVar , false ) ;
106
+
107
+ if ( elseTypeName ) {
108
+ ternaryMatch = regExp . exec ( elseTypeName ) ;
109
+ if ( ! ternaryMatch ) {
110
+ return searchResult . typeName !== elseTypeName ;
111
+ }
112
+ } else {
113
+ return false ;
114
+ }
115
+ }
116
+ return false ;
105
117
}
106
- return false ;
107
- }
108
118
109
- /**
110
- * Searches for a class with the same name as object and returns the name if found.
111
- * @param object The object.
112
- * @param documentText The text to search
113
- */
114
- export function classWithSameName ( object : string , documentText : string ) : string | null {
115
- const clsMatch = new RegExp ( `^ *class +(${ object } )` , "mi" ) . exec ( documentText ) ;
116
- return clsMatch ? clsMatch [ 1 ] : null ;
117
- }
119
+ /**
120
+ * Searches for a class with the same name as object and returns the name if found.
121
+ *
122
+ * @param object The object.
123
+ * @param documentText The text to search
124
+ */
125
+ public static classWithSameName ( object : string , documentText : string ) : string | null {
126
+ const clsMatch = new RegExp ( `^ *class +(${ object } )[(:]` , "mi" ) . exec ( documentText ) ;
127
+ return clsMatch ? clsMatch [ 1 ] : null ;
128
+ }
118
129
119
- function importFound ( object : string , documentText : string ) : boolean {
120
- return new RegExp (
121
- `^[ \t]*(import +${ object } |from +[a-zA-Z_][a-zA-Z0-9_-]* +import +${ object } |import +${ anyTypeName } +as +${ object } )` ,
122
- "m"
123
- ) . test ( documentText ) ;
124
- }
130
+ /**
131
+ * Detects the type of an initialized variable.
132
+ *
133
+ * @param src The line of code or value to detect a type for.
134
+ * @param srcIsLineOfCode Determine the type from a line of code.
135
+ */
136
+ private static async detectBasicType ( src : string , srcIsLineOfCode = true ) : Promise < string | null > {
137
+ const typeSearchOrder = [
138
+ TypeName . List ,
139
+ TypeName . Bool ,
140
+ TypeName . Complex ,
141
+ TypeName . Float ,
142
+ TypeName . String ,
143
+ TypeName . Tuple ,
144
+ TypeName . Set ,
145
+ TypeName . Dict ,
146
+ TypeName . Int ,
147
+ TypeName . Object
148
+ ] ;
149
+ for ( const typeName of typeSearchOrder ) {
150
+ let r = this . typeSearchRegExp ( typeName , srcIsLineOfCode ? "= *" : "" ) ;
151
+ if ( r . test ( src ) ) {
152
+ return typeName ;
153
+ }
154
+ }
155
+ return null ;
156
+ }
125
157
126
- function isProbablyAClass ( lineText : string ) : boolean {
127
- return new RegExp ( `^([a-zA-Z0-9_]+\\.)*[A-Z]` , "m" ) . test ( lineText ) ;
128
- }
158
+ /**
159
+ * Returns a match for if a variable is initialized with a function call, an object or another variable.
160
+ */
161
+ private static matchNonValueAssignment ( lineText : string , patternSuffix : string ) : RegExpExecArray | null {
162
+ return new RegExp ( `= *(${ anyClassOrFunctionName } )${ patternSuffix } ` ) . exec ( lineText ) ;
163
+ }
129
164
130
- function isType ( text : string ) : boolean {
131
- return Object . values ( Types ) . includes ( text as Types ) ;
132
- }
165
+ private static isImported ( object : string , documentText : string ) : boolean {
166
+ return new RegExp (
167
+ `^[ \t]*(import +${ object } |from +[a-zA-Z_][a-zA-Z0-9_-]* +import +${ object } |import +${ anyClassOrFunctionName } +as +${ object } )` ,
168
+ "m"
169
+ ) . test ( documentText ) ;
170
+ }
171
+
172
+ private static isProbablyAClass ( lineText : string ) : boolean {
173
+ return new RegExp ( `^([a-zA-Z0-9_]+\\.)*[A-Z]` , "m" ) . test ( lineText ) ;
174
+ }
133
175
134
- const typeSearchOrder = [
135
- Types . List ,
136
- Types . Bool ,
137
- Types . Complex ,
138
- Types . Float ,
139
- Types . String ,
140
- Types . Tuple ,
141
- Types . Set ,
142
- Types . Dict ,
143
- Types . Int ,
144
- Types . Object
145
- ] ;
176
+ private static isType ( text : string ) : boolean {
177
+ return Object . values ( TypeName ) . includes ( text as TypeName ) ;
178
+ }
146
179
147
- /**
148
- * Get a new RegExp for finding basic types and {@class object}.
149
- *
150
- * @param typeName the type name
151
- * @param prefix a prefix added to the RegExp pattern
152
- */
153
- function typeSearchRegExp ( typeName : string , prefix : string ) : RegExp {
154
- switch ( typeName ) {
155
- case Types . List :
156
- return new RegExp ( `${ prefix } (\\[|list\\()` , "m" ) ;
157
- case Types . Bool :
158
- return new RegExp ( `${ prefix } (True|False|bool\\()` , "m" ) ;
159
- case Types . Complex :
160
- return new RegExp ( `${ prefix } (\\(complex\\(|[[0-9+*\\/ -.]*[0-9][jJ])` , "m" ) ;
161
- case Types . Float :
162
- return new RegExp ( `${ prefix } (-*[0-9+*\/ -]*\\.[0-9]|float\\()` , "m" ) ;
163
- case Types . Tuple :
164
- return new RegExp ( `${ prefix } (\\(|tuple\\()` , "m" ) ;
165
- case Types . String :
166
- return new RegExp ( `${ prefix } (['\"]{3}|(\\( *)?\"[^\"]*\"(?! *,)|(\\( *)?'[^']*'(?! *,)|str\\()` , "m" ) ;
167
- case Types . Set :
168
- return new RegExp ( `${ prefix } ({[^:]+[,}]|set\\()` , "m" ) ;
169
- case Types . Dict :
170
- return new RegExp ( `${ prefix } ({|dict\\()` , "m" ) ;
171
- case Types . Int :
172
- return new RegExp ( `${ prefix } (-*[0-9]|int\\()` , "m" ) ;
173
- case Types . Object :
174
- return new RegExp ( `${ prefix } object\\(` , "m" ) ;
175
- default :
176
- return new RegExp ( `^.*$` , "m" ) ;
180
+ /**
181
+ * Get a new RegExp for finding basic types and {@class object}.
182
+ *
183
+ * @param typeName the type name
184
+ * @param prefix a prefix added to the RegExp pattern
185
+ */
186
+ private static typeSearchRegExp ( typeName : string , prefix : string ) : RegExp {
187
+ switch ( typeName ) {
188
+ case TypeName . List :
189
+ return new RegExp ( `${ prefix } (\\[|list\\()` , "m" ) ;
190
+ case TypeName . Bool :
191
+ return new RegExp ( `${ prefix } (True|False|bool\\()` , "m" ) ;
192
+ case TypeName . Complex :
193
+ return new RegExp ( `${ prefix } (\\(complex\\(|[[0-9+*\\/ -.]*[0-9][jJ])` , "m" ) ;
194
+ case TypeName . Float :
195
+ return new RegExp ( `${ prefix } (-*[0-9+*\/ -]*\\.[0-9]|float\\()` , "m" ) ;
196
+ case TypeName . Tuple :
197
+ return new RegExp ( `${ prefix } (\\(|tuple\\()` , "m" ) ;
198
+ case TypeName . String :
199
+ return new RegExp ( `${ prefix } (['\"]{3}|(\\( *)?\"[^\"]*\"(?! *,)|(\\( *)?'[^']*'(?! *,)|str\\()` , "m" ) ;
200
+ case TypeName . Set :
201
+ return new RegExp ( `${ prefix } ({[^:]+[,}]|set\\()` , "m" ) ;
202
+ case TypeName . Dict :
203
+ return new RegExp ( `${ prefix } ({|dict\\()` , "m" ) ;
204
+ case TypeName . Int :
205
+ return new RegExp ( `${ prefix } (-*[0-9]|int\\()` , "m" ) ;
206
+ case TypeName . Object :
207
+ return new RegExp ( `${ prefix } object\\(` , "m" ) ;
208
+ default :
209
+ return new RegExp ( `^.*$` , "m" ) ;
210
+ }
177
211
}
178
- }
212
+ }
0 commit comments