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

rokucommunity / bslint / 26774958230

01 Jun 2026 06:48PM UTC coverage: 92.031% (-0.3%) from 92.308%
26774958230

Pull #96

github

web-flow
Merge 5e3b3f43f into 1dcfe326c
Pull Request #96: v1

1033 of 1172 branches covered (88.14%)

Branch coverage included in aggregate %.

302 of 312 new or added lines in 13 files covered. (96.79%)

14 existing lines in 2 files now uncovered.

1115 of 1162 relevant lines covered (95.96%)

85.4 hits per line

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

93.04
/src/plugins/trackCodeFlow/index.ts
1
import { BsDiagnostic, BrsFile, ProvideCodeActionsEvent, Statement, EmptyStatement, FunctionExpression, isForEachStatement, isForStatement, isIfStatement, isWhileStatement, createStackedVisitor, isBrsFile, isStatement, isExpression, WalkMode, isTryCatchStatement, isCatchStatement, CompilerPlugin, AfterValidateScopeEvent, AfterValidateFileEvent, util, isFunctionExpression, InternalWalkMode, isConditionalCompileStatement } 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
import { BsLintDiagnosticContext, BsLintScopeDiagnosticContext, BsLintScopeDiagnosticTag } from '../../Linter';
1✔
8
import type { Location } from 'vscode-languageserver-types'; // TODO: Get this from brighterscript
9

10
export interface NarrowingInfo {
11
    text: string;
12
    location: Location;
13
    type: 'valid' | 'invalid';
14
    block: Statement;
15
}
16

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

26
export enum VarRestriction {
1✔
27
    Iterator = 1,
1✔
28
    CatchedError = 2
1✔
29
}
30

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

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

