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

rokucommunity / bslint / #901

21 Jun 2024 12:02PM UTC coverage: 91.851% (-0.06%) from 91.908%
#901

Pull #96

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

779 of 882 branches covered (88.32%)

Branch coverage included in aggregate %.

113 of 114 new or added lines in 10 files covered. (99.12%)

19 existing lines in 6 files now uncovered.

923 of 971 relevant lines covered (95.06%)

65.57 hits per line

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

90.51
/src/plugins/trackCodeFlow/index.ts
1
import { BsDiagnostic, BrsFile, OnGetCodeActionsEvent, Statement, EmptyStatement, FunctionExpression, isForEachStatement, isForStatement, isIfStatement, isWhileStatement, createStackedVisitor, isBrsFile, isStatement, isExpression, WalkMode, isTryCatchStatement, isCatchStatement, CompilerPlugin, AfterScopeValidateEvent, AfterFileValidateEvent, util, isFunctionExpression } 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 } 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
}
54

55
export default class TrackCodeFlow implements CompilerPlugin {
1✔
56

57
    name: 'trackCodeFlow';
58

59
    constructor(private lintContext: PluginContext) {
110✔
60
    }
61

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

67
    afterScopeValidate(event: AfterScopeValidateEvent) {
68
        const { scope } = event;
36✔
69
        const callablesMap = util.getCallableContainersByLowerName(scope.getAllCallables());
36✔
70
        const diagnostics = runDeferredValidation(this.lintContext, scope, scope.getAllFiles(), callablesMap);
36✔
71
        event.program.diagnostics.register(diagnostics as any, {
36✔
72
            ...BsLintDiagnosticContext,
73
            scope: scope
74
        });
75
    }
76

77
    afterFileValidate(event: AfterFileValidateEvent) {
78
        const { file } = event;
36✔
79
        if (!isBrsFile(file) || this.lintContext.ignores(file)) {
36!
80
            return;
×
81
        }
82
        let diagnostics: BsDiagnostic[] = [];
36✔
83

84
        resetVarContext(file);
36✔
85

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

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

116
            }, (opened) => {
117
                state.blocks.set(opened, curr);
345✔
118
                varLinter.openBlock(curr);
345✔
119

120
                if (isIfStatement(opened)) {
345✔
121
                    state.ifs = curr;
78✔
122
                } else if (isTryCatchStatement(opened)) {
267✔
123
                    state.trys = curr;
3✔
124
                } else if (!curr.parent || isIfStatement(curr.parent) || isTryCatchStatement(curr.parent) || isCatchStatement(curr.parent)) {
264✔
125
                    state.branch = curr;
233✔
126
                }
127
                state.parent = curr;
345✔
128
            }, (closed, stack) => {
129
                const block = state.blocks.get(closed);
167✔
130
                state.parent = state.blocks.get(stack[stack.length - 1]);
167✔
131
                if (isIfStatement(closed)) {
167✔
132
                    const { ifs, branch } = findIfBranch(state);
56✔
133
                    state.ifs = ifs;
56✔
134
                    state.branch = branch;
56✔
135
                } else if (isTryCatchStatement(closed)) {
111✔
136
                    const { trys, branch } = findTryBranch(state);
2✔
137
                    state.trys = trys;
2✔
138
                    state.branch = branch;
2✔
139
                }
140
                if (block) {
167!
141
                    returnLinter.closeBlock(block);
167✔
142
                    varLinter.closeBlock(block);
167✔
143
                }
144
            });
145

146
            visitStatement(fun.body, undefined);
129✔
147

148
            if (fun.body.statements.length > 0) {
129✔
149
                /* eslint-disable no-bitwise */
150
                fun.body.walk((elem, parent) => {
124✔
151
                    // note: logic to ignore CommentStatement used as expression
152
                    if (isStatement(elem) && !isExpression(parent)) {
1,477✔
153
                        visitStatement(elem, parent);
593✔
154
                    } else if (parent) {
884!
155
                        varLinter.visitExpression(elem, parent, curr);
884✔
156
                    }
157
                }, { walkMode: WalkMode.visitStatements | WalkMode.visitExpressions });
158
            } else {
159
                // ensure empty functions are finalized
160
                state.blocks.set(fun.body, curr);
5✔
161
                state.stack.push(fun.body);
5✔
162
            }
163

164
            // close remaining open blocks
165
            let remain = state.stack.length;
129✔
166
            while (remain-- > 0) {
129✔
167
                const last = state.stack.pop();
183✔
168
                if (!last) {
183!
UNCOV
169
                    continue;
×
170
                }
171
                const block = state.blocks.get(last);
183✔
172
                state.parent = remain > 0 ? state.blocks.get(state.stack[remain - 1]) : undefined;
183✔
173
                if (block) {
183!
174
                    returnLinter.closeBlock(block);
183✔
175
                    varLinter.closeBlock(block);
183✔
176
                }
177
            }
178
        }
179

180
        if (this.lintContext.fix) {
36✔
181
            diagnostics = extractFixes(this.lintContext.addFixes, diagnostics);
1✔
182
        }
183
        event.program.diagnostics.register(diagnostics, BsLintDiagnosticContext);
36✔
184
    }
185
}
186

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

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

221
// `if` and `for/while` are considered as multi-branch
222
export function isBranchedStatement(stat: Statement) {
1✔
223
    return isIfStatement(stat) || isForStatement(stat) || isForEachStatement(stat) || isWhileStatement(stat) || isTryCatchStatement(stat);
722✔
224
}
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