@player-tools/typescript-expression-plugin 0.2.2--canary.20.485 → 0.4.0-next.1

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/index.cjs.js CHANGED
@@ -3,7 +3,9 @@
3
3
  var typescriptTemplateLanguageServiceDecorator = require('typescript-template-language-service-decorator');
4
4
  var ts = require('typescript/lib/tsserverlibrary');
5
5
  var xlrUtils = require('@player-tools/xlr-utils');
6
+ var xlrSdk = require('@player-tools/xlr-sdk');
6
7
  var player = require('@player-ui/player');
8
+ var jsoncParser = require('jsonc-parser');
7
9
 
8
10
  function _interopNamespace(e) {
9
11
  if (e && e.__esModule) return e;
@@ -52,6 +54,83 @@ function getTokenAtPosition(node, position) {
52
54
  return node;
53
55
  }
54
56
  }
57
+ function toTSLocation(node) {
58
+ var _a, _b;
59
+ const start = (_a = node.location) == null ? void 0 : _a.start.character;
60
+ const end = (_b = node.location) == null ? void 0 : _b.end.character;
61
+ if (start === void 0 || end === void 0) {
62
+ return {
63
+ start: 0,
64
+ length: 0
65
+ };
66
+ }
67
+ return {
68
+ start,
69
+ length: end - start
70
+ };
71
+ }
72
+ function convertExprToValue(exprNode) {
73
+ let val;
74
+ if (exprNode.type === "Literal") {
75
+ val = exprNode.value;
76
+ } else if (exprNode.type === "Object") {
77
+ val = {};
78
+ exprNode.attributes.forEach((prop) => {
79
+ val[convertExprToValue(prop.key)] = convertExprToValue(prop.value);
80
+ });
81
+ } else if (exprNode.type === "ArrayExpression") {
82
+ val = exprNode.elements.map(convertExprToValue);
83
+ }
84
+ return val;
85
+ }
86
+ function convertExprToJSONNode(exprNode) {
87
+ const val = convertExprToValue(exprNode);
88
+ if (val === void 0) {
89
+ return void 0;
90
+ }
91
+ return jsoncParser.parseTree(JSON.stringify(val));
92
+ }
93
+
94
+ var __defProp$1 = Object.defineProperty;
95
+ var __defProps$1 = Object.defineProperties;
96
+ var __getOwnPropDescs$1 = Object.getOwnPropertyDescriptors;
97
+ var __getOwnPropSymbols$1 = Object.getOwnPropertySymbols;
98
+ var __hasOwnProp$1 = Object.prototype.hasOwnProperty;
99
+ var __propIsEnum$1 = Object.prototype.propertyIsEnumerable;
100
+ var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
101
+ var __spreadValues$1 = (a, b) => {
102
+ for (var prop in b || (b = {}))
103
+ if (__hasOwnProp$1.call(b, prop))
104
+ __defNormalProp$1(a, prop, b[prop]);
105
+ if (__getOwnPropSymbols$1)
106
+ for (var prop of __getOwnPropSymbols$1(b)) {
107
+ if (__propIsEnum$1.call(b, prop))
108
+ __defNormalProp$1(a, prop, b[prop]);
109
+ }
110
+ return a;
111
+ };
112
+ var __spreadProps$1 = (a, b) => __defProps$1(a, __getOwnPropDescs$1(b));
113
+ const toFunction = xlrSdk.simpleTransformGenerator("ref", "Expressions", (exp) => {
114
+ if (!exp.genericArguments || exp.ref !== "ExpressionHandler") {
115
+ return exp;
116
+ }
117
+ const [args, returnType] = exp.genericArguments;
118
+ const parameters = (args.type === "tuple" ? args.elementTypes : []).map((elementType, index) => {
119
+ var _a, _b, _c, _d, _e, _f;
120
+ return {
121
+ name: (_c = (_b = (_a = elementType.name) != null ? _a : elementType.type.name) != null ? _b : elementType.type.title) != null ? _c : `arg_${index}`,
122
+ type: __spreadValues$1({
123
+ name: (_f = (_e = (_d = elementType.name) != null ? _d : elementType.type.name) != null ? _e : elementType.type.title) != null ? _f : `arg_${index}`
124
+ }, elementType.type),
125
+ optional: elementType.optional === true ? elementType.optional : void 0
126
+ };
127
+ });
128
+ return __spreadProps$1(__spreadValues$1({}, exp), {
129
+ type: "function",
130
+ parameters,
131
+ returnType
132
+ });
133
+ });
55
134
 
56
135
  var __defProp = Object.defineProperty;
57
136
  var __defProps = Object.defineProperties;
@@ -72,59 +151,44 @@ var __spreadValues = (a, b) => {
72
151
  return a;
73
152
  };
74
153
  var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
