@player-lang/json-language-service 0.0.2-next.0

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.
Files changed (59) hide show
  1. package/dist/cjs/index.cjs +2314 -0
  2. package/dist/cjs/index.cjs.map +1 -0
  3. package/dist/index.legacy-esm.js +2249 -0
  4. package/dist/index.mjs +2249 -0
  5. package/dist/index.mjs.map +1 -0
  6. package/package.json +40 -0
  7. package/src/__tests__/__snapshots__/service.test.ts.snap +213 -0
  8. package/src/__tests__/service.test.ts +298 -0
  9. package/src/constants.ts +38 -0
  10. package/src/index.ts +490 -0
  11. package/src/parser/__tests__/parse.test.ts +18 -0
  12. package/src/parser/document.ts +456 -0
  13. package/src/parser/edits.ts +31 -0
  14. package/src/parser/index.ts +38 -0
  15. package/src/parser/jsonParseErrors.ts +69 -0
  16. package/src/parser/types.ts +314 -0
  17. package/src/parser/utils.ts +94 -0
  18. package/src/plugins/__tests__/asset-wrapper-array-plugin.test.ts +112 -0
  19. package/src/plugins/__tests__/binding-schema-plugin.test.ts +62 -0
  20. package/src/plugins/__tests__/duplicate-id-plugin.test.ts +195 -0
  21. package/src/plugins/__tests__/missing-asset-wrapper-plugin.test.ts +190 -0
  22. package/src/plugins/__tests__/nav-state-plugin.test.ts +136 -0
  23. package/src/plugins/__tests__/view-node-plugin.test.ts +154 -0
  24. package/src/plugins/asset-wrapper-array-plugin.ts +123 -0
  25. package/src/plugins/binding-schema-plugin.ts +289 -0
  26. package/src/plugins/duplicate-id-plugin.ts +158 -0
  27. package/src/plugins/missing-asset-wrapper-plugin.ts +96 -0
  28. package/src/plugins/nav-state-plugin.ts +139 -0
  29. package/src/plugins/view-node-plugin.ts +225 -0
  30. package/src/plugins/xlr-plugin.ts +371 -0
  31. package/src/types.ts +119 -0
  32. package/src/utils.ts +143 -0
  33. package/src/xlr/__tests__/__snapshots__/transform.test.ts.snap +390 -0
  34. package/src/xlr/__tests__/transform.test.ts +108 -0
  35. package/src/xlr/index.ts +3 -0
  36. package/src/xlr/registry.ts +99 -0
  37. package/src/xlr/service.ts +190 -0
  38. package/src/xlr/transforms.ts +169 -0
  39. package/types/constants.d.ts +7 -0
  40. package/types/index.d.ts +69 -0
  41. package/types/parser/document.d.ts +25 -0
  42. package/types/parser/edits.d.ts +10 -0
  43. package/types/parser/index.d.ts +16 -0
  44. package/types/parser/jsonParseErrors.d.ts +27 -0
  45. package/types/parser/types.d.ts +188 -0
  46. package/types/parser/utils.d.ts +26 -0
  47. package/types/plugins/asset-wrapper-array-plugin.d.ts +9 -0
  48. package/types/plugins/binding-schema-plugin.d.ts +15 -0
  49. package/types/plugins/duplicate-id-plugin.d.ts +7 -0
  50. package/types/plugins/missing-asset-wrapper-plugin.d.ts +9 -0
  51. package/types/plugins/nav-state-plugin.d.ts +9 -0
  52. package/types/plugins/view-node-plugin.d.ts +9 -0
  53. package/types/plugins/xlr-plugin.d.ts +7 -0
  54. package/types/types.d.ts +81 -0
  55. package/types/utils.d.ts +24 -0
  56. package/types/xlr/index.d.ts +4 -0
  57. package/types/xlr/registry.d.ts +17 -0
  58. package/types/xlr/service.d.ts +22 -0
  59. package/types/xlr/transforms.d.ts +18 -0
