• 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

90.23
/src/plugins/trackCodeFlow/index.ts
1
import { BscFile, Scope, BsDiagnostic, CallableContainerMap, BrsFile, OnGetCodeActionsEvent, Statement, EmptyStatement, FunctionExpression, isForEachStatement, isForStatement, isIfStatement, isWhileStatement, Range, createStackedVisitor, isBrsFile, isStatement, isExpression, WalkMode, isTryCatchStatement, isCatchStatement } from 'brighterscript';
1✔
2
import { PluginContext } from '../../util';
3
import { createReturnLinter } from './returnTracking';
1✔
4
import { createVarLinter, resetVarContext, runDeferredValidation } from './varTracking';
1✔
5
import { extractFixes } from './trackFixes';
1✔
6
import { addFixesToEvent } from '../../textEdit';
1✔
7

8
export interface NarrowingInfo {
9
    text: string;
10
    range: Range;
11
    type: 'valid' | 'invalid';
12
    block: Statement;
13
}
14

15
export interface StatementInfo {
16
    stat: Statement;
17
    parent?: Statement;
18
    locals?: Map<string, VarInfo>;
19
    branches?: number;
20
    returns?: boolean;
21
    narrows?: NarrowingInfo[];
22
}
23

24
export enum VarRestriction {
1✔
25
    Iterator = 1,
1✔
26
    CatchedError = 2
1✔
27
}
28

29
export interface VarInfo {
30
    name: string;
31
    range: Range;
32
    isGlobal?: boolean;
33
    isParam?: boolean;
34
    isUnsafe: boolean;
35
    restriction?: VarRestriction;
36
    parent?: StatementInfo;
37
    metBranches?: number;
38
    narrowed?: NarrowingInfo;
39
    isUsed: boolean;
40
}
41

42
export interface LintState {
43
    file: BrsFile;
44
    fun: FunctionExpression;
45
    parent?: StatementInfo;
46
    stack: Statement[];
47
    blocks: WeakMap<Statement, StatementInfo>;
48
    ifs?: StatementInfo;
49
    trys?: StatementInfo;
50
    branch?: StatementInfo;
51
}
52

53
export default class TrackCodeFlow {
1✔
54

55
    name: 'trackCodeFlow';
56

57
    constructor(private lintContext: PluginContext) {
110✔
58
    }
59

60
    onGetCodeActions(event: OnGetCodeActionsEvent) {
61
        const addFixes = addFixesToEvent(event);
×
62
        extractFixes(addFixes, event.diagnostics);
×
63
    }
64

65
    afterScopeValidate(scope: Scope, files: BscFile[], callables: CallableContainerMap) {
66
        const diagnostics = runDeferredValidation(this.lintContext, scope, files, callables);
36✔
67
        scope.addDiagnostics(diagnostics);
36✔
68
    }
69

70
    afterFileValidate(file: BscFile) {
71
        if (!isBrsFile(file) || this.lintContext.ignores(file)) {
36!
72
            return;
×
73
        }
74
        let diagnostics: BsDiagnostic[] = [];
36✔
75

76
        resetVarContext(file);
36✔
77

78
        file.parser.references.functionExpressions.forEach((fun) => {
36✔
79
            const state: LintState = {
128✔
80
                file: file,
81
                fun: fun,
82
                parent: undefined,
83
                stack: [],
84
                blocks: new WeakMap(),
85
                ifs: undefined,
86
                trys: undefined,
87
                branch: undefined
88
            };
89
            let curr: StatementInfo = {
128✔
90
                stat: new EmptyStatement()
91
            };
92
            const returnLinter = createReturnLinter(this.lintContext, file, fun, state, diagnostics);
128✔
93
            const varLinter = createVarLinter(this.lintContext, file, fun, state, diagnostics);
128✔
94

95
            // 1. close
96
            // 2. visit -> curr
97
            // 3. open -> curr becomes parent
98
            const visitStatement = createStackedVisitor((stat: Statement, stack: Statement[]) => {
128✔
99
                state.stack = stack;
793✔
100
                curr = {
793✔
101
                    stat: stat,
102
                    parent: stack[stack.length - 1],
103
                    branches: isBranchedStatement(stat) ? 2 : 1
793✔
104
                };
105
                returnLinter.visitStatement(curr);
793✔
106
                varLinter.visitStatement(curr);
793✔
107

108
            }, (opened) => {
109
                state.blocks.set(opened, curr);
348✔
110
                varLinter.openBlock(curr);
348✔
111

112
                if (isIfStatement(opened)) {
348✔
113
                    state.ifs = curr;
78✔
114
                } else if (isTryCatchStatement(opened)) {
270✔
115
                    state.trys = curr;
3✔
116
                } else if (!curr.parent || isIfStatement(curr.parent) || isTryCatchStatement(curr.parent) || isCatchStatement(curr.parent)) {
267✔
117
                    state.branch = curr;
236✔
118
                }
119
                state.parent = curr;
348✔
120
            }, (closed, stack) => {
121
                const block = state.blocks.get(closed);
167✔
122
                state.parent = state.blocks.get(stack[stack.length - 1]);
167✔
123
                if (isIfStatement(closed)) {
167✔
124
                    const { ifs, branch } = findIfBranch(state);
56✔
125
                    state.ifs = ifs;
56✔
126
                    state.branch = branch;
56✔
127
                } else if (isTryCatchStatement(closed)) {
111✔
128
                    const { trys, branch } = findTryBranch(state);
2✔
129
                    state.trys = trys;
2✔
130
                    state.branch = branch;
2✔
131
                }
132
                if (block) {
167!
133
                    returnLinter.closeBlock(block);
167✔
134
                    varLinter.closeBlock(block);
167✔
135
                }
136
            });
137

138
            visitStatement(fun.body, undefined);
128✔
139

140
            if (fun.body.statements.length > 0) {
128✔
141
                /* eslint-disable no-bitwise */
142
                fun.body.walk((elem, parent) => {
126✔
143
                    // note: logic to ignore CommentStatement used as expression
144
                    if (isStatement(elem) && !isExpression(parent)) {
1,554✔
145
                        visitStatement(elem, parent);
665✔
146
                    } else if (parent) {
889!
147
                        varLinter.visitExpression(elem, parent, curr);
889✔
148
                    }
149
                }, { walkMode: WalkMode.visitStatements | WalkMode.visitExpressions });
150
            } else {
151
                // ensure empty functions are finalized
152
                state.blocks.set(fun.body, curr);
2✔
153
                state.stack.push(fun.body);
2✔
154
            }
155

156
            // close remaining open blocks
157
            let remain = state.stack.length;
128✔
158
            while (remain-- > 0) {
128✔
159
                const last = state.stack.pop();
183✔
160
                if (!last) {
183!
161
                    continue;
×
162
                }
163
                const block = state.blocks.get(last);
183✔
164
                state.parent = remain > 0 ? state.blocks.get(state.stack[remain - 1]) : undefined;
183✔
165
                if (block) {
183!
166
                    returnLinter.closeBlock(block);
183✔
167
                    varLinter.closeBlock(block);
183✔
168
                }
169
            }
170
        });
171

172
        if (this.lintContext.fix) {
36✔
173
            diagnostics = extractFixes(this.lintContext.addFixes, diagnostics);
1✔
174
        }
175

176
        file.addDiagnostics(diagnostics);
36✔
177
    }
178
}
179

180
// Find parent if and block where code flow is branched
181
function findIfBranch(state: LintState): { ifs?: StatementInfo; branch?: StatementInfo } {
182
    const { blocks, parent, stack } = state;
56✔
183
    for (let i = stack.length - 2; i >= 0; i--) {
56✔
184
        if (isIfStatement(stack[i])) {
24✔
185
            return {
12✔
186
                ifs: blocks.get(stack[i]),
187
                branch: blocks.get(stack[i + 1])
188
            };
189
        }
190
    }
191
    return {
44✔
192
        ifs: undefined,
193
        branch: parent
194
    };
195
}
196

197
// Find parent try and block where code flow is branched
198
function findTryBranch(state: LintState): { trys?: StatementInfo; branch?: StatementInfo } {
199
    const { blocks, parent, stack } = state;
2✔
200
    for (let i = stack.length - 2; i >= 0; i--) {
2✔
201
        if (isTryCatchStatement(stack[i])) {
×
202
            return {
×
203
                trys: blocks.get(stack[i]),
204
                branch: blocks.get(stack[i + 1])
205
            };
206
        }
207
    }
208
    return {
2✔
209
        trys: undefined,
210
        branch: parent
211
    };
212
}
213

214
// `if` and `for/while` are considered as multi-branch
215
export function isBranchedStatement(stat: Statement) {
1✔
216
    return isIfStatement(stat) || isForStatement(stat) || isForEachStatement(stat) || isWhileStatement(stat) || isTryCatchStatement(stat);
793✔
217
}
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