@player-tools/json-language-service 0.2.2--canary.20.454

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.
@@ -0,0 +1,289 @@
1
+ import type { Location } from 'vscode-languageserver-types';
2
+ import { CompletionItemKind } from 'vscode-languageserver-types';
3
+ import type { NodeType } from '@player-tools/xlr';
4
+ import type { PlayerLanguageService, PlayerLanguageServicePlugin } from '..';
5
+ import type {
6
+ DocumentContext,
7
+ EnhancedDocumentContextWithPosition,
8
+ } from '../types';
9
+ import { getLSLocationOfNode, getProperty, isValueCompletion } from '../utils';
10
+ import type { PropertyASTNode, StringASTNode } from '../parser';
11
+ import { getContentNode } from '../parser';
12
+
13
+ interface SchemaInfo {
14
+ /** mapping of binding to schema path */
15
+ bindingToSchemaType: Map<
16
+ string,
17
+ {
18
+ /** the binding */
19
+ binding: string;
20
+
21
+ /** the type name */
22
+ typeName: string;
23
+
24
+ /** the name of the key */
25
+ key: string;
26
+ }
27
+ >;
28
+
29
+ /** JSON AST mapping of type to the node */
30
+ typeToNode: Map<
31
+ string,
32
+ {
33
+ /** the type */
34
+ type: string;
35
+
36
+ /** the json node */
37
+ typeNode: PropertyASTNode;
38
+ }
39
+ >;
40
+ }
41
+
42
+ /** parse the document for the schema info */
43
+ function getBindingInfo(ctx: DocumentContext): SchemaInfo {
44
+ const info: SchemaInfo = {
45
+ bindingToSchemaType: new Map(),
46
+ typeToNode: new Map(),
47
+ };
48
+
49
+ if (ctx.PlayerContent.root.type !== 'content') {
50
+ return info;
51
+ }
52
+
53
+ const schemaRoot = ctx.PlayerContent.root.properties?.find(
54
+ (child) => child.keyNode.value === 'schema'
55
+ );
56
+
57
+ if (!schemaRoot || schemaRoot.valueNode?.type !== 'object') {
58
+ return info;
59
+ }
60
+
61
+ const schemaTypeQueue: Array<{
62
+ /** the current path */
63
+ currentPath: string;
64
+
65
+ /** the next type to visit */
66
+ typeToVisit: string;
67
+
68
+ /** list of visited types (to prevent loops) */
69
+ visited: Set<string>;
70
+ }> = [
71
+ {
72
+ currentPath: '',
73
+ typeToVisit: 'ROOT',
74
+ visited: new Set(),
75
+ },
76
+ ];
77
+
78
+ while (schemaTypeQueue.length > 0) {
79
+ const next = schemaTypeQueue.shift();
80
+ if (!next) {
81
+ break;
82
+ }
83
+
84
+ if (next.visited.has(next.typeToVisit)) {
85
+ continue;
86
+ }
87
+
88
+ const visited = new Set(...next.visited, next.typeToVisit);
89
+ const { currentPath, typeToVisit } = next;
90
+
91
+ const typeNode = schemaRoot.valueNode.properties.find(
92
+ (child) => child.keyNode.value === typeToVisit
93
+ );
94
+
95
+ if (!typeNode || typeNode.valueNode?.type !== 'object') {
96
+ continue;
97
+ }
98
+
99
+ info.typeToNode.set(typeToVisit, { type: typeToVisit, typeNode });
100
+
101
+ typeNode.valueNode.properties.forEach((prop) => {
102
+ // PropName is the path
103
+ // { type: TYPE } is the next nested type
104
+
105
+ const nextPath = [currentPath, prop.keyNode.value].join(
106
+ currentPath === '' ? '' : '.'
107
+ );
108
+
109
+ info.bindingToSchemaType.set(nextPath, {
110
+ binding: nextPath,
111
+ typeName: typeToVisit,
112
+ key: prop.keyNode.value,
113
+ });
114
+
115
+ if (prop.valueNode?.type === 'object') {
116
+ const nestedTypeName = prop.valueNode.properties.find(
117
+ (c) => c.keyNode.value === 'type'
118
+ );
119
+
120
+ if (nestedTypeName && nestedTypeName.valueNode?.type === 'string') {
121
+ schemaTypeQueue.push({
122
+ currentPath: nextPath,
123
+ typeToVisit: nestedTypeName.valueNode.value,
124
+ visited,
125
+ });
126
+ }
127
+ }
128
+ });
129
+ }
130
+
131
+ return info;
132
+ }
133
+
134
+ /**
135
+ * Checks to see if there is a Binding ref node somewhere at this level
136
+ *
137
+ * - @param nodes Array of nodes to check for an AssetWrapper ref
138
+ */
139
+ const checkTypesForBinding = (nodes: Array<NodeType>): boolean => {
140
+ for (let i = 0; i < nodes.length; i++) {
141
+ const node = nodes[i];
142
+ if (node.type === 'string' && node.name === 'Binding') return true;
143
+ if (node.type === 'or') return checkTypesForBinding(node.or);
144
+ if (node.type === 'and') return checkTypesForBinding(node.and);
145
+ }
146
+
147
+ return false;
148
+ };
149
+
150
+ /** check if the property is of type Binding */
151
+ function isBindingPropertyAssignment(
152
+ ctx: EnhancedDocumentContextWithPosition
153
+ ): boolean {
154
+ if (ctx.node.type !== 'string' || ctx.node.parent?.type !== 'property') {
155
+ return false;
156
+ }
157
+
158
+ if (checkTypesForBinding(ctx.XLR?.nodes ?? [])) {
159
+ return true;
160
+ }
161
+
162
+ return false;
163
+ }
164
+
165
+ /** find where in the document the type def is located */
166
+ function getLocationForBindingTypeDefinition(
167
+ ctx: EnhancedDocumentContextWithPosition,
168
+ schemaInfo: SchemaInfo
169
+ ): Location | undefined {
170
+ if (!isBindingPropertyAssignment(ctx)) {
171
+ return;
172
+ }
173
+
174
+ const existingBindingValue = (ctx.node as StringASTNode).value;
175
+ const info = schemaInfo.bindingToSchemaType.get(existingBindingValue);
176
+
177
+ if (!info) {
178
+ return;
179
+ }
180
+
181
+ const nodeLocation = schemaInfo.typeToNode.get(info.typeName);
182
+
183
+ if (!nodeLocation || nodeLocation.typeNode.valueNode?.type !== 'object') {
184
+ return;
185
+ }
186
+
187
+ const prop = getProperty(nodeLocation.typeNode.valueNode, info.key);
188
+
189
+ if (!prop) {
190
+ return;
191
+ }
192
+
193
+ return getLSLocationOfNode(ctx.document, prop);
194
+ }
195
+
196
+ /** find where the schema is for a type */
197
+ function getLocationForSchemaType(
198
+ ctx: EnhancedDocumentContextWithPosition,
199
+ schemaInfo: SchemaInfo
200
+ ): Location | undefined {
201
+ if (isValueCompletion(ctx.node)) {
202
+ // See if we're the "type" prop of a schema lookup
203
+
204
+ if (
205
+ ctx.node.parent?.type === 'property' &&
206
+ ctx.node.type === 'string' &&
207
+ ctx.node.parent.keyNode.value === 'type'
208
+ ) {
209
+ const typeName = ctx.node.value;
210
+ const node = schemaInfo.typeToNode.get(typeName);
211
+
212
+ if (!node) {
213
+ return;
214
+ }
215
+
216
+ const schemaPropNode = getContentNode(ctx.node)?.properties.find(
217
+ (p) => p.keyNode.value === 'schema'
218
+ );
219
+
220
+ if (schemaPropNode?.valueNode?.type !== 'object') {
221
+ return;
222
+ }
223
+
224
+ const schemaTypeNode = schemaPropNode.valueNode.properties.find(
225
+ (p) => p.keyNode.value === typeName
226
+ );
227
+
228
+ if (schemaTypeNode !== node.typeNode) {
229
+ return;
230
+ }
231
+
232
+ return getLSLocationOfNode(ctx.document, node.typeNode);
233
+ }
234
+ }
235
+ }
236
+
237
+ /**
238
+ *
239
+ * Adds completions for:
240
+ * - any `Binding` type from TS
241
+ * - "type" and "key" for non-defined types
242
+ *
243
+ * Adds definitions for:
244
+ * - any `Binding` reference to the schema def
245
+ */
246
+ export class SchemaInfoPlugin implements PlayerLanguageServicePlugin {
247
+ name = 'view-node';
248
+
249
+ apply(service: PlayerLanguageService) {
250
+ let schemaInfo: SchemaInfo | undefined;
251
+
252
+ service.hooks.onDocumentUpdate.tap(this.name, (ctx) => {
253
+ schemaInfo = getBindingInfo(ctx);
254
+ });
255
+
256
+ service.hooks.complete.tap(this.name, async (ctx, completionCtx) => {
257
+ // Is this a `binding` type
258
+ if (!isBindingPropertyAssignment(ctx)) {
259
+ return;
260
+ }
261
+
262
+ const existingBindingValue = (ctx.node as StringASTNode).value;
263
+
264
+ const bindings = Array.from(
265
+ schemaInfo?.bindingToSchemaType.keys() ?? []
266
+ ).filter((k) => k.startsWith(existingBindingValue));
267
+
268
+ bindings.forEach((b) => {
269
+ completionCtx.addCompletionItem({
270
+ kind: CompletionItemKind.Value,
271
+ label: b.substring(existingBindingValue.length),
272
+ });
273
+ });
274
+
275
+ // get bindings from schema
276
+ });
277
+
278
+ service.hooks.definition.tap(this.name, (ctx) => {
279
+ if (!schemaInfo) {
280
+ return;
281
+ }
282
+
283
+ return (
284
+ getLocationForSchemaType(ctx, schemaInfo) ||
285
+ getLocationForBindingTypeDefinition(ctx, schemaInfo)
286
+ );
287
+ });
288
+ }
289
+ }
@@ -0,0 +1,118 @@
1
+ import { DiagnosticSeverity } from 'vscode-languageserver-types';
2
+ import type { AssetASTNode, ASTNode, ViewASTNode } from '../parser';
3
+ import { getViewNode, replaceString } from '../parser';
4
+ import type { PlayerLanguageService, PlayerLanguageServicePlugin } from '..';
5
+ import type { Violation, ValidationContext, ASTVisitor } from '../types';
6
+
7
+ /** Create an id for the node given it's path */
8
+ const generateID = (node?: ASTNode): string => {
9
+ if (!node || node.type === 'view') {
10
+ return '';
11
+ }
12
+
13
+ const prefix = generateID(node.parent);
14
+ let current = '';
15
+
16
+ if (node.type === 'property') {
17
+ current = node.keyNode.value;
18
+ } else if (node.type === 'asset' && node.assetType?.valueNode?.value) {
19
+ current = node.assetType.valueNode?.value;
20
+ }
21
+
22
+ return [prefix, current].filter(Boolean).join('-');
23
+ };
24
+
25
+ /** Create a duplicate id violation for the given node */
26
+ const createViolation = (node: AssetASTNode): Violation | undefined => {
27
+ const valueNode = node.id?.valueNode;
28
+
29
+ if (!valueNode) {
30
+ return;
31
+ }
32
+
33
+ return {
34
+ node: valueNode,
35
+ severity: DiagnosticSeverity.Error,
36
+ message: `The id '${node.id?.valueNode?.value}' is already in use in this view.`,
37
+ fix: () => {
38
+ return {
39
+ name: 'Generate new ID',
40
+ edit: replaceString(valueNode, `"${generateID(node)}"`),
41
+ };
42
+ },
43
+ };
44
+ };
45
+
46
+ /** visit each of the assets in a view and check for duplicate ids */
47
+ const createValidationVisitor = (ctx: ValidationContext): ASTVisitor => {
48
+ const viewInfo = new Map<
49
+ ViewASTNode | AssetASTNode,
50
+ Map<
51
+ string,
52
+ {
53
+ /** the original asset node */
54
+ original: AssetASTNode;
55
+
56
+ /** if we've accounted for this node already */
57
+ handled: boolean;
58
+ }
59
+ >
60
+ >();
61
+
62
+ return {
63
+ AssetNode: (assetNode) => {
64
+ const view = getViewNode(assetNode);
65
+ if (!view) {
66
+ // not sure how you can get here
67
+ throw new Error(
68
+ 'Asset found but not within a view. Something is wrong'
69
+ );
70
+ }
71
+
72
+ const assetID = assetNode.id;
73
+
74
+ if (!assetID || !assetID.valueNode?.value) {
75
+ // Can't check for dupe ids if the asset doesn't have one
76
+ return;
77
+ }
78
+
79
+ const id = assetID.valueNode?.value;
80
+ if (!viewInfo.has(view)) {
81
+ viewInfo.set(view, new Map());
82
+ }
83
+
84
+ const assetIDMap = viewInfo.get(view);
85
+ const idInfo = assetIDMap?.get(id);
86
+
87
+ if (idInfo) {
88
+ if (!idInfo.handled) {
89
+ const origViolation = createViolation(idInfo.original);
90
+ if (origViolation) {
91
+ ctx.addViolation(origViolation);
92
+ }
93
+
94
+ idInfo.handled = true;
95
+ }
96
+
97
+ const assetViolation = createViolation(assetNode);
98
+ if (assetViolation) {
99
+ ctx.addViolation(assetViolation);
100
+ }
101
+ } else {
102
+ // not claimed yet so this is the first
103
+ assetIDMap?.set(id, { original: assetNode, handled: false });
104
+ }
105
+ },
106
+ };
107
+ };
108
+
109
+ /** The plugin to enable duplicate id checking/fixing */
110
+ export class DuplicateIDPlugin implements PlayerLanguageServicePlugin {
111
+ name = 'duplicate-id';
112
+
113
+ apply(service: PlayerLanguageService) {
114
+ service.hooks.validate.tap(this.name, async (ctx, validation) => {
115
+ validation.useASTVisitor(createValidationVisitor(validation));
116
+ });
117
+ }
118
+ }
@@ -0,0 +1,79 @@
1
+ import { DiagnosticSeverity } from 'vscode-languageserver-types';
2
+ import type { PlayerLanguageService, PlayerLanguageServicePlugin } from '..';
3
+ import type { ASTNode } from '../parser';
4
+ import { getNodeValue } from '../parser';
5
+ import type { ASTVisitor, ValidationContext } from '../types';
6
+
7
+ /** Create an AST visitor for checking the legacy action */
8
+ function createRuleVisitor(context: ValidationContext): ASTVisitor {
9
+ /** Check if a node is using the action asset or not */
10
+ const checkForLegacyAction = (node: ASTNode) => {
11
+ if (node.type === 'asset') {
12
+ return;
13
+ }
14
+
15
+ if (
16
+ node.type === 'object' &&
17
+ !node.properties.some(
18
+ (p) =>
19
+ p.keyNode.value === 'asset' ||
20
+ p.keyNode.value === 'dynamicSwitch' ||
21
+ p.keyNode.value === 'staticSwitch'
22
+ )
23
+ ) {
24
+ context.addViolation({
25
+ message: 'Migrate to an action-asset',
26
+ node,
27
+ severity: DiagnosticSeverity.Warning,
28
+ fix: () => {
29
+ const newActionAsset = {
30
+ asset: {
31
+ type: 'action',
32
+ ...getNodeValue(node),
33
+ },
34
+ };
35
+
36
+ return {
37
+ name: 'Convert to Asset',
38
+ edit: {
39
+ type: 'replace',
40
+ node,
41
+ value: JSON.stringify(newActionAsset, null, 2),
42
+ },
43
+ };
44
+ },
45
+ });
46
+ }
47
+ };
48
+
49
+ return {
50
+ ViewNode: (viewNode) => {
51
+ // Check for an `actions` array of non-assets
52
+
53
+ const actionsProp = viewNode.properties.find(
54
+ (p) => p.keyNode.value === 'actions'
55
+ );
56
+
57
+ if (!actionsProp || actionsProp.valueNode?.type !== 'array') {
58
+ return;
59
+ }
60
+
61
+ // Go through the array and add a violation/fix for anything that's not an asset
62
+
63
+ actionsProp.valueNode.children.forEach((action) => {
64
+ checkForLegacyAction(action);
65
+ });
66
+ },
67
+ };
68
+ }
69
+
70
+ /** A plugin that validates and corrects the usage of non-asset actions in a view */
71
+ export class LegacyActionPlugin implements PlayerLanguageServicePlugin {
72
+ name = 'legacy-action';
73
+
74
+ apply(service: PlayerLanguageService) {
75
+ service.hooks.validate.tap(this.name, async (ctx, validationContext) => {
76
+ validationContext.useASTVisitor(createRuleVisitor(validationContext));
77
+ });
78
+ }
79
+ }
@@ -0,0 +1,125 @@
1
+ import { addLast, omit, set } from 'timm';
2
+ import { DiagnosticSeverity } from 'vscode-languageserver-types';
3
+ import type { PlayerLanguageService, PlayerLanguageServicePlugin } from '..';
4
+ import type { AssetASTNode, ObjectASTNode, ViewASTNode } from '../parser';
5
+ import { getNodeValue } from '../parser';
6
+ import type {
7
+ ASTVisitor,
8
+ DocumentContext,
9
+ ValidationContext,
10
+ Violation,
11
+ } from '../types';
12
+ import { formatLikeNode } from '../utils';
13
+
14
+ /** Create a visitor for handling old template syntax */
15
+ function createRuleVisitor(
16
+ context: ValidationContext,
17
+ docInfo: DocumentContext
18
+ ): ASTVisitor {
19
+ /** Check a node for any signs of a legacy-template */
20
+ const checkForLegacyTemplate = (
21
+ node: ObjectASTNode | AssetASTNode | ViewASTNode
22
+ ) => {
23
+ // check if it has any of the 3 props we care about
24
+
25
+ const templateDataProp = node.properties.find(
26
+ (p) => p.keyNode.value === 'templateData'
27
+ );
28
+ const templateValueProp = node.properties.find(
29
+ (p) => p.keyNode.value === 'template'
30
+ );
31
+ const templateOutputProp = node.properties.find(
32
+ (p) => p.keyNode.value === 'templateOutput'
33
+ );
34
+
35
+ // If we don't have any of those props, or we just have the`template` prop and it points to an array, skip it all, we good.
36
+ if (
37
+ !templateDataProp &&
38
+ !templateOutputProp &&
39
+ (!templateValueProp || templateValueProp.valueNode?.type === 'array')
40
+ ) {
41
+ return;
42
+ }
43
+
44
+ const templateViolation: Omit<Violation, 'node'> = {
45
+ severity: DiagnosticSeverity.Error,
46
+ message: `Migrate to the template[] syntax.`,
47
+ fix: () => {
48
+ // Create the new template object;
49
+ const path = [
50
+ 'template',
51
+ templateValueProp?.valueNode?.type === 'array'
52
+ ? templateValueProp.valueNode.children.length
53
+ : 0,
54
+ ];
55
+
56
+ const newTemplateObj = {
57
+ value:
58
+ templateValueProp?.valueNode?.type !== 'array' &&
59
+ templateValueProp?.valueNode
60
+ ? getNodeValue(templateValueProp?.valueNode)
61
+ : {},
62
+ output: templateOutputProp?.valueNode?.jsonNode.value ?? '',
63
+ data: templateDataProp?.valueNode?.jsonNode.value ?? '',
64
+ };
65
+
66
+ const oldValue = getNodeValue(node);
67
+ let newValue = omit(oldValue, 'templateData');
68
+ newValue = omit(newValue, 'templateOutput');
69
+
70
+ if (templateValueProp?.valueNode?.type !== 'array') {
71
+ newValue = omit(newValue, 'template');
72
+ }
73
+
74
+ newValue = set(
75
+ newValue,
76
+ 'template',
77
+ addLast(newValue.template ?? [], newTemplateObj)
78
+ );
79
+
80
+ return {
81
+ edit: {
82
+ type: 'replace',
83
+ path,
84
+ node,
85
+ value: formatLikeNode(docInfo.document, node, newValue),
86
+ },
87
+ name: 'Convert to template[]',
88
+ };
89
+ },
90
+ };
91
+
92
+ if (templateDataProp) {
93
+ context.addViolation({
94
+ ...templateViolation,
95
+ node: templateDataProp,
96
+ });
97
+ }
98
+
99
+ if (templateOutputProp) {
100
+ context.addViolation({
101
+ ...templateViolation,
102
+ node: templateOutputProp,
103
+ });
104
+ }
105
+ };
106
+
107
+ return {
108
+ ViewNode: checkForLegacyTemplate,
109
+ AssetNode: checkForLegacyTemplate,
110
+ ObjectNode: checkForLegacyTemplate,
111
+ };
112
+ }
113
+
114
+ /** A plugin that handles the old legacy template syntax */
115
+ export class LegacyTemplatePlugin implements PlayerLanguageServicePlugin {
116
+ name = 'legacy-template';
117
+
118
+ apply(service: PlayerLanguageService) {
119
+ service.hooks.validate.tap(this.name, async (ctx, validationContext) => {
120
+ validationContext.useASTVisitor(
121
+ createRuleVisitor(validationContext, ctx)
122
+ );
123
+ });
124
+ }
125
+ }
@@ -0,0 +1,96 @@
1
+ import { DiagnosticSeverity } from 'vscode-languageserver-types';
2
+ import type { PlayerLanguageService, PlayerLanguageServicePlugin } from '..';
3
+ import type { ASTNode, ObjectASTNode } from '../parser';
4
+ import {
5
+ getNodeValue,
6
+ isKeyNode,
7
+ isObjectNode,
8
+ isPropertyNode,
9
+ } from '../parser';
10
+ import { formatLikeNode } from '../utils';
11
+
12
+ /** Get the JSON object that the validation targets */
13
+ const getObjectTarget = (node?: ASTNode): ObjectASTNode | undefined => {
14
+ if (isObjectNode(node)) {
15
+ return node;
16
+ }
17
+
18
+ if (
19
+ isKeyNode(node) &&
20
+ isPropertyNode(node.parent) &&
21
+ isObjectNode(node.parent.valueNode)
22
+ ) {
23
+ return node.parent.valueNode;
24
+ }
25
+ };
26
+
27
+ /**
28
+ * A plugin to help identify and fix the issue of forgetting the "asset" wrapper object
29
+ */
30
+ export class MissingAssetWrapperPlugin implements PlayerLanguageServicePlugin {
31
+ name = 'missing-asset-wrapper';
32
+
33
+ apply(languageService: PlayerLanguageService): void {
34
+ languageService.hooks.onValidateEnd.tap(
35
+ this.name,
36
+ (diagnostics, { addFixableViolation, documentContext }) => {
37
+ // Just be naive here
38
+ // If there's an error for "expected asset" + an unexpected `id` and `type`, replace that with our own
39
+
40
+ let filteredDiags = diagnostics;
41
+
42
+ const expectedAssetDiags = diagnostics.filter(
43
+ (d) =>
44
+ d.message.includes(
45
+ "Does not match any of the expected types for type: 'AssetWrapperOrSwitch'"
46
+ ) || d.message.startsWith('Expected property: asset')
47
+ );
48
+
49
+ expectedAssetDiags.forEach((d) => {
50
+ const originalNode = documentContext.PlayerContent.getNodeFromOffset(
51
+ documentContext.document.offsetAt(d.range.start)
52
+ );
53
+
54
+ const objectNode = getObjectTarget(originalNode);
55
+
56
+ if (objectNode && originalNode) {
57
+ // This 'expected property' diag is for the key of a property, where the value is the stubbed out asset
58
+ // Check for diags for keys in that nested object
59
+
60
+ // Now group the other diagnostics that are for unexpected props underneath that object
61
+ // We'll suppress these for now since they are bound to be wrong until they're wrapped in an asset
62
+ const associatedDiags = filteredDiags.filter((nestedDiag) => {
63
+ const diagNode = documentContext.PlayerContent.getNodeFromOffset(
64
+ documentContext.document.offsetAt(nestedDiag.range.start)
65
+ );
66
+
67
+ return objectNode.properties.some((p) => p.keyNode === diagNode);
68
+ });
69
+
70
+ addFixableViolation(d, {
71
+ node: originalNode,
72
+ message: d.message,
73
+ severity: d.severity ?? DiagnosticSeverity.Error,
74
+ fix: () => ({
75
+ name: `Wrap in "asset"`,
76
+ edit: {
77
+ type: 'replace',
78
+ node: objectNode,
79
+ value: formatLikeNode(documentContext.document, objectNode, {
80
+ asset: getNodeValue(objectNode),
81
+ }),
82
+ },
83
+ }),
84
+ });
85
+
86
+ filteredDiags = filteredDiags.filter(
87
+ (filteredD) => !associatedDiags.includes(filteredD)
88
+ );
89
+ }
90
+ });
91
+
92
+ return filteredDiags;
93
+ }
94
+ );
95
+ }
96
+ }