144 lines
5.1 KiB
JavaScript
144 lines
5.1 KiB
JavaScript
/**
|
|
* @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,
|
|
});
|
|
}
|
|
}
|