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

rokucommunity / brighterscript / #13040

06 Sep 2024 01:54PM UTC coverage: 86.524% (+0.01%) from 86.512%
#13040

push

web-flow
Merge b2bbc792d into 43cbf8b72

10841 of 13320 branches covered (81.39%)

Branch coverage included in aggregate %.

213 of 221 new or added lines in 8 files covered. (96.38%)

147 existing lines in 6 files now uncovered.

12537 of 13699 relevant lines covered (91.52%)

27494.65 hits per line

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

90.08
/src/parser/BrightScriptDocParser.ts
1
import type { GetSymbolTypeOptions } from '../SymbolTable';
2
import { SymbolTypeFlag } from '../SymbolTypeFlag';
1✔
3
import util from '../util';
1✔
4
import type { AstNode } from './AstNode';
5
import type { Location } from 'vscode-languageserver';
6

7
const tagRegex = /@(\w+)(?:\s+(.*))?/;
1✔
8
const paramRegex = /(?:{([^}]*)}\s+)?(?:(\[?\w+\]?))\s*(.*)/;
1✔
9
const returnRegex = /(?:{([^}]*)})?\s*(.*)/;
1✔
10
const typeTagRegex = /(?:{([^}]*)})?/;
1✔
11

12
export enum BrsDocTagKind {
1✔
13
    Description = 'description',
1✔
14
    Param = 'param',
1✔
15
    Return = 'return',
1✔
16
    Type = 'type',
1✔
17
    Var = 'var'
1✔
18
}
19

20

