Skip to content

Commit 7316c7c

Browse files
committed
Multiple estimations, shift responsibility to TypeResolver
1 parent 7a915f6 commit 7316c7c

File tree

8 files changed

+277
-118
lines changed

8 files changed

+277
-118
lines changed

src/codeSearch.ts

Lines changed: 50 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import {
2-
TextLine,
3-
Position,
4-
TextDocument
5-
} from "vscode";
1+
import { anyTypeName, Types } from "./python";
62

7-
import { anyTypeName, Type } from "./syntax";
3+
4+
export interface TypeSearchResult {
5+
typeName: string | null;
6+
7+
}
88

99
/**
1010
* Detects the type of an initialized variable.
@@ -15,8 +15,8 @@ import { anyTypeName, Type } from "./syntax";
1515
*/
1616
export function detectBasicType(src: string, srcIsLineOfCode = true): string | null {
1717

18-
for (const typeName of Object.values(Type)) {
19-
let r = getTypeRegEx(typeName, srcIsLineOfCode ? "= *" : "");
18+
for (const typeName of typeSearchOrder) {
19+
let r = typeSearchRegExp(typeName, srcIsLineOfCode ? "= *" : "");
2020
if (r.test(src)) {
2121
return typeName;
2222
}
@@ -41,7 +41,7 @@ export function detectNonBasicType(lineText: string, documentText: string): stri
4141

4242
if (match[0].endsWith("(")) {
4343

44-
if (isClass(match[1], documentText)) {
44+
if (classWithSameName(match[1], documentText)) {
4545
return match[1];
4646
}
4747

@@ -87,10 +87,7 @@ export function detectNonBasicType(lineText: string, documentText: string): stri
8787
*/
8888
export function invalidTernaryOperator(typeName: string, lineSrc: string) {
8989

90-
const regExp = new RegExp(
91-
" if +[^ ]+ +else( +[^ ]+) *$",
92-
"m"
93-
);
90+
const regExp = new RegExp(" if +[^ ]+ +else( +[^ ]+) *$", "m");
9491

9592
let ternaryMatch = regExp.exec(lineSrc);
9693
while (ternaryMatch) {
@@ -109,11 +106,14 @@ export function invalidTernaryOperator(typeName: string, lineSrc: string) {
109106
return false;
110107
}
111108

112-
function isClass(object: string, documentText: string) {
113-
return new RegExp(
114-
`^ *class +${object}`,
115-
"m"
116-
).test(documentText);
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;
117117
}
118118

119119
function importFound(object: string, documentText: string): boolean {
@@ -128,37 +128,51 @@ function isProbablyAClass(lineText: string): boolean {
128128
}
129129

130130
function isType(text: string): boolean {
131-
return Object.values(Type).includes(text as Type);
131+
return Object.values(Types).includes(text as Types);
132132
}
133133

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+
];
146+
134147
/**
135148
* Get a new RegExp for finding basic types and {@class object}.
136149
*
137150
* @param typeName the type name
138151
* @param prefix a prefix added to the RegExp pattern
139152
*/
140-
function getTypeRegEx(typeName: string, prefix: string): RegExp {
153+
function typeSearchRegExp(typeName: string, prefix: string): RegExp {
141154
switch (typeName) {
142-
case Type.Bool:
143-
return new RegExp(`${prefix}(True|False|bool\\()`, "m");
144-
case Type.Dict:
145-
return new RegExp(`${prefix}({|dict\\()`, "m");
146-
case Type.Int:
147-
return new RegExp(`${prefix}(-*[0-9]+(?!(\\.| *\\)| *,))|int\\()`, "m");
148-
case Type.List:
155+
case Types.List:
149156
return new RegExp(`${prefix}(\\[|list\\()`, "m");
150-
case Type.String:
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:
151166
return new RegExp(`${prefix}(['\"]{3}|(\\( *)?\"[^\"]*\"(?! *,)|(\\( *)?'[^']*'(?! *,)|str\\()`, "m");
152-
case Type.Float:
153-
return new RegExp(`${prefix}(-*[0-9]*\\.[0-9]+|float\\()`, "m");
154-
case Type.Tuple:
155-
return new RegExp(`${prefix}(\\(([^'\",)]+,|\"[^\"]*\"(?= *,)|'[^']*'(?= *,))|tuple\\()`, "m");
156-
case Type.Complex:
157-
return new RegExp(`${prefix}(\\(complex\\(|[0-9][0-9+-/*.]*[jJ])`, "m");
158-
case Type.Object:
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:
159174
return new RegExp(`${prefix}object\\(`, "m");
160175
default:
161176
return new RegExp(`^.*$`, "m");
162177
}
163178
}
164-

src/completionProvider.ts

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,27 @@ import {
99
TextLine,
1010
TextDocument
1111
} from "vscode";
12-
import { TypeResolver } from "./typeResolver";
13-
import { paramHintTrigger, returnHintTrigger, Type } from "./syntax";
12+
import { TypeResolver, TypeResolution } from "./typeResolver";
13+
import { paramHintTrigger, returnHintTrigger, Types } from "./python";
14+
15+
16+
abstract class CompletionProvider {
17+
18+
protected pushTypesToItems(types: string[], completionItems: CompletionItem[]) {
19+
for (const type of types) {
20+
const item = new CompletionItem(" " + type, CompletionItemKind.TypeParameter);
21+
22+
// Prioritize type estimations and sort remaining items alphabetically
23+
item.sortText = `999${type}`;
24+
completionItems.push(item);
25+
}
26+
}
27+
}
1428

1529
/**
1630
* Provides one or more parameter type hint {@link CompletionItem}.
1731
*/
18-
export class ParamHintCompletionProvider implements CompletionItemProvider {
32+
export class ParamHintCompletionProvider extends CompletionProvider implements CompletionItemProvider {
1933

2034
public provideCompletionItems(
2135
doc: TextDocument,
@@ -31,13 +45,10 @@ export class ParamHintCompletionProvider implements CompletionItemProvider {
3145
const param = this.findParam(line, pos);
3246

3347
if (param && param.length > 0) {
34-
let hint = new TypeResolver().EstimateType(doc, param);
35-
36-
if (hint) {
37-
items.push(new CompletionItem(" " + hint, CompletionItemKind.TypeParameter));
38-
} else {
39-
pushDefaultCompletionItems(items);
40-
}
48+
const resolution = new TypeResolver().ResolveTypes(param, doc);
49+
this.pushTypeResolutionToItems(resolution, items);
50+
} else {
51+
this.pushTypesToItems(Object.values(Types), items);
4152
}
4253
return Promise.resolve(new CompletionList(items, false));
4354
}
@@ -57,12 +68,27 @@ export class ParamHintCompletionProvider implements CompletionItemProvider {
5768
}
5869
return param;
5970
}
71+
72+
private pushTypeResolutionToItems(resolution: TypeResolution, items: CompletionItem[]) {
73+
74+
if (resolution.estimations) {
75+
for (let i = 0; i < resolution.estimations.length; i++) {
76+
const typeName = resolution.estimations[i];
77+
const item = new CompletionItem(" " + typeName, CompletionItemKind.TypeParameter);
78+
item.sortText = `${i}${typeName}`;
79+
item.preselect = true;
80+
items.push(item);
81+
}
82+
83+
}
84+
this.pushTypesToItems(resolution.remainingTypes, items);
85+
}
6086
}
6187

6288
/**
6389
* Provides one or more return type hint {@link CompletionItem}.
6490
*/
65-
export class ReturnHintCompletionProvider implements CompletionItemProvider {
91+
export class ReturnHintCompletionProvider extends CompletionProvider implements CompletionItemProvider {
6692

6793
public provideCompletionItems(
6894
doc: TextDocument,
@@ -77,7 +103,7 @@ export class ReturnHintCompletionProvider implements CompletionItemProvider {
77103
const line = doc.lineAt(pos);
78104

79105
if (this.shouldProvideReturnHint(line, pos)) {
80-
pushDefaultCompletionItems(items);
106+
this.pushTypesToItems(Object.values(Types), items);
81107
}
82108
return Promise.resolve(new CompletionList(items, false));
83109
}
@@ -90,10 +116,4 @@ export class ReturnHintCompletionProvider implements CompletionItemProvider {
90116
}
91117
return false;
92118
}
93-
}
94-
95-
function pushDefaultCompletionItems(items: CompletionItem[]) {
96-
for (const type of Object.values(Type)) {
97-
items.push(new CompletionItem(" " + type, CompletionItemKind.TypeParameter));
98-
}
99119
}

src/extension.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as vscode from 'vscode';
22
import { ParamHintCompletionProvider, ReturnHintCompletionProvider } from './completionProvider';
3-
import { paramHintTrigger, returnHintTrigger } from "./syntax";
3+
import { paramHintTrigger, returnHintTrigger } from "./python";
44

55
// Called when the extension is activated.
66
export function activate(context: vscode.ExtensionContext) {

src/python.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
2+
export const anyTypeName: string = "[a-zA-Z_][a-zA-Z0-9_.]*";
3+
4+
export const paramHintTrigger: string = ":";
5+
export const returnHintTrigger: string = ">";
6+
7+
8+
export class DataType {
9+
name: Types;
10+
category: TypeCategory;
11+
12+
constructor(name: Types, category: TypeCategory) {
13+
this.name = name;
14+
this.category = category;
15+
}
16+
}
17+
18+
export interface DataTypes {
19+
[key: string]: DataType
20+
};
21+
22+
export const getDataTypes = (): DataTypes => {
23+
return {
24+
bool: new DataType(Types.Bool, typeCategories.bool),
25+
complex: new DataType(Types.Complex, typeCategories.complex),
26+
dict: new DataType(Types.Dict, typeCategories.dict),
27+
float: new DataType(Types.Float, typeCategories.float),
28+
int: new DataType(Types.Int, typeCategories.int),
29+
list: new DataType(Types.List, typeCategories.list),
30+
object: new DataType(Types.Object, typeCategories.object),
31+
set: new DataType(Types.Set, typeCategories.set),
32+
str: new DataType(Types.String, typeCategories.string),
33+
tuple: new DataType(Types.Tuple, typeCategories.tuple)
34+
};
35+
};
36+
37+
/**
38+
* Names of built-in Python types which can be hinted.
39+
*/
40+
export enum Types {
41+
Bool = "bool",
42+
Complex = "complex",
43+
Dict = "dict",
44+
Float = "float",
45+
Int = "int",
46+
List = "list",
47+
Object = "object",
48+
Set = "set",
49+
String = "str",
50+
Tuple = "tuple",
51+
}
52+
53+
/**
54+
* Categories of Python types.
55+
*/
56+
export enum TypeCategory {
57+
Abstract,
58+
Basic,
59+
Collection
60+
}
61+
62+
/**
63+
* Type name keys with Type values.
64+
*/
65+
const typeCategories: { [key: string]: TypeCategory } = {
66+
bool: TypeCategory.Basic,
67+
complex: TypeCategory.Basic,
68+
dict: TypeCategory.Collection,
69+
float: TypeCategory.Basic,
70+
int: TypeCategory.Basic,
71+
list: TypeCategory.Collection,
72+
object: TypeCategory.Abstract,
73+
set: TypeCategory.Collection,
74+
string: TypeCategory.Basic,
75+
tuple: TypeCategory.Collection
76+
};

src/syntax.ts

Lines changed: 0 additions & 20 deletions
This file was deleted.

src/test/suite/codeSearch/detectBasicType.test.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ suite('detectBasicType', function() {
6060
src = "var = -.3";
6161
actual = detectBasicType(src);
6262
assert.equal(actual, expected, getErrorMessage(src, expected, actual));
63+
64+
src = "var = 1 + 2 - 1 * 2 / 2.0";
65+
actual = detectBasicType(src);
66+
assert.equal(actual, expected, getErrorMessage(src, expected, actual));
6367
});
6468

6569
test("detects complex numbers", () => {
@@ -126,6 +130,22 @@ suite('detectBasicType', function() {
126130
let actual = detectBasicType(src);
127131
assert.equal(actual, expected);
128132
});
133+
134+
test("detects sets", () => {
135+
const expected = "set";
136+
137+
let src = "var = {'dont return dict please'}";
138+
let actual = detectBasicType(src);
139+
assert.equal(actual, expected);
140+
141+
src = "var = {1, 2}";
142+
actual = detectBasicType(src);
143+
assert.equal(actual, expected, getErrorMessage(src, expected, actual));
144+
145+
src = "var = {1 , 2}";
146+
actual = detectBasicType(src);
147+
assert.equal(actual, expected, getErrorMessage(src, expected, actual));
148+
});
129149

130150
test("detects type() call", () => {
131151
const testCases: TestCase[] = [
@@ -134,7 +154,8 @@ suite('detectBasicType', function() {
134154
{ data: "var = list(foo)", expected: "list"},
135155
{ data: "var = dict(foo)", expected: "dict"},
136156
{ data: "var = tuple(foo)", expected: "tuple"},
137-
{ data: "var = str(1)", expected: "str"}
157+
{ data: "var = str(1)", expected: "str"},
158+
{ data: "var = set([1])", expected: "set"}
138159
];
139160
for (const c of testCases) {
140161
assert.equal(detectBasicType(c.data), c.expected);

0 commit comments

Comments
 (0)