Skip to content

Commit 50110d0

Browse files
authored
Fix typings and add interfaces (#20)
This pull request refactors and improves type safety in the `Configuration` class by introducing and applying more precise TypeScript types throughout the codebase. It also updates configuration access patterns and improves code readability. The most important changes are grouped below: ### Type Safety & Refactoring * Replaced generic `string` types with more specific types such as `LanguageId`, `SingleLineCommentStyle`, and `ExtensionMetaData` for various Maps, method parameters, and return types throughout `src/configuration.ts`. This enhances type safety and code clarity. * Updated method signatures and internal logic to use these new types, including for language config file paths, language configs, comment block maps, and extension metadata handling. ### Configuration Access & Usage * Changed configuration access to use the extension's `namespace` instead of `name`, and updated methods to use strongly-typed keys and values for getting and updating configuration values. * Improved the retrieval and usage of extension discovery paths and extension metadata by utilizing new helper methods in `ExtensionData`. ### Code Quality & Readability * Added explicit return types to public methods, improved JSDoc comments, and clarified parameter types and return values for better code documentation and maintainability. * Added extra debug logging to output extension discovery paths on activation. ### Miscellaneous * Updated `.prettierrc.json` to enforce trailing commas in ES5 style for improved code formatting consistency. * Renamed the TypeScript check workflow job from `deploy` to `check-ts` in `.github/workflows/check-ts.yml` for clarity. ~ Summary generated by Copilot
2 parents 19c500a + 19dd940 commit 50110d0

11 files changed

Lines changed: 474 additions & 205 deletions

File tree

.github/workflows/check-ts.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ on:
33

44
name: Compile TypeScript to check for errors
55
jobs:
6-
deploy:
6+
check-ts:
77
runs-on: ubuntu-latest
88
steps:
99
- uses: actions/checkout@v4

.prettierrc.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
"*.ts"
3131
],
3232
"options": {
33-
"quoteProps": "consistent"
33+
"quoteProps": "consistent",
34+
"trailingComma": "es5"
3435
}
3536
}
3637
]

src/configuration.ts

Lines changed: 157 additions & 120 deletions
Large diffs are not rendered by default.

