brighterscript-xml-plugin 0.3.0 → 1.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -3
- package/dist/SGXmlCompletionProvider.js +4 -4
- package/dist/SGXmlDiagnostics.js +12 -9
- package/dist/SGXmlValidator.js +28 -27
- package/dist/index.js +17 -3
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
# brighterscript-xml-plugin
|
|
2
|
+
|
|
2
3
|
## BrighterScript plugin for SceneGraph XML completions
|
|
3
4
|
|
|
4
5
|
### It Does:
|
|
6
|
+
|
|
5
7
|
- suggest components
|
|
6
8
|
- suggest component fields
|
|
9
|
+
- validate field values
|
|
10
|
+
- validate field names
|
|
11
|
+
- validate child components
|
|
7
12
|
|
|
8
13
|
### It Does Not:
|
|
9
|
-
|
|
14
|
+
|
|
10
15
|
- suggest interface elements like `field` and `function`
|
|
11
16
|
|
|
12
17
|
### Installation
|
|
18
|
+
|
|
13
19
|
```
|
|
14
20
|
npm i brighterscript-xml-plugin
|
|
15
21
|
```
|
|
22
|
+
|
|
16
23
|
`bsconfig.json`
|
|
24
|
+
|
|
17
25
|
```
|
|
18
26
|
{
|
|
19
27
|
...
|
|
@@ -24,12 +32,13 @@ npm i brighterscript-xml-plugin
|
|
|
24
32
|
```
|
|
25
33
|
|
|
26
34
|
### Usage
|
|
35
|
+
|
|
27
36
|
SceneGraph Component completions
|
|
28
37
|

