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

rokucommunity / brighterscript / #13215

19 Oct 2024 06:15PM UTC coverage: 86.838% (-1.4%) from 88.214%
#13215

push

web-flow
Merge 65d0479eb into 7cfaaa047

11593 of 14113 branches covered (82.14%)

Branch coverage included in aggregate %.

7018 of 7610 new or added lines in 100 files covered. (92.22%)

87 existing lines in 18 files now uncovered.

12720 of 13885 relevant lines covered (91.61%)

29935.23 hits per line

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

95.96
/src/parser/TranspileState.ts
1
import { SourceNode } from 'source-map';
1✔
2
import type { Location } from 'vscode-languageserver';
3
import type { BsConfig } from '../BsConfig';
4
import { TokenKind } from '../lexer/TokenKind';
1✔
5
import { type Token } from '../lexer/Token';
6
import type { RangeLike } from '../util';
7
import { util } from '../util';
1✔
8
import type { TranspileResult } from '../interfaces';
9

10
interface TranspileToken {
11
    location?: Location;
12
    text: string;
13
    kind?: TokenKind;
14
    leadingWhitespace?: string;
15
    leadingTrivia?: Array<TranspileToken>;
16
}
17

18
/**
19
 * Holds the state of a transpile operation as it works its way through the transpile process
20
 */
