@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,139 @@
1
+ import {
2
+ CompletionItemKind,
3
+ DiagnosticSeverity,
4
+ } from "vscode-languageserver-types";
5
+ import type { PlayerLanguageService, PlayerLanguageServicePlugin } from "..";
6
+ import type { ASTNode, FlowASTNode } from "../parser";
7
+ import { isFlowNode, isPropertyNode, isStateNode } from "../parser";
8
+ import type { ASTVisitor, ValidationContext } from "../types";
9
+ import type { PropertyASTNode } from "..";
10
+ import { getLSLocationOfNode, isValueCompletion } from "../utils";
11
+
12
+ /** Create a validation visitor for dealing with transition states */
13
+ const createValidationVisitor = (ctx: ValidationContext): ASTVisitor => {
14
+ const validTransitions = new Map<string, Set<string>>();
15
+
16
+ return {
17
+ FlowNode: (flowNode) => {
18
+ const flowNodeId = (flowNode.parent as PropertyASTNode)?.keyNode.value;
19
+
20
+ flowNode.states.forEach((p) => {
21
+ if (!validTransitions.has(flowNodeId)) {
22
+ validTransitions.set(flowNodeId, new Set());
23
+ }
24
+
25
+ validTransitions.get(flowNodeId)?.add(p.keyNode.value);
26
+ });
27
+ },
28
+
29
+ FlowStateNode: (flowState) => {
30
+ const transitions = flowState.properties.find(
31
+ (p) => p.keyNode.value === "transitions",
32
+ );
33
+
34
+ let flowNode = flowState.parent;
35
+ while (flowNode && !isFlowNode(flowNode)) {
36
+ flowNode = flowNode?.parent;
37
+ }
38
+
39
+ const flowNodeId = (flowNode?.parent as PropertyASTNode)?.keyNode.value;
40
+
41
+ transitions?.valueNode?.children?.forEach((transitionObjects) => {
42
+ if (
43
+ transitionObjects.type === "property" &&
44
+ transitionObjects.valueNode?.type === "string"
45
+ ) {
46
+ // Validate that the target is valid
47
+
48
+ if (
49
+ !validTransitions
50
+ .get(flowNodeId)
51
+ ?.has(transitionObjects.valueNode.value)
52
+ ) {
53
+ ctx.addViolation({
54
+ node: transitionObjects.valueNode,
55
+ severity: DiagnosticSeverity.Error,
56
+ message: `Node "${transitionObjects.valueNode.value}" not found`,
57
+ });
58
+ }
59
+ }
60
+ });
61
+ },
62
+ };
63
+ };
64
+
65
+ /** Check that the node is a string completion of a transition state */
66
+ const isTransitionValue = (node: ASTNode): boolean => {
67
+ return (
68
+ node.type === "string" &&
69
+ isPropertyNode(node.parent) &&
70
+ node.parent.parent?.type === "object" &&
71
+ isPropertyNode(node.parent.parent.parent) &&
72
+ node.parent.parent.parent.keyNode.value === "transitions" &&
73
+ isStateNode(node.parent.parent.parent.parent) &&
74
+ isPropertyNode(node.parent.parent.parent.parent.parent) &&
75
+ isFlowNode(node.parent.parent.parent.parent.parent.parent)
76
+ );
77
+ };
78
+
79
+ /** Find the first parent flow node */
80
+ const getFlowNode = (node: ASTNode): FlowASTNode | undefined => {
81
+ if (isFlowNode(node)) {
82
+ return node;
83
+ }
84
+
85
+ if (node.parent) {
86
+ return getFlowNode(node.parent);
87
+ }
88
+ };
89
+
90
+ /**
91
+ * Handles everything associated with navigation nodes (transitions, jump-to-def, reachability)
92
+ */
93
+ export class NavStatePlugin implements PlayerLanguageServicePlugin {
94
+ name = "nav-state";
95
+
96
+ apply(service: PlayerLanguageService): void {
97
+ service.hooks.validate.tap(this.name, async (ctx, validation) => {
98
+ validation.useASTVisitor(createValidationVisitor(validation));
99
+ });
100
+
101
+ service.hooks.complete.tap(this.name, async (ctx, completionCtx) => {
102
+ // auto-complete any transition nodes
103
+
104
+ if (!isValueCompletion(ctx.node)) {
105
+ return;
106
+ }
107
+
108
+ if (isTransitionValue(ctx.node)) {
109
+ const flowNode = getFlowNode(ctx.node);
110
+
111
+ flowNode?.states.forEach((p) => {
112
+ completionCtx.addCompletionItem({
113
+ kind: CompletionItemKind.Value,
114
+ label: p.keyNode.value,
115
+ });
116
+ });
117
+ }
118
+ });
119
+
120
+ service.hooks.definition.tap(this.name, (ctx) => {
121
+ if (!isValueCompletion(ctx.node)) {
122
+ return;
123
+ }
124
+
125
+ if (ctx.node.type === "string" && isTransitionValue(ctx.node)) {
126
+ const flowNode = getFlowNode(ctx.node);
127
+ const { value } = ctx.node;
128
+
129
+ const flowProp = flowNode?.states.find(
130
+ (n) => n.keyNode.value === value,
131
+ );
132
+
133
+ if (flowProp) {
134
+ return getLSLocationOfNode(ctx.document, flowProp.keyNode);
135
+ }
136
+ }
137
+ });
138
+ }
139
+ }
@@ -0,0 +1,225 @@
1
+ import {
2
+ CompletionItemKind,
3
+ DiagnosticSeverity,
4
+ } from "vscode-languageserver-types";
5
+ import type { PlayerLanguageService, PlayerLanguageServicePlugin } from "..";
6
+ import type { PropertyASTNode, StringASTNode } from "../parser";
7
+ import { isPropertyNode, isStateNode, isViewNode } from "../parser";
8
+ import type { ASTVisitor, DocumentContext, ValidationContext } from "../types";
9
+ import { getLSLocationOfNode, getProperty, isValueCompletion } from "../utils";
10
+
11
+ interface DocumentViewInfo {
12
+ /** list of views */
13
+ views: Map<
14
+ string,
15
+ {
16
+ /** view id */
17
+ id: string;
18
+
19
+ /** view id prop */
20
+ idProp: PropertyASTNode<StringASTNode>;
21
+ }
22
+ >;
23
+
24
+ /** list of view nodes */
25
+ nodes: Map<
26
+ string,
27
+ {
28
+ /** id of the view */
29
+ id: string;
30
+
31
+ /** view ref */
32
+ refProp: PropertyASTNode<StringASTNode>;
33
+ }
34
+ >;
35
+ }
36
+
37
+ /** create a visitor to handle view nodes */
38
+ const createValidationVisitor = (
39
+ ctx: ValidationContext,
40
+ viewInfo: DocumentViewInfo,
41
+ ): ASTVisitor => {
42
+ return {
43
+ FlowStateNode: (flowState) => {
44
+ if (flowState.stateType?.valueNode?.value === "VIEW") {
45
+ const refNode = getProperty(flowState, "ref");
46
+ if (!refNode || refNode.valueNode?.type !== "string") {
47
+ return;
48
+ }
49
+
50
+ const refID = refNode.valueNode.value;
51
+
52
+ if (viewInfo.views.has(refID)) {
53
+ return;
54
+ }
55
+
56
+ ctx.addViolation({
57
+ node: refNode.valueNode,
58
+ message: `View with id: ${refID} does not exist.`,
59
+ severity: DiagnosticSeverity.Error,
60
+ });
61
+ }
62
+ },
63
+ ViewNode: (viewNode) => {
64
+ if (viewNode.id && viewNode.id.valueNode?.value !== undefined) {
65
+ if (!viewInfo.nodes.get(viewNode.id.valueNode?.value)) {
66
+ ctx.addViolation({
67
+ node: viewNode.id.valueNode,
68
+ message: `View is not reachable`,
69
+ severity: DiagnosticSeverity.Warning,
70
+ });
71
+ }
72
+ }
73
+ },
74
+ };
75
+ };
76
+
77
+ /** get the info for a given view */
78
+ const getViewInfo = (ctx: DocumentContext): DocumentViewInfo => {
79
+ const views = new Map<
80
+ string,
81
+ {
82
+ /** view id */
83
+ id: string;
84
+
85
+ /** view id prop */
86
+ idProp: PropertyASTNode<StringASTNode>;
87
+ }
88
+ >();
89
+
90
+ const nodes = new Map<
91
+ string,
92
+ {
93
+ /** id of the view */
94
+ id: string;
95
+
96
+ /** view ref */
97
+ refProp: PropertyASTNode<StringASTNode>;
98
+ }
99
+ >();
100
+
101
+ // Go through the views array and fetch the ID
102
+
103
+ const { root } = ctx.PlayerContent;
104
+
105
+ if (root.type === "content") {
106
+ root.views?.valueNode?.children.forEach((c) => {
107
+ if (isViewNode(c) && c.id?.valueNode) {
108
+ views.set(c.id.valueNode.value, {
109
+ id: c.id.valueNode.value,
110
+ idProp: c.id,
111
+ });
112
+ }
113
+ });
114
+
115
+ root.navigation?.valueNode?.flows.forEach((flow) => {
116
+ flow.valueNode?.states?.forEach((state) => {
117
+ if (state.valueNode?.stateType?.valueNode?.value === "VIEW") {
118
+ const ref = state.valueNode.properties.find(
119
+ (p) => p.keyNode.value === "ref",
120
+ );
121
+ if (ref?.valueNode?.type === "string") {
122
+ const refVal = ref.valueNode.value;
123
+ nodes.set(refVal, {
124
+ id: refVal,
125
+ refProp: ref as PropertyASTNode<StringASTNode>,
126
+ });
127
+ }
128
+ }
129
+ });
130
+ });
131
+ }
132
+
133
+ return {
134
+ views,
135
+ nodes,
136
+ };
137
+ };
138
+
139
+ /**
140
+ * Handles everything associated with the VIEW node type (definition, validation, auto-complete)
141
+ */
142
+ export class ViewNodePlugin implements PlayerLanguageServicePlugin {
143
+ name = "view-node";
144
+
145
+ apply(service: PlayerLanguageService): void {
146
+ let viewInfo: DocumentViewInfo | undefined;
147
+
148
+ service.hooks.validate.tap(this.name, async (ctx, validation) => {
149
+ if (!viewInfo) {
150
+ return;
151
+ }
152
+
153
+ validation.useASTVisitor(createValidationVisitor(validation, viewInfo));
154
+ });
155
+
156
+ service.hooks.onDocumentUpdate.tap(this.name, (ctx) => {
157
+ // Update the parsing for the locations of view info across the document
158
+ viewInfo = getViewInfo(ctx);
159
+ });
160
+
161
+ service.hooks.complete.tap(this.name, async (ctx, completionCtx) => {
162
+ // Auto-complete ViewNode refs and view-ids based on the document
163
+
164
+ if (!isValueCompletion(ctx.node)) {
165
+ return;
166
+ }
167
+
168
+ if (
169
+ ctx.node.type === "string" &&
170
+ isPropertyNode(ctx.node.parent) &&
171
+ isStateNode(ctx.node.parent.parent) &&
172
+ ctx.node.parent.keyNode.value === "ref"
173
+ ) {
174
+ Array.from(viewInfo?.views.keys() ?? []).forEach((vID) => {
175
+ completionCtx.addCompletionItem({
176
+ kind: CompletionItemKind.Value,
177
+ label: vID,
178
+ });
179
+ });
180
+ } else if (
181
+ ctx.node.type === "string" &&
182
+ isPropertyNode(ctx.node.parent) &&
183
+ isViewNode(ctx.node.parent.parent) &&
184
+ ctx.node.parent.keyNode.value === "id"
185
+ ) {
186
+ Array.from(viewInfo?.nodes.keys() ?? []).forEach((vID) => {
187
+ completionCtx.addCompletionItem({
188
+ kind: CompletionItemKind.Value,
189
+ label: vID,
190
+ });
191
+ });
192
+ }
193
+ });
194
+
195
+ service.hooks.definition.tap(this.name, (ctx) => {
196
+ // Jump between view-refs and ViewNode's
197
+
198
+ if (!isValueCompletion(ctx.node)) {
199
+ return;
200
+ }
201
+
202
+ if (ctx.node.type === "string" && isPropertyNode(ctx.node.parent)) {
203
+ if (
204
+ isViewNode(ctx.node.parent.parent) &&
205
+ ctx.node.parent.keyNode.value === "id"
206
+ ) {
207
+ const { value } = ctx.node;
208
+ const stateNode = viewInfo?.nodes.get(value);
209
+ if (stateNode) {
210
+ return getLSLocationOfNode(ctx.document, stateNode.refProp);
211
+ }
212
+ } else if (
213
+ isStateNode(ctx.node.parent.parent) &&
214
+ ctx.node.parent.keyNode.value === "ref"
215
+ ) {
216
+ const { value } = ctx.node;
217
+ const viewNode = viewInfo?.views.get(value);
218
+ if (viewNode) {
219
+ return getLSLocationOfNode(ctx.document, viewNode.idProp);
220
+ }
221
+ }
222
+ }
223
+ });
224
+ }
225
+ }