@player-tools/json-language-service 0.9.0 → 0.9.1--canary.196.4186
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/dist/cjs/index.cjs +86 -43
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/index.legacy-esm.js +86 -43
- package/dist/index.mjs +86 -43
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
- package/src/__tests__/__snapshots__/service.test.ts.snap +14 -0
- package/src/plugins/__tests__/asset-wrapper-array-plugin.test.ts +15 -0
- package/src/plugins/__tests__/duplicate-id-plugin.test.ts +89 -0
- package/src/plugins/__tests__/missing-asset-wrapper-plugin.test.ts +16 -1
- package/src/plugins/duplicate-id-plugin.ts +41 -1
- package/src/plugins/xlr-plugin.ts +32 -9
- package/src/xlr/__tests__/__snapshots__/transform.test.ts.snap +17 -3
- package/src/xlr/transforms.ts +40 -36
package/package.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"types"
|
|
7
7
|
],
|
|
8
8
|
"name": "@player-tools/json-language-service",
|
|
9
|
-
"version": "0.9.
|
|
9
|
+
"version": "0.9.1--canary.196.4186",
|
|
10
10
|
"main": "dist/cjs/index.cjs",
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@player-tools/xlr": "0.9.
|
|
13
|
-
"@player-tools/xlr-sdk": "0.9.
|
|
14
|
-
"@player-tools/xlr-utils": "0.9.
|
|
12
|
+
"@player-tools/xlr": "0.9.1--canary.196.4186",
|
|
13
|
+
"@player-tools/xlr-sdk": "0.9.1--canary.196.4186",
|
|
14
|
+
"@player-tools/xlr-utils": "0.9.1--canary.196.4186",
|
|
15
15
|
"@player-ui/player": "0.7.3",
|
|
16
16
|
"change-case": "^4.1.1",
|
|
17
17
|
"cross-fetch": "^3.0.5",
|
|
@@ -181,6 +181,20 @@ exports[`player language service > validation > throws AssetWrapper errors 1`] =
|
|
|
181
181
|
},
|
|
182
182
|
"severity": 1,
|
|
183
183
|
},
|
|
184
|
+
{
|
|
185
|
+
"message": "Expected: AssetWrapper & object literal | StaticSwitch & object literal | DynamicSwitch & object literal",
|
|
186
|
+
"range": {
|
|
187
|
+
"end": {
|
|
188
|
+
"character": 16,
|
|
189
|
+
"line": 12,
|
|
190
|
+
},
|
|
191
|
+
"start": {
|
|
192
|
+
"character": 23,
|
|
193
|
+
"line": 8,
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
"severity": 3,
|
|
197
|
+
},
|
|
184
198
|
{
|
|
185
199
|
"message": "Implicit Array -> "collection" assets is not supported.",
|
|
186
200
|
"range": {
|
|
@@ -57,6 +57,7 @@ describe("asset-wrapper-array-plugin", () => {
|
|
|
57
57
|
expect(diags?.map((d) => d.message)).toMatchInlineSnapshot(`
|
|
58
58
|
[
|
|
59
59
|
"View Validation Error - value: Does not match any of the expected types for type: 'AssetWrapperOrSwitch'",
|
|
60
|
+
"Expected: AssetWrapper & object literal | StaticSwitch & object literal | DynamicSwitch & object literal",
|
|
60
61
|
"Implicit Array -> "collection" assets is not supported.",
|
|
61
62
|
]
|
|
62
63
|
`);
|
|
@@ -77,6 +78,20 @@ describe("asset-wrapper-array-plugin", () => {
|
|
|
77
78
|
},
|
|
78
79
|
"severity": 1,
|
|
79
80
|
},
|
|
81
|
+
{
|
|
82
|
+
"message": "Expected: AssetWrapper & object literal | StaticSwitch & object literal | DynamicSwitch & object literal",
|
|
83
|
+
"range": {
|
|
84
|
+
"end": {
|
|
85
|
+
"character": 7,
|
|
86
|
+
"line": 23,
|
|
87
|
+
},
|
|
88
|
+
"start": {
|
|
89
|
+
"character": 21,
|
|
90
|
+
"line": 17,
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
"severity": 3,
|
|
94
|
+
},
|
|
80
95
|
{
|
|
81
96
|
"message": "Implicit Array -> "collection" assets is not supported.",
|
|
82
97
|
"range": {
|
|
@@ -32,6 +32,81 @@ const simpleDupIDDocument = toTextDocument(
|
|
|
32
32
|
)
|
|
33
33
|
);
|
|
34
34
|
|
|
35
|
+
const dupIDDocumentWithTemplate = toTextDocument(
|
|
36
|
+
JSON.stringify(
|
|
37
|
+
{
|
|
38
|
+
id: "test",
|
|
39
|
+
views: [
|
|
40
|
+
{
|
|
41
|
+
id: "root",
|
|
42
|
+
type: "info",
|
|
43
|
+
primaryInfo: {
|
|
44
|
+
asset: {
|
|
45
|
+
id: "root-primaryInfo-collection",
|
|
46
|
+
type: "collection",
|
|
47
|
+
template: [
|
|
48
|
+
{
|
|
49
|
+
data: "some.data",
|
|
50
|
+
output: "values",
|
|
51
|
+
value: {
|
|
52
|
+
asset: {
|
|
53
|
+
id: "root-primaryInfo-collection-values-collection",
|
|
54
|
+
type: "collection",
|
|
55
|
+
template: [
|
|
56
|
+
{
|
|
57
|
+
data: "some.other.data",
|
|
58
|
+
output: "values",
|
|
59
|
+
value: {
|
|
60
|
+
asset: {
|
|
61
|
+
id: "root-primaryInfo-collection-values-collection-values-text",
|
|
62
|
+
type: "text",
|
|
63
|
+
value: "something",
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
data: "some.other.data",
|
|
69
|
+
output: "values",
|
|
70
|
+
value: {
|
|
71
|
+
asset: {
|
|
72
|
+
id: "root-primaryInfo-collection-values-_index_collection-values_index1_-text",
|
|
73
|
+
type: "text",
|
|
74
|
+
value: "something",
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
navigation: {
|
|
88
|
+
BEGIN: "FLOW_1",
|
|
89
|
+
FLOW_1: {
|
|
90
|
+
VIEW_1: {
|
|
91
|
+
ref: "root",
|
|
92
|
+
state_type: "VIEW",
|
|
93
|
+
transitions: {
|
|
94
|
+
"*": "END_Done",
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
END_Done: {
|
|
98
|
+
state_type: "END",
|
|
99
|
+
outcome: "done",
|
|
100
|
+
},
|
|
101
|
+
startState: "VIEW_1",
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
null,
|
|
106
|
+
2
|
|
107
|
+
)
|
|
108
|
+
);
|
|
109
|
+
|
|
35
110
|
describe("duplicate-id-plugin", () => {
|
|
36
111
|
let service: PlayerLanguageService;
|
|
37
112
|
|
|
@@ -63,6 +138,20 @@ describe("duplicate-id-plugin", () => {
|
|
|
63
138
|
`);
|
|
64
139
|
});
|
|
65
140
|
|
|
141
|
+
test("finds templates with missing index segments", async () => {
|
|
142
|
+
const validations = await service.validateTextDocument(
|
|
143
|
+
dupIDDocumentWithTemplate
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
expect(validations).toHaveLength(2);
|
|
147
|
+
expect(validations?.map((v) => v.message)).toMatchInlineSnapshot(`
|
|
148
|
+
[
|
|
149
|
+
"The id for this templated elements is missing the following index segments: _index_",
|
|
150
|
+
"The id for this templated elements is missing the following index segments: _index_, _index1_",
|
|
151
|
+
]
|
|
152
|
+
`);
|
|
153
|
+
});
|
|
154
|
+
|
|
66
155
|
test("fixes dup ids", async () => {
|
|
67
156
|
const diags = await service.validateTextDocument(simpleDupIDDocument);
|
|
68
157
|
|
|
@@ -56,10 +56,11 @@ describe("missing-asset-wrapper", () => {
|
|
|
56
56
|
simpleAssetWrapperDocument
|
|
57
57
|
);
|
|
58
58
|
|
|
59
|
-
expect(validations).toHaveLength(
|
|
59
|
+
expect(validations).toHaveLength(2);
|
|
60
60
|
expect(validations?.map((v) => v.message)).toMatchInlineSnapshot(`
|
|
61
61
|
[
|
|
62
62
|
"View Validation Error - value: Does not match any of the expected types for type: 'AssetWrapperOrSwitch'",
|
|
63
|
+
"Expected: AssetWrapper & object literal | StaticSwitch & object literal | DynamicSwitch & object literal",
|
|
63
64
|
]
|
|
64
65
|
`);
|
|
65
66
|
});
|
|
@@ -85,6 +86,20 @@ describe("missing-asset-wrapper", () => {
|
|
|
85
86
|
},
|
|
86
87
|
"severity": 1,
|
|
87
88
|
},
|
|
89
|
+
{
|
|
90
|
+
"message": "Expected: AssetWrapper & object literal | StaticSwitch & object literal | DynamicSwitch & object literal",
|
|
91
|
+
"range": {
|
|
92
|
+
"end": {
|
|
93
|
+
"character": 7,
|
|
94
|
+
"line": 22,
|
|
95
|
+
},
|
|
96
|
+
"start": {
|
|
97
|
+
"character": 15,
|
|
98
|
+
"line": 18,
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
"severity": 3,
|
|
102
|
+
},
|
|
88
103
|
]
|
|
89
104
|
`);
|
|
90
105
|
|
|
@@ -1,9 +1,27 @@
|
|
|
1
1
|
import { DiagnosticSeverity } from "vscode-languageserver-types";
|
|
2
2
|
import type { AssetASTNode, ASTNode, ViewASTNode } from "../parser";
|
|
3
|
-
import { getViewNode, replaceString } from "../parser";
|
|
3
|
+
import { getViewNode, isPropertyNode, replaceString } from "../parser";
|
|
4
4
|
import type { PlayerLanguageService, PlayerLanguageServicePlugin } from "..";
|
|
5
5
|
import type { Violation, ValidationContext, ASTVisitor } from "../types";
|
|
6
6
|
|
|
7
|
+
/** Recurse up tree from node to find how many parents are templates */
|
|
8
|
+
const checkParentTemplate = (node: ASTNode, depth = 0) => {
|
|
9
|
+
if (node.parent) {
|
|
10
|
+
if (
|
|
11
|
+
isPropertyNode(node.parent) &&
|
|
12
|
+
node.parent.keyNode.value === "template"
|
|
13
|
+
) {
|
|
14
|
+
// Increase the template count each time it finds a nested template
|
|
15
|
+
|
|
16
|
+
depth += 1;
|
|
17
|
+
|
|
18
|
+
return checkParentTemplate(node.parent, depth);
|
|
19
|
+
}
|
|
20
|
+
return checkParentTemplate(node.parent, depth);
|
|
21
|
+
}
|
|
22
|
+
return depth;
|
|
23
|
+
};
|
|
24
|
+
|
|
7
25
|
/** Create an id for the node given it's path */
|
|
8
26
|
const generateID = (node?: ASTNode): string => {
|
|
9
27
|
if (!node || node.type === "view") {
|
|
@@ -84,6 +102,28 @@ const createValidationVisitor = (ctx: ValidationContext): ASTVisitor => {
|
|
|
84
102
|
const assetIDMap = viewInfo.get(view);
|
|
85
103
|
const idInfo = assetIDMap?.get(id);
|
|
86
104
|
|
|
105
|
+
const templateDepth = checkParentTemplate(assetNode);
|
|
106
|
+
|
|
107
|
+
if (templateDepth > 0) {
|
|
108
|
+
const expectedIndexElements = [];
|
|
109
|
+
for (let i = 0; i < templateDepth; i++) {
|
|
110
|
+
expectedIndexElements.push(`_index${i === 0 ? "" : i}_`);
|
|
111
|
+
}
|
|
112
|
+
const missingIndexSegments = expectedIndexElements.filter(
|
|
113
|
+
(e) => !id.includes(e)
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
if (missingIndexSegments.length !== 0) {
|
|
117
|
+
ctx.addViolation({
|
|
118
|
+
node: assetNode,
|
|
119
|
+
severity: DiagnosticSeverity.Error,
|
|
120
|
+
message: `The id for this templated elements is missing the following index segments: ${missingIndexSegments.join(
|
|
121
|
+
", "
|
|
122
|
+
)}`,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
87
127
|
if (idInfo) {
|
|
88
128
|
if (!idInfo.handled) {
|
|
89
129
|
const origViolation = createViolation(idInfo.original);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { NodeType } from "@player-tools/xlr";
|
|
2
|
-
import type {
|
|
2
|
+
import type { ValidationMessage } from "@player-tools/xlr-sdk";
|
|
3
|
+
import { ValidationSeverity, XLRSDK } from "@player-tools/xlr-sdk";
|
|
3
4
|
import type { CompletionItem } from "vscode-languageserver-types";
|
|
4
5
|
import {
|
|
5
6
|
CompletionItemKind,
|
|
@@ -17,6 +18,10 @@ import { mapFlowStateToType } from "../utils";
|
|
|
17
18
|
import type { ASTNode, ObjectASTNode } from "../parser";
|
|
18
19
|
import type { EnhancedDocumentContextWithPosition } from "../types";
|
|
19
20
|
|
|
21
|
+
function isError(issue: ValidationMessage): boolean {
|
|
22
|
+
return issue.severity === DiagnosticSeverity.Error;
|
|
23
|
+
}
|
|
24
|
+
|
|
20
25
|
/** BFS search to find a JSONC node in children of some AST Node */
|
|
21
26
|
const findErrorNode = (rootNode: ASTNode, nodeToFind: Node): ASTNode => {
|
|
22
27
|
const children: Array<ASTNode> = [rootNode];
|
|
@@ -36,6 +41,16 @@ const findErrorNode = (rootNode: ASTNode, nodeToFind: Node): ASTNode => {
|
|
|
36
41
|
return rootNode;
|
|
37
42
|
};
|
|
38
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Translates an SDK severity level to an LSP severity level
|
|
46
|
+
* Relies on both levels having the values associated to the underlying levels
|
|
47
|
+
*/
|
|
48
|
+
const translateSeverity = (
|
|
49
|
+
severity: ValidationSeverity
|
|
50
|
+
): DiagnosticSeverity => {
|
|
51
|
+
return severity as DiagnosticSeverity;
|
|
52
|
+
};
|
|
53
|
+
|
|
39
54
|
/**
|
|
40
55
|
* Create Validation walkers
|
|
41
56
|
*/
|
|
@@ -65,13 +80,17 @@ function createValidationVisitor(
|
|
|
65
80
|
assetNode.jsonNode
|
|
66
81
|
);
|
|
67
82
|
validationIssues.forEach((issue) => {
|
|
68
|
-
if (!nodesWithErrors.has(issue.node)
|
|
69
|
-
nodesWithErrors.add(issue.node);
|
|
83
|
+
if (!(nodesWithErrors.has(issue.node) && isError(issue))) {
|
|
70
84
|
ctx.addViolation({
|
|
71
85
|
node: findErrorNode(assetNode, issue.node),
|
|
72
|
-
message:
|
|
73
|
-
|
|
86
|
+
message: isError(issue)
|
|
87
|
+
? `Asset Validation Error - ${issue.type}: ${issue.message}`
|
|
88
|
+
: issue.message,
|
|
89
|
+
severity: translateSeverity(issue.severity),
|
|
74
90
|
});
|
|
91
|
+
if (isError(issue)) {
|
|
92
|
+
nodesWithErrors.add(issue.node);
|
|
93
|
+
}
|
|
75
94
|
}
|
|
76
95
|
});
|
|
77
96
|
},
|
|
@@ -92,13 +111,17 @@ function createValidationVisitor(
|
|
|
92
111
|
viewNode.jsonNode
|
|
93
112
|
);
|
|
94
113
|
validationIssues.forEach((issue) => {
|
|
95
|
-
if (!nodesWithErrors.has(issue.node)
|
|
96
|
-
nodesWithErrors.add(issue.node);
|
|
114
|
+
if (!(nodesWithErrors.has(issue.node) && isError(issue))) {
|
|
97
115
|
ctx.addViolation({
|
|
98
116
|
node: findErrorNode(viewNode, issue.node),
|
|
99
|
-
message:
|
|
100
|
-
|
|
117
|
+
message: isError(issue)
|
|
118
|
+
? `View Validation Error - ${issue.type}: ${issue.message}`
|
|
119
|
+
: issue.message,
|
|
120
|
+
severity: translateSeverity(issue.severity),
|
|
101
121
|
});
|
|
122
|
+
if (isError(issue)) {
|
|
123
|
+
nodesWithErrors.add(issue.node);
|
|
124
|
+
}
|
|
102
125
|
}
|
|
103
126
|
});
|
|
104
127
|
},
|
|
@@ -339,10 +339,24 @@ exports[`Transform Tests > applyValueRefs Transform 1`] = `
|
|
|
339
339
|
},
|
|
340
340
|
"type": {
|
|
341
341
|
"node": {
|
|
342
|
-
"const": "mock",
|
|
343
342
|
"description": "The asset type determines the semantics of how a user interacts with a page",
|
|
344
|
-
"
|
|
345
|
-
|
|
343
|
+
"or": [
|
|
344
|
+
{
|
|
345
|
+
"const": "mock",
|
|
346
|
+
"description": "The asset type determines the semantics of how a user interacts with a page",
|
|
347
|
+
"title": "Asset.type",
|
|
348
|
+
"type": "string",
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
"ref": "ExpressionRef",
|
|
352
|
+
"type": "ref",
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
"ref": "BindingRef",
|
|
356
|
+
"type": "ref",
|
|
357
|
+
},
|
|
358
|
+
],
|
|
359
|
+
"type": "or",
|
|
346
360
|
},
|
|
347
361
|
"required": true,
|
|
348
362
|
},
|
package/src/xlr/transforms.ts
CHANGED
|
@@ -78,45 +78,49 @@ export const applyAssetWrapperOrSwitch: TransformFunction = (
|
|
|
78
78
|
* Modifies any primitive type property node (except id/type) to be Bindings or Expressions
|
|
79
79
|
*/
|
|
80
80
|
export const applyValueRefs: TransformFunction = (node, capability) => {
|
|
81
|
-
return simpleTransformGenerator(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
81
|
+
return simpleTransformGenerator(
|
|
82
|
+
"object",
|
|
83
|
+
["Assets", "Views"],
|
|
84
|
+
(inputNode) => {
|
|
85
|
+
const xlrNode = { ...inputNode };
|
|
86
|
+
for (const key in xlrNode.properties) {
|
|
87
|
+
if (key === "id") {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
87
90
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
91
|
+
const value = xlrNode.properties[key];
|
|
92
|
+
if (value.node.type === "or") {
|
|
93
|
+
value.node.or.push({
|
|
94
|
+
type: "ref",
|
|
95
|
+
ref: "ExpressionRef",
|
|
96
|
+
});
|
|
97
|
+
value.node.or.push({
|
|
98
|
+
type: "ref",
|
|
99
|
+
ref: "BindingRef",
|
|
100
|
+
});
|
|
101
|
+
} else if (isPrimitiveTypeNode(value.node)) {
|
|
102
|
+
const newUnionType: OrType = {
|
|
103
|
+
type: "or",
|
|
104
|
+
description: value.node.description,
|
|
105
|
+
or: [
|
|
106
|
+
value.node,
|
|
107
|
+
{
|
|
108
|
+
type: "ref",
|
|
109
|
+
ref: "ExpressionRef",
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
type: "ref",
|
|
113
|
+
ref: "BindingRef",
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
};
|
|
117
|
+
value.node = newUnionType;
|
|
118
|
+
}
|
|
115
119
|
}
|
|
116
|
-
}
|
|
117
120
|
|
|
118
|
-
|
|
119
|
-
|
|
121
|
+
return xlrNode;
|
|
122
|
+
}
|
|
123
|
+
)(node, capability);
|
|
120
124
|
};
|
|
121
125
|
|
|
122
126
|
/**
|