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

rokucommunity / brighterscript / #13028

05 Sep 2024 06:50PM UTC coverage: 86.418% (-1.5%) from 87.933%
#13028

push

web-flow
Merge c5c56b09a into 43cbf8b72

10784 of 13272 branches covered (81.25%)

Branch coverage included in aggregate %.

153 of 158 new or added lines in 7 files covered. (96.84%)

811 existing lines in 44 files now uncovered.

12484 of 13653 relevant lines covered (91.44%)

27481.68 hits per line

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

91.71
/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

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 class BrightScriptDocParser {
1✔
13

14

15
    public parseNode(node: AstNode) {
16
        return this.parse(util.getNodeDocumentation(node, false));
10,157✔
17
    }
18

19
    public parse(documentation: string) {
20
        const brsDoc = new BrightScriptDoc(documentation);
10,169✔
21
        if (!documentation) {
10,169✔
22
            return brsDoc;
9,897✔
23
        }
24
        const lines = documentation.split('\n');
272✔
25
        const blockLines = [] as string[];
272✔
26
        const descriptionLines = [] as string[];
272✔
27
        let lastTag: BrsDocTag;
28
        function augmentLastTagWithBlockLines() {
29
            if (blockLines.length > 0 && lastTag) {
420✔
30
                // add to the description or details to previous tag
31
                if (typeof (lastTag as BrsDocWithDescription).description !== 'undefined') {
12✔
32
                    (lastTag as BrsDocWithDescription).description += '\n' + blockLines.join('\n');
7✔
33
                    (lastTag as BrsDocWithDescription).description = (lastTag as BrsDocWithDescription).description.trim();
7✔
34
                }
35
                if (typeof lastTag.detail !== 'undefined') {
12!
36
                    lastTag.detail += '\n' + blockLines.join('\n');
12✔
37
                    lastTag.detail = lastTag.detail.trim();
12✔
38
                }
39
            }
40
            blockLines.length = 0;
420✔
41
        }
42
        for (let line of lines) {
272✔
43
            line = line.trim();
378✔
44
            while (line.startsWith('\'')) {
378✔
45
                // remove leading apostrophes
46
                line = line.substring(1).trim();
2✔
47
            }
48
            if (!line.startsWith('@')) {
378✔
49
                if (lastTag) {
230✔
50

51
                    blockLines.push(line);
16✔
52
                } else if (descriptionLines.length > 0 || line) {
214✔
53
                    // add a line to the list if it's not empty
54
                    descriptionLines.push(line);
203✔
55
                }
56
            } else {
57
                augmentLastTagWithBlockLines();
148✔
58
                const newTag = this.parseLine(line);
148✔
59
                lastTag = newTag;
148✔
60
                if (newTag) {
148!
61
                    brsDoc.tags.push(newTag);
148✔
62
                }
63
            }
64
        }
65
        augmentLastTagWithBlockLines();
272✔
66
        brsDoc.description = descriptionLines.join('\n').trim();
272✔
67
        return brsDoc;
272✔
68
    }
69

70
    private parseLine(line: string) {
71
        line = line.trim();
148✔
72
        const match = tagRegex.exec(line);
148✔
73
        if (!match) {
148!
NEW
74
            return;
×
75
        }
76
        const tagName = match[1].toLowerCase();
148✔
77
        const detail = match[2] ?? '';
148!
78

79
        switch (tagName) {
148✔
80
            case 'param':
185✔
81
                return this.parseParam(detail);
95✔
82
            case 'return':
83
            case 'returns':
84
                return this.parseReturn(detail);
42✔
85
            case 'type':
86
                return this.parseType(detail);
7✔
87
        }
88
        return {
4✔
89
            tagName: tagName,
90
            detail: detail
91
        };
92
    }
93

94

95
    private parseParam(detail: string): BrsDocParamTag {
96
        let type = '';
95✔
97
        let description = '';
95✔
98
        let optional = false;
95✔
99
        let paramName = '';
95✔
100
        let match = paramRegex.exec(detail);
95✔
101
        if (match) {
95!
102
            type = match[1] ?? '';
95✔
103
            paramName = match[2] ?? '';
95!
104
            description = match[3] ?? '';
95!
105
        } else {
NEW
106
            paramName = detail.trim();
×
107
        }
108
        if (paramName) {
95!
109
            optional = paramName.startsWith('[') && paramName.endsWith(']');
95✔
110
            paramName = paramName.replace(/\[|\]/g, '').trim();
95✔
111
        }
112
        return {
95✔
113
            tagName: 'param',
114
            name: paramName,
115
            type: type,
116
            description: description,
117
            optional: optional,
118
            detail: detail
119
        };
120
    }
121

122
    private parseReturn(detail: string): BrsDocWithDescription {
123
        let match = returnRegex.exec(detail);
42✔
124
        let type = '';
42✔
125
        let description = '';
42✔
126
        if (match) {
42!
127
            type = match[1] ?? '';
42✔
128
            description = match[2] ?? '';
42!
129
        }
130
        return {
42✔
131
            tagName: 'return',
132
            type: type,
133
            description: description,
134
            detail: detail
135
        };
136
    }
137

138
    private parseType(detail: string): BrsDocWithType {
139
        let match = typeTagRegex.exec(detail);
7✔
140
        let type = '';
7✔
141
        if (match) {
7!
142
            if (match[1]) {
7!
143
                type = match[1] ?? '';
7!
144
            }
145
        }
146
        return {
7✔
147
            tagName: 'type',
148
            type: type,
149
            detail: detail
150
        };
151
    }
152
}
153