21
export class TranspileState {
1✔
22
    constructor(
23
        /**
24
         * The absolute path to the source location of this file. If sourceRoot is specified,
25
         * this path will be full path to the file in sourceRoot instead of rootDir.
26
         * If the file resides outside of rootDir, then no changes will be made to this path.
27
         */
28
        public srcPath: string,
736✔
29
        public options: BsConfig
736✔
30
    ) {
31
        this.srcPath = srcPath;
736✔
32

33
        //if a sourceRoot is specified, use that instead of the rootDir
34
        if (this.options.sourceRoot) {
736✔
35
            this.srcPath = this.srcPath.replace(
10✔
36
                this.options.rootDir,
37
                this.options.sourceRoot
38
            );
39
        }
40
    }
41

42
    public indentText = '';
736✔
43

44
    /**
45
     * Append whitespace until we reach the current blockDepth amount
46
     * @param blockDepthChange - if provided, this will add (or subtract if negative) the value to the block depth BEFORE getting the next indent amount.
47
     */
48
    public indent(blockDepthChange = 0) {
16,174✔
49
        this.blockDepth += blockDepthChange;
16,264✔
50
        return this.indentText;
16,264✔
51
    }
52

53
    /**
54
     * The number of active parent blocks for the current location of the state.
55
     */
56
    get blockDepth() {
57
        return this._blockDepth;
25,336✔
58
    }
59
    set blockDepth(value: number) {
60
        this._blockDepth = value;
25,336✔
61
        this.indentText = value === 0 ? '' : '    '.repeat(value);
25,336✔
62
    }
63
    private _blockDepth = 0;
736✔
64

65
    public newline = '\n';
736✔
66

67
    private getSource(locatable: RangeLike) {
68
        let srcPath = (locatable as { location: Location })?.location?.uri ?? (locatable as Location).uri;
53,338!
69
        if (srcPath) {
53,338✔
70
            srcPath = util.uriToPath(srcPath);
53,284✔
71
            //if a sourceRoot is specified, use that instead of the rootDir
72
            if (this.options.sourceRoot) {
53,284✔
73
                srcPath = srcPath.replace(
710✔
74
                    this.options.rootDir,
75
                    this.options.sourceRoot
76
                );
77
            }
78
            return srcPath;
53,284✔
79
        } else {
80
            return this.srcPath;
54✔
81
        }
82
    }
83

84
    /**
85
     * Shorthand for creating a new source node
86
     */
87
    public sourceNode(locatable: RangeLike, code: string | SourceNode | TranspileResult): SourceNode {
88
        let range = util.extractRange(locatable);
6,728✔
89
        return util.sourceNodeFromTranspileResult(
6,728✔
90
            //convert 0-based range line to 1-based SourceNode line
91
            range ? range.start.line + 1 : null,
6,728✔
92
            //range and SourceNode character are both 0-based, so no conversion necessary
93
            range ? range.start.character : null,
6,728✔
94
            this.getSource(locatable),
95
            code
96
        );
97
    }
98

99
    /**
100
     * Create a SourceNode from a token. This is more efficient than the above `sourceNode` function
101
     * because the entire token is passed by reference, instead of the raw string being copied to the parameter,
102
     * only to then be copied again for the SourceNode constructor
103
     */
104
    public tokenToSourceNode(token: TranspileToken) {
105
        return new SourceNode(
46,606✔
106
            //convert 0-based range line to 1-based SourceNode line
107
            token.location?.range ? token.location.range.start.line + 1 : null,
186,424✔
108
            //range and SourceNode character are both 0-based, so no conversion necessary
109
            token.location?.range ? token.location.range.start.character : null,
186,424✔
110
            this.getSource(token),
111
            token.text
112
        );
113
    }
114

115
    public transpileLeadingCommentsForAstNode(node: { leadingTrivia?: Token[] }) {
116
        const leadingTrivia = node?.leadingTrivia ?? [];
38!
117
        const leadingCommentsSourceNodes = this.transpileComments(leadingTrivia);
38✔
118
        if (leadingCommentsSourceNodes.length > 0) {
38✔
119
            // indent in preparation for next text
120
            leadingCommentsSourceNodes.push(this.indent());
2✔
121
        }
122

123
        return leadingCommentsSourceNodes;
38✔
124
    }
125

126
    public transpileLeadingComments(token: TranspileToken) {
127
        const leadingTrivia = (token?.leadingTrivia ?? []);
41,785✔
128
        const leadingCommentsSourceNodes = this.transpileComments(leadingTrivia);
41,785✔
129
        if (leadingCommentsSourceNodes.length > 0 && token.text) {
41,785✔
130
            // indent in preparation for next text
131
            leadingCommentsSourceNodes.push(this.indent());
1,788✔
132
        }
133

134
        return leadingCommentsSourceNodes;
41,785✔
135
    }
136

137
    public transpileComments(tokens: TranspileToken[], prepNextLine = false): Array<string | SourceNode> {
41,841✔
138
        const leadingCommentsSourceNodes = [];
41,841✔
139
        const justComments = tokens.filter(t => t.kind === TokenKind.Comment || t.kind === TokenKind.Newline);
45,550✔
140
        let newLinesSinceComment = 0;
41,841✔
141

142
        let transpiledCommentAlready = false;
41,841✔
143
        for (const commentToken of justComments) {
41,841✔
144
            if (commentToken.kind === TokenKind.Newline && !transpiledCommentAlready) {
19,642✔
145
                continue;
7,156✔
146
            }
147
            if (commentToken.kind === TokenKind.Comment) {
12,486✔
148
                if (leadingCommentsSourceNodes.length > 0) {
5,901✔
149
                    leadingCommentsSourceNodes.push(this.indent());
4,065✔
150
                }
151
                leadingCommentsSourceNodes.push(this.tokenToSourceNode(commentToken));
5,901✔
152
                newLinesSinceComment = 0;
5,901✔
153
            } else {
154
                newLinesSinceComment++;
6,585✔
155
            }
156

157
            if (newLinesSinceComment === 1 || newLinesSinceComment === 2) {
12,486✔
158
                //new line that is not touching a previous new line
159
                leadingCommentsSourceNodes.push(this.newline);
6,583✔
160
            }
161
            transpiledCommentAlready = true;
12,486✔
162
        }
163
        //if we should prepare for the next line, add an indent (only if applicable)
164
        if (prepNextLine && transpiledCommentAlready) {
41,841!
NEW
165
            leadingCommentsSourceNodes.push(this.indent());
×
166
        }
167
        return leadingCommentsSourceNodes;
41,841✔
168
    }
169

170
    /**
171
     * Create a SourceNode from a token, accounting for missing range and multi-line text
172
     * Adds all leading trivia for the token
173
     */
174
    public transpileToken(token: TranspileToken, defaultValue?: string, commentOut = false, skipLeadingComments = false): TranspileResult {
80,364✔
175
        const leadingCommentsSourceNodes = skipLeadingComments ? [] : this.transpileLeadingComments(token);
41,611✔
176
        const commentIfCommentedOut = commentOut ? `'` : '';
41,611✔
177

178
        if (!token?.text && defaultValue !== undefined) {
41,611✔
179
            return [new SourceNode(null, null, null, [...leadingCommentsSourceNodes, commentIfCommentedOut, defaultValue])];
3✔
180
        }
181

182
        if (!token?.location?.range) {
41,608!
183
            return [new SourceNode(null, null, null, [...leadingCommentsSourceNodes, commentIfCommentedOut, token.text])];
946✔
184
        }
185
        //split multi-line text
186
        if (token.location.range.end.line > token.location.range.start.line) {
40,662✔
187
            const lines = token.text.split(/\r?\n/g);
1✔
188
            const code = [
1✔
189
                this.sourceNode(token, [...leadingCommentsSourceNodes, commentIfCommentedOut, lines[0]])
190
            ] as Array<string | SourceNode>;
191
            for (let i = 1; i < lines.length; i++) {
1✔
192
                code.push(
4✔
193
                    this.newline,
194
                    commentIfCommentedOut,
195
                    new SourceNode(
196
                        //convert 0-based range line to 1-based SourceNode line
197
                        token.location.range.start.line + i + 1,
198
                        //SourceNode column is 0-based, and this starts at the beginning of the line
199
                        0,
200
                        this.getSource(token),
201
                        lines[i]
202
                    )
203
                );
204
            }
205
            return [new SourceNode(null, null, null, code)];
1✔
206
        } else {
207
            return [...leadingCommentsSourceNodes, commentIfCommentedOut, this.tokenToSourceNode(token)];
40,661✔
208
        }
209
    }
210

211
    public transpileEndBlockToken(previousLocatable: { location?: Location }, endToken: Token, defaultValue: string, alwaysAddNewlineBeforeEndToken = true) {
4,211✔
212
        const result = [];
4,322✔
213

214
        if (util.hasLeadingComments(endToken)) {
4,322✔
215
            // add comments before `end token` - they should be indented
216
            if (util.isLeadingCommentOnSameLine(previousLocatable?.location, endToken)) {
28!
217
                this.blockDepth++;
22✔
218
                result.push(' ');
22✔
219
            } else {
220
                result.push(this.newline);
6✔
221
                result.push(this.indent(1));
6✔
222
            }
223
            result.push(...this.transpileToken({ ...endToken, text: '' }));
28✔
224
            this.blockDepth--;
28✔
225
            result.push(this.indent());
28✔
226
        } else if (alwaysAddNewlineBeforeEndToken) {
4,294✔
227
            result.push(this.newline, this.indent());
4,236✔
228
        }
229
        result.push(this.transpileToken({ ...endToken, leadingTrivia: [] }, defaultValue));
4,322✔
230
        return result;
4,322✔
231
    }
232
}
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