Search code examples
typescripteslintsetter

Get a warning when using setter as a property


I'm working on an angular project where we often use this "pattern"

public _value;
@Input() public set value(value) {
    this._value = value;
}

It is widespread in the project and it has its uses so we would like to keep this. The issue is that it often happens the value is used without underscore so that it points to the setter (which returns undefined without giving any error or warning) leading to unexpected results and frustrating debugging. Something like this

if(this.value) {
    //Never enters here because this.value returns undefined
}

Is there an eslint rule or tsconfig setting that would show a warning when "reading" a setter?

I read this question and the issues linked Why does accessing a missing getter in Typescript return 'undefined' rather than cause a compilation error? So i guess there's no support for this through typescript yet, I've tried googling for some eslint setting but I found nothing useful.


Solution

  • There is no eslint rule for my situation, so I decided to make my own. It's far from being similar to "accessor-pair", they refer to two unrelated issues so I wrote mine from scratch (maybe the only thing I could copy is how to find getters and setters and use it on objectliterals too). Probably will break on some cases (I'm thinking files with two classes) but before today I didn't even know how to write ESlint rules, I'll improve it with time but suggestions are welcome.

    I'm sharing the code "as is" and without tests, so far it seems to work (it found 2 setters used as property I had missed)

    let setters = [];
    // @ts-check
    /** @type {import('eslint').Rule.RuleModule} */
    module.exports = {
        meta: {
            type: 'problem',
            hasSuggestions: true,
            fixable: false,
        },
        create: (context) => {
            return {
                ClassBody(node) {
                    if(setters) {
                        setters = [];
                    }
    
                    const getters = node.body.filter((n) => n.type === 'MethodDefinition' && n.kind === 'get').map((n) => n["key"].name);
                    setters.push(...node.body.filter((n) => n.type === 'MethodDefinition' && n.kind === 'set' && !getters.includes(n["key"].name)).map((n) => n["key"].name));
                    
                    return null;
                },
                MemberExpression(node) {
                    if (node.parent.type !== 'AssignmentExpression') {
                        if (setters.includes(node.property["name"])) {
                            context.report({
                                node,
                                message: 'Found setter used as property',
                            });
                        }
                    }
    
                    return null;
                },
            };
        },
    };