Skip to content

Commit ab75f5e

Browse files
committed
Provide completion from @param arg = SomeFunc()
1 parent 3c0c8f3 commit ab75f5e

File tree

7 files changed

+294
-9
lines changed

7 files changed

+294
-9
lines changed

src/org/klesun/deep_js_completion/completion_providers/DeepKeysPvdr.scala

+4
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ class DeepKeysPvdr extends CompletionProvider[CompletionParameters] {
9090
val psi = parameters.getOriginalPosition
9191
val depth = getMaxDepth(parameters.isAutoPopup)
9292
val search = new SearchCtx().setDepth(depth)
93+
val startTime = System.nanoTime
9394
val suggestions = Option(parameters.getOriginalPosition.getParent)
9495
.flatMap(findRefExpr(_))
9596
.flatMap(ref => Option(ref.getQualifier))
@@ -99,6 +100,9 @@ class DeepKeysPvdr extends CompletionProvider[CompletionParameters] {
99100
.zipWithIndex
100101
.map({case (e,i) => makeLookup(e,i)})
101102

103+
val elapsed = System.nanoTime - startTime
104+
result.addLookupAdvertisement("Resolved in " + (elapsed / 1000000000.0) + " seconds")
105+
102106
val nameToLookup = ListMap(suggestions.map(t => t.getLookupString -> t) : _*)
103107
val builtInSuggestions = new util.ArrayList[LookupElement]
104108
val onlyTyped = JSRootConfiguration.getInstance(psi.getProject).isOnlyTypeBasedCompletion

src/org/klesun/deep_js_completion/resolvers/FuncCallRes.scala

+1-7
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,7 @@ case class FuncCallRes(ctx: ICtx) {
3333
Option(funcCall.getMethodExpression)
3434
.flatMap(expr => {
3535
val definedRts = ctx.findExprType(expr)
36-
.toList.flatMap(funcT => funcT match {
37-
case funcT: JSFunctionTypeImpl => List(funcT)
38-
case mt: JSContextualUnionTypeImpl => mt.getTypes.asScala
39-
.flatMap(cast[JSFunctionTypeImpl](_))
40-
case _ => List()
41-
})
42-
.flatMap(funcT => Option(funcT.getReturnType))
36+
.toList.flatMap(funcT => MultiType.getReturnType(funcT))
4337
val builtInRts = cast[JSReferenceExpression](expr)
4438
.flatMap(ref => Option(ref.getReferenceName)
4539
.flatMap(name => Option(ref.getQualifier)

src/org/klesun/deep_js_completion/resolvers/MainRes.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import scala.util.Try
1818

1919
object MainRes {
2020

21-
private def getReturns(func: PsiElement): List[JSExpression] = {
21+
def getReturns(func: PsiElement): List[JSExpression] = {
2222
val arrow = cast[JSFunctionExpression](func)
2323
.flatMap(f => Option(f.getLastChild))
2424
.flatMap(cast[JSExpression](_))

src/org/klesun/deep_js_completion/resolvers/VarRes.scala

+69-1
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,17 @@ import java.util
44
import java.util.Objects
55

66
import com.intellij.lang.javascript.dialects.JSDialectSpecificHandlersFactory
7+
import com.intellij.lang.javascript.documentation.JSDocumentationUtils
78
import com.intellij.lang.javascript.psi.impl.{JSDefinitionExpressionImpl, JSFunctionImpl, JSReferenceExpressionImpl}
89
import com.intellij.lang.javascript.psi._
10+
import com.intellij.lang.javascript.psi.jsdoc.JSDocTag
11+
import com.intellij.lang.javascript.psi.jsdoc.impl.JSDocCommentImpl
912
import com.intellij.lang.javascript.psi.resolve.JSResolveUtil
1013
import com.intellij.lang.javascript.psi.types._
1114
import com.intellij.psi.impl.source.resolve.ResolveCache.PolyVariantResolver
12-
import com.intellij.psi.{PsiElement, PsiFile}
15+
import com.intellij.psi.{PsiElement, PsiFile, PsiWhiteSpace}
1316
import com.intellij.psi.impl.source.resolve.reference.impl.providers.{FileReference, FileReferenceSet}
17+
import com.intellij.psi.impl.source.tree.LeafPsiElement
1418
import com.intellij.psi.util.PsiTreeUtil
1519
import org.klesun.deep_js_completion.entry.PathStrGoToDecl
1620
import org.klesun.deep_js_completion.helpers.{ICtx, MultiType}
@@ -19,6 +23,9 @@ import org.klesun.lang.Lang
1923
import scala.collection.JavaConverters._
2024
import org.klesun.lang.Lang._
2125

26+
import scala.collection.GenTraversable
27+
import scala.collection.mutable.ListBuffer
28+
2229
/**
2330
* resolves variable type
2431
*/
@@ -79,9 +86,70 @@ case class VarRes(ctx: ICtx) {
7986
.flatMap(file => resolveRequireJsFormatDef(file))
8087
.flatMap(clsT => ensureFunc(clsT))
8188

89+
private def getDocTagComment(docTag: JSDocTag) = {
90+
var next = docTag.getNextSibling
91+
val tokens = new ListBuffer[PsiElement]
92+
while (next != null && (
93+
next.isInstanceOf[LeafPsiElement] ||
94+
next.isInstanceOf[PsiWhiteSpace]
95+
)) {
96+
tokens.append(next)
97+
next = next.getNextSibling
98+
}
99+
tokens.map(t => t.getText).mkString("")
100+
.replaceAll("""\n\s*\* """, "\n")
101+
}
102+
103+
private def findVarDecl(caretPsi: PsiElement, varName: String): Option[JSType] = {
104+
Lang.findParent[JSBlockStatement](caretPsi)
105+
.toList.flatMap(b => b.getStatements
106+
.flatMap(st => st match {
107+
case varSt: JSVarStatement =>
108+
varSt.getDeclarations
109+
.filter(own => varName.equals(own.getName))
110+
.map(own => own.getInitializer)
111+
.flatMap(expr => ctx.findExprType(expr))
112+
case func: JSFunctionDeclaration =>
113+
if (varName.equals(func.getName)) {
114+
val rts = MainRes.getReturns(func)
115+
.flatMap(ret => ctx.findExprType(ret))
116+
val rt = MultiType.mergeTypes(rts).getOrElse(JSUnknownType.JS_INSTANCE)
117+
Some(new JSFunctionTypeImpl(JSTypeSource.EMPTY, new util.ArrayList, rt))
118+
} else {
119+
None
120+
}
121+
case _ => None
122+
})
123+
.++(findVarDecl(b, varName))
124+
)
125+
.lift(0)
126+
}
127+
128+
private def parseDocExpr(caretPsi: PsiElement, expr: String): Option[JSType] = {
129+
"""^\s*=\s*(\w+)(\([^\)]*\)|)\s*$""".r.findFirstMatchIn(expr)
130+
.flatMap(found => {
131+
val varName = found.group(1)
132+
val isFuncCall = !found.group(2).equals("")
133+
findVarDecl(caretPsi, varName)
134+
.flatMap(t => if (isFuncCall) MultiType.getReturnType(t) else Some(t))
135+
})
136+
}
137+
138+
private def getArgDocExprType(func: JSFunction, para: JSParameter): List[JSType] = {
139+
Option(JSDocumentationUtils.findDocComment(para))
140+
.flatMap(cast[JSDocCommentImpl](_)).toList
141+
.flatMap(tag => tag.getTags)
142+
.filter(tag => "param".equals(tag.getName))
143+
.filter(tag => Option(tag.getDocCommentData)
144+
.exists(data => Objects.equals(para.getName, data.getText)))
145+
.map(tag => getDocTagComment(tag))
146+
.flatMap(expr => parseDocExpr(para, expr))
147+
}
148+
82149
private def resolveArg(para: JSParameter): Option[JSType] = {
83150
val types = Option(para.getDeclaringFunction)
84151
.toList.flatMap(func => List[JSType]()
152+
++ getArgDocExprType(func, para)
85153
++ getInlineFuncArgType(func)
86154
++ getKlesunRequiresArgType(func))
87155
MultiType.mergeTypes(types)

tests/KlesunRequires.js

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* this is a handy wrapper for require.js
3+
* are you tired of specifying module paths in one
4+
* array, and matching resolved values in another?
5+
* then this util is for you:
6+
*
7+
* Before:
8+
* define(['A.js', 'B.js', 'E.js', 'D.js'], (A, B, E, D) => {...})
9+
*
10+
* After:
11+
* klesun.requires('A.js').then = A =>
12+
* klesun.requires('B.js').then = B =>
13+
* klesun.requires('E.js').then = E =>
14+
* klesun.requires('D.js').then = D =>
15+
* klesun.whenLoaded = () => {...}
16+
*
17+
* Note that each module is supposed to return a function, so this
18+
* util will be useful only for local project module resolution
19+
*/
20+
var Klesun = Klesun || (() => {
21+
let modulePaths = [];
22+
let loadedClasses = [];
23+
return 1 && {
24+
requires: function (modulePath) {
25+
modulePaths.push(modulePath);
26+
let i = modulePaths.length - 1;
27+
let makeInstance = (...args) => {
28+
let module = loadedClasses[i];
29+
if (typeof module === 'function') {
30+
// module is a function (class) - call it with passed params
31+
return module(...args);
32+
} else {
33+
// module is a static object - just return it
34+
return module;
35+
}
36+
};
37+
return {
38+
set then(callback) {
39+
callback(makeInstance);
40+
}
41+
};
42+
},
43+
set whenLoaded(finalCallback) {
44+
define(modulePaths, (...modules) => {
45+
loadedClasses.push(...modules);
46+
return finalCallback();
47+
});
48+
},
49+
};
50+
});

tests/Tls.js

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
2+
/**
3+
* provides various coding tools like Optional-s and Promise-s
4+
*/
5+
define([], () => (...ctorArgs) => {
6+
7+
/** similar to Java's Optional
8+
* @param forceIsPresent - use when value may be null (when it's not typed for example) */
9+
let opt = function (value, forceIsPresent = false) {
10+
let has = () => forceIsPresent ||
11+
value !== null && value !== undefined;
12+
let self;
13+
return self = {
14+
map: (f) => has() ? opt(f(value)) : opt(null),
15+
/** flat map - return empty or mapped array*/
16+
fmp: (f) => has() ? f(value) : [],
17+
flt: (f) => has() && f(value) ? opt(value) : opt(null),
18+
saf: (f) => {
19+
if (has()) {
20+
try { return opt(f(value)); }
21+
catch (exc) { console.error('Opt mapping threw an exception', exc); }
22+
}
23+
return opt(null);
24+
},
25+
def: (def) => has() ? value : def,
26+
has: has,
27+
set get(cb) { if (has()) { cb(value); } },
28+
wth: (f) => {
29+
if (has()) { f(value); }
30+
return self;
31+
},
32+
uni: (some, none) => has() ? some(value) : none(),
33+
err: (none) => {
34+
if (has()) {
35+
return { set els(some) { some(value); } };
36+
} else {
37+
none();
38+
return { set els(some) { } };
39+
}
40+
},
41+
};
42+
};
43+
44+
/**
45+
* similar to the built-in Promise, I guess,
46+
* but in slightly more convenient format
47+
*/
48+
let promise = function(giveMemento)
49+
{
50+
let done = false;
51+
let result;
52+
let thens = [];
53+
giveMemento(r => {
54+
done = true;
55+
result = r;
56+
thens.forEach((cb) => cb(result));
57+
});
58+
let self = {
59+
set then(receive) {
60+
if (done) {
61+
receive(result);
62+
} else {
63+
thens.push(receive);
64+
}
65+
},
66+
map: (f) => promise(
67+
delayedReturn => self.then =
68+
(r) => delayedReturn(f(r))
69+
),
70+
};
71+
return self;
72+
};
73+
74+
/** @param responseType = 'arraybuffer' | 'json' | 'text' */
75+
let http = (url, responseType) => promise(done => {
76+
let oReq = new XMLHttpRequest();
77+
oReq.open("GET", url, true);
78+
if (responseType) {
79+
oReq.responseType = responseType;
80+
}
81+
oReq.onload = () => done(oReq.response);
82+
oReq.send(null);
83+
});
84+
85+
/** a shorthand to create dom elements in js with one expression */
86+
let mkDom = (tagName, params) => {
87+
let dom = document.createElement(tagName);
88+
for (let [k,v] of Object.entries(params || {})) {
89+
if (k === 'innerHTML') {
90+
dom.innerHTML = v;
91+
} else if (k === 'children') {
92+
v.forEach(c => dom.appendChild(c));
93+
} else if (k === 'style') {
94+
Object.keys(v).forEach(k => dom.style[k] = v[k]);
95+
} else if (k === 'classList') {
96+
v.forEach(c => dom.classList.add(c));
97+
} else {
98+
dom[k] = v;
99+
if (typeof v !== 'function') {
100+
dom.setAttribute(k, v);
101+
}
102+
}
103+
}
104+
return dom;
105+
};
106+
107+
return {
108+
opt: opt,
109+
promise: promise,
110+
http: http,
111+
mkDom: mkDom,
112+
range: (l, r) => new Array(r - l).fill(0).map((_, i) => l + i),
113+
deepCopy: val => JSON.parse(JSON.stringify(val)),
114+
};
115+
});

tests/index.js

+54
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,60 @@
22

33
let ssrLineNumbers = [];
44

5+
var klesun = Klesun();
6+
klesun.requires('./Tls.js').then = Tls =>
7+
klesun.whenLoaded = () => (...ctorParams) => {
8+
let tls = Tls();
9+
// should suggest: opt, promise, http, mkDom, range, deepCopy
10+
tls.o;
11+
};
12+
13+
define(['./Tls.js'], (Tls) => (...ctorArgs) => {
14+
let tls = Tls();
15+
// should suggest: opt, promise, http, mkDom, range, deepCopy
16+
tls.o;
17+
});
18+
19+
/**
20+
* @typedef {Object} generatedPoint
21+
* @type {Object}
22+
* @property {number} x The x coordinate.
23+
* @property {number} y The y coordinate.
24+
* @property {number} amount
25+
*/
26+
27+
let a = 5,
28+
b = 6;
29+
30+
/** @param whiskey {generatedPoint} */
31+
let Denis = function(whiskey){
32+
return {
33+
monthsTilDiploma: 12 + whiskey.amount / 1000,
34+
takeTripleShot: () => alert('I aint drunk yeaaaat'),
35+
dropTheLocker: () => alert('Bam!'),
36+
};
37+
};
38+
39+
setTimeout(() => {
40+
/**
41+
* @param denis = Denis()
42+
* @param tls = from('./Tls.js')()
43+
* @param smf {from('./SmfAdapter.js')()}
44+
* @param pax {{first: 'Vova', last: 'Pupkin', age: '18'}}
45+
* @param segment {{a: string, b: int}}
46+
* @param segment.from
47+
* @param segment.to
48+
*/
49+
let testArgDocComment = function(denis, tls, smf, pax, segment) {
50+
// should suggest: yearsTilDiploma, takeTripleShot, dropTheLocker
51+
denis.t;
52+
// should suggest: first, last, age
53+
pax.f;
54+
// should suggest: opt, promise, http, mkDom, range, deepCopy
55+
tls.o;
56+
};
57+
}, 100);
58+
559
let collectSmfNotes = function(smf) {
660
let notes = [];
761
notes.push({zalupa:123});

0 commit comments

Comments
 (0)