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

rokucommunity / bslint / #1039

03 Oct 2024 07:42PM CUT coverage: 91.231% (-0.5%) from 91.746%
#1039

Pull #96

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

927 of 1061 branches covered (87.37%)

Branch coverage included in aggregate %.

231 of 240 new or added lines in 12 files covered. (96.25%)

11 existing lines in 3 files now uncovered.

1008 of 1060 relevant lines covered (95.09%)

68.84 hits per line

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

94.86
/src/plugins/trackCodeFlow/returnTracking.ts
1
import { BscFile, FunctionExpression, BsDiagnostic, DiagnosticTag, 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
    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;
145✔
32
    const returns: ReturnInfo[] = [];
145✔
33
    const throws: ThrowInfo[] = [];
145✔
34

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

67
    function closeBlock(closed: StatementInfo) {
68
        const { parent } = state;
400✔
69
        if (!parent) {
400✔
70
            finalize(closed);
145✔
71
        } else if (isIfStatement(closed.stat) || isConditionalCompileStatement(closed.stat) || isTryCatchStatement(closed.stat) || isCatchStatement(closed.stat)) {
255✔
72
            if (closed.branches === 0) {
98✔
73
                parent.returns = true;
13✔
74
                parent.branches--;
13✔
75
            }
76
        } else if (closed.returns) {
157✔
77
            if (isIfStatement(parent.stat)) {
44✔
78
                parent.branches--;
36✔
79
            } else if (isConditionalCompileStatement(parent.stat)) {
8✔
80
                parent.branches--;
5✔
81
            } else if (isTryCatchStatement(parent.stat)) {
3✔
82
                parent.branches--;
1✔
83
            } else if (isCatchStatement(parent.stat)) {
2✔
84
                parent.branches--;
1✔
85
            }
86
        }
87
    }
88

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

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

119
        const requiresReturnValue =
120
            !!fun.returnTypeExpression ||
40✔
121
            returnedValues.length > 0 ||
122
            (kind === 'Function' && returns.length > 0);
123
        const missingValue = requiresReturnValue && returnedValues.length !== returns.length;
40✔
124
        const missingBranches = !last.returns;
40✔
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)) {
40✔
129
            diagnostics.push({
7✔
130
                severity: consistentReturn,
131
                code: ReturnLintError.UnsafeReturnValue,
132
                message: 'Not all code paths return a value',
133
                location: funLocation
134
            });
135
        }
136

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

152
    return {
145✔
153
        closeBlock: closeBlock,
154
        visitStatement: visitStatement
155
    };
156
}
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