From 8bdfab8e5ba7aaa60bfbe1617e91e2e535eb49ee Mon Sep 17 00:00:00 2001 From: tenninebt <5684363+tenninebt@users.noreply.github.com> Date: Thu, 26 Oct 2023 14:13:40 +0200 Subject: [PATCH] activate autoRefresh config + refactoring --- src/ConfigStore.ts | 190 ++++++++++++++++++++++++++++-------------- src/CoverageParser.ts | 4 +- src/DataProvider.ts | 86 +++++++++++-------- src/Extension.ts | 17 ++-- src/FilesLoader.ts | 36 ++++---- src/Logger.ts | 49 +++++++++++ 6 files changed, 253 insertions(+), 129 deletions(-) create mode 100644 src/Logger.ts diff --git a/src/ConfigStore.ts b/src/ConfigStore.ts index 9cec1b9..0e47ee0 100644 --- a/src/ConfigStore.ts +++ b/src/ConfigStore.ts @@ -1,26 +1,33 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ import * as vscode from "vscode" import * as rx from "rxjs" -import type * as vscodeLogging from "@vscode-logging/logger" +import type { Logger } from "./Logger" export class ConfigStore { private readonly configurationKey: string = "koverage" - private readonly _configChangedNotifier: rx.Subject - public readonly configChangedNotifier: rx.Observable + private readonly _configChanged: rx.Subject + public readonly ConfigChanged: rx.Observable + private readonly _perFolderConfigChanged: Map> - private readonly _perFolderConfig: Map> - public get(workspaceFolder: vscode.WorkspaceFolder): Config { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - return this._perFolderConfig.get(workspaceFolder.uri)!.value - } + private readonly _perFolderConfig: Map + + constructor(private readonly logger: Logger) { + this._configChanged = new rx.Subject() + this.ConfigChanged = this._configChanged.asObservable() - constructor(private readonly logger: vscodeLogging.IVSCodeExtLogger) { - this._configChangedNotifier = new rx.Subject() - this.configChangedNotifier = this._configChangedNotifier.asObservable() - this._perFolderConfig = new Map>() + this._perFolderConfigChanged = new Map> + this._perFolderConfig = new Map() + } - void this.readConfig() + public async init(): Promise { + await this.readConfig() + // Reload the cached values if a workspace folder is added or deleted + vscode.workspace.onDidChangeWorkspaceFolders(async (e) => { + this.cleanUpRemove(e.removed) + await this.readConfig() + }) // Reload the cached values if the configuration changes vscode.workspace.onDidChangeConfiguration(async (e) => { if (e.affectsConfiguration(this.configurationKey)) { @@ -29,56 +36,83 @@ export class ConfigStore { }) } - private async readWorkspaceConfig(workspaceFolder: vscode.WorkspaceFolder): Promise { - const updatedRawConfig = vscode.workspace.getConfiguration(this.configurationKey, workspaceFolder) - const updatedConfig = this.convertConfig(updatedRawConfig) - if (updatedConfig.isValid) { - let workspaceFolderConfig = this._perFolderConfig.get(workspaceFolder.uri) - if (workspaceFolderConfig == null) { - workspaceFolderConfig = new rx.BehaviorSubject(updatedConfig) - this._perFolderConfig.set(workspaceFolder.uri, workspaceFolderConfig) - } else { - workspaceFolderConfig.next(updatedConfig) - } - this._configChangedNotifier.next() - } else { - let rollbackConfig: Config - const current = this._perFolderConfig.get(workspaceFolder.uri)?.value - if (current?.isValid) { - rollbackConfig = current - } else { - const coverageCommand = updatedRawConfig.inspect("coverageCommand")?.defaultValue as string - const coverageFileNames = updatedRawConfig.inspect("coverageFileNames")?.defaultValue as string[] - const coverageFilePaths = updatedRawConfig.inspect("coverageFilePaths")?.defaultValue as string[] - const ignoredPathGlobs = updatedRawConfig.inspect("ignoredPathGlobs")?.defaultValue as string - const lowCoverageThreshold = updatedRawConfig.inspect("lowCoverageThreshold")?.defaultValue as number - const sufficientCoverageThreshold = updatedRawConfig.inspect("sufficientCoverageThreshold")?.defaultValue as number - rollbackConfig = new Config({ - coverageCommand, - coverageFileNames, - coverageFilePaths, - ignoredPathGlobs, - lowCoverageThreshold, - sufficientCoverageThreshold - }) - } - this.logger.warn(`Invalid configuration : ${JSON.stringify(updatedConfig, null, 2)}`) - this.logger.warn(`Last valid configuration will be used : ${JSON.stringify(rollbackConfig)}`) - await updatedRawConfig.update("coverageCommand", rollbackConfig.coverageCommand) - await updatedRawConfig.update("coverageFileNames", rollbackConfig.coverageFileNames) - await updatedRawConfig.update("coverageFilePaths", rollbackConfig.coverageFilePaths) - await updatedRawConfig.update("lowCoverageThreshold", rollbackConfig.lowCoverageThreshold) - await updatedRawConfig.update("sufficientCoverageThreshold", rollbackConfig.sufficientCoverageThreshold) - } + public getObservable(workspaceFolder: vscode.WorkspaceFolder): rx.Observable { + return this._perFolderConfigChanged.get(workspaceFolder.uri)!.asObservable() + } + + public get(workspaceFolder: vscode.WorkspaceFolder): Config { + return this._perFolderConfig.get(workspaceFolder.uri)! + } + + + private cleanUpRemove(removed: readonly vscode.WorkspaceFolder[]): void { + removed.forEach((workspaceFolder) => { + this._perFolderConfigChanged.get(workspaceFolder.uri)?.complete() + this._perFolderConfigChanged.delete(workspaceFolder.uri) + }) } private async readConfig(): Promise { + const promises = vscode.workspace.workspaceFolders?.map(async (workspaceFolder) => { await this.readWorkspaceConfig(workspaceFolder) }) await Promise.all(promises ?? [Promise.resolve()]) } + private async readWorkspaceConfig(workspaceFolder: vscode.WorkspaceFolder): Promise { + + const rawConfig = vscode.workspace.getConfiguration(this.configurationKey, workspaceFolder) + const defaultConfig = this.getDefaultConfig(rawConfig) + const newConfig = this.convertConfig(rawConfig) + const { invalidRules, validConfig } = newConfig.validate(defaultConfig) + + this._perFolderConfig.set(workspaceFolder.uri, validConfig) + if (invalidRules?.length) { + this.logger.warn(`Invalid configuration : \n${invalidRules.join("\n- ")}\nThe following configuration will be used :\n ${JSON.stringify(validConfig, null, 2)}`) + } + await this.publishConfigToVSCode(rawConfig, validConfig) + let notifier = this._perFolderConfigChanged.get(workspaceFolder.uri) + if (!notifier) { + notifier = new rx.Subject() + this._perFolderConfigChanged.set(workspaceFolder.uri, notifier) + } + notifier.next(validConfig) + } + + private getDefaultConfig(rawWorkspaceConfig: vscode.WorkspaceConfiguration): Config { + const coverageCommand = rawWorkspaceConfig.inspect("coverageCommand")?.defaultValue as string + const coverageFileNames = rawWorkspaceConfig.inspect("coverageFileNames")?.defaultValue as string[] + const coverageFilePaths = rawWorkspaceConfig.inspect("coverageFilePaths")?.defaultValue as string[] + const ignoredPathGlobs = rawWorkspaceConfig.inspect("ignoredPathGlobs")?.defaultValue as string + const lowCoverageThreshold = rawWorkspaceConfig.inspect("lowCoverageThreshold")?.defaultValue as number + const sufficientCoverageThreshold = rawWorkspaceConfig.inspect("sufficientCoverageThreshold")?.defaultValue as number + const autoRefresh = rawWorkspaceConfig.inspect("autoRefresh")?.defaultValue as boolean + const autoRefreshDebounce = rawWorkspaceConfig.inspect("autoRefreshDebounce")?.defaultValue as number + const defaultConfig = new Config({ + coverageCommand, + autoRefresh, + autoRefreshDebounce, + coverageFileNames, + coverageFilePaths, + ignoredPathGlobs, + lowCoverageThreshold, + sufficientCoverageThreshold + }) + return defaultConfig + } + + private async publishConfigToVSCode(updatedRawConfig: vscode.WorkspaceConfiguration, config: Config): Promise { + await updatedRawConfig.update("coverageCommand", config.coverageCommand) + await updatedRawConfig.update("autoRefresh", config.autoRefresh) + await updatedRawConfig.update("autoRefreshDebounce", config.autoRefreshDebounce) + await updatedRawConfig.update("coverageFileNames", config.coverageFileNames) + await updatedRawConfig.update("coverageFilePaths", config.coverageFilePaths) + await updatedRawConfig.update("lowCoverageThreshold", config.lowCoverageThreshold) + await updatedRawConfig.update("sufficientCoverageThreshold", config.sufficientCoverageThreshold) + } + + private convertConfig(workspaceConfiguration: vscode.WorkspaceConfiguration): Config { // Basic configurations const coverageCommand = workspaceConfiguration.get("coverageCommand") as string @@ -87,8 +121,12 @@ export class ConfigStore { const ignoredPathGlobs = workspaceConfiguration.get("ignoredPathGlobs") as string const lowCoverageThreshold = workspaceConfiguration.get("lowCoverageThreshold") as number const sufficientCoverageThreshold = workspaceConfiguration.get("sufficientCoverageThreshold") as number + const autoRefresh = workspaceConfiguration.get("autoRefresh") as boolean + const autoRefreshDebounce = workspaceConfiguration.get("autoRefreshDebounce") as number return new Config({ coverageCommand, + autoRefresh, + autoRefreshDebounce, coverageFileNames, coverageFilePaths, ignoredPathGlobs, @@ -99,9 +137,10 @@ export class ConfigStore { } export class Config { - public readonly isValid: boolean public coverageCommand: string + public autoRefresh: boolean + public autoRefreshDebounce: number public coverageFileNames: string[] public coverageFilePaths: string[] public ignoredPathGlobs: string @@ -114,7 +153,9 @@ export class Config { coverageFilePaths, ignoredPathGlobs, lowCoverageThreshold, - sufficientCoverageThreshold + sufficientCoverageThreshold, + autoRefresh, + autoRefreshDebounce, }: { coverageCommand: string coverageFileNames: string[] @@ -122,8 +163,12 @@ export class Config { ignoredPathGlobs: string lowCoverageThreshold: number sufficientCoverageThreshold: number + autoRefresh: boolean + autoRefreshDebounce: number }) { this.coverageCommand = coverageCommand + this.autoRefresh = autoRefresh + this.autoRefreshDebounce = autoRefreshDebounce this.coverageFileNames = coverageFileNames this.coverageFilePaths = coverageFilePaths this.ignoredPathGlobs = ignoredPathGlobs @@ -134,19 +179,40 @@ export class Config { // Make filePaths unique this.coverageFilePaths = [...new Set(this.coverageFilePaths)] - this.isValid = this.checkRules() === null } - private checkRules(): string | null { + public validate(defaultValues: Config): { validConfig: Config; invalidRules: string[] } { + let validConfig = { + ...this + } + const invalidRules: string[] = [] + if (this.sufficientCoverageThreshold <= 0 || this.sufficientCoverageThreshold > 100) { - return "Rule: 0 < sufficientCoverageThreshold < 100" + validConfig = { + ...validConfig, + sufficientCoverageThreshold: defaultValues.sufficientCoverageThreshold + } + invalidRules.push(`Rule: 0 < sufficientCoverageThreshold(${this.sufficientCoverageThreshold}) < 100`) } if (this.lowCoverageThreshold < 0 || this.lowCoverageThreshold >= 99) { - return "Rule: 0 <= lowCoverageThreshold < 99" + validConfig = { + ...validConfig, + lowCoverageThreshold: defaultValues.lowCoverageThreshold + } + invalidRules.push(`Rule: 0 <= lowCoverageThreshold(${this.lowCoverageThreshold}) < 99`) } if (this.sufficientCoverageThreshold < this.lowCoverageThreshold) { - return "sufficientCoverageThreshold > lowCoverageThreshold" + validConfig = { + ...validConfig, + lowCoverageThreshold: defaultValues.lowCoverageThreshold, + sufficientCoverageThreshold: defaultValues.sufficientCoverageThreshold + } + invalidRules.push(`sufficientCoverageThreshold(${this.sufficientCoverageThreshold}) > lowCoverageThreshold(${this.lowCoverageThreshold})`) + } + return { + validConfig, + invalidRules } - return null } } + diff --git a/src/CoverageParser.ts b/src/CoverageParser.ts index 3ae061f..4b8c94c 100644 --- a/src/CoverageParser.ts +++ b/src/CoverageParser.ts @@ -1,7 +1,7 @@ import util from "util" import * as iopath from "path" import type * as vscode from "vscode" -import type * as vscodeLogging from "@vscode-logging/logger" +import type { Logger } from "./Logger" import { type Section as CoverageSection, source } from "lcov-parse" import clover from "@cvrg-report/clover-json" import cobertura from "cobertura-parse" @@ -11,7 +11,7 @@ import { WorkspaceFolderCoverage, type WorkspaceFolderCoverageFiles } from "./Wo type CoverageFormat = "lcov-parse" | "clover-parse" | "jacoco-parse" | "cobertura-parse" export class CoverageParser { - constructor(private readonly logger: vscodeLogging.IVSCodeExtLogger) { } + constructor(private readonly logger: Logger) { } /** * Extracts coverage sections of type xml and lcov diff --git a/src/DataProvider.ts b/src/DataProvider.ts index 2f02b41..f00028f 100644 --- a/src/DataProvider.ts +++ b/src/DataProvider.ts @@ -1,4 +1,4 @@ -import type * as vscodeLogging from "@vscode-logging/logger" +import type { Logger } from "./Logger" import * as fs from "fs" import * as iopath from "path" import * as childProcess from "child_process" @@ -8,7 +8,7 @@ import { type CoverageParser } from "./CoverageParser" import { type FilesLoader } from "./FilesLoader" import { type Section as CoverageSection } from "lcov-parse" import { WorkspaceFolderCoverage } from "./WorkspaceFolderCoverageFile" -import { type Subscription, Subject, EMPTY, Observable, throttleTime, merge, map, share } from "rxjs" +import * as rx from "rxjs" import { type BaseNode, type CoverageNode, RootCoverageNode, FolderCoverageNode, FileCoverageNode } from "./TreeNodes" import { CoverageLevelThresholds } from "./CoverageLevel" @@ -18,15 +18,15 @@ export class FileCoverageDataProvider implements vscode.TreeDataProvider - private readonly refreshObservable: Observable - private readonly refreshSubscription: Subscription + private refreshSink: rx.Subject + private refreshObservable: rx.Observable + private refreshSubscription: rx.Subscription constructor( private readonly configStore: ConfigStore, private readonly coverageParser: CoverageParser, private readonly filesLoader: FilesLoader, - private readonly logger: vscodeLogging.IVSCodeExtLogger + private readonly logger: Logger ) { if (configStore === null || configStore === undefined) { throw new Error("configStore must be defined") @@ -42,10 +42,27 @@ export class FileCoverageDataProvider implements vscode.TreeDataProvider() - this.refreshObservable = merge(this.refreshSink, this.getConfigObservable(), this.getCoverageObservable()) - .pipe(throttleTime(3000)) + this.subscripeToRefreshEvents() + } + + private subscripeToRefreshEvents(): void { + if (!vscode.workspace.workspaceFolders) { + this.logger.warn("Empty workspace") + throw new Error("Empty workspace") + } + this.refreshSink = new rx.Subject() + const coverageObservablePerWorkspace = vscode.workspace.workspaceFolders?.map((workspaceFolder) => { + // Distinct debounce interval observable + const debounceIntervalObservable = this.configStore.getObservable(workspaceFolder).pipe(rx.map((config) => config.autoRefreshDebounce), rx.distinctUntilChanged()) + // Swaps the source observable (throttled with the new interval instead of the old interval), seamlessly to the observers + const getAutoSwapObservable = rx.switchMap((throttleInterval: number) => this.getCoverageObservable(workspaceFolder).pipe(rx.throttleTime(throttleInterval))) + const autoSwapObservable = getAutoSwapObservable(debounceIntervalObservable) + // Suspends the observable if autoRefresh is disabled + const suspendableObservable = autoSwapObservable.pipe(rx.filter((_) => this.configStore.get(workspaceFolder).autoRefresh)) + return suspendableObservable + }) + this.refreshObservable = rx.merge(this.refreshSink, this.getConfigObservable(), ...coverageObservablePerWorkspace) this.refreshSubscription = this.refreshObservable.subscribe((reason) => { this.logger.info(`Refreshing due to ${reason}...`) this._onDidChangeTreeData.fire(undefined) @@ -116,39 +133,34 @@ export class FileCoverageDataProvider implements vscode.TreeDataProvider { - return this.configStore.configChangedNotifier.pipe(map(() => "")) + private getConfigObservable(): rx.Observable { + return this.configStore.ConfigChanged.pipe(rx.map(() => "")) } - private getCoverageObservable(): Observable { - if (vscode.workspace.workspaceFolders == null) { + private getCoverageObservable(workspaceFolder: vscode.WorkspaceFolder): rx.Observable { + if (!workspaceFolder) { this.logger.debug("No file coverage in empty workspace") - return EMPTY + return rx.EMPTY } - return merge( - ...vscode.workspace.workspaceFolders.map((workspaceFolder) => { - return new Observable((observer) => { - const searchPattern = iopath.join( - workspaceFolder.uri.fsPath, - `**${iopath.sep}{${this.configStore.get(workspaceFolder)?.coverageFilePaths?.join(",")}}${iopath.sep}**}` - ) - this.logger.info(`createFileSystemWatcher(Pattern = ${searchPattern})`) - const coverageWatcher = vscode.workspace.createFileSystemWatcher(searchPattern) - const fileWatcherEvents = new Observable(observer => { - coverageWatcher.onDidCreate(() => observer.next("")) - coverageWatcher.onDidChange(() => observer.next("")) - coverageWatcher.onDidDelete(() => observer.next("")) - }) - const subscription = fileWatcherEvents.subscribe(observer) - return () => { - this.logger.info(`Dispose FileSystemWatcher(Pattern = ${searchPattern})`) - subscription.unsubscribe() - coverageWatcher.dispose() - } - }) + return new rx.Observable((observer) => { + const searchPattern = iopath.join( + workspaceFolder.uri.fsPath, + `**${iopath.sep}{${this.configStore.get(workspaceFolder)?.coverageFilePaths?.join(",")}}${iopath.sep}**}` + ) + this.logger.info(`createFileSystemWatcher(Pattern = ${searchPattern})`) + const coverageWatcher = vscode.workspace.createFileSystemWatcher(searchPattern) + const fileWatcherEvents = new rx.Observable(observer => { + coverageWatcher.onDidCreate(() => observer.next("")) + coverageWatcher.onDidChange(() => observer.next("")) + coverageWatcher.onDidDelete(() => observer.next("")) }) - ).pipe(share()) - + const subscription = fileWatcherEvents.subscribe(observer) + return () => { + this.logger.info(`Dispose FileSystemWatcher(Pattern = ${searchPattern})`) + subscription.unsubscribe() + coverageWatcher.dispose() + } + }).pipe(rx.share()) } private async getRawCoverageData(): Promise> { diff --git a/src/Extension.ts b/src/Extension.ts index 53d0bc3..211fe4f 100644 --- a/src/Extension.ts +++ b/src/Extension.ts @@ -6,25 +6,20 @@ import { FileCoverageDataProvider } from "./DataProvider" import { CoverageParser } from "./CoverageParser" import { FilesLoader } from "./FilesLoader" import { ConfigStore } from "./ConfigStore" -import * as vscodeLogging from "@vscode-logging/logger" +import { OutputChannelLogger } from "./Logger" // this method is called when your extension is activated // your extension is activated the very first time the command is executed -export function activate(context: vscode.ExtensionContext): void { - const outputChannel = vscode.window.createOutputChannel("Koverage") - const logger = vscodeLogging.getExtensionLogger({ - extName: "Koverage", - level: "debug", // See LogLevel type in @vscode-logging/types for possible logLevels - logPath: context.logUri.fsPath, // The logPath is only available from the `vscode.ExtensionContext` - logOutputChannel: outputChannel, // OutputChannel for the logger - sourceLocationTracking: false - }) +export async function activate(context: vscode.ExtensionContext): Promise { + + const logger = new OutputChannelLogger() // The command has been defined in the package.json file // Now provide the implementation of the command with registerCommand // The commandId parameter must match the command field in package.json const configStore = new ConfigStore(logger) + await configStore.init() const fileCoverageDataProvider = new FileCoverageDataProvider(configStore, new CoverageParser(logger), new FilesLoader(configStore, logger), logger) const treeView = vscode.window.createTreeView("koverage", { @@ -51,7 +46,7 @@ export function activate(context: vscode.ExtensionContext): void { context.subscriptions.push(generateCoverage) context.subscriptions.push(openFile) context.subscriptions.push(treeView) - context.subscriptions.push(outputChannel) + context.subscriptions.push(logger) } // this method is called when your extension is deactivated diff --git a/src/FilesLoader.ts b/src/FilesLoader.ts index 26d3092..e06f742 100644 --- a/src/FilesLoader.ts +++ b/src/FilesLoader.ts @@ -1,4 +1,4 @@ -import type * as vscodeLogging from "@vscode-logging/logger" +import type { Logger } from "./Logger" import { glob } from "glob" import { readFile } from "fs" import * as vscode from "vscode" @@ -6,7 +6,7 @@ import { type ConfigStore } from "./ConfigStore" import { WorkspaceFolderCoverageFile, WorkspaceFolderCoverageFiles } from "./WorkspaceFolderCoverageFile" export class FilesLoader { - constructor(private readonly configStore: ConfigStore, private readonly logger: vscodeLogging.IVSCodeExtLogger) { } + constructor(private readonly configStore: ConfigStore, private readonly logger: Logger) { } /** * Takes files and converts to data strings for coverage consumption @@ -22,26 +22,28 @@ export class FilesLoader { private async loadCoverageInWorkspace(): Promise> { const coverageFiles = new Map() - if (vscode.workspace.workspaceFolders != null) { - for (const workspaceFolder of vscode.workspace.workspaceFolders) { - const folderConfig = this.configStore.get(workspaceFolder) - const filesPaths = folderConfig?.coverageFilePaths ?? [] - const fileNames = folderConfig?.coverageFileNames ?? [] - for (const filePath of filesPaths) { - for (const fileName of fileNames) { - const coverageFileFullPath = await this.globFind(workspaceFolder, fileName, filePath) - for (const f of coverageFileFullPath) { - if (!coverageFiles.has(workspaceFolder.uri.fsPath)) { - coverageFiles.set(workspaceFolder.uri.fsPath, new WorkspaceFolderCoverageFiles(workspaceFolder)) - } - coverageFiles.get(workspaceFolder.uri.fsPath)?.coverageFiles.add(new WorkspaceFolderCoverageFile(f, await this.load(f))) + if (!vscode.workspace.workspaceFolders) { + this.logger.warn("Empty workspace") + throw new Error("Empty workspace") + } + + for (const workspaceFolder of vscode.workspace.workspaceFolders) { + const folderConfig = this.configStore.get(workspaceFolder) + const filesPaths = folderConfig?.coverageFilePaths ?? [] + const fileNames = folderConfig?.coverageFileNames ?? [] + for (const filePath of filesPaths) { + for (const fileName of fileNames) { + const coverageFileFullPath = await this.globFind(workspaceFolder, fileName, filePath) + + for (const f of coverageFileFullPath) { + if (!coverageFiles.has(workspaceFolder.uri.fsPath)) { + coverageFiles.set(workspaceFolder.uri.fsPath, new WorkspaceFolderCoverageFiles(workspaceFolder)) } + coverageFiles.get(workspaceFolder.uri.fsPath)?.coverageFiles.add(new WorkspaceFolderCoverageFile(f, await this.load(f))) } } } - } else { - this.logger.warn("Empty workspace") } return new Set(coverageFiles.values()) diff --git a/src/Logger.ts b/src/Logger.ts new file mode 100644 index 0000000..f4e90fb --- /dev/null +++ b/src/Logger.ts @@ -0,0 +1,49 @@ +import * as vscode from "vscode" + +export interface Logger { + trace: (message: string, ...args: any[]) => void + debug: (message: string, ...args: any[]) => void + info: (message: string, ...args: any[]) => void + warn: (message: string, data?: any) => void + error: (message: string, data?: any) => void +} + +export class OutputChannelLogger implements vscode.Disposable { + private readonly output: vscode.LogOutputChannel + + constructor(name = "Koverage") { + this.output = vscode.window.createOutputChannel(name, { log: true }) + } + + public get logLevel(): vscode.LogLevel { + return this.output.logLevel + } + + public info(message: string, ...args: any[]): void { + this.output.info(message, ...args) + } + + public debug(message: string, ...args: any[]): void { + this.output.debug(message, ...args) + } + + public trace(message: string, ...args: any[]): void { + this.output.trace(message, ...args) + } + + public warn(message: string, data?: any): void { + this.output.warn(message, ...(data ? [data] : [])) + } + + public error(message: string, data?: any): void { + // See https://github.com/microsoft/TypeScript/issues/10496 + if (data && data.message === 'No content available.') { + return + } + this.output.error(message, ...(data ? [data] : [])) + } + + public dispose(): void { + this.output.dispose() + } +} \ No newline at end of file