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

rokucommunity / brighterscript / #14363

08 May 2025 05:50PM UTC coverage: 89.017%. Remained the same
#14363

push

web-flow
removed no-throw-literal lint rule (#1489)

7641 of 9053 branches covered (84.4%)

Branch coverage included in aggregate %.

9907 of 10660 relevant lines covered (92.94%)

1859.63 hits per line

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

84.85
/src/preprocessor/Preprocessor.ts
1
import type { Token } from '../lexer/Token';
2
import { TokenKind } from '../lexer/TokenKind';
1✔
3
import type * as CC from './Chunk';
4
import type { Diagnostic } from 'vscode-languageserver';
5
import { DiagnosticMessages } from '../DiagnosticMessages';
1✔
6
import { PreprocessorParser } from './PreprocessorParser';
1✔
7
import type { Manifest } from './Manifest';
8
import { getBsConst } from './Manifest';
1✔
9

10
/**
11
 * A simple pre-processor that executes BrightScript's conditional compilation directives by
12
 * selecting chunks of tokens to be considered for later evaluation.
13
 */
14
export class Preprocessor implements CC.Visitor {
1✔
15
    private constants = new Map<string, boolean>();
1,232✔
16

17
    /** The set of errors encountered when pre-processing conditional compilation directives. */
18
    public diagnostics = [] as Diagnostic[];
1,232✔
19

20
    public processedTokens: Token[] = [];
1,232✔
21

22
    /**
23
     * Filters the tokens contained within a set of chunks based on a set of constants.
24
     * @param tokens the tokens
25
     * @param manifest a manifest used to extract bs_const properties from
26
     * @returns an object containing an array of `errors` and an array of `processedTokens` filtered by conditional
27
     *          compilation directives included within
28
     */
29
    public process(tokens: Token[], manifest: Manifest) {
30
        this.processedTokens = [];
1,225✔
31
        let parser = PreprocessorParser.parse(tokens);
1,225✔
32
        //absorb the parser's diagnostic messages
33
        this.diagnostics.push(...parser.diagnostics);
1,225✔
34

35
        //if we found diagnostics, quit now
36
        if (parser.diagnostics.length > 0) {
1,225✔
37
            return this;
1✔
38
        }
39
        let bsConst = getBsConst(manifest);
1,224✔
40
        this.filter(parser.chunks, bsConst);
1,224✔
41
        return this;
1,224✔
42
    }
43

44
    public filter(chunks: ReadonlyArray<CC.Chunk>, bsConst?: Map<string, boolean>) {
45
        this.constants = new Map(bsConst);
1,231✔
46

47
        this.processedTokens = chunks
1,231✔
48
            .map(chunk => chunk.accept(this))
2,358✔
49
            .reduce(
50
                (allTokens: Token[], chunkTokens: Token[]) => [
2,358✔
51
                    ...allTokens,
52
                    ...chunkTokens
53
                ], []
54
            );
55
        return this;
1,231✔
56
    }
57

58
    public static process(tokens: Token[], manifest: Manifest) {
59
        return new Preprocessor().process(tokens, manifest);
×
60
    }
61

62
    /**
63
     * Emits an error via this processor's `events` property, then throws it.
64
     * @param diagnostic the ParseError to emit then throw
65
     */
66
    private addError(diagnostic: Diagnostic) {
67
        this.diagnostics.push(diagnostic);
3✔
68
        return diagnostic;
3✔
69
    }
70

71

72
    /**
73
     * Handles a simple chunk of BrightScript tokens by returning the tokens contained within.
74
     * @param chunk the chunk to extract tokens from
75
     * @returns the array of tokens contained within `chunk`
76
     */
77
    public visitBrightScript(chunk: CC.BrightScriptChunk) {
78
        return chunk.tokens;
2,346✔
79
    }
80

81
    /**
82
     * Handles a BrightScript `#const` directive, creating a variable in-scope only for the
83
     * conditional compilation pass.
84
     * @param chunk the `#const` directive, including the name and variable to use for the constant
85
     * @returns an empty array, since `#const` directives are always removed from the evaluated script.
86
     */
87
    public visitDeclaration(chunk: CC.DeclarationChunk) {
88
        const nameLower = chunk.name?.text?.toLocaleLowerCase();
8!
89
        if (this.constants.has(nameLower)) {
8!
90
            this.addError({
×
91
                ...DiagnosticMessages.duplicateConstDeclaration(chunk.name.text),
92
                range: chunk.name.range
93
            });
94
        }
95

96
        let value;
97
        switch (chunk.value.kind) {
8✔
98
            case TokenKind.True:
8!
99
                value = true;
6✔
100
                break;
6✔
101
            case TokenKind.False:
102
                value = false;
1✔
103
                break;
1✔
104
            case TokenKind.Identifier:
105
                if (this.constants.has(nameLower)) {
×
106
                    value = this.constants.get(nameLower);
×
107
                    break;
×
108
                }
109

110
                this.addError({
×
111
                    ...DiagnosticMessages.constAliasDoesNotExist(chunk.value.text),
112
                    range: chunk.value.range
113
                });
114
                break;
×
115
            default:
116
                this.addError({
1✔
117
                    ...DiagnosticMessages.invalidHashConstValue(),
118
                    range: chunk.value.range
119
                });
120
        }
121

122
        this.constants.set(nameLower, value);
8✔
123

124
        return [];
8✔
125
    }
126

127
    /**
128
     * Throws an error, stopping "compilation" of the program.
129
     * @param chunk the error to report to users
130
     * @throws a JavaScript error with the provided message
131
     */
132
    public visitError(chunk: CC.ErrorChunk): never {
133
        // eslint-disable-next-line @typescript-eslint/no-throw-literal
134
        throw this.addError({
×
135
            ...DiagnosticMessages.hashError(chunk.message.text),
136
            range: chunk.range
137
        });
138
    }
139

140
    /**
141
     * Produces tokens from a branch of a conditional-compilation `#if`, or no tokens if no branches evaluate to `true`.
142
     * @param chunk the `#if` directive, any `#else if` or `#else` directives, and their associated BrightScript chunks.
143
     * @returns an array of tokens to include in the final executed script.
144
     */
145
    public visitIf(chunk: CC.HashIfStatement): Token[] {
146
        if (this.evaluateCondition(chunk.condition)) {
16✔
147
            return chunk.thenChunks
10✔
148
                .map(chunk => chunk.accept(this))
10✔
149
                .reduce((allTokens, chunkTokens: Token[]) => [...allTokens, ...chunkTokens], []);
10✔
150
        } else {
151
            for (const elseIf of chunk.elseIfs) {
6✔
152
                if (this.evaluateCondition(elseIf.condition)) {
2✔
153
                    return elseIf.thenChunks
1✔
154
                        .map(chunk => chunk.accept(this))
1✔
155
                        .reduce(
156
                            (allTokens, chunkTokens: Token[]) => [...allTokens, ...chunkTokens],
1✔
157
                            []
158
                        );
159
                }
160
            }
161
        }
162

163
        if (chunk.elseChunks) {
5✔
164
            return chunk.elseChunks
1✔
165
                .map(chunk => chunk.accept(this))
1✔
166
                .reduce((allTokens, chunkTokens: Token[]) => [...allTokens, ...chunkTokens], []);
1✔
167
        }
168

169
        return [];
4✔
170
    }
171

172
    /**
173
     * Resolves a token to a JavaScript boolean value, or logs a diagnostic error.
174
     * @param token the token to resolve to either `true`, `false`, or `undefined`
175
     */
176
    public evaluateCondition(token: Token): boolean | undefined {
177
        switch (token.kind) {
18✔
178
            case TokenKind.True:
18✔
179
                return true;
2✔
180
            case TokenKind.False:
181
                return false;
5✔
182
            case TokenKind.Identifier:
183
                const nameLower = token.text?.toLowerCase();
10!
184
                if (this.constants.has(nameLower)) {
10✔
185
                    return !!this.constants.get(nameLower);
9✔
186
                }
187
                this.addError({
1✔
188
                    ...DiagnosticMessages.referencedConstDoesNotExist(),
189
                    range: token.range
190
                });
191
                break;
1✔
192
            default:
193
                this.addError({
1✔
194
                    ...DiagnosticMessages.invalidHashIfValue(),
195
                    range: token.range
196
                });
197
        }
198
    }
199
}
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