JSDoc #param - reference class from another module - node.js

I have 2 Node modules. In module A I have the following definition:
/**
* HTTP Client
* #module src/http/client
*/
/**
* A HTTP Client
* #alias src/http/client
*/
class HTTPClient {
[... class def with documented methods etc]
}
module.exports = HTTPClient
And now in module B I want to say that the first constructor parameter should be of type HTTPClient. So I tried the following
class PackageFactory {
/**
* #param {module:src/http/client} httpClient - the HTTPClient instance
*/
constructor(httpClient) {
this._httpClient = httpClient
}
}
I also tried a few variations but it never worked. Within module B the httpClient is always of type "any". What do I have to change so I can see the class member of HTTPClient in module B?

The solution was easier then I thought. There is no need to include the module paths (aka longname) or anything.
const HTTPClient = require('../http/client')
class PackageFactory {
/**
* #param {HTTPClient} httpClient - the HTTPClient instance that shall be used to make requests
*/
constructor(httpClient) {
this._httpClient = httpClient
}
}

To avoid the HTTPClient is unused error that any linter will give you, you can actually import the class in the #param field itself. This makes the doc a bit less easy to read, but you get autocompletion at design time, a nice tradeoff.
Note that you can combine this with the compilerOptions.paths option of tsconfig.json to create aliases that will make those documentation-imports look better.
class PackageFactory {
/**
* #param {import('../http/client')} httpClient - the HTTPClient instance that shall be used to make requests
*/
constructor(httpClient) {
this._httpClient = httpClient
}
}

Related

Application crash when calling container.get() in abstract class's constructor

I'm currently trying to implement a base class which has multiple properties. All but one property are being injected via the constructor using InversifyJS's #Inject tag. I'm also getting an instance via the container.Get() function in the constructor. When I run my application everything is fine, but when the application recieves a request, the app crashes without an error.
Base class
/**
* Base class for all intent logic
*/
#injectable()
export default abstract class IntentBase implements IIntent {
protected logger: ILogger;
protected responseService: IResponseService;
protected contextService: IContextService;
protected fallbackIntent: IIntent;
protected responseBuilder: IResponseBuilder;
/**
* Constructor
* #param logger logger
* #param responseService response service
* #param contextService context service
*/
constructor(
#inject(TYPES.WinstonLogger) logger: ILogger,
#inject(TYPES.ResponseService) responseService: IResponseService,
#inject(TYPES.ContextService) contextService: IContextService,
#inject(TYPES.ResponseBuilder) responseBuilder: IResponseBuilder,
) {
this.logger = logger;
this.responseService = responseService;
this.contextService = contextService;
this.responseBuilder = responseBuilder;
this.fallbackIntent = container.get(TYPES.DefaultFallbackIntent); // <-- container.Get() line
}
/**
* Override the standard fallback logic for an intent.
* #param fallback fallback to set
*/
setFallback(fallback: IIntent): void {
this.fallbackIntent = fallback;
}
When I uncomment the container.get(TYPES.DefaultFallbackIntent) line, my application can recieve requests like normally and it doesn't crash. The reason why I am trying this injection setup is because I'd like to set a default behavior for each child class in the constructor.
Does anyone have an explaination to why I cannot seem to inject this class?
Thanks in advance
Update
inversify.config.ts
import DefaultFallbackIntent from "./src/bot/intents/fallbacks/defaultFallbackIntent";
import TextResponseRepository from "./src/repositories/textResponseRepository";
import SsmlResponseRepsitory from "./src/repositories/ssmlResponseRepository";
import ContextService from "./src/services/contextService";
import GoogleResponseBuilder from "./src/builders/googleResponseBuilder";
const container = new Container();
container.bind<IGoogleAssistantController>(TYPES.GoogleController).to(GoogleAssistantController);
container.bind<IResponseService>(TYPES.ResponseService).to(ResponseSerivce);
container.bind<IContextService>(TYPES.ContextService).to(ContextService);
container.bind<IResponseRepository>(TYPES.TextResponseRepository).to(TextResponseRepository);
container.bind<IResponseRepository>(TYPES.SsmlResponseRepository).to(SsmlResponseRepsitory);
container.bind<ILogger>(TYPES.WinstonLogger).to(WinstonLogger);
container.bind<IIntent>(TYPES.WelcomeIntent).to(WelcomeIntent);
container.bind<IIntent>(TYPES.DefaultFallbackIntent).to(DefaultFallbackIntent);
container.bind<IResponseBuilder>(TYPES.ResponseBuilder).to(GoogleResponseBuilder);
export { container };
Default fallback intent
/**
* Default fallback intent class
*/
#injectable()
export default class DefaultFallbackIntent extends IntentBase {
invoke(conv: DialogflowConversation): DialogflowConversation {
const response = this.responseService.getResponse("defaultFallback");
return conv.ask(response);
}
}
In order for container.get(<Type>) to work, <Type> must be bound to the container at some point. In your composition root (where you set up your container) you can create this binding:
const container = new Container();
container.bind<IIntent>(TYPES.DefaultFallbackIntent)).to(<TheDefaultFallbackIntentClass>);
EDIT:
From the discussion in the comments it seems that DefaultFallbackIntent inherting from IntentBase is the problem, because at the time DefaultFallbackIntent gets instantiated (through binding it), there doesn't exist such an instance in the container when the base constructor is executed.
A workaround would be to not inherit from IntentBase and just implement the interface and set the required fields in this class as well:
/**
* Default fallback intent class
*/
#injectable()
export default class DefaultFallbackIntent implements IIntent {
protected logger: ILogger;
private responseService: IResponseService;
private contextService: IContextService;
private fallbackIntent: IIntent;
private responseBuilder: IResponseBuilder;
invoke(conv: DialogflowConversation): DialogflowConversation {
const response = this.responseService.getResponse("defaultFallback");
return conv.ask(response);
}
}
However, a better solution would be two create another super class which contains all the required fields both the default fallback intent and others have in order to reduce duplicated code.

