diff --git a/.github/workflows/check-ts.yml b/.github/workflows/check-ts.yml index 5ce44be..e35c0f8 100644 --- a/.github/workflows/check-ts.yml +++ b/.github/workflows/check-ts.yml @@ -3,7 +3,7 @@ on: name: Compile TypeScript to check for errors jobs: - deploy: + check-ts: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/src/configuration.ts b/src/configuration.ts index b4182f5..815d073 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -11,6 +11,9 @@ import {Rules} from "./rules"; import {logger} from "./logger"; import * as utils from "./utils"; import {ExtensionData} from "./extensionData"; +import {Settings} from "./interfaces/settings"; +import {LineComment, SingleLineCommentStyle} from "./interfaces/commentStyles"; +import {ExtensionMetaData} from "./interfaces/extensionMetaData"; export class Configuration { /************** @@ -74,6 +77,7 @@ export class Configuration { public constructor() { // Always output extension information to channel on activate. logger.debug(`Extension details:`, this.extensionData.getAll()); + logger.debug(`Extension Discovery Paths:`, this.extensionData.getAllExtensionDiscoveryPaths()); this.findAllLanguageConfigFilePaths(); this.setLanguageConfigDefinitions(); @@ -163,7 +167,7 @@ export class Configuration { "auto-comment-blocks.changeBladeMultiLineBlock", (textEditor, edit, args) => { this.handleChangeBladeMultiLineBlock(textEditor); - } + }, ); return [singleLineBlockCommand, changeBladeMultiLineBlockCommand]; } @@ -178,28 +182,34 @@ export class Configuration { * If `true`, it returns the comments, if `false` (default), it sets the comments to * the language directly. * + * @returns {vscode.CharacterPair | void} Returns the blade comments if `onStart` is `true`, otherwise nothing. + * */ - public setBladeComments(bladeOverrideComments: boolean, onStart: boolean = false): any { + public setBladeComments(bladeOverrideComments: boolean, onStart: boolean = false): vscode.CharacterPair | void { // Is enabled AND blade langId is NOT set as disabled... if (bladeOverrideComments === true && !this.isLangIdDisabled("blade")) { + const bladeComments: vscode.CharacterPair = ["{{--", "--}}"]; + if (onStart) { - return ["{{--", "--}}"]; + return bladeComments; } else { vscode.languages.setLanguageConfiguration("blade", { comments: { - blockComment: ["{{--", "--}}"], + blockComment: bladeComments, }, }); } } // Is disabled OR blade langId is set as disabled... else if (!bladeOverrideComments || this.isLangIdDisabled("blade")) { + const htmlComments: vscode.CharacterPair = [""]; + if (onStart) { - return [""]; + return htmlComments; } else { vscode.languages.setLanguageConfiguration("blade", { comments: { - blockComment: [""], + blockComment: htmlComments, }, }); } @@ -212,24 +222,22 @@ export class Configuration { * @returns {vscode.WorkspaceConfiguration} */ public getConfiguration(): vscode.WorkspaceConfiguration { - return vscode.workspace.getConfiguration(this.extensionData.get("name"), null); + return vscode.workspace.getConfiguration(this.extensionData.get("namespace"), null); } /** * Get value of the specified key from the extension's user configuration settings. * - * @param {string} key The key of the specific setting. - * - * @returns {T} Returns the value of the `key`. + * @param {K} key The key of the specific setting. * - * NOTE: Return is typed as `T`, which is a generic type that represents the type that is declared when called (as explained in this StackOverflow answer: https://stackoverflow.com/a/49622066/2358222) + * @returns {Settings[K]} Returns the value of the `key` with proper typing. * * @example ```ts - * this.getConfigurationValue("disabledLanguages"); + * this.getConfigurationValue("disabledLanguages"); // Returns string[] with full type safety * ``` */ - public getConfigurationValue(key: string): T { - return this.getConfiguration().get(key); + public getConfigurationValue(key: K): Settings[K] { + return this.getConfiguration().get(key); } /** @@ -237,13 +245,13 @@ export class Configuration { * * @param {string} key The key of the specific setting. - * @param {any} value The value to update the setting with. + * @param {unknown} value The value to update the setting with. * * @example ```ts * this.updateConfigurationValue("bladeOverrideComments", true); * ``` */ - public updateConfigurationValue(key: string, value: any) { + public updateConfigurationValue(key: string, value: unknown) { // .update(config key, new value, global) this.getConfiguration().update(key, value, true); } @@ -254,7 +262,7 @@ export class Configuration { * @returns {boolean} */ public isLangIdDisabled(langId: string): boolean { - return this.getConfigurationValue("disabledLanguages").includes(langId); + return this.getConfigurationValue("disabledLanguages").includes(langId); } /** @@ -264,7 +272,7 @@ export class Configuration { * @returns {boolean} */ private isLangIdMultiLineCommentOverridden(langId: string): boolean { - const overriddenList = this.getConfigurationValue("overrideDefaultLanguageMultiLineComments"); + const overriddenList = this.getConfigurationValue("overrideDefaultLanguageMultiLineComments"); return overriddenList.hasOwnProperty(langId); } @@ -276,7 +284,7 @@ export class Configuration { * @returns {string} */ private getOverriddenMultiLineComment(langId: string) { - const overriddenList = this.getConfigurationValue("overrideDefaultLanguageMultiLineComments"); + const overriddenList = this.getConfigurationValue("overrideDefaultLanguageMultiLineComments"); return overriddenList[langId]; } @@ -298,13 +306,13 @@ export class Configuration { * (built-in and 3rd party). */ private findAllLanguageConfigFilePaths() { - const extensions: any[] = []; + const extensions: ExtensionMetaData[] = []; // If running in WSL... if (isWsl) { // Get the Windows user and built-in extensions paths. - const windowsUserExtensionsPath = this.extensionData.get("WindowsUserExtensionsPathFromWsl"); - const windowsBuiltInExtensionsPath = this.extensionData.get("WindowsBuiltInExtensionsPathFromWsl"); + const windowsUserExtensionsPath = this.extensionData.getExtensionDiscoveryPath("WindowsUserExtensionsPathFromWsl"); + const windowsBuiltInExtensionsPath = this.extensionData.getExtensionDiscoveryPath("WindowsBuiltInExtensionsPathFromWsl"); // Read the paths and create arrays of the extensions. const windowsBuiltInExtensions = this.readExtensionsFromDirectory(windowsBuiltInExtensionsPath); @@ -314,8 +322,8 @@ export class Configuration { extensions.push(...windowsBuiltInExtensions, ...windowsUserExtensions); } - const userExtensionsPath = this.extensionData.get("userExtensionsPath"); - const builtInExtensionsPath = this.extensionData.get("builtInExtensionsPath"); + const userExtensionsPath = this.extensionData.getExtensionDiscoveryPath("userExtensionsPath"); + const builtInExtensionsPath = this.extensionData.getExtensionDiscoveryPath("builtInExtensionsPath"); // Read the paths and create arrays of the extensions. const userExtensions = this.readExtensionsFromDirectory(userExtensionsPath); @@ -470,12 +478,11 @@ export class Configuration { * * @param {string} extensionsPath The path where extensions are stored. * - * @returns {Array<{ id: string; extensionPath: string; packageJSON: IPackageJson }>} + * @returns {ExtensionMetaData[]} */ - private readExtensionsFromDirectory(extensionsPath: string): Array<{id: string; extensionPath: string; packageJSON: IPackageJson}> { + private readExtensionsFromDirectory(extensionsPath: string): ExtensionMetaData[] { // Create an array to hold the found extensions. - const foundExtensions: Array<{id: string; extensionPath: string; packageJSON: IPackageJson}> = []; - + const foundExtensions: ExtensionMetaData[] = []; fs.readdirSync(extensionsPath).forEach((extensionName) => { const extensionPath = path.join(extensionsPath, extensionName); @@ -486,18 +493,14 @@ export class Configuration { return; } - // Get the package.json file path. - const packageJSONPath = path.join(extensionPath, "package.json"); + const extensionData = new ExtensionData(extensionPath).getAll(); - // If the package.json file exists... - if (fs.existsSync(packageJSONPath)) { - const packageJSON: IPackageJson = utils.readJsonFile(packageJSONPath); - - const id = `${packageJSON.publisher}.${packageJSON.name}`; - - // Push the extension data object into the array. - foundExtensions.push({id, extensionPath, packageJSON}); + if (extensionData === null) { + return; } + + // Push the extension data object into the array. + foundExtensions.push(extensionData); } }); @@ -528,9 +531,9 @@ export class Configuration { * Set the multi-line comments language definitions. */ private setMultiLineCommentLanguageDefinitions() { - let langArray = []; + let langArray: string[] = []; - this.languageConfigs.forEach((config: any, langId: string) => { + this.languageConfigs.forEach((config: vscode.LanguageConfiguration, langId: string) => { // If the config object has own property of comments AND the comments key has // own property of blockComment... if (Object.hasOwn(config, "comments") && Object.hasOwn(config.comments, "blockComment")) { @@ -552,7 +555,7 @@ export class Configuration { // for sanity reasons. this.multiLineBlocksMap.set("supportedLanguages", langArray.sort()); - const multiLineStyleBlocksLangs = this.getConfigurationValue("multiLineStyleBlocks"); + const multiLineStyleBlocksLangs = this.getConfigurationValue("multiLineStyleBlocks"); // Empty the langArray to reuse it. langArray = []; @@ -574,20 +577,19 @@ export class Configuration { * Set the single-line comments language definitions. */ private setSingleLineCommentLanguageDefinitions() { - let style: string; const tempMap: Map = new Map(); - this.languageConfigs.forEach((config: any, langId: string) => { + this.languageConfigs.forEach((config: vscode.LanguageConfiguration, langId: string) => { // console.log(langId, config.comments.lineComment); - let style: string = ""; + let style: SingleLineCommentStyle | null = null; // If the config object has own property of comments AND the comments key has // own property of lineComment... if (Object.hasOwn(config, "comments") && Object.hasOwn(config.comments, "lineComment")) { - let lineComment = config.comments.lineComment; + let lineComment: LineComment = config.comments.lineComment as LineComment; // Line comments can be a string or an object with a "comment" key. // If the lineComment is an object, get the "comment" key value. - if (Object.hasOwn(lineComment, "comment")) { + if (typeof lineComment === "object" && lineComment !== null && Object.hasOwn(lineComment, "comment")) { lineComment = lineComment.comment; } @@ -599,15 +601,15 @@ export class Configuration { else if (lineComment === "#") { style = "#"; } - // If the lineComment includes a ";" (; or ;;)... - else if (lineComment.includes(";")) { + // If the lineComment is ";" or ";;"... + else if (lineComment === ";" || lineComment === ";;") { style = ";"; } - // If style is NOT an empty string, (i.e. not an unsupported single-line + // If style is NOT null, (i.e. not an unsupported single-line // comment like bat's @rem), AND // the langId isn't set as disabled... - if (style != "" && !this.isLangIdDisabled(langId)) { + if (style !== null && !this.isLangIdDisabled(langId)) { // Set the langId and it's style into the Map. tempMap.set(langId, style); } @@ -622,7 +624,7 @@ export class Configuration { tempMap.clear(); // Get user-customized langIds for the //-style and add to the map. - let customSlashLangs = this.getConfigurationValue("slashStyleBlocks"); + let customSlashLangs = this.getConfigurationValue("slashStyleBlocks"); for (let langId of customSlashLangs) { // If langId is exists (ie. not NULL or empty string) AND // the langId is longer than 0, AND @@ -633,7 +635,7 @@ export class Configuration { } // Get user-customized langIds for the #-style and add to the map. - let customHashLangs = this.getConfigurationValue("hashStyleBlocks"); + let customHashLangs = this.getConfigurationValue("hashStyleBlocks"); for (let langId of customHashLangs) { // If langId is exists (ie. not NULL or empty string) AND // the langId is longer than 0, AND @@ -644,7 +646,7 @@ export class Configuration { } // Get user-customized langIds for the ;-style and add to the map. - let customSemicolonLangs = this.getConfigurationValue("semicolonStyleBlocks"); + let customSemicolonLangs = this.getConfigurationValue("semicolonStyleBlocks"); for (let langId of customSemicolonLangs) { // If langId is exists (ie. not NULL or empty string) AND // the langId is longer than 0, AND @@ -697,7 +699,7 @@ export class Configuration { */ private setLanguageConfiguration(langId: string, multiLine?: boolean, singleLineStyle?: string): vscode.Disposable { const internalLangConfig: vscode.LanguageConfiguration = this.getLanguageConfig(langId); - const defaultMultiLineConfig: any = utils.readJsonFile(`${__dirname}/../../config/default-multi-line-config.json`); + const defaultMultiLineConfig: vscode.LanguageConfiguration = utils.readJsonFile(`${__dirname}/../../config/default-multi-line-config.json`); let langConfig = {...internalLangConfig}; @@ -721,12 +723,16 @@ export class Configuration { * Get the user settings/configuration and set the blade or html comments accordingly. */ if (langId === "blade") { - langConfig.comments.blockComment = this.setBladeComments(this.getConfigurationValue("bladeOverrideComments"), true); + const bladeComments = this.setBladeComments(this.getConfigurationValue("bladeOverrideComments"), true); + + // If bladeComments is has a value... + if (bladeComments) { + langConfig.comments.blockComment = bladeComments; + } } } - let isOnEnter = this.getConfigurationValue("singleLineBlockOnEnter"); - + let isOnEnter = this.getConfigurationValue("singleLineBlockOnEnter"); // Add the single-line onEnter rules to the langConfig. // // If isOnEnter is true AND singleLineStyle isn't false, i.e. is a string, @@ -906,7 +912,7 @@ export class Configuration { } var indentedNewLine = "\n" + line.text.substring(0, line.text.search(indentRegex)); - let isOnEnter = this.getConfigurationValue("singleLineBlockOnEnter"); + let isOnEnter = this.getConfigurationValue("singleLineBlockOnEnter"); if (!isOnEnter) { indentedNewLine += style + " "; } @@ -923,12 +929,12 @@ export class Configuration { */ private handleChangeBladeMultiLineBlock(textEditor: vscode.TextEditor) { let langId = textEditor.document.languageId; - const extensionName = this.extensionData.get("name"); + const extensionName = this.extensionData.get("namespace"); // Only carry out function if languageId is blade. if (langId === "blade" && !this.isLangIdDisabled(langId)) { // Read current value - let isOverridden = this.getConfigurationValue("bladeOverrideComments"); + let isOverridden = this.getConfigurationValue("bladeOverrideComments"); if (isOverridden === false) { // Update to true @@ -938,7 +944,7 @@ export class Configuration { this.updateConfigurationValue("bladeOverrideComments", false); } // Read new value - let bladeOverrideComments = this.getConfigurationValue("bladeOverrideComments"); + let bladeOverrideComments = this.getConfigurationValue("bladeOverrideComments"); // Set the comments for blade language. this.setBladeComments(bladeOverrideComments); @@ -948,7 +954,7 @@ export class Configuration { else if (langId == "blade" && this.isLangIdDisabled(langId)) { vscode.window.showInformationMessage( `Blade is set as disabled in the "${extensionName}.disabledLanguages" setting. The "${extensionName}.bladeOverrideComments" setting will have no affect.`, - "OK" + "OK", ); // Set the comments for blade language. @@ -962,25 +968,25 @@ export class Configuration { private logDebugInfo() { // The path to the built-in extensions. The env variable changes when on WSL. // So we can use it for both Windows and WSL. - const builtInExtensionsPath = this.extensionData.get("builtInExtensionsPath"); + const builtInExtensionsPath = this.extensionData.getExtensionDiscoveryPath("builtInExtensionsPath"); let extensionsPaths = {}; if (isWsl) { // Get the Windows user and built-in extensions paths. - const windowsUserExtensionsPath = this.extensionData.get("WindowsUserExtensionsPathFromWsl"); - const windowsBuiltInExtensionsPath = this.extensionData.get("WindowsBuiltInExtensionsPathFromWsl"); + const windowsUserExtensionsPath = this.extensionData.getExtensionDiscoveryPath("WindowsUserExtensionsPathFromWsl"); + const windowsBuiltInExtensionsPath = this.extensionData.getExtensionDiscoveryPath("WindowsBuiltInExtensionsPathFromWsl"); extensionsPaths = { "Windows-installed Built-in Extensions Path": windowsBuiltInExtensionsPath, "Windows-installed User Extensions Path": windowsUserExtensionsPath, "WSL-installed Built-in Extensions Path": builtInExtensionsPath, - "WSL-installed User Extensions Path": this.extensionData.get("userExtensionsPath"), + "WSL-installed User Extensions Path": this.extensionData.getExtensionDiscoveryPath("userExtensionsPath"), }; } else { extensionsPaths = { "Built-in Extensions Path": builtInExtensionsPath, - "User Extensions Path": this.extensionData.get("userExtensionsPath"), + "User Extensions Path": this.extensionData.getExtensionDiscoveryPath("userExtensionsPath"), }; } diff --git a/src/extension.ts b/src/extension.ts index 4d24398..9f95d44 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -18,11 +18,11 @@ export function activate(context: vscode.ExtensionContext) { disposables.push(...configureCommentBlocksDisposable, ...registerCommandsDisposable); - const extensionName = extensionData.get("name"); + const extensionName = extensionData.get("namespace"); const extensionDisplayName = extensionData.get("displayName"); - let disabledLangConfig: string[] = configuration.getConfigurationValue("disabledLanguages"); + let disabledLangConfig: string[] = configuration.getConfigurationValue("disabledLanguages"); if (disabledLangConfig.length > 0) { vscode.window.showInformationMessage(`${disabledLangConfig.join(", ")} languages are disabled for ${extensionDisplayName}.`); @@ -41,7 +41,7 @@ export function activate(context: vscode.ExtensionContext) { // If the affected setting is bladeOverrideComments... if (event.affectsConfiguration(`${extensionName}.bladeOverrideComments`)) { // Get the setting. - let bladeOverrideComments: boolean = configuration.getConfigurationValue("bladeOverrideComments"); + let bladeOverrideComments: boolean = configuration.getConfigurationValue("bladeOverrideComments"); configuration.setBladeComments(bladeOverrideComments); diff --git a/src/extensionData.ts b/src/extensionData.ts index 473513a..8d7fd63 100644 --- a/src/extensionData.ts +++ b/src/extensionData.ts @@ -4,14 +4,29 @@ import isWsl from "is-wsl"; import {IPackageJson} from "package-json-type"; import {readJsonFile} from "./utils"; +import {ExtensionMetaData, ExtensionPaths, ExtensionMetaDataValue} from "./interfaces/extensionMetaData"; export class ExtensionData { /** - * This extension details in the form of a key:value Map object. + * Extension data in the form of a key:value Map object. * - * @type {Map} + * @type {Map} */ - private extensionData = new Map(); + private extensionData = new Map(); + + /** + * Extension discovery paths in the form of a key:value Map object. + * + * @type {Map} + */ + private extensionDiscoveryPaths = new Map(); + + /** + * The absolute path of the requested extension. + * + * @type {string} + */ + private readonly extensionPath: string; /** * The package.json data for this extension. @@ -20,93 +35,115 @@ export class ExtensionData { */ private packageJsonData: IPackageJson; - public constructor() { + public constructor(extensionPath: string | null = null) { + // Set the path if provided, otherwise default to this extension's path. + this.extensionPath = extensionPath ?? path.join(__dirname, "../../"); + this.packageJsonData = this.getExtensionPackageJsonData(); - this.setExtensionData(); + + // Only proceed with extension data setup if packageJsonData is NOT null. + if (this.packageJsonData !== null) { + this.setExtensionData(); + } + + this.setExtensionDiscoveryPaths(); } /** * Get the names, id, and version of this extension from package.json. * - * @returns {IPackageJson} The package.json data for this extension, with extra custom keys. + * @returns {IPackageJson | null} The package.json data for this extension, with extra custom keys. + */ + private getExtensionPackageJsonData(): IPackageJson | null { + // Get the package.json file path. + const packageJSONPath = path.join(this.extensionPath, "package.json"); + return readJsonFile(packageJSONPath, false); + } + + /** + * Set the extension data into the extensionData Map. */ - private getExtensionPackageJsonData(): IPackageJson { - const extensionPath = path.join(__dirname, "../../"); + private setExtensionData() { + // Create the extension ID (publisher.name). + const id = `${this.packageJsonData.publisher}.${this.packageJsonData.name}`; + + // Set each key-value pair directly into the Map + this.extensionData.set("id", id); + this.extensionData.set("name", this.packageJsonData.name); + + // Only set the namespace if it dealing with this extension. + if (this.packageJsonData.name === "automatic-comment-blocks") { + // The configuration settings namespace is a shortened version of the extension name. + // We just need to replace "automatic" with "auto" in the name. + const settingsNamespace: string = this.packageJsonData.name.replace("automatic", "auto"); + + this.extensionData.set("namespace", settingsNamespace); + } - const packageJSON: IPackageJson = readJsonFile(path.join(extensionPath, "package.json")); + this.extensionData.set("displayName", this.packageJsonData.displayName); + this.extensionData.set("version", this.packageJsonData.version); + this.extensionData.set("extensionPath", this.extensionPath); + this.extensionData.set("packageJSON", this.packageJsonData); + } - // Set the id (publisher.name) into the packageJSON object as a new `id` key. - packageJSON.id = `${packageJSON.publisher}.${packageJSON.name}`; - packageJSON.extensionPath = extensionPath; + private setExtensionDiscoveryPaths() { + // The path to the user extensions. + const userExtensionsPath = isWsl ? path.join(vscode.env.appRoot, "../../", "extensions") : path.join(this.extensionPath, "../"); - // The configuration settings namespace is a shortened version of the extension name. - // We just need to replace "automatic" with "auto" in the name. - const settingsNamespace: string = packageJSON.name.replace("automatic", "auto"); - // Set the namespace to the packageJSON `configuration` object as a new `namespace` key. - packageJSON.contributes.configuration.namespace = settingsNamespace; + this.extensionDiscoveryPaths.set("userExtensionsPath", userExtensionsPath); + // The path to the built-in extensions. + // This env variable changes when on WSL to it's WSL-built-in extensions path. + this.extensionDiscoveryPaths.set("builtInExtensionsPath", path.join(vscode.env.appRoot, "extensions")); - return packageJSON; + // Only set these if running in WSL + if (isWsl) { + this.extensionDiscoveryPaths.set("WindowsUserExtensionsPathFromWsl", path.dirname(process.env.VSCODE_WSL_EXT_LOCATION!)); + this.extensionDiscoveryPaths.set("WindowsBuiltInExtensionsPathFromWsl", path.join(process.env.VSCODE_CWD!, "resources/app/extensions")); + } } /** - * Set the extension data into the extensionData Map. + * Get the extension's data by a specified key. + * + * @param {K} key The key of the extension detail to get. + * + * @returns {ExtensionMetaData[K] | undefined} The value of the extension detail, or undefined if the key does not exist. */ - private setExtensionData() { - // Set all entries in the extensionData Map. - Object.entries(this.createExtensionData()).forEach(([key, value]) => { - this.extensionData.set(key, value); - }); + public get(key: K): ExtensionMetaData[K] | undefined { + return this.extensionData.get(key) as ExtensionMetaData[K] | undefined; } /** - * Create the extension data object for the extensionData Map. - * It also helps for type inference intellisense in the get method. + * Get all extension data as a plain object. * - * @returns The extension data object with keys and values. + * @returns {ExtensionMetaData} A plain object containing all extension details. */ - private createExtensionData() { - // The path to the user extensions. - const userExtensionsPath = isWsl - ? path.join(vscode.env.appRoot, "../../", "extensions") - : path.join(this.packageJsonData.extensionPath, "../"); - - // Set the keys and values for the Map. - // The keys will also be used for type inference in VSCode intellisense. - return { - id: this.packageJsonData.id, - name: this.packageJsonData.contributes.configuration.namespace, - displayName: this.packageJsonData.displayName, - version: this.packageJsonData.version, - userExtensionsPath: userExtensionsPath, - // The path to the built-in extensions. - // This env variable changes when on WSL to it's WSL-built-in extensions path. - builtInExtensionsPath: path.join(vscode.env.appRoot, "extensions"), - - // Only set these if running in WSL. - ...(isWsl && { - WindowsUserExtensionsPathFromWsl: path.dirname(process.env.VSCODE_WSL_EXT_LOCATION!), - WindowsBuiltInExtensionsPathFromWsl: path.join(process.env.VSCODE_CWD!, "resources/app/extensions"), - }), - } as const; + public getAll(): ExtensionMetaData | null { + // If no data, return null + if (this.extensionData.size === 0) { + return null; + } + + return Object.fromEntries(this.extensionData) as unknown as ExtensionMetaData; } /** - * Get the extension's data by a specified key. + * Get the extension discovery paths by a specified key. * - * @param {K} key The key of the extension detail to get. + * @param {K} key The key of the specific path to get. * - * @returns {ReturnType[K] | undefined} The value of the extension detail, or undefined if the key does not exist. + * @returns {ExtensionPaths[K] | undefined} The value of the extension detail, or undefined if the key does not exist. */ - public get>(key: K): ReturnType[K] | undefined { - return this.extensionData.get(key) as ReturnType[K] | undefined; + public getExtensionDiscoveryPath(key: K): ExtensionPaths[K] | undefined { + return this.extensionDiscoveryPaths.get(key) as ExtensionPaths[K] | undefined; } /** - * Get all extension data. + * Get all extension discovery paths. * - * @returns {ReadonlyMap} A read-only Map containing all extension details. + * @returns {ReadonlyMap} A read-only Map containing all extension discovery paths. */ - public getAll(): ReadonlyMap { - return this.extensionData; + public getAllExtensionDiscoveryPaths(): ReadonlyMap { + return this.extensionDiscoveryPaths; } } diff --git a/src/interfaces/commentStyles.ts b/src/interfaces/commentStyles.ts new file mode 100644 index 0000000..eb9f0fd --- /dev/null +++ b/src/interfaces/commentStyles.ts @@ -0,0 +1,33 @@ +export type SingleLineCommentStyle = "//" | "#" | ";"; + +/** + * Line Comments + * + * Taken directly from VScode's commit in June 2025 that changed the line comment config. + * https://github.com/microsoft/vscode/commit/d9145a291dcef0bad3ace81a3d55727ca294c122#diff-0dfa7db579eface8250affb76bc88717725a121401d4d8598bc36b92b0b6ef62 + * + * The @types/vscode package does not yet have these changes. + * So until they're added, we define them manually. + */ + +/** + * The line comment token, like `// this is a comment`. + * Can be a string, an object with comment and optional noIndent properties, or null. + */ +export type LineComment = string | LineCommentConfig | null; + +/** + * Configuration for line comments. + */ +export interface LineCommentConfig { + /** + * The line comment token, like `//` + */ + comment: string; + + /** + * Whether the comment token should not be indented and placed at the first column. + * Defaults to false. + */ + noIndent?: boolean; +} diff --git a/src/interfaces/extensionMetaData.ts b/src/interfaces/extensionMetaData.ts new file mode 100644 index 0000000..646ec81 --- /dev/null +++ b/src/interfaces/extensionMetaData.ts @@ -0,0 +1,77 @@ +import {IPackageJson} from "package-json-type"; + +// Utility types for cleaner Map typing +export type ExtensionMetaDataValue = ExtensionMetaData[keyof ExtensionMetaData]; + +/** + * Extension metadata for a VSCode extension + */ +export interface ExtensionMetaData { + /** + * The unique ID in the form of `publisher.name`. + */ + id: string; + + /** + * The name. + * Directly from package.json "name" key. + */ + name: string; + + /** + * The namespace for this extension's configuration settings, + * which is a slightly shorter version of the name. + */ + namespace?: string; + + /** + * The display name. + * Directly from package.json "displayName" key. + */ + displayName: string; + + /** + * The version. + * Directly from package.json "version" key. + */ + version: string; + + /** + * The absolute path to the extension. + */ + extensionPath: string; + + /** + * The full package.json data + */ + packageJSON: IPackageJson; +} + +/** + * Extension discovery paths configuration for this extension + */ +export interface ExtensionPaths { + /** + * The path to the user extensions. + */ + userExtensionsPath: string; + + /** + * The path to the built-in extensions. + */ + builtInExtensionsPath: string; + + /** + * The Windows path to the user extensions when running in WSL. + * + * Only set when running in WSL. + */ + WindowsUserExtensionsPathFromWsl?: string; + + /** + * The Windows path to the built-in extensions when running in WSL. + * + * Only set when running in WSL. + */ + WindowsBuiltInExtensionsPathFromWsl?: string; +} diff --git a/src/interfaces/settings.ts b/src/interfaces/settings.ts new file mode 100644 index 0000000..da080b7 --- /dev/null +++ b/src/interfaces/settings.ts @@ -0,0 +1,10 @@ +export interface Settings { + singleLineBlockOnEnter: boolean; + disabledLanguages: string[]; + slashStyleBlocks: string[]; + hashStyleBlocks: string[]; + semicolonStyleBlocks: string[]; + multiLineStyleBlocks: string[]; + overrideDefaultLanguageMultiLineComments: Record; + bladeOverrideComments: boolean; +} diff --git a/src/logger.ts b/src/logger.ts index 3d9c58e..6d91cbe 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -84,9 +84,9 @@ class Logger { * This is helpful for logging objects and arrays. * * @param {string} message The message to be logged. - * @param {unknown} data Extra data that is useful for debugging, like an object or array. + * @param {unknown} data [Optional] Extra data that is useful for debugging, like an object or array. */ - public debug(message: string, data: unknown): void { + public debug(message: string, data?: unknown): void { if (this.debugMode) { this.logMessage("DEBUG", message, data); } diff --git a/src/utils.ts b/src/utils.ts index ea4faba..fc296ae 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -7,11 +7,26 @@ import {window} from "vscode"; * Read the file and parse the JSON. * * @param {string} filepath The path of the file. + * @param {boolean} [throwOnFileMissing=true] Whether to throw an error if the file doesn't exist. + * If `false`, returns `null`. Default is `true`. * - * @returns {any} The JSON file content as an object. - * @throws Will throw an error if the JSON file cannot be parsed. + * @returns {any | null} The JSON file content as an object, or `null` if file doesn't exist and `throwOnFileMissing` is `false`. + * @throws Will throw an error if the JSON file cannot be parsed or if file doesn't exist and `throwOnFileMissing` is `true`. */ -export function readJsonFile(filepath: string): any { +export function readJsonFile(filepath: string, throwOnFileMissing: boolean = true): any | null { + // Check if file exists first. + // If file doesn't exist... + if (!fs.existsSync(filepath)) { + // If throwOnFileMissing param is true, throw an error. + if (throwOnFileMissing) { + const error = new Error(`JSON file not found: "${filepath}"`); + logger.error(error.stack); + throw error; + } + // Otherwise just return null. + return null; + } + const jsonErrors: jsonc.ParseError[] = []; const fileContent = fs @@ -31,7 +46,7 @@ export function readJsonFile(filepath: string): any { .showErrorMessage( `${errorMsg}. The extension cannot continue. Please check the "Auto Comment Blocks" Output Channel for errors.`, "OK", - "Open Output Channel" + "Open Output Channel", ) .then((selection) => { if (selection === "Open Output Channel") {