Skip to content

add a flag that enforces boolean expressions where booleans are expected #7306

Closed

Description

a flag that enforces:

  • any expression with the following operators: ||, &&, ! to be boolean
  • a condition expression to be of type boolean in the if statement and in the conditional expression (ternary operator)

related: #7256

Activity

  1. RyanCavanaugh commented on Feb 29, 2016

    @RyanCavanaugh
    Member

    This would be extremely non-idiomatic for JavaScript (as in, I expect almost no codebases to successfully compile under such a flag). I think TSLint would be the place for a rule like this.

  2. basarat commented on Mar 1, 2016

    @basarat
    Contributor

    Agree with ryan. Just today :

    image

    It is highly idiomatic to do such checks in JSX/TSX/React 🌹

  3. myitcv commented on Mar 4, 2016

    @myitcv

    Aleksey-Bykov palantir/tslint#253 (amongst others)

    This is blocked on palantir/tslint#680 however

  4. zpdDG4gta8XKpMCd commented on Mar 4, 2016

    @zpdDG4gta8XKpMCd
    Author

    Paul Jolly (@myitcv) Thanks for letting me know. We at our project stopped using TsLint for this exact reason, the rules can only validate the syntax, not the semantics. We developed a homemade rule runner that gets a type checker and runs against the a type checked code of the whole application rather than an AST of a single file.

    It would not be a problem to put such rule together.

  5. myitcv commented on Mar 5, 2016

    @myitcv

    Aleksey-Bykov in the absence of support for this kind of rule in tslint have you shared your custom rule runner?

  6. zpdDG4gta8XKpMCd commented on Mar 5, 2016

    @zpdDG4gta8XKpMCd
    Author

    I cannot share the real runner, but I can outline how to make one:

    import * as ts from 'typescript';
    import * as fs from 'fs';
    
    export function run(
        projectFileOrFolder: string
    ): void {
    
        const options: ts.CompilerOptions = {
            project: projectFileOrFolder
        };
    
        const host = tlb.toCompilerHost(options);
    
        const program = ts.createProgram(
            [],
            options,
            host
        );
    
        const emitted = program.emit();
        if (emitted.diagnostics.length > 0) { throw new Error('program has problems'); }
    
        const checker = program.getTypeChecker();
        const rootFileNames = program.getRootFileNames();
    
        rootFileNames.map(rootFileName => {
            const rootNode = program.getSourceFile(rootFileName);
            analyze(rootNode, checker);
        });
    }
    
    function analyze(rootNode: ts.Node, checker: ts.TypeChecker): void {
       // TODO: put your rule here
    }
    
    function toCompilerHost(
        options: ts.CompilerOptions
    ): ts.CompilerHost {
        return {
            readFile: (fileName: string): string => {
                return fs.readFileSync(fileName, 'utf8');
            },
            fileExists: (fileName: string): boolean => {
                return fs.existsSync(fileName);
            },
            getSourceFile: function (filepath: string) {
                try {
                    return ts.createSourceFile(
                        filepath,
                        fs.readFileSync(filepath, 'utf8'),
                        options.target,
                        true
                    );
                }
                catch (e) {
                    return undefined;
                }
            },
            writeFile: () => null,
            getDefaultLibFileName: () => 'lib.d.ts',
            useCaseSensitiveFileNames: () => true,
            getCanonicalFileName: (filename: string) => filename,
            getCurrentDirectory: () => '',
            getNewLine: () => '\n'
        };
    }
  7. zpdDG4gta8XKpMCd commented on Mar 5, 2016

    @zpdDG4gta8XKpMCd
    Author

    then comes the simplified rule

    function analyze(rootNode: ts.Node, checker: ts.TypeChecker): void {
        visitEachNode(rootNode, node => {
            if (node.kind === ts.SyntaxKind.IfStatement) {
               checkIfStatement(<ts.IfStatement>node, checker);
            } else if (node.kind === ts.SyntaxKind.ConditionalExpression) {
               checkConditionalExpression(<ts.ConditionalExpression>node, checker);
            }
        });   
    }
    
    function checkIfStatement(node: ts.IfStatement, checker: ts.TypeChecker): void {
        var type = checker.getTypeAtLocation(node.expression);
        if ((type.flags & ts.TypeFlags.Boolean) !== ts.TypeFlags.Boolean) {
            console.warn('if expression is not boolean');
        }
    }
    
    function checkConditionalExpression(node: ts.ConditionalExpression, checker: ts.TypeChecker): void {
        var type = checker.getTypeAtLocation(node.condition);
        if ((type.flags & ts.TypeFlags.Boolean) !== ts.TypeFlags.Boolean) {
            console.warn('if expression is not boolean');
        }
    }
    
    function visitEachNode(rootNode: ts.Node, visit: (node: ts.Node) => void): void {
        ts.forEachChild(rootNode, child => {
            visit(child);
            visitEachNode(child, visit);
        });
    }
  8. zpdDG4gta8XKpMCd commented on Mar 5, 2016

    @zpdDG4gta8XKpMCd
    Author
  9. myitcv commented on Mar 6, 2016

    @myitcv

    Aleksey-Bykov thanks for the pointers

  10. locked and limited conversation to collaborators on Jun 19, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Out of ScopeThis idea sits outside of the TypeScript language design constraintsSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions