• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ota-meshi / eslint-plugin-jsonc / 28832621178

07 Jul 2026 12:24AM UTC coverage: 70.883% (+0.005%) from 70.878%
28832621178

push

github

web-flow
chore: release eslint-plugin-jsonc (#522)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

1064 of 1307 branches covered (81.41%)

Branch coverage included in aggregate %.

7510 of 10789 relevant lines covered (69.61%)

93.48 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.53
/lib/rules/sort-array-values.ts
1
import naturalCompare from "natural-compare";
✔
2
import { createRule } from "../utils/index.ts";
×
3
import type { AST } from "jsonc-eslint-parser";
×
4
import { getStaticJSONValue } from "jsonc-eslint-parser";
×
5
import type { AroundTarget } from "../utils/fix-sort-elements.ts";
×
6
import {
×
7
  fixToMoveDownForSorting,
×
8
  fixToMoveUpForSorting,
×
9
} from "../utils/fix-sort-elements.ts";
1✔
10
import { calcShortestEditScript } from "../utils/calc-shortest-edit-script.ts";
1✔
11
import type { JSONCSourceCode } from "../language/jsonc-source-code.ts";
1✔
12

1✔
13
type JSONValue = ReturnType<typeof getStaticJSONValue>;
1✔
14

1✔
15
//------------------------------------------------------------------------------
1✔
16
// Helpers
1✔
17
//------------------------------------------------------------------------------
1✔
18

1✔
19
type UserOptions = PatternOption[];
1✔
20
type OrderTypeOption = "asc" | "desc";
1✔
21
type PatternOption = {
1✔
22
  pathPattern: string;
1✔
23
  order:
1✔
24
    | OrderObject
1✔
25
    | (
1✔
26
        | string
1✔
27
        | {
1✔
28
            valuePattern?: string;
1✔
29
            order?: OrderObject;
1✔
30
          }
1✔
31
      )[];
1✔
32
  minValues?: number;
1✔
33
};
1✔
34
type OrderObject = {
1✔
35
  type?: OrderTypeOption;
1✔
36
  caseSensitive?: boolean;
1✔
37
  natural?: boolean;
1✔
38
  key?: string;
1✔
39
};
1✔
40
type ParsedOption = {
1✔
41
  isTargetArray: (node: JSONArrayData) => boolean;
1✔
42
  ignore: (data: JSONElementData) => boolean;
1✔
43
  isValidOrder: Validator;
1✔
44
  orderText: (data: JSONElementData) => string;
1✔
45
  key?: string;
1✔
46
};
1✔
47
type Validator = (a: JSONElementData, b: JSONElementData) => boolean;
1✔
48

1✔
49
type JSONElement = AST.JSONArrayExpression["elements"][number];
1✔
50
class JSONElementData {
1✔
51
  public readonly array: JSONArrayData;
409✔
52

409✔
53
  public readonly node: JSONElement;
409✔
54

409✔
55
  public readonly index: number;
409✔
56

409✔
57
  private cached: { value: JSONValue } | null = null;
409✔
58

409✔
59
  private cachedKeyValues: Map<string, JSONValue | undefined> | null = null;
409✔
60

409✔
61
  private cachedAround: AroundTarget | null = null;
409✔
62

409✔
63
  public get reportLoc() {
409✔
64
    if (this.node) {
77✔
65
      return this.node.loc;
77✔
66
    }
77✔
67
    const around = this.around;
77!
68
    return {
×
69
      start: around.before.loc.end,
×
70
      end: around.after.loc.start,
×
71
    };
×
72
  }
×
73

×
74
  public get around(): AroundTarget {
✔
75
    if (this.cachedAround) {
154✔
76
      return this.cachedAround;
16✔
77
    }
16✔
78
    const sourceCode = this.array.sourceCode;
154✔
79
    if (this.node) {
138✔
80
      return (this.cachedAround = {
138✔
81
        node: this.node,
138✔
82
        before: sourceCode.getTokenBefore(this.node)!,
138✔
83
        after: sourceCode.getTokenAfter(this.node)!,
138✔
84
      });
×
85
    }
×
86
    const before =
×
87
      this.index > 0
×
88
        ? this.array.elements[this.index - 1].around.after
×
89
        : sourceCode.getFirstToken(this.array.node);
×
90
    const after = sourceCode.getTokenAfter(before)!;
×
91
    return (this.cachedAround = { before, after });
×
92
  }
×
93

×
94
  public constructor(array: JSONArrayData, node: JSONElement, index: number) {
✔
95
    this.array = array;
408✔
96
    this.node = node;
408✔
97
    this.index = index;
408✔
98
  }
408✔
99

×
100
  public get value() {
✔
101
    return (
2,671✔
102
      this.cached ??
2,671✔
103
      (this.cached = {
378✔
104
        value: this.node == null ? null : getStaticJSONValue(this.node),
378✔
105
      })
378✔
106
    ).value;
2,671✔
107
  }
2,671✔
108

×
109
  /**
×
110
   * Get the value of the given property key when this element is an object.
×
111
   * Only the matched property's value is evaluated (and memoized per key),
×
112
   * avoiding a full deep conversion of the whole element. Returns `undefined`
×
113
   * for non-object elements or when the key is absent.
×
114
   */
×
115
  public getValueForKey(key: string): JSONValue | undefined {
✔
116
    const cache = (this.cachedKeyValues ??= new Map());
156✔
117
    if (cache.has(key)) {
156✔
118
      return cache.get(key);
112✔
119
    }
112✔
120
    let result: JSONValue | undefined;
156✔
121
    const node = this.node;
44✔
122
    if (node && node.type === "JSONObjectExpression") {
156✔
123
      for (const prop of node.properties) {
40✔
124
        if (getPropertyName(prop) === key) {
40✔
125
          result = getStaticJSONValue(prop.value);
38✔
126
          break;
×
127
        }
×
128
      }
×
129
    }
×
130
    cache.set(key, result);
✔
131
    return result;
×
132
  }
×
133
}
×
134
class JSONArrayData {
✔
135
  public readonly node: AST.JSONArrayExpression;
111✔
136

111✔
137
  public readonly sourceCode: JSONCSourceCode;
111✔
138

111✔
139
  private cachedElements: JSONElementData[] | null = null;
111✔
140

111✔
141
  public constructor(
111✔
142
    node: AST.JSONArrayExpression,
110✔
143
    sourceCode: JSONCSourceCode,
110✔
144
  ) {
110✔
145
    this.node = node;
110✔
146
    this.sourceCode = sourceCode;
110✔
147
  }
110✔
148

111✔
149
  public get elements() {
111✔
150
    return (this.cachedElements ??= this.node.elements.map(
104✔
151
      (e, index) => new JSONElementData(this, e, index),
104✔
152
    ));
104✔
153
  }
104✔
154
}
111✔
155

1✔
156
/**
1✔
157
 * Build function which check that the given 2 names are in specific order.
1✔
158
 */
1✔
159
function buildValidatorFromType(
92✔
160
  order: OrderTypeOption,
92✔
161
  insensitive: boolean,
92✔
162
  natural: boolean,
92✔
163
  key?: string,
92✔
164
): Validator {
92✔
165
  type Compare<T> = ([a, b]: T[]) => boolean;
92✔
166

92✔
167
  let compareValue: Compare<any> = ([a, b]) => a <= b;
92✔
168
  let compareText: Compare<string> = compareValue;
92✔
169

92✔
170
  if (natural) {
92✔
171
    compareText = ([a, b]) => naturalCompare(a, b) <= 0;
4✔
172
  }
4✔
173
  if (insensitive) {
92✔
174
    const baseCompareText = compareText;
×
175
    compareText = ([a, b]: string[]) =>
✔
176
      baseCompareText([a.toLowerCase(), b.toLowerCase()]);
24✔
177
  }
×
178
  if (order === "desc") {
✔
179
    const baseCompareText = compareText;
×
180
    compareText = (args: string[]) => baseCompareText(args.reverse());
✔
181
    const baseCompareValue = compareValue;
×
182
    compareValue = (args) => baseCompareValue(args.reverse());
✔
183
  }
×
184

×
185
  /**
×
186
   * Compare two resolved values (an element value, or a value extracted by key).
×
187
   */
×
188
  function compare(aVal: JSONValue, bVal: JSONValue): boolean {
✔
189
    if (typeof aVal === "string" && typeof bVal === "string") {
359✔
190
      return compareText([aVal, bVal]);
316✔
191
    }
316✔
192
    const type = getJSONPrimitiveType(aVal);
359✔
193
    if (type && type === getJSONPrimitiveType(bVal)) {
359✔
194
      return compareValue([aVal, bVal]);
18✔
195
    }
18✔
196
    // Unknown
359✔
197
    return true;
359✔
198
  }
359✔
199

×
200
  return (a: JSONElementData, b: JSONElementData) => {
✔
201
    if (key) {
359✔
202
      const aVal = a.getValueForKey(key);
36✔
203
      const bVal = b.getValueForKey(key);
36✔
204
      // Elements missing the key are left in place — they are excluded from
×
205
      // ordering by the caller.
×
206
      if (aVal === undefined || bVal === undefined) return true;
×
207
      return compare(aVal, bVal);
×
208
    }
×
209

×
210
    return compare(a.value, b.value);
✔
211
  };
×
212
}
×
213

×
214
/**
×
215
 * Parse options
×
216
 */
×
217
function parseOptions(options: UserOptions): ParsedOption[] {
106✔
218
  return options.map((opt) => {
106✔
219
    const order = opt.order;
106✔
220
    const pathPattern = new RegExp(opt.pathPattern);
106✔
221
    const minValues: number = opt.minValues ?? 2;
106✔
222
    if (!Array.isArray(order)) {
106✔
223
      const type: OrderTypeOption = order.type ?? "asc";
64!
224
      const insensitive = order.caseSensitive === false;
64✔
225
      const natural = Boolean(order.natural);
64✔
226
      const key = order.key;
64✔
227

×
228
      return {
×
229
        isTargetArray,
×
230
        // When sorting by a key, elements missing that key are skipped —
×
231
        // excluded from the sort and left in place.
×
232
        ignore: key ? (v) => v.getValueForKey(key) === undefined : () => false,
✔
233
        isValidOrder: buildValidatorFromType(type, insensitive, natural, key),
×
234
        orderText(data) {
✔
235
          const base =
41✔
236
            typeof data.value === "string" || key
41✔
237
              ? `${natural ? "natural " : ""}${
36✔
238
                  insensitive ? "insensitive " : ""
36✔
239
                }${type}ending`
36✔
240
              : `${type}ending`;
5✔
241
          return key ? `${base} by '${key}'` : base;
✔
242
        },
×
243
        key,
×
244
      };
×
245
    }
×
246
    const parsedOrder: {
✔
247
      test: (v: JSONElementData) => boolean;
×
248
      isValidNestOrder: Validator;
×
249
    }[] = [];
×
250
    for (const o of order) {
✔
251
      if (typeof o === "string") {
✔
252
        parsedOrder.push({
×
253
          test: (v) => v.value === o,
✔
254
          isValidNestOrder: () => true,
×
255
        });
×
256
      } else {
✔
257
        const valuePattern = o.valuePattern ? new RegExp(o.valuePattern) : null;
28!
258
        const nestOrder = o.order ?? {};
28!
259
        const type: OrderTypeOption = nestOrder.type ?? "asc";
28!
260
        const insensitive = nestOrder.caseSensitive === false;
×
261
        const natural = Boolean(nestOrder.natural);
×
262
        const itemKey = nestOrder.key;
×
263
        parsedOrder.push({
×
264
          test: (v) => {
✔
265
            if (itemKey) {
294✔
266
              const keyVal = v.getValueForKey(itemKey);
48✔
267
              return valuePattern
48!
268
                ? keyVal !== undefined && valuePattern.test(String(keyVal))
×
269
                : keyVal !== undefined;
48✔
270
            }
48✔
271
            return valuePattern
294!
272
              ? Boolean(getJSONPrimitiveType(v.value)) &&
×
273
                  valuePattern.test(String(v.value))
✔
274
              : true;
246✔
275
          },
294✔
276
          isValidNestOrder: buildValidatorFromType(
×
277
            type,
×
278
            insensitive,
×
279
            natural,
×
280
            itemKey,
×
281
          ),
×
282
        });
×
283
      }
×
284
    }
×
285

×
286
    return {
✔
287
      isTargetArray,
×
288
      ignore: (v) => parsedOrder.every((p) => !p.test(v)),
✔
289
      isValidOrder(a, b) {
✔
290
        for (const p of parsedOrder) {
304✔
291
          const matchA = p.test(a);
750✔
292
          const matchB = p.test(b);
750✔
293
          if (!matchA || !matchB) {
750✔
294
            if (matchA) {
646✔
295
              return true;
170✔
296
            }
170✔
297
            if (matchB) {
646✔
298
              return false;
30✔
299
            }
30✔
300
            continue;
646✔
301
          }
446✔
302
          return p.isValidNestOrder(a, b);
750✔
303
        }
104✔
304
        return false;
304!
305
      },
304✔
306
      orderText: () => "specified",
✔
307
    };
×
308

×
309
    /**
×
310
     * Checks whether given node data is verify target
×
311
     */
×
312
    function isTargetArray(data: JSONArrayData) {
×
313
      if (data.node.elements.length < minValues) {
110✔
314
        return false;
4✔
315
      }
4✔
316

110✔
317
      // Check whether the path is match or not.
110✔
318
      let path = "";
110✔
319
      let curr: AST.JSONNode = data.node;
106✔
320
      let p: AST.JSONNode | null = curr.parent;
106✔
321
      while (p) {
110✔
322
        if (p.type === "JSONProperty") {
400✔
323
          const name = getPropertyName(p);
94✔
324
          if (/^[$a-z_][\w$]*$/iu.test(name)) {
94✔
325
            path = `.${name}${path}`;
92✔
326
          } else {
94✔
327
            path = `[${JSON.stringify(name)}]${path}`;
2✔
328
          }
2✔
329
        } else if (p.type === "JSONArrayExpression") {
400!
330
          const index = (p.elements as AST.JSONNode[]).indexOf(curr);
×
331
          path = `[${index}]${path}`;
×
332
        }
×
333
        curr = p;
400✔
334
        p = curr.parent;
400✔
335
      }
400✔
336
      if (path.startsWith(".")) {
110✔
337
        path = path.slice(1);
92✔
338
      }
92✔
339
      return pathPattern.test(path);
110✔
340
    }
110✔
341
  });
×
342
}
×
343

×
344
/**
×
345
 * Gets the property name of the given `Property` node.
×
346
 */
×
347
function getPropertyName(node: AST.JSONProperty): string {
134✔
348
  const prop = node.key;
134✔
349
  if (prop.type === "JSONIdentifier") {
134!
350
    return prop.name;
×
351
  }
×
352
  return String(getStaticJSONValue(prop));
134✔
353
}
134✔
354

×
355
/**
×
356
 * Get the type name from given value when value is primitive like value
×
357
 */
×
358
function getJSONPrimitiveType(val: JSONValue) {
224✔
359
  const t = typeof val;
224✔
360
  if (t === "string" || t === "number" || t === "boolean" || t === "bigint") {
224✔
361
    return t;
201✔
362
  }
201✔
363
  if (val === null) {
224✔
364
    return "null";
15✔
365
  }
15✔
366
  if (val === undefined) {
224!
367
    return "undefined";
×
368
  }
×
369
  if (val instanceof RegExp) {
224!
370
    return "regexp";
×
371
  }
×
372
  return null;
224✔
373
}
224✔
374

1✔
375
const ALLOW_ORDER_TYPES: OrderTypeOption[] = ["asc", "desc"];
1✔
376
const ORDER_OBJECT_SCHEMA = {
1✔
377
  type: "object",
1✔
378
  properties: {
1✔
379
    type: {
1✔
380
      enum: ALLOW_ORDER_TYPES,
1✔
381
    },
1✔
382
    caseSensitive: {
1✔
383
      type: "boolean",
1✔
384
    },
1✔
385
    natural: {
1✔
386
      type: "boolean",
1✔
387
    },
1✔
388
    key: {
1✔
389
      type: "string",
1✔
390
    },
1✔
391
  },
1✔
392
  additionalProperties: false,
1✔
393
} as const;
1✔
394

1✔
395
//------------------------------------------------------------------------------
1✔
396
// Rule Definition
1✔
397
//------------------------------------------------------------------------------
1✔
398

1✔
399
export default createRule<UserOptions>("sort-array-values", {
1✔
400
  meta: {
1✔
401
    docs: {
1✔
402
      description: "require array values to be sorted",
1✔
403
      recommended: null,
1✔
404
      extensionRule: false,
1✔
405
      layout: false,
1✔
406
    },
1✔
407
    fixable: "code",
1✔
408
    schema: {
1✔
409
      type: "array",
1✔
410
      items: {
1✔
411
        type: "object",
1✔
412
        properties: {
1✔
413
          pathPattern: { type: "string" },
1✔
414
          order: {
1✔
415
            oneOf: [
1✔
416
              {
1✔
417
                type: "array",
1✔
418
                items: {
1✔
419
                  anyOf: [
1✔
420
                    { type: "string" },
1✔
421
                    {
1✔
422
                      type: "object",
1✔
423
                      properties: {
1✔
424
                        valuePattern: {
1✔
425
                          type: "string",
1✔
426
                        },
1✔
427
                        order: ORDER_OBJECT_SCHEMA,
1✔
428
                      },
1✔
429
                      additionalProperties: false,
1✔
430
                    },
1✔
431
                  ],
1✔
432
                },
1✔
433
                uniqueItems: true,
1✔
434
              },
1✔
435
              ORDER_OBJECT_SCHEMA,
1✔
436
            ],
1✔
437
          },
1✔
438
          minValues: {
1✔
439
            type: "integer",
1✔
440
            minimum: 2,
1✔
441
          },
1✔
442
        },
1✔
443
        required: ["pathPattern", "order"],
1✔
444
        additionalProperties: false,
1✔
445
      },
1✔
446
      minItems: 1,
1✔
447
    },
1✔
448

1✔
449
    messages: {
1✔
450
      shouldBeBefore:
1✔
451
        "Expected array values to be in {{orderText}} order. '{{thisValue}}' should be before '{{targetValue}}'.",
1✔
452
      shouldBeAfter:
1✔
453
        "Expected array values to be in {{orderText}} order. '{{thisValue}}' should be after '{{targetValue}}'.",
1✔
454
    },
1✔
455
    type: "suggestion",
1✔
456
  },
1✔
457
  create(context) {
1✔
458
    const sourceCode = context.sourceCode;
106✔
459
    if (!sourceCode.parserServices.isJSON) {
106!
460
      return {};
×
461
    }
×
462
    // Parse options.
106✔
463
    const parsedOptions = parseOptions(context.options);
106✔
464

106✔
465
    /**
106✔
466
     * Sort elements by bubble sort.
106✔
467
     */
106✔
468
    function bubbleSort(elements: JSONElementData[], option: ParsedOption) {
106✔
469
      const l = elements.length;
104✔
470
      const result = [...elements];
104✔
471
      let swapped: boolean;
104✔
472
      do {
104✔
473
        swapped = false;
191✔
474
        for (let nextIndex = 1; nextIndex < l; nextIndex++) {
191✔
475
          const prevIndex = nextIndex - 1;
559✔
476
          if (option.isValidOrder(result[prevIndex], result[nextIndex]))
559✔
477
            continue;
559✔
478
          [result[prevIndex], result[nextIndex]] = [
559✔
479
            result[nextIndex],
117✔
480
            result[prevIndex],
117✔
481
          ];
117✔
482
          swapped = true;
117✔
483
        }
117✔
484
      } while (swapped);
104✔
485
      return result;
104✔
486
    }
104✔
487

106✔
488
    /**
106✔
489
     * Verify for array elements
106✔
490
     */
106✔
491
    function verifyArrayElements(
106✔
492
      elements: JSONElementData[],
104✔
493
      option: ParsedOption,
104✔
494
    ) {
104✔
495
      const sorted = bubbleSort(elements, option);
104✔
496
      const editScript = calcShortestEditScript(elements, sorted);
104✔
497
      for (let index = 0; index < editScript.length; index++) {
104✔
498
        const edit = editScript[index];
465✔
499
        if (edit.type !== "delete") continue;
465✔
500
        const insertEditIndex = editScript.findIndex(
465✔
501
          (e) => e.type === "insert" && e.b === edit.a,
77✔
502
        );
77✔
503
        if (insertEditIndex === -1) {
465!
504
          // should not happen
×
505
          continue;
×
506
        }
×
507
        if (index < insertEditIndex) {
465✔
508
          const target = findInsertAfterTarget(edit.a, insertEditIndex);
59✔
509
          if (!target) {
59!
510
            // should not happen
×
511
            continue;
×
512
          }
×
513
          context.report({
59✔
514
            loc: edit.a.reportLoc,
59✔
515
            messageId: "shouldBeAfter",
59✔
516
            data: {
59✔
517
              thisValue: toText(edit.a, option.key),
59✔
518
              targetValue: toText(target, option.key),
59✔
519
              orderText: option.orderText(edit.a),
59✔
520
            },
59✔
521
            *fix(fixer) {
59✔
522
              yield* fixToMoveDownForSorting(
59✔
523
                fixer,
59✔
524
                sourceCode,
59✔
525
                edit.a.around,
59✔
526
                target.around,
59✔
527
              );
59✔
528
            },
59✔
529
          });
59✔
530
        } else {
465✔
531
          const target = findInsertBeforeTarget(edit.a, insertEditIndex);
18✔
532
          if (!target) {
18!
533
            // should not happen
×
534
            continue;
×
535
          }
×
536
          context.report({
18✔
537
            loc: edit.a.reportLoc,
18✔
538
            messageId: "shouldBeBefore",
18✔
539
            data: {
18✔
540
              thisValue: toText(edit.a, option.key),
18✔
541
              targetValue: toText(target, option.key),
18✔
542
              orderText: option.orderText(edit.a),
18✔
543
            },
18✔
544
            *fix(fixer) {
18✔
545
              yield* fixToMoveUpForSorting(
18✔
546
                fixer,
18✔
547
                sourceCode,
18✔
548
                edit.a.around,
18✔
549
                target.around,
18✔
550
              );
18✔
551
            },
18✔
552
          });
18✔
553
        }
18✔
554
      }
465✔
555

465✔
556
      /**
465✔
557
       * Find insert after target
465✔
558
       */
465✔
559
      function findInsertAfterTarget(
465✔
560
        element: JSONElementData,
59✔
561
        insertEditIndex: number,
59✔
562
      ) {
59✔
563
        for (let index = insertEditIndex - 1; index >= 0; index--) {
59✔
564
          const edit = editScript[index];
75✔
565
          if (edit.type === "delete" && edit.a === element) break;
75!
566
          if (edit.type !== "common") continue;
75✔
567
          return edit.a;
75✔
568
        }
59✔
569

59✔
570
        let lastTarget: JSONElementData | null = null;
59!
571
        for (
×
572
          let index = elements.indexOf(element) + 1;
×
573
          index < elements.length;
×
574
          index++
×
575
        ) {
×
576
          const el = elements[index];
×
577
          if (option.isValidOrder(el, element)) {
×
578
            lastTarget = el;
×
579
            continue;
×
580
          }
×
581
          return lastTarget;
×
582
        }
×
583
        return lastTarget;
×
584
      }
59✔
585

465✔
586
      /**
465✔
587
       * Find insert before target
465✔
588
       */
465✔
589
      function findInsertBeforeTarget(
465✔
590
        element: JSONElementData,
18✔
591
        insertEditIndex: number,
18✔
592
      ) {
18✔
593
        for (
18✔
594
          let index = insertEditIndex + 1;
18✔
595
          index < editScript.length;
18✔
596
          index++
18✔
597
        ) {
18✔
598
          const edit = editScript[index];
18✔
599
          if (edit.type === "delete" && edit.a === element) break;
18!
600
          if (edit.type !== "common") continue;
18!
601
          return edit.a;
18✔
602
        }
18✔
603

18✔
604
        let lastTarget: JSONElementData | null = null;
18!
605
        for (let index = elements.indexOf(element) - 1; index >= 0; index--) {
×
606
          const el = elements[index];
×
607
          if (option.isValidOrder(element, el)) {
×
608
            lastTarget = el;
×
609
            continue;
×
610
          }
×
611
          return lastTarget;
×
612
        }
×
613
        return lastTarget;
×
614
      }
18✔
615
    }
104✔
616

106✔
617
    /**
106✔
618
     * Convert to display text.
106✔
619
     */
106✔
620
    function toText(data: JSONElementData, key?: string) {
106✔
621
      if (key) {
154✔
622
        const keyVal = data.getValueForKey(key);
12✔
623
        if (keyVal !== undefined) {
12✔
624
          return String(keyVal);
12✔
625
        }
12✔
626
      }
12✔
627
      if (getJSONPrimitiveType(data.value)) {
154✔
628
        return String(data.value);
138✔
629
      }
138✔
630
      return sourceCode.getText(data.node!);
154✔
631
    }
154✔
632

106✔
633
    return {
106✔
634
      JSONArrayExpression(node) {
106✔
635
        const data = new JSONArrayData(node, sourceCode);
110✔
636
        const option = parsedOptions.find((o) => o.isTargetArray(data));
110✔
637
        if (!option) {
110✔
638
          return;
6✔
639
        }
6✔
640
        verifyArrayElements(
110✔
641
          data.elements.filter((d) => !option.ignore(d)),
104✔
642
          option,
104✔
643
        );
104✔
644
      },
110✔
645
    };
106✔
646
  },
106✔
647
});
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc