@player-ui/player 0.3.1-next.1 → 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.
@@ -12,6 +12,7 @@ import type {
12
12
  ValidationProvider,
13
13
  ValidationResponse,
14
14
  WarningValidationResponse,
15
+ StrongOrWeakBinding,
15
16
  } from '../../validator';
16
17
  import { ValidationMiddleware, ValidatorRegistry } from '../../validator';
17
18
  import type { Logger } from '../../logger';
@@ -326,24 +327,33 @@ export class ValidationController implements BindingTracker {
326
327
  const strongValidation = this.getValidationForBinding(binding);
327
328
 
328
329
  // return validation issues directly on bindings first
329
- if (strongValidation?.get()) return strongValidation.get();
330
+ if (strongValidation?.get()?.severity === 'error') {
331
+ return strongValidation.get();
332
+ }
330
333
 
331
334
  // if none, check to see any validations this binding may be a weak ref of and return
332
- const newInvalidBindings: Set<BindingInstance> = new Set();
333
- for (const [, weakValidation] of Array.from(this.validations)) {
335
+ const newInvalidBindings: Set<StrongOrWeakBinding> = new Set();
336
+ this.validations.forEach((weakValidation, strongBinding) => {
334
337
  if (
335
338
  caresAboutDataChanges(
336
339
  new Set([binding]),
337
340
  weakValidation.weakBindings
338
341
  ) &&
339
- weakValidation?.get()
342
+ weakValidation?.get()?.severity === 'error'
340
343
  ) {
341
- weakValidation?.weakBindings.forEach(
342
- newInvalidBindings.add,
343
- newInvalidBindings
344
- );
344
+ weakValidation?.weakBindings.forEach((weakBinding) => {
345
+ weakBinding === strongBinding
346
+ ? newInvalidBindings.add({
347
+ binding: weakBinding,
348
+ isStrong: true,
349
+ })
350
+ : newInvalidBindings.add({
351
+ binding: weakBinding,
352
+ isStrong: false,
353
+ });
354
+ });
345
355
  }
346
- }
356
+ });
347
357
 
348
358
  if (newInvalidBindings.size > 0) {
349
359
  return newInvalidBindings;
@@ -376,7 +386,10 @@ export class ValidationController implements BindingTracker {
376
386
  });
377
387
 
378
388
  if (originalValue !== withoutDefault) {
379
- this.options.model.set([[binding, originalValue]]);
389
+ // Don't trigger updates when setting the default value
390
+ this.options.model.set([[binding, originalValue]], {
391
+ silent: true,
392
+ });
380
393
  }
381
394
 
382
395
  this.updateValidationsForBinding(
@@ -452,7 +465,7 @@ export class ValidationController implements BindingTracker {
452
465
  const response = this.validationRunner(
453
466
  validationObj,
454
467
  context,
455
- binding
468
+ vBinding
456
469
  );
457
470
  return response ? { message: response.message } : undefined;
458
471
  });
@@ -582,7 +595,7 @@ export class ValidationController implements BindingTracker {
582
595
 
583
596
  const validations = new Map<BindingInstance, ValidationResponse>();
584
597
 
585
- for (const b of this.getBindings()) {
598
+ this.getBindings().forEach((b) => {
586
599
  const invalid = this.getValidationForBinding(b)?.get();
587
600
 
588
601
  if (invalid) {
@@ -594,7 +607,7 @@ export class ValidationController implements BindingTracker {
594
607
 
595
608
  validations.set(b, invalid);
596
609
  }
597
- }
610
+ });
598
611
 
599
612
  return {
600
613
  canTransition: validations.size === 0,
package/src/data/model.ts CHANGED
@@ -41,6 +41,11 @@ export interface DataModelOptions {
41
41
  */
42
42
  ignoreDefaultValue?: boolean;
43
43
 
44
+ /**
45
+ * A flag to indicate that this update should happen silently
46
+ */
47
+ silent?: boolean;
48
+
44
49
  /** Other context associated with this request */
45
50
  context?: {
46
51
  /** The data model to use when getting other data from the context of this request */
@@ -1,6 +1,7 @@
1
1
  import { SyncWaterfallHook, SyncBailHook } from 'tapable-ts';
2
2
  import parse from './parser';
3
3
  import * as DEFAULT_EXPRESSION_HANDLERS from './evaluator-functions';
4
+ import { isExpressionNode } from './types';
4
5
  import type {
5
6
  ExpressionNode,
6
7
  BinaryOperator,
@@ -9,7 +10,6 @@ import type {
9
10
  ExpressionContext,
10
11
  ExpressionHandler,
11
12
  } from './types';
12
- import { isExpressionNode } from '.';
13
13
 
14
14
  /** a && b -- but handles short cutting if the first value is false */
15
15
  const andandOperator: BinaryOperator = (ctx, a, b) => {
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * An expression to AST parser based on JSEP: http://jsep.from.so/
4
4
  */
5
- import type { ExpressionNode, ExpressionNodeType } from './types';
5
+ import type { ExpressionNode, ExpressionNodeType, NodeLocation } from './types';
6
6
  import { ExpNodeOpaqueIdentifier } from './types';
7
7
 
8
8
  const PERIOD_CODE = 46; // '.'
@@ -80,6 +80,18 @@ function throwError(message: string, index: number) {
80
80
  throw err;
81
81
  }
82
82
 
83
+ /** Create a new location marker that spans both nodes */
84
+ function createSpanningLocation(start?: NodeLocation, end?: NodeLocation) {
85
+ if (!start || !end) {
86
+ return;
87
+ }
88
+
89
+ return {
90
+ start: start.start,
91
+ end: end.end,
92
+ };
93
+ }
94
+
83
95
  /** Get return the longest key length of any object */
84
96
  function getMaxKeyLen(obj: object): number {
85
97
  let maxLen = 0;
@@ -121,7 +133,8 @@ function binaryPrecedence(opVal: string): number {
121
133
  function createBinaryExpression(
122
134
  operator: string | boolean,
123
135
  left: string,
124
- right: string
136
+ right: string,
137
+ location?: NodeLocation
125
138
  ) {
126
139
  let type: ExpressionNodeType;
127
140
 
@@ -146,6 +159,7 @@ function createBinaryExpression(
146
159
  operator,
147
160
  left,
148
161
  right,
162
+ location,
149
163
  };
150
164
  }
151
165
 
@@ -190,6 +204,18 @@ export default function parseExpression(expr: string): ExpressionNode {
190
204
 
191
205
  let index = 0;
192
206
 
207
+ /** Create a location object */
208
+ const getLocation = (startChar: number) => {
209
+ return {
210
+ start: {
211
+ character: startChar,
212
+ },
213
+ end: {
214
+ character: index,
215
+ },
216
+ };
217
+ };
218
+
193
219
  /** Grab the char at the index from the expression */
194
220
  function exprI(i: number) {
195
221
  return charAtFunc.call(expr, i);
@@ -217,13 +243,14 @@ export default function parseExpression(expr: string): ExpressionNode {
217
243
  let key;
218
244
  let value;
219
245
  let chCode;
246
+ const startCharIndex = index;
247
+
220
248
  // get rid of OCURL_CODE
221
249
  ++index;
222
250
 
223
251
  while (index < length) {
224
252
  gobbleSpaces();
225
253
  chCode = exprICode(index);
226
-
227
254
  // check for end
228
255
  if (chCode === CCURL_CODE) {
229
256
  // if we are at the end but a key was defined
@@ -244,7 +271,6 @@ export default function parseExpression(expr: string): ExpressionNode {
244
271
  key = gobbleStringLiteral();
245
272
  // remove spaces
246
273
  gobbleSpaces();
247
-
248
274
  // remove colon
249
275
  if (exprICode(index) === COLON_CODE) {
250
276
  index++;
@@ -258,7 +284,6 @@ export default function parseExpression(expr: string): ExpressionNode {
258
284
  attributes.push({ key, value });
259
285
  gobbleSpaces();
260
286
  chCode = exprICode(index);
261
-
262
287
  if (chCode === COMMA_CODE) {
263
288
  index++;
264
289
  } else if (chCode !== CCURL_CODE) {
@@ -282,6 +307,7 @@ export default function parseExpression(expr: string): ExpressionNode {
282
307
  __id: ExpNodeOpaqueIdentifier,
283
308
  type: 'Object',
284
309
  attributes,
310
+ location: getLocation(startCharIndex),
285
311
  };
286
312
  }
287
313
 
@@ -290,7 +316,6 @@ export default function parseExpression(expr: string): ExpressionNode {
290
316
  */
291
317
  function gobbleSpaces() {
292
318
  let ch = exprICode(index);
293
-
294
319
  // Space or tab
295
320
  while (ch === 32 || ch === 9) {
296
321
  ch = exprICode(++index);
@@ -303,6 +328,7 @@ export default function parseExpression(expr: string): ExpressionNode {
303
328
  function gobbleExpression(): ExpressionNode {
304
329
  const test = gobbleBinaryExpression();
305
330
  gobbleSpaces();
331
+ const startCharIndex = index;
306
332
 
307
333
  if (index < length && exprICode(index) === QUMARK_CODE) {
308
334
  // Ternary expression: test ? consequent : alternate
@@ -329,6 +355,7 @@ export default function parseExpression(expr: string): ExpressionNode {
329
355
  test,
330
356
  consequent,
331
357
  alternate,
358
+ location: getLocation(startCharIndex),
332
359
  };
333
360
  }
334
361
 
@@ -353,7 +380,6 @@ export default function parseExpression(expr: string): ExpressionNode {
353
380
  while (tcLen > 0) {
354
381
  if (Object.prototype.hasOwnProperty.call(binaryOps, toCheck)) {
355
382
  index += tcLen;
356
-
357
383
  return toCheck;
358
384
  }
359
385
 
@@ -395,7 +421,6 @@ export default function parseExpression(expr: string): ExpressionNode {
395
421
 
396
422
  // Properly deal with precedence using [recursive descent](http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm)
397
423
  biop = gobbleBinaryOp();
398
-
399
424
  while (biop) {
400
425
  prec = binaryPrecedence(biop);
401
426
 
@@ -410,7 +435,12 @@ export default function parseExpression(expr: string): ExpressionNode {
410
435
  right = stack.pop();
411
436
  biop = stack.pop().value;
412
437
  left = stack.pop();
413
- node = createBinaryExpression(biop, left, right);
438
+ node = createBinaryExpression(
439
+ biop,
440
+ left,
441
+ right,
442
+ createSpanningLocation(left.location, right.location)
443
+ );
414
444
  stack.push(node);
415
445
  }
416
446
 
@@ -428,7 +458,12 @@ export default function parseExpression(expr: string): ExpressionNode {
428
458
  node = stack[i];
429
459
 
430
460
  while (i > 1) {
431
- node = createBinaryExpression(stack[i - 1].value, stack[i - 2], node);
461
+ node = createBinaryExpression(
462
+ stack[i - 1].value,
463
+ stack[i - 2],
464
+ node,
465
+ createSpanningLocation(stack[i - 2].location, node.location)
466
+ );
432
467
  i -= 2;
433
468
  }
434
469
 
@@ -442,6 +477,7 @@ export default function parseExpression(expr: string): ExpressionNode {
442
477
  function gobbleToken(): any {
443
478
  gobbleSpaces();
444
479
  const ch = exprICode(index);
480
+ const startCharIndex = index;
445
481
 
446
482
  if (isDecimalDigit(ch) || ch === PERIOD_CODE) {
447
483
  // Char code 46 is a dot `.` which can start off a numeric literal
@@ -478,13 +514,13 @@ export default function parseExpression(expr: string): ExpressionNode {
478
514
  while (tcLen > 0) {
479
515
  if (Object.prototype.hasOwnProperty.call(unaryOps, toCheck)) {
480
516
  index += tcLen;
481
-
482
517
  return {
483
518
  __id: ExpNodeOpaqueIdentifier,
484
519
  type: 'UnaryExpression',
485
520
  operator: toCheck,
486
521
  argument: gobbleToken(),
487
522
  prefix: true,
523
+ location: getLocation(startCharIndex),
488
524
  };
489
525
  }
490
526
 
@@ -500,6 +536,7 @@ export default function parseExpression(expr: string): ExpressionNode {
500
536
  */
501
537
  function gobbleNumericLiteral() {
502
538
  let num = '';
539
+ const startCharIndex = index;
503
540
 
504
541
  while (isDecimalDigit(exprICode(index))) {
505
542
  num += exprI(index++);
@@ -515,7 +552,6 @@ export default function parseExpression(expr: string): ExpressionNode {
515
552
  }
516
553
 
517
554
  let ch = exprI(index);
518
-
519
555
  if (ch === 'e' || ch === 'E') {
520
556
  // Exponent marker
521
557
  num += exprI(index++);
@@ -537,7 +573,6 @@ export default function parseExpression(expr: string): ExpressionNode {
537
573
  }
538
574
 
539
575
  const chCode = exprICode(index);
540
-
541
576
  // Check to make sure this isn't a variable name that start with a number (123abc)
542
577
  if (isIdentifierStart(chCode)) {
543
578
  throwError(
@@ -553,6 +588,7 @@ export default function parseExpression(expr: string): ExpressionNode {
553
588
  type: 'Literal',
554
589
  value: parseFloat(num),
555
590
  raw: num,
591
+ location: getLocation(startCharIndex),
556
592
  };
557
593
  }
558
594
 
@@ -564,6 +600,7 @@ export default function parseExpression(expr: string): ExpressionNode {
564
600
  const quote = exprI(index++);
565
601
  let str = '';
566
602
  let closed = false;
603
+ const startCharIndex = index;
567
604
 
568
605
  while (index < length) {
569
606
  let ch = exprI(index++);
@@ -613,6 +650,7 @@ export default function parseExpression(expr: string): ExpressionNode {
613
650
  type: 'Literal',
614
651
  value: str,
615
652
  raw: `${quote}${str}${quote}`,
653
+ location: getLocation(startCharIndex),
616
654
  };
617
655
  }
618
656
 
@@ -624,9 +662,9 @@ export default function parseExpression(expr: string): ExpressionNode {
624
662
  let str = '';
625
663
  let closed = false;
626
664
  let openBraceCount = 1;
665
+ const startCharIndex = index;
627
666
 
628
667
  index += 2; // Skip the {{
629
-
630
668
  while (index < length) {
631
669
  const ch = exprI(index++);
632
670
 
@@ -657,6 +695,7 @@ export default function parseExpression(expr: string): ExpressionNode {
657
695
  __id: ExpNodeOpaqueIdentifier,
658
696
  type: 'ModelRef',
659
697
  ref: str,
698
+ location: getLocation(startCharIndex),
660
699
  };
661
700
  }
662
701
 
@@ -678,7 +717,6 @@ export default function parseExpression(expr: string): ExpressionNode {
678
717
 
679
718
  while (index < length) {
680
719
  ch = exprICode(index);
681
-
682
720
  if (isIdentifierPart(ch)) {
683
721
  index++;
684
722
  } else {
@@ -694,6 +732,7 @@ export default function parseExpression(expr: string): ExpressionNode {
694
732
  type: 'Literal',
695
733
  value: (literals as any)[identifier],
696
734
  raw: identifier,
735
+ location: getLocation(start),
697
736
  };
698
737
  }
699
738
 
@@ -701,6 +740,7 @@ export default function parseExpression(expr: string): ExpressionNode {
701
740
  return {
702
741
  __id: ExpNodeOpaqueIdentifier,
703
742
  type: 'ThisExpression',
743
+ location: getLocation(start),
704
744
  };
705
745
  }
706
746
 
@@ -708,6 +748,7 @@ export default function parseExpression(expr: string): ExpressionNode {
708
748
  __id: ExpNodeOpaqueIdentifier,
709
749
  type: 'Identifier',
710
750
  name: identifier,
751
+ location: getLocation(start),
711
752
  };
712
753
  }
713
754
 
@@ -761,7 +802,7 @@ export default function parseExpression(expr: string): ExpressionNode {
761
802
  let charIndex = exprICode(index);
762
803
  let node: any =
763
804
  charIndex === OPAREN_CODE ? gobbleGroup() : gobbleIdentifier();
764
-
805
+ const startCharIndex = index;
765
806
  gobbleSpaces();
766
807
  charIndex = exprICode(index);
767
808
 
@@ -781,6 +822,7 @@ export default function parseExpression(expr: string): ExpressionNode {
781
822
  computed: false,
782
823
  object: node,
783
824
  property: gobbleIdentifier(),
825
+ location: getLocation(startCharIndex),
784
826
  };
785
827
  } else if (charIndex === OBRACK_CODE) {
786
828
  node = {
@@ -789,6 +831,7 @@ export default function parseExpression(expr: string): ExpressionNode {
789
831
  computed: true,
790
832
  object: node,
791
833
  property: gobbleExpression(),
834
+ location: getLocation(startCharIndex),
792
835
  };
793
836
 
794
837
  gobbleSpaces();
@@ -806,6 +849,7 @@ export default function parseExpression(expr: string): ExpressionNode {
806
849
  type: 'CallExpression',
807
850
  args: gobbleArguments(CPAREN_CODE),
808
851
  callTarget: node,
852
+ location: getLocation(startCharIndex),
809
853
  };
810
854
  }
811
855
 
@@ -817,7 +861,7 @@ export default function parseExpression(expr: string): ExpressionNode {
817
861
  }
818
862
 
819
863
  /**
820
- * Responsible for parsing a group within parentheses `()`
864
+ * Responsible for parsing a group of things within parentheses `()`
821
865
  * This function assumes that it needs to gobble the opening parenthesis
822
866
  * and then tries to gobble everything within that parenthesis, assuming
823
867
  * that the next thing it should see is the close parenthesis. If not,
@@ -830,7 +874,6 @@ export default function parseExpression(expr: string): ExpressionNode {
830
874
 
831
875
  if (exprICode(index) === CPAREN_CODE) {
832
876
  index++;
833
-
834
877
  return node;
835
878
  }
836
879
 
@@ -843,12 +886,14 @@ export default function parseExpression(expr: string): ExpressionNode {
843
886
  * and then tries to gobble the expressions as arguments.
844
887
  */
845
888
  function gobbleArray() {
889
+ const startCharIndex = index;
846
890
  index++;
847
891
 
848
892
  return {
849
893
  __id: ExpNodeOpaqueIdentifier,
850
894
  type: 'ArrayExpression',
851
895
  elements: gobbleArguments(CBRACK_CODE),
896
+ location: getLocation(startCharIndex),
852
897
  };
853
898
  }
854
899
 
@@ -885,5 +930,6 @@ export default function parseExpression(expr: string): ExpressionNode {
885
930
  __id: ExpNodeOpaqueIdentifier,
886
931
  type: 'Compound',
887
932
  body: nodes,
933
+ location: getLocation(0),
888
934
  };
889
935
  }
@@ -56,12 +56,31 @@ export function isExpressionNode(x: any): x is ExpressionNode {
56
56
  return typeof x === 'object' && x.__id === ExpNodeOpaqueIdentifier;
57
57
  }
58
58
 
59
+ export interface NodePosition {
60
+ /** The character location */
61
+ character: number;
62
+ }
63
+
64
+ export interface NodeLocation {
65
+ // We only care about the character offset, not the line/column for now
66
+ // But making these objects allows us to add more (like line number) later
67
+
68
+ /** The start of the node */
69
+ start: NodePosition;
70
+
71
+ /** The end of the node */
72
+ end: NodePosition;
73
+ }
74
+
59
75
  export interface BaseNode<T> {
60
76
  /** The thing to discriminate the AST type on */
61
77
  type: T;
62
78
 
63
79
  /** How to tell this apart from other objects */
64
80
  __id: typeof ExpNodeOpaqueIdentifier;
81
+
82
+ /** The location of the node in the source expression string */
83
+ location?: NodeLocation;
65
84
  }
66
85
 
67
86
  /** A helper interface for nodes that container left and right children */
@@ -88,13 +107,9 @@ export interface BinaryNode
88
107
  operator: string;
89
108
  }
90
109
 
91
- export interface LogicalNode extends BaseNode<'LogicalExpression'> {
92
- /** The left hand side of the equation */
93
- left: any;
94
-
95
- /** The right hand side of the equation */
96
- right: any;
97
-
110
+ export interface LogicalNode
111
+ extends BaseNode<'LogicalExpression'>,
112
+ DirectionalNode {
98
113
  /** The logical operation to perform on the nodes */
99
114
  operator: string;
100
115
  }
@@ -104,7 +119,7 @@ export interface UnaryNode extends BaseNode<'UnaryExpression'> {
104
119
  operator: string;
105
120
 
106
121
  /** The single argument that the operation should be performed on */
107
- argument: any;
122
+ argument: ExpressionNode;
108
123
  }
109
124
 
110
125
  export type ThisNode = BaseNode<'ThisExpression'>;
@@ -118,10 +133,10 @@ export interface ObjectNode extends BaseNode<'Object'> {
118
133
  /** */
119
134
  attributes: Array<{
120
135
  /** The property name of the object */
121
- key: any;
136
+ key: ExpressionNode;
122
137
 
123
138
  /** the associated value */
124
- value: any;
139
+ value: ExpressionNode;
125
140
  }>;
126
141
  }
127
142
 
@@ -155,7 +170,7 @@ export interface CompoundNode extends BaseNode<'Compound'> {
155
170
 
156
171
  export interface CallExpressionNode extends BaseNode<'CallExpression'> {
157
172
  /** The arguments to the function */
158
- args: any[];
173
+ args: ExpressionNode[];
159
174
 
160
175
  /** The function name */
161
176
  callTarget: IdentifierNode;
@@ -163,7 +178,7 @@ export interface CallExpressionNode extends BaseNode<'CallExpression'> {
163
178
 
164
179
  export interface ArrayExpressionNode extends BaseNode<'ArrayExpression'> {
165
180
  /** The items in an array */
166
- elements: any[];
181
+ elements: ExpressionNode[];
167
182
  }
168
183
 
169
184
  export interface IdentifierNode extends BaseNode<'Identifier'> {
@@ -1,4 +1,9 @@
1
- import type { ExpressionHandler } from './types';
1
+ import type {
2
+ ExpressionHandler,
3
+ ExpressionNode,
4
+ NodeLocation,
5
+ NodePosition,
6
+ } from './types';
2
7
 
3
8
  /** Generates a function by removing the first context argument */
4
9
  export function withoutContext<T extends unknown[], Return>(
@@ -6,3 +11,121 @@ export function withoutContext<T extends unknown[], Return>(
6
11
  ): ExpressionHandler<T, Return> {
7
12
  return (_context, ...args) => fn(...args);
8
13
  }
14
+
15
+ /** Checks if the location includes the target position */
16
+ function isInRange(position: NodePosition, location: NodeLocation) {
17
+ return (
18
+ position.character >= location.start.character &&
19
+ position.character <= location.end.character
20
+ );
21
+ }
22
+
23
+ /** Get the node in the expression that's closest to the desired position */
24
+ export function findClosestNodeAtPosition(
25
+ node: ExpressionNode,
26
+ position: NodePosition
27
+ ): ExpressionNode | undefined {
28
+ // This is just mapping recursively over nodes in the tree
29
+
30
+ // eslint-disable-next-line default-case
31
+ switch (node.type) {
32
+ case 'Modification':
33
+ case 'Assignment':
34
+ case 'LogicalExpression':
35
+ case 'BinaryExpression': {
36
+ const check =
37
+ findClosestNodeAtPosition(node.left, position) ??
38
+ findClosestNodeAtPosition(node.right, position);
39
+ if (check) {
40
+ return check;
41
+ }
42
+
43
+ break;
44
+ }
45
+
46
+ case 'UnaryExpression': {
47
+ const checkArg = findClosestNodeAtPosition(node.argument, position);
48
+ if (checkArg) {
49
+ return checkArg;
50
+ }
51
+
52
+ break;
53
+ }
54
+
55
+ case 'MemberExpression': {
56
+ const checkObject =
57
+ findClosestNodeAtPosition(node.object, position) ??
58
+ findClosestNodeAtPosition(node.property, position);
59
+ if (checkObject) {
60
+ return checkObject;
61
+ }
62
+
63
+ break;
64
+ }
65
+
66
+ case 'ConditionalExpression': {
67
+ const checkObject =
68
+ findClosestNodeAtPosition(node.test, position) ??
69
+ findClosestNodeAtPosition(node.consequent, position) ??
70
+ findClosestNodeAtPosition(node.alternate, position);
71
+ if (checkObject) {
72
+ return checkObject;
73
+ }
74
+
75
+ break;
76
+ }
77
+
78
+ case 'ArrayExpression':
79
+ case 'Compound': {
80
+ const elements =
81
+ node.type === 'ArrayExpression' ? node.elements : node.body;
82
+
83
+ const anyElements = elements.find((e) =>
84
+ findClosestNodeAtPosition(e, position)
85
+ );
86
+
87
+ if (anyElements) {
88
+ return anyElements;
89
+ }
90
+
91
+ break;
92
+ }
93
+
94
+ case 'Object': {
95
+ const checkObject = node.attributes.reduce<ExpressionNode | undefined>(
96
+ (found, next) => {
97
+ return (
98
+ found ??
99
+ findClosestNodeAtPosition(next.key, position) ??
100
+ findClosestNodeAtPosition(next.value, position)
101
+ );
102
+ },
103
+ undefined
104
+ );
105
+
106
+ if (checkObject) {
107
+ return checkObject;
108
+ }
109
+
110
+ break;
111
+ }
112
+
113
+ case 'CallExpression': {
114
+ const anyArgs =
115
+ node.args.find((arg) => {
116
+ return findClosestNodeAtPosition(arg, position);
117
+ }) ?? findClosestNodeAtPosition(node.callTarget, position);
118
+
119
+ if (anyArgs) {
120
+ return anyArgs;
121
+ }
122
+
123
+ break;
124
+ }
125
+ }
126
+
127
+ // Lastly check for yourself
128
+ if (node.location && isInRange(position, node.location)) {
129
+ return node;
130
+ }
131
+ }