56
export default class TrackCodeFlow implements CompilerPlugin {
1✔
57

58
    name = 'bslint-trackCodeFlow';
165✔
59

60
    constructor(private lintContext: PluginContext) {
165✔
61
    }
62

63
    provideCodeActions(event: ProvideCodeActionsEvent) {
64
        const addFixes = addFixesToEvent(event);
4✔
65
        extractFixes(event.file, addFixes, event.diagnostics);
4✔
66
    }
67

68
    afterValidateScope(event: AfterValidateScopeEvent) {
69
        const { scope } = event;
65✔
70
        event.program.diagnostics.clearByFilter({ tag: BsLintScopeDiagnosticTag, scope: scope });
65✔
71
        const callablesMap = util.getCallableContainersByLowerName(scope.getAllCallables());
65✔
72
        const diagnostics = runDeferredValidation(this.lintContext, scope, scope.getAllFiles(), callablesMap);
65✔
73
        event.program.diagnostics.register(diagnostics as any, {
65✔
74
            ...BsLintScopeDiagnosticContext,
75
            scope: scope
76
        });
77
    }
78

79
    afterValidateFile(event: AfterValidateFileEvent) {
80
        const { file } = event;
65✔
81
        if (!isBrsFile(file) || this.lintContext.ignores(file)) {
65✔
82
            return;
2✔
83
        }
84
        let diagnostics: BsDiagnostic[] = [];
63✔
85

86
        resetVarContext(file);
63✔
87

88
        for (const fun of file.parser.ast.findChildren<FunctionExpression>(isFunctionExpression, { walkMode: WalkMode.visitExpressionsRecursive })) {
63✔
89
            const state: LintState = {
175✔
90
                file: file,
91
                fun: fun,
92
                parent: undefined,
93
                stack: [],
94
                blocks: new WeakMap(),
95
                ifs: undefined,
96
                trys: undefined,
97
                branch: undefined,
98
                conditionalCompiles: undefined
99
            };
100
            let curr: StatementInfo = {
175✔
101
                stat: new EmptyStatement()
102
            };
103
            const returnLinter = createReturnLinter(this.lintContext, file, fun, state, diagnostics);
175✔
104
            const varLinter = createVarLinter(this.lintContext, file, fun, state, diagnostics);
175✔
105

106
            // 1. close
107
            // 2. visit -> curr
108
            // 3. open -> curr becomes parent
109
            const visitStatement = createStackedVisitor((stat: Statement, stack: Statement[]) => {
175✔
110
                state.stack = stack;
1,028✔
111
                curr = {
1,028✔
112
                    stat: stat,
113
                    parent: stack[stack.length - 1],
114
                    branches: isBranchedStatement(stat) ? 2 : 1
1,028✔
115
                };
116
                returnLinter.visitStatement(curr);
1,028✔
117
                varLinter.visitStatement(curr);
1,028✔
118

119
            }, (opened) => {
120
                state.blocks.set(opened, curr);
519✔
121
                varLinter.openBlock(curr);
519✔
122

123
                if (isIfStatement(opened)) {
519✔
124
                    state.ifs = curr;
81✔
125
                } else if (isConditionalCompileStatement(opened)) {
438✔
126
                    state.conditionalCompiles = curr;
14✔
127
                } else if (isTryCatchStatement(opened)) {
424✔
128
                    state.trys = curr;
3✔
129
                } else if (!curr.parent || isIfStatement(curr.parent) || isConditionalCompileStatement(curr.parent) || isTryCatchStatement(curr.parent) || isCatchStatement(curr.parent)) {
421✔
130
                    state.branch = curr;
300✔
131
                }
132
                state.parent = curr;
519✔
133
            }, (closed, stack) => {
134
                const block = state.blocks.get(closed);
247✔
135
                state.parent = state.blocks.get(stack[stack.length - 1]);
247✔
136
                if (isIfStatement(closed)) {
247✔
137
                    const { ifs, branch } = findIfBranch(state);
57✔
138
                    state.ifs = ifs;
57✔
139
                    state.branch = branch;
57✔
140
                } else if (isConditionalCompileStatement(closed)) {
190✔
141
                    const { conditionalCompiles, branch } = findConditionalCompileBranch(state);
11✔
142
                    state.conditionalCompiles = conditionalCompiles;
11✔
143
                    state.branch = branch;
11✔
144
                } else if (isTryCatchStatement(closed)) {
179✔
145
                    const { trys, branch } = findTryBranch(state);
2✔
146
                    state.trys = trys;
2✔
147
                    state.branch = branch;
2✔
148
                }
149
                if (block) {
247!
150
                    returnLinter.closeBlock(block);
247✔
151
                    varLinter.closeBlock(block);
247✔
152
                }
153
            });
154

155
            visitStatement(fun.body, undefined);
175✔
156

157
            if (fun.body.statements.length > 0) {
175✔
158
                /* eslint-disable no-bitwise */
159
                fun.body.walk((elem, parent) => {
168✔
160
                    // note: logic to ignore CommentStatement used as expression
161
                    if (isStatement(elem) && !isExpression(parent)) {
2,086✔
162
                        visitStatement(elem, parent);
853✔
163
                    } else if (parent) {
1,233!
164
                        varLinter.visitExpression(elem, parent, curr);
1,233✔
165
                    }
166
                }, { walkMode: WalkMode.visitStatements | WalkMode.visitExpressions | InternalWalkMode.visitFalseConditionalCompilationBlocks });
167
            } else {
168
                // ensure empty functions are finalized
169
                state.blocks.set(fun.body, curr);
7✔
170
                state.stack.push(fun.body);
7✔
171
            }
172

173
            // close remaining open blocks
174
            let remain = state.stack.length;
175✔
175
            while (remain-- > 0) {
175✔
176
                const last = state.stack.pop();
279✔
177
                if (!last) {
279!
178
                    continue;
×
179
                }
180
                const block = state.blocks.get(last);
279✔
181
                state.parent = remain > 0 ? state.blocks.get(state.stack[remain - 1]) : undefined;
279✔
182
                if (block) {
279!
183
                    returnLinter.closeBlock(block);
279✔
184
                    varLinter.closeBlock(block);
279✔
185
                }
186
            }
187
        }
188

189
        if (this.lintContext.fix) {
63✔
190
            diagnostics = extractFixes(event.file, this.lintContext.addFixes, diagnostics);
1✔
191
        }
192
        event.program.diagnostics.register(diagnostics, BsLintDiagnosticContext);
63✔
193
    }
194
}
195

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

213
// Find parent if and block where code flow is branched
214
function findConditionalCompileBranch(state: LintState): { conditionalCompiles?: StatementInfo; branch?: StatementInfo } {
215
    const { blocks, parent, stack } = state;
11✔
216
    for (let i = stack.length - 2; i >= 0; i--) {
11✔
217
        if (isConditionalCompileStatement(stack[i])) {
3!
NEW
218
            return {
×
219
                conditionalCompiles: blocks.get(stack[i]),
220
                branch: blocks.get(stack[i + 1])
221
            };
222
        }
223
    }
224
    return {
11✔
225
        conditionalCompiles: undefined,
226
        branch: parent
227
    };
228
}
229

230
// Find parent try and block where code flow is branched
231
function findTryBranch(state: LintState): { trys?: StatementInfo; branch?: StatementInfo } {
232
    const { blocks, parent, stack } = state;
2✔
233
    for (let i = stack.length - 2; i >= 0; i--) {
2✔
234
        if (isTryCatchStatement(stack[i])) {
×
235
            return {
×
236
                trys: blocks.get(stack[i]),
237
                branch: blocks.get(stack[i + 1])
238
            };
239
        }
240
    }
241
    return {
2✔
242
        trys: undefined,
243
        branch: parent
244
    };
245
}
246

247
// `if` and `for/while` are considered as multi-branch
248
export function isBranchedStatement(stat: Statement) {
1✔
249
    return isIfStatement(stat) || isForStatement(stat) || isForEachStatement(stat) || isWhileStatement(stat) || isTryCatchStatement(stat) || isConditionalCompileStatement(stat);
1,028✔
250
}
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

© 2026 Coveralls, Inc