JSImplicitElementmpl / Enhancing JS completions

Hi, 

 

I'm currently using the `JavaScript.frameworkIndexingHandler` extension point to add some elements to completions which to IDE can otherwise not know about. Thats because the framework for example creates missing getter and setter functions at runtime. 

My current code for this looks somewhat like this:  (in `processProperty(String name, JSElement value, JSElementIndexingData outData)`)

if (properties != null && properties.getValue() instanceof JSObjectLiteralExpression) {
JSObjectLiteralExpression props = (JSObjectLiteralExpression) properties.getValue();
JSSymbolUtil.forEachIdentifierProperty(props, (titleCasedPropName, property) -> {
String typeString = getPropertyType(property);
JSImplicitFunctionImpl.Builder getterBuilder = (new JSImplicitFunctionImpl.Builder(JSSymbolUtil.suggestGetterName(titleCasedPropName), property));
getterBuilder.setNamespace(((JSProperty) value.getParent()).getNamespace())
.setContext(JSContext.INSTANCE)
.setProperties(JSImplicitElement.Property.GetFunction)
.setType(JSImplicitElement.Type.Function)
.setTypeString(typeString);

outData.addImplicitElement(getterBuilder.toImplicitElement());
JSImplicitFunctionImpl.Builder setterBuilder = (new JSImplicitFunctionImpl.Builder(JSSymbolUtil.suggestSetterName(titleCasedPropName), property));
List<JSImplicitParameterStructure> parameters = new ArrayList<>();
parameters.add(new JSImplicitParameterStructure("value", typeString, false, false, false));
setterBuilder.setNamespace(((JSProperty) value.getParent()).getNamespace())
.setContext(JSContext.INSTANCE)
.setProperties(JSImplicitElement.Property.SetFunction)
.setType(JSImplicitElement.Type.Function)
.setParameters(parameters)
.setTypeString("null");
outData.addImplicitElement(setterBuilder.toImplicitElement());
});
}

 

I got some problems with this. 

First, the completion is always incomplete, as in missing the parenthesis and the arguments. Additionally, the IDE does not seem to use the return value information for futher completions. 

Another point is that I guessed (because I could not find documentation) that `.setContext(JSContext.INSTANCE)` would mark the function as a "method", but I also receive completions for these functions in the global context. 

Both functions are added by the code above, one in the file edited, one in another file. I'd expect the first completion (setItemsPath) to appear only after a "this." and contain a parameter "value" which is expected to be a string. 

When completeing after this. I only receive the functions that belong to this "class" (or whatever you may call this in js) but again without parameters. If I expand that completion it looks like this: 

The setLazyKey on the other hand is a realy existing function in this object and suggestions and completions work find for that one: 



Is it possible to create the same "knowledge" using the  JSImplicitFunctionImpl/JSImplicitElementImpl? If not, is there any other way? 

 

 

0
6 comments

Hi Mark,

sorry for the delayed reply.

Let's start with the simple problem first: parenthesis. Parenthesis are not shown for setters and getters because you can use such functions without explicit parentheses (e.g. foo.keyPath = ). If you remove 

.setProperties(JSImplicitElement.Property.GetFunction)

you should see parentheses and parameters. Also, I'd probably remove 

.setTypeString("null");

from the setter to get rid of those null on the right hand side of completion popup.

To understand the problem with namespaces and types I'd probably need more context on the framework (e.g. how does the class with such getters look like) and the actual values that are passed to setNamespace and setTypeString

0

Hi Dennis, 

 

thanks for you replay. Removing the properties acutally fixed it. Don't know why I haven't tried that myself... 

 

The Framework uses its own "class framework" to achive some magic (like these generated getters/setters) and RTTI in JS. 

A complete (minimalistic) class file looks like this: 

```

sap.ui.define(["some/Dependency"], function (Dependency) {

return Dependency.extend("my.namespace.ClassName", {
metadata: {
properties: {
color: {type: "string"}
}
},


constructor: function () {
// this is whats called for `new my.namespace.Classname()` or `new Classname()` when imported
},

chooseRandomColor: function () {
this.setColor("blue"); // a default setColor implementation is created by the framework if none exists
}

});
}, true);

``` 

 

Theres a "AMD-like" module syntax with some own class handling (I guess "Not Invented Here" is what they shout before every meeting to get in the mood). 

In this case, "my.namespace.Classname" would get passed to "setNamespace" and "string" to setTypeString. 

 

`setTypeString` actually works and shows the correct return type, but I still get suggestions for calling the methods out of their context. 

 

Heres the current version of the source code: 

``` 

JSSymbolUtil.forEachIdentifierProperty(props, (titleCasedPropName, property) -> {
String typeString = getPropertyType(property);
System.out.println("namespace: " + ((JSProperty) value.getParent()).getNamespace());
System.out.println("typestring: " + typeString);
JSImplicitElementImpl.Builder getterBuilder = new JSImplicitElementImpl.Builder(JSSymbolUtil.suggestGetterName(titleCasedPropName), property);
getterBuilder.setNamespace(((JSProperty) value.getParent()).getNamespace())
.setContext(JSContext.INSTANCE)
.setType(JSImplicitElement.Type.Function)
.setTypeString(typeString);

outData.addImplicitElement(getterBuilder.toImplicitElement());
JSImplicitFunctionImpl.Builder setterBuilder = (new JSImplicitFunctionImpl.Builder(JSSymbolUtil.suggestSetterName(titleCasedPropName), property));
List<JSImplicitParameterStructure> parameters = new ArrayList<>();
parameters.add(new JSImplicitParameterStructure("value", typeString, false, false, false));
setterBuilder.setNamespace(((JSProperty) value.getParent()).getNamespace())
.setContext(JSContext.INSTANCE)
.setType(JSImplicitElement.Type.Function)
.setParameters(parameters);
outData.addImplicitElement(setterBuilder.toImplicitElement());
});
```

 

0

I think you'll need to override resolveContextFromProperty in your indexing handler with proper namespace for methods in such "class" and probably provide a containing implicit element for this "class" too (namespace: my.namespace, name: Classname, type: Class)

0

I'm already creating this class implicit element (via `createLiteralImplicitElementProvider`), so I added an implementation for "resolveContextFromProperty" which returns the "my.namespace.Classname" when given the object thats passed as a second argument to that .extend (basically a .getContext().getArguments()[0].getValue(), but with a lot of asserts and ifs). 

This did not fix it. While debugging, I saw that the Icons used for the suggestions are different and that the suggestion that happens out of context (the one i want to get rid of) does not include the parenthesis and arguments. I did some debugging and found that the QxDooFrameworkHandler calls "JSElementIndexingData::addAccessorsFromObjectLiteral" in "processProperty", too. So it wasn't mine IndexingHandler that created the "wrong" entries, its acutally code in the official JavaScript plugin itself that does that. Guess I can't do anything about that ;) 

 

Thanks for your help, much appreciated!

0

I'm pushing a fix that will make QxDooFrameworkHandler less aggressive, so these should go away. It will be available in 2017.3 for sure and may be we'll backport it to one of 2017.2 bugfix updates

1

Thanks, I "fixed" it by removing these entries in processProperty when I'm sure I'm running in a UI5-Context. Kind of works good, but a fixed QxDooFrameworkHandler would be the better solutions. Great news! 

My plugin may finally be on a good way for a new release :D 

0

Please sign in to leave a comment.