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

rokucommunity / brighterscript / #13186

15 Oct 2024 11:38AM UTC coverage: 89.043% (+2.2%) from 86.831%
#13186

push

web-flow
Merge 2b9d8bd39 into 1519a87aa

7212 of 8538 branches covered (84.47%)

Branch coverage included in aggregate %.

10 of 10 new or added lines in 1 file covered. (100.0%)

539 existing lines in 53 files now uncovered.

9619 of 10364 relevant lines covered (92.81%)

1781.3 hits per line

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

75.49
/src/parser/SGTypes.ts
1
import { SourceNode } from 'source-map';
1✔
2
import type { Range } from 'vscode-languageserver';
3
import { createSGAttribute } from '../astUtils/creators';
1✔
4
import { isSGChildren, isSGCustomization, isSGField, isSGFunction, isSGInterface, isSGScript } from '../astUtils/xml';
1✔
5
import type { FileReference, TranspileResult } from '../interfaces';
6
import util from '../util';
1✔
7
import type { TranspileState } from './TranspileState';
8

9
export interface SGToken {
10
    text: string;
11
    range?: Range;
12
}
13

14
export interface SGAttribute {
15
    key: SGToken;
16
    openQuote?: SGToken;
17
    value: SGToken;
18
    closeQuote?: SGToken;
19
    range?: Range;
20
}
21

22
export class SGTag {
1✔
23

24
    constructor(
25
        public tag: SGToken,
667✔
26
        public attributes: SGAttribute[] = [],
667✔
27
        public range?: Range
667✔
28
    ) { }
29

30
    get id() {
31
        return this.getAttributeValue('id');
9✔
32
    }
33
    set id(value: string) {
UNCOV
34
        this.setAttribute('id', value);
×
35
    }
36

37
    getAttribute(name: string): SGAttribute | undefined {
38
        return this.attributes.find(att => att.key.text.toLowerCase() === name);
1,879✔
39
    }
40

41
    getAttributeValue(name: string): string | undefined {
42
        return this.getAttribute(name.toLowerCase())?.value?.text;
680✔
43
    }
44

45
    setAttribute(name: string, value: string) {
46
        const attr = this.getAttribute(name);
58✔
47
        if (attr) {
58!
UNCOV
48
            if (value) {
×
UNCOV
49
                attr.value = { text: value };
×
50
                attr.range = undefined;
×
51
            } else {
UNCOV
52
                this.attributes.splice(this.attributes.indexOf(attr), 1);
×
53
            }
54
        } else if (value) {
58!
55
            this.attributes.push({
58✔
56
                key: { text: name },
57
                value: { text: value }
58
            });
59
        }
60
    }
61

62
    transpile(state: TranspileState): SourceNode {
63
        return new SourceNode(null, null, state.srcPath, [
83✔
64
            state.indentText,
65
            '<',
66
            state.transpileToken(this.tag),
67
            ...this.transpileAttributes(state, this.attributes),
68
            ...this.transpileBody(state)
69
        ]);
70
    }
71

72
    protected transpileBody(state: TranspileState): (string | SourceNode)[] {
73
        return [' />\n'];
53✔
74
    }
75

76
    protected transpileAttributes(state: TranspileState, attributes: SGAttribute[]): (string | SourceNode)[] {
77
        const result = [];
104✔
78
        for (const attr of attributes) {
104✔
79
            result.push(
198✔
80
                ' ',
81
                state.transpileToken(attr.key),
82
                '=',
83
                state.transpileToken(attr.openQuote ?? { text: '"' }),
594✔
84
                state.transpileToken(attr.value),
85
                state.transpileToken(attr.closeQuote ?? { text: '"' })
594✔
86
            );
87
        }
88
        return result;
104✔
89
    }
90
}
91

92
export class SGProlog extends SGTag {
1✔
93

94
    transpile(state: TranspileState) {
95
        return new SourceNode(null, null, state.srcPath, [
21✔
96
            '<?xml',
97
            ...this.transpileAttributes(state, this.attributes),
98
            ' ?>\n'
99
        ]);
100
    }
101
}
102