How do I JSDoc custom EventEmitter on events in Visual Studio Code?

I've been working on a Node.js project and only just noticed that Visual Studio Code provides information about base EventEmitter objects. So I imagine it should be possible to provide JSDoc for custom ones too.
I've already attempted following the JSDoc http://usejsdoc.org/tags-event.html documentation but doesn't seem to pick up.
I don't know if this is effecting it but I'm using the ES6 class where the events get processed in a function outside it but it's inside the same script.
This is the test code.
// voice
if (voice) {
try {
/**
* Voice event.
*
* #event TelegramBot#voice
* #type {object}
* #property {object} chat - [object Chat]
* #property {number} date - Date when content was sent.
* #property {object} from - [object User]
* #property {number} message_id - Message id.
* #property {string} caption - Caption added to message. Value is undefined if none is added.
* #property {object} voice - [object Voice]
*/
context.emit('voice', chat, date, from, message_id, caption, voice)
} catch (error) {
context.emit('error', error)
}
}
I found this question while attempting to do the same, so I kept searching and I found a possible solution, by overriding the addListener methods, and documenting them.
I successfully made it work for both NodeJS and browser JS.
Result in VSCode
Typedef
First, create the typedef with your events names :
/**
* #typedef {["someEvent" | "someOtherEvent", ...any[]]} eventsDef
*/
I am using the spread syntax since I don't know how many arguments the user will pass to the method.
Implementation
For NodeJS:
The class needs to override the on and addListener methods, document the arguments, and simply call the parent's own method :
class Test extends EventEmitter {
/**
* #param {eventsDef} args
*/
addListener(...args) {
super.addListener(...args);
}
/**
* #param {eventsDef} args
*/
on(...args) {
super.on(...args);
}
fireEvent() {
this.emit("someEvent", "someValue");
}
}
Then you can use this class like so :
const t = new Test();
t.on("someEvent", (val) => console.log(val));
t.fireEvent(); // Outputs "someValue"
For the browser:
The class needs to extends EventTarget and override the addEventListener method.
Here is the snippet :
class Test extends EventTarget {
/**
* #param {["someEvent" | "someOtherEvent", ...any[]]} args
*/
addEventListener(...args) {
super.addEventListener(...args);
}
fireEvent() {
this.dispatchEvent(new CustomEvent("someEvent", {detail: "someValue"}))
}
}
const t = new Test();
t.addEventListener("someEvent", val => console.log(val.detail));
document.querySelector("button").addEventListener("click", () => {
t.fireEvent();
})
<button>Fire event</button>

File::load in drupal8 custom module controller

i have created one custom module, in the controller file i used File::load static method. but when I'm run phpcs for check coding standards it will give an error as
File::load calls should be avoided in classes, use dependency injection instead
anyone can please tell how to create dependency injection for this.
The be achieved by using Drupal\Core\Entity\EntityTypeManagerInterface.
use Drupal\Core\Entity\EntityTypeManagerInterface;
class MyForm extends FormBase {
/**
* The storage handler class for files.
*
* #var \Drupal\file\FileStorage
*/
protected $fileStorage;
/**
* This is an example.
*
* #param \Drupal\Core\Entity\EntityTypeManagerInterface $entity
* The Entity type manager service.
*/
public function __construct(EntityTypeManagerInterface $entity) {
$this->fileStorage = $entity->getStorage('file');
}
....
}
From there you can update the File::load($fid) to $this->fileStorage->load($fid)