|
|
29
38
|
|
|
30
|
-
|
|
39
|
+
- validates children components
|
|
31
40
|
- provides both built-in Roku SceneGraph components and custom project components
|
|
32
41
|
- provides built-in fields and custom fields
|
|
33
42
|
- auto suggestions for fields when typing inside an element tag
|
|
34
43
|
- use completions shortcut to trigger manually
|
|
35
|
-
- component completions on `<` do not fill automatically, pretty sure this is something that needs to be configured at the vscode extension level.
|
|
44
|
+
- component completions on `<` do not fill automatically, pretty sure this is something that needs to be configured at the vscode extension level.
|
|
@@ -31,7 +31,7 @@ class SGXmlCompletionProvider {
|
|
|
31
31
|
let result = [];
|
|
32
32
|
if (projectComponent) {
|
|
33
33
|
result.push(...this.getCompletionsFromXmlFile(projectComponent.file));
|
|
34
|
-
const extendsName = (_b = (_a = projectComponent.file.ast.
|
|
34
|
+
const extendsName = (_b = (_a = projectComponent.file.ast.componentElement) === null || _a === void 0 ? void 0 : _a.extends) !== null && _b !== void 0 ? _b : 'Node';
|
|
35
35
|
if (extendsName) {
|
|
36
36
|
result.push(...this.getAllAvailableFields(extendsName));
|
|
37
37
|
}
|
|
@@ -50,7 +50,7 @@ class SGXmlCompletionProvider {
|
|
|
50
50
|
}
|
|
51
51
|
getCompletionsFromXmlFile(xmlFile) {
|
|
52
52
|
var _a, _b, _c;
|
|
53
|
-
return ((_c = (_b = (_a = xmlFile.ast.
|
|
53
|
+
return ((_c = (_b = (_a = xmlFile.ast.componentElement) === null || _a === void 0 ? void 0 : _a.interfaceElement) === null || _b === void 0 ? void 0 : _b.fields.map((f) => {
|
|
54
54
|
return {
|
|
55
55
|
label: f.id,
|
|
56
56
|
detail: `${f.type}${f.value ? `: ${f.value}` : ''}`,
|
|
@@ -90,14 +90,14 @@ class SGXmlCompletionProvider {
|
|
|
90
90
|
let result = [];
|
|
91
91
|
if (component) {
|
|
92
92
|
result = [
|
|
93
|
-
...((_c = (_b = (_a = component.file.ast.
|
|
93
|
+
...((_c = (_b = (_a = component.file.ast.componentElement) === null || _a === void 0 ? void 0 : _a.interfaceElement) === null || _b === void 0 ? void 0 : _b.fields.map((f) => {
|
|
94
94
|
return {
|
|
95
95
|
name: f.id,
|
|
96
96
|
type: f.type,
|
|
97
97
|
sortText: '0_' + f.id,
|
|
98
98
|
};
|
|
99
99
|
})) !== null && _c !== void 0 ? _c : []),
|
|
100
|
-
...this.getAllAvailableFields((_e = (_d = component.file.ast.
|
|
100
|
+
...this.getAllAvailableFields((_e = (_d = component.file.ast.componentElement) === null || _d === void 0 ? void 0 : _d.extends) !== null && _e !== void 0 ? _e : '').map((f) => {
|
|
101
101
|
return Object.assign(Object.assign({}, f), { sortText: '1_' + f.sortText });
|
|
102
102
|
}),
|
|
103
103
|
];
|
package/dist/SGXmlDiagnostics.js
CHANGED
|
@@ -7,24 +7,27 @@ exports.SGXmlDiagnostics = {
|
|
|
7
7
|
message: `Unknown component: ${componentName}`,
|
|
8
8
|
severity: brighterscript_1.DiagnosticSeverity.Error,
|
|
9
9
|
code: 'SG1001',
|
|
10
|
-
|
|
10
|
+
location: element.tokens.startTagName.location
|
|
11
11
|
}),
|
|
12
12
|
InvalidSystemAttribute: (attributeName, component, attr) => ({
|
|
13
13
|
message: `Field "${attributeName}" not found on component "${component}"`,
|
|
14
14
|
severity: brighterscript_1.DiagnosticSeverity.Error,
|
|
15
15
|
code: 'SG1002',
|
|
16
|
-
|
|
17
|
-
}),
|
|
18
|
-
InvalidAttributeValue: (attributeName, expectedType, attr) => ({
|
|
19
|
-
message: `Field value "${attributeName}" does not match type ${expectedType}`,
|
|
20
|
-
severity: brighterscript_1.DiagnosticSeverity.Error,
|
|
21
|
-
code: 'SG1003',
|
|
22
|
-
range: attr.value.range
|
|
16
|
+
location: attr.tokens.key.location
|
|
23
17
|
}),
|
|
18
|
+
InvalidAttributeValue: (attributeName, expectedType, attr) => {
|
|
19
|
+
var _a;
|
|
20
|
+
return ({
|
|
21
|
+
message: `Field value "${attributeName}" does not match type ${expectedType}`,
|
|
22
|
+
severity: brighterscript_1.DiagnosticSeverity.Error,
|
|
23
|
+
code: 'SG1003',
|
|
24
|
+
location: (_a = attr.tokens.value) === null || _a === void 0 ? void 0 : _a.location
|
|
25
|
+
});
|
|
26
|
+
},
|
|
24
27
|
AttributeNameCaseMismatch: (attributeName, expectedName, attr) => ({
|
|
25
28
|
message: `Field "${attributeName}" should match case for "${expectedName}"`,
|
|
26
29
|
severity: brighterscript_1.DiagnosticSeverity.Warning,
|
|
27
30
|
code: 'SG2001',
|
|
28
|
-
|
|
31
|
+
location: attr.tokens.key.location
|
|
29
32
|
})
|
|
30
33
|
};
|
package/dist/SGXmlValidator.js
CHANGED
|
@@ -13,64 +13,65 @@ class SGXmlValidator {
|
|
|
13
13
|
this.program = program;
|
|
14
14
|
}
|
|
15
15
|
validateXmlFile(event) {
|
|
16
|
-
var _a, _b;
|
|
16
|
+
var _a, _b, _c, _d;
|
|
17
17
|
const file = event.file;
|
|
18
|
-
const diags = (_b = (_a = file === null || file === void 0 ? void 0 : file.ast) === null || _a === void 0 ? void 0 : _a.
|
|
19
|
-
return this.validateComponent(child)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
});
|
|
23
|
-
event.program.addDiagnostics(diags || []);
|
|
18
|
+
const diags = (_d = (_c = (_b = (_a = file === null || file === void 0 ? void 0 : file.ast) === null || _a === void 0 ? void 0 : _a.componentElement) === null || _b === void 0 ? void 0 : _b.childrenElement) === null || _c === void 0 ? void 0 : _c.elements.flatMap((child) => {
|
|
19
|
+
return this.validateComponent(child);
|
|
20
|
+
})) !== null && _d !== void 0 ? _d : [];
|
|
21
|
+
event.program.diagnostics.register(diags);
|
|
24
22
|
}
|
|
25
23
|
validateComponent(element) {
|
|
26
24
|
const diags = [];
|
|
25
|
+
if (!element.tagName) {
|
|
26
|
+
return diags;
|
|
27
|
+
}
|
|
27
28
|
if (this.validateComponentExists(element)) {
|
|
28
29
|
diags.push(...this.validateNodeElement(element));
|
|
29
|
-
element.
|
|
30
|
+
element.elements.forEach((child) => {
|
|
30
31
|
diags.push(...this.validateComponent(child));
|
|
31
32
|
});
|
|
32
33
|
}
|
|
33
34
|
else {
|
|
34
|
-
this.program.logger.error(`Unknown component: ${element.
|
|
35
|
-
const diag = SGXmlDiagnostics_1.SGXmlDiagnostics.UnknownComponent(element.
|
|
35
|
+
this.program.logger.error(`Unknown component: ${element.tagName}`, element);
|
|
36
|
+
const diag = SGXmlDiagnostics_1.SGXmlDiagnostics.UnknownComponent(element.tagName, element);
|
|
36
37
|
diags.push(diag);
|
|
37
38
|
}
|
|
38
39
|
return diags;
|
|
39
40
|
}
|
|
40
41
|
validateNodeElement(element) {
|
|
41
42
|
const diags = [];
|
|
42
|
-
const fields = this.findAllFields(element.
|
|
43
|
+
const fields = this.findAllFields(element.tagName);
|
|
43
44
|
element.attributes.forEach((attr) => {
|
|
44
45
|
const field = fields.find(a => {
|
|
45
|
-
return a.id.toLowerCase() === attr.key.
|
|
46
|
+
return a.id.toLowerCase() === attr.key.toLowerCase();
|
|
46
47
|
});
|
|
47
48
|
if (!field) {
|
|
48
|
-
this.program.logger.error(`Invalid system attribute: ${attr.key
|
|
49
|
-
diags.push(SGXmlDiagnostics_1.SGXmlDiagnostics.InvalidSystemAttribute(attr.key
|
|
49
|
+
this.program.logger.error(`Invalid system attribute: ${attr.key}`, attr);
|
|
50
|
+
diags.push(SGXmlDiagnostics_1.SGXmlDiagnostics.InvalidSystemAttribute(attr.key, element.tagName, attr));
|
|
50
51
|
return;
|
|
51
52
|
}
|
|
52
|
-
if (field.type && !FieldTypeValidator_1.default.validateFieldType(attr.value
|
|
53
|
-
this.program.logger.error(`Invalid value for attribute: ${attr.key
|
|
54
|
-
diags.push(SGXmlDiagnostics_1.SGXmlDiagnostics.InvalidAttributeValue(attr.key
|
|
53
|
+
if (field.type && !FieldTypeValidator_1.default.validateFieldType(attr.value, field.type)) {
|
|
54
|
+
this.program.logger.error(`Invalid value for attribute: ${attr.key}`, attr);
|
|
55
|
+
diags.push(SGXmlDiagnostics_1.SGXmlDiagnostics.InvalidAttributeValue(attr.key, field.type, attr));
|
|
55
56
|
}
|
|
56
|
-
if (field.id !== attr.key
|
|
57
|
-
this.program.logger.warn(`Field "${attr.key
|
|
58
|
-
diags.push(SGXmlDiagnostics_1.SGXmlDiagnostics.AttributeNameCaseMismatch(attr.key
|
|
57
|
+
if (field.id !== attr.key) {
|
|
58
|
+
this.program.logger.warn(`Field "${attr.key}" should be "${field.id}"`, attr);
|
|
59
|
+
diags.push(SGXmlDiagnostics_1.SGXmlDiagnostics.AttributeNameCaseMismatch(attr.key, field.id, attr));
|
|
59
60
|
}
|
|
60
61
|
});
|
|
61
62
|
return diags;
|
|
62
63
|
}
|
|
63
64
|
validateComponentExists(element) {
|
|
64
|
-
let component = this.program.getComponent(element.
|
|
65
|
+
let component = this.program.getComponent(element.tagName) || systemNodes[element.tagName.toLocaleLowerCase()];
|
|
65
66
|
return !!component;
|
|
66
67
|
}
|
|
67
68
|
findAllFields(name) {
|
|
68
|
-
var _a, _b, _c, _d, _e
|
|
69
|
+
var _a, _b, _c, _d, _e;
|
|
69
70
|
const fields = [];
|
|
70
71
|
let component = (_a = this.program.getComponent(name)) === null || _a === void 0 ? void 0 : _a.file;
|
|
71
72
|
if (component) {
|
|
72
|
-
if ((_c = (_b = component.ast.
|
|
73
|
-
fields.push(...component.ast.
|
|
73
|
+
if ((_c = (_b = component.ast.componentElement) === null || _b === void 0 ? void 0 : _b.interfaceElement) === null || _c === void 0 ? void 0 : _c.fields) {
|
|
74
|
+
fields.push(...component.ast.componentElement.interfaceElement.fields.map(f => {
|
|
74
75
|
return {
|
|
75
76
|
id: f.id,
|
|
76
77
|
type: f.type,
|
|
@@ -78,13 +79,13 @@ class SGXmlValidator {
|
|
|
78
79
|
};
|
|
79
80
|
}) || []);
|
|
80
81
|
}
|
|
81
|
-
if ((_d = component.ast.
|
|
82
|
-
fields.push(...this.findAllFields(
|
|
82
|
+
if ((_d = component.ast.componentElement) === null || _d === void 0 ? void 0 : _d.extends) {
|
|
83
|
+
fields.push(...this.findAllFields(component.ast.componentElement.extends));
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
component = systemNodes[name.toLocaleLowerCase()];
|
|
86
87
|
if (component) {
|
|
87
|
-
fields.push(...((
|
|
88
|
+
fields.push(...((_e = component.fields) === null || _e === void 0 ? void 0 : _e.map(f => {
|
|
88
89
|
return {
|
|
89
90
|
id: f.name,
|
|
90
91
|
type: f.type
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.BscXmlPlugin = void 0;
|
|
4
7
|
const brighterscript_1 = require("brighterscript");
|
|
5
8
|
const SGXmlCompletionProvider_1 = require("./SGXmlCompletionProvider");
|
|
6
9
|
const SGXmlValidator_1 = require("./SGXmlValidator");
|
|
10
|
+
const SGParser_1 = __importDefault(require("brighterscript/dist/parser/SGParser"));
|
|
11
|
+
//@ts-ignore
|
|
12
|
+
SGParser_1.default.prototype._mapNode = SGParser_1.default.prototype.mapNode;
|
|
13
|
+
//@ts-ignore
|
|
14
|
+
SGParser_1.default.prototype.mapNode = function (node) {
|
|
15
|
+
if (!node.children.Name) {
|
|
16
|
+
node.children.Name = [];
|
|
17
|
+
}
|
|
18
|
+
//@ts-ignore
|
|
19
|
+
return this._mapNode(node);
|
|
20
|
+
};
|
|
7
21
|
class BscXmlPlugin {
|
|
8
22
|
constructor() {
|
|
9
23
|
this.name = 'bsc-xml-plugin';
|
|
@@ -20,13 +34,13 @@ class BscXmlPlugin {
|
|
|
20
34
|
}
|
|
21
35
|
return this.provider;
|
|
22
36
|
}
|
|
23
|
-
|
|
37
|
+
provideCompletions(event) {
|
|
38
|
+
console.log('provideCompletions', event.file.constructor.name);
|
|
24
39
|
if ((0, brighterscript_1.isXmlFile)(event.file)) {
|
|
25
|
-
console.log(event.completions);
|
|
26
40
|
this.getProvider(event.program).process(event);
|
|
27
41
|
}
|
|
28
42
|
}
|
|
29
|
-
|
|
43
|
+
validateFile(event) {
|
|
30
44
|
if ((0, brighterscript_1.isXmlFile)(event.file)) {
|
|
31
45
|
this.getValidator(event).validateXmlFile(event);
|
|
32
46
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brighterscript-xml-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0-alpha.2",
|
|
4
4
|
"description": "BrighterScript plugin for SceneGraph XML completions",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"repository": {
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"@types/chai": "^4.3.4",
|
|
38
38
|
"@types/mocha": "^10.0.1",
|
|
39
39
|
"@types/node": "^18.15.3",
|
|
40
|
-
"brighterscript": "0.
|
|
40
|
+
"brighterscript": "^1.0.0-alpha.50",
|
|
41
41
|
"chai": "^4.3.7",
|
|
42
42
|
"mocha": "^10.2.0",
|
|
43
43
|
"rimraf": "^5.0.5",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"vscode-languageserver-types": "^3.17.3"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
|
-
"brighterscript": ">=
|
|
50
|
+
"brighterscript": ">= 1 < 2"
|
|
51
51
|
},
|
|
52
52
|
"peerDependenciesMeta": {
|
|
53
53
|
"brighterscript": {
|
|
@@ -58,4 +58,4 @@
|
|
|
58
58
|
"dist/*.js",
|
|
59
59
|
"!dist/*.spec.js"
|
|
60
60
|
]
|
|
61
|
-
}
|
|
61
|
+
}
|