Skip to content

Commit 287d1ab

Browse files
committed
Typing support, async, refactoring
1 parent 7316c7c commit 287d1ab

11 files changed

+598
-438
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"type": "git",
88
"url": "https://github.com/nj2532/vscode-python-typehint.git"
99
},
10-
"version": "0.5.0",
10+
"version": "0.5.1",
1111
"engines": {
1212
"vscode": "^1.43.0"
1313
},

src/codeSearch.ts

Lines changed: 179 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -1,178 +1,212 @@
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

Comments
 (0)