@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/package.json CHANGED
@@ -6,12 +6,12 @@
6
6
  "types"
7
7
  ],
8
8
  "name": "@player-tools/json-language-service",
9
- "version": "0.9.0",
9
+ "version": "0.9.1--canary.196.4186",
10
10
  "main": "dist/cjs/index.cjs",
11
11
  "dependencies": {
12
- "@player-tools/xlr": "0.9.0",
13
- "@player-tools/xlr-sdk": "0.9.0",
14
- "@player-tools/xlr-utils": "0.9.0",
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(1);
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 { XLRSDK } from "@player-tools/xlr-sdk";
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) || issue.type === "missing") {
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: `Asset Validation Error - ${issue.type}: ${issue.message}`,
73
- severity: DiagnosticSeverity.Error,
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) || issue.type === "missing") {
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: `View Validation Error - ${issue.type}: ${issue.message}`,
100
- severity: DiagnosticSeverity.Error,
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
- "title": "Asset.type",
345
- "type": "string",
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
  },
@@ -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("object", "Assets", (inputNode) => {
82
- const xlrNode = { ...inputNode };
83
- for (const key in xlrNode.properties) {
84
- if (key === "id" || key === "type") {
85
- continue;
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
- const value = xlrNode.properties[key];
89
- if (value.node.type === "or") {
90
- value.node.or.push({
91
- type: "ref",
92
- ref: "ExpressionRef",
93
- });
94
- value.node.or.push({
95
- type: "ref",
96
- ref: "BindingRef",
97
- });
98
- } else if (isPrimitiveTypeNode(value.node)) {
99
- const newUnionType: OrType = {
100
- type: "or",
101
- description: value.node.description,
102
- or: [
103
- value.node,
104
- {
105
- type: "ref",
106
- ref: "ExpressionRef",
107
- },
108
- {
109
- type: "ref",
110
- ref: "BindingRef",
111
- },
112
- ],
113
- };
114
- value.node = newUnionType;
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
- return xlrNode;
119
- })(node, capability);
121
+ return xlrNode;
122
+ }
123
+ )(node, capability);
120
124
  };
121
125
 
122
126
  /**