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

rokucommunity / brighterscript / #13039

06 Sep 2024 12:44PM UTC coverage: 86.512%. Remained the same
#13039

push

web-flow
Merge 3d66f6a37 into 43cbf8b72

10813 of 13289 branches covered (81.37%)

Branch coverage included in aggregate %.

173 of 180 new or added lines in 7 files covered. (96.11%)

147 existing lines in 6 files now uncovered.

12514 of 13675 relevant lines covered (91.51%)

27481.17 hits per line

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

90.34
/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 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
        return this.parse(util.getNodeDocumentation(node, false));
28,491✔
25
    }
26

27
    public parse(documentation: string) {
28
        const brsDoc = new BrightScriptDoc(documentation);
28,506✔
29
        if (!documentation) {
28,506✔
30
            return brsDoc;
27,945✔
31
        }
32
        const lines = documentation.split('\n');
561✔
33
        const blockLines = [] as string[];
561✔
34
        const descriptionLines = [] as string[];
561✔
35
        let lastTag: BrsDocTag;
36
        function augmentLastTagWithBlockLines() {
37
            if (blockLines.length > 0 && lastTag) {
749✔
38
                // add to the description or details to previous tag
39
                if (typeof (lastTag as BrsDocWithDescription).description !== 'undefined') {
15✔
40
                    (lastTag as BrsDocWithDescription).description += '\n' + blockLines.join('\n');
9✔
41
                    (lastTag as BrsDocWithDescription).description = (lastTag as BrsDocWithDescription).description.trim();
9✔
42
                }
43
                if (typeof lastTag.detail !== 'undefined') {
15!
44
                    lastTag.detail += '\n' + blockLines.join('\n');
15✔
45
                    lastTag.detail = lastTag.detail.trim();
15✔
46
                }
47
            }
48
            blockLines.length = 0;
749✔
49
        }
50
        for (let line of lines) {
561✔
51
            line = line.trim();
690✔
52
            while (line.startsWith('\'')) {
690✔
53
                // remove leading apostrophes
54
                line = line.substring(1).trim();
2✔
55
            }
56
            if (!line.startsWith('@')) {
690✔
57
                if (lastTag) {
502✔
58

59
                    blockLines.push(line);
19✔
60
                } else if (descriptionLines.length > 0 || line) {
483✔
61
                    // add a line to the list if it's not empty
62
                    descriptionLines.push(line);
469✔
63
                }
64
            } else {
65
                augmentLastTagWithBlockLines();
188✔
66
                const newTag = this.parseLine(line);
188✔
67
                lastTag = newTag;
188✔
68
                if (newTag) {
188!
69
                    brsDoc.tags.push(newTag);
188✔
70
                }
71
            }
72
        }
73
        augmentLastTagWithBlockLines();
561✔
74
        brsDoc.description = descriptionLines.join('\n').trim();
561✔
75
        return brsDoc;
561✔
76
    }
77

78
    private parseLine(line: string) {
79
        line = line.trim();
188✔
80
        const match = tagRegex.exec(line);
188✔
81
        if (!match) {
188!
NEW
82
            return;
×
83
        }
84
        const tagName = match[1];
188✔
85
        const detail = match[2] ?? '';
188✔
86

87
        switch (tagName) {
188✔
88
            case BrsDocTagKind.Param:
237✔
89
                return this.parseParam(detail);
114✔
90
            case BrsDocTagKind.Return:
91
            case 'returns':
92
                return this.parseReturn(detail);
55✔
93
            case BrsDocTagKind.Type:
94
                return this.parseType(detail);
13✔
95
            case BrsDocTagKind.Var:
96
                return { ...this.parseParam(detail), tagName: BrsDocTagKind.Var };
1✔
97
        }
98
        return {
5✔
99
            tagName: tagName,
100
            detail: detail
101
        };
102
    }
103

104
    private parseParam(detail: string): BrsDocParamTag {
105
        let type = '';
115✔
106
        let description = '';
115✔
107
        let optional = false;
115✔
108
        let paramName = '';
115✔
109
        let match = paramRegex.exec(detail);
115✔
110
        if (match) {
115!
111
            type = match[1] ?? '';
115✔
112
            paramName = match[2] ?? '';
115!
113
            description = match[3] ?? '';
115!
114
        } else {
NEW
115
            paramName = detail.trim();
×
116
        }
117
        if (paramName) {
115!
118
            optional = paramName.startsWith('[') && paramName.endsWith(']');
115✔
119
            paramName = paramName.replace(/\[|\]/g, '').trim();
115✔
120
        }
121
        return {
115✔
122
            tagName: BrsDocTagKind.Param,
123
            name: paramName,
124
            type: type,
125
            description: description,
126
            optional: optional,
127
            detail: detail
128
        };
129
    }
130

131
    private parseReturn(detail: string): BrsDocWithDescription {
132
        let match = returnRegex.exec(detail);
55✔
133
        let type = '';
55✔
134
        let description = '';
55✔
135
        if (match) {
55!
136
            type = match[1] ?? '';
55✔
137
            description = match[2] ?? '';
55!
138
        }
139
        return {
55✔
140
            tagName: BrsDocTagKind.Return,
141
            type: type,
142
            description: description,
143
            detail: detail
144
        };
145
    }
