1- import { anyTypeName , Types } from "./python" ;
1+ import { anyTypeName as anyClassOrFunctionName , TypeName , Initialization , TypeCategory } from "./python" ;
22
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
710}
811
912/**
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.
1514 */
16- export function detectBasicType ( src : string , srcIsLineOfCode = true ) : string | null {
15+ export class TypeSearchResult {
16+ public typeName : string ;
17+ public estimationSource : EstimationSource ;
1718
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 ;
2322 }
24- return null ;
23+
2524}
2625
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 {
3727
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 > {
4136
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 ;
4646 }
4747
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 ) ;
5252 }
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" ) ;
5653
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 ) ;
5864
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+ }
6269 }
6370 }
71+ return null ;
6472 }
65- return null ;
66- }
67- if ( importFound ( match [ 1 ] , documentText . substr ( match . index - match . length ) ) ) {
73+
6874 // 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 ) ;
7086 }
7187
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 > {
8096
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 ) {
10398 return false ;
10499 }
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 ;
105117 }
106- return false ;
107- }
108118
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+ }
118129
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+ }
125157
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+ }
129164
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+ }
133175
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+ }
146179
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+ }
177211 }
178- }
212+ }
0 commit comments