103
export class SGNode extends SGTag {
1✔
104

105
    constructor(
106
        tag: SGToken,
107
        attributes?: SGAttribute[],
108
        public children: SGNode[] = [],
17!
109
        range?: Range
110
    ) {
111
        super(tag, attributes, range);
17✔
112
    }
113

114
    protected transpileBody(state: TranspileState): (string | SourceNode)[] {
115
        if (this.children.length > 0) {
11✔
116
            const body: (string | SourceNode)[] = ['>\n'];
5✔
117
            state.blockDepth++;
5✔
118
            body.push(...this.children.map(node => node.transpile(state)));
5✔
119
            state.blockDepth--;
5✔
120
            body.push(state.indentText, '</', this.tag.text, '>\n');
5✔
121
            return body;
5✔
122
        } else {
123
            return super.transpileBody(state);
6✔
124
        }
125
    }
126
}
127

128
export class SGChildren extends SGNode {
1✔
129

130
    constructor(
131
        tag: SGToken = { text: 'children' },
×
132
        children: SGNode[] = [],
×
133
        range?: Range
134
    ) {
135
        super(tag, [], children, range);
7✔
136
    }
137
}
138

139
export class SGScript extends SGTag {
1✔
140

141
    constructor(
142
        tag: SGToken = { text: 'script' },
29✔
143
        attributes?: SGAttribute[],
144
        public cdata?: SGToken,
198✔
145
        range?: Range
146
    ) {
147
        super(tag, attributes, range);
198✔
148
        if (!attributes) {
198✔
149
            this.type = 'text/brightscript';
29✔
150
        }
151
    }
152

153
    get type() {
154
        return this.getAttributeValue('type');
161✔
155
    }
156
    set type(value: string) {
157
        this.setAttribute('type', value);
29✔
158
    }
159

160
    get uri() {
161
        return this.getAttributeValue('uri');
152✔
162
    }
163
    set uri(value: string) {
164
        this.setAttribute('uri', value);
29✔
165
    }
166

167
    protected transpileBody(state: TranspileState): (string | SourceNode)[] {
168
        if (this.cdata) {
44✔
169
            return [
1✔
170
                '>',
171
                state.transpileToken(this.cdata),
172
                '</',
173
                this.tag.text,
174
                '>\n'
175
            ];
176
        } else {
177
            return super.transpileBody(state);
43✔
178
        }
179
    }
180

181
    protected transpileAttributes(state: TranspileState, attributes: SGAttribute[]): (string | SourceNode)[] {
182
        const modifiedAttributes = [] as SGAttribute[];
44✔
183
        let foundType = false;
44✔
184
        const bsExtensionRegexp = /\.bs$/i;
44✔
185

186
        for (const attr of attributes) {
44✔
187
            const lowerKey = attr.key.text.toLowerCase();
86✔
188
            if (lowerKey === 'uri' && bsExtensionRegexp.exec(attr.value.text)) {
86✔
189
                modifiedAttributes.push(
11✔
190
                    util.cloneSGAttribute(attr, attr.value.text.replace(bsExtensionRegexp, '.brs'))
191
                );
192
            } else if (lowerKey === 'type') {
75✔
193
                foundType = true;
43✔
194
                if (attr.value.text.toLowerCase().endsWith('brighterscript')) {
43✔
195
                    modifiedAttributes.push(
4✔
196
                        util.cloneSGAttribute(attr, 'text/brightscript')
197
                    );
198
                } else {
199
                    modifiedAttributes.push(attr);
39✔
200
                }
201
            } else {
202
                modifiedAttributes.push(attr);
32✔
203
            }
204
        }
205
        if (!foundType) {
44✔
206
            modifiedAttributes.push(
1✔
207
                createSGAttribute('type', 'text/brightscript')
208
            );
209
        }
210
        return super.transpileAttributes(state, modifiedAttributes);
44✔
211
    }
212
}
213

