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

rokucommunity / bslint / #882

16 May 2024 05:26PM UTC coverage: 92.085% (+0.2%) from 91.908%
#882

Pull #96

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

764 of 862 branches covered (88.63%)

Branch coverage included in aggregate %.

94 of 95 new or added lines in 10 files covered. (98.95%)

19 existing lines in 5 files now uncovered.

923 of 970 relevant lines covered (95.15%)

65.18 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, Range, 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

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

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

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

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

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

54
export default class TrackCodeFlow implements CompilerPlugin {
1✔
55

56
    name: 'trackCodeFlow';
57

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

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

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

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

83
        resetVarContext(file);
36✔
84

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

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

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

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

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

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

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

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

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

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

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