Skip to content
This repository was archived by the owner on Apr 30, 2021. It is now read-only.

Commit 9206563

Browse files
committed
Wrote some unit tests to make splitToParamList more maintainable
1 parent c4c8b79 commit 9206563

File tree

7 files changed

+188
-114
lines changed

7 files changed

+188
-114
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## 0.8.4 - Sep 15, 2018
4+
- [Bug] Continued working on cleaning up parameter splitting algorithm. Now doesn't break with functions
5+
- [Feature] Wrote unit tests for splitToParamList function
6+
37
## 0.8.3 - Sep 14, 2018
48
- [Bug] Importing functions with require don't cause issues with annotations anymore.
59
- [Bug] TS functions with Generic types now properly annotate

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "vscode-js-annotations",
33
"displayName": "JS Parameter Annotations",
44
"description": "Annotations for parameters in your JS / TS Files to mimic named parameters",
5-
"version": "0.8.3",
5+
"version": "0.8.4",
66
"publisher": "lannonbr",
77
"engines": {
88
"vscode": "^1.25.0"

src/decorator.ts

Lines changed: 6 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
import * as vscode from "vscode";
22
import { Annotations } from "./annotationProvider";
33
import { IFunctionCallObject } from "./functionCallObject";
4+
import { grabPossibleParameters } from "./paramExtractor";
45

56
export async function decorateFunctionCall(currentEditor: vscode.TextEditor, documentCache: any, decArray, errDecArray: vscode.DecorationOptions[], fc: IFunctionCallObject, diagnostics: vscode.Diagnostic[]): Promise<void> {
67
// Check for existence of functionCallObject and a defintion location
78
if (fc === undefined || fc.definitionLocation === undefined) {
89
return;
910
}
1011

11-
// config option to hide annotations if param and arg names match
12+
// configs for decorations
1213
const hideIfEqual = vscode.workspace.getConfiguration("jsannotations").get("hideIfEqual");
14+
const willShowDiagnostics = vscode.workspace.getConfiguration("jsannotations").get("hideDiagnostics") === false;
15+
const willShowErrorAnnotation = vscode.workspace.getConfiguration("jsannotations").get("hideInvalidAnnotation") === false;
1316

1417
const document = await loadDefinitionDocument(fc.definitionLocation.uri, documentCache);
1518
const definitionLine = document.lineAt(fc.definitionLocation.range.start.line).text;
@@ -49,12 +52,12 @@ export async function decorateFunctionCall(currentEditor: vscode.TextEditor, doc
4952
decoration = Annotations.paramAnnotation(paramList[restParamIdx] + `[${idx - restParamIdx}]: `, currentArgRange);
5053
} else {
5154
if (idx >= paramList.length) {
52-
if (currentEditor.document.languageId === "javascript" && vscode.workspace.getConfiguration("jsannotations").get("hideDiagnostics") === false) {
55+
if (currentEditor.document.languageId === "javascript" && willShowDiagnostics) {
5356
const diag = new vscode.Diagnostic(currentArgRange, "[JS Param Annotations] Invalid parameter", vscode.DiagnosticSeverity.Error);
5457
diagnostics.push(diag);
5558
}
5659

57-
if (vscode.workspace.getConfiguration("jsannotations").get("hideInvalidAnnotation") === false) {
60+
if (willShowErrorAnnotation) {
5861
const errorDecoration = Annotations.errorParamAnnotation(currentArgRange);
5962
errDecArray.push(errorDecoration);
6063
}
@@ -89,113 +92,6 @@ async function loadDefinitionDocument(uri: vscode.Uri, documentCache: any) {
8992
return document;
9093
}
9194

92-
function grabPossibleParameters(fc: IFunctionCallObject, definitionLine: string): string[] {
93-
let paramList: string[] = [];
94-
95-
// Grab any params inside the definition line
96-
const defintionParam = grabDefLineParams(definitionLine);
97-
98-
if (defintionParam !== "") {
99-
if (fc.functionRange === undefined) {
100-
return;
101-
}
102-
103-
paramList = defintionParam.split(/\s*,\s*/);
104-
105-
paramList = squishGenerics(paramList);
106-
107-
paramList = paramList.map((param) => {
108-
// Extract identifiers
109-
110-
const words = param.trim().split(" ");
111-
112-
// If there are multiple words and the first word doesn't end with a colon, use the 2nd word as the param
113-
// this will make sure the param name is used and not the access modifier in TS functions
114-
if (words.length > 1 && !words[0].endsWith(":")) {
115-
param = words[1];
116-
}
117-
118-
const identifiers = param.match(/([\.a-zA-Z0-9]+):?/);
119-
120-
if (identifiers && identifiers.length > 1) {
121-
return identifiers[1];
122-
}
123-
return "";
124-
}).filter((param) => param !== "");
125-
}
126-
127-
return paramList;
128-
}
129-
130-
// This function will cycle through all of the current strings split by a comma, and combine strings that were of generic types and split incorrectly
131-
function squishGenerics(paramList: string[]): string[] {
132-
const newParamList = [];
133-
134-
let currentStr = "";
135-
let numToGet = 1; // Always grab 1 string at the beginning
136-
137-
for (const item of paramList) {
138-
currentStr += item;
139-
numToGet--;
140-
141-
// If numToGet is zero, check the difference between '<' and '>' characters
142-
if (numToGet === 0) {
143-
const numOfLeftBrackets = currentStr.split("<").length - 1;
144-
const numOfRightBrackets = currentStr.split(">").length - 1;
145-
const numOfEqualSigns = currentStr.split("=").length - 1;
146-
147-
// Diff is |num of left brackets ('<') minus the num of solo right brackets (which is the number of '>' minus the num of '=' signs)|
148-
const diff = Math.abs(numOfLeftBrackets - (numOfRightBrackets - numOfEqualSigns));
149-
150-
if ((numOfEqualSigns > 0) || diff === 0) {
151-
// If the difference is zero, we have a full param, push it to the new params, and start over at the next string
152-
// Also, do this if there is equal signs in the params which exist with arrow functions.
153-
newParamList.push(currentStr);
154-
currentStr = "";
155-
numToGet = 1;
156-
} else {
157-
// Otherwise, set the number of strings to grab to the diff
158-
numToGet = diff;
159-
}
160-
}
161-
}
162-
163-
return newParamList;
164-
}
165-
166-
function grabDefLineParams(definitionLine: string): string {
167-
let startIdx;
168-
let endIdx;
169-
170-
startIdx = definitionLine.indexOf("(");
171-
172-
if (startIdx === -1) {
173-
return "";
174-
} else {
175-
startIdx++;
176-
}
177-
178-
let depth = 1;
179-
180-
for (let i = startIdx; i < definitionLine.length; i++) {
181-
if (definitionLine[i] === "(") {
182-
depth++;
183-
} else if (definitionLine[i] === ")") {
184-
depth--;
185-
if (depth === 0) {
186-
endIdx = i;
187-
break;
188-
}
189-
}
190-
}
191-
192-
if (endIdx === undefined) {
193-
return "";
194-
}
195-
196-
return definitionLine.substring(startIdx, endIdx);
197-
}
198-
19995
function shouldntBeDecorated(paramList: string[], functionCall: IFunctionCallObject, functionCallLine: string, definitionLine: string): boolean {
20096
// If the functionName is one of the parameters, don't decorate it
20197
if (paramList.some((param) => param === functionCall.functionName)) {

src/paramExtractor.ts

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { IFunctionCallObject } from "./functionCallObject";
2+
3+
export function grabPossibleParameters(fc: IFunctionCallObject, definitionLine: string): string[] {
4+
let paramList: string[] = [];
5+
6+
// Grab any params inside the definition line
7+
const defintionParam = grabDefLineParams(definitionLine);
8+
9+
if (defintionParam !== "") {
10+
if (fc.functionRange === undefined) {
11+
return;
12+
}
13+
14+
paramList = splitToParamList(defintionParam);
15+
16+
paramList = paramList.map((param) => {
17+
// Extract identifiers
18+
const words = param.trim().split(" ");
19+
20+
// If there are multiple words and the first word doesn't end with a colon, use the 2nd word as the param
21+
// this will make sure the param name is used and not the access modifier in TS functions
22+
if (words.length > 1 && !words[0].endsWith(":")) {
23+
param = words[1];
24+
}
25+
26+
const identifiers = param.match(/([\.a-zA-Z0-9]+):?/);
27+
28+
if (identifiers && identifiers.length > 1) {
29+
return identifiers[1];
30+
}
31+
return "";
32+
}).filter((param) => param !== "");
33+
}
34+
35+
return paramList;
36+
}
37+
38+
// This function will cycle through all of the current strings split by a comma, and combine strings that were split incorrectly
39+
export function splitToParamList(defintionParam: string): string[] {
40+
const paramList = defintionParam.split(/\s*,\s*/);
41+
const newParamList = [];
42+
43+
let currentStr = "";
44+
let numToGet = 1; // Always grab 1 string at the beginning
45+
46+
for (const item of paramList) {
47+
currentStr += item + ", ";
48+
numToGet--;
49+
50+
// If numToGet is zero, check the difference between '<' and '>' characters
51+
if (numToGet === 0) {
52+
const numOfLessThanBrackets = currentStr.split("<").length - 1;
53+
const numOfGreaterThanBrackets = currentStr.split(">").length - 1;
54+
55+
const numOfOpenParen = currentStr.split("(").length - 1;
56+
const numOfCloseParen = currentStr.split(")").length - 1;
57+
58+
const numOfEqualSigns = currentStr.split("=").length - 1;
59+
60+
// Diff is |num of left brackets ('<') minus the num of solo right brackets (which is the number of '>' minus the num of '=' signs)|
61+
const triBracketDiff = Math.abs(numOfLessThanBrackets - (numOfGreaterThanBrackets - numOfEqualSigns));
62+
63+
const parenDiff = Math.abs(numOfOpenParen - numOfCloseParen);
64+
65+
if (((numOfEqualSigns > 0) && (numOfGreaterThanBrackets - numOfEqualSigns) === numOfLessThanBrackets) || (triBracketDiff === 0 && parenDiff === 0)) {
66+
// If the difference is zero, we have a full param, push it to the new params, and start over at the next string
67+
// Also, do this if there is equal signs in the params which exist with arrow functions.
68+
currentStr = currentStr.substr(0, currentStr.length - 2);
69+
70+
newParamList.push(currentStr);
71+
currentStr = "";
72+
numToGet = 1;
73+
} else {
74+
// Otherwise, set the number of strings to grab to the diff
75+
numToGet = triBracketDiff + parenDiff;
76+
}
77+
}
78+
}
79+
80+
return newParamList;
81+
}
82+
83+
function grabDefLineParams(definitionLine: string): string {
84+
let startIdx;
85+
let endIdx;
86+
87+
startIdx = definitionLine.indexOf("(");
88+
89+
if (startIdx === -1) {
90+
return "";
91+
} else {
92+
startIdx++;
93+
}
94+
95+
let depth = 1;
96+
97+
for (let i = startIdx; i < definitionLine.length; i++) {
98+
if (definitionLine[i] === "(") {
99+
depth++;
100+
} else if (definitionLine[i] === ")") {
101+
depth--;
102+
if (depth === 0) {
103+
endIdx = i;
104+
break;
105+
}
106+
}
107+
}
108+
109+
if (endIdx === undefined) {
110+
return "";
111+
}
112+
113+
return definitionLine.substring(startIdx, endIdx);
114+
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import * as assert from "assert";
22
import * as path from "path";
33
import * as vscode from "vscode";
4-
import * as Extension from "../extension";
4+
import * as Extension from "../../extension";
55

6-
const testFolderLocation = "/../../src/test/examples/";
6+
const testFolderLocation = "/../../../src/test/examples/";
77

88
suite("js annotations", () => {
99
test("should annotate function with parameters", async () => {
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import * as assert from "assert";
2+
import { splitToParamList } from "../../paramExtractor";
3+
4+
suite("splitToParamList tests", () => {
5+
test("should properly split simple parameter def with generic type", () => {
6+
const paramDef = "op1: Opt<T, A>";
7+
const expectedList = ["op1: Opt<T, A>"];
8+
const paramList = splitToParamList(paramDef);
9+
10+
assert.deepEqual(paramList, expectedList);
11+
});
12+
13+
test("should properly split parameter def with nested generic type", () => {
14+
const paramDef = "op1: Opt<T, Test<A, B, C>, D>";
15+
const expectedList = ["op1: Opt<T, Test<A, B, C>, D>"];
16+
const paramList = splitToParamList(paramDef);
17+
18+
assert.deepEqual(paramList, expectedList);
19+
});
20+
21+
test("should work with parameter def with arrow functions", () => {
22+
const paramDef = "fn: (num: number, str: string) => string, opts: any";
23+
const expectedList = ["fn: (num: number, str: string) => string", "opts: any"];
24+
const paramList = splitToParamList(paramDef);
25+
26+
assert.deepEqual(paramList, expectedList);
27+
});
28+
29+
test("should work with parameter def with arrow functions and generic return type", () => {
30+
const paramDef = "fn: (num: number, str: string) => Opt<A, B>, opts: any";
31+
const expectedList = ["fn: (num: number, str: string) => Opt<A, B>", "opts: any"];
32+
const paramList = splitToParamList(paramDef);
33+
34+
assert.deepEqual(paramList, expectedList);
35+
});
36+
37+
test("should work with complex parameter def with arrow functions and generic return type", () => {
38+
const paramDef = "fn: (num: number, strOpt: Opt<A, B, C>) => Opt<A, B>, opts: any";
39+
const expectedList = ["fn: (num: number, strOpt: Opt<A, B, C>) => Opt<A, B>", "opts: any"];
40+
const paramList = splitToParamList(paramDef);
41+
42+
assert.deepEqual(paramList, expectedList);
43+
});
44+
45+
test("should be fine with primitive types", () => {
46+
const paramDef = "str: string, opts: any";
47+
const expectedList = ["str: string", "opts: any"];
48+
const paramList = splitToParamList(paramDef);
49+
50+
assert.deepEqual(paramList, expectedList);
51+
});
52+
53+
test("should be fine with rest params", () => {
54+
const paramDef = "cats: number, ...rest: string[]";
55+
const expectedList = ["cats: number", "...rest: string[]"];
56+
const paramList = splitToParamList(paramDef);
57+
58+
assert.deepEqual(paramList, expectedList);
59+
});
60+
});

0 commit comments

Comments
 (0)