@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 +199 -59
- package/dist/index.esm.js +199 -59
- package/package.json +6 -5
- package/src/service.ts +194 -95
- package/src/transforms.ts +44 -0
- package/src/utils.ts +49 -0
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.
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
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 = (
|
|
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.
|
|
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
|
|
180
|
-
const expression = this._expressions.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
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 = (
|
|
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.
|
|
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
|
|
158
|
-
const expression = this._expressions.
|
|
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.
|
|
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.
|
|
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.
|
|
11
|
-
"@player-tools/xlr-sdk": "0.
|
|
12
|
-
"@player-tools/xlr-utils": "0.
|
|
13
|
-
"@player-ui/player": "0.
|
|
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 {
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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:
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
162
|
-
)
|
|
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.
|
|
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.
|
|
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
|
+
}
|