Search code examples
javascriptclassphpstormwebstormjsdoc

JSDoc: Accept instances of base class and it's child classes in `@type` tag


I have a base class (Validator) that is extended by a child class (ValidatorRequired).

Then in another class I want to have a collection of instances that may be of base class type (Validator) or any of its descendants (ValidatorRequired).

However, when I try to create an instance of a child class and place it inside that collection (at FormField class constructor), I get a weak warning from PhpStorm: Assigned expression type ValidatorRequired is not assignable to type Validator:

PhpStorm inspection "Assigned expression type ValidatorRequired is not assignable to type Validator"

I suspect the error comes from the fact I've not defined the @type tag correctly.

What is the correct way to fix that inspection?

FormField.js:

import { ValidatorRequired } from './ValidatorRequired';

export class FormField {
    /**
     * Field's current value.
     *
     * @type {number|string}
     */
    value;

    /**
     * A collection of validators that should be run each time the field is validated.
     *
     * @type {Object.<string, Validator>}
     */
    validators = {};

    constructor({ required }) {
        this.required = Boolean(required);

        // The part after the `=` operator is underlined with grey as a weak warning
        this.validators.validatorRequired = new ValidatorRequired({ formField: this });
    }
}

Validator.js:

export class Validator {
    /**
     * The field to validate.
     *
     * @type {FormField}
     */
    formField;

    /**
     * @param formField {FormField}
     */
    constructor({ formField }) {
        this.formField = formField;
    }

    /**
     * Validates a field.
     * Should be overridden by child classes.
     *
     * @returns {boolean}
     */
    validate() {
        return this.success();
    }

    /**
     * Ends validation with successful result.
     *
     * @returns {boolean}
     */
    success() {
        return true;
    }

    /**
     * Ends validation with an error.
     *
     * @returns {boolean}
     */
    error() {
        return false;
    }
}

ValidatorRequired.js:

import { Validator } from './Validator';

/**
 * A validator that checks for field's value to be non-empty when it is required.
 */
export class ValidatorRequired extends Validator {
    /**
     * Validates a field.
     *
     * @returns {boolean}
     */
    validate() {
        // If the field is not required, return success.
        if (!this.formField.required) {
            return this.success();
        }

        // If the value is not falsy, return success.
        if (this.formField.value) {
            return this.success();
        }

        // If all previous checks have failed, return error.
        return this.error();
    }
}

Solution

  • Turns out the error was not in the @type tag in JSDoc, rather in how PhpStorm treats the class that is specified there.

    I have React library imported somewhere else in my project. And React has a class Validator defined. PhpStorm is aware of that and thinks the Validator I specified in JSDoc is the Validator in React and not my own Validator.

    This fact is proved when you try going to the definition of class Validator through JSDoc comment for validators field (Ctrl + click on the class, or place a cursor at the class and press Ctrl+B). If you do that, you get a "Choose Declaration" dialogue window, where all the possible declarations are listed:

    "Choose Declaration" dialogue window

    So the solution is to make PhpStorm think the Validator specified is 100% my own class. This is easily done by explicitly importing the Validator:

    FormField.js:

    import { Validator } from './Validator';
    

    This solution has a minor downside: your base class (Validator) will be included in the bundle regardless of whether it is used or not. However, I think this has a negligible impact, because:

    1. If you use a class that is a descendant from the base class, you certainly will have the base class included in the bundle anyway (class ValidatorRequired extends Validator).
    2. If you don't use any of the descendants right where you document the @type tag (class FormField), you are probably using them in the descendants of the class that is documenting the usage (FormField descendants), or you are doing something wrong.
    3. The size of the base class is probably too tiny to bring a vast effect on the bundle.