75
- function reduceExpression(plugins) {
76
- const expressions = new Map();
77
- plugins.forEach((plugin) => {
78
- var _a;
79
- const { capabilities } = plugin;
80
- const registeredExpressions = (_a = capabilities == null ? void 0 : capabilities.Expressions) != null ? _a : [];
81
- registeredExpressions.forEach((exp) => {
82
- var _a2;
83
- if (exp.type !== "ref" || !exp.genericArguments) {
84
- return;
85
- }
86
- const expName = exp.name;
87
- const [args, returnType] = exp.genericArguments;
88
- const parameters = (args.type === "tuple" ? args.elementTypes : []).map((elementType) => {
89
- var _a3, _b, _c;
90
- return {
91
- name: (_c = (_b = (_a3 = elementType.name) != null ? _a3 : elementType.type.name) != null ? _b : elementType.type.title) != null ? _c : "",
92
- type: elementType.type,
93
- optional: elementType.optional === true ? elementType.optional : void 0
94
- };
95
- });
96
- const entry = {
97
- name: expName,
98
- description: (_a2 = exp.description) != null ? _a2 : "",
99
- source: plugin,
100
- type: __spreadProps(__spreadValues({}, exp), {
101
- type: "function",
102
- parameters,
103
- returnType
104
- })
105
- };
106
- expressions.set(expName, entry);
107
- });
108
- });
109
- const expList = {
110
- entries: expressions
111
- };
112
- return expList;
113
- }
114
154
  class ExpressionLanguageService {
115
155
  constructor(options) {
116
156
  this._plugins = [];
117
157
  var _a;
118
158
  this.logger = options == null ? void 0 : options.logger;
119
159
  this._plugins = (_a = options == null ? void 0 : options.plugins) != null ? _a : [];
120
- this._expressions = reduceExpression(this._plugins);
160
+ this.xlr = new xlrSdk.XLRSDK();
161
+ this._plugins.forEach((p) => {
162
+ this.xlr.loadDefinitionsFromModule(p, void 0, [toFunction]);
163
+ });
164
+ this._expressions = this.reduceExpression();
121
165
  }
122
166
  setConfig(config) {
123
167
  this._plugins = config.plugins;
124
- this._expressions = reduceExpression(this._plugins);
168
+ this.xlr = new xlrSdk.XLRSDK();
169
+ this._plugins.forEach((p) => {
170
+ this.xlr.loadDefinitionsFromModule(p, void 0, [toFunction]);
171
+ });
172
+ this._expressions = this.reduceExpression();
173
+ }
174
+ reduceExpression() {
175
+ const expressions = new Map();
176
+ this.xlr.listTypes().forEach((type) => {
177
+ const typeInfo = this.xlr.getTypeInfo(type.name);
178
+ const source = this._plugins.find((value) => value.pluginName === (typeInfo == null ? void 0 : typeInfo.plugin));
179
+ if (type.type === "function" && (typeInfo == null ? void 0 : typeInfo.capability) === "Expressions" && source) {
180
+ expressions.set(type.name, {
181
+ name: type.name,
182
+ description: type.description,
183
+ type,
184
+ source
185
+ });
186
+ }
187
+ });
188
+ return expressions;
125
189
  }
126
190
  getCompletionsAtPosition(context, position) {
127
- var _a, _b, _c, _d;
191
+ var _a, _b;
128
192
  const completionInfo = {
129
193
  isGlobalCompletion: false,
130
194
  isMemberCompletion: false,
@@ -132,7 +196,7 @@ class ExpressionLanguageService {
132
196
  entries: []
133
197
  };
134
198
  if (context.text.length === 0) {
135
- this._expressions.entries.forEach((exp) => {
199
+ this._expressions.forEach((exp) => {
136
200
  completionInfo.entries.push({
137
201
  name: exp.name,
138
202
  kind: ts__namespace.ScriptElementKind.functionElement,
@@ -146,9 +210,8 @@ class ExpressionLanguageService {
146
210
  const line = context.text.split(/\n/g)[position.line];
147
211
  const parsed = player.parseExpression(line, { strict: false });
148
212
  const token = getTokenAtPosition(parsed, position);
149
- (_b = this.logger) == null ? void 0 : _b.log(`Token ${token == null ? void 0 : token.type} ${(_a = token == null ? void 0 : token.error) == null ? void 0 : _a.message}`);
150
213
  if ((token == null ? void 0 : token.type) === "Compound" && token.error) {
151
- this._expressions.entries.forEach((exp) => {
214
+ this._expressions.forEach((exp) => {
152
215
  completionInfo.entries.push({
153
216
  name: exp.name,
154
217
  kind: ts__namespace.ScriptElementKind.functionElement,
@@ -160,9 +223,9 @@ class ExpressionLanguageService {
160
223
  return completionInfo;
161
224
  }
162
225
  if ((token == null ? void 0 : token.type) === "Identifier") {
163
- const start = (_d = (_c = token.location) == null ? void 0 : _c.start) != null ? _d : { character: 0 };
226
+ const start = (_b = (_a = token.location) == null ? void 0 : _a.start) != null ? _b : { character: 0 };
164
227
  const wordFromStart = line.slice(start.character, position.character);
165
- const allCompletions = Array.from(this._expressions.entries.keys()).filter((key) => key.startsWith(wordFromStart));
228
+ const allCompletions = Array.from(this._expressions.keys()).filter((key) => key.startsWith(wordFromStart));
166
229
  allCompletions.forEach((c) => {
167
230
  completionInfo.entries.push({
168
231
  name: c,
@@ -176,8 +239,8 @@ class ExpressionLanguageService {
176
239
  return completionInfo;
177
240
  }
178
241
  getCompletionEntryDetails(context, position, name) {
179
- var _a, _b, _c;
180
- const expression = this._expressions.entries.get(name);
242
+ var _a;
243
+ const expression = this._expressions.get(name);
181
244
  const completionDetails = {
182
245
  name,
183
246
  kind: ts__namespace.ScriptElementKind.functionElement,
@@ -190,30 +253,107 @@ class ExpressionLanguageService {
190
253
  ],
191
254
  displayParts: expression ? xlrUtils.createTSDocString(expression.type) : []
192
255
  };
193
- (_b = this.logger) == null ? void 0 : _b.log(`getCompletionEntryDetails: ${name}`);
194
- (_c = this.logger) == null ? void 0 : _c.log(JSON.stringify(completionDetails));
195
256
  return completionDetails;
196
257
  }
197
258
  getQuickInfoAtPosition(context, position) {
198
- var _a, _b, _c, _d, _e, _f, _g, _h;
199
- (_a = this.logger) == null ? void 0 : _a.log(`getQuickInfoAtPosition: ${context.text}`);
200
259
  const parsed = player.parseExpression(context.text, { strict: false });
201
260
  const token = getTokenAtPosition(parsed, position);
202
- (_b = this.logger) == null ? void 0 : _b.log(`token: ${token == null ? void 0 : token.type}`);
203
261
  if ((token == null ? void 0 : token.type) === "Identifier") {
204
- const expression = this._expressions.entries.get(token.name);
262
+ const expression = this._expressions.get(token.name);
205
263
  if (expression) {
206
264
  const completionDetails = this.getCompletionEntryDetails(context, position, expression.name);
207
265
  return __spreadProps(__spreadValues({}, completionDetails), {
208
- textSpan: {
209
- start: (_d = (_c = token.location) == null ? void 0 : _c.start.character) != null ? _d : 0,
210
- length: ((_f = (_e = token.location) == null ? void 0 : _e.end.character) != null ? _f : 0) - ((_h = (_g = token.location) == null ? void 0 : _g.start.character) != null ? _h : 0)
211
- }
266
+ textSpan: toTSLocation(token)
212
267
  });
213
268
  }
214
269
  }
215
270
  return void 0;
216
271
  }
272
+ checkNode(context, node, xlrType) {
273
+ const diagnostics = [];
274
+ const asJsonNodeValue = convertExprToJSONNode(node);
275
+ if (asJsonNodeValue) {
276
+ const xlrDiags = this.xlr.validateByType(xlrType, asJsonNodeValue);
277
+ xlrDiags.forEach((d) => {
278
+ diagnostics.push(__spreadProps(__spreadValues({
279
+ file: context.node.getSourceFile()
280
+ }, toTSLocation(node)), {
281
+ messageText: d.message,
282
+ category: ts__namespace.DiagnosticCategory.Error,
283
+ code: 1
284
+ }));
285
+ });
286
+ }
287
+ return diagnostics;
288
+ }
289
+ getDiagnosticsForNode(context, node) {
290
+ const diags = [];
291
+ if (node.type === "Compound") {
292
+ node.body.forEach((n) => {
293
+ diags.push(...this.getDiagnosticsForNode(context, n));
294
+ });
295
+ }
296
+ if (node.type === "CallExpression") {
297
+ const exprName = node.callTarget.name;
298
+ const expression = this._expressions.get(exprName);
299
+ node.args.forEach((n) => {
300
+ diags.push(...this.getDiagnosticsForNode(context, n));
301
+ });
302
+ if (expression) {
303
+ const expectedArgs = expression.type.parameters;
304
+ const actualArgs = node.args;
305
+ actualArgs.forEach((actualArg, index) => {
306
+ const expectedArg = expectedArgs[index];
307
+ if (expectedArg) {
308
+ diags.push(...this.checkNode(context, actualArg, expectedArg.type));
309
+ }
310
+ });
311
+ if (expectedArgs.length > actualArgs.length) {
312
+ const requiredArgs = expectedArgs.filter((a) => !a.optional);
313
+ if (actualArgs.length < requiredArgs.length) {
314
+ diags.push(__spreadProps(__spreadValues({
315
+ category: ts__namespace.DiagnosticCategory.Error,
316
+ code: 1,
317
+ file: context.node.getSourceFile()
318
+ }, toTSLocation(node.callTarget)), {
319
+ messageText: `Expected ${requiredArgs.length} argument(s), got ${actualArgs.length}`
320
+ }));
321
+ }
322
+ }
323
+ } else {
324
+ diags.push(__spreadProps(__spreadValues({
325
+ category: ts__namespace.DiagnosticCategory.Error,
326
+ code: 1,
327
+ file: context.node.getSourceFile()
328
+ }, toTSLocation(node.callTarget)), {
329
+ messageText: `Unknown expression ${exprName}`
330
+ }));
331
+ }
332
+ }
333
+ return diags;
334
+ }
335
+ getSemanticDiagnostics(context) {
336
+ if (this._plugins.length === 0) {
337
+ return [];
338
+ }
339
+ const parsed = player.parseExpression(context.text.trim(), { strict: false });
340
+ return this.getDiagnosticsForNode(context, parsed);
341
+ }
342
+ getSyntacticDiagnostics(context) {
343
+ const parsed = player.parseExpression(context.text.trim(), { strict: false });
344
+ if (parsed.error) {
345
+ return [
346
+ __spreadProps(__spreadValues({
347
+ category: ts__namespace.DiagnosticCategory.Error,
348
+ code: 1,
349
+ file: context.node.getSourceFile()
350
+ }, toTSLocation(parsed)), {
351
+ messageText: parsed.error.message
352
+ })
353
+ ];
354
+ }
355
+ return [];
356
+ }
217
357
  }
218
358
 
219
359
  class LSPLogger {
package/dist/index.esm.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import { decorateWithTemplateLanguageService } from 'typescript-template-language-service-decorator';
2
2
  import * as ts from 'typescript/lib/tsserverlibrary';
3
3
  import { createTSDocString } from '@player-tools/xlr-utils';
4
+ import { simpleTransformGenerator, XLRSDK } from '@player-tools/xlr-sdk';
4
5
  import { parseExpression } from '@player-ui/player';
6
+ import { parseTree } from 'jsonc-parser';
5
7
 
6
8
  function isInRange(position, location) {
7
9
  return position.character >= location.start.character && position.character <= location.end.character;
@@ -30,6 +32,83 @@ function getTokenAtPosition(node, position) {
30
32
  return node;
31
33
  }
32
34
  }
35
+ function toTSLocation(node) {
36
+ var _a, _b;
37
+ const start = (_a = node.location) == null ? void 0 : _a.start.character;
38
+ const end = (_b = node.location) == null ? void 0 : _b.end.character;
39
+ if (start === void 0 || end === void 0) {
40
+ return {
41
+ start: 0,
42
+ length: 0
43
+ };
44
+ }
45
+ return {
46
+ start,
47
+ length: end - start
48
+ };
49
+ }
50
+ function convertExprToValue(exprNode) {
51
+ let val;
52
+ if (exprNode.type === "Literal") {
53
+ val = exprNode.value;
54
+ } else if (exprNode.type === "Object") {
55
+ val = {};
56
+ exprNode.attributes.forEach((prop) => {
57
+ val[convertExprToValue(prop.key)] = convertExprToValue(prop.value);
58
+ });
59
+ } else if (exprNode.type === "ArrayExpression") {
60
+ val = exprNode.elements.map(convertExprToValue);
61
+ }
62
+ return val;
63
+ }
64
+ function convertExprToJSONNode(exprNode) {
65
+ const val = convertExprToValue(exprNode);
66
+ if (val === void 0) {
67
+ return void 0;
68
+ }
69
+ return parseTree(JSON.stringify(val));
70
+ }
71
+
72
+ var __defProp$1 = Object.defineProperty;
73
+ var __defProps$1 = Object.defineProperties;
74
+ var __getOwnPropDescs$1 = Object.getOwnPropertyDescriptors;
75
+ var __getOwnPropSymbols$1 = Object.getOwnPropertySymbols;
76
+ var __hasOwnProp$1 = Object.prototype.hasOwnProperty;
77
+ var __propIsEnum$1 = Object.prototype.propertyIsEnumerable;
78
+ var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
79
+ var __spreadValues$1 = (a, b) => {
80
+ for (var prop in b || (b = {}))
81
+ if (__hasOwnProp$1.call(b, prop))
82
+ __defNormalProp$1(a, prop, b[prop]);
83
+ if (__getOwnPropSymbols$1)
84
+ for (var prop of __getOwnPropSymbols$1(b)) {
85
+ if (__propIsEnum$1.call(b, prop))
86
+ __defNormalProp$1(a, prop, b[prop]);
87
+ }
88
+ return a;
89
+ };
90
+ var __spreadProps$1 = (a, b) => __defProps$1(a, __getOwnPropDescs$1(b));
91
+ const toFunction = simpleTransformGenerator("ref", "Expressions", (exp) => {
92
+ if (!exp.genericArguments || exp.ref !== "ExpressionHandler") {
93
+ return exp;
94
+ }
95
+ const [args, returnType] = exp.genericArguments;
96
+ const parameters = (args.type === "tuple" ? args.elementTypes : []).map((elementType, index) => {
97
+ var _a, _b, _c, _d, _e, _f;
98
+ return {
99
+ name: (_c = (_b = (_a = elementType.name) != null ? _a : elementType.type.name) != null ? _b : elementType.type.title) != null ? _c : `arg_${index}`,
100
+ type: __spreadValues$1({
101
+ name: (_f = (_e = (_d = elementType.name) != null ? _d : elementType.type.name) != null ? _e : elementType.type.title) != null ? _f : `arg_${index}`
102
+ }, elementType.type),
103
+ optional: elementType.optional === true ? elementType.optional : void 0
104
+ };
105
+ });
106
+ return __spreadProps$1(__spreadValues$1({}, exp), {
107
+ type: "function",
108
+ parameters,
109
+ returnType
110
+ });
111
+ });
33
112
 
34
113
  var __defProp = Object.defineProperty;
35
114
  var __defProps = Object.defineProperties;
@@ -50,59 +129,44 @@ var __spreadValues = (a, b) => {
50
129
  return a;
51
130
  };
52
131
  var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
53
- function reduceExpression(plugins) {
54
- const expressions = new Map();
55
- plugins.forEach((plugin) => {
56
- var _a;
57
- const { capabilities } = plugin;
58
- const registeredExpressions = (_a = capabilities == null ? void 0 : capabilities.Expressions) != null ? _a : [];
59
- registeredExpressions.forEach((exp) => {
60
- var _a2;
61
- if (exp.type !== "ref" || !exp.genericArguments) {
62
- return;
63
- }
64
- const expName = exp.name;
65
- const [args, returnType] = exp.genericArguments;
66
- const parameters = (args.type === "tuple" ? args.elementTypes : []).map((elementType) => {
67
- var _a3, _b, _c;
68
- return {
69
- name: (_c = (_b = (_a3 = elementType.name) != null ? _a3 : elementType.type.name) != null ? _b : elementType.type.title) != null ? _c : "",
70
- type: elementType.type,
71
- optional: elementType.optional === true ? elementType.optional : void 0
72
- };
73
- });
74
- const entry = {
75
- name: expName,
76
- description: (_a2 = exp.description) != null ? _a2 : "",
77
- source: plugin,
78
- type: __spreadProps(__spreadValues({}, exp), {
79
- type: "function",
80
- parameters,
81
- returnType
82
- })
83
- };
84
- expressions.set(expName, entry);
85
- });
86
- });
87
- const expList = {
88
- entries: expressions
89
- };
90
- return expList;
91
- }
92
132
  class ExpressionLanguageService {
93
133
  constructor(options) {
94
134
  this._plugins = [];
95
135
  var _a;
96
136
  this.logger = options == null ? void 0 : options.logger;
97
137
  this._plugins = (_a = options == null ? void 0 : options.plugins) != null ? _a : [];
98
- this._expressions = reduceExpression(this._plugins);
138
+ this.xlr = new XLRSDK();
139
+ this._plugins.forEach((p) => {
140
+ this.xlr.loadDefinitionsFromModule(p, void 0, [toFunction]);
141
+ });
142
+ this._expressions = this.reduceExpression();
99
143
  }
100
144
  setConfig(config) {
101
145
  this._plugins = config.plugins;
102
- this._expressions = reduceExpression(this._plugins);
146
+ this.xlr = new XLRSDK();
147
+ this._plugins.forEach((p) => {
148
+ this.xlr.loadDefinitionsFromModule(p, void 0, [toFunction]);
149
+ });
150
+ this._expressions = this.reduceExpression();
151
+ }
152
+ reduceExpression() {
153
+ const expressions = new Map();
154
+ this.xlr.listTypes().forEach((type) => {
155
+ const typeInfo = this.xlr.getTypeInfo(type.name);
156
+ const source = this._plugins.find((value) => value.pluginName === (typeInfo == null ? void 0 : typeInfo.plugin));
157
+ if (type.type === "function" && (typeInfo == null ? void 0 : typeInfo.capability) === "Expressions" && source) {
158
+ expressions.set(type.name, {
159
+ name: type.name,
160
+ description: type.description,
161
+ type,
162
+ source
163
+ });
164
+ }
165
+ });
166
+ return expressions;
103
167
  }
104
168
  getCompletionsAtPosition(context, position) {
105
- var _a, _b, _c, _d;
169
+ var _a, _b;
106
170
  const completionInfo = {
107
171
  isGlobalCompletion: false,
108
172
  isMemberCompletion: false,
@@ -110,7 +174,7 @@ class ExpressionLanguageService {
110
174
  entries: []
111
175
  };
112
176
  if (context.text.length === 0) {
113
- this._expressions.entries.forEach((exp) => {
177
+ this._expressions.forEach((exp) => {
114
178
  completionInfo.entries.push({
115
179
  name: exp.name,
116
180
  kind: ts.ScriptElementKind.functionElement,
@@ -124,9 +188,8 @@ class ExpressionLanguageService {
124
188
  const line = context.text.split(/\n/g)[position.line];
125
189
  const parsed = parseExpression(line, { strict: false });
126
190
  const token = getTokenAtPosition(parsed, position);
127
- (_b = this.logger) == null ? void 0 : _b.log(`Token ${token == null ? void 0 : token.type} ${(_a = token == null ? void 0 : token.error) == null ? void 0 : _a.message}`);
128
191
  if ((token == null ? void 0 : token.type) === "Compound" && token.error) {
129
- this._expressions.entries.forEach((exp) => {
192
+ this._expressions.forEach((exp) => {
130
193
  completionInfo.entries.push({
131
194
  name: exp.name,
132
195
  kind: ts.ScriptElementKind.functionElement,
@@ -138,9 +201,9 @@ class ExpressionLanguageService {
138
201
  return completionInfo;
139
202
  }
140
203
  if ((token == null ? void 0 : token.type) === "Identifier") {
141
- const start = (_d = (_c = token.location) == null ? void 0 : _c.start) != null ? _d : { character: 0 };
204
+ const start = (_b = (_a = token.location) == null ? void 0 : _a.start) != null ? _b : { character: 0 };
142
205
  const wordFromStart = line.slice(start.character, position.character);
143
- const allCompletions = Array.from(this._expressions.entries.keys()).filter((key) => key.startsWith(wordFromStart));
206
+ const allCompletions = Array.from(this._expressions.keys()).filter((key) => key.startsWith(wordFromStart));
144
207
  allCompletions.forEach((c) => {
145
208
  completionInfo.entries.push({
146
209
  name: c,
@@ -154,8 +217,8 @@ class ExpressionLanguageService {
154
217
  return completionInfo;
155
218
  }
156
219
  getCompletionEntryDetails(context, position, name) {
157
- var _a, _b, _c;
158
- const expression = this._expressions.entries.get(name);
220
+ var _a;
221
+ const expression = this._expressions.get(name);
159
222
  const completionDetails = {
160
223
  name,
161
224
  kind: ts.ScriptElementKind.functionElement,
@@ -168,30 +231,107 @@ class ExpressionLanguageService {
168
231
  ],
169
232
  displayParts: expression ? createTSDocString(expression.type) : []
170
233
  };
171
- (_b = this.logger) == null ? void 0 : _b.log(`getCompletionEntryDetails: ${name}`);
172
- (_c = this.logger) == null ? void 0 : _c.log(JSON.stringify(completionDetails));
173
234
  return completionDetails;
174
235
  }
175
236
  getQuickInfoAtPosition(context, position) {
176
- var _a, _b, _c, _d, _e, _f, _g, _h;
177
- (_a = this.logger) == null ? void 0 : _a.log(`getQuickInfoAtPosition: ${context.text}`);
178
237
  const parsed = parseExpression(context.text, { strict: false });
179
238
  const token = getTokenAtPosition(parsed, position);
180
- (_b = this.logger) == null ? void 0 : _b.log(`token: ${token == null ? void 0 : token.type}`);
181
239
  if ((token == null ? void 0 : token.type) === "Identifier") {
182
- const expression = this._expressions.entries.get(token.name);
240
+ const expression = this._expressions.get(token.name);
183
241
  if (expression) {
184
242
  const completionDetails = this.getCompletionEntryDetails(context, position, expression.name);
185
243
  return __spreadProps(__spreadValues({}, completionDetails), {
186
- textSpan: {
187
- start: (_d = (_c = token.location) == null ? void 0 : _c.start.character) != null ? _d : 0,
188
- length: ((_f = (_e = token.location) == null ? void 0 : _e.end.character) != null ? _f : 0) - ((_h = (_g = token.location) == null ? void 0 : _g.start.character) != null ? _h : 0)
189
- }
244
+ textSpan: toTSLocation(token)
190
245
  });
191
246
  }
192
247
  }
193
248
  return void 0;
194
249
  }
250
+ checkNode(context, node, xlrType) {
251
+ const diagnostics = [];
252
+ const asJsonNodeValue = convertExprToJSONNode(node);
253
+ if (asJsonNodeValue) {
254
+ const xlrDiags = this.xlr.validateByType(xlrType, asJsonNodeValue);
255
+ xlrDiags.forEach((d) => {
256
+ diagnostics.push(__spreadProps(__spreadValues({
257
+ file: context.node.getSourceFile()
258
+ }, toTSLocation(node)), {
259
+ messageText: d.message,
260
+ category: ts.DiagnosticCategory.Error,
261
+ code: 1
262
+ }));
263
+ });
264
+ }
265
+ return diagnostics;
266
+ }
267
+ getDiagnosticsForNode(context, node) {
268
+ const diags = [];
269
+ if (node.type === "Compound") {
270
+ node.body.forEach((n) => {
271
+ diags.push(...this.getDiagnosticsForNode(context, n));
272
+ });
273
+ }
274
+ if (node.type === "CallExpression") {
275
+ const exprName = node.callTarget.name;
276
+ const expression = this._expressions.get(exprName);
277
+ node.args.forEach((n) => {
278
+ diags.push(...this.getDiagnosticsForNode(context, n));
279
+ });
280
+ if (expression) {
281
+ const expectedArgs = expression.type.parameters;
282
+ const actualArgs = node.args;
283
+ actualArgs.forEach((actualArg, index) => {
284
+ const expectedArg = expectedArgs[index];
285
+ if (expectedArg) {
286
+ diags.push(...this.checkNode(context, actualArg, expectedArg.type));
287
+ }
288
+ });
289
+ if (expectedArgs.length > actualArgs.length) {
290
+ const requiredArgs = expectedArgs.filter((a) => !a.optional);
291
+ if (actualArgs.length < requiredArgs.length) {
292
+ diags.push(__spreadProps(__spreadValues({
293
+ category: ts.DiagnosticCategory.Error,
294
+ code: 1,
295
+ file: context.node.getSourceFile()
296
+ }, toTSLocation(node.callTarget)), {
297
+ messageText: `Expected ${requiredArgs.length} argument(s), got ${actualArgs.length}`
298
+ }));
299
+ }
300
+ }
301
+ } else {
302
+ diags.push(__spreadProps(__spreadValues({
303
+ category: ts.DiagnosticCategory.Error,
304
+ code: 1,
305
+ file: context.node.getSourceFile()
306
+ }, toTSLocation(node.callTarget)), {
307
+ messageText: `Unknown expression ${exprName}`
308
+ }));
309
+ }
310
+ }
311
+ return diags;
312
+ }
313
+ getSemanticDiagnostics(context) {
314
+ if (this._plugins.length === 0) {
315
+ return [];
316
+ }
317
+ const parsed = parseExpression(context.text.trim(), { strict: false });
318
+ return this.getDiagnosticsForNode(context, parsed);
319
+ }
320
+ getSyntacticDiagnostics(context) {
321
+ const parsed = parseExpression(context.text.trim(), { strict: false });
322
+ if (parsed.error) {
323
+ return [
324
+ __spreadProps(__spreadValues({
325
+ category: ts.DiagnosticCategory.Error,
326
+ code: 1,
327
+ file: context.node.getSourceFile()
328
+ }, toTSLocation(parsed)), {
329
+ messageText: parsed.error.message
330
+ })
331
+ ];
332
+ }
333
+ return [];
334
+ }
195
335
  }
196
336
 
197
337
  class LSPLogger {
package/package.json CHANGED
@@ -1,16 +1,17 @@
1
1
  {
2
2
  "name": "@player-tools/typescript-expression-plugin",
3
- "version": "0.2.2--canary.20.485",
3
+ "version": "0.4.0-next.1",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org"
7
7
  },
8
8
  "peerDependencies": {},
9
9
  "dependencies": {
10
- "@player-tools/xlr": "0.2.2--canary.20.485",
11
- "@player-tools/xlr-sdk": "0.2.2--canary.20.485",
12
- "@player-tools/xlr-utils": "0.2.2--canary.20.485",
13
- "@player-ui/player": "0.3.1--canary.117.4130",
10
+ "@player-tools/xlr": "0.4.0-next.1",
11
+ "@player-tools/xlr-sdk": "0.4.0-next.1",
12
+ "@player-tools/xlr-utils": "0.4.0-next.1",
13
+ "@player-ui/player": "0.4.0-next.7",
14
+ "jsonc-parser": "^2.3.1",
14
15
  "typescript-template-language-service-decorator": "^2.3.1",
15
16
  "@babel/runtime": "7.15.4"
16
17
  },
package/src/service.ts CHANGED
@@ -4,103 +4,102 @@ import type {
4
4
  TemplateContext,
5
5
  Logger,
6
6
  } from 'typescript-template-language-service-decorator';
7
- import type {
8
- FunctionType,
9
- TSManifest,
10
- FunctionTypeParameters,
11
- NodeType,
12
- } from '@player-tools/xlr';
7
+ import type { FunctionType, TSManifest, NodeType } from '@player-tools/xlr';
13
8
  import { createTSDocString } from '@player-tools/xlr-utils';
9
+ import { XLRSDK } from '@player-tools/xlr-sdk';
10
+ import type { ExpressionNode } from '@player-ui/player';
14
11
  import { parseExpression } from '@player-ui/player';
15
- import { getTokenAtPosition } from './utils';
16
-
17
- interface ExpEntry {
12
+ import {
13
+ getTokenAtPosition,
14
+ toTSLocation,
15
+ convertExprToJSONNode,
16
+ } from './utils';
17
+ import { toFunction } from './transforms';
18
+
19
+ interface ExpressionEntry {
20
+ /**
21
+ * The name of the expression
22
+ */
18
23
  name: string;
19
- description: string;
24
+ /**
25
+ * The description of the expression
26
+ */
27
+ description?: string;
28
+ /**
29
+ * The XLR type of the expression
30
+ */
20
31
  type: FunctionType;
32
+ /**
33
+ * The XLR enabled plugin the expression was sourced from
34
+ */
21
35
  source: TSManifest;
22
36
  }
23
37
 
24
- interface ExpressionList {
25
- entries: Map<string, ExpEntry>;
26
- }
27
-
28
38
  export interface ExpressionLanguageServiceConfig {
39
+ /**
40
+ * The list of XLR enabled plugins to load
41
+ */
29
42
  plugins: Array<TSManifest>;
30
43
  }
31
44
 
32
- // TODO: This should move into the xlr/cli package so expressions are converted to functions at build time
33
- function reduceExpression(plugins: Array<TSManifest>): ExpressionList {
34
- // Overlaps in names will be resolved by the last plugin to be loaded
35
- // So use a map to ensure no duplicates
36
- const expressions = new Map<string, ExpEntry>();
37
-
38
- plugins.forEach((plugin) => {
39
- const { capabilities } = plugin;
40
- const registeredExpressions = capabilities?.Expressions ?? [];
41
-
42
- registeredExpressions.forEach((exp) => {
43
- if (exp.type !== 'ref' || !exp.genericArguments) {
44
- return;
45
- }
46
-
47
- const expName = exp.name;
48
- const [args, returnType] = exp.genericArguments;
49
-
50
- const parameters: Array<FunctionTypeParameters> = (
51
- args.type === 'tuple' ? args.elementTypes : []
52
- ).map((elementType) => {
53
- return {
54
- name:
55
- elementType.name ??
56
- elementType.type.name ??
57
- elementType.type.title ??
58
- '',
59
- type: elementType.type,
60
- optional:
61
- elementType.optional === true ? elementType.optional : undefined,
62
- };
63
- });
64
-
65
- const entry: ExpEntry = {
66
- name: expName,
67
- description: exp.description ?? '',
68
- source: plugin,
69
- type: {
70
- ...exp,
71
- type: 'function',
72
- parameters,
73
- returnType,
74
- },
75
- };
76
-
77
- expressions.set(expName, entry);
78
- });
79
- });
80
-
81
- const expList: ExpressionList = {
82
- entries: expressions,
83
- };
84
-
85
- return expList;
86
- }
87
-
45
+ /**
46
+ * Language server to check Player expression syntax and usage
47
+ */
88
48
  export class ExpressionLanguageService implements TemplateLanguageService {
89
49
  private logger?: Logger;
90
50
  private _plugins: Array<TSManifest> = [];
91
- private _expressions: ExpressionList;
51
+ private _expressions: Map<string, ExpressionEntry>;
52
+ private xlr: XLRSDK;
92
53
 
93
54
  constructor(
94
55
  options?: { logger?: Logger } & Partial<ExpressionLanguageServiceConfig>
95
56
  ) {
96
57
  this.logger = options?.logger;
97
58
  this._plugins = options?.plugins ?? [];
98
- this._expressions = reduceExpression(this._plugins);
59
+ this.xlr = new XLRSDK();
60
+
61
+ this._plugins.forEach((p) => {
62
+ this.xlr.loadDefinitionsFromModule(p, undefined, [toFunction]);
63
+ });
64
+ this._expressions = this.reduceExpression();
99
65
  }
100
66
 
101
67
  setConfig(config: ExpressionLanguageServiceConfig) {
102
68
  this._plugins = config.plugins;
103
- this._expressions = reduceExpression(this._plugins);
69
+ this.xlr = new XLRSDK();
70
+
71
+ this._plugins.forEach((p) => {
72
+ this.xlr.loadDefinitionsFromModule(p, undefined, [toFunction]);
73
+ });
74
+ this._expressions = this.reduceExpression();
75
+ }
76
+
77
+ private reduceExpression(): Map<string, ExpressionEntry> {
78
+ // Overlaps in names will be resolved by the last plugin to be loaded
79
+ // So use a map to ensure no duplicates
80
+ const expressions = new Map<string, ExpressionEntry>();
81
+
82
+ this.xlr.listTypes().forEach((type) => {
83
+ const typeInfo = this.xlr.getTypeInfo(type.name);
84
+ const source = this._plugins.find(
85
+ (value) => value.pluginName === typeInfo?.plugin
86
+ );
87
+
88
+ if (
89
+ type.type === 'function' &&
90
+ typeInfo?.capability === 'Expressions' &&
91
+ source
92
+ ) {
93
+ expressions.set(type.name, {
94
+ name: type.name,
95
+ description: type.description,
96
+ type: type as FunctionType,
97
+ source,
98
+ });
99
+ }
100
+ });
101
+
102
+ return expressions;
104
103
  }
105
104
 
106
105
  getCompletionsAtPosition(
@@ -118,7 +117,7 @@ export class ExpressionLanguageService implements TemplateLanguageService {
118
117
  // This happens for the start of an expression (e``)
119
118
  // Provide all the completions in this case
120
119
 
121
- this._expressions.entries.forEach((exp) => {
120
+ this._expressions.forEach((exp) => {
122
121
  completionInfo.entries.push({
123
122
  name: exp.name,
124
123
  kind: ts.ScriptElementKind.functionElement,
@@ -135,12 +134,10 @@ export class ExpressionLanguageService implements TemplateLanguageService {
135
134
  const parsed = parseExpression(line, { strict: false });
136
135
  const token = getTokenAtPosition(parsed, position);
137
136
 
138
- this.logger?.log(`Token ${token?.type} ${token?.error?.message}`);
139
-
140
137
  if (token?.type === 'Compound' && token.error) {
141
138
  // We hit the end of the expression, and it's expecting more
142
139
  // so provide all the completions
143
- this._expressions.entries.forEach((exp) => {
140
+ this._expressions.forEach((exp) => {
144
141
  completionInfo.entries.push({
145
142
  name: exp.name,
146
143
  kind: ts.ScriptElementKind.functionElement,
@@ -157,9 +154,9 @@ export class ExpressionLanguageService implements TemplateLanguageService {
157
154
  // get the relevant start of the identifier
158
155
  const start = token.location?.start ?? { character: 0 };
159
156
  const wordFromStart = line.slice(start.character, position.character);
160
- const allCompletions = Array.from(
161
- this._expressions.entries.keys()
162
- ).filter((key) => key.startsWith(wordFromStart));
157
+ const allCompletions = Array.from(this._expressions.keys()).filter(
158
+ (key) => key.startsWith(wordFromStart)
159
+ );
163
160
 
164
161
  allCompletions.forEach((c) => {
165
162
  completionInfo.entries.push({
@@ -180,7 +177,7 @@ export class ExpressionLanguageService implements TemplateLanguageService {
180
177
  position: ts.LineAndCharacter,
181
178
  name: string
182
179
  ): ts.CompletionEntryDetails {
183
- const expression = this._expressions.entries.get(name);
180
+ const expression = this._expressions.get(name);
184
181
 
185
182
  const completionDetails: ts.CompletionEntryDetails = {
186
183
  name,
@@ -196,9 +193,6 @@ export class ExpressionLanguageService implements TemplateLanguageService {
196
193
  displayParts: expression ? createTSDocString(expression.type) : [],
197
194
  };
198
195
 
199
- this.logger?.log(`getCompletionEntryDetails: ${name}`);
200
- this.logger?.log(JSON.stringify(completionDetails));
201
-
202
196
  return completionDetails;
203
197
  }
204
198
 
@@ -206,15 +200,11 @@ export class ExpressionLanguageService implements TemplateLanguageService {
206
200
  context: TemplateContext,
207
201
  position: ts.LineAndCharacter
208
202
  ): ts.QuickInfo | undefined {
209
- this.logger?.log(`getQuickInfoAtPosition: ${context.text}`);
210
-
211
203
  const parsed = parseExpression(context.text, { strict: false });
212
204
  const token = getTokenAtPosition(parsed, position);
213
205
 
214
- this.logger?.log(`token: ${token?.type}`);
215
-
216
206
  if (token?.type === 'Identifier') {
217
- const expression = this._expressions.entries.get(token.name);
207
+ const expression = this._expressions.get(token.name);
218
208
 
219
209
  if (expression) {
220
210
  const completionDetails = this.getCompletionEntryDetails(
@@ -225,16 +215,125 @@ export class ExpressionLanguageService implements TemplateLanguageService {
225
215
 
226
216
  return {
227
217
  ...completionDetails,
228
- textSpan: {
229
- start: token.location?.start.character ?? 0,
230
- length:
231
- (token.location?.end.character ?? 0) -
232
- (token.location?.start.character ?? 0),
233
- },
218
+ textSpan: toTSLocation(token),
234
219
  };
235
220
  }
236
221
  }
237
222
 
238
223
  return undefined;
239
224
  }
225
+
226
+ private checkNode(
227
+ context: TemplateContext,
228
+ node: ExpressionNode,
229
+ xlrType: NodeType
230
+ ): ts.Diagnostic[] {
231
+ const diagnostics: ts.Diagnostic[] = [];
232
+ const asJsonNodeValue = convertExprToJSONNode(node);
233
+
234
+ if (asJsonNodeValue) {
235
+ const xlrDiags = this.xlr.validateByType(xlrType, asJsonNodeValue);
236
+
237
+ xlrDiags.forEach((d) => {
238
+ diagnostics.push({
239
+ file: context.node.getSourceFile(),
240
+ ...toTSLocation(node),
241
+ messageText: d.message,
242
+ category: ts.DiagnosticCategory.Error,
243
+ code: 1,
244
+ });
245
+ });
246
+ }
247
+
248
+ return diagnostics;
249
+ }
250
+
251
+ private getDiagnosticsForNode(
252
+ context: TemplateContext,
253
+ node: ExpressionNode
254
+ ): ts.Diagnostic[] {
255
+ const diags: ts.Diagnostic[] = [];
256
+
257
+ if (node.type === 'Compound') {
258
+ node.body.forEach((n) => {
259
+ diags.push(...this.getDiagnosticsForNode(context, n));
260
+ });
261
+ }
262
+
263
+ if (node.type === 'CallExpression') {
264
+ // Check that the expression is valid
265
+ const exprName = node.callTarget.name;
266
+ const expression = this._expressions.get(exprName);
267
+
268
+ node.args.forEach((n) => {
269
+ diags.push(...this.getDiagnosticsForNode(context, n));
270
+ });
271
+
272
+ if (expression) {
273
+ // Double check the arguments match what we expect
274
+ const expectedArgs = expression.type.parameters;
275
+ const actualArgs = node.args;
276
+
277
+ actualArgs.forEach((actualArg, index) => {
278
+ const expectedArg = expectedArgs[index];
279
+
280
+ if (expectedArg) {
281
+ // Check that the type satisfies the expected type
282
+ diags.push(...this.checkNode(context, actualArg, expectedArg.type));
283
+ }
284
+ });
285
+
286
+ if (expectedArgs.length > actualArgs.length) {
287
+ const requiredArgs = expectedArgs.filter((a) => !a.optional);
288
+ if (actualArgs.length < requiredArgs.length) {
289
+ diags.push({
290
+ category: ts.DiagnosticCategory.Error,
291
+ code: 1,
292
+ file: context.node.getSourceFile(),
293
+ ...toTSLocation(node.callTarget),
294
+ messageText: `Expected ${requiredArgs.length} argument(s), got ${actualArgs.length}`,
295
+ });
296
+ }
297
+ }
298
+ } else {
299
+ diags.push({
300
+ category: ts.DiagnosticCategory.Error,
301
+ code: 1,
302
+ file: context.node.getSourceFile(),
303
+ ...toTSLocation(node.callTarget),
304
+ messageText: `Unknown expression ${exprName}`,
305
+ });
306
+ }
307
+ }
308
+
309
+ return diags;
310
+ }
311
+
312
+ getSemanticDiagnostics(context: TemplateContext): ts.Diagnostic[] {
313
+ // Shortcut any type-checking if we don't have any info about what expressions are registered
314
+ if (this._plugins.length === 0) {
315
+ return [];
316
+ }
317
+
318
+ const parsed = parseExpression(context.text.trim(), { strict: false });
319
+ return this.getDiagnosticsForNode(context, parsed);
320
+ }
321
+
322
+ getSyntacticDiagnostics(context: TemplateContext): ts.Diagnostic[] {
323
+ const parsed = parseExpression(context.text.trim(), { strict: false });
324
+
325
+ if (parsed.error) {
326
+ return [
327
+ {
328
+ category: ts.DiagnosticCategory.Error,
329
+ code: 1,
330
+ file: context.node.getSourceFile(),
331
+ ...toTSLocation(parsed),
332
+ messageText: parsed.error.message,
333
+ },
334
+ ];
335
+ }
336
+
337
+ return [];
338
+ }
240
339
  }
@@ -0,0 +1,44 @@
1
+ import type { FunctionType, FunctionTypeParameters } from '@player-tools/xlr';
2
+ import { simpleTransformGenerator } from '@player-tools/xlr-sdk';
3
+
4
+ export const toFunction = simpleTransformGenerator(
5
+ 'ref',
6
+ 'Expressions',
7
+ (exp) => {
8
+ if (!exp.genericArguments || exp.ref !== 'ExpressionHandler') {
9
+ return exp;
10
+ }
11
+
12
+ const [args, returnType] = exp.genericArguments;
13
+
14
+ const parameters: Array<FunctionTypeParameters> = (
15
+ args.type === 'tuple' ? args.elementTypes : []
16
+ ).map((elementType, index) => {
17
+ return {
18
+ name:
19
+ elementType.name ??
20
+ elementType.type.name ??
21
+ elementType.type.title ??
22
+ `arg_${index}`,
23
+
24
+ type: {
25
+ name:
26
+ elementType.name ??
27
+ elementType.type.name ??
28
+ elementType.type.title ??
29
+ `arg_${index}`,
30
+ ...elementType.type,
31
+ },
32
+ optional:
33
+ elementType.optional === true ? elementType.optional : undefined,
34
+ };
35
+ });
36
+
37
+ return {
38
+ ...exp,
39
+ type: 'function',
40
+ parameters,
41
+ returnType,
42
+ } as FunctionType as any;
43
+ }
44
+ );
package/src/utils.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import type { Position } from 'vscode-languageserver-types';
2
2
  import type { ExpressionNode, NodeLocation } from '@player-ui/player';
3
+ import { parseTree } from 'jsonc-parser';
4
+ import type { Node } from 'jsonc-parser';
3
5
 
4
6
  /** Check if the vscode position overlaps with the expression location */
5
7
  export function isInRange(position: Position, location: NodeLocation) {
@@ -43,3 +45,50 @@ export function getTokenAtPosition(
43
45
  return node;
44
46
  }
45
47
  }
48
+
49
+ /** Get the location info that TS expects for it's diags */
50
+ export function toTSLocation(node: ExpressionNode): ts.TextSpan {
51
+ const start = node.location?.start.character;
52
+ const end = node.location?.end.character;
53
+ if (start === undefined || end === undefined) {
54
+ return {
55
+ start: 0,
56
+ length: 0,
57
+ };
58
+ }
59
+
60
+ return {
61
+ start,
62
+ length: end - start,
63
+ };
64
+ }
65
+
66
+ /** ExpressionNode -> raw value */
67
+ export function convertExprToValue(exprNode: ExpressionNode): any {
68
+ let val;
69
+
70
+ if (exprNode.type === 'Literal') {
71
+ val = exprNode.value;
72
+ } else if (exprNode.type === 'Object') {
73
+ val = {};
74
+ exprNode.attributes.forEach((prop) => {
75
+ val[convertExprToValue(prop.key)] = convertExprToValue(prop.value);
76
+ });
77
+ } else if (exprNode.type === 'ArrayExpression') {
78
+ val = exprNode.elements.map(convertExprToValue);
79
+ }
80
+
81
+ return val;
82
+ }
83
+
84
+ /** ExpressionNode -> JSONC Node */
85
+ export function convertExprToJSONNode(
86
+ exprNode: ExpressionNode
87
+ ): Node | undefined {
88
+ const val = convertExprToValue(exprNode);
89
+ if (val === undefined) {
90
+ return undefined;
91
+ }
92
+
93
+ return parseTree(JSON.stringify(val));
94
+ }