• 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

90.38
/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, 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 } 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: 'trackCodeFlow';
59

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

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

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

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

85
        resetVarContext(file);
39✔
86

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

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

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

122
                if (isIfStatement(opened)) {
393✔
123
                    state.ifs = curr;
78✔
124
                } else if (isConditionalCompileStatement(opened)) {
315✔
125
                    state.conditionalCompiles = curr;
14✔
126
                } else if (isTryCatchStatement(opened)) {
301✔
127
                    state.trys = curr;
3✔
128
                } else if (!curr.parent || isIfStatement(curr.parent) || isConditionalCompileStatement(curr.parent) || isTryCatchStatement(curr.parent) || isCatchStatement(curr.parent)) {
298✔
129
                    state.branch = curr;
267✔
130
                }
131
                state.parent = curr;
393✔
132
            }, (closed, stack) => {
133
                const block = state.blocks.get(closed);
195✔
134
                state.parent = state.blocks.get(stack[stack.length - 1]);
195✔
135
                if (isIfStatement(closed)) {
195✔
136
                    const { ifs, branch } = findIfBranch(state);
56✔
137
                    state.ifs = ifs;
56✔
138
                    state.branch = branch;
56✔
139
                } else if (isConditionalCompileStatement(closed)) {
139✔
140
                    const { conditionalCompiles, branch } = findConditionalCompileBranch(state);
11✔
141
                    state.conditionalCompiles = conditionalCompiles;
11✔
142
                    state.branch = branch;
11✔
143
                } else if (isTryCatchStatement(closed)) {
128✔
144
                    const { trys, branch } = findTryBranch(state);
2✔
145
                    state.trys = trys;
2✔
146
                    state.branch = branch;
2✔
147
                }
148
                if (block) {
195!
149
                    returnLinter.closeBlock(block);
195✔
150
                    varLinter.closeBlock(block);
195✔
151
                }
152
            });
153

154
            visitStatement(fun.body, undefined);
145✔
155

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

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

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

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

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

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

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