@cldmv/eslint-plugin-jsonv 1.0.12 → 1.0.13

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.mjs CHANGED
@@ -12,7 +12,8 @@
12
12
  * - Reports parse errors with accurate source locations
13
13
  * - Supports all ES2011-2025 features (JSON5, binary/octal literals, BigInt, numeric separators, etc.)
14
14
  * - Detects internal reference errors (circular refs, undefined refs)
15
- * - Configurable year-based feature detection
15
+ * - Configurable language options: target ES year, parse mode, strict BigInt and octal checks,
16
+ * and internal reference resolution
16
17
  * - Exposes a positioned AST (objects, arrays, properties, literals, references, templates),
17
18
  * comments and tokens, so rules can select nodes and inline `eslint-disable` comments work
18
19
  *
@@ -38,6 +39,254 @@ import { createRequire } from "node:module";
38
39
  import { dirname, join } from "node:path";
39
40
  import { fileURLToPath } from "node:url";
40
41
 
42
+ //-----------------------------------------------------------------------------
43
+ // Types
44
+ //
45
+ // The language, its SourceCode and the plugin are typed against @eslint/core's
46
+ // generics (`Language`, `TextSourceCode`, `OkParseResult`, `Plugin`, ...), the same
47
+ // way @eslint/json types its JSON language. `tsc -p .configs/tsconfig.types.json`
48
+ // type-checks this JSDoc (`checkJs`) and emits it as `dist/index.d.mts`.
49
+ //-----------------------------------------------------------------------------
50
+
51
+ /**
52
+ * @import { ConfigObject, File, FileError, FileProblem, Language, OkParseResult, ParseResult, Plugin, RulesConfig, RuleVisitor, SourceLocation, SourceRange } from "@eslint/core";
53
+ * @import { CustomRuleDefinitionType, CustomRuleTypeDefinitions, CustomRuleVisitorWithExit, DirectiveType } from "@eslint/plugin-kit";
54
+ * @import { AstResult, SourceLocation as JsonvParserLocation, TokenType } from "@cldmv/jsonv/parser";
55
+ */
56
+
57
+ /**
58
+ * The ES years the `year` language option accepts, matching @cldmv/jsonv's `ParseOptions["year"]`.
59
+ *
60
+ * @public
61
+ * @typedef {2011 | 2015 | 2016 | 2017 | 2018 | 2019 | 2020 | 2021 | 2022 | 2023 | 2024 | 2025} JsonvYear
62
+ */
63
+
64
+ /**
65
+ * The parse modes the `mode` language option accepts, matching @cldmv/jsonv's `ParseOptions["mode"]`:
66
+ * `"jsonv"` (every jsonv feature of the selected year), `"json5"` (JSON5 only) or `"json"` (strict JSON).
67
+ *
68
+ * @public
69
+ * @typedef {"jsonv" | "json5" | "json"} JsonvMode
70
+ */
71
+
72
+ /**
73
+ * Language options for `language: "jsonv/jsonv"`. Each one is forwarded to @cldmv/jsonv's
74
+ * `parseWithOptions()` and `parseToAst()`. Any other key is rejected by `validateLanguageOptions`,
75
+ * so the type is closed.
76
+ *
77
+ * @public
78
+ * @typedef {object} JsonvLanguageOptions
79
+ * @property {JsonvYear} [year] The ES year whose jsonv features are allowed. Defaults to `2025`.
80
+ * @property {JsonvMode} [mode] The parse mode. Defaults to `"jsonv"`.
81
+ * @property {boolean} [strictBigInt] When `true`, an integer outside the safe range without an `n` suffix is an error instead of
82
+ * being converted to a BigInt. Defaults to `false`.
83
+ * @property {boolean} [strictOctal] When `true`, a legacy octal literal such as `0755` is an error (`0o755` is still allowed).
84
+ * Defaults to `false`.
85
+ * @property {boolean} [allowInternalReferences] When `false`, internal references are left unresolved, so an undefined or
86
+ * circular reference is no longer reported. Defaults to `true`.
87
+ */
88
+
89
+ /**
90
+ * A `Literal`: string, number, BigInt, boolean, `null`, `Infinity` or `NaN`, with its source text in `raw`.
91
+ *
92
+ * @public
93
+ * @typedef {object} JsonvLiteral
94
+ * @property {"Literal"} type The node type.
95
+ * @property {string | number | bigint | boolean | null} value The evaluated value.
96
+ * @property {string} raw The source text.
97
+ * @property {string} [bigint] The BigInt digits, for a BigInt literal.
98
+ * @property {SourceLocation} loc The location (1-based lines and columns).
99
+ * @property {SourceRange} range The `[start, end)` source offsets.
100
+ */
101
+
102
+ /**
103
+ * An `Identifier`: an unquoted key, or an internal reference such as `backup: port`.
104
+ *
105
+ * @public
106
+ * @typedef {object} JsonvIdentifier
107
+ * @property {"Identifier"} type The node type.
108
+ * @property {string} name The identifier text.
109
+ * @property {SourceLocation} loc The location (1-based lines and columns).
110
+ * @property {SourceRange} range The `[start, end)` source offsets.
111
+ */
112
+
113
+ /**
114
+ * An `ObjectExpression`: `{ ... }`.
115
+ *
116
+ * @public
117
+ * @typedef {object} JsonvObjectExpression
118
+ * @property {"ObjectExpression"} type The node type.
119
+ * @property {JsonvProperty[]} properties The properties, in source order.
120
+ * @property {SourceLocation} loc The location (1-based lines and columns).
121
+ * @property {SourceRange} range The `[start, end)` source offsets.
122
+ */
123
+
124
+ /**
125
+ * A `Property`: `key: value`. The key is a `Literal` (quoted or numeric key) or an `Identifier` (unquoted key).
126
+ *
127
+ * @public
128
+ * @typedef {object} JsonvProperty
129
+ * @property {"Property"} type The node type.
130
+ * @property {JsonvLiteral | JsonvIdentifier} key The key.
131
+ * @property {JsonvExpression} value The value.
132
+ * @property {boolean} computed Always `false` in jsonv.
133
+ * @property {SourceLocation} loc The location (1-based lines and columns).
134
+ * @property {SourceRange} range The `[start, end)` source offsets.
135
+ */
136
+
137
+ /**
138
+ * An `ArrayExpression`: `[ ... ]`.
139
+ *
140
+ * @public
141
+ * @typedef {object} JsonvArrayExpression
142
+ * @property {"ArrayExpression"} type The node type.
143
+ * @property {JsonvExpression[]} elements The elements, in source order.
144
+ * @property {SourceLocation} loc The location (1-based lines and columns).
145
+ * @property {SourceRange} range The `[start, end)` source offsets.
146
+ */
147
+
148
+ /**
149
+ * A `MemberExpression`: a dotted internal reference such as `server.port`.
150
+ *
151
+ * @public
152
+ * @typedef {object} JsonvMemberExpression
153
+ * @property {"MemberExpression"} type The node type.
154
+ * @property {JsonvExpression} object The object being accessed.
155
+ * @property {JsonvIdentifier} property The accessed property.
156
+ * @property {false} computed Always `false`.
157
+ * @property {SourceLocation} loc The location (1-based lines and columns).
158
+ * @property {SourceRange} range The `[start, end)` source offsets.
159
+ */
160
+
161
+ /**
162
+ * A `TemplateElement`: one string segment of a template literal.
163
+ *
164
+ * @public
165
+ * @typedef {object} JsonvTemplateElement
166
+ * @property {"TemplateElement"} type The node type.
167
+ * @property {{raw: string, cooked: string}} value The raw and cooked segment text.
168
+ * @property {boolean} tail Whether this is the last segment.
169
+ * @property {SourceLocation} loc The location (1-based lines and columns).
170
+ * @property {SourceRange} range The `[start, end)` source offsets.
171
+ */
172
+
173
+ /**
174
+ * A `TemplateLiteral`: a backtick string and its `${...}` interpolations.
175
+ *
176
+ * @public
177
+ * @typedef {object} JsonvTemplateLiteral
178
+ * @property {"TemplateLiteral"} type The node type.
179
+ * @property {JsonvTemplateElement[]} quasis The string segments.
180
+ * @property {JsonvExpression[]} expressions The interpolated expressions.
181
+ * @property {SourceLocation} loc The location (1-based lines and columns).
182
+ * @property {SourceRange} range The `[start, end)` source offsets.
183
+ */
184
+
185
+ /**
186
+ * Any value node.
187
+ *
188
+ * @public
189
+ * @typedef {JsonvLiteral | JsonvObjectExpression | JsonvArrayExpression | JsonvIdentifier | JsonvTemplateLiteral | JsonvMemberExpression} JsonvExpression
190
+ */
191
+
192
+ /**
193
+ * A `Line` (`// ...`) or `Block` (`/* ... *\/`) comment. `value` excludes the delimiters; `loc` and `range` include them.
194
+ *
195
+ * @public
196
+ * @typedef {object} JsonvComment
197
+ * @property {"Line" | "Block"} type The comment kind.
198
+ * @property {string} value The comment text without its delimiters.
199
+ * @property {SourceLocation} loc The location (1-based lines and columns).
200
+ * @property {SourceRange} range The `[start, end)` source offsets.
201
+ */
202
+
203
+ /**
204
+ * A token (comments and the end-of-file token are not included).
205
+ *
206
+ * @public
207
+ * @typedef {object} JsonvToken
208
+ * @property {`${TokenType}`} type The token type, e.g. `"{"`, `"String"`, `"Identifier"`, `"TemplateHead"`.
209
+ * @property {string | number | bigint | boolean | null} value The token's value.
210
+ * @property {string} raw The source text.
211
+ * @property {SourceLocation} loc The location (1-based lines and columns).
212
+ * @property {SourceRange} range The `[start, end)` source offsets.
213
+ */
214
+
215
+ /**
216
+ * The `Program` root: `body` is the root value; `comments` and `tokens` are in source order.
217
+ *
218
+ * @public
219
+ * @typedef {object} JsonvProgram
220
+ * @property {"Program"} type The node type.
221
+ * @property {JsonvExpression} body The root value.
222
+ * @property {JsonvComment[]} comments The comments.
223
+ * @property {JsonvToken[]} tokens The tokens, excluding comments.
224
+ * @property {SourceLocation} loc The location of the whole document (1-based lines and columns).
225
+ * @property {SourceRange} range The `[start, end)` source offsets of the whole document.
226
+ */
227
+
228
+ /**
229
+ * Any AST node.
230
+ *
231
+ * @public
232
+ * @typedef {JsonvProgram | JsonvExpression | JsonvProperty | JsonvTemplateElement} JsonvNode
233
+ */
234
+
235
+ /**
236
+ * Anything with a location: a node, a token or a comment.
237
+ *
238
+ * @public
239
+ * @typedef {JsonvNode | JsonvToken | JsonvComment} JsonvSyntaxElement
240
+ */
241
+
242
+ /**
243
+ * The jsonv language, typed with @eslint/core's `Language` generics.
244
+ *
245
+ * @public
246
+ * @typedef {Language<{LangOptions: JsonvLanguageOptions, Code: JsonvSourceCode, RootNode: JsonvProgram, Node: JsonvNode}>} JsonvLanguage
247
+ */
248
+
249
+ /**
250
+ * A successful `parse()` result. Besides `ast` it carries `jsonvValue`, the evaluated document
251
+ * (references resolved).
252
+ *
253
+ * @public
254
+ * @typedef {OkParseResult<JsonvProgram>} JsonvOkParseResult
255
+ */
256
+
257
+ /**
258
+ * A `parse()` result: {@link JsonvOkParseResult}, or a failure carrying `errors`.
259
+ *
260
+ * @public
261
+ * @typedef {ParseResult<JsonvProgram>} JsonvParseResult
262
+ */
263
+
264
+ /**
265
+ * The visitor a jsonv rule returns: a handler per node type (plus `:exit` variants), each called
266
+ * with the node and its parent.
267
+ *
268
+ * @public
269
+ * @typedef {RuleVisitor & CustomRuleVisitorWithExit<{
270
+ * Program?: (node: JsonvProgram) => void,
271
+ * ObjectExpression?: (node: JsonvObjectExpression, parent?: JsonvNode) => void,
272
+ * Property?: (node: JsonvProperty, parent?: JsonvObjectExpression) => void,
273
+ * ArrayExpression?: (node: JsonvArrayExpression, parent?: JsonvNode) => void,
274
+ * Literal?: (node: JsonvLiteral, parent?: JsonvNode) => void,
275
+ * Identifier?: (node: JsonvIdentifier, parent?: JsonvNode) => void,
276
+ * MemberExpression?: (node: JsonvMemberExpression, parent?: JsonvNode) => void,
277
+ * TemplateLiteral?: (node: JsonvTemplateLiteral, parent?: JsonvNode) => void,
278
+ * TemplateElement?: (node: JsonvTemplateElement, parent?: JsonvTemplateLiteral) => void
279
+ * }>} JsonvRuleVisitor
280
+ */
281
+
282
+ /**
283
+ * A rule for jsonv files. `Options` sets `RuleOptions`, `MessageIds` and `ExtRuleDocs`.
284
+ *
285
+ * @public
286
+ * @template {Partial<CustomRuleTypeDefinitions>} [Options={}]
287
+ * @typedef {CustomRuleDefinitionType<{LangOptions: JsonvLanguageOptions, Code: JsonvSourceCode, Visitor: JsonvRuleVisitor, Node: JsonvNode}, Options>} JsonvRuleDefinition
288
+ */
289
+
41
290
  /**
42
291
  * Reads this package's own `name` and `version` from its `package.json`.
43
292
  *
@@ -61,14 +310,43 @@ function readPackageMeta() {
61
310
  }
62
311
 
63
312
  /**
64
- * Language options `jsonvLanguage#parse` reads from `context.languageOptions` and
65
- * forwards to `@cldmv/jsonv`'s `parseWithOptions`. Any other key is rejected by
66
- * `validateLanguageOptions` since it would silently do nothing.
313
+ * The default of every language option `jsonvLanguage#parse` reads from `context.languageOptions`
314
+ * and forwards to @cldmv/jsonv's `parseWithOptions()` and `parseToAst()`. The defaults are
315
+ * @cldmv/jsonv's own (`ParseOptions` in its `src/api-types.mts`), except `year`, which jsonv
316
+ * derives from the current date and the plugin pins to the latest supported year.
317
+ *
318
+ * @public
319
+ * @type {Readonly<Required<JsonvLanguageOptions>>}
320
+ */
321
+ const DEFAULT_LANGUAGE_OPTIONS = Object.freeze({
322
+ year: 2025,
323
+ mode: "jsonv",
324
+ strictBigInt: false,
325
+ strictOctal: false,
326
+ allowInternalReferences: true
327
+ });
328
+
329
+ /**
330
+ * The language options the plugin accepts: exactly the keys of {@link DEFAULT_LANGUAGE_OPTIONS}.
331
+ * Any other key is rejected by `validateLanguageOptions`, since it would silently do nothing.
67
332
  *
68
333
  * @public
69
334
  * @type {Set<string>}
70
335
  */
