Skip to content

Commit c65e4ad

Browse files
committed
Improvements
- New tests - Minor fixes
1 parent 9893216 commit c65e4ad

9 files changed

+248
-109
lines changed

src/completionProvider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ export class ParamHintCompletionProvider extends CompletionProvider implements C
9595
last = precedingText[i];
9696
}
9797
param = param.trim();
98-
return !param || /[),!:?/\\{}.+/=()'"&%¤|<>$^~¨ -]/.test(param) ? null : param;
98+
return !param || /[!:?/\\{}.+/=)'";@&£%¤|<>$^~¨ -]/.test(param) ? null : param;
9999
}
100100

101101
private pushEstimationsToItems(typeHints: string[], items: CompletionItem[]) {

src/python.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

2-
export const anyClassOrFunctionName: string = "[a-zA-Z_][a-zA-Z0-9_.]*";
2+
export const simpleIdentifier: string = "[a-zA-Z_][a-zA-Z0-9_.]*";
33

44
export const paramHintTrigger: string = ":";
55
export const returnHintTrigger: string = ">";

test/common.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
1-
2-
3-
export const getErrorMessage = (
4-
testCase: TestCase,
5-
actual: any
6-
): string => {
7-
return `${actual} == ${testCase.expected}. \n[Test data]: ${testCase.data}`;
8-
};
1+
import { VariableSearchResult, EstimationSource } from "../src/typeSearch";
92

103
export interface TestCase {
114
data: any,
125
expected: any
13-
}
6+
}
7+
8+
export class SetupError extends Error {
9+
constructor(message: string) {
10+
super(message);
11+
this.name = "SetupError";
12+
}
13+
}
14+
15+
export function messageFor(testCase: TestCase, actual: any): string {
16+
return `${actual} == ${testCase.expected}. \n[Test data]\n${testCase.data}`;
17+
};
18+
19+
export function varSearchResult(typeName: string, valueAssignment: string): VariableSearchResult {
20+
return { typeName, estimationSource: EstimationSource.Value, valueAssignment };
21+
};
Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,64 @@
11
import * as assert from 'assert';
22
import * as vsc from 'vscode';
3-
import { paramHintTrigger } from "../../src/python";
3+
import { paramHintTrigger, PythonType } from "../../src/python";
44
import { CompletionProvider, ParamHintCompletionProvider } from "../../src/completionProvider";
55
import { TypeHintSettings } from '../../src/settings';
6+
import { messageFor } from '../common';
67

