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

rokucommunity / brighterscript / #12930

13 Aug 2024 05:02PM UTC coverage: 86.193% (-1.7%) from 87.933%
#12930

push

web-flow
Merge 58ad447a2 into 0e968f1c3

10630 of 13125 branches covered (80.99%)

Branch coverage included in aggregate %.

6675 of 7284 new or added lines in 99 files covered. (91.64%)

84 existing lines in 18 files now uncovered.

12312 of 13492 relevant lines covered (91.25%)

26865.48 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,
688✔
29
        public options: BsConfig
688✔
30
    ) {
31
        this.srcPath = srcPath;
688✔
32

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

42
    public indentText = '';
688✔
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) {
15,002✔
49
        this.blockDepth += blockDepthChange;
15,092✔
50
        return this.indentText;
15,092✔
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;
23,490✔
58
    }
59
    set blockDepth(value: number) {
60
        this._blockDepth = value;
23,490✔
61
        this.indentText = value === 0 ? '' : '    '.repeat(value);
23,490✔
62
    }
63
    private _blockDepth = 0;
688✔
64

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

67
    private getSource(locatable: RangeLike) {
68
        let srcPath = (locatable as { location: Location })?.location?.uri ?? (locatable as Location).uri;
49,438!
69
        if (srcPath) {
49,438✔
70
            srcPath = util.uriToPath(srcPath);
49,384✔
71
            //if a sourceRoot is specified, use that instead of the rootDir
72
            if (this.options.sourceRoot) {
49,384✔
73
                srcPath = srcPath.replace(
710✔
74
                    this.options.rootDir,
75
                    this.options.sourceRoot
76
                );
77
            }
78
            return srcPath;
49,384✔
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,250✔
89
        return util.sourceNodeFromTranspileResult(
6,250✔
90
            //convert 0-based range line to 1-based SourceNode line
91
            range ? range.start.line + 1 : null,
6,250✔
92
            //range and SourceNode character are both 0-based, so no conversion necessary
93
            range ? range.start.character : null,
6,250✔
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(
43,184✔
106
            //convert 0-based range line to 1-based SourceNode line
107
            token.location?.range ? token.location.range.start.line + 1 : null,
172,736✔
108
            //range and SourceNode character are both 0-based, so no conversion necessary
109
            token.location?.range ? token.location.range.start.character : null,
172,736✔
110
            this.getSource(token),
111
            token.text
112
        );
113
    }
114

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

123
        return leadingCommentsSourceNodes;
29✔
124
    }
125

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

134
        return leadingCommentsSourceNodes;
38,748✔
135
    }
136

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

142
        let transpiledCommentAlready = false;
38,795✔
143
        for (const commentToken of justComments) {
38,795✔
144
            if (commentToken.kind === TokenKind.Newline && !transpiledCommentAlready) {
18,137✔
145
                continue;
6,551✔
146
            }
147
            if (commentToken.kind === TokenKind.Comment) {
11,586✔
148
                if (leadingCommentsSourceNodes.length > 0) {
5,476✔
149
                    leadingCommentsSourceNodes.push(this.indent());
3,765✔
150
                }
151
                leadingCommentsSourceNodes.push(this.tokenToSourceNode(commentToken));
5,476✔
152
                newLinesSinceComment = 0;
5,476✔
153
            } else {
154
                newLinesSinceComment++;
6,110✔
155
            }
156

157
            if (newLinesSinceComment === 1 || newLinesSinceComment === 2) {
11,586✔
158
                //new line that is not touching a previous new line
159
                leadingCommentsSourceNodes.push(this.newline);
6,108✔
160
            }
161
            transpiledCommentAlready = true;
11,586✔
162
        }
163
        //if we should prepare for the next line, add an indent (only if applicable)
164
        if (prepNextLine && transpiledCommentAlready) {
38,795!
NEW
165
            leadingCommentsSourceNodes.push(this.indent());
×
166
        }
167
        return leadingCommentsSourceNodes;
38,795✔
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 {
74,586✔
175
        const leadingCommentsSourceNodes = skipLeadingComments ? [] : this.transpileLeadingComments(token);
38,610✔
176
        const commentIfCommentedOut = commentOut ? `'` : '';
38,610✔
177

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

182
        if (!token?.location?.range) {
38,607!
183
            return [new SourceNode(null, null, null, [...leadingCommentsSourceNodes, commentIfCommentedOut, token.text])];
938✔
184
        }
185
        //split multi-line text
186
        if (token.location.range.end.line > token.location.range.start.line) {
37,669✔
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)];
37,668✔
208
        }
209
    }
210

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

214
        if (util.hasLeadingComments(endToken)) {
3,993✔
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) {
3,965✔
227
            result.push(this.newline, this.indent());
3,915✔
228
        }
229
        result.push(this.transpileToken({ ...endToken, leadingTrivia: [] }, defaultValue));
3,993✔
230
        return result;
3,993✔
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