@cldmv/eslint-plugin-jsonv 1.0.11 → 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/README.md +91 -12
- package/dist/index.d.mts +440 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +744 -113
- package/package.json +26 -7
package/dist/index.mjs
CHANGED
|
@@ -12,7 +12,10 @@
|
|
|
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
|
|
15
|
+
* - Configurable language options: target ES year, parse mode, strict BigInt and octal checks,
|
|
16
|
+
* and internal reference resolution
|
|
17
|
+
* - Exposes a positioned AST (objects, arrays, properties, literals, references, templates),
|
|
18
|
+
* comments and tokens, so rules can select nodes and inline `eslint-disable` comments work
|
|
16
19
|
*
|
|
17
20
|
* @example
|
|
18
21
|
* // In eslint.config.mjs
|
|
@@ -28,14 +31,262 @@
|
|
|
28
31
|
* ];
|
|
29
32
|
*/
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
import {
|
|
34
|
+
import { JsonvReferenceError, JsonvSyntaxError, parseToAst, parseWithOptions } from "@cldmv/jsonv/parser";
|
|
35
|
+
import { ConfigCommentParser, Directive, TextSourceCodeBase, VisitNodeStep } from "@eslint/plugin-kit";
|
|
33
36
|
|
|
34
37
|
import { existsSync } from "node:fs";
|
|
35
38
|
import { createRequire } from "node:module";
|
|
36
39
|
import { dirname, join } from "node:path";
|
|
37
40
|
import { fileURLToPath } from "node:url";
|
|
38
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
|
+
|
|
39
290
|
/**
|
|
40
291
|
* Reads this package's own `name` and `version` from its `package.json`.
|
|
41
292
|
*
|
|
@@ -59,14 +310,43 @@ function readPackageMeta() {
|
|
|
59
310
|
}
|
|
60
311
|
|
|
61
312
|
/**
|
|
62
|
-
*
|
|
63
|
-
* forwards to
|
|
64
|
-
* `
|
|
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.
|
|
65
332
|
*
|
|
66
333
|
* @public
|
|
67
334
|
* @type {Set<string>}
|
|
68
335
|
*/
|
|
69
|
-
const SUPPORTED_LANGUAGE_OPTIONS = new Set(
|
|
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
|
+
});
|
|
70
350
|
|
|
71
351
|
/**
|
|
72
352
|
* Valid `year` language option values, matching @cldmv/jsonv's `ParseOptions["year"]`
|
|
@@ -77,71 +357,461 @@ const SUPPORTED_LANGUAGE_OPTIONS = new Set(["year", "strictBigInt"]);
|
|
|
77
357
|
*/
|
|
78
358
|
const VALID_YEARS = new Set([2011, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025]);
|
|
79
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
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Child keys of every node type in the AST `jsonvLanguage#parse` produces, in source order
|
|
378
|
+
* where that is defined. The node types and their shapes come from @cldmv/jsonv's own AST
|
|
379
|
+
* (`@cldmv/jsonv/parser`'s `Program`/`Expression` types); the plugin only re-positions them.
|
|
380
|
+
*
|
|
381
|
+
* - `Program` — the document; `body` is the single root value.
|
|
382
|
+
* - `ObjectExpression` — `{ ... }`, with `properties` (`Property` nodes).
|
|
383
|
+
* - `Property` — `key: value`; `key` is a `Literal` (quoted or numeric key) or an `Identifier` (unquoted key).
|
|
384
|
+
* - `ArrayExpression` — `[ ... ]`, with `elements`.
|
|
385
|
+
* - `Literal` — string, number, BigInt, boolean, null, `Infinity`, `NaN` (`raw` holds the source text).
|
|
386
|
+
* - `Identifier` — an unquoted key, or an internal reference such as `backup: port`.
|
|
387
|
+
* - `MemberExpression` — a dotted internal reference such as `server.port`.
|
|
388
|
+
* - `TemplateLiteral` / `TemplateElement` — backtick strings and their `${...}` interpolations.
|
|
389
|
+
*
|
|
390
|
+
* @public
|
|
391
|
+
* @type {Readonly<Record<string, ReadonlyArray<string>>>}
|
|
392
|
+
*/
|
|
393
|
+
const VISITOR_KEYS = Object.freeze({
|
|
394
|
+
Program: Object.freeze(["body"]),
|
|
395
|
+
ObjectExpression: Object.freeze(["properties"]),
|
|
396
|
+
Property: Object.freeze(["key", "value"]),
|
|
397
|
+
ArrayExpression: Object.freeze(["elements"]),
|
|
398
|
+
Literal: Object.freeze([]),
|
|
399
|
+
Identifier: Object.freeze([]),
|
|
400
|
+
MemberExpression: Object.freeze(["object", "property"]),
|
|
401
|
+
TemplateLiteral: Object.freeze(["quasis", "expressions"]),
|
|
402
|
+
TemplateElement: Object.freeze([])
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Line terminators, matching how @cldmv/jsonv advances its line counter (`\r\n` as one
|
|
407
|
+
* break, plus lone `\r`, `\n`, U+2028 and U+2029), so `sourceCode.lines` and
|
|
408
|
+
* `getLocFromIndex()` agree with the node positions.
|
|
409
|
+
*
|
|
410
|
+
* @public
|
|
411
|
+
* @type {RegExp}
|
|
412
|
+
*/
|
|
413
|
+
const LINE_ENDING_PATTERN = /\r\n|[\r\n\u2028\u2029]/u;
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Matches the comment bodies ESLint treats as inline configuration (same pattern as @eslint/json).
|
|
417
|
+
*
|
|
418
|
+
* @public
|
|
419
|
+
* @type {RegExp}
|
|
420
|
+
*/
|
|
421
|
+
const INLINE_CONFIG = /^\s*eslint(?:-enable|-disable(?:(?:-next)?-line)?)?(?:\s|$)/u;
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Shared parser for `eslint-*` directive comments.
|
|
425
|
+
*
|
|
426
|
+
* @public
|
|
427
|
+
* @type {ConfigCommentParser}
|
|
428
|
+
*/
|
|
429
|
+
const commentParser = new ConfigCommentParser();
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Tests whether a value is a positioned @cldmv/jsonv AST node.
|
|
433
|
+
* @public
|
|
434
|
+
* @param {unknown} value - Any property value of a node.
|
|
435
|
+
* @returns {value is {type: string, [key: string]: unknown}} `true` for an object with a string `type` and a `loc`.
|
|
436
|
+
* @example
|
|
437
|
+
* isJsonvNode({ type: "Literal", loc: { start: {}, end: {} } }); // true
|
|
438
|
+
*/
|
|
439
|
+
function isJsonvNode(value) {
|
|
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
|
+
);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Returns the child keys to visit for a node: its entry in {@link VISITOR_KEYS}, or — for a
|
|
452
|
+
* node type @cldmv/jsonv may add later — every key holding a node or an array of nodes.
|
|
453
|
+
* @public
|
|
454
|
+
* @param {{type: string, [key: string]: unknown}} node - An AST node.
|
|
455
|
+
* @returns {ReadonlyArray<string>} Child property names.
|
|
456
|
+
* @example
|
|
457
|
+
* getChildKeys({ type: "Property", key, value }); // ["key", "value"]
|
|
458
|
+
*/
|
|
459
|
+
function getChildKeys(node) {
|
|
460
|
+
return (
|
|
461
|
+
VISITOR_KEYS[node.type] ??
|
|
462
|
+
Object.keys(node).filter((key) => {
|
|
463
|
+
const value = node[key];
|
|
464
|
+
return Array.isArray(value) ? value.some(isJsonvNode) : isJsonvNode(value);
|
|
465
|
+
})
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Converts a @cldmv/jsonv `loc` (1-based lines, 0-based columns, 0-based `offset`s) into the
|
|
471
|
+
* ESLint `loc`/`range` pair for this language. The language declares `lineStart: 1` and
|
|
472
|
+
* `columnStart: 1`, so columns are shifted to 1-based; `range` comes from the offsets.
|
|
473
|
+
* @public
|
|
474
|
+
* @param {JsonvParserLocation} loc - A jsonv location.
|
|
475
|
+
* @returns {{loc: SourceLocation, range: SourceRange}} The ESLint location and range.
|
|
476
|
+
* @example
|
|
477
|
+
* toEslintPosition({ start: { line: 1, column: 0, offset: 0 }, end: { line: 1, column: 3, offset: 3 } });
|
|
478
|
+
* // { loc: { start: { line: 1, column: 1 }, end: { line: 1, column: 4 } }, range: [0, 3] }
|
|
479
|
+
*/
|
|
480
|
+
function toEslintPosition(loc) {
|
|
481
|
+
return {
|
|
482
|
+
loc: {
|
|
483
|
+
start: { line: loc.start.line, column: loc.start.column + 1 },
|
|
484
|
+
end: { line: loc.end.line, column: loc.end.column + 1 }
|
|
485
|
+
},
|
|
486
|
+
range: [loc.start.offset, loc.end.offset]
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Copies a jsonv comment or token with ESLint positions (a 1-based `loc` and a `range`).
|
|
492
|
+
* Tokens are never walked for children: some token types share a name with a node type
|
|
493
|
+
* (`Identifier`, `TemplateLiteral`) but carry no child nodes.
|
|
494
|
+
* @public
|
|
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.
|
|
498
|
+
* @example
|
|
499
|
+
* convertLeaf(parseToAst("// note\n1").comments[0]);
|
|
500
|
+
*/
|
|
501
|
+
function convertLeaf(element) {
|
|
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
|
+
});
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Deep-copies a jsonv AST node with ESLint positions: every node gets a 1-based `loc` and a
|
|
512
|
+
* `range`, and child nodes are converted recursively.
|
|
513
|
+
* @public
|
|
514
|
+
* @param {{type: string, loc: JsonvParserLocation, [key: string]: unknown}} node - A jsonv AST node.
|
|
515
|
+
* @returns {{type: string, [key: string]: unknown}} The converted copy.
|
|
516
|
+
* @example
|
|
517
|
+
* convertNode(parseToAst("{ a: 1 }").program.body);
|
|
518
|
+
*/
|
|
519
|
+
function convertNode(node) {
|
|
520
|
+
/** @type {{type: string, [key: string]: unknown}} */
|
|
521
|
+
const converted = convertLeaf(node);
|
|
522
|
+
for (const key of getChildKeys(node)) {
|
|
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]);
|
|
525
|
+
converted[key] = Array.isArray(child) ? child.map(convertNode) : convertNode(child);
|
|
526
|
+
}
|
|
527
|
+
return converted;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Builds the ESLint AST for a document from @cldmv/jsonv's `parseToAst()` result: the
|
|
532
|
+
* `Program` root (whose `body` is the root value) with positioned `comments` and `tokens`.
|
|
533
|
+
* @public
|
|
534
|
+
* @param {AstResult} astResult - The `parseToAst()` result.
|
|
535
|
+
* @returns {JsonvProgram} The `Program` node.
|
|
536
|
+
* @example
|
|
537
|
+
* buildProgram(parseToAst("// note\n{ a: 1 }"));
|
|
538
|
+
*/
|
|
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));
|
|
545
|
+
return {
|
|
546
|
+
...root,
|
|
547
|
+
comments: comments.map(convertLeaf),
|
|
548
|
+
tokens: tokens.map(convertLeaf)
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* SourceCode for a parsed `.jsonv` file, modelled on @eslint/json's `JSONSourceCode`:
|
|
554
|
+
* positions, lines and text helpers come from `TextSourceCodeBase`; this class adds AST
|
|
555
|
+
* traversal, parent lookup and inline configuration (`eslint-disable*` / `eslint-enable`
|
|
556
|
+
* directives and `/* eslint rule: ... *\/` config comments).
|
|
557
|
+
*
|
|
558
|
+
* @public
|
|
559
|
+
* @extends {TextSourceCodeBase<{LangOptions: JsonvLanguageOptions, RootNode: JsonvProgram, SyntaxElementWithLoc: JsonvSyntaxElement, ConfigNode: JsonvComment}>}
|
|
560
|
+
*/
|
|
561
|
+
class JsonvSourceCode extends TextSourceCodeBase {
|
|
562
|
+
/**
|
|
563
|
+
* Cached traversal steps.
|
|
564
|
+
* @type {VisitNodeStep[]|undefined}
|
|
565
|
+
*/
|
|
566
|
+
#steps;
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Parent of every non-root node.
|
|
570
|
+
* @type {WeakMap<object, JsonvNode>}
|
|
571
|
+
*/
|
|
572
|
+
#parents = new WeakMap();
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Cached inline configuration comments.
|
|
576
|
+
* @type {JsonvComment[]|undefined}
|
|
577
|
+
*/
|
|
578
|
+
#inlineConfigComments;
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Creates a SourceCode object.
|
|
582
|
+
* @param {Object} options - Options.
|
|
583
|
+
* @param {string} options.text - The source text.
|
|
584
|
+
* @param {JsonvProgram} options.ast - The `Program` node built by `jsonvLanguage#parse`.
|
|
585
|
+
*/
|
|
586
|
+
constructor({ text, ast }) {
|
|
587
|
+
super({ text, ast, lineEndingPattern: LINE_ENDING_PATTERN });
|
|
588
|
+
/** @type {JsonvComment[]} Comments in source order. */
|
|
589
|
+
this.comments = ast.comments;
|
|
590
|
+
/** @type {JsonvToken[]} Tokens in source order, excluding comments. */
|
|
591
|
+
this.tokens = ast.tokens;
|
|
592
|
+
/** @type {boolean} Whether the text starts with a byte order mark. */
|
|
593
|
+
this.hasBOM = text.charCodeAt(0) === 0xfeff;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Returns the source text split into lines (a fresh copy of `lines`).
|
|
598
|
+
* @public
|
|
599
|
+
* @returns {string[]} The lines.
|
|
600
|
+
*/
|
|
601
|
+
getLines() {
|
|
602
|
+
return [...this.lines];
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Walks the AST once, recording enter/exit steps and each node's parent.
|
|
607
|
+
* @returns {VisitNodeStep[]} The traversal steps.
|
|
608
|
+
*/
|
|
609
|
+
#ensureTraversal() {
|
|
610
|
+
if (!this.#steps) {
|
|
611
|
+
/** @type {VisitNodeStep[]} */
|
|
612
|
+
const steps = [];
|
|
613
|
+
/**
|
|
614
|
+
* @param {JsonvNode} node - The node to visit.
|
|
615
|
+
* @param {JsonvNode|undefined} parent - Its parent (`undefined` for the root).
|
|
616
|
+
*/
|
|
617
|
+
const visit = (node, parent) => {
|
|
618
|
+
if (parent) this.#parents.set(node, parent);
|
|
619
|
+
steps.push(new VisitNodeStep({ target: node, phase: 1, args: [node, parent] }));
|
|
620
|
+
for (const key of getChildKeys(node)) {
|
|
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]);
|
|
623
|
+
for (const item of Array.isArray(child) ? child : [child]) visit(item, node);
|
|
624
|
+
}
|
|
625
|
+
steps.push(new VisitNodeStep({ target: node, phase: 2, args: [node, parent] }));
|
|
626
|
+
};
|
|
627
|
+
visit(this.ast, undefined);
|
|
628
|
+
this.#steps = steps;
|
|
629
|
+
}
|
|
630
|
+
return this.#steps;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Returns the enter/exit visit steps for every node, depth first in source order.
|
|
635
|
+
* @public
|
|
636
|
+
* @returns {IterableIterator<VisitNodeStep>} The traversal steps.
|
|
637
|
+
*/
|
|
638
|
+
traverse() {
|
|
639
|
+
return this.#ensureTraversal().values();
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* Returns the parent of a node, or `undefined` for the `Program` root.
|
|
644
|
+
* @public
|
|
645
|
+
* @param {JsonvSyntaxElement} node - An AST node.
|
|
646
|
+
* @returns {JsonvNode|undefined} The parent node.
|
|
647
|
+
*/
|
|
648
|
+
getParent(node) {
|
|
649
|
+
this.#ensureTraversal();
|
|
650
|
+
return this.#parents.get(node);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Returns the comments that hold inline configuration (`eslint`, `eslint-enable`,
|
|
655
|
+
* `eslint-disable`, `eslint-disable-line`, `eslint-disable-next-line`).
|
|
656
|
+
* @public
|
|
657
|
+
* @returns {JsonvComment[]} The inline configuration comments.
|
|
658
|
+
*/
|
|
659
|
+
getInlineConfigNodes() {
|
|
660
|
+
if (!this.#inlineConfigComments) {
|
|
661
|
+
this.#inlineConfigComments = this.comments.filter((comment) => INLINE_CONFIG.test(comment.value));
|
|
662
|
+
}
|
|
663
|
+
return this.#inlineConfigComments;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Returns the `eslint-disable*` / `eslint-enable` directives, plus problems for malformed
|
|
668
|
+
* ones (an `eslint-disable-line` spanning several lines).
|
|
669
|
+
* @public
|
|
670
|
+
* @returns {{problems: FileProblem[], directives: Directive[]}} Directives and problems.
|
|
671
|
+
*/
|
|
672
|
+
getDisableDirectives() {
|
|
673
|
+
/** @type {FileProblem[]} */
|
|
674
|
+
const problems = [];
|
|
675
|
+
/** @type {Directive[]} */
|
|
676
|
+
const directives = [];
|
|
677
|
+
|
|
678
|
+
for (const comment of this.getInlineConfigNodes()) {
|
|
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
|
+
);
|
|
684
|
+
|
|
685
|
+
if (label === "eslint-disable-line" && comment.loc.start.line !== comment.loc.end.line) {
|
|
686
|
+
problems.push({ ruleId: null, message: `${label} comment should not span multiple lines.`, loc: comment.loc });
|
|
687
|
+
continue;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
if (label !== "eslint") {
|
|
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 }));
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
return { problems, directives };
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* Returns the rule configurations from `/* eslint rule: ... *\/` comments, plus problems for
|
|
703
|
+
* ones that fail to parse.
|
|
704
|
+
* @public
|
|
705
|
+
* @returns {{problems: FileProblem[], configs: Array<{config: {rules: RulesConfig}, loc: SourceLocation}>}} Configs and problems.
|
|
706
|
+
*/
|
|
707
|
+
applyInlineConfig() {
|
|
708
|
+
/** @type {FileProblem[]} */
|
|
709
|
+
const problems = [];
|
|
710
|
+
/** @type {Array<{config: {rules: RulesConfig}, loc: SourceLocation}>} */
|
|
711
|
+
const configs = [];
|
|
712
|
+
|
|
713
|
+
for (const comment of this.getInlineConfigNodes()) {
|
|
714
|
+
// Defined for the same reason as in getDisableDirectives().
|
|
715
|
+
const { label, value } = /** @type {NonNullable<ReturnType<ConfigCommentParser["parseDirective"]>>} */ (
|
|
716
|
+
commentParser.parseDirective(comment.value)
|
|
717
|
+
);
|
|
718
|
+
if (label !== "eslint") continue;
|
|
719
|
+
|
|
720
|
+
const parseResult = commentParser.parseJSONLikeConfig(value);
|
|
721
|
+
if (parseResult.ok) {
|
|
722
|
+
configs.push({ config: { rules: parseResult.config }, loc: comment.loc });
|
|
723
|
+
} else {
|
|
724
|
+
problems.push({ ruleId: null, message: parseResult.error.message, loc: comment.loc });
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
return { problems, configs };
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
80
732
|
/**
|
|
81
733
|
* Language definition for jsonv files
|
|
82
734
|
*
|
|
83
735
|
* @public
|
|
84
|
-
* @type {
|
|
736
|
+
* @type {JsonvLanguage}
|
|
85
737
|
*/
|
|
86
738
|
const jsonvLanguage = {
|
|
87
739
|
/** File type - must be "text" for text-based languages */
|
|
88
740
|
fileType: "text",
|
|
89
741
|
/** Line numbering starts at 1 */
|
|
90
742
|
lineStart: 1,
|
|
91
|
-
/** Column numbering starts at 1 */
|
|
743
|
+
/** Column numbering starts at 1 (node `loc` columns are 1-based) */
|
|
92
744
|
columnStart: 1,
|
|
93
745
|
/** Property name for node types */
|
|
94
746
|
nodeTypeKey: "type",
|
|
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),
|
|
95
752
|
|
|
96
753
|
/**
|
|
97
|
-
* Parse jsonv source code - ESLint
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
754
|
+
* Parse jsonv source code - ESLint Language API.
|
|
755
|
+
*
|
|
756
|
+
* The file is first parsed and evaluated with `parseWithOptions` — that is what reports
|
|
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.
|
|
762
|
+
*
|
|
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}
|
|
101
766
|
*/
|
|
102
767
|
parse(file, context) {
|
|
103
|
-
|
|
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} */
|
|
104
771
|
const options = context.languageOptions || {};
|
|
772
|
+
const parseOptions = {
|
|
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
|
|
779
|
+
};
|
|
105
780
|
|
|
106
781
|
try {
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
strictBigInt: options.strictBigInt !== undefined ? options.strictBigInt : false,
|
|
111
|
-
mode: "jsonv",
|
|
112
|
-
preserveComments: true,
|
|
113
|
-
tolerant: false
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
// Return parse result with minimal ESTree-compatible AST
|
|
782
|
+
const jsonvValue = parseWithOptions(text, { ...parseOptions, tolerant: false });
|
|
783
|
+
const ast = buildProgram(parseToAst(text, parseOptions));
|
|
784
|
+
|
|
117
785
|
return {
|
|
118
786
|
ok: true,
|
|
119
|
-
ast
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
sourceType: "module",
|
|
123
|
-
comments: [],
|
|
124
|
-
tokens: []
|
|
125
|
-
},
|
|
126
|
-
// Store the parsed jsonv value for potential use by rules
|
|
127
|
-
jsonvValue: result
|
|
787
|
+
ast,
|
|
788
|
+
// The evaluated document (references resolved) for callers of parse()
|
|
789
|
+
jsonvValue
|
|
128
790
|
};
|
|
129
791
|
} catch (error) {
|
|
130
|
-
// @cldmv/jsonv (>=1.0
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
// expects 1-based columns,
|
|
792
|
+
// @cldmv/jsonv (>=1.1.0) throws a `JsonvSyntaxError` (or its `LexerError` subclass) for
|
|
793
|
+
// every parser-level and lexer-level failure, and a `JsonvReferenceError` for an
|
|
794
|
+
// unresolved or circular internal reference; both carry the real `line`/`column`/`loc`.
|
|
795
|
+
// Their `column` is 0-based (matching the column embedded in jsonv's messages), while
|
|
796
|
+
// this language's `columnStart: 1` means ESLint expects 1-based columns, hence `+ 1`.
|
|
135
797
|
//
|
|
136
|
-
//
|
|
137
|
-
// position — fall back to line 1, column 1
|
|
138
|
-
const hasPosition = error instanceof JsonvSyntaxError;
|
|
798
|
+
// Anything else (e.g. a RangeError from exhausting the call stack on pathologically deep
|
|
799
|
+
// nesting) has no position — fall back to line 1, column 1.
|
|
800
|
+
const hasPosition = error instanceof JsonvSyntaxError || error instanceof JsonvReferenceError;
|
|
139
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} */
|
|
140
805
|
const errorInfo = hasPosition
|
|
141
|
-
? {
|
|
142
|
-
|
|
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 };
|
|
143
814
|
|
|
144
|
-
// Return error result
|
|
145
815
|
return {
|
|
146
816
|
ok: false,
|
|
147
817
|
errors: [errorInfo]
|
|
@@ -150,67 +820,16 @@ const jsonvLanguage = {
|
|
|
150
820
|
},
|
|
151
821
|
|
|
152
822
|
/**
|
|
153
|
-
* Create
|
|
154
|
-
* Required by ESLint 9 language API
|
|
823
|
+
* Create the SourceCode object ESLint hands to rules
|
|
155
824
|
*
|
|
156
825
|
* @public
|
|
157
|
-
* @param {
|
|
158
|
-
* @param {
|
|
159
|
-
* @
|
|
160
|
-
* @returns {Object} SourceCode object
|
|
826
|
+
* @param {File} file - The file object
|
|
827
|
+
* @param {JsonvOkParseResult} parseResult - The successful result from parse()
|
|
828
|
+
* @returns {JsonvSourceCode} SourceCode object
|
|
161
829
|
*/
|
|
162
|
-
createSourceCode(file, parseResult
|
|
830
|
+
createSourceCode(file, parseResult) {
|
|
163
831
|
const text = typeof file.body === "string" ? file.body : String(file.body || "");
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
return {
|
|
167
|
-
text,
|
|
168
|
-
ast,
|
|
169
|
-
lines: text.split(/\r\n|[\r\n\u2028\u2029]/g),
|
|
170
|
-
hasBOM: text.charCodeAt(0) === 0xfeff,
|
|
171
|
-
|
|
172
|
-
// Required methods
|
|
173
|
-
getLoc(node) {
|
|
174
|
-
return node.loc || { start: { line: 1, column: 0 }, end: { line: 1, column: 0 } };
|
|
175
|
-
},
|
|
176
|
-
|
|
177
|
-
getRange(node) {
|
|
178
|
-
return node.range || [0, 0];
|
|
179
|
-
},
|
|
180
|
-
|
|
181
|
-
traverse() {
|
|
182
|
-
// ESLint expects: { kind: 1 (VISIT), target: node, phase: 1|2, args: [] }
|
|
183
|
-
// kind: 1 = STEP_KIND_VISIT
|
|
184
|
-
// phase: 1 = enter, 2 = exit
|
|
185
|
-
return [
|
|
186
|
-
{
|
|
187
|
-
kind: 1,
|
|
188
|
-
target: ast,
|
|
189
|
-
phase: 1,
|
|
190
|
-
args: [ast]
|
|
191
|
-
},
|
|
192
|
-
{
|
|
193
|
-
kind: 1,
|
|
194
|
-
target: ast,
|
|
195
|
-
phase: 2,
|
|
196
|
-
args: [ast]
|
|
197
|
-
}
|
|
198
|
-
].values();
|
|
199
|
-
},
|
|
200
|
-
|
|
201
|
-
// Optional helper methods
|
|
202
|
-
getText(node, beforeCount, afterCount) {
|
|
203
|
-
if (node && node.range) {
|
|
204
|
-
const [start, end] = node.range;
|
|
205
|
-
return text.slice(Math.max(0, start - (beforeCount || 0)), Math.min(text.length, end + (afterCount || 0)));
|
|
206
|
-
}
|
|
207
|
-
return text;
|
|
208
|
-
},
|
|
209
|
-
|
|
210
|
-
getLines() {
|
|
211
|
-
return text.split(/\r\n|[\r\n\u2028\u2029]/g);
|
|
212
|
-
}
|
|
213
|
-
};
|
|
832
|
+
return new JsonvSourceCode({ text, ast: parseResult.ast });
|
|
214
833
|
},
|
|
215
834
|
|
|
216
835
|
/**
|
|
@@ -220,16 +839,21 @@ const jsonvLanguage = {
|
|
|
220
839
|
* reflects the effective options that will reach `parse()`.
|
|
221
840
|
*
|
|
222
841
|
* @public
|
|
223
|
-
* @param {
|
|
842
|
+
* @param {JsonvLanguageOptions} languageOptions - The (already-merged) language options to validate
|
|
224
843
|
* @returns {void}
|
|
225
844
|
* @throws {TypeError} When an unsupported key is present, or a supported key holds an invalid value
|
|
226
845
|
*/
|
|
227
846
|
validateLanguageOptions(languageOptions) {
|
|
228
847
|
const options = languageOptions ?? {};
|
|
229
848
|
|
|
849
|
+
const supported = `Supported options are: ${[...SUPPORTED_LANGUAGE_OPTIONS].join(", ")}.`;
|
|
850
|
+
|
|
230
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
|
+
}
|
|
231
855
|
if (!SUPPORTED_LANGUAGE_OPTIONS.has(key)) {
|
|
232
|
-
throw new TypeError(`Unknown language option "${key}".
|
|
856
|
+
throw new TypeError(`Unknown language option "${key}". ${supported}`);
|
|
233
857
|
}
|
|
234
858
|
}
|
|
235
859
|
|
|
@@ -239,8 +863,16 @@ const jsonvLanguage = {
|
|
|
239
863
|
);
|
|
240
864
|
}
|
|
241
865
|
|
|
242
|
-
if (options.
|
|
243
|
-
throw new TypeError(
|
|
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
|
+
}
|
|
244
876
|
}
|
|
245
877
|
},
|
|
246
878
|
|
|
@@ -251,12 +883,9 @@ const jsonvLanguage = {
|
|
|
251
883
|
* calling `validateLanguageOptions` and `parse()`.
|
|
252
884
|
*
|
|
253
885
|
* @public
|
|
254
|
-
* @type {
|
|
886
|
+
* @type {JsonvLanguageOptions}
|
|
255
887
|
*/
|
|
256
|
-
defaultLanguageOptions: {
|
|
257
|
-
year: 2025,
|
|
258
|
-
strictBigInt: false
|
|
259
|
-
}
|
|
888
|
+
defaultLanguageOptions: { ...DEFAULT_LANGUAGE_OPTIONS }
|
|
260
889
|
};
|
|
261
890
|
|
|
262
891
|
/**
|
|
@@ -267,7 +896,7 @@ const jsonvLanguage = {
|
|
|
267
896
|
* `language` such as `jsonv/jsonv` never consults it, so setting it here was inert.
|
|
268
897
|
*
|
|
269
898
|
* @public
|
|
270
|
-
* @
|
|
899
|
+
* @satisfies {ConfigObject}
|
|
271
900
|
*/
|
|
272
901
|
const recommendedConfig = {
|
|
273
902
|
rules: {
|
|
@@ -277,10 +906,11 @@ const recommendedConfig = {
|
|
|
277
906
|
};
|
|
278
907
|
|
|
279
908
|
/**
|
|
280
|
-
* 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`).
|
|
281
911
|
*
|
|
282
912
|
* @public
|
|
283
|
-
* @
|
|
913
|
+
* @satisfies {Plugin}
|
|
284
914
|
*/
|
|
285
915
|
const plugin = {
|
|
286
916
|
meta: readPackageMeta(),
|
|
@@ -301,3 +931,4 @@ const plugin = {
|
|
|
301
931
|
};
|
|
302
932
|
|
|
303
933
|
export default plugin;
|
|
934
|
+
export { JsonvSourceCode };
|