UNPKG

@eslint/json

Version:

JSON linting plugin for ESLint

144 lines (143 loc) 5.19 kB
/** * @fileoverview The JSONLanguage class. * @author Nicholas C. Zakas */ //------------------------------------------------------------------------------ // Imports //------------------------------------------------------------------------------ import { parse } from "@humanwhocodes/momoa"; import { JSONSourceCode } from "./json-source-code.js"; import { visitorKeys } from "@humanwhocodes/momoa"; //----------------------------------------------------------------------------- // Types //----------------------------------------------------------------------------- /** * @import { DocumentNode, AnyNode } from "@humanwhocodes/momoa"; * @import { Language, OkParseResult, ParseResult, File } from "@eslint/core"; * @typedef {OkParseResult<DocumentNode>} JSONOkParseResult * @typedef {ParseResult<DocumentNode>} JSONParseResult * @typedef {Object} JSONLanguageOptions * @property {boolean} [allowTrailingCommas] Whether to allow trailing commas in JSONC mode. */ //----------------------------------------------------------------------------- // Exports //----------------------------------------------------------------------------- /** * JSON Language Object * @implements {Language<{ LangOptions: JSONLanguageOptions; Code: JSONSourceCode; RootNode: DocumentNode; Node: AnyNode }>} */ export class JSONLanguage { /** * The type of file to read. * @type {"text"} */ fileType = "text"; /** * The line number at which the parser starts counting. * @type {0|1} */ lineStart = 1; /** * The column number at which the parser starts counting. * @type {0|1} */ columnStart = 1; /** * The name of the key that holds the type of the node. * @type {string} */ nodeTypeKey = "type"; /** * The parser mode. * @type {"json"|"jsonc"|"json5"} */ #mode = "json"; /** * The visitor keys. * @type {Record<string, string[]>} */ visitorKeys = Object.fromEntries([...visitorKeys]); /** * Creates a new instance. * @param {Object} options The options to use for this instance. * @param {"json"|"jsonc"|"json5"} options.mode The parser mode to use. */ constructor({ mode }) { this.#mode = mode; } /** * Validates the language options. * @param {JSONLanguageOptions} languageOptions The language options to validate. * @returns {void} * @throws {Error} When the language options are invalid. */ validateLanguageOptions(languageOptions) { if (languageOptions.allowTrailingCommas !== undefined) { if (typeof languageOptions.allowTrailingCommas !== "boolean") { throw new Error("allowTrailingCommas must be a boolean if provided."); } // we know that allowTrailingCommas is a boolean here // only allowed in JSONC mode if (this.#mode !== "jsonc") { throw new Error("allowTrailingCommas option is only available in JSONC."); } } } /** * Parses the given file into an AST. * @param {File} file The virtual file to parse. * @param {{languageOptions: JSONLanguageOptions}} context The options to use for parsing. * @returns {JSONParseResult} The result of parsing. */ parse(file, context) { // Note: BOM already removed const text = /** @type {string} */ (file.body); const allowTrailingCommas = context?.languageOptions?.allowTrailingCommas; /* * Check for parsing errors first. If there's a parsing error, nothing * else can happen. However, a parsing error does not throw an error * from this method - it's just considered a fatal error message, a * problem that ESLint identified just like any other. */ try { const root = parse(text, { mode: this.#mode, ranges: true, tokens: true, allowTrailingCommas, }); return { ok: true, ast: root, }; } catch (ex) { // error messages end with (line:column) so we strip that off for ESLint const message = ex.message .slice(0, ex.message.lastIndexOf("(")) .trim(); return { ok: false, errors: [ { ...ex, message, }, ], }; } } /* eslint-disable class-methods-use-this -- Required to complete interface. */ /** * Creates a new `JSONSourceCode` object from the given information. * @param {File} file The virtual file to create a `JSONSourceCode` object from. * @param {JSONOkParseResult} parseResult The result returned from `parse()`. * @returns {JSONSourceCode} The new `JSONSourceCode` object. */ createSourceCode(file, parseResult) { return new JSONSourceCode({ text: /** @type {string} */ (file.body), ast: parseResult.ast, }); } }