21
export class BrightScriptDocParser {
1✔
22

23
    public parseNode(node: AstNode) {
24
        const matchingLocations: Location[] = [];
28,765✔
25
        return this.parse(
28,765✔
26
            util.getNodeDocumentation(node, {
27
                prettyPrint: false,
28
                matchingLocations: matchingLocations
29
            }),
30
            matchingLocations);
31
    }
32

33
    public parse(documentation: string, matchingLocations: Location[] = []) {
15✔
34
        const brsDoc = new BrightScriptDoc(documentation);
28,780✔
35
        if (!documentation) {
28,780✔
36
            return brsDoc;
27,972✔
37
        }
38
        const lines = documentation.split('\n');
808✔
39
        const blockLines = [] as { line: string; location?: Location }[];
808✔
40
        const descriptionLines = [] as { line: string; location?: Location }[];
808✔
41
        let lastTag: BrsDocTag;
42
        let haveMatchingLocations = false;
808✔
43
        if (lines.length === matchingLocations.length) {
808✔
44
            // We locations for each line, so we can add Locations
45
            haveMatchingLocations = true;
793✔
46
        }
47
        function augmentLastTagWithBlockLines() {
48
            if (blockLines.length > 0 && lastTag) {
1,044✔
49
                // add to the description or details to previous tag
50
                if (typeof (lastTag as BrsDocWithDescription).description !== 'undefined') {
15✔
51
                    (lastTag as BrsDocWithDescription).description += '\n' + blockLines.map(obj => obj.line).join('\n');
12✔
52
                    (lastTag as BrsDocWithDescription).description = (lastTag as BrsDocWithDescription).description.trim();
9✔
53
                }
54
                if (typeof lastTag.detail !== 'undefined') {
15!
55
                    lastTag.detail += '\n' + blockLines.map(obj => obj.line).join('\n');
19✔
56
                    lastTag.detail = lastTag.detail.trim();
15✔
57
                }
58
                if (haveMatchingLocations) {
15!
NEW
59
                    lastTag.location = util.createBoundingLocation(lastTag.location, blockLines[blockLines.length - 1].location);
×
60
                }
61
            }
62
            blockLines.length = 0;
1,044✔
63
        }
64
        for (let line of lines) {
808✔
65
            let location = haveMatchingLocations ? matchingLocations.shift() : undefined;
946✔
66
            line = line.trim();
946✔
67
            while (line.startsWith('\'')) {
946✔
68
                // remove leading apostrophes
69
                line = line.substring(1).trim();
2✔
70
            }
71
            if (!line.startsWith('@')) {
946✔
72
                if (lastTag) {
710✔
73

74
                    blockLines.push({ line: line, location: location });
19✔
75
                } else if (descriptionLines.length > 0 || line) {
691✔
76
                    // add a line to the list if it's not empty
77
                    descriptionLines.push({ line: line, location: location });
677✔
78
                }
79
            } else {
80
                augmentLastTagWithBlockLines();
236✔
81
                const newTag = this.parseLine(line, location);
236✔
82
                lastTag = newTag;
236✔
83
                if (newTag) {
236!
84
                    brsDoc.tags.push(newTag);
236✔
85
                }
86
            }
87
        }
88
        augmentLastTagWithBlockLines();
808✔
89
        brsDoc.description = descriptionLines.map(obj => obj.line).join('\n').trim();
808✔
90
        return brsDoc;
808✔
91
    }
92

93
    private parseLine(line: string, location?: Location) {
94
        line = line.trim();
236✔
95
        const match = tagRegex.exec(line);
236✔
96
        if (!match) {
236!
NEW
97
            return;
×
98
        }
99
        const tagName = match[1];
236✔
100
        const detail = match[2] ?? '';
236✔
101

102
        switch (tagName) {
236✔
103
            case BrsDocTagKind.Param:
303✔
104
                return { ...this.parseParam(detail), location: location };
132✔
105
            case BrsDocTagKind.Return:
106
            case 'returns':
107
                return { ...this.parseReturn(detail), location: location };
73✔
108
            case BrsDocTagKind.Type:
109
                return { ...this.parseType(detail), location: location };
25✔
110
            case BrsDocTagKind.Var:
111
                return { ...this.parseParam(detail), tagName: BrsDocTagKind.Var, location: location };
1✔
112
        }
113
        return {
5✔
114
            tagName: tagName,
115
            detail: detail,
116
            location: location
117
        };
118
    }
119

120
    private parseParam(detail: string): BrsDocParamTag {
121
        let type = '';
133✔
122
        let description = '';
133✔
123
        let optional = false;
133✔
124
        let paramName = '';
133✔
125
        let match = paramRegex.exec(detail);
133✔
126
        if (match) {
133!
127
            type = match[1] ?? '';
133✔
128
            paramName = match[2] ?? '';
133!
129
            description = match[3] ?? '';
133!
130
        } else {
NEW
131
            paramName = detail.trim();
×
132
        }
133
        if (paramName) {
133!
134
            optional = paramName.startsWith('[') && paramName.endsWith(']');
133✔
135
            paramName = paramName.replace(/\[|\]/g, '').trim();
133✔
136
        }
137
        return {
133✔
138
            tagName: BrsDocTagKind.Param,
139
            name: paramName,
140
            type: type,
141
            description: description,
142
            optional: optional,
143
            detail: detail
144
        };
145
    }
146

147
    private parseReturn(detail: string): BrsDocWithDescription {
148
        let match = returnRegex.exec(detail);
73✔
149
        let type = '';
73✔
150
        let description = '';
73✔
151
        if (match) {
73!
152
            type = match[1] ?? '';
73✔
153
            description = match[2] ?? '';
73!
154
        }
155
        return {
73✔
156
            tagName: BrsDocTagKind.Return,
157
            type: type,
158
            description: description,
159
            detail: detail
160
        };
161
    }
162

163
    private parseType(detail: string): BrsDocWithType {
164
        let match = typeTagRegex.exec(detail);
25✔
165
        let type = '';
25✔
166
        if (match) {
25!
167
            if (match[1]) {
25!
168
                type = match[1] ?? '';
25!
169
            }
170
        }
171
        return {
25✔
172
            tagName: BrsDocTagKind.Type,
173
            type: type,
174
            detail: detail
175
        };
176
    }
177
}
178