78
suite('ParamHintCompletionProvider', () => {
89
const provider = new ParamHintCompletionProvider(new TypeHintSettings());
910

1011
test("provides items for first param", async () => {
11-
let param = "paramName: ";
12-
let actual = providerResult(provider, param, "\nparamName = 123");
13-
assert.notEqual(actual, null);
12+
let param = "paramName:";
13+
let actual = await providerResult(provider, param, "):\n\nparamName = 12");
14+
assert.equal(actual?.items[0].label.trim(), PythonType.Int);
1415
});
1516

1617
test("provides items for non-first param", async () => {
17-
let param = "first: str, paramName: ";
18-
let actual = await providerResult(provider, param, "\nparamName = 123");
19-
assert.notEqual(actual, null);
18+
let param = "first: str, paramName:";
19+
let actual = await providerResult(provider, param, "\n\nparamName = 12");
20+
assert.equal(actual?.items[0].label.trim(), PythonType.Int);
2021
});
2122

2223
test("provides items for param on new line", async () => {
23-
let param = "\n paramName: ";
24-
let actual = await providerResult(provider, param, "\nparamName = 123");
25-
assert.notEqual(actual, null);
24+
let param = "\n paramName:";
25+
let actual = await providerResult(provider, param, "\n\nparamName = 12");
26+
assert.equal(actual?.items[0].label.trim(), PythonType.Int);
2627

27-
param = "\n\tparamName: ";
28-
actual = await providerResult(provider, param, "\nparamName = 123");
29-
assert.notEqual(actual, null);
28+
param = "\n\tparamName:";
29+
actual = await providerResult(provider, param, "\n\nparamName = 12");
30+
assert.equal(actual?.items[0].label.trim(), PythonType.Int);
31+
});
32+
33+
test("provides items for param with legal non-ascii chars", async () => {
34+
let param = "a変な:";
35+
let actual = await providerResult(provider, param, "\n\na変な = 12");
36+
assert.equal(actual?.items[0].label.trim(), PythonType.Int);
3037
});
31-
3238

3339
test("does not provide items for dict keys", async () => {
3440
let expected = null;
3541
let actual = await providerResult(provider, "):\n d = { key:");
3642
assert.equal(actual, expected);
3743
});
3844

39-
test("does not provide items for ':' within strings under function def", async () => {
45+
test("does not provide items for ':' without a param (within function brackets)", async () => {
4046
let expected = null;
41-
let actual = await providerResult(provider, "):\n d = { key: 'val:'");
47+
let actual = await providerResult(provider, "param, :");
4248
assert.equal(actual, expected);
4349
});
4450

51+
test("does not provide items for ':' under a function def", async () => {
52+
let data = "):\n d = 'val:";
53+
let expected = null;
54+
let actual = await providerResult(provider, data);
55+
assert.equal(actual, expected, messageFor({ data, expected }, actual));
56+
57+
data = "):\n :";
58+
actual = await providerResult(provider, data);
59+
assert.equal(actual, expected, messageFor({ data, expected }, actual));
60+
});
61+
4562
test("does not provide items for end of function definition", async () => {
4663
let expected = null;
4764
let actual = await providerResult(provider, "):");
@@ -55,20 +72,20 @@ const language = "python";
5572
async function providerResult(
5673
provider: CompletionProvider,
5774
functionText: string,
58-
postFunctionText?: string
75+
trailingText?: string
5976
): Promise<vsc.CompletionList | null> {
6077
let content = `def func(${functionText}`;
61-
if (postFunctionText) {
62-
content += postFunctionText;
78+
const lines: string[] = content.split("\n");
79+
const lastLineIdx = lines.length - 1;
80+
const lastPos = new vsc.Position(lastLineIdx, lines[lastLineIdx].length);
81+
82+
if (trailingText) {
83+
content += trailingText;
6384
}
6485

6586
const doc = await vsc.workspace.openTextDocument({ language, content });
6687
const token = new vsc.CancellationTokenSource().token;
6788
const ctx = { triggerCharacter: paramHintTrigger, triggerKind: vsc.CompletionTriggerKind.TriggerCharacter };
6889

69-
const lines: string[] = content.split("\n");
70-
const lastLineIdx = lines.length - 1;
71-
const lastPos = new vsc.Position(lastLineIdx, lines[lastLineIdx].length);
72-
7390
return provider.provideCompletionItems(doc, lastPos, token, ctx);
7491
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import * as assert from 'assert';
2+
import * as vsc from 'vscode';
3+
import { getDataTypeContainer, DataType, PythonType } from "../../src/python";
4+
import { TypingHintProvider } from "../../src/typingHintProvider";
5+
import { TypeHintSettings } from '../../src/settings';
6+
import { SetupError, varSearchResult, messageFor, TestCase } from '../common';
7+
8+
suite('TypingHintProvider', () => {
9+
10+
const importTyping: string = "import x\nimport typing";
11+
const importTypingAsX: string = "import x\nimport typing as x";
12+
const fromTypingImport: string = "import x\nfrom typing import List";
13+
const typeContainer = getDataTypeContainer();
14+
15+
suite('containsTyping', () => {
16+
17+
test("returns true for 'import typing'", async () => {
18+
const provider = new TypingHintProvider(importTyping, typeContainer);
19+
const actual = await provider.containsTyping();
20+
assert.equal(actual, true);
21+
});
22+
23+
test("returns true for 'import typing as x'", async () => {
24+
const provider = new TypingHintProvider(importTypingAsX, typeContainer);
25+
const actual = await provider.containsTyping();
26+
assert.equal(actual, true);
27+
});
28+
29+
test("returns true for 'from typing import x'", async () => {
30+
const provider = new TypingHintProvider(fromTypingImport, typeContainer);
31+
const actual = await provider.containsTyping();
32+
assert.equal(actual, true);
33+
});
34+
});
35+
36+
suite("getTypingHint", () => {
37+
38+
test("returns typing.Type[", async () => {
39+
const provider = new TypingHintProvider(importTyping, typeContainer);
40+
const expected = "typing.List[";
41+
providerTest(getTypingHint, await provider.containsTyping(), provider, expected);
42+
});
43+
44+
test("returns x.Type[ for 'import typing as x'", async () => {
45+
const provider = new TypingHintProvider(importTypingAsX, typeContainer);
46+
const expected = "x.List[";
47+
providerTest(getTypingHint, await provider.containsTyping(), provider, expected);
48+
});
49+
50+
test("returns Type[ for 'from typing' import", async () => {
51+
const provider = new TypingHintProvider(fromTypingImport, typeContainer);
52+
const expected = "List[";
53+
providerTest(getTypingHint, await provider.containsTyping(), provider, expected);
54+
});
55+
56+
function getTypingHint(provider: TypingHintProvider, expected: string) {
57+
const actual = provider.getTypingHint(PythonType.List);
58+
assert.equal(actual, expected);
59+
}
60+
});
61+
62+
suite("getTypingHints", () => {
63+
64+
const provider = new TypingHintProvider(fromTypingImport, typeContainer);
65+
let typingImported: boolean;
66+
67+
setup(async () => {
68+
typingImported = await provider.containsTyping();
69+
});
70+
71+
test("returns Type[ for empty collection", () => {
72+
const data = "[]";
73+
const expected = ["List["];
74+
providerTest(getTypingHints, typingImported, provider, {data, expected }, PythonType.List);
75+
});
76+
77+
test("returns Dict[ and 'Dict[key,' for dicts", () => {
78+
let data = "{ 1: 2 }";
79+
let expected = ["Dict[", "Dict[int"];
80+
providerTest(getTypingHints, typingImported, provider, { data, expected }, PythonType.Dict);
81+
});
82+
83+
test("handles nestled dicts", () => {
84+
let data = "[ { 1: 2 } ]";
85+
let expected = ["List[", "List[Dict[int"];
86+
providerTest(getTypingHints, typingImported, provider, { data, expected }, PythonType.List);
87+
});
88+
89+
test("returns Type[ and Type[type] for non-dicts", () => {
90+
let data = "['str']";
91+
let expected = ["List[", "List[str]"];
92+
providerTest(getTypingHints, typingImported, provider, { data, expected }, PythonType.List);
93+
94+
data = "(1, {'ignore': 'this'})";
95+
expected = ["Tuple[", "Tuple[int]"];
96+
providerTest(getTypingHints, typingImported, provider, { data, expected }, PythonType.Tuple);
97+
98+
data = "{ 1, 2 }";
99+
expected = ["Set[", "Set[int]"];
100+
providerTest(getTypingHints, typingImported, provider, { data, expected }, PythonType.Set);
101+
});
102+
103+
test("adds typing prefixes for 'import typing' imports", async () => {
104+
let p = new TypingHintProvider(importTyping, typeContainer);
105+
let data = "[ { 1: 2 } ]";
106+
let expected = ["typing.List[", "typing.List[typing.Dict[int"];
107+
providerTest(getTypingHints, await p.containsTyping(), p, { data, expected }, PythonType.List);
108+
});
109+
110+
function getTypingHints(provider: TypingHintProvider, testCase: TestCase, type: PythonType) {
111+
const actual = provider.getTypingHints(varSearchResult(type, testCase.data));
112+
assert.deepEqual(actual, testCase.expected, messageFor(testCase, actual));
113+
}
114+
});
115+
116+
function providerTest(test: (...params: any) => void, typingDetected: boolean, ...args: any) {
117+
if (typingDetected) {
118+
test(...args);
119+
} else {
120+
throw new SetupError("The provider failed to detect a typing import.");
121+
}
122+
}
123+
});

0 commit comments

Comments
 (0)