-
-
Notifications
You must be signed in to change notification settings - Fork 12
feat(vscode): support workspaces and monorepo #688
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,66 +1,69 @@ | ||||||||||||||||||||||||||||
| import * as v from 'valibot'; | ||||||||||||||||||||||||||||
| import vscode from 'vscode'; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Centralized configuration types for the extension. | ||||||||||||||||||||||||||||
| // Add new keys here to extend configuration in a type-safe way. | ||||||||||||||||||||||||||||
| export type ExtensionConfig = { | ||||||||||||||||||||||||||||
| const configSchema = v.object({ | ||||||||||||||||||||||||||||
| // Glob patterns that determine which files are considered tests. | ||||||||||||||||||||||||||||
| // Must be an array of strings. | ||||||||||||||||||||||||||||
| testFileGlobPattern: string[]; | ||||||||||||||||||||||||||||
| // The path to a package.json file of a Rstest executable. | ||||||||||||||||||||||||||||
| // Used as a last resort if the extension cannot auto-detect @rstest/core. | ||||||||||||||||||||||||||||
| rstestPackagePath?: string; | ||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| export const defaultConfig: ExtensionConfig = { | ||||||||||||||||||||||||||||
| // https://code.visualstudio.com/docs/editor/glob-patterns | ||||||||||||||||||||||||||||
| testFileGlobPattern: [ | ||||||||||||||||||||||||||||
| testFileGlobPattern: v.fallback(v.array(v.string()), [ | ||||||||||||||||||||||||||||
| '**/*.{test,spec}.[jt]s', | ||||||||||||||||||||||||||||
| '**/*.{test,spec}.[cm][jt]s', | ||||||||||||||||||||||||||||
| '**/*.{test,spec}.[jt]sx', | ||||||||||||||||||||||||||||
| '**/*.{test,spec}.[cm][jt]sx', | ||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||
| ]), | ||||||||||||||||||||||||||||
| // The path to a package.json file of a Rstest executable. | ||||||||||||||||||||||||||||
| // Used as a last resort if the extension cannot auto-detect @rstest/core. | ||||||||||||||||||||||||||||
| rstestPackagePath: v.fallback(v.optional(v.string()), undefined), | ||||||||||||||||||||||||||||
| configFileGlobPattern: v.fallback(v.array(v.string()), [ | ||||||||||||||||||||||||||||
| '**/rstest.config.{mjs,ts,js,cjs,mts,cts}', | ||||||||||||||||||||||||||||
| ]), | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| export type ExtensionConfig = v.InferOutput<typeof configSchema>; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Type-safe getter for a single config value with priority: | ||||||||||||||||||||||||||||
| // workspaceFolder > workspace > user (global) > default. | ||||||||||||||||||||||||||||
| // Type-safe getter for a single config value | ||||||||||||||||||||||||||||
| export function getConfigValue<K extends keyof ExtensionConfig>( | ||||||||||||||||||||||||||||
| key: K, | ||||||||||||||||||||||||||||
| folder?: vscode.WorkspaceFolder, | ||||||||||||||||||||||||||||
| scope?: vscode.ConfigurationScope | null, | ||||||||||||||||||||||||||||
| ): ExtensionConfig[K] { | ||||||||||||||||||||||||||||
| const section = vscode.workspace.getConfiguration('rstest', folder); | ||||||||||||||||||||||||||||
| const inspected = section.inspect<ExtensionConfig[K]>(key); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| // Priority order (highest first): folder, workspace, user, default | ||||||||||||||||||||||||||||
| const value = | ||||||||||||||||||||||||||||
| inspected?.workspaceFolderValue ?? | ||||||||||||||||||||||||||||
| inspected?.workspaceValue ?? | ||||||||||||||||||||||||||||
| inspected?.globalValue ?? | ||||||||||||||||||||||||||||
| inspected?.defaultValue ?? | ||||||||||||||||||||||||||||
| defaultConfig[key]; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| if (key === 'testFileGlobPattern') { | ||||||||||||||||||||||||||||
| const v = value as unknown; | ||||||||||||||||||||||||||||
| return (isStringArray(v) ? v : defaultConfig[key]) as ExtensionConfig[K]; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| if (key === 'rstestPackagePath') { | ||||||||||||||||||||||||||||
| const v = value as unknown; | ||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||
| typeof v === 'string' && v.trim().length > 0 ? v : undefined | ||||||||||||||||||||||||||||
| ) as ExtensionConfig[K]; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| return value as ExtensionConfig[K]; | ||||||||||||||||||||||||||||
| const value = vscode.workspace.getConfiguration('rstest', scope).get(key); | ||||||||||||||||||||||||||||
| return v.parse(configSchema.entries[key], value) as ExtensionConfig[K]; | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
| return v.parse(configSchema.entries[key], value) as ExtensionConfig[K]; | |
| const result = v.safeParse(configSchema.entries[key], value); | |
| if (result.success) { | |
| return result.output as ExtensionConfig[K]; | |
| } else { | |
| // If validation fails, use the fallback value if defined, otherwise undefined | |
| // v.fallback sets the default value, so we can parse 'undefined' to get it | |
| const fallbackResult = v.safeParse(configSchema.entries[key], undefined); | |
| if (fallbackResult.success) { | |
| return fallbackResult.output as ExtensionConfig[K]; | |
| } | |
| return undefined as ExtensionConfig[K]; | |
| } |
Copilot
AI
Nov 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using Object.fromEntries with Object.keys loses type safety. The iteration could include unexpected keys if configSchema.entries is modified. Consider using a more explicit approach or using Object.entries directly on a typed object.
Copilot
AI
Nov 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Creating a new CancellationTokenSource on each config change without proper cleanup could lead to resource leaks. While the old source is cancelled, consider explicitly disposing it with cancelSource.dispose() before creating a new one.
Copilot
AI
Nov 17, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The dispose handler calls both disposable.dispose() and cancelSource.cancel(), but doesn't call cancelSource.dispose(). Consider disposing the final CancellationTokenSource to prevent resource leaks.
| cancelSource.cancel(); | |
| cancelSource.cancel(); | |
| cancelSource.dispose(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
setting to
watch:localwill make debug waiting forpreLaunchTaskwhich will never stop, is that expected?