• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rokucommunity / bslint / 38941741350

16 Oct 2026 05:42PM UTC coverage: 92.217% (-0.2%) from 92.377%
38941741350

Pull #96

github

web-flow
Merge 97b66f59b into ed0c89795
Pull Request #96: v1

1071 of 1214 branches covered (88.22%)

Branch coverage included in aggregate %.

370 of 381 new or added lines in 13 files covered. (97.11%)

1192 of 1240 relevant lines covered (96.13%)

89.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.96
/src/plugins/trackCodeFlow/returnTracking.ts
1
import { BscFile, FunctionExpression, BsDiagnostic, isReturnStatement, isIfStatement, isThrowStatement, TokenKind, util, ReturnStatement, ThrowStatement, isTryCatchStatement, isCatchStatement, isVoidType, SymbolTypeFlag, isConditionalCompileStatement } from 'brighterscript';
1 ✔
2
import { LintState, StatementInfo } from '.';
3
import { PluginContext } from '../../util';
4

5
interface ReturnInfo {
6
    stat: ReturnStatement;
7
    hasValue: boolean;
8
}
9

10
interface ThrowInfo {
11
    stat: ThrowStatement;
12
}
13

14
enum ReturnLintError {
1 ✔
15
    ReturnValueUnexpected = 'return-value-found',
1 ✔
16
    ReturnValueExpected = 'missing-return-value',
1 ✔
17
    UnsafeReturnValue = 'unsafe-return-value',
1 ✔
18
    ReturnValueRequired = 'return-value-required',
1 ✔
19
    ReturnValueMissing = 'missing-return-value',
1 ✔
20
    LastReturnValueMissing = 'missing-last-return-value'
1 ✔
21
}
22

23
enum ReturnLintLegacyError {
1 ✔
24
    ReturnValueUnexpected = 'LINT2002',
1 ✔
25
    ReturnValueExpected = 'LINT2003',
1 ✔
26
    UnsafeReturnValue = 'LINT2004',
1 ✔
27
    ReturnValueRequired = 'LINT2005',
1 ✔
28
    ReturnValueMissing = 'LINT2006',
1 ✔
29
    LastReturnValueMissing = 'LINT2007'
1 ✔
30
}
31