146

147
    private parseType(detail: string): BrsDocWithType {
148
        let match = typeTagRegex.exec(detail);
13✔
149
        let type = '';
13✔
150
        if (match) {
13!
151
            if (match[1]) {
13!
152
                type = match[1] ?? '';
13!
153
            }
154
        }
155
        return {
13✔
156
            tagName: BrsDocTagKind.Type,
157
            type: type,
158
            detail: detail
159
        };
160
    }
161
}
162

163
export class BrightScriptDoc {
1✔
164

165
    protected _description: string;
166

167
    public tags = [] as BrsDocTag[];
28,506✔
168

169
    constructor(
170
        public readonly documentation: string
28,506✔
171
    ) {
172
    }
173

174
    set description(value: string) {
175
        this._description = value;
561✔
176
    }
177

178
    get description() {
179
        const descTag = this.tags.find((tag) => {
9✔
180
            return tag.tagName === BrsDocTagKind.Description;
10✔
181
        });
182

183
        let result = this._description ?? '';
9!
184
        if (descTag) {
9✔
185
            const descTagDetail = descTag.detail;
2✔
186
            result = result ? result + '\n' + descTagDetail : descTagDetail;
2!
187
        }
188
        return result.trim();
9✔
189
    }
190

191
    getParam(name: string) {
192
        const lowerName = name.toLowerCase();
5,141✔
193
        return this.tags.find((tag) => {
5,141✔
194
            return tag.tagName === BrsDocTagKind.Param && (tag as BrsDocParamTag).name.toLowerCase() === lowerName;
154✔
195
        }) as BrsDocParamTag;
196
    }
197

198
    getVar(name: string) {
199
        const lowerName = name.toLowerCase();
1✔
200
        return this.tags.find((tag) => {
1✔
201
            return tag.tagName === BrsDocTagKind.Var && (tag as BrsDocParamTag).name.toLowerCase() === lowerName;
1✔
202
        }) as BrsDocParamTag;
203
    }
204

205

206
    getReturn() {
207
        return this.tags.find((tag) => {
3,787✔
208
            return tag.tagName === BrsDocTagKind.Return || tag.tagName === 'returns';
62✔
209
        }) as BrsDocWithDescription;
210
    }
211

212
    getTypeTag() {
213
        return this.tags.find((tag) => {
1,263✔
214
            return tag.tagName === BrsDocTagKind.Type;
7✔
215
        }) as BrsDocWithType;
216
    }
217

218
    getTag(tagName: string) {
219
        return this.tags.find((tag) => {
3✔
220
            return tag.tagName === tagName;
3✔
221
        });
222
    }
223

224
    getAllTags(tagName: string) {
225
        return this.tags.filter((tag) => {
3✔
226
            return tag.tagName === tagName;
14✔
227
        });
228
    }
229

230
    getParamBscType(name: string, nodeContext: AstNode, options: GetSymbolTypeOptions) {
231
        const param = this.getParam(name);
5,117✔
232

233
        return this.getTypeFromContext(param?.type, nodeContext, options);
5,117✔
234
    }
235

236
    getVarBscType(name: string, nodeContext: AstNode, options: GetSymbolTypeOptions) {
NEW
237
        const param = this.getVar(name);
×
238

NEW
239
        return this.getTypeFromContext(param?.type, nodeContext, options);
×
240
    }
241

242
    getReturnBscType(nodeContext: AstNode, options: GetSymbolTypeOptions) {
243
        const retTag = this.getReturn();
3,776✔
244

245
        return this.getTypeFromContext(retTag?.type, nodeContext, options);
3,776✔
246
    }
247

248

249
    getTypeTagBscType(nodeContext: AstNode, options: GetSymbolTypeOptions) {
250
        const retTag = this.getTypeTag();
1,262✔
251
        return this.getTypeFromContext(retTag?.type, nodeContext, options);
1,262✔
252
    }
253

254
    getTypeFromContext(typeName: string, nodeContext: AstNode, options: GetSymbolTypeOptions) {
255
        // TODO: Add support for union types here
256
        const topSymbolTable = nodeContext?.getSymbolTable();
10,155!
257
        if (!topSymbolTable || !typeName) {
10,155✔
258
            return undefined;
10,082✔
259
        }
260
        const fullName = typeName;
73✔
261
        const parts = typeName.split('.');
73✔
262
        const optionsToUse = {
73✔
263
            ...options,
264
            flags: SymbolTypeFlag.typetime,
265
            fullName: fullName,
266
            typeChain: undefined
267
        };
268
        let result = topSymbolTable.getSymbolType(parts.shift(), optionsToUse);
73✔
269
        while (result && parts.length > 0) {
73✔
270
            result = result.getMemberType(parts.shift(), optionsToUse);
7✔
271
        }
272
        return result;
73✔
273
    }
274
}
275

276
export interface BrsDocTag {
277
    tagName: string;
278
    detail?: string;
279
}
280
export interface BrsDocWithType extends BrsDocTag {
281
    type?: string;
282
}
283

284
export interface BrsDocWithDescription extends BrsDocWithType {
285
    description?: string;
286
}
287

288
export interface BrsDocParamTag extends BrsDocWithDescription {
289
    name: string;
290
    optional?: boolean;
291
}
292

293

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