214
export class SGField extends SGTag {
1✔
215

216
    constructor(
217
        tag: SGToken = { text: 'field' },
×
218
        attributes: SGAttribute[] = [],
×
219
        range?: Range
220
    ) {
221
        super(tag, attributes, range);
11✔
222
    }
223

224
    get type() {
225
        return this.getAttributeValue('type');
9✔
226
    }
227
    set type(value: string) {
UNCOV
228
        this.setAttribute('type', value);
×
229
    }
230

231
    get alias() {
232
        return this.getAttributeValue('alias');
3✔
233
    }
234
    set alias(value: string) {
UNCOV
235
        this.setAttribute('alias', value);
×
236
    }
237

238
    get value() {
UNCOV
239
        return this.getAttributeValue('value');
×
240
    }
241
    set value(value: string) {
UNCOV
242
        this.setAttribute('value', value);
×
243
    }
244

245
    get onChange() {
246
        return this.getAttributeValue('onChange');
9✔
247
    }
248
    set onChange(value: string) {
UNCOV
249
        this.setAttribute('onChange', value);
×
250
    }
251

252
    get alwaysNotify() {
UNCOV
253
        return this.getAttributeValue('alwaysNotify');
×
254
    }
255
    set alwaysNotify(value: string) {
UNCOV
256
        this.setAttribute('alwaysNotify', value);
×
257
    }
258
}
259

260
export const SGFieldTypes = [
1✔
261
    'integer', 'int', 'longinteger', 'float', 'string', 'str', 'boolean', 'bool',
262
    'vector2d', 'color', 'time', 'uri', 'node', 'floatarray', 'intarray', 'boolarray',
263
    'stringarray', 'vector2darray', 'colorarray', 'timearray', 'nodearray', 'assocarray',
264
    'array', 'roarray', 'rect2d', 'rect2darray'
265
];
266

267
export class SGFunction extends SGTag {
1✔
268

269
    constructor(
270
        tag: SGToken = { text: 'function' },
×
271
        attributes: SGAttribute[] = [],
×
272
        range?: Range
273
    ) {
274
        super(tag, attributes, range);
24✔
275
    }
276

277
    get name() {
278
        return this.getAttributeValue('name');
37✔
279
    }
280
    set name(value: string) {
UNCOV
281
        this.setAttribute('name', value);
×
282
    }
283
}
284

285
export class SGInterface extends SGTag {
1✔
286

287
    fields: SGField[] = [];
20✔
288
    functions: SGFunction[] = [];
20✔
289

290
    constructor(
291
        tag: SGToken = { text: 'interface' },
×
292
        content?: SGTag[],
293
        range?: Range
294
    ) {
295
        super(tag, [], range);
20✔
296
        if (content) {
20!
297
            for (const tag of content) {
20✔
298
                if (isSGField(tag)) {
35✔
299
                    this.fields.push(tag);
11✔
300
                } else if (isSGFunction(tag)) {
24!
301
                    this.functions.push(tag);
24✔
302
                }
303
            }
304
        }
305
    }
306

307
    getField(id: string) {
UNCOV
308
        return this.fields.find(field => field.id === id);
×
309
    }
310
    setField(id: string, type: string, onChange?: string, alwaysNotify?: boolean, alias?: string) {
UNCOV
311
        let field = this.getField(id);
×
UNCOV
312
        if (!field) {
×
UNCOV
313
            field = new SGField();
×
UNCOV
314
            field.id = id;
×
UNCOV
315
            this.fields.push(field);
×
316
        }
UNCOV
317
        field.type = type;
×
UNCOV
318
        field.onChange = onChange;
×
UNCOV
319
        if (alwaysNotify === undefined) {
×
UNCOV
320
            field.alwaysNotify = undefined;
×
321
        } else {
UNCOV
322
            field.alwaysNotify = alwaysNotify ? 'true' : 'false';
×
323
        }
UNCOV
324
        field.alias = alias;
×
325
    }
326

327
    getFunction(name: string) {
UNCOV
328
        return this.functions.find(field => field.name === name);
×
329
    }
330
    setFunction(name: string) {
UNCOV
331
        let func = this.getFunction(name);
×
UNCOV
332
        if (!func) {
×
UNCOV
333
            func = new SGFunction();
×
UNCOV
334
            func.name = name;
×
UNCOV
335
            this.functions.push(func);
×
336
        }
337
    }
338

339
    protected transpileBody(state: TranspileState): (string | SourceNode)[] {
340
        const body: (string | SourceNode)[] = ['>\n'];
2✔
341
        state.blockDepth++;
2✔
342
        if (this.fields.length > 0) {
2!
343
            body.push(...this.fields.map(node => node.transpile(state)));
2✔
344
        }
345
        if (this.functions.length > 0) {
2!
346
            body.push(...this.functions.map(node => node.transpile(state)));
2✔
347
        }
348
        state.blockDepth--;
2✔
349
        body.push(state.indentText, '</', this.tag.text, '>\n');
2✔
350
        return body;
2✔
351
    }
352
}
353

