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

rokucommunity / brighterscript / 28897077244

07 Jul 2026 08:38PM UTC coverage: 88.373%. First build
28897077244

Pull #1742

github

web-flow
Merge 95d7393d7 into a27b5e573
Pull Request #1742: Validate SceneGraph node types and fields in xml components

9182 of 10930 branches covered (84.01%)

Branch coverage included in aggregate %.

38 of 48 new or added lines in 3 files covered. (79.17%)

11408 of 12369 relevant lines covered (92.23%)

2030.62 hits per line

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

92.63
/src/bscPlugin/validation/XmlFileValidator.ts
1
import { DiagnosticMessages } from '../../DiagnosticMessages';
1✔
2
import type { XmlFile } from '../../files/XmlFile';
3
import type { OnFileValidateEvent } from '../../interfaces';
4
import type { SGAst, SGNode } from '../../parser/SGTypes';
5
import util from '../../util';
1✔
6
import { isValidSceneGraphFieldValue } from './FieldTypeValidator';
1✔
7

8
export class XmlFileValidator {
1✔
9
    constructor(
10
        public event: OnFileValidateEvent<XmlFile>
228✔
11
    ) {
12
    }
13

14
    public process() {
15
        util.validateTooDeepFile(this.event.file);
228✔
16
        if (this.event.file.parser.ast.root) {
228✔
17
            this.validateComponent(this.event.file.parser.ast);
227✔
18
        } else {
19
            //skip empty XML
20
        }
21
    }
22

23
    private validateComponent(ast: SGAst) {
24
        const { root, component } = ast;
227✔
25
        if (!component) {
227✔
26
            //not a SG component
27
            this.event.file.diagnostics.push({
1✔
28
                ...DiagnosticMessages.xmlComponentMissingComponentDeclaration(),
29
                range: root.range,
30
                file: this.event.file
31
            });
32
            return;
1✔
33
        }
34

35
        //component name/extends
36
        if (!component.name) {
226✔
37
            this.event.file.diagnostics.push({
9✔
38
                ...DiagnosticMessages.xmlComponentMissingNameAttribute(),
39
                range: component.tag.range,
40
                file: this.event.file
41
            });
42
        }
43
        if (!component.extends) {
226✔
44
            this.event.file.diagnostics.push({
48✔
45
                ...DiagnosticMessages.xmlComponentMissingExtendsAttribute(),
46
                range: component.tag.range,
47
                file: this.event.file
48
            });
49
        }
50

51

52
        //catch script imports with same path as the auto-imported codebehind file
53
        const scriptTagImports = this.event.file.parser.references.scriptTagImports;
226✔
54
        let explicitCodebehindScriptTag = this.event.file.program.options.autoImportComponentScript === true
226✔
55
            ? scriptTagImports.find(x => this.event.file.possibleCodebehindPkgPaths.includes(x.pkgPath))
26✔
56
            : undefined;
57
        if (explicitCodebehindScriptTag) {
226✔
58
            this.event.file.diagnostics.push({
6✔
59
                ...DiagnosticMessages.unnecessaryCodebehindScriptImport(),
60
                file: this.event.file,
61
                range: explicitCodebehindScriptTag.filePathRange
62
            });
63
        }
64

65
        //validate the node instances declared in the <children> block
66
        for (const node of component.children?.children ?? []) {
226✔
67
            this.validateNode(node);
13✔
68
        }
69
    }
70

71
    /**
72
     * Validate a single node instance and recurse into its children. Flags unknown node types, and (for
73
     * known types) unknown/mis-cased field names and clearly-invalid field values.
74
     */
75
    private validateNode(node: SGNode) {
76
        const nodeName = node.tag.text;
14✔
77
        if (this.event.program.hasSceneGraphNode(nodeName)) {
14✔
78
            this.validateNodeAttributes(node, nodeName);
13✔
79
        } else {
80
            this.event.file.diagnostics.push({
1✔
81
                ...DiagnosticMessages.xmlUnknownComponentType(nodeName),
82
                range: node.tag.range,
83
                file: this.event.file
84
            });
85
        }
86
        //recurse regardless, so nested typos are still reported
87
        for (const child of node.children ?? []) {
14!
88
            this.validateNode(child);
1✔
89
        }
90
    }
91

92
    private validateNodeAttributes(node: SGNode, nodeName: string) {
93
        const fields = this.event.program.getSceneGraphNodeFields(nodeName);
13✔
94
        //if we can't resolve any fields for a known node, don't guess (avoids mass false positives)
95
        if (fields.length === 0) {
13!
NEW
96
            return;
×
97
        }
98
        const fieldsByLowerName = new Map(fields.map(field => [field.name.toLowerCase(), field]));
444✔
99
        for (const attribute of node.attributes ?? []) {
13!
100
            const attributeName = attribute.key.text;
15✔
101
            const field = fieldsByLowerName.get(attributeName.toLowerCase());
15✔
102
            if (!field) {
15✔
103
                this.event.file.diagnostics.push({
2✔
104
                    ...DiagnosticMessages.xmlUnknownField(attributeName, nodeName),
105
                    range: attribute.key.range,
106
                    file: this.event.file
107
                });
108
                continue;
2✔
109
            }
110
            if (field.name !== attributeName) {
13✔
111
                this.event.file.diagnostics.push({
1✔
112
                    ...DiagnosticMessages.xmlFieldNameCaseMismatch(attributeName, field.name),
113
                    range: attribute.key.range,
114
                    file: this.event.file
115
                });
116
            }
117
            if (field.type && !isValidSceneGraphFieldValue(attribute.value?.text, field.type)) {
13!
118
                this.event.file.diagnostics.push({
1✔
119
                    ...DiagnosticMessages.xmlInvalidFieldValue(field.name, field.type),
120
                    range: attribute.value?.range ?? attribute.key.range,
6!
121
                    file: this.event.file
122
                });
123
            }
124
        }
125
    }
126

127
}
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