From dd0141856d1990096f0efa514710bf02f3f93ee4 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Sun, 11 Jan 2026 04:08:29 +0000 Subject: [PATCH 01/26] feat: add `Settings` interface to allow intellisense & full type safety. - Added new `Settings` interface to define all the configuration settings and their typings. - Refactored the `Configuration::getConfigurationValue` method to use the new `Settings` interface and automatically get the correct typing of the specified key from the interface. This also enables intellisense support for the settings keys. It also removes the requirement of hardcoding the type via generic typing when using the method. ie. `getConfigurationValue()`. - Removed all generic typings from the `getConfigurationValue` method calls. --- src/configuration.ts | 38 ++++++++++++++++++-------------------- src/interfaces/settings.ts | 10 ++++++++++ 2 files changed, 28 insertions(+), 20 deletions(-) create mode 100644 src/interfaces/settings.ts diff --git a/src/configuration.ts b/src/configuration.ts index b4182f5..86ff0cf 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -11,6 +11,7 @@ import {Rules} from "./rules"; import {logger} from "./logger"; import * as utils from "./utils"; import {ExtensionData} from "./extensionData"; +import {Settings, SupportUnsupportedLanguages} from "./interfaces/settings"; export class Configuration { /************** @@ -218,18 +219,16 @@ export class Configuration { /** * 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); } /** @@ -254,7 +253,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 +263,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 +275,7 @@ export class Configuration { * @returns {string} */ private getOverriddenMultiLineComment(langId: string) { - const overriddenList = this.getConfigurationValue("overrideDefaultLanguageMultiLineComments"); + const overriddenList = this.getConfigurationValue("overrideDefaultLanguageMultiLineComments"); return overriddenList[langId]; } @@ -552,7 +551,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 = []; @@ -622,7 +621,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 +632,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 +643,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 @@ -721,12 +720,11 @@ 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); + langConfig.comments.blockComment = this.setBladeComments(this.getConfigurationValue("bladeOverrideComments"), true); } } - 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 +904,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 + " "; } @@ -928,7 +926,7 @@ export class Configuration { // 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 +936,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); diff --git a/src/interfaces/settings.ts b/src/interfaces/settings.ts new file mode 100644 index 0000000..8b0cb53 --- /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: string[]; + bladeOverrideComments: boolean; +} From d3e9bac51708e11e692a8495d3172f953ab85fbf Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Sun, 11 Jan 2026 04:16:33 +0000 Subject: [PATCH 02/26] fix: `overrideDefaultLanguageMultiLineComments` setting type. - Fixed the typing of `overrideDefaultLanguageMultiLineComments` setting to use the `Record` utility type. The setting is defined in the package.json as an object, but it has always been wrongly typed as an array (`string[]`). The utility type `Record` defines the types for the keys and values, assuming all keys and all values will be of the same typing, ie stringed keys and numeric values, in our case though it's stringed values. It's an easier way of typing it as `{ [ key: string ]: string }`. --- src/interfaces/settings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interfaces/settings.ts b/src/interfaces/settings.ts index 8b0cb53..da080b7 100644 --- a/src/interfaces/settings.ts +++ b/src/interfaces/settings.ts @@ -5,6 +5,6 @@ export interface Settings { hashStyleBlocks: string[]; semicolonStyleBlocks: string[]; multiLineStyleBlocks: string[]; - overrideDefaultLanguageMultiLineComments: string[]; + overrideDefaultLanguageMultiLineComments: Record; bladeOverrideComments: boolean; } From dce0661aa0b3f6a668fed1ef08dc1cc363ba412e Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Sun, 11 Jan 2026 04:27:03 +0000 Subject: [PATCH 03/26] fix: remove `SupportUnsupportedLanguages` interface import. - Removed the import for the `SupportUnsupportedLanguages` interface, as this isn't available until 1.2.0. This was wrongly added to this PR while adding it for the 1.2.0 version. --- src/configuration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/configuration.ts b/src/configuration.ts index 86ff0cf..5f19be0 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -11,7 +11,7 @@ import {Rules} from "./rules"; import {logger} from "./logger"; import * as utils from "./utils"; import {ExtensionData} from "./extensionData"; -import {Settings, SupportUnsupportedLanguages} from "./interfaces/settings"; +import {Settings} from "./interfaces/settings"; export class Configuration { /************** From 00db701e9fc0191170849ee217aa70608e26ead2 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Sun, 11 Jan 2026 04:30:51 +0000 Subject: [PATCH 04/26] fix: removed the generic typing in extension.ts - Removed the generic typing from the `Configuration::getConfigurationValue` method call in extension.ts file. --- src/extension.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 4d24398..9909b02 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -22,7 +22,7 @@ export function activate(context: vscode.ExtensionContext) { 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); From 16e481d6372454e4897c219439e279b20b697264 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Sun, 11 Jan 2026 04:35:34 +0000 Subject: [PATCH 05/26] ci: change job name from `deploy` to `check-ts` - Changed the job name in check-ts.yml from `deploy` to `check-ts`, as it's confusing seeing `deploy` on GitHub PR's and actions when it actually isn't the deploy workflow. This workflow only checks ts errors. --- .github/workflows/check-ts.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From b5d1bcbbcfffc1404348e95ac09c7bac6985c89b Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Sat, 17 Jan 2026 04:07:54 +0000 Subject: [PATCH 06/26] refactor: merge `createExtensionData` into `setExtensionData` method - Merge `ExtensionData::createExtensionData` method into `ExtensionData::setExtensionData` method for simplicity. We once had a unified method before until I split them into the 2 aforementioned methods in PR #12 (commit b0a40aa). Not sure why they were split, maybe because I thought too many `this.extensionData.set` was duplication, but it actually isn't. So we now merge them back. --- src/extensionData.ts | 44 ++++++++++++++------------------------------ 1 file changed, 14 insertions(+), 30 deletions(-) diff --git a/src/extensionData.ts b/src/extensionData.ts index 473513a..2d6ed4d 100644 --- a/src/extensionData.ts +++ b/src/extensionData.ts @@ -52,42 +52,26 @@ export class ExtensionData { * Set the extension data into the extensionData Map. */ private setExtensionData() { - // Set all entries in the extensionData Map. - Object.entries(this.createExtensionData()).forEach(([key, value]) => { - this.extensionData.set(key, value); - }); - } - - /** - * Create the extension data object for the extensionData Map. - * It also helps for type inference intellisense in the get method. - * - * @returns The extension data object with keys and values. - */ - 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"), + // Set each key-value pair directly into the Map + this.extensionData.set("id", this.packageJsonData.id); + this.extensionData.set("name", this.packageJsonData.contributes.configuration.namespace); + this.extensionData.set("displayName", this.packageJsonData.displayName); + this.extensionData.set("version", this.packageJsonData.version); + this.extensionData.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.extensionData.set("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; + // Only set these if running in WSL + if (isWsl) { + this.extensionData.set("WindowsUserExtensionsPathFromWsl", path.dirname(process.env.VSCODE_WSL_EXT_LOCATION!)); + this.extensionData.set("WindowsBuiltInExtensionsPathFromWsl", path.join(process.env.VSCODE_CWD!, "resources/app/extensions")); + } } /** From 83c51edbbbaefedc534c9ba4b3118e9bdd4550cc Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Sat, 17 Jan 2026 04:23:49 +0000 Subject: [PATCH 07/26] feat: add new `ExtensionMetaData` interface for better typings - Added new `ExtensionMetaData` interface for better typings and vscode intellisense. - Changed the `extensionData` property map to use typing of the new `ExtensionMetaData` interface. - Changed `get` and `getAll` methods to use the typing of `ExtensionMetaData` interface. This is instead of using return type of the old `createExtensionData` method, and still allows vscode intellisense of the data properties. It also is a better way to type the properties. --- src/extensionData.ts | 15 ++++++++------- src/interfaces/extensionMetaData.ts | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 7 deletions(-) create mode 100644 src/interfaces/extensionMetaData.ts diff --git a/src/extensionData.ts b/src/extensionData.ts index 2d6ed4d..1f8863f 100644 --- a/src/extensionData.ts +++ b/src/extensionData.ts @@ -4,14 +4,15 @@ import isWsl from "is-wsl"; import {IPackageJson} from "package-json-type"; import {readJsonFile} from "./utils"; +import {ExtensionMetaData} from "./interfaces/extensionMetaData"; export class ExtensionData { /** * This extension details in the form of a key:value Map object. * - * @type {Map} + * @type {Map} */ - private extensionData = new Map(); + private extensionData = new Map(); /** * The package.json data for this extension. @@ -79,18 +80,18 @@ export class ExtensionData { * * @param {K} key The key of the extension detail to get. * - * @returns {ReturnType[K] | undefined} The value of the extension detail, or undefined if the key does not exist. + * @returns {ExtensionMetaData[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 get(key: K): ExtensionMetaData[K] | undefined { + return this.extensionData.get(key) as ExtensionMetaData[K] | undefined; } /** * Get all extension data. * - * @returns {ReadonlyMap} A read-only Map containing all extension details. + * @returns {ReadonlyMap} A read-only Map containing all extension details. */ - public getAll(): ReadonlyMap { + public getAll(): ReadonlyMap { return this.extensionData; } } diff --git a/src/interfaces/extensionMetaData.ts b/src/interfaces/extensionMetaData.ts new file mode 100644 index 0000000..94c51b2 --- /dev/null +++ b/src/interfaces/extensionMetaData.ts @@ -0,0 +1,15 @@ +export interface ExtensionMetaData { + id: string; + name: string; + displayName: string; + version: string; + userExtensionsPath: string; + builtInExtensionsPath: string; + + /** + * Only set when running in WSL. + */ + + WindowsUserExtensionsPathFromWsl?: string; + WindowsBuiltInExtensionsPathFromWsl?: string; +} From 81f797b3baa0066ede35b90a4fafaaaeb44c8985 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Wed, 21 Jan 2026 05:30:23 +0000 Subject: [PATCH 08/26] refactor: `setBladeComments` method and fix typings. - Refactor `Configuration::setBladeComments` method to remove the duplicated blade and html comment arrays. We now only define the arrays once as a variable and use it in the return statement. - Fix `setBladeComments` method return type to vscode's `CharacterPair` type. Also typed the blade and html comments variables as `CharacterPair`. --- src/configuration.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/configuration.ts b/src/configuration.ts index 5f19be0..4b16e20 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -179,28 +179,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, }, }); } From 8af89da5550bdc7c19b6b5275f75d3c0707e55c2 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Wed, 21 Jan 2026 05:38:36 +0000 Subject: [PATCH 09/26] fix: parameter type `any` to `unknown` in `updateConfigurationValue` - Fixed the type of the `value` parameter of `Configuration::updateConfigurationValue` method to use `unknown` instead of `any`. This is because `any` type just disables TypeScript's error reporting, while `unknown` is a type-safe usage for "anything". Using `unknown` type is generally preferred over `any`. --- src/configuration.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/configuration.ts b/src/configuration.ts index 4b16e20..d83e9ee 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -242,13 +242,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); } From 21bfecbd9a3ccf5c827294d734efdd0da37eaf69 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Wed, 21 Jan 2026 05:47:05 +0000 Subject: [PATCH 10/26] fix: language config types to vscode's `LanguageConfiguration` - Fixed the type for the `config` param to vscode's `LanguageConfiguration` instead of `any` in both `setMultiLineCommentLanguageDefinitions` and `setSingleLineCommentLanguageDefinitions` methods. - Typed the `langArray` variable in `setMultiLineCommentLanguageDefinitions` method as `string[]`. - Fixed `defaultMultiLineConfig` variable's type to vscode's `LanguageConfiguration` instead of `any` in `setLanguageConfiguration`. --- src/configuration.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/configuration.ts b/src/configuration.ts index d83e9ee..405a398 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -533,9 +533,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")) { @@ -581,7 +581,7 @@ export class Configuration { 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 = ""; @@ -702,7 +702,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}; From 2821c589406698e6ff0ab57cd6e50cc04282d7d1 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Wed, 21 Jan 2026 05:57:23 +0000 Subject: [PATCH 11/26] refactor: the `lineComment` style check to use strict equality checks Previously, the `lineComment` style check in `setSingleLineCommentLanguageDefinitions` used the string `includes` method to determine if the style "included" a `;` because they could also be `;;`. But since those are the only 2 strings that it can be, it makes more sense to just do a strict equality check for both instead. - Refactored `lineComment` style conditional to remove the `includes` method and use strict equality checks for `;` OR `;;`. --- src/configuration.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/configuration.ts b/src/configuration.ts index 405a398..c792577 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -604,8 +604,8 @@ 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 = ";"; } From f2504bc546c04ae3a1cb355d0d87029187d0a6d3 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Wed, 21 Jan 2026 07:31:57 +0000 Subject: [PATCH 12/26] fix: types for line comments in `setSingleLineCommentLanguageDefinitions` - Added a new custom type, `SingleLineCommentStyle`, in the new `commentStyles` file to define 3 union strings for the single line styles. - Fix `style` variable type in `setSingleLineCommentLanguageDefinitions` to the union type of the new `SingleLineCommentStyle` type and `null`. This gives more info to ts for what `style` should be, either 1 of the 3 strings or `null`. By typing the 3 style options ("//", "#", ";"), we are also enabling vscode's intellisense to auto complete the value. This helps for future development. - Changed the default value for `style` variable to be `null` instead of an empty string. This is so that we don't need to type the new `SingleLineCommentStyle` as `""` for empty string which looks weird as a union type. It also provides a better understanding of why the variable might be null, ie. an unsupported style. Also changed the style empty string checks to null checks. - Fixed typing for the language config `lineComment` property. The property used to be solely a string, but as of June 2025, VScode changed it to be a string, object or null, in the PR https://github.com/microsoft/vscode/pull/243283, specifically commit microsoft/vscode@d9145a2. However, this has not been updated in the npm package `@types/vscode` by DefinitelyTyped, so ts still thinks it should be a string only. So when we try to access the `comment` property in the object, then ts spits out errors. While I created an issue for it (https://github.com/DefinitelyTyped/DefinitelyTyped/issues/74372), we need need to manually add the changes until @types/vscode is changed accordingly. - Added new `LineComment` custom type in the new commentStyles file to be a union of `string`, `LineCommentConfig`, and `null`. This typing is inline with vscode's change, but as a custom type instead of inside an interface. Also typed the `lineComment` variable as this new type and cast the `config.comments.lineComment` as this type too, to avoid any ts errors. - Added new `LineCommentConfig` interface in the new commentStyles file, to provide info for the object options. This is inline with vscode's change. - Changed the conditional for the `lineComment` as an object to first check it is of type object and not null before checking if `comment` property exists in the object. This prevents ts erroring. --- src/configuration.ts | 10 +++++----- src/interfaces/commentStyles.ts | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 src/interfaces/commentStyles.ts diff --git a/src/configuration.ts b/src/configuration.ts index c792577..60ffbf8 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -583,16 +583,16 @@ export class Configuration { const tempMap: Map = new Map(); 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; } @@ -609,10 +609,10 @@ export class Configuration { 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); } 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; +} From 5965287b41e2c2f413f376b93022df1e75190117 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Wed, 21 Jan 2026 08:42:58 +0000 Subject: [PATCH 13/26] fix: ts type error while trying to set blade comments. - Fixed ts type error `Type 'void | CharacterPair' is not assignable to type 'CharacterPair'` while trying to set blade comments in `setLanguageConfiguration` method. This probably happens because the `setBladeComments` method is returning void and being set directly to the lang config `blockComment` property which doesn't work. So to avoid this, we set the blade comments into a new variable instead and then only set the `blockComment` property if the conditional check of `bladeComments` variable is truthy, ie. has any value except null,undefined, etc. --- src/configuration.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/configuration.ts b/src/configuration.ts index 60ffbf8..20769d0 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -726,7 +726,12 @@ 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; + } } } From 0b776fcfaf3b5f04112cc609721114f56f286050 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Wed, 21 Jan 2026 08:51:19 +0000 Subject: [PATCH 14/26] remove: the unused duplicate `style` variable. - Removed the unused duplicate `style` variable in `setSingleLineCommentLanguageDefinitions` method. --- src/configuration.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/configuration.ts b/src/configuration.ts index 20769d0..b3bb484 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -579,7 +579,6 @@ export class Configuration { * Set the single-line comments language definitions. */ private setSingleLineCommentLanguageDefinitions() { - let style: string; const tempMap: Map = new Map(); this.languageConfigs.forEach((config: vscode.LanguageConfiguration, langId: string) => { // console.log(langId, config.comments.lineComment); From 1365c469c4b557ca17a68a5db970b3726483d693 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Wed, 21 Jan 2026 08:54:25 +0000 Subject: [PATCH 15/26] fix: the missing imports for `LineComment` & `SingleLineCommentStyle` --- src/configuration.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/configuration.ts b/src/configuration.ts index b3bb484..f43eca1 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -12,6 +12,7 @@ import {logger} from "./logger"; import * as utils from "./utils"; import {ExtensionData} from "./extensionData"; import {Settings} from "./interfaces/settings"; +import {LineComment, SingleLineCommentStyle} from "./interfaces/commentStyles"; export class Configuration { /************** From 824ce9b084926d5eaa3d41928f047c6b82958373 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Fri, 23 Jan 2026 16:19:01 +0000 Subject: [PATCH 16/26] fix: `data` param in logger's `debug` method to be optional - Fixed the `data` param in logger's `debug` method to be an optional param, so it no longer is required if we just want the `DEBUG` message in the logs. --- src/logger.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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); } From 6534893ff64984cf5a601a6c504da91c099c5396 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Fri, 23 Jan 2026 17:38:01 +0000 Subject: [PATCH 17/26] feat: add a dedicated `namespace` property for the `extensionData`. - Added an optional `namespace` property in the `ExtensionMetaData` interface. - Move the configuration namespace value of the `name` property in `setExtensionData` to the new `namespace` property as it's value. - Set the `name` property n `setExtensionData` to the name in the packageJson instead of the configuration namespace. - Change all occurrences of getting the `name` property to the new `namespace` property since each occurrence is for the namespace and not the actual name. --- src/configuration.ts | 4 ++-- src/extension.ts | 2 +- src/extensionData.ts | 3 ++- src/interfaces/extensionMetaData.ts | 1 + 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/configuration.ts b/src/configuration.ts index f43eca1..36ceddd 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -220,7 +220,7 @@ 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); } /** @@ -932,7 +932,7 @@ 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)) { diff --git a/src/extension.ts b/src/extension.ts index 9909b02..9f95d44 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -18,7 +18,7 @@ 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"); diff --git a/src/extensionData.ts b/src/extensionData.ts index 1f8863f..4739816 100644 --- a/src/extensionData.ts +++ b/src/extensionData.ts @@ -60,7 +60,8 @@ export class ExtensionData { // Set each key-value pair directly into the Map this.extensionData.set("id", this.packageJsonData.id); - this.extensionData.set("name", this.packageJsonData.contributes.configuration.namespace); + this.extensionData.set("name", this.packageJsonData.name); + this.extensionData.set("namespace", this.packageJsonData.contributes.configuration.namespace); this.extensionData.set("displayName", this.packageJsonData.displayName); this.extensionData.set("version", this.packageJsonData.version); this.extensionData.set("userExtensionsPath", userExtensionsPath); diff --git a/src/interfaces/extensionMetaData.ts b/src/interfaces/extensionMetaData.ts index 94c51b2..c1cadbd 100644 --- a/src/interfaces/extensionMetaData.ts +++ b/src/interfaces/extensionMetaData.ts @@ -1,6 +1,7 @@ export interface ExtensionMetaData { id: string; name: string; + namespace?: string; displayName: string; version: string; userExtensionsPath: string; From 07def7e07e6b0508261834d37264ad13ec268c5b Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Fri, 23 Jan 2026 17:43:44 +0000 Subject: [PATCH 18/26] docs: add docblocks to the `ExtensionMetaData` interface. --- src/interfaces/extensionMetaData.ts | 42 ++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/interfaces/extensionMetaData.ts b/src/interfaces/extensionMetaData.ts index c1cadbd..090bc75 100644 --- a/src/interfaces/extensionMetaData.ts +++ b/src/interfaces/extensionMetaData.ts @@ -1,16 +1,56 @@ +/** + * 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 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; } From be9d9726df2b7157a61ea8078d845798fd232e3b Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Sat, 24 Jan 2026 23:45:59 +0000 Subject: [PATCH 19/26] refactor: paths to installed extensions into a separate method - Added new `ExtensionData::setExtensionDiscoveryPaths` method to set the paths to installed extensions, and added it's method call to the constructor. - Moved the setting of the extension paths from `setExtensionData` into the new `setExtensionDiscoveryPaths` method. - Added new `ExtensionPaths` interface and moved all the related properties from the `ExtensionMetaData` interface into the new interface. - Added new `ExtensionData::extensionDiscoveryPaths` property to hold the Map of all the paths, and uses the `ExtensionPaths` interface. - Changed references to the `extensionData` property in `setExtensionDiscoveryPaths` method to use the new `extensionDiscoveryPaths` property. - Added `getExtensionDiscoveryPath` and `getAllExtensionDiscoveryPaths` methods to get a specific extension path by key or get all paths, respectively. - Changed all method calls to `get` extension paths in `Configuration` class to use the `getExtensionDiscoveryPath` method; and added the `getAllExtensionDiscoveryPaths` method call in the constructor. --- src/configuration.ts | 19 ++++++----- src/extensionData.ts | 52 ++++++++++++++++++++++++----- src/interfaces/extensionMetaData.ts | 7 ++++ 3 files changed, 60 insertions(+), 18 deletions(-) diff --git a/src/configuration.ts b/src/configuration.ts index 36ceddd..a77b83e 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -76,6 +76,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(); @@ -309,8 +310,8 @@ export class Configuration { // 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); @@ -320,8 +321,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); @@ -971,25 +972,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/extensionData.ts b/src/extensionData.ts index 4739816..ad26b6d 100644 --- a/src/extensionData.ts +++ b/src/extensionData.ts @@ -4,7 +4,7 @@ import isWsl from "is-wsl"; import {IPackageJson} from "package-json-type"; import {readJsonFile} from "./utils"; -import {ExtensionMetaData} from "./interfaces/extensionMetaData"; +import {ExtensionMetaData, ExtensionPaths} from "./interfaces/extensionMetaData"; export class ExtensionData { /** @@ -14,6 +14,14 @@ export class ExtensionData { */ private extensionData = new Map(); + /** + * Extension discovery paths in the form of a key:value Map object. + * + * @type {Map} + */ + private extensionDiscoveryPaths = new Map(); + + /** * The package.json data for this extension. * @@ -24,6 +32,7 @@ export class ExtensionData { public constructor() { this.packageJsonData = this.getExtensionPackageJsonData(); this.setExtensionData(); + this.setExtensionDiscoveryPaths(); } /** @@ -53,10 +62,6 @@ export class ExtensionData { * Set the extension data into the extensionData Map. */ private setExtensionData() { - // The path to the user extensions. - const userExtensionsPath = isWsl - ? path.join(vscode.env.appRoot, "../../", "extensions") - : path.join(this.packageJsonData.extensionPath, "../"); // Set each key-value pair directly into the Map this.extensionData.set("id", this.packageJsonData.id); @@ -64,15 +69,24 @@ export class ExtensionData { this.extensionData.set("namespace", this.packageJsonData.contributes.configuration.namespace); this.extensionData.set("displayName", this.packageJsonData.displayName); this.extensionData.set("version", this.packageJsonData.version); - this.extensionData.set("userExtensionsPath", userExtensionsPath); + } + + private setExtensionDiscoveryPaths() { + // The path to the user extensions. + // const userExtensionsPath = isWsl ? path.join(vscode.env.appRoot, "../../", "extensions") : path.join(this.extensionPath, "../"); + + //TODO: REMOVE THIS BEFORE RELEASE. FOR TESTING ONLY. + const userExtensionsPath = isWsl ? path.join(vscode.env.appRoot, "../../", "extensions") : "C:\\Users\\Stuart\\.vscode\\extensions"; + + 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.extensionData.set("builtInExtensionsPath", path.join(vscode.env.appRoot, "extensions")); + this.extensionDiscoveryPaths.set("builtInExtensionsPath", path.join(vscode.env.appRoot, "extensions")); // Only set these if running in WSL if (isWsl) { - this.extensionData.set("WindowsUserExtensionsPathFromWsl", path.dirname(process.env.VSCODE_WSL_EXT_LOCATION!)); - this.extensionData.set("WindowsBuiltInExtensionsPathFromWsl", path.join(process.env.VSCODE_CWD!, "resources/app/extensions")); + 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")); } } @@ -95,4 +109,24 @@ export class ExtensionData { public getAll(): ReadonlyMap { return this.extensionData; } + + /** + * Get the extension discovery paths by a specified key. + * + * @param {K} key The key of the specific path to get. + * + * @returns {ExtensionPaths[K] | undefined} The value of the extension detail, or undefined if the key does not exist. + */ + public getExtensionDiscoveryPath(key: K): ExtensionPaths[K] | undefined { + return this.extensionDiscoveryPaths.get(key) as ExtensionPaths[K] | undefined; + } + + /** + * Get all extension discovery paths. + * + * @returns {ReadonlyMap} A read-only Map containing all extension discovery paths. + */ + public getAllExtensionDiscoveryPaths(): ReadonlyMap { + return this.extensionDiscoveryPaths; + } } diff --git a/src/interfaces/extensionMetaData.ts b/src/interfaces/extensionMetaData.ts index 090bc75..a4fc8de 100644 --- a/src/interfaces/extensionMetaData.ts +++ b/src/interfaces/extensionMetaData.ts @@ -30,6 +30,13 @@ export interface ExtensionMetaData { * Directly from package.json "version" key. */ version: string; + +} + +/** + * Extension discovery paths configuration for this extension + */ +export interface ExtensionPaths { /** * The path to the user extensions. */ From 2b0ce9a276474c849bf1989b6cf2524accaae805 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Sun, 25 Jan 2026 22:53:53 +0000 Subject: [PATCH 20/26] refactor: move creation of the extension ID into `setExtensionData`. - Move the creation of the extension ID from the `getExtensionPackageJsonData` method into the `setExtensionData` method. And set the id property of the `extensionData` Map directly as the id variable. This is instead of using the `packageJSON` object as the middleman, and using a custom property which is then used to set the `extensionData` Map. The ID doesn't need to be in the packageJSON object, so we can just create and set the id directly in the `setExtensionData` method. --- src/extensionData.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/extensionData.ts b/src/extensionData.ts index ad26b6d..45bc5e8 100644 --- a/src/extensionData.ts +++ b/src/extensionData.ts @@ -45,8 +45,6 @@ export class ExtensionData { const packageJSON: IPackageJson = readJsonFile(path.join(extensionPath, "package.json")); - // Set the id (publisher.name) into the packageJSON object as a new `id` key. - packageJSON.id = `${packageJSON.publisher}.${packageJSON.name}`; packageJSON.extensionPath = extensionPath; // The configuration settings namespace is a shortened version of the extension name. @@ -62,9 +60,11 @@ export class ExtensionData { * Set the extension data into the extensionData Map. */ 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", this.packageJsonData.id); + this.extensionData.set("id", id); this.extensionData.set("name", this.packageJsonData.name); this.extensionData.set("namespace", this.packageJsonData.contributes.configuration.namespace); this.extensionData.set("displayName", this.packageJsonData.displayName); From 6f77cc51f56776eba0d0f82073990d58c54ac629 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Sun, 25 Jan 2026 23:15:40 +0000 Subject: [PATCH 21/26] refactor: move `settingsNamespace` creation into `setExtensionData`. - Move `settingsNamespace` creation from `getExtensionPackageJsonData` method into the `setExtensionData` method. So that the `namespace` property of the `extensionData` Map can be set directly using the variable instead of using the `packageJSON` object as a middleman again. We only add namespace to `packageJSON.contributes.configuration` as a way to eventually add it to the `extensionData` Map. This is redundant code. --- src/extensionData.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/extensionData.ts b/src/extensionData.ts index 45bc5e8..7d6c454 100644 --- a/src/extensionData.ts +++ b/src/extensionData.ts @@ -47,11 +47,6 @@ export class ExtensionData { packageJSON.extensionPath = 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; return packageJSON; } @@ -66,7 +61,11 @@ export class ExtensionData { // Set each key-value pair directly into the Map this.extensionData.set("id", id); this.extensionData.set("name", this.packageJsonData.name); - this.extensionData.set("namespace", this.packageJsonData.contributes.configuration.namespace); + // 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); this.extensionData.set("displayName", this.packageJsonData.displayName); this.extensionData.set("version", this.packageJsonData.version); } From c2711eb53a9354f671519d98832e46231e11985c Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Mon, 26 Jan 2026 01:54:25 +0000 Subject: [PATCH 22/26] fix: JSON file reading with optional `null` return for missing files. - Moved the check for package.json file existing (`fs.existsSync`) in `Configruation::readExtensionsFromDirectory` method into the `readJsonFile` utility function. This is so that the util function can check if a JSON file exists before trying to read it. If file doesn't exist then it just returns `null`. - Removed the `fs.existsSync` check and replaced it with a `null` check in the `readExtensionsFromDirectory` method. - Refactored `readJsonFile` util function to accept a new `throwOnFileMissing` param with a default value of `true`. This param, along with it's accompanying logic, will throw an error if a file is missing (doesn't exist) and when the param is `true, instead of returning `null`. This allows only specific usages of the util function to return `null` instead of error throwing when the param is set to `false`. Thus being backward compatibile with other usages. --- src/configuration.ts | 15 ++++++++------- src/utils.ts | 23 +++++++++++++++++++---- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/configuration.ts b/src/configuration.ts index a77b83e..e75fe38 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -495,16 +495,17 @@ export class Configuration { // Get the package.json file path. const packageJSONPath = path.join(extensionPath, "package.json"); + const packageJSON: IPackageJson = utils.readJsonFile(packageJSONPath, false); - // If the package.json file exists... - if (fs.existsSync(packageJSONPath)) { - const packageJSON: IPackageJson = utils.readJsonFile(packageJSONPath); + if (packageJSON === null) { + return; + } - const id = `${packageJSON.publisher}.${packageJSON.name}`; + const id = `${packageJSON.publisher}.${packageJSON.name}`; - // Push the extension data object into the array. - foundExtensions.push({id, extensionPath, packageJSON}); - } + // Push the extension data object into the array. + foundExtensions.push({id, extensionPath, packageJSON}); + } }); 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") { From ea401c8797a185d5d252c654122ca2f31bf9daf6 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Mon, 26 Jan 2026 02:14:37 +0000 Subject: [PATCH 23/26] feat: add `extensionPath` property to the `ExtensionData` class. - Added new `extensionPath` private property to the `ExtensionData` class to hold the absolute path of the extension. - Move the `extensionPath` creation from `getExtensionPackageJsonData` method into the `constructor`, and set it to the new class property of the same name. - Split the extension package.json path creation from the `readJsonFile` method call param into a new separate `packageJSONPath` variable in the `getExtensionPackageJsonData` method, and use this new variable as the param of `readJsonFile`. - Added new `extensionPath` property to the `ExtensionMetaData` interface to allow the `extensionData` Map to hold this new property, and added setting it's value into the `setExtensionData` method. - Removed the setting of the `extensionPath` into the `packageJson` object in `getExtensionPackageJsonData`, in favour of the new `extensionData` Map property of the same name. --- src/extensionData.ts | 21 +++++++++++++-------- src/interfaces/extensionMetaData.ts | 4 ++++ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/extensionData.ts b/src/extensionData.ts index 7d6c454..8f376c4 100644 --- a/src/extensionData.ts +++ b/src/extensionData.ts @@ -21,6 +21,12 @@ export class ExtensionData { */ private extensionDiscoveryPaths = new Map(); + /** + * The absolute path of the extension. + * + * @type {string} + */ + private readonly extensionPath: string; /** * The package.json data for this extension. @@ -30,6 +36,9 @@ export class ExtensionData { private packageJsonData: IPackageJson; public constructor() { + // Set the path to this extension's path. + this.extensionPath = path.join(__dirname, "../../"); + this.packageJsonData = this.getExtensionPackageJsonData(); this.setExtensionData(); this.setExtensionDiscoveryPaths(); @@ -41,14 +50,9 @@ export class ExtensionData { * @returns {IPackageJson} The package.json data for this extension, with extra custom keys. */ private getExtensionPackageJsonData(): IPackageJson { - const extensionPath = path.join(__dirname, "../../"); - - const packageJSON: IPackageJson = readJsonFile(path.join(extensionPath, "package.json")); - - packageJSON.extensionPath = extensionPath; - - - return packageJSON; + // Get the package.json file path. + const packageJSONPath = path.join(this.extensionPath, "package.json"); + return readJsonFile(packageJSONPath); } /** @@ -68,6 +72,7 @@ export class ExtensionData { this.extensionData.set("namespace", settingsNamespace); this.extensionData.set("displayName", this.packageJsonData.displayName); this.extensionData.set("version", this.packageJsonData.version); + this.extensionData.set("extensionPath", this.extensionPath); } private setExtensionDiscoveryPaths() { diff --git a/src/interfaces/extensionMetaData.ts b/src/interfaces/extensionMetaData.ts index a4fc8de..7ee8c8e 100644 --- a/src/interfaces/extensionMetaData.ts +++ b/src/interfaces/extensionMetaData.ts @@ -31,6 +31,10 @@ export interface ExtensionMetaData { */ version: string; + /** + * The absolute path to the extension. + */ + extensionPath: string; } /** From b693795098cd6bceb6879b131abf69038e30b17b Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Mon, 26 Jan 2026 02:33:40 +0000 Subject: [PATCH 24/26] fix: add `null` check for `packageJsonData` before setting extensionData - Added the `throwOnFileMissing` param of the readJsonFile util function call to as `false` in the `getExtensionPackageJsonData` method to allow the util function to return `null`. - Changed return types of the `getExtensionPackageJsonData` function to allow `null`. - Added a `packageJson` null check in the constructor to only allow running the `setExtensionData` method when `packageJsonData` is not `null`. --- src/extensionData.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/extensionData.ts b/src/extensionData.ts index 8f376c4..9bbdbd1 100644 --- a/src/extensionData.ts +++ b/src/extensionData.ts @@ -40,19 +40,24 @@ export class ExtensionData { this.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 { + private getExtensionPackageJsonData(): IPackageJson | null { // Get the package.json file path. const packageJSONPath = path.join(this.extensionPath, "package.json"); - return readJsonFile(packageJSONPath); + return readJsonFile(packageJSONPath, false); } /** From 279d3b4d9361336e88c715ed49abfb43839c6de3 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Mon, 26 Jan 2026 04:30:52 +0000 Subject: [PATCH 25/26] refactor: allow the `extensionData` Map for any extension's data. - Added `packageJson` property to the `ExtensionMetaData` interface with the type of `IPackageJson`. - Added a new `ExtensionMetaDataValue` utility type to automatically get all types of the `ExtensionMetaData` and construct them as a union type. This is needed because typing the Map value as `string` will no longer work with the new `packageJson` property since it's not a string. - Changed the typing of the `extensionData` Map value to use the new `ExtensionMetaDataValue` type. - Added new `extensionPath` param to the constructor so that the class can be initialised with an optional extension path. The param is `null` be default. - Added the new `extensionPath` param variable to set the class's `extensionPath` property. Using a nullish coalescing operator (`??`) on the variable, we set the property to the variable if it's a string, otherwise set it to our extension's path. - Changed setting of the extension namespace property to only be set if the extension is ours - wrapped the code in an if statement in `setExtensionData` method. This is because we only need to know our extension's config namespace. - Set the new `packageJson` property into the `extensionData` Map with the class's `packageJsonData` property as the value in `setExtensionData`. - Changed all typings of any variables relating to an array of extensions in `Configuration` class to use the `ExtensionMetaData` interface. Because we have the required properties in place now we don't need to type them as `any`, we can now use the interface for a better developing experience. In addition, we can also now remove all occurrences of the array object typing of `Array<{ id: string; extensionPath: string; packageJSON: IPackageJson }>` and replace it with the `ExtensionMetaData` interface as the type `ExtensionMetaData[]`. This makes it easier to handle, and removes all duplication. - Changed `Configuration::readExtensionsFromDirectory` method to use the `ExtensionData` class to `getAll` data of each extension in the directory. This removes all now redundant code dealing with the package.json within the method, since it's handled in the `ExtensionData` class. - Changed `ExtensionData::getAll` method to return a plain object instead of a readonly Map, this is so that we can use it in the `Configuration::readExtensionsFromDirectory` method without ts errors. Also added a conditional that will return null if the `extensionData` map has a size of 0, ie. is empty. --- src/configuration.ts | 25 +++++++---------- src/extensionData.ts | 43 ++++++++++++++++++----------- src/interfaces/extensionMetaData.ts | 10 +++++++ 3 files changed, 47 insertions(+), 31 deletions(-) diff --git a/src/configuration.ts b/src/configuration.ts index e75fe38..815d073 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -13,6 +13,7 @@ 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 { /************** @@ -166,7 +167,7 @@ export class Configuration { "auto-comment-blocks.changeBladeMultiLineBlock", (textEditor, edit, args) => { this.handleChangeBladeMultiLineBlock(textEditor); - } + }, ); return [singleLineBlockCommand, changeBladeMultiLineBlockCommand]; } @@ -305,7 +306,7 @@ export class Configuration { * (built-in and 3rd party). */ private findAllLanguageConfigFilePaths() { - const extensions: any[] = []; + const extensions: ExtensionMetaData[] = []; // If running in WSL... if (isWsl) { @@ -477,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); @@ -493,19 +493,14 @@ export class Configuration { return; } - // Get the package.json file path. - const packageJSONPath = path.join(extensionPath, "package.json"); - const packageJSON: IPackageJson = utils.readJsonFile(packageJSONPath, false); + const extensionData = new ExtensionData(extensionPath).getAll(); - if (packageJSON === null) { + if (extensionData === null) { return; } - const id = `${packageJSON.publisher}.${packageJSON.name}`; - // Push the extension data object into the array. - foundExtensions.push({id, extensionPath, packageJSON}); - + foundExtensions.push(extensionData); } }); @@ -959,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. diff --git a/src/extensionData.ts b/src/extensionData.ts index 9bbdbd1..08582b5 100644 --- a/src/extensionData.ts +++ b/src/extensionData.ts @@ -4,15 +4,15 @@ import isWsl from "is-wsl"; import {IPackageJson} from "package-json-type"; import {readJsonFile} from "./utils"; -import {ExtensionMetaData, ExtensionPaths} from "./interfaces/extensionMetaData"; +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. @@ -22,7 +22,7 @@ export class ExtensionData { private extensionDiscoveryPaths = new Map(); /** - * The absolute path of the extension. + * The absolute path of the requested extension. * * @type {string} */ @@ -35,9 +35,9 @@ export class ExtensionData { */ private packageJsonData: IPackageJson; - public constructor() { - // Set the path to this extension's path. - this.extensionPath = path.join(__dirname, "../../"); + 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(); @@ -70,14 +70,20 @@ export class ExtensionData { // Set each key-value pair directly into the Map this.extensionData.set("id", id); this.extensionData.set("name", this.packageJsonData.name); - // 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); + // 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); + } + 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); } private setExtensionDiscoveryPaths() { @@ -111,12 +117,17 @@ export class ExtensionData { } /** - * Get all extension data. + * Get all extension data as a plain object. * - * @returns {ReadonlyMap} A read-only Map containing all extension details. + * @returns {ExtensionMetaData} A plain object containing all extension details. */ - public getAll(): ReadonlyMap { - return this.extensionData; + 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; } /** diff --git a/src/interfaces/extensionMetaData.ts b/src/interfaces/extensionMetaData.ts index 7ee8c8e..646ec81 100644 --- a/src/interfaces/extensionMetaData.ts +++ b/src/interfaces/extensionMetaData.ts @@ -1,3 +1,8 @@ +import {IPackageJson} from "package-json-type"; + +// Utility types for cleaner Map typing +export type ExtensionMetaDataValue = ExtensionMetaData[keyof ExtensionMetaData]; + /** * Extension metadata for a VSCode extension */ @@ -35,6 +40,11 @@ export interface ExtensionMetaData { * The absolute path to the extension. */ extensionPath: string; + + /** + * The full package.json data + */ + packageJSON: IPackageJson; } /** From 9ff08579b9626f620b6f50a893802ff5284c39c2 Mon Sep 17 00:00:00 2001 From: yCodeTech Date: Mon, 26 Jan 2026 04:41:26 +0000 Subject: [PATCH 26/26] fix: remove the testing only code mistakenly added in commit be9d972 The `//TODO: REMOVE THIS BEFORE RELEASE. FOR TESTING ONLY.` comment along with it's testing `userExtensionsPath` variable code was mistakenly added in commit be9d972. This was never supposed have seen the lights of git, and is only used in development to ensure it gets the right extensions path otherwise the development environment would get the wrong path. Fixed by removing the testing code, and uncommenting the real code. --- src/extensionData.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/extensionData.ts b/src/extensionData.ts index 08582b5..8d7fd63 100644 --- a/src/extensionData.ts +++ b/src/extensionData.ts @@ -88,10 +88,7 @@ export class ExtensionData { private setExtensionDiscoveryPaths() { // The path to the user extensions. - // const userExtensionsPath = isWsl ? path.join(vscode.env.appRoot, "../../", "extensions") : path.join(this.extensionPath, "../"); - - //TODO: REMOVE THIS BEFORE RELEASE. FOR TESTING ONLY. - const userExtensionsPath = isWsl ? path.join(vscode.env.appRoot, "../../", "extensions") : "C:\\Users\\Stuart\\.vscode\\extensions"; + const userExtensionsPath = isWsl ? path.join(vscode.env.appRoot, "../../", "extensions") : path.join(this.extensionPath, "../"); this.extensionDiscoveryPaths.set("userExtensionsPath", userExtensionsPath); // The path to the built-in extensions.