71
- const SUPPORTED_LANGUAGE_OPTIONS = new Set(["year", "strictBigInt"]);
336
+ const SUPPORTED_LANGUAGE_OPTIONS = new Set(Object.keys(DEFAULT_LANGUAGE_OPTIONS));
337
+
338
+ /**
339
+ * @cldmv/jsonv parse options the plugin deliberately does not accept, each with the reason
340
+ * `validateLanguageOptions` gives when a config sets it.
341
+ *
342
+ * @public
343
+ * @type {Readonly<Record<string, string>>}
344
+ */
345
+ const UNSUPPORTED_JSONV_OPTIONS = Object.freeze({
346
+ reviver: "a reviver only transforms the evaluated value, which linting does not use",
347
+ preserveComments: "the AST always carries the comments",
348
+ tolerant: "the plugin controls how parse errors are collected"
349
+ });
72
350
 
73
351
  /**
74
352
  * Valid `year` language option values, matching @cldmv/jsonv's `ParseOptions["year"]`
@@ -79,6 +357,22 @@ const SUPPORTED_LANGUAGE_OPTIONS = new Set(["year", "strictBigInt"]);
79
357
  */
80
358
  const VALID_YEARS = new Set([2011, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025]);
81
359
 
360
+ /**
361
+ * Valid `mode` language option values, matching @cldmv/jsonv's `ParseOptions["mode"]` union.
362
+ *
363
+ * @public
364
+ * @type {Set<string>}
365
+ */
366
+ const VALID_MODES = new Set(["jsonv", "json5", "json"]);
367
+
368
+ /**
369
+ * The language options that take a boolean.
370
+ *
371
+ * @public
372
+ * @type {ReadonlyArray<"strictBigInt" | "strictOctal" | "allowInternalReferences">}
373
+ */
374
+ const BOOLEAN_LANGUAGE_OPTIONS = Object.freeze(["strictBigInt", "strictOctal", "allowInternalReferences"]);
375
+
82
376
  /**
83
377
  * Child keys of every node type in the AST `jsonvLanguage#parse` produces, in source order
84
378
  * where that is defined. The node types and their shapes come from @cldmv/jsonv's own AST
@@ -138,19 +432,26 @@ const commentParser = new ConfigCommentParser();
138
432
  * Tests whether a value is a positioned @cldmv/jsonv AST node.
139
433
  * @public
140
434
  * @param {unknown} value - Any property value of a node.
141
- * @returns {boolean} `true` for an object with a string `type` and a `loc`.
435
+ * @returns {value is {type: string, [key: string]: unknown}} `true` for an object with a string `type` and a `loc`.
142
436
  * @example
143
437
  * isJsonvNode({ type: "Literal", loc: { start: {}, end: {} } }); // true
144
438
  */
145
439
  function isJsonvNode(value) {
146
- return value !== null && typeof value === "object" && typeof value.type === "string" && typeof value.loc === "object";
440
+ return (
441
+ value !== null &&
442
+ typeof value === "object" &&
443
+ "type" in value &&
444
+ typeof value.type === "string" &&
445
+ "loc" in value &&
446
+ typeof value.loc === "object"
447
+ );
147
448
  }
148
449
 
149
450
  /**
150
451
  * Returns the child keys to visit for a node: its entry in {@link VISITOR_KEYS}, or — for a
151
452
  * node type @cldmv/jsonv may add later — every key holding a node or an array of nodes.
152
453
  * @public
153
- * @param {Object} node - An AST node.
454
+ * @param {{type: string, [key: string]: unknown}} node - An AST node.
154
455
  * @returns {ReadonlyArray<string>} Child property names.
155
456
  * @example
156
457
  * getChildKeys({ type: "Property", key, value }); // ["key", "value"]
@@ -170,8 +471,8 @@ function getChildKeys(node) {
170
471
  * ESLint `loc`/`range` pair for this language. The language declares `lineStart: 1` and
171
472
  * `columnStart: 1`, so columns are shifted to 1-based; `range` comes from the offsets.
172
473
  * @public
173
- * @param {{start: {line: number, column: number, offset: number}, end: {line: number, column: number, offset: number}}} loc - A jsonv location.
174
- * @returns {{loc: {start: {line: number, column: number}, end: {line: number, column: number}}, range: [number, number]}} The ESLint location and range.
474
+ * @param {JsonvParserLocation} loc - A jsonv location.
475
+ * @returns {{loc: SourceLocation, range: SourceRange}} The ESLint location and range.
175
476
  * @example
176
477
  * toEslintPosition({ start: { line: 1, column: 0, offset: 0 }, end: { line: 1, column: 3, offset: 3 } });
177
478
  * // { loc: { start: { line: 1, column: 1 }, end: { line: 1, column: 4 } }, range: [0, 3] }
@@ -191,28 +492,36 @@ function toEslintPosition(loc) {
191
492
  * Tokens are never walked for children: some token types share a name with a node type
192
493
  * (`Identifier`, `TemplateLiteral`) but carry no child nodes.
193
494
  * @public
194
- * @param {Object} element - A jsonv comment or token.
195
- * @returns {Object} The converted copy.
495
+ * @template {{loc: JsonvParserLocation}} T
496
+ * @param {T} element - A jsonv comment or token.
497
+ * @returns {{[K in keyof T]: K extends "loc" ? SourceLocation : T[K]} & {range: SourceRange}} The converted copy.
196
498
  * @example
197
499
  * convertLeaf(parseToAst("// note\n1").comments[0]);
198
500
  */