@@ -0,0 +1,195 @@
1
+ import { test, expect, describe, beforeEach } from "vitest";
2
+ import { TextDocument } from "vscode-languageserver-textdocument";
3
+ import {
4
+ ReferenceAssetsWebPluginManifest,
5
+ Types,
6
+ } from "@player-lang/static-xlrs";
7
+ import { PlayerLanguageService } from "../..";
8
+ import { toTextDocument } from "../../utils";
9
+
10
+ const simpleDupIDDocument = toTextDocument(
11
+ JSON.stringify(
12
+ {
13
+ views: [
14
+ {
15
+ id: "no",
16
+ type: "bar",
17
+ foo: {
18
+ asset: {
19
+ id: "bar",
20
+ },
21
+ },
22
+ bar: {
23
+ asset: {
24
+ id: "bar",
25
+ },
26
+ },
27
+ },
28
+ ],
29
+ },
30
+ null,
31
+ 2,
32
+ ),
33
+ );
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
+
110
+ describe("duplicate-id-plugin", () => {
111
+ let service: PlayerLanguageService;
112
+
113
+ beforeEach(async () => {
114
+ service = new PlayerLanguageService();
115
+ await service.setAssetTypesFromModule([
116
+ Types,
117
+ ReferenceAssetsWebPluginManifest,
118
+ ]);
119
+ });
120
+
121
+ test("finds dupe ids", async () => {
122
+ const validations = await service.validateTextDocument(simpleDupIDDocument);
123
+
124
+ expect(validations).toHaveLength(10);
125
+ expect(validations?.map((v) => v.message)).toMatchInlineSnapshot(`
126
+ [
127
+ "Content Validation Error - missing: Property "id" missing from type "Flow"",
128
+ "Content Validation Error - missing: Property "navigation" missing from type "Flow"",
129
+ "View is not reachable",
130
+ "Warning - View Type bar was not loaded into Validator definitions",
131
+ "Warning - Asset Type undefined was not loaded into Validator definitions",
132
+ "Asset Validation Error - missing: Property "type" missing from type "Asset"",
133
+ "The id "bar" is already in use in this view.",
134
+ "The id "bar" is already in use in this view.",
135
+ "Warning - Asset Type undefined was not loaded into Validator definitions",
136
+ "Asset Validation Error - missing: Property "type" missing from type "Asset"",
137
+ ]
138
+ `);
139
+ });
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
+
155
+ test("fixes dup ids", async () => {
156
+ const diags = await service.validateTextDocument(simpleDupIDDocument);
157
+
158
+ const actions = await service.getCodeActionsInRange(simpleDupIDDocument, {
159
+ diagnostics: diags ?? [],
160
+ });
161
+
162
+ expect(actions).toHaveLength(2);
163
+ const editActions = actions[0].edit?.changes?.[simpleDupIDDocument.uri];
164
+
165
+ const appliedAction = TextDocument.applyEdits(
166
+ simpleDupIDDocument,
167
+ editActions ?? [],
168
+ );
169
+
170
+ expect(appliedAction).toStrictEqual(
171
+ JSON.stringify(
172
+ {
173
+ views: [
174
+ {
175
+ id: "no",
176
+ type: "bar",
177
+ foo: {
178
+ asset: {
179
+ id: "foo-asset",
180
+ },
181
+ },
182
+ bar: {
183
+ asset: {
184
+ id: "bar",
185
+ },
186
+ },
187
+ },
188
+ ],
189
+ },
190
+ null,
191
+ 2,
192
+ ),
193
+ );
194
+ });
195
+ });
@@ -0,0 +1,190 @@
1
+ import { test, expect, describe, beforeEach } from "vitest";
2
+ import { TextDocument } from "vscode-languageserver-textdocument";
3
+ import {
4
+ ReferenceAssetsWebPluginManifest,
5
+ Types,
6
+ } from "@player-lang/static-xlrs";
7
+ import { PlayerLanguageService } from "../..";
8
+ import { toTextDocument } from "../../utils";
9
+
10
+ const simpleAssetWrapperDocument = toTextDocument(
11
+ JSON.stringify(
12
+ {
13
+ id: "foo",
14
+ navigation: {
15
+ BEGIN: "FLOW_1",
16
+ FLOW_1: {
17
+ startState: "VIEW_1",
18
+ VIEW_1: {
19
+ state_type: "VIEW",
20
+ ref: "input",
21
+ transitions: {},
22
+ },
23
+ },
24
+ },
25
+ views: [
26
+ {
27
+ id: "input",
28
+ type: "input",
29
+ binding: "foo.bar",
30
+ label: {
31
+ id: "input-label",
32
+ type: "text",
33
+ value: "Label",
34
+ },
35
+ },
36
+ ],
37
+ },
38
+ null,
39
+ 2,
40
+ ),
41
+ );
42
+
43
+ describe("missing-asset-wrapper", () => {
44
+ let service: PlayerLanguageService;
45
+
46
+ beforeEach(async () => {
47
+ service = new PlayerLanguageService();
48
+ await service.setAssetTypesFromModule([
49
+ Types,
50
+ ReferenceAssetsWebPluginManifest,
51
+ ]);
52
+ });
53
+
54
+ test("adds validation for the asset wrapper", async () => {
55
+ const validations = await service.validateTextDocument(
56
+ simpleAssetWrapperDocument,
57
+ );
58
+
59
+ expect(validations).toHaveLength(2);
60
+ expect(validations?.map((v) => v.message)).toMatchInlineSnapshot(`
61
+ [
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",
64
+ ]
65
+ `);
66
+ });
67
+
68
+ test("fixes the violation", async () => {
69
+ const diags = await service.validateTextDocument(
70
+ simpleAssetWrapperDocument,
71
+ );
72
+
73
+ expect(diags).toMatchInlineSnapshot(`
74
+ [
75
+ {
76
+ "message": "View Validation Error - value: Does not match any of the expected types for type: 'AssetWrapperOrSwitch'",
77
+ "range": {
78
+ "end": {
79
+ "character": 7,
80
+ "line": 22,
81
+ },
82
+ "start": {
83
+ "character": 15,
84
+ "line": 18,
85
+ },
86
+ },
87
+ "severity": 1,
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
+ },
103
+ ]
104
+ `);
105
+
106
+ const actions = await service.getCodeActionsInRange(
107
+ simpleAssetWrapperDocument,
108
+ {
109
+ diagnostics: diags ?? [],
110
+ },
111
+ );
112
+
113
+ expect(actions).toMatchInlineSnapshot(`
114
+ [
115
+ {
116
+ "edit": {
117
+ "changes": {
118
+ "foo": [
119
+ {
120
+ "newText": "{
121
+ "asset": {
122
+ "id": "input-label",
123
+ "type": "text",
124
+ "value": "Label"
125
+ }
126
+ }",
127
+ "range": {
128
+ "end": {
129
+ "character": 7,
130
+ "line": 22,
131
+ },
132
+ "start": {
133
+ "character": 15,
134
+ "line": 18,
135
+ },
136
+ },
137
+ },
138
+ ],
139
+ },
140
+ },
141
+ "kind": "quickfix",
142
+ "title": "Wrap in "asset"",
143
+ },
144
+ ]
145
+ `);
146
+
147
+ const editActions =
148
+ actions[0]?.edit?.changes?.[simpleAssetWrapperDocument.uri];
149
+
150
+ const appliedAction = TextDocument.applyEdits(
151
+ simpleAssetWrapperDocument,
152
+ editActions ?? [],
153
+ );
154
+
155
+ expect(appliedAction).toStrictEqual(
156
+ JSON.stringify(
157
+ {
158
+ id: "foo",
159
+ navigation: {
160
+ BEGIN: "FLOW_1",
161
+ FLOW_1: {
162
+ startState: "VIEW_1",
163
+ VIEW_1: {
164
+ state_type: "VIEW",
165
+ ref: "input",
166
+ transitions: {},
167
+ },
168
+ },
169
+ },
170
+ views: [
171
+ {
172
+ id: "input",
173
+ type: "input",
174
+ binding: "foo.bar",
175
+ label: {
176
+ asset: {
177
+ id: "input-label",
178
+ type: "text",
179
+ value: "Label",
180
+ },
181
+ },
182
+ },
183
+ ],
184
+ },
185
+ null,
186
+ 2,
187
+ ),
188
+ );
189
+ });
190
+ });
@@ -0,0 +1,136 @@
1
+ import { test, expect, describe, beforeEach } from "vitest";
2
+ import {
3
+ ReferenceAssetsWebPluginManifest,
4
+ Types,
5
+ } from "@player-lang/static-xlrs";
6
+ import { PlayerLanguageService } from "../..";
7
+ import { toTextDocument } from "../../utils";
8
+
9
+ describe("nav-state-plugin", () => {
10
+ let service: PlayerLanguageService;
11
+
12
+ beforeEach(async () => {
13
+ service = new PlayerLanguageService();
14
+ await service.setAssetTypesFromModule([
15
+ Types,
16
+ ReferenceAssetsWebPluginManifest,
17
+ ]);
18
+ });
19
+
20
+ test("validates node transitions", async () => {
21
+ const testDocument = toTextDocument(
22
+ JSON.stringify(
23
+ {
24
+ id: "foo",
25
+ views: [{ id: "view-1", type: "view" }],
26
+ navigation: {
27
+ BEGIN: "FLOW_1",
28
+ FLOW_1: {
29
+ startState: "VIEW_1",
30
+ VIEW_1: {
31
+ state_type: "VIEW",
32
+ ref: "view-1",
33
+ transitions: {
34
+ next: "ACTION_1",
35
+ },
36
+ },
37
+ },
38
+ },
39
+ },
40
+ null,
41
+ 2,
42
+ ),
43
+ );
44
+
45
+ const diags = await service.validateTextDocument(testDocument);
46
+
47
+ expect(diags).toHaveLength(2);
48
+ expect(diags?.map((m) => m.message)).toMatchInlineSnapshot(`
49
+ [
50
+ "Warning - View Type view was not loaded into Validator definitions",
51
+ "Node "ACTION_1" not found",
52
+ ]
53
+ `);
54
+ });
55
+
56
+ test("validates node transitions with multiple flows", async () => {
57
+ const testDocument = toTextDocument(
58
+ JSON.stringify(
59
+ {
60
+ id: "foo",
61
+ views: [
62
+ { id: "view-1", type: "view" },
63
+ { id: "view-2", type: "view" },
64
+ ],
65
+ navigation: {
66
+ BEGIN: "FLOW_1",
67
+ FLOW_1: {
68
+ startState: "VIEW_1",
69
+ VIEW_1: {
70
+ state_type: "VIEW",
71
+ ref: "view-1",
72
+ transitions: {
73
+ next: "END_Done",
74
+ },
75
+ },
76
+ END_Done: {
77
+ state_type: "END",
78
+ outcome: "done",
79
+ },
80
+ },
81
+ FLOW_2: {
82
+ startState: "VIEW_2",
83
+ VIEW_2: {
84
+ state_type: "VIEW",
85
+ ref: "view-2",
86
+ transitions: {
87
+ back: "END_Back",
88
+ },
89
+ },
90
+ END_Back: {
91
+ state_type: "END",
92
+ outcome: "done",
93
+ },
94
+ },
95
+ },
96
+ },
97
+ null,
98
+ 2,
99
+ ),
100
+ );
101
+ const diags = await service.validateTextDocument(testDocument);
102
+
103
+ expect(diags?.filter((d) => d.severity !== 2)).toMatchInlineSnapshot(`
104
+ [
105
+ {
106
+ "message": "Warning - View Type view was not loaded into Validator definitions",
107
+ "range": {
108
+ "end": {
109
+ "character": 5,
110
+ "line": 6,
111
+ },
112
+ "start": {
113
+ "character": 4,
114
+ "line": 3,
115
+ },
116
+ },
117
+ "severity": 1,
118
+ },
119
+ {
120
+ "message": "Warning - View Type view was not loaded into Validator definitions",
121
+ "range": {
122
+ "end": {
123
+ "character": 5,
124
+ "line": 10,
125
+ },
126
+ "start": {
127
+ "character": 4,
128
+ "line": 7,
129
+ },
130
+ },
131
+ "severity": 1,
132
+ },
133
+ ]
134
+ `);
135
+ });
136
+ });
@@ -0,0 +1,154 @@
1
+ import { test, expect, describe, beforeEach } from "vitest";
2
+ import { Position } from "vscode-languageserver-types";
3
+ import {
4
+ ReferenceAssetsWebPluginManifest,
5
+ Types,
6
+ } from "@player-lang/static-xlrs";
7
+ import { PlayerLanguageService } from "../..";
8
+ import { toTextDocument } from "../../utils";
9
+
10
+ describe("duplicate-id-plugin", () => {
11
+ let service: PlayerLanguageService;
12
+
13
+ beforeEach(async () => {
14
+ service = new PlayerLanguageService();
15
+ await service.setAssetTypesFromModule([
16
+ Types,
17
+ ReferenceAssetsWebPluginManifest,
18
+ ]);
19
+ });
20
+
21
+ test("validates view ids", async () => {
22
+ const textDocument = toTextDocument(
23
+ JSON.stringify({
24
+ id: "test",
25
+ views: [
26
+ {
27
+ id: "yes",
28
+ type: "view",
29
+ },
30
+ {
31
+ // Should warn
32
+ id: "no",
33
+ type: "view",
34
+ },
35
+ ],
36
+ navigation: {
37
+ BEGIN: "FLOW_1",
38
+ FLOW_1: {
39
+ startState: "VIEW_1",
40
+ VIEW_1: {
41
+ state_type: "VIEW",
42
+ ref: "yes",
43
+ transitions: {},
44
+ },
45
+ VIEW_2: {
46
+ state_type: "VIEW",
47
+ // Should error
48
+ ref: "nope",
49
+ transitions: {},
50
+ },
51
+ },
52
+ },
53
+ }),
54
+ );
55
+
56
+ const validations = await service.validateTextDocument(textDocument);
57
+ expect(validations).toHaveLength(4);
58
+ expect(validations?.map((v) => v.message)).toMatchInlineSnapshot(`
59
+ [
60
+ "Warning - View Type view was not loaded into Validator definitions",
61
+ "View is not reachable",
62
+ "Warning - View Type view was not loaded into Validator definitions",
63
+ "View with id: nope does not exist.",
64
+ ]
65
+ `);
66
+ });
67
+
68
+ test("completes view ids in view obj", async () => {
69
+ const textDocument = toTextDocument(
70
+ JSON.stringify(
71
+ {
72
+ views: [
73
+ {
74
+ id: "",
75
+ type: "view",
76
+ },
77
+ ],
78
+ navigation: {
79
+ BEGIN: "FLOW_1",
80
+ FLOW_1: {
81
+ startState: "VIEW_1",
82
+ VIEW_1: {
83
+ state_type: "VIEW",
84
+ ref: "view-1",
85
+ transitions: {},
86
+ },
87
+ VIEW_2: {
88
+ state_type: "VIEW",
89
+ ref: "other-view",
90
+ transitions: {},
91
+ },
92
+ },
93
+ },
94
+ },
95
+ null,
96
+ 2,
97
+ ),
98
+ );
99
+
100
+ const completions = await service.getCompletionsAtPosition(
101
+ textDocument,
102
+ Position.create(3, 13),
103
+ );
104
+
105
+ expect(completions.items?.map((i) => i.label)).toContain("view-1");
106
+ expect(completions.items?.map((i) => i.label)).toContain("other-view");
107
+ expect(completions.items?.map((i) => i.label)).toMatchInlineSnapshot(`
108
+ [
109
+ "view-1",
110
+ "other-view",
111
+ ]
112
+ `);
113
+ });
114
+
115
+ test("completes view ids in view nodes", async () => {
116
+ const textDocument = toTextDocument(
117
+ JSON.stringify(
118
+ {
119
+ views: [
120
+ {
121
+ id: "view-1",
122
+ type: "view",
123
+ },
124
+ ],
125
+ navigation: {
126
+ BEGIN: "FLOW_1",
127
+ FLOW_1: {
128
+ startState: "VIEW_1",
129
+ VIEW_1: {
130
+ state_type: "VIEW",
131
+ ref: "",
132
+ transitions: {},
133
+ },
134
+ },
135
+ },
136
+ },
137
+ null,
138
+ 2,
139
+ ),
140
+ );
141
+
142
+ const completions = await service.getCompletionsAtPosition(
143
+ textDocument,
144
+ Position.create(13, 15),
145
+ );
146
+
147
+ expect(completions.items?.map((i) => i.label)).toContain("view-1");
148
+ expect(completions.items?.map((i) => i.label)).toMatchInlineSnapshot(`
149
+ [
150
+ "view-1",
151
+ ]
152
+ `);
153
+ });
154
+ });