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

rokucommunity / brighterscript / #13031

05 Sep 2024 06:52PM UTC coverage: 86.418%. Remained the same
#13031

push

web-flow
Merge 0fda0c105 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%)

157 existing lines in 6 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
    public parseNode(node: AstNode) {
15
        return this.parse(util.getNodeDocumentation(node, false));
10,157✔
16
    }
17

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

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

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

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

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

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

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

152
class BrightScriptDoc {
153

154
    protected _description: string;
155

156
    public tags = [] as BrsDocTag[];
10,169✔
157

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

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

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

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

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

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

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

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

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

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

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

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

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

224

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

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

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

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

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

269

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