199
501
  function convertLeaf(element) {
200
- return { ...element, ...toEslintPosition(element.loc) };
502
+ // TypeScript types a spread of a generic as an intersection (`T & {loc, range}`), which keeps
503
+ // the jsonv `loc` type alongside the ESLint one; the cast states the actual replacement.
504
+ return /** @type {{[K in keyof T]: K extends "loc" ? SourceLocation : T[K]} & {range: SourceRange}} */ ({
505
+ ...element,
506
+ ...toEslintPosition(element.loc)
507
+ });
201
508
  }
202
509
 
203
510
  /**
204
511
  * Deep-copies a jsonv AST node with ESLint positions: every node gets a 1-based `loc` and a
205
512
  * `range`, and child nodes are converted recursively.
206
513
  * @public
207
- * @param {Object} node - A jsonv AST node.
208
- * @returns {Object} The converted copy.
514
+ * @param {{type: string, loc: JsonvParserLocation, [key: string]: unknown}} node - A jsonv AST node.
515
+ * @returns {{type: string, [key: string]: unknown}} The converted copy.
209
516
  * @example
210
517
  * convertNode(parseToAst("{ a: 1 }").program.body);
211
518
  */
212
519
  function convertNode(node) {
520
+ /** @type {{type: string, [key: string]: unknown}} */
213
521
  const converted = convertLeaf(node);
214
522
  for (const key of getChildKeys(node)) {
215
- const child = node[key];
523
+ // getChildKeys() only returns keys that hold a node or an array of nodes.
524
+ const child = /** @type {Parameters<typeof convertNode>[0] | Parameters<typeof convertNode>[0][]} */ (node[key]);
216
525
  converted[key] = Array.isArray(child) ? child.map(convertNode) : convertNode(child);
217
526
  }
218
527
  return converted;
@@ -222,14 +531,19 @@ function convertNode(node) {
222
531
  * Builds the ESLint AST for a document from @cldmv/jsonv's `parseToAst()` result: the
223
532
  * `Program` root (whose `body` is the root value) with positioned `comments` and `tokens`.
224
533
  * @public
225
- * @param {{program: Object, comments: Object[], tokens: Object[]}} astResult - The `parseToAst()` result.
226
- * @returns {Object} The `Program` node.
534
+ * @param {AstResult} astResult - The `parseToAst()` result.
535
+ * @returns {JsonvProgram} The `Program` node.
227
536
  * @example
228
537
  * buildProgram(parseToAst("// note\n{ a: 1 }"));
229
538
  */
230
539
  function buildProgram({ program, comments, tokens }) {
540
+ // @cldmv/jsonv positions every node it produces (`ASTNode#loc` is only optional in its type), and
541
+ // convertNode() keeps each node's shape while swapping in ESLint positions, so the copy is a
542
+ // JsonvProgram. The generic, runtime-keyed walk cannot express that per node type, hence the casts.
543
+ const rawProgram = /** @type {Parameters<typeof convertNode>[0]} */ (/** @type {unknown} */ (program));
544
+ const root = /** @type {Omit<JsonvProgram, "comments" | "tokens">} */ (convertNode(rawProgram));
231
545
  return {
232
- ...convertNode(program),
546
+ ...root,
233
547
  comments: comments.map(convertLeaf),
234
548
  tokens: tokens.map(convertLeaf)
235
549
  };
@@ -242,6 +556,7 @@ function buildProgram({ program, comments, tokens }) {
242
556
  * directives and `/* eslint rule: ... *\/` config comments).
243
557
  *
244
558
  * @public
559
+ * @extends {TextSourceCodeBase<{LangOptions: JsonvLanguageOptions, RootNode: JsonvProgram, SyntaxElementWithLoc: JsonvSyntaxElement, ConfigNode: JsonvComment}>}
245
560
  */
246
561
  class JsonvSourceCode extends TextSourceCodeBase {
247
562
  /**
@@ -252,13 +567,13 @@ class JsonvSourceCode extends TextSourceCodeBase {
252
567
 
253
568
  /**
254
569
  * Parent of every non-root node.
255
- * @type {WeakMap<Object, Object>}
570
+ * @type {WeakMap<object, JsonvNode>}
256
571
  */
257
572
  #parents = new WeakMap();
258
573
 
259
574
  /**
260
575
  * Cached inline configuration comments.
261
- * @type {Object[]|undefined}
576
+ * @type {JsonvComment[]|undefined}
262
577
  */
263
578
  #inlineConfigComments;
264
579
 
@@ -266,13 +581,13 @@ class JsonvSourceCode extends TextSourceCodeBase {
266
581
  * Creates a SourceCode object.
267
582
  * @param {Object} options - Options.
268
583
  * @param {string} options.text - The source text.
269
- * @param {Object} options.ast - The `Program` node built by `jsonvLanguage#parse`.
584
+ * @param {JsonvProgram} options.ast - The `Program` node built by `jsonvLanguage#parse`.
270
585
  */
271
586
  constructor({ text, ast }) {
272
587
  super({ text, ast, lineEndingPattern: LINE_ENDING_PATTERN });
273
- /** @type {Object[]} Comments in source order. */
588
+ /** @type {JsonvComment[]} Comments in source order. */
274
589
  this.comments = ast.comments;
275
- /** @type {Object[]} Tokens in source order, excluding comments. */
590
+ /** @type {JsonvToken[]} Tokens in source order, excluding comments. */
276
591
  this.tokens = ast.tokens;
277
592
  /** @type {boolean} Whether the text starts with a byte order mark. */
278
593
  this.hasBOM = text.charCodeAt(0) === 0xfeff;
@@ -293,12 +608,18 @@ class JsonvSourceCode extends TextSourceCodeBase {
293
608
  */
294
609
  #ensureTraversal() {
295
610
  if (!this.#steps) {
611
+ /** @type {VisitNodeStep[]} */
296
612
  const steps = [];
613
+ /**
614
+ * @param {JsonvNode} node - The node to visit.
615
+ * @param {JsonvNode|undefined} parent - Its parent (`undefined` for the root).
616
+ */
297
617
  const visit = (node, parent) => {
298
618
  if (parent) this.#parents.set(node, parent);
299
619
  steps.push(new VisitNodeStep({ target: node, phase: 1, args: [node, parent] }));
300
620
  for (const key of getChildKeys(node)) {
301
- const child = node[key];
621
+ // getChildKeys() only returns keys that hold a node or an array of nodes.
622
+ const child = /** @type {JsonvNode | JsonvNode[]} */ (/** @type {{type: string, [key: string]: unknown}} */ (node)[key]);
302
623
  for (const item of Array.isArray(child) ? child : [child]) visit(item, node);
303
624
  }
304
625
  steps.push(new VisitNodeStep({ target: node, phase: 2, args: [node, parent] }));
@@ -312,7 +633,7 @@ class JsonvSourceCode extends TextSourceCodeBase {
312
633
  /**
313
634
  * Returns the enter/exit visit steps for every node, depth first in source order.
314
635
  * @public
315
- * @returns {Iterator<VisitNodeStep>} The traversal steps.
636
+ * @returns {IterableIterator<VisitNodeStep>} The traversal steps.
316
637
  */
317
638
  traverse() {
318
639
  return this.#ensureTraversal().values();
@@ -321,8 +642,8 @@ class JsonvSourceCode extends TextSourceCodeBase {
321
642
  /**
322
643
  * Returns the parent of a node, or `undefined` for the `Program` root.
323
644
  * @public
324
- * @param {Object} node - An AST node.
325
- * @returns {Object|undefined} The parent node.
645
+ * @param {JsonvSyntaxElement} node - An AST node.
646
+ * @returns {JsonvNode|undefined} The parent node.
326
647
  */
327
648
  getParent(node) {
328
649
  this.#ensureTraversal();
@@ -333,7 +654,7 @@ class JsonvSourceCode extends TextSourceCodeBase {
333
654
  * Returns the comments that hold inline configuration (`eslint`, `eslint-enable`,
334
655
  * `eslint-disable`, `eslint-disable-line`, `eslint-disable-next-line`).
335
656
  * @public
336
- * @returns {Object[]} The inline configuration comments.
657
+ * @returns {JsonvComment[]} The inline configuration comments.
337
658
  */
338
659
  getInlineConfigNodes() {
339
660
  if (!this.#inlineConfigComments) {
@@ -346,14 +667,20 @@ class JsonvSourceCode extends TextSourceCodeBase {
346
667
  * Returns the `eslint-disable*` / `eslint-enable` directives, plus problems for malformed
347
668
  * ones (an `eslint-disable-line` spanning several lines).
348
669
  * @public
349
- * @returns {{problems: Object[], directives: Directive[]}} Directives and problems.
670
+ * @returns {{problems: FileProblem[], directives: Directive[]}} Directives and problems.
350
671
  */
351
672
  getDisableDirectives() {
673
+ /** @type {FileProblem[]} */
352
674
  const problems = [];
675
+ /** @type {Directive[]} */
353
676
  const directives = [];
354
677
 
355
678
  for (const comment of this.getInlineConfigNodes()) {
356
- const { label, value, justification } = commentParser.parseDirective(comment.value);
679
+ // parseDirective() returns undefined only for text that doesn't start with a directive label;
680
+ // every comment here matched INLINE_CONFIG, which requires one.
681
+ const { label, value, justification } = /** @type {NonNullable<ReturnType<ConfigCommentParser["parseDirective"]>>} */ (
682
+ commentParser.parseDirective(comment.value)
683
+ );
357
684
 
358
685
  if (label === "eslint-disable-line" && comment.loc.start.line !== comment.loc.end.line) {
359
686
  problems.push({ ruleId: null, message: `${label} comment should not span multiple lines.`, loc: comment.loc });
@@ -361,7 +688,10 @@ class JsonvSourceCode extends TextSourceCodeBase {
361
688
  }
362
689
 
363
690
  if (label !== "eslint") {
364
- directives.push(new Directive({ type: label.slice("eslint-".length), node: comment, value, justification }));
691
+ // The comment matched INLINE_CONFIG, so any label other than "eslint" is an
692
+ // `eslint-disable*` / `eslint-enable` label, i.e. a DirectiveType once unprefixed.
693
+ const type = /** @type {DirectiveType} */ (label.slice("eslint-".length));
694
+ directives.push(new Directive({ type, node: comment, value, justification }));
365
695
  }
366
696
  }
367
697
 
@@ -372,14 +702,19 @@ class JsonvSourceCode extends TextSourceCodeBase {
372
702
  * Returns the rule configurations from `/* eslint rule: ... *\/` comments, plus problems for
373
703
  * ones that fail to parse.
374
704
  * @public
375
- * @returns {{problems: Object[], configs: Array<{config: {rules: Object}, loc: Object}>}} Configs and problems.
705
+ * @returns {{problems: FileProblem[], configs: Array<{config: {rules: RulesConfig}, loc: SourceLocation}>}} Configs and problems.
376
706
  */
377
707
  applyInlineConfig() {
708
+ /** @type {FileProblem[]} */
378
709
  const problems = [];
710
+ /** @type {Array<{config: {rules: RulesConfig}, loc: SourceLocation}>} */
379
711
  const configs = [];
380
712
 
381
713
  for (const comment of this.getInlineConfigNodes()) {
382
- const { label, value } = commentParser.parseDirective(comment.value);
714
+ // Defined for the same reason as in getDisableDirectives().
715
+ const { label, value } = /** @type {NonNullable<ReturnType<ConfigCommentParser["parseDirective"]>>} */ (
716
+ commentParser.parseDirective(comment.value)
717
+ );
383
718
  if (label !== "eslint") continue;
384
719
 
385
720
  const parseResult = commentParser.parseJSONLikeConfig(value);
@@ -398,7 +733,7 @@ class JsonvSourceCode extends TextSourceCodeBase {
398
733
  * Language definition for jsonv files
399
734
  *
400
735
  * @public
401
- * @type {Object}
736
+ * @type {JsonvLanguage}
402
737
  */
403
738
  const jsonvLanguage = {
404
739
  /** File type - must be "text" for text-based languages */
@@ -409,28 +744,38 @@ const jsonvLanguage = {
409
744
  columnStart: 1,
410
745
  /** Property name for node types */
411
746
  nodeTypeKey: "type",
412
- /** Child keys of each AST node type, used for selector matching */
413
- visitorKeys: VISITOR_KEYS,
747
+ /**
748
+ * Child keys of each AST node type, used for selector matching. `Language#visitorKeys` is typed
749
+ * with mutable arrays; ESLint only reads them, and VISITOR_KEYS stays frozen at runtime.
750
+ */
751
+ visitorKeys: /** @type {Record<string, string[]>} */ (VISITOR_KEYS),
414
752
 
415
753
  /**
416
754
  * Parse jsonv source code - ESLint Language API.
417
755
  *
418
756
  * The file is first parsed and evaluated with `parseWithOptions` — that is what reports
419
- * syntax errors, year-gated features, `strictBigInt` violations and unresolved/circular
420
- * internal references, each with its source position. A file that passes is then parsed
421
- * with `parseToAst` to build the positioned AST, comments and tokens ESLint traverses.
757
+ * syntax errors, year-gated features, `mode`, `strictBigInt` and `strictOctal` violations
758
+ * and unresolved/circular internal references (unless `allowInternalReferences` is `false`),
759
+ * each with its source position. A file that passes is then parsed with `parseToAst` to
760
+ * build the positioned AST, comments and tokens ESLint traverses. Both calls receive every
761
+ * language option, with the defaults filled in.
422
762
  *
423
- * @param {Object} file - File object with body property containing source text
424
- * @param {Object} context - Context with languageOptions
425
- * @returns {Object} Parse result {ok, ast, jsonvValue} or {ok: false, errors}
763
+ * @param {File} file - File object with body property containing source text
764
+ * @param {{languageOptions: JsonvLanguageOptions}} context - Context with languageOptions
765
+ * @returns {JsonvParseResult} Parse result {ok, ast, jsonvValue} or {ok: false, errors}
426
766
  */
427
767
  parse(file, context) {
428
- const text = file.body;
768
+ // `fileType: "text"` means ESLint reads the file as text, so `body` is a string.
769
+ const text = /** @type {string} */ (file.body);
770
+ /** @type {JsonvLanguageOptions} */
429
771
  const options = context.languageOptions || {};
430
772
  const parseOptions = {
431
- year: options.year || 2025,
432
- strictBigInt: options.strictBigInt !== undefined ? options.strictBigInt : false,
433
- mode: "jsonv"
773
+ // `||`, not `??`: a falsy year (e.g. `0` from a direct `parse()` call) has always meant the default.
774
+ year: options.year || DEFAULT_LANGUAGE_OPTIONS.year,
775
+ mode: options.mode ?? DEFAULT_LANGUAGE_OPTIONS.mode,
776
+ strictBigInt: options.strictBigInt ?? DEFAULT_LANGUAGE_OPTIONS.strictBigInt,
777
+ strictOctal: options.strictOctal ?? DEFAULT_LANGUAGE_OPTIONS.strictOctal,
778
+ allowInternalReferences: options.allowInternalReferences ?? DEFAULT_LANGUAGE_OPTIONS.allowInternalReferences
434
779
  };
435
780
 
436
781
  try {
@@ -454,9 +799,18 @@ const jsonvLanguage = {
454
799
  // nesting) has no position — fall back to line 1, column 1.
455
800
  const hasPosition = error instanceof JsonvSyntaxError || error instanceof JsonvReferenceError;
456
801
 
802
+ // Everything the parser and buildProgram() throw is an Error instance (the RangeError
803
+ // above included), hence the cast in the fallback branch.
804
+ /** @type {FileError} */
457
805
  const errorInfo = hasPosition
458
- ? { message: error.message, line: error.line, column: error.column + 1, endLine: error.loc.end.line, endColumn: error.loc.end.column + 1 }
459
- : { message: error.message, line: 1, column: 1 };
806
+ ? {
807
+ message: error.message,
808
+ line: error.line,
809
+ column: error.column + 1,
810
+ endLine: error.loc.end.line,
811
+ endColumn: error.loc.end.column + 1
812
+ }
813
+ : { message: /** @type {Error} */ (error).message, line: 1, column: 1 };
460
814
 
461
815
  return {
462
816
  ok: false,
@@ -469,8 +823,8 @@ const jsonvLanguage = {
469
823
  * Create the SourceCode object ESLint hands to rules
470
824
  *
471
825
  * @public
472
- * @param {Object} file - The file object
473
- * @param {Object} parseResult - The successful result from parse()
826
+ * @param {File} file - The file object
827
+ * @param {JsonvOkParseResult} parseResult - The successful result from parse()
474
828
  * @returns {JsonvSourceCode} SourceCode object
475
829
  */
476
830
  createSourceCode(file, parseResult) {
@@ -485,16 +839,21 @@ const jsonvLanguage = {
485
839
  * reflects the effective options that will reach `parse()`.
486
840
  *
487
841
  * @public
488
- * @param {Object} languageOptions - The (already-merged) language options to validate
842
+ * @param {JsonvLanguageOptions} languageOptions - The (already-merged) language options to validate
489
843
  * @returns {void}
490
844
  * @throws {TypeError} When an unsupported key is present, or a supported key holds an invalid value
491
845
  */
492
846
  validateLanguageOptions(languageOptions) {
493
847
  const options = languageOptions ?? {};
494
848
 
849
+ const supported = `Supported options are: ${[...SUPPORTED_LANGUAGE_OPTIONS].join(", ")}.`;
850
+
495
851
  for (const key of Object.keys(options)) {
852
+ if (Object.hasOwn(UNSUPPORTED_JSONV_OPTIONS, key)) {
853
+ throw new TypeError(`Language option "${key}" is not supported: ${UNSUPPORTED_JSONV_OPTIONS[key]}. ${supported}`);
854
+ }
496
855
  if (!SUPPORTED_LANGUAGE_OPTIONS.has(key)) {
497
- throw new TypeError(`Unknown language option "${key}". Supported options are: ${[...SUPPORTED_LANGUAGE_OPTIONS].join(", ")}.`);
856
+ throw new TypeError(`Unknown language option "${key}". ${supported}`);
498
857
  }
499
858
  }
500
859
 
@@ -504,8 +863,16 @@ const jsonvLanguage = {
504
863
  );
505
864
  }
506
865
 
507
- if (options.strictBigInt !== undefined && typeof options.strictBigInt !== "boolean") {
508
- throw new TypeError(`Invalid "strictBigInt" language option: expected a boolean, got ${typeof options.strictBigInt}.`);
866
+ if (options.mode !== undefined && !VALID_MODES.has(options.mode)) {
867
+ throw new TypeError(
868
+ `Invalid "mode" language option: ${JSON.stringify(options.mode)}. Supported modes are: ${[...VALID_MODES].join(", ")}.`
869
+ );
870
+ }
871
+
872
+ for (const key of BOOLEAN_LANGUAGE_OPTIONS) {
873
+ if (options[key] !== undefined && typeof options[key] !== "boolean") {
874
+ throw new TypeError(`Invalid "${key}" language option: expected a boolean, got ${typeof options[key]}.`);
875
+ }
509
876
  }
510
877
  },
511
878
 
@@ -516,12 +883,9 @@ const jsonvLanguage = {
516
883
  * calling `validateLanguageOptions` and `parse()`.
517
884
  *
518
885
  * @public
519
- * @type {Object}
886
+ * @type {JsonvLanguageOptions}
520
887
  */
521
- defaultLanguageOptions: {
522
- year: 2025,
523
- strictBigInt: false
524
- }
888
+ defaultLanguageOptions: { ...DEFAULT_LANGUAGE_OPTIONS }
525
889
  };
526
890
 
527
891
  /**
@@ -532,7 +896,7 @@ const jsonvLanguage = {
532
896
  * `language` such as `jsonv/jsonv` never consults it, so setting it here was inert.
533
897
  *
534
898
  * @public
535
- * @type {Object}
899
+ * @satisfies {ConfigObject}
536
900
  */
537
901
  const recommendedConfig = {
538
902
  rules: {
@@ -542,10 +906,11 @@ const recommendedConfig = {
542
906
  };
543
907
 
544
908
  /**
545
- * ESLint plugin export
909
+ * ESLint plugin export. `@satisfies` checks it against @eslint/core's `Plugin` (which ESLint's
910
+ * own `ESLint.Plugin` is) while keeping its literal keys (`languages.jsonv`, `configs.recommended`).
546
911
  *
547
912
  * @public
548
- * @type {Object}
913
+ * @satisfies {Plugin}
549
914
  */
550
915
  const plugin = {
551
916
  meta: readPackageMeta(),
@@ -566,3 +931,4 @@ const plugin = {
566
931
  };
567
932
 
568
933
  export default plugin;
934
+ export { JsonvSourceCode };