154
class BrightScriptDoc {
155

156
    protected _description: string;
157

158
    public tags = [] as BrsDocTag[];
10,169✔
159

160
    constructor(
161
        public readonly documentation: string
10,169✔
162
    ) {
163
    }
164

165
    set description(value: string) {
166
        this._description = value;
272✔
167
    }
168

169
    get description() {
170
        const descTag = this.tags.find((tag) => {
8✔
171
            return tag.tagName === 'description';
7✔
172
        });
173

174
        let result = this._description ?? '';
8!
175
        if (descTag) {
8✔
176
            const descTagDetail = descTag.detail;
2✔
177
            result = result ? result + '\n' + descTagDetail : descTagDetail;
2!
178
        }
179
        return result.trim();
8✔
180
    }
181

182
    getParam(name: string) {
183
        return this.tags.find((tag) => {
5,138✔
184
            return tag.tagName === 'param' && (tag as BrsDocParamTag).name === name;
150✔
185
        }) as BrsDocParamTag;
186
    }
187

188
    getReturn() {
189
        return this.tags.find((tag) => {
3,785✔
190
            return tag.tagName === 'return' || tag.tagName === 'returns';
56✔
191
        }) as BrsDocWithDescription;
192
    }
193

194
    getTypeTag() {
195
        return this.tags.find((tag) => {
1,263✔
196
            return tag.tagName === 'type';
7✔
197
        }) as BrsDocWithType;
198
    }
199

200
    getTag(tagName: string) {
201
        const lowerTagName = tagName.toLowerCase();
2✔
202
        return this.tags.find((tag) => {
2✔
203
            return tag.tagName === lowerTagName;
2✔
204
        });
205
    }
206

207
    getAllTags(tagName: string) {
208
        const lowerTagName = tagName.toLowerCase();
2✔
209
        return this.tags.filter((tag) => {
2✔
210
            return tag.tagName === lowerTagName;
11✔
211
        });
212
    }
213

214
    getParamBscType(name: string, nodeContext: AstNode, options: GetSymbolTypeOptions) {
215
        const param = this.getParam(name);
5,117✔
216

217
        return this.getTypeFromContext(param?.type, nodeContext, options);
5,117✔
218
    }
219

220
    getReturnBscType(nodeContext: AstNode, options: GetSymbolTypeOptions) {
221
        const retTag = this.getReturn();
3,776✔
222

223
        return this.getTypeFromContext(retTag?.type, nodeContext, options);
3,776✔
224
    }
225

226

227
    getTypeTagBscType(nodeContext: AstNode, options: GetSymbolTypeOptions) {
228
        const retTag = this.getTypeTag();
1,262✔
229
        return this.getTypeFromContext(retTag?.type, nodeContext, options);
1,262✔
230
    }
231

232
    private getTypeFromContext(typeName: string, nodeContext: AstNode, options: GetSymbolTypeOptions) {
233
        const topSymbolTable = nodeContext?.getSymbolTable();
10,155!
234
        if (!topSymbolTable || !typeName) {
10,155✔
235
            return undefined;
10,082✔
236
        }
237
        const fullName = typeName;
73✔
238
        const parts = typeName.split('.');
73✔
239
        const optionsToUse = {
73✔
240
            ...options,
241
            flags: SymbolTypeFlag.typetime,
242
            fullName: fullName,
243
            typeChain: undefined
244
        };
245
        let result = topSymbolTable.getSymbolType(parts.shift(), optionsToUse);
73✔
246
        while (result && parts.length > 0) {
73✔
247
            result = result.getMemberType(parts.shift(), optionsToUse);
7✔
248
        }
249
        return result;
73✔
250
    }
251
}
252

253
interface BrsDocTag {
254
    tagName: string;
255
    detail?: string;
256
}
257
interface BrsDocWithType extends BrsDocTag {
258
    type?: string;
259
}
260

261
interface BrsDocWithDescription extends BrsDocWithType {
262
    description?: string;
263
}
264

265
interface BrsDocParamTag extends BrsDocWithDescription {
266
    name: string;
267
    optional?: boolean;
268
}
269

270

271
export let brsDocParser = new BrightScriptDocParser();
1✔
272
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

© 2025 Coveralls, Inc