Is there a eslint plugin to enforce braceless one-line if statements?
Asked Answered
J

1

8

I want to enforce one-line if statements not having braces. For example:

// incorrect
if(foo) {
    bar()
}

// correct
if(foo) bar()

But if I have else/else ifs, I still want to keep braces:

// incorrect
if(foo) bar()
else(baz) qux()

// correct
if(foo) {
    bar()
} else(baz) {
    qux()
}

EDIT: this only applies to if-statements. Other loops and such are exempt.

// correct:
for(foo) {
   bar()
}

// incorrect:
while(foo) bar

I was able to create a plugin like this:

module.exports = {
    meta: {
        type: "layout",

        fixable: "whitespace",

        messages: {
            noSingleLineIfsAsBlock: "Don't use braces with one line if statements",
            useBlocksWithElseCases: "Use braces when there are else ifs and/or elses"
        }
    },
    create(context) {
        return {
            IfStatement(node) {
                if(node.alternate === null && node.parent.type !== "IfStatement") {
                    if(node.consequent.type === "BlockStatement" && node.consequent.body.length > 0) {
                        // assumes that blank lines are removed by other eslint rules, so at most three lines for a if block with one line inside
                        if(node.consequent.loc.end.line - node.consequent.loc.start.line + 1 <= 3) {
                            context.report({
                                node: node,
                                messageId: "noSingleLineIfsAsBlock",
                                fix(fixer) {
                                    const sourceCode = context.getSourceCode();

                                    const openingBrace = sourceCode.getFirstToken(node.consequent);
                                    const closingBrace = sourceCode.getLastToken(node.consequent);
                                    const firstValueToken = sourceCode.getFirstToken(node.consequent.body[0]);
                                    const lastValueToken = sourceCode.getLastToken(node.consequent.body[0]);

                                    return [
                                        fixer.removeRange([openingBrace.range[0], firstValueToken.range[0]]),
                                        fixer.removeRange([lastValueToken.range[1], closingBrace.range[1]])
                                    ];
                                }
                            })
                        }
                    }
                } else if(node.alternate || node.parent.type === "IfStatement") {
                    if(node.consequent.type !== "BlockStatement") {
                        context.report({
                            node: node,
                            messageId: "useBlocksWithElseCases",
                            fix(fixer) {
                                // assumes that other eslint rules will fix brace styling
                                const sourceCode = context.getSourceCode();

                                const firstValueToken = sourceCode.getFirstToken(node.consequent);
                                const lastValueToken = sourceCode.getLastToken(node.consequent);

                                return [
                                    fixer.insertTextBefore(firstValueToken, "{"),
                                    fixer.insertTextAfter(lastValueToken, "}")
                                ];
                            }
                        })        
                    }

                    if(node.alternate && node.alternate.type !== "IfStatement" && node.alternate.type !== "BlockStatement") {
                        context.report({
                            node: node,
                            messageId: "useBlocksWithElseCases",
                            fix(fixer) {
                                // assumes that other eslint rules will fix brace styling
                                const sourceCode = context.getSourceCode();

                                const firstValueToken = sourceCode.getFirstToken(node.alternate);
                                const lastValueToken = sourceCode.getLastToken(node.alternate);

                                return [
                                    fixer.insertTextBefore(firstValueToken, "{"),
                                    fixer.insertTextAfter(lastValueToken, "}")
                                ];
                            }
                        })    
                    }
                }
            }
        };
    }
};

But I was wondering if there are already existing implementations I could use instead so I wouldn't have to store this in my project.

I browsed npm packages relating to eslint and searched for things like "eslint if statement formatting" but I couldn't find anything. I'm also pretty sure eslint doesn't have a built-in rule for this.

The most related issue I found was this, but it's asking how to avoid the style I'm trying to implement, and I'm not using prettier.

Are there any eslint plugins like this that can solve my problem?

Jaw answered 3/10, 2022 at 3:26 Comment(0)
P
0

It looks like the curly rule will do this with the config:

curly: ["error", "multi"]

Docs: https://eslint.org/docs/latest/rules/curly

Predicate answered 12/3 at 13:30 Comment(3)
This will also allow and enforce if(foo) bar(); else qux(); , as there is only one statement per block in this case. However, OP wants to use & enforce braces if if-else is both present. See also example fromt he docs: if (foo) foo++; else if (bar) baz() else doSomething();Schaffel
Yes, it seems pretty flexible to allow whatever style you prefer. I think the "multi" setting is what OP wants though. It's all auto-fixable so you can write several different types and then run the autofixer to see what it changes and what it doe not change.Predicate
thanks for your answer! while i no longer need this plugin, i believe that the issue i was trying to specifically address was for only if-statements; the "multi" option applies to while and for loops, as shown in the linked doc's example. i'll update the post.Jaw

© 2022 - 2024 — McMap. All rights reserved.