179
export class BrightScriptDoc {
1✔
180

181
    protected _description: string;
182

183
    public tags = [] as BrsDocTag[];
28,780✔
184

185
    constructor(
186
        public readonly documentation: string
28,780✔
187
    ) {
188
    }
189

190
    set description(value: string) {
191
        this._description = value;
808✔
192
    }
193

194
    get description() {
195
        const descTag = this.tags.find((tag) => {
9✔
196
            return tag.tagName === BrsDocTagKind.Description;
10✔
197
        });
198

199
        let result = this._description ?? '';
9!
200
        if (descTag) {
9✔
201
            const descTagDetail = descTag.detail;
2✔
202
            result = result ? result + '\n' + descTagDetail : descTagDetail;
2!
203
        }
204
        return result.trim();
9✔
205
    }
206

207
    getParam(name: string) {
208
        const lowerName = name.toLowerCase();
5,157✔
209
        return this.tags.find((tag) => {
5,157✔
210
            return tag.tagName === BrsDocTagKind.Param && (tag as BrsDocParamTag).name.toLowerCase() === lowerName;
165✔
211
        }) as BrsDocParamTag;
212
    }
213

214
    getVar(name: string) {
215
        const lowerName = name.toLowerCase();
1✔
216
        return this.tags.find((tag) => {
1✔
217
            return tag.tagName === BrsDocTagKind.Var && (tag as BrsDocParamTag).name.toLowerCase() === lowerName;
1✔
218
        }) as BrsDocParamTag;
219
    }
220

221

222
    getReturn() {
223
        return this.tags.find((tag) => {
3,794✔
224
            return tag.tagName === BrsDocTagKind.Return || tag.tagName === 'returns';
67✔
225
        }) as BrsDocWithDescription;
226
    }
227

228
    getTypeTag() {
229
        return this.tags.find((tag) => {
1,265✔
230
            return tag.tagName === BrsDocTagKind.Type;
9✔
231
        }) as BrsDocWithType;
232
    }
233

234
    getTag(tagName: string) {
235
        return this.tags.find((tag) => {
3✔
236
            return tag.tagName === tagName;
3✔
237
        });
238
    }
239

240
    getAllTags(tagName: string) {
241
        return this.tags.filter((tag) => {
3✔
242
            return tag.tagName === tagName;
14✔
243
        });
244
    }
245

246
    getParamBscType(name: string, nodeContext: AstNode, options: GetSymbolTypeOptions) {
247
        const param = this.getParam(name);
5,133✔
248
        return this.getTypeFromContext(param?.type, nodeContext, options);
5,133✔
249
    }
250

251
    getVarBscType(name: string, nodeContext: AstNode, options: GetSymbolTypeOptions) {
NEW
252
        const param = this.getVar(name);
×
NEW
253
        return this.getTypeFromContext(param?.type, nodeContext, options);
×
254
    }
255

256
    getReturnBscType(nodeContext: AstNode, options: GetSymbolTypeOptions) {
257
        const retTag = this.getReturn();
3,783✔
258

259
        return this.getTypeFromContext(retTag?.type, nodeContext, options);
3,783✔
260
    }
261

262

263
    getTypeTagBscType(nodeContext: AstNode, options: GetSymbolTypeOptions) {
264
        const retTag = this.getTypeTag();
1,264✔
265
        return this.getTypeFromContext(retTag?.type, nodeContext, options);
1,264✔
266
    }
267

268
    getTypeFromContext(typeName: string, nodeContext: AstNode, options: GetSymbolTypeOptions) {
269
        // TODO: Add support for union types here
270
        const topSymbolTable = nodeContext?.getSymbolTable();
10,203!
271
        if (!topSymbolTable || !typeName) {
10,203✔
272
            return undefined;
10,097✔
273
        }
274
        const fullName = typeName;
106✔
275
        const parts = typeName.split('.');
106✔
276
        const optionsToUse = {
106✔
277
            ...options,
278
            flags: SymbolTypeFlag.typetime,
279
            fullName: fullName,
280
            typeChain: undefined
281
        };
282
        let result = topSymbolTable.getSymbolType(parts.shift(), optionsToUse);
106✔
283
        while (result && parts.length > 0) {
106✔
284
            result = result.getMemberType(parts.shift(), optionsToUse);
9✔
285
        }
286
        return result;
106✔
287
    }
288
}
289

290
export interface BrsDocTag {
291
    tagName: string;
292
    detail?: string;
293
    location?: Location;
294
}
295
export interface BrsDocWithType extends BrsDocTag {
296
    type?: string;
297
}
298

299
export interface BrsDocWithDescription extends BrsDocWithType {
300
    description?: string;
301
}
302

303
export interface BrsDocParamTag extends BrsDocWithDescription {
304
    name: string;
305
    optional?: boolean;
306
}
307

308

309
export let brsDocParser = new BrightScriptDocParser();
1✔
310
export default brsDocParser;
1✔
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