diff --git a/src/configuration.ts b/src/configuration.ts index 2746e74..b4182f5 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -8,7 +8,7 @@ import isWsl from "is-wsl"; import {IPackageJson} from "package-json-type"; import {Rules} from "./rules"; -import {Logger} from "./logger"; +import {logger} from "./logger"; import * as utils from "./utils"; import {ExtensionData} from "./extensionData"; @@ -17,13 +17,6 @@ export class Configuration { * Properties * **************/ - /** - * The Logger class instance for logging messages to the Output Channel. - * - * @type {Logger} - */ - private logger: Logger; - /** * This extension data class instance. * @type {ExtensionData} @@ -78,11 +71,9 @@ export class Configuration { * Methods * ***********/ - public constructor(logger: Logger) { - this.logger = logger; - + public constructor() { // Always output extension information to channel on activate. - this.logger.debug(`Extension details:`, this.extensionData.getAll()); + logger.debug(`Extension details:`, this.extensionData.getAll()); this.findAllLanguageConfigFilePaths(); this.setLanguageConfigDefinitions(); @@ -861,7 +852,7 @@ export class Configuration { this.normalizeOnEnterRules(langConfig.onEnterRules); } - this.logger.debug(`The language config for ${langId}:`, langConfig); + logger.debug(`The language config for ${langId}:`, langConfig); return vscode.languages.setLanguageConfiguration(langId, langConfig); } @@ -1004,16 +995,16 @@ export class Configuration { }, "Other System Env Variables": process.env, }; - this.logger.debug("Environment:", env); + logger.debug("Environment:", env); // Log the extension's user configuration settings. - this.logger.debug("Configuration settings:", this.getConfiguration()); + logger.debug("Configuration settings:", this.getConfiguration()); // Log the objects for debugging purposes. - this.logger.debug("The language config filepaths found are:", this.languageConfigFilePaths); - this.logger.debug("The language configs found are:", this.languageConfigs); - this.logger.debug("The supported languages for multi-line blocks:", utils.readJsonFile(this.multiLineLangDefinitionFilePath)); - this.logger.debug("The supported languages for single-line blocks:", utils.readJsonFile(this.singleLineLangDefinitionFilePath)); + logger.debug("The language config filepaths found are:", this.languageConfigFilePaths); + logger.debug("The language configs found are:", this.languageConfigs); + logger.debug("The supported languages for multi-line blocks:", utils.readJsonFile(this.multiLineLangDefinitionFilePath)); + logger.debug("The supported languages for single-line blocks:", utils.readJsonFile(this.singleLineLangDefinitionFilePath)); } /** diff --git a/src/extension.ts b/src/extension.ts index a5c2c2a..4d24398 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -3,13 +3,12 @@ import * as vscode from "vscode"; import {Configuration} from "./configuration"; -import {Logger} from "./logger"; +import {logger} from "./logger"; import {ExtensionData} from "./extensionData"; -const logger = new Logger(); const extensionData = new ExtensionData(); logger.setupOutputChannel(); -let configuration = new Configuration(logger); +let configuration = new Configuration(); const disposables: vscode.Disposable[] = []; diff --git a/src/logger.ts b/src/logger.ts index 3427a2d..3d9c58e 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -6,7 +6,7 @@ import {OutputChannel, window} from "vscode"; * * @class Logger */ -export class Logger { +class Logger { /************** * Properties * **************/ @@ -61,6 +61,15 @@ export class Logger { this.outputChannel.dispose(); } + /** + * Show the output channel to the user. + */ + public showChannel(): void { + if (this.outputChannel) { + this.outputChannel.show(); + } + } + /** * Sends a basic info log to the output channel. * @@ -163,3 +172,5 @@ export class Logger { return value; } } + +export const logger = new Logger(); diff --git a/src/utils.ts b/src/utils.ts index 1ffffeb..553b7af 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,15 +1,75 @@ import * as fs from "node:fs"; import * as jsonc from "jsonc-parser"; +import {logger} from "./logger"; +import {window} from "vscode"; /** * Read the file and parse the JSON. * * @param {string} filepath The path of the file. * - * @returns The file content. + * @returns {any} The JSON file content as an object. + * @throws Will throw an error if the JSON file cannot be parsed. */ export function readJsonFile(filepath: string): any { - return jsonc.parse(fs.readFileSync(filepath).toString()); + const jsonErrors: jsonc.ParseError[] = []; + + const fileContent = fs.readFileSync(filepath).toString(); + const jsonContents = jsonc.parse(fileContent, jsonErrors) ?? {}; + + if (jsonErrors.length > 0) { + const errorMessages = constructJsonParseErrorMsg(filepath, fileContent, jsonErrors); + const errorMsg = "Failed to parse a required JSON file"; + const error = new Error(`${errorMsg}: "${filepath}"\n\n\tParse Errors:\n\n${errorMessages}\n\tStack Trace:`); + + logger.error(error.stack); + + window + .showErrorMessage( + `${errorMsg}. The extension cannot continue. Please check the "Auto Comment Blocks" Output Channel for errors.`, + "OK", + "Open Output Channel" + ) + .then((selection) => { + if (selection === "Open Output Channel") { + logger.showChannel(); + } + }); + + throw error; + } + + return jsonContents; +} + +/** + * Construct detailed JSON parse error messages with file, line, and column information. + * + * @param {string} filepath The path of the file. + * @param {string} fileContent The content of the file. + * @param {jsonc.ParseError[]} jsonErrors The JSON parse errors. + * + * @returns {string} The constructed error message. + */ +function constructJsonParseErrorMsg(filepath: string, fileContent: string, jsonErrors: jsonc.ParseError[]): string { + return jsonErrors + .map((err, i) => { + // Get the error name from the numeric error code. + // The name is PascalCased, so we need to format it by adding spaces + // before capital letters for readability. + const errorName = jsonc + .printParseErrorCode(err.error) + .replace(/([A-Z])/g, " $1") + .trim(); + + // Calculate line and column numbers from the error offset. + const lineNumber = fileContent.substring(0, err.offset).split("\n").length; + const columnNumber = err.offset - fileContent.lastIndexOf("\n", err.offset - 1); + + // Return the formatted error message. + return `\tError ${i + 1} - ${errorName} at "${filepath}:${lineNumber}:${columnNumber}"\n`; + }) + .join("\n"); } /**