src/extension.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ export function activate(context: vscode.ExtensionContext) {
1818

1919
disposables.push(...configureCommentBlocksDisposable, ...registerCommandsDisposable);
2020

21-
const extensionName = extensionData.get("name");
21+
const extensionName = extensionData.get("namespace");
2222

2323
const extensionDisplayName = extensionData.get("displayName");
2424

25-
let disabledLangConfig: string[] = configuration.getConfigurationValue<string[]>("disabledLanguages");
25+
let disabledLangConfig: string[] = configuration.getConfigurationValue("disabledLanguages");
2626

2727
if (disabledLangConfig.length > 0) {
2828
vscode.window.showInformationMessage(`${disabledLangConfig.join(", ")} languages are disabled for ${extensionDisplayName}.`);
@@ -32,7 +32,7 @@ export function activate(context: vscode.ExtensionContext) {
3232
* When the configuration/user settings are changed, set the extension
3333
* to reflect the settings and output a message to the user.
3434
*/
35-
vscode.workspace.onDidChangeConfiguration((event: any) => {
35+
vscode.workspace.onDidChangeConfiguration((event: vscode.ConfigurationChangeEvent) => {
3636
// TODO: Work on automatically updating the languages instead of making the user reload the extension.
3737

3838
/**
@@ -41,7 +41,7 @@ export function activate(context: vscode.ExtensionContext) {
4141
// If the affected setting is bladeOverrideComments...
4242
if (event.affectsConfiguration(`${extensionName}.bladeOverrideComments`)) {
4343
// Get the setting.
44-
let bladeOverrideComments: boolean = configuration.getConfigurationValue<boolean>("bladeOverrideComments");
44+
let bladeOverrideComments: boolean = configuration.getConfigurationValue("bladeOverrideComments");
4545

4646
configuration.setBladeComments(bladeOverrideComments);
4747

src/extensionData.ts

Lines changed: 107 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,29 @@ import isWsl from "is-wsl";
44
import {IPackageJson} from "package-json-type";
55

66
import {readJsonFile} from "./utils";
7+
import {ExtensionMetaData, ExtensionPaths, ExtensionMetaDataValue} from "./interfaces/extensionMetaData";
78

89
export class ExtensionData {
910
/**
10-
* This extension details in the form of a key:value Map object.
11+
* Extension data in the form of a key:value Map object.
1112
*
12-
* @type {Map<string, string>}
13+
* @type {Map<keyof ExtensionMetaData, ExtensionMetaDataValue>}
1314
*/
14-
private extensionData = new Map<string, string>();
15+
private extensionData = new Map<keyof ExtensionMetaData, ExtensionMetaDataValue>();
16+
17+
/**
18+
* Extension discovery paths in the form of a key:value Map object.
19+
*
20+
* @type {Map<keyof ExtensionPaths, string>}
21+
*/
22+
private extensionDiscoveryPaths = new Map<keyof ExtensionPaths, string>();
23+
24+
/**
25+
* The absolute path of the requested extension.
26+
*
27+
* @type {string}
28+
*/
29+
private readonly extensionPath: string;
1530

1631
/**
1732
* The package.json data for this extension.
@@ -20,93 +35,125 @@ export class ExtensionData {
2035
*/
2136
private packageJsonData: IPackageJson;
2237

23-
public constructor() {
38+
public constructor(extensionPath: string | null = null) {
39+
// Set the path if provided, otherwise default to this extension's path.
40+
//
41+
// For this extension's path, we use `__dirname` and go up two levels
42+
// (from "out/src" to the extension root). This path is also used to locate all other
43+
// user-installed extensions later for the `userExtensionsPath` discovery path.
44+
this.extensionPath = extensionPath ?? path.join(__dirname, "../../");
45+
2446
this.packageJsonData = this.getExtensionPackageJsonData();
25-
this.setExtensionData();
47+
48+
// Only proceed with extension data setup if packageJsonData is NOT null.
49+
if (this.packageJsonData !== null) {
50+
this.setExtensionData();
51+
}
52+
53+
this.setExtensionDiscoveryPaths();
2654
}
2755

2856
/**
2957
* Get the names, id, and version of this extension from package.json.
3058
*
31-
* @returns {IPackageJson} The package.json data for this extension, with extra custom keys.
59+
* @returns {IPackageJson | null} The package.json data for this extension, with extra custom keys.
3260
*/
33-
private getExtensionPackageJsonData(): IPackageJson {
34-
const extensionPath = path.join(__dirname, "../../");
35-
36-
const packageJSON: IPackageJson = readJsonFile(path.join(extensionPath, "package.json"));
37-
38-
// Set the id (publisher.name) into the packageJSON object as a new `id` key.
39-
packageJSON.id = `${packageJSON.publisher}.${packageJSON.name}`;
40-
packageJSON.extensionPath = extensionPath;
41-
42-
// The configuration settings namespace is a shortened version of the extension name.
43-
// We just need to replace "automatic" with "auto" in the name.
44-
const settingsNamespace: string = packageJSON.name.replace("automatic", "auto");
45-
// Set the namespace to the packageJSON `configuration` object as a new `namespace` key.
46-
packageJSON.contributes.configuration.namespace = settingsNamespace;
47-
48-
return packageJSON;
61+
private getExtensionPackageJsonData(): IPackageJson | null {
62+
// Get the package.json file path.
63+
const packageJSONPath = path.join(this.extensionPath, "package.json");
64+
return readJsonFile<IPackageJson>(packageJSONPath, false);
4965
}
5066

5167
/**
5268
* Set the extension data into the extensionData Map.
5369
*/
5470
private setExtensionData() {
55-
// Set all entries in the extensionData Map.
56-
Object.entries(this.createExtensionData()).forEach(([key, value]) => {
57-
this.extensionData.set(key, value);
58-
});
71+
// Create the extension ID (publisher.name).
72+
const id = `${this.packageJsonData.publisher}.${this.packageJsonData.name}`;
73+
74+
// Set each key-value pair directly into the Map
75+
this.extensionData.set("id", id);
76+
this.extensionData.set("name", this.packageJsonData.name);
77+
78+
// Only set the namespace if it dealing with this extension.
79+
if (this.packageJsonData.name === "automatic-comment-blocks") {
80+
// The configuration settings namespace is a shortened version of the extension name.
81+
// We just need to replace "automatic" with "auto" in the name.
82+
const settingsNamespace: string = this.packageJsonData.name.replace("automatic", "auto");
83+
84+
this.extensionData.set("namespace", settingsNamespace);
85+
}
86+
87+
this.extensionData.set("displayName", this.packageJsonData.displayName);
88+
this.extensionData.set("version", this.packageJsonData.version);
89+
this.extensionData.set("extensionPath", this.extensionPath);
90+
this.extensionData.set("packageJSON", this.packageJsonData);
5991
}
6092

6193
/**
62-
* Create the extension data object for the extensionData Map.
63-
* It also helps for type inference intellisense in the get method.
64-
*
65-
* @returns The extension data object with keys and values.
94+
* Set the extension discovery paths into the extensionDiscoveryPaths Map.
6695
*/
67-
private createExtensionData() {
96+
private setExtensionDiscoveryPaths() {
6897
// The path to the user extensions.
69-
const userExtensionsPath = isWsl
70-
? path.join(vscode.env.appRoot, "../../", "extensions")
71-
: path.join(this.packageJsonData.extensionPath, "../");
72-
73-
// Set the keys and values for the Map.
74-
// The keys will also be used for type inference in VSCode intellisense.
75-
return {
76-
id: this.packageJsonData.id,
77-
name: this.packageJsonData.contributes.configuration.namespace,
78-
displayName: this.packageJsonData.displayName,
79-
version: this.packageJsonData.version,
80-
userExtensionsPath: userExtensionsPath,
81-
// The path to the built-in extensions.
82-
// This env variable changes when on WSL to it's WSL-built-in extensions path.
83-
builtInExtensionsPath: path.join(vscode.env.appRoot, "extensions"),
84-
85-
// Only set these if running in WSL.
86-
...(isWsl && {
87-
WindowsUserExtensionsPathFromWsl: path.dirname(process.env.VSCODE_WSL_EXT_LOCATION!),
88-
WindowsBuiltInExtensionsPathFromWsl: path.join(process.env.VSCODE_CWD!, "resources/app/extensions"),
89-
}),
90-
} as const;
98+
//
99+
// On Windows/Linux/Mac: ~/.vscode[-server|remote]/extensions
100+
// On WSL: ~/.vscode-[server|remote]/extensions
101+
const userExtensionsPath = isWsl ? path.join(vscode.env.appRoot, "../../", "extensions") : path.join(this.extensionPath, "../");
102+
103+
this.extensionDiscoveryPaths.set("userExtensionsPath", userExtensionsPath);
104+
// The path to the built-in extensions.
105+
// This env variable changes when on WSL to it's WSL-built-in extensions path.
106+
this.extensionDiscoveryPaths.set("builtInExtensionsPath", path.join(vscode.env.appRoot, "extensions"));
107+
108+
// Only set these if running in WSL
109+
if (isWsl) {
110+
this.extensionDiscoveryPaths.set("WindowsUserExtensionsPathFromWsl", path.dirname(process.env.VSCODE_WSL_EXT_LOCATION!));
111+
this.extensionDiscoveryPaths.set("WindowsBuiltInExtensionsPathFromWsl", path.join(process.env.VSCODE_CWD!, "resources/app/extensions"));
112+
}
91113
}
92114

93115
/**
94116
* Get the extension's data by a specified key.
95117
*
96118
* @param {K} key The key of the extension detail to get.
97119
*
98-
* @returns {ReturnType<typeof this.createExtensionData>[K] | undefined} The value of the extension detail, or undefined if the key does not exist.
120+
* @returns {ExtensionMetaData[K] | undefined} The value of the extension detail, or undefined if the key does not exist.
121+
*/
122+
public get<K extends keyof ExtensionMetaData>(key: K): ExtensionMetaData[K] | undefined {
123+
return this.extensionData.get(key) as ExtensionMetaData[K] | undefined;
124+
}
125+
126+
/**
127+
* Get all extension data as a plain object.
128+
*
129+
* @returns {ExtensionMetaData} A plain object containing all extension details.
130+
*/
131+
public getAll(): ExtensionMetaData | null {
132+
// If no data, return null
133+
if (this.extensionData.size === 0) {
134+
return null;
135+
}
136+
137+
return Object.fromEntries(this.extensionData) as unknown as ExtensionMetaData;
138+
}
139+
140+
/**
141+
* Get the extension discovery paths by a specified key.
142+
*
143+
* @param {K} key The key of the specific path to get.
144+
*
145+
* @returns {ExtensionPaths[K] | undefined} The value of the extension detail, or undefined if the key does not exist.
99146
*/
100-
public get<K extends keyof ReturnType<typeof this.createExtensionData>>(key: K): ReturnType<typeof this.createExtensionData>[K] | undefined {
101-
return this.extensionData.get(key) as ReturnType<typeof this.createExtensionData>[K] | undefined;
147+
public getExtensionDiscoveryPath<K extends keyof ExtensionPaths>(key: K): ExtensionPaths[K] | undefined {
148+
return this.extensionDiscoveryPaths.get(key) as ExtensionPaths[K] | undefined;
102149
}
103150

104151
/**
105-
* Get all extension data.
152+
* Get all extension discovery paths.
106153
*
107-
* @returns {ReadonlyMap<string, string>} A read-only Map containing all extension details.
154+
* @returns {ReadonlyMap<keyof ExtensionPaths, string>} A read-only Map containing all extension discovery paths.
108155
*/
109-
public getAll(): ReadonlyMap<string, string> {
110-
return this.extensionData;
156+
public getAllExtensionDiscoveryPaths(): ReadonlyMap<keyof ExtensionPaths, string> {
157+
return this.extensionDiscoveryPaths;
111158
}
112159
}

src/interfaces/commentStyles.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* Define the single-line comment styles.
3+
*/
4+
export type SingleLineCommentStyle = "//" | "#" | ";";
5+
6+
/**
7+
* Define the extra single-line comment styles, like `///`, etc.
8+
*/
9+
export type ExtraSingleLineCommentStyles = "##" | ";;" | "///" | "//!";
10+
11+
/**
12+
* Line Comments
13+
*
14+
* Taken directly from VScode's commit in June 2025 that changed the line comment config.
15+
* https://github.com/microsoft/vscode/commit/d9145a291dcef0bad3ace81a3d55727ca294c122#diff-0dfa7db579eface8250affb76bc88717725a121401d4d8598bc36b92b0b6ef62
16+
*
17+
* The @types/vscode package does not yet have these changes.
18+
* So until they're added, we define them manually.
19+
*/
20+
21+
/**
22+
* The line comment token, like `// this is a comment`.
23+
* Can be a string, an object with comment and optional noIndent properties, or null.
24+
*/
25+
export type LineComment = string | LineCommentConfig | null;
26+
27+
/**
28+
* Configuration for line comments.
29+
*/
30+
export interface LineCommentConfig {
31+
/**
32+
* The line comment token, like `//`
33+
*/
34+
comment: string;
35+
36+
/**
37+
* Whether the comment token should not be indented and placed at the first column.
38+
* Defaults to false.
39+
*/
40+
noIndent?: boolean;
41+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import {IPackageJson} from "package-json-type";
2+
3+
// Utility types for cleaner Map typing
4+
export type ExtensionMetaDataValue = ExtensionMetaData[keyof ExtensionMetaData];
5+
6+
/**
7+
* Extension metadata for a VSCode extension
8+
*/
9+
export interface ExtensionMetaData {
10+
/**
11+
* The unique ID in the form of `publisher.name`.
12+
*/
13+
id: string;
14+
15+
/**
16+
* The name.
17+
* Directly from package.json "name" key.
18+
*/
19+
name: string;
20+
21+
/**
22+
* The namespace for this extension's configuration settings,
23+
* which is a slightly shorter version of the name.
24+
*/
25+
namespace?: string;
26+
27+
/**
28+
* The display name.
29+
* Directly from package.json "displayName" key.
30+
*/
31+
displayName: string;
32+
33+
/**
34+
* The version.
35+
* Directly from package.json "version" key.
36+
*/
37+
version: string;
38+
39+
/**
40+
* The absolute path to the extension.
41+
*/
42+
extensionPath: string;
43+
44+
/**
45+
* The full package.json data
46+
*/
47+
packageJSON: IPackageJson;
48+
}
49+
50+
/**
51+
* Extension discovery paths configuration for this extension
52+
*/
53+
export interface ExtensionPaths {
54+
/**
55+
* The path to the user extensions.
56+
*/
57+
userExtensionsPath: string;
58+
59+
/**
60+
* The path to the built-in extensions.
61+
*/
62+
builtInExtensionsPath: string;
63+
64+
/**
65+
* The Windows path to the user extensions when running in WSL.
66+
*
67+
* Only set when running in WSL.
68+
*/
69+
WindowsUserExtensionsPathFromWsl?: string;
70+
71+
/**
72+
* The Windows path to the built-in extensions when running in WSL.
73+
*
74+
* Only set when running in WSL.
75+
*/
76+
WindowsBuiltInExtensionsPathFromWsl?: string;
77+
}

src/interfaces/settings.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export interface Settings {
2+
singleLineBlockOnEnter: boolean;
3+
disabledLanguages: string[];
4+
slashStyleBlocks: string[];
5+
hashStyleBlocks: string[];
6+
semicolonStyleBlocks: string[];
7+
multiLineStyleBlocks: string[];
8+
overrideDefaultLanguageMultiLineComments: Record<string, string>;
9+
bladeOverrideComments: boolean;
10+
}

0 commit comments

Comments
 (0)