How to document ES6 class module with jsdoc3?

If I am developing a node module something-nifty which has the following directory structure:
something-nifty/
lib/
plugins/
Plugin.cs
index.js
package.json
And plugin.cs is defined as follows:
"use strict";
/**
* #module something-nifty/lib/plugins/Plugin
*/
/**
* The base class for a 'something-nifty' plugin.
*/
class Plugin {
/**
* Constructs the plugin...
*/
constructor() {
}
}
module.exports = Plugin;
In the generated documentation the class is documented as though it has the #inner tag which means that I need to repeat the class name twice whenever I refer to it:
"use strict";
/**
* #module something-nifty/lib/foo
*/
/**
* Foo...
* #param {module:something-nifty/lib/plugins/Plugin~Plugin} plugin
*/
exports.abc = function(plugin) { ... };
Surely I shouldn't need to specify the class name twice in this situation since the module is essentially the class. What is the correct way to annotate this with jsdoc3 tags such that it outputs documentation that is properly structured (module and class listings)?
Having spent some time searching the best that I have been able to find is this is to (in my opinion) "misuse" the #alias tag like shown in the following snippet:
"use strict";
/**
* #module something-nifty/lib/plugins/Plugin
*/
/**
* The base class for a 'something-nifty' plugin.
* #alias module:something-nifty/lib/plugins/Plugin
*/
class Plugin {
/**
* Constructs the plugin...
*/
constructor() {
}
}
module.exports = Plugin;
The following link refers to the article where I picked up on this #alias trick:
http://www.pagosoft.com/javascript/using-jsdoc-amd-modules/

Is there a way to make the #Builder annotation work for immutable classes?