354
export class SGComponent extends SGTag {
1✔
355
    constructor(
356
        tag: SGToken = { text: 'component' },
×
357
        attributes?: SGAttribute[],
358
        content?: SGTag[],
359
        range?: Range
360
    ) {
361
        super(tag, attributes, range);
207✔
362
        if (content) {
207!
363
            for (const tag of content) {
207✔
364
                if (isSGInterface(tag)) {
197✔
365
                    this.api = tag;
20✔
366
                } else if (isSGScript(tag)) {
177✔
367
                    this.scripts.push(tag);
168✔
368
                } else if (isSGChildren(tag)) {
9✔
369
                    this.children = tag;
7✔
370
                } else if (isSGCustomization(tag)) {
2!
371
                    this.customizations.push(tag);
2✔
372
                }
373
            }
374
        }
375
    }
376

377
    public api: SGInterface;
378

379
    public scripts: SGScript[] = [];
207✔
380

381
    public children: SGChildren;
382

383
    public customizations: SGNode[] = [];
207✔
384

385
    get name() {
386
        return this.getAttributeValue('name');
150✔
387
    }
388
    set name(value: string) {
UNCOV
389
        this.setAttribute('name', value);
×
390
    }
391

392
    get extends() {
393
        return this.getAttributeValue('extends');
150✔
394
    }
395
    set extends(value: string) {
UNCOV
396
        this.setAttribute('extends', value);
×
397
    }
398

399
    protected transpileBody(state: TranspileState): (string | SourceNode)[] {
400
        const body: (string | SourceNode)[] = ['>\n'];
22✔
401
        state.blockDepth++;
22✔
402
        if (this.api) {
22✔
403
            body.push(this.api.transpile(state));
2✔
404
        }
405
        if (this.scripts.length > 0) {
22!
406
            body.push(...this.scripts.map(node => node.transpile(state)));
44✔
407
        }
408
        if (this.children) {
22✔
409
            body.push(this.children.transpile(state));
4✔
410
        }
411
        if (this.customizations.length > 0) {
22✔
412
            body.push(...this.customizations.map(node => node.transpile(state)));
2✔
413
        }
414
        state.blockDepth--;
22✔
415
        body.push(state.indentText, '</', this.tag.text, '>\n');
22✔
416
        return body;
22✔
417
    }
418
}
419

420
export interface SGReferences {
421
    name?: SGToken;
422
    extends?: SGToken;
423
    scriptTagImports: Pick<FileReference, 'pkgPath' | 'text' | 'filePathRange'>[];
424
}
425

426
export class SGAst {
1✔
427

428
    constructor(
429
        public prolog?: SGProlog,
504✔
430
        public root?: SGTag,
504✔
431
        public component?: SGComponent
504✔
432
    ) {
433
    }
434

435
    transpile(state: TranspileState) {
436
        const chunks = [] as TranspileResult;
22✔
437
        //write XML prolog
438
        if (this.prolog) {
22✔
439
            chunks.push(this.prolog.transpile(state));
21✔
440
        }
441
        if (this.component) {
22!
442
            //write content
443
            chunks.push(this.component.transpile(state));
22✔
444
        }
445
        return chunks;
22✔
446
    }
447
}
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