first commit
This commit is contained in:
+143
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* @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,
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user