32
export function createReturnLinter(
1 ✔
33
    lintContext: PluginContext,
34
    file: BscFile,
35
    fun: FunctionExpression,
36
    state: LintState,
37
    diagnostics: BsDiagnostic[]
38
) {
39
    const { severity } = lintContext;
199 ✔
40
    const returns: ReturnInfo[] = [];
199 ✔
41
    const throws: ThrowInfo[] = [];
199 ✔
42

43
    function visitStatement(curr: StatementInfo) {
44
        if (state.parent?.returns) {
1,189 !
NEW
45
            return;
×
46
        }
47
        if (isReturnStatement(curr.stat)) {
1,189 ✔
48
            const { ifs, conditionalCompiles, trys, branch, parent } = state;
58 ✔
49
            returns.push({
58 ✔
50
                stat: curr.stat,
51
                hasValue: !!curr.stat.value
52
            });
53
            // flag parent branch to return
54
            const returnBlock = (ifs || conditionalCompiles || trys) ? branch : parent;
58 ✔
55
            if (returnBlock && parent?.branches === 1) {
58 !
56
                returnBlock.returns = true;
58 ✔
57
            }
58
        } else if (isThrowStatement(curr.stat)) {
1,131 ✔
59
            const { ifs, conditionalCompiles, trys, branch, parent } = state;
7 ✔
60
            throws.push({ stat: curr.stat });
7 ✔
61
            // flag parent branch to 'return'
62
            const returnBlock = (ifs || conditionalCompiles || trys) ? branch : parent;
7 ✔
63
            if (returnBlock && parent?.branches === 1) {
7 !
64
                returnBlock.returns = true;
7 ✔
65
            }
66
        }
67
    }
68

69
    function closeBlock(closed: StatementInfo) {
70
        const { parent } = state;
632 ✔
71
        if (!parent) {
632 ✔
72
            finalize(closed);
199 ✔
73
        } else if (isIfStatement(closed.stat) || isConditionalCompileStatement(closed.stat) || isTryCatchStatement(closed.stat) || isCatchStatement(closed.stat)) {
433 ✔
74
            if (closed.branches === 0) {
130 ✔
75
                parent.returns = true;
8 ✔
76
                parent.branches--;
8 ✔
77
            }
78
        } else if (closed.returns) {
303 ✔
79
            if (isIfStatement(parent.stat)) {
28 ✔
80
                parent.branches--;
19 ✔
81
            } else if (isConditionalCompileStatement(parent.stat)) {
9 ✔
82
                parent.branches--;
6 ✔
83
            } else if (isTryCatchStatement(parent.stat)) {
3 ✔
84
                parent.branches--;
1 ✔
85
            } else if (isCatchStatement(parent.stat)) {
2 ✔
86
                parent.branches--;
1 ✔
87
            }
88
        }
89
    }
90

91
    function finalize(last: StatementInfo) {
92
        const { consistentReturn } = severity;
199 ✔
93
        const kind = fun.tokens.functionType?.kind === TokenKind.Sub ? 'Sub' : 'Function';
199 !
94
        const returnedValues = returns.filter((r) => r.hasValue);
199 ✔
95
        const hasReturnedValue = returnedValues.length > 0;
199 ✔
96
        // Function range only includes the function signature
97
        const funRangeStart = (fun.tokens.functionType ?? fun.tokens.leftParen).location.range.start;
199 !
98
        const funRangeEnd = (fun.returnTypeExpression ?? fun.tokens.rightParen).location.range.end;
199 ✔
99
        const funRange = util.createRangeFromPositions(funRangeStart, funRangeEnd);
199 ✔
100
        const funLocation = util.createLocationFromRange(fun.location.uri, funRange);
199 ✔
101
        // Explicit `as void` or `sub` without return type should never return a value
102
        const returnType = fun.returnTypeExpression?.getType({ flags: SymbolTypeFlag.typetime });
199 ✔
103

104
        if (
199 ✔
105
            isVoidType(returnType) ||
548 ✔
106
            (kind === 'Sub' && !fun.returnTypeExpression)
107
        ) {
108
            if (hasReturnedValue) {
148 ✔
109
                returnedValues.forEach((r) => {
2 ✔
110
                    diagnostics.push({
2 ✔
111
                        severity: consistentReturn,
112
                        code: ReturnLintError.ReturnValueUnexpected,
113
                        legacyCode: ReturnLintLegacyError.ReturnValueUnexpected,
114
                        message: `${kind} as void should not return a value`,
115
                        location: r.stat?.location ?? funLocation
12 !
116
                    });
117
                });
118
            }
119
            return;
148 ✔
120
        }
121

122
        const requiresReturnValue =
123
            !!fun.returnTypeExpression ||
51 ✔
124
            returnedValues.length > 0 ||
125
            (kind === 'Function' && returns.length > 0);
126
        const missingValue = requiresReturnValue && returnedValues.length !== returns.length;
51 ✔
127
        const missingBranches = !last.returns;
51 ✔
128

129
        // Function doesn't consistently return,
130
        // or doesn't return at all but has an explicit type
131
        if ((requiresReturnValue || hasReturnedValue) && (missingBranches || (returns.length + throws.length) === 0)) {
51 ✔
132
            diagnostics.push({
7 ✔
133
                severity: consistentReturn,
134
                code: ReturnLintError.UnsafeReturnValue,
135
                legacyCode: ReturnLintLegacyError.ReturnValueRequired,
136
                message: 'Not all code paths return a value',
137
                location: funLocation
138
            });
139
        }
140

141
        // Some return don't have a value
142
        if (missingValue) {
51 ✔
143
            returns
2 ✔
144
                .filter((r) => !r.hasValue)
2 ✔
145
                .forEach((r) => {
146
                    diagnostics.push({
2 ✔
147
                        severity: consistentReturn,
148
                        code: ReturnLintError.ReturnValueMissing,
149
                        legacyCode: ReturnLintLegacyError.ReturnValueMissing,
150
                        message: `${kind} should consistently return a value`,
151
                        location: r.stat.location || funLocation
2 !
152
                    });
153
                });
154
        }
155
    }
156

157
    return {
199 ✔
158
        closeBlock: closeBlock,
159
        visitStatement: visitStatement
160
    };
161
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc