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

rokucommunity / bslint / #882

16 May 2024 05:26PM CUT coverage: 92.085% (+0.2%) from 91.908%
#882

Pull #96

TwitchBronBron
1.0.0-alpha.30
Pull Request #96: v1

764 of 862 branches covered (88.63%)

Branch coverage included in aggregate %.

94 of 95 new or added lines in 10 files covered. (98.95%)

19 existing lines in 5 files now uncovered.

923 of 970 relevant lines covered (95.15%)

65.18 hits per line

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

94.55
/src/plugins/trackCodeFlow/returnTracking.ts
1
import { BscFile, FunctionExpression, BsDiagnostic, DiagnosticTag, isReturnStatement, isIfStatement, isThrowStatement, TokenKind, util, ReturnStatement, ThrowStatement, isTryCatchStatement, isCatchStatement, isVoidType, SymbolTypeFlag } 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;
129✔
32
    const returns: ReturnInfo[] = [];
129✔
33
    const throws: ThrowInfo[] = [];
129✔
34

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

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

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

98
        // Explicit `as void` or `sub` without return type should never return a value
99
        const returnType = fun.returnTypeExpression?.getType({ flags: SymbolTypeFlag.typetime });
129✔
100

101
        if (
129✔
102
            isVoidType(returnType) ||
361✔
103
            (kind === 'Sub' && !fun.returnTypeExpression)
104
        ) {
105
            if (hasReturnedValue) {
101✔
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;
101✔
117
        }
118

119
        const requiresReturnValue =
120
            !!fun.returnTypeExpression ||
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 {
129✔
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