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

rokucommunity / brighterscript / #13112

30 Sep 2024 04:35PM UTC coverage: 86.842% (-1.4%) from 88.193%
#13112

push

web-flow
Merge 25fc06528 into 3a2dc7282

11525 of 14034 branches covered (82.12%)

Branch coverage included in aggregate %.

6990 of 7581 new or added lines in 100 files covered. (92.2%)

83 existing lines in 18 files now uncovered.

12691 of 13851 relevant lines covered (91.63%)

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

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

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

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

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

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

123
        return leadingCommentsSourceNodes;
28✔
124
    }
125

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

134
        return leadingCommentsSourceNodes;
40,755✔
135
    }
136

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

142
        let transpiledCommentAlready = false;
40,801✔
143
        for (const commentToken of justComments) {
40,801✔
144
            if (commentToken.kind === TokenKind.Newline && !transpiledCommentAlready) {
19,141✔
145
                continue;
6,943✔
146
            }
147
            if (commentToken.kind === TokenKind.Comment) {
12,198✔
148
                if (leadingCommentsSourceNodes.length > 0) {
5,765✔
149
                    leadingCommentsSourceNodes.push(this.indent());
3,969✔
150
                }
151
                leadingCommentsSourceNodes.push(this.tokenToSourceNode(commentToken));
5,765✔
152
                newLinesSinceComment = 0;
5,765✔
153
            } else {
154
                newLinesSinceComment++;
6,433✔
155
            }
156

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

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

182
        if (!token?.location?.range) {
40,588!
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) {
39,642✔
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)];
39,641✔
208
        }
209
    }
210

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

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