I'm trying to develop a project in Groovy and I've been looking through my code and trying to find areas which I could replace with something more idiomatically Groovy until I find a solution for another issue I've been having.
I've started taking a more in-depth look into the use of AST transformation annotations - they've helped significantly cut down on the amount of code I have to write in some places. However, I'm having an issue using the groovy.transform.builder.Builder annotation with one of my immutable value classes. The source for this annotation is hosted here.
The issue is that the annotation seems to make the builder set the values of the buildee directly rather than storing a copy of the values and passing them to the buildee's constructor. This results in a ReadOnlyPropertyException when you try to use it with immutable classes.
There are four possible Builder strategies you can select with this annotation, and of them I've tried DefaultStrategy, ExternalStrategy and InitializerStrategy. However, all of these have caused problems.
ExternalStrategy looks like the most promising of the four, and you can find an SSCCE based on it detailing the problem here.
The source code from the example is also included below:
import groovy.transform.Immutable
import groovy.transform.builder.Builder as GBuilder
import groovy.transform.builder.ExternalStrategy
/*
* Uncommenting the below causes a failure:
* 'groovy.lang.ReadOnlyPropertyException: Cannot set readonly property: value for class: Value'
*/
//#Immutable
class Value {
#GBuilder(forClass = Value, prefix = 'set', builderStrategy = ExternalStrategy)
static class Builder { }
int value
String toString() { "Value($value)" }
}
def builder = new Value.Builder()
println builder.setValue(1).build()
There also seems to be a relevant JIRA discussion on the matter here.
Edit
I've tried using CFrick's answer below, using InitializerStrategy rather than ExternalStrategy.
Everything now compiles, but I get the following errors at run-time when I try to execute my tests:
java.lang.IllegalAccessError: tried to access class com.github.tagc.semver.version.BaseVersion from class com.github.tagc.semver.version.BaseVersion$com.github.tagc.semver.version.BaseVersionInitializer
at java.lang.Class.getDeclaringClass(Class.java:1227)
at java.beans.MethodRef.set(MethodRef.java:46)
at java.beans.MethodDescriptor.setMethod(MethodDescriptor.java:117)
at java.beans.MethodDescriptor.<init>(MethodDescriptor.java:72)
at java.beans.MethodDescriptor.<init>(MethodDescriptor.java:56)
at java.beans.Introspector.getTargetMethodInfo(Introspector.java:1163)
at java.beans.Introspector.getBeanInfo(Introspector.java:426)
at java.beans.Introspector.getBeanInfo(Introspector.java:173)
at com.github.tagc.semver.version.VersionFactory.createBaseVersion(VersionFactory.groovy:34)
at com.github.tagc.semver.test.util.TestSetup.<clinit>(TestSetup.groovy:77)
at java.lang.Class.forName(Class.java:344)
at com.github.tagc.semver.version.SnapshotDecoratorSpec.#decoratedVersion should be considered equal to patch-bumped #releaseVersion snapshot(SnapshotDecoratorSpec.groovy:24)
Followed thereafter by a series of exceptions like the following:
java.lang.NoClassDefFoundError: Could not initialize class com.github.tagc.semver.test.util.TestSetup
at java.lang.Class.forName(Class.java:344)
at com.github.tagc.semver.version.SnapshotDecoratorSpec.#decoratedVersion should be considered equal to minor-bumped #releaseVersion snapshot(SnapshotDecoratorSpec.groovy:36)
What I have right now is a BaseVersion class like the following:
/**
* A concrete, base implementation of {#link com.github.tagc.semver.version.Version Version}.
*
* #author davidfallah
* #since v0.1.0
*/
#Immutable
#Builder(prefix = 'set', builderStrategy = InitializerStrategy)
#PackageScope
final class BaseVersion implements Version {
// ...
/**
* The major category of this version.
*/
int major = 0
/**
* The minor category of this version.
*/
int minor = 0
/**
* The patch category of this version.
*/
int patch = 0
/**
* Whether this version is a release or snapshot version.
*/
boolean release = false
// ...
}
A factory to produce instances of these:
/**
* A factory for producing base and decorated {#code Version} objects.
*
* #author davidfallah
* #since v0.5.0
*/
class VersionFactory {
// ...
/**
* Returns an instance of {#link com.github.tagc.semver.version.BaseVersion BaseVersion} constructed
* with the given parameters.
*
* #param major the major category value of the version instance
* #param minor the minor category value of the version instance
* #param patch the patch category value of the version instance
* #param release the release setting of the version instance
* #return an instance of {#code BaseVersion}
*/
static BaseVersion createBaseVersion(int major, int minor, int patch, boolean release) {
return new BaseVersion(major, minor, patch, release)
}
/**
* Returns an instance of {#link com.github.tagc.semver.version.BaseVersion BaseVersion} constructed
* with the given parameters.
*
* #param m a map of parameter names and their corresponding values corresponding to the
* construction parameters of {#code BaseVersion}.
*
* #return an instance of {#code BaseVersion}
*/
static BaseVersion createBaseVersion(Map m) {
return new BaseVersion(m)
}
/**
* Returns an instance of {#link com.github.tagc.semver.version.BaseVersion BaseVersion} constructed
* with the given parameters.
*
* #param l a list of parameter values corresponding to the construction parameters of {#code BaseVersion}.
*
* #return an instance of {#code BaseVersion}
*/
static BaseVersion createBaseVersion(List l) {
return new BaseVersion(l)
}
/**
* Returns a builder for {#link com.github.tagc.semver.version.BaseVersion BaseVersion} to specify
* the construction parameters for the {#code BaseVersion} incrementally.
*
* #return an instance of {#code BaseVersion.Builder}
*/
static Object createBaseVersionBuilder() {
return BaseVersion.builder()
}
// ...
}
A test specification class for Version objects:
/**
* Test specification for {#link com.github.tagc.semver.version.Version Version}.
*
* #author davidfallah
* #since 0.1.0
*/
#Unroll
class VersionSpec extends Specification {
static exampleVersions = [
VersionFactory.createBaseVersion(major:1, minor:2, patch:3),
VersionFactory.createBaseVersion(major:0, minor:0, patch:0),
VersionFactory.createBaseVersion(major:5, minor:4, patch:3),
VersionFactory.createBaseVersion(major:1, minor:16, patch:2),
VersionFactory.createBaseVersion(major:4, minor:5, patch:8),
]
// ...
}
And other classes that try to create instances of BaseVersion that are failing, such as TestSetup.
Your code there fails, because chosen strategy there basically does:
def v = new Value().with{ setValue(1); return it }
and this can not be done on #Immutable objects.
According to the docs, there is only InitializerStrategy, that can explicitly cope with #Immutable.
You can use the InitializerStrategy in conjunction with #Canonical and #Immutable. If your #Builder annotation doesn’t have explicit includes or excludes annotation attributes but your #Canonical annotation does, the ones from #Canonical will be re-used for #Builder.
E.g.
import groovy.transform.*
import groovy.transform.builder.*
#Immutable
#ToString
#Builder(prefix='set', builderStrategy=InitializerStrategy)
class Value {
int value
}
def builder = Value.createInitializer().setValue(1)
assert new Value(builder).toString()=='Value(1)'
Depending on what you are up to, this is rahter ugly syntax and you might be better off just using the Map-based c'tors. Even without e.g. #TypeChecked a new Value(vlaue: 666) will generate an error and leaving params (for a class with multiple properties) will leave them null.

Resources