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

rokucommunity / bslint / #769

07 Dec 2023 06:32PM CUT coverage: 91.908%. Remained the same
#769

push

TwitchBronBron
0.8.13

775 of 875 branches covered (0.0%)

Branch coverage included in aggregate %.

906 of 954 relevant lines covered (94.97%)

63.17 hits per line

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

94.67
/src/plugins/trackCodeFlow/returnTracking.ts
1
import { BscFile, FunctionExpression, BsDiagnostic, isCommentStatement, DiagnosticTag, isReturnStatement, isIfStatement, isThrowStatement, TokenKind, util, ReturnStatement, ThrowStatement, isTryCatchStatement, isCatchStatement } 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
    UnreachableCode = 'LINT2001',
1✔
16
    ReturnValueUnexpected = 'LINT2002',
1✔
17
    ReturnValueExpected = 'LINT2003',
1✔
18
    UnsafeReturnValue = 'LINT2004',
1✔
19
    ReturnValueRequired = 'LINT2005',
1✔
20
    ReturnValueMissing = 'LINT2006',
1✔
21
    LastReturnValueMissing = 'LINT2007'
1✔
22
}
23

24
export function createReturnLinter(
1✔
25
    lintContext: PluginContext,
26
    file: BscFile,
27
    fun: FunctionExpression,
28
    state: LintState,
29
    diagnostics: BsDiagnostic[]
30
) {
31
    const { severity } = lintContext;
128✔
32
    const returns: ReturnInfo[] = [];
128✔
33
    const throws: ThrowInfo[] = [];
128✔
34

35
    function visitStatement(curr: StatementInfo) {
36
        const { parent } = state;
793✔
37
        if (parent?.returns) {
793✔
38
            if (!isCommentStatement(curr.stat)) {
16✔
39
                diagnostics.push({
5✔
40
                    severity: severity.unreachableCode,
41
                    code: ReturnLintError.UnreachableCode,
42
                    message: 'Unreachable code',
43
                    range: curr.stat.range,
44
                    file: file,
45
                    tags: [DiagnosticTag.Unnecessary]
46
                });
47
            }
48
        } else if (isReturnStatement(curr.stat)) {
777✔
49
            const { ifs, trys, branch, parent } = state;
50✔
50
            returns.push({
50✔
51
                stat: curr.stat,
52
                hasValue: !!curr.stat.value && !isCommentStatement(curr.stat.value)
72✔
53
            });
54
            // flag parent branch to return
55
            const returnBlock = (ifs || trys) ? branch : parent;
50✔
56
            if (returnBlock && parent?.branches === 1) {
50!
57
                returnBlock.returns = true;
50✔
58
            }
59
        } else if (isThrowStatement(curr.stat)) {
727✔
60
            const { ifs, trys, branch, parent } = state;
6✔
61
            throws.push({ stat: curr.stat });
6✔
62
            // flag parent branch to 'return'
63
            const returnBlock = (ifs || trys) ? branch : parent;
6✔
64
            if (returnBlock && parent?.branches === 1) {
6!
65
                returnBlock.returns = true;
6✔
66
            }
67
        }
68
    }
69

70
    function closeBlock(closed: StatementInfo) {
71
        const { parent } = state;
350✔
72
        if (!parent) {
350✔
73
            finalize(closed);
128✔
74
        } else if (isIfStatement(closed.stat) || isTryCatchStatement(closed.stat) || isCatchStatement(closed.stat)) {
222✔
75
            if (closed.branches === 0) {
84✔
76
                parent.returns = true;
11✔
77
                parent.branches--;
11✔
78
            }
79
        } else if (closed.returns) {
138✔
80
            if (isIfStatement(parent.stat)) {
39✔
81
                parent.branches--;
36✔
82
            } else if (isTryCatchStatement(parent.stat)) {
3✔
83
                parent.branches--;
1✔
84
            } else if (isCatchStatement(parent.stat)) {
2✔
85
                parent.branches--;
1✔
86
            }
87
        }
88
    }
89

90
    function finalize(last: StatementInfo) {
91
        const { consistentReturn } = severity;
128✔
92
        const kind = fun.functionType?.kind === TokenKind.Sub ? 'Sub' : 'Function';
128!
93
        const returnedValues = returns.filter((r) => r.hasValue);
128✔
94
        const hasReturnedValue = returnedValues.length > 0;
128✔
95
        // Function range only includes the function signature
96
        const funRangeStart = (fun.functionType ?? fun.leftParen).range.start;
128!
97
        const funRangeEnd = (fun.returnTypeToken ?? fun.rightParen).range.end;
128✔
98
        const funRange = util.createRangeFromPositions(funRangeStart, funRangeEnd);
128✔
99

100
        // Explicit `as void` or `sub` without return type should never return a value
101
        if (
128✔
102
            fun.returnTypeToken?.kind === TokenKind.Void ||
742✔
103
            (kind === 'Sub' && !fun.returnTypeToken)
104
        ) {
105
            if (hasReturnedValue) {
100✔
106
                returnedValues.forEach((r) => {
2✔
107
                    diagnostics.push({
2✔
108
                        severity: consistentReturn,
109
                        code: ReturnLintError.ReturnValueUnexpected,
110
                        message: `${kind} as void should not return a value`,
111
                        range: r.stat?.range || funRange,
8!
112
                        file: file
113
                    });
114
                });
115
            }
116
            return;
100✔
117
        }
118

119
        const requiresReturnValue =
120
            !!fun.returnTypeToken ||
28✔
121
            returnedValues.length > 0 ||
122
            (kind === 'Function' && returns.length > 0);
123
        const missingValue = requiresReturnValue && returnedValues.length !== returns.length;
28✔
124
        const missingBranches = !last.returns;
28✔
125

126
        // Function doesn't consistently return,
127
        // or doesn't return at all but has an explicit type
128
        if ((requiresReturnValue || hasReturnedValue) && (missingBranches || (returns.length + throws.length) === 0)) {
28✔
129
            diagnostics.push({
6✔
130
                severity: consistentReturn,
131
                code: ReturnLintError.UnsafeReturnValue,
132
                message: 'Not all code paths return a value',
133
                range: funRange,
134
                file: file
135
            });
136
        }
137

138
        // Some return don't have a value
139
        if (missingValue) {
28✔
140
            returns
2✔
141
                .filter((r) => !r.hasValue)
2✔
142
                .forEach((r) => {
143
                    diagnostics.push({
2✔
144
                        severity: consistentReturn,
145
                        code: ReturnLintError.ReturnValueMissing,
146
                        message: `${kind} should consistently return a value`,
147
                        range: r.stat.range || funRange,
2!
148
                        file: file
149
                    });
150
                });
151
        }
152
    }
153

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

© 2025 Coveralls, Inc