diff --git a/CHANGELOG.md b/CHANGELOG.md index 05d34ebba..e1a2f16fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ * Added preview Razor (cshtml) language service with support for C# completions and diagnostics in ASP.NET Core projects. Please report issues with the preview Razor tooling on the [aspnet/Razor.VSCode](https://github.com/aspnet/Razor.VSCode/issues) repo. To disable the preview Razor tooling set the "razor.disabled" setting to `true`. (PR: [2554](https://github.com/OmniSharp/omnisharp-vscode/pull/2554)) * Added omnisharp.minFindSymbolsFilterLength setting to configure the number of characters a user must type in for "Go to Symbol in Workspace" command to return any results (default is 0 to preserve existing behavior). Additionally added omnisharp.maxFindSymbolsItems for configuring maximum number of items returned by "Go to Symbol in Workspace" command. The default is 1000. (PR: [#2487](https://github.com/OmniSharp/omnisharp-vscode/pull/2487)) * Added a command - "CSharp: Start authoring a new issue on GitHub" to enable the users to file issues on github from within the extension with helpful config information from their system.(PR: [#2503](https://github.com/OmniSharp/omnisharp-vscode/pull/2503)) +* Added a `csharp.maxProjectFileCountForDiagnosticAnalysis` setting to configure the file limit when the extension stops reporting errors for whole workspace. When this threshold is reached, the diagnostics are reported for currently opened files only. This mechanism was available in previous versions, but now can be configured. The default is 1000. (PR: [#1877](https://github.com/OmniSharp/omnisharp-vscode/pull/1877)) #### Debugger * Fixed crash at the end of debug sessions on Linux ([#2439](https://github.com/OmniSharp/omnisharp-vscode/issues/2439)) @@ -42,7 +43,7 @@ * Modified the "Unresolved dependencies" prompt to restore the all the projects in the currently selected solution or workspace. (PR: [#2323](https://github.com/OmniSharp/omnisharp-vscode/pull/2323)) -* Added support to configure the default *.sln file loaded when opening a project with multiple *.sln files in the root. _(Contributed by [@janaka](https://github.com/janaka))_ (PR: [#2053](https://github.com/OmniSharp/omnisharp-vscode/pull/2053)) +* Added support to configure the default *.sln file loaded when opening a project with multiple *.sln files in the root. _(Contributed by [@janaka](https://github.com/janaka))_ (PR: [#2053](https://github.com/OmniSharp/omnisharp-vscode/pull/2053)) * Added support for tracking opening, closing and changing of virtual documents that don't exist on disk. (PR: [#2436](https://github.com/OmniSharp/omnisharp-vscode/pull/2436)) _(Contributed by [@NTaylorMullen](https://github.com/NTaylorMullen))_ @@ -61,7 +62,7 @@ #### Testing * Added test execution output to the output of the Run/Debug Test CodeLens. (PR: [#2337](https://github.com/OmniSharp/omnisharp-vscode/pull/2337), [#2343](https://github.com/OmniSharp/omnisharp-vscode/pull/2343), [omnisharp-roslyn#1203](https://github.com/OmniSharp/omnisharp-roslyn/pull/1203)) -* Fixed a bug where a debug session could not be started and duplicate logs were displayed after a previous one failed due to build failure. (PR: [#2405](https://github.com/OmniSharp/omnisharp-vscode/pull/2405), [omnisharp-roslyn#1239](https://github.com/OmniSharp/omnisharp-roslyn/pull/1239)) +* Fixed a bug where a debug session could not be started and duplicate logs were displayed after a previous one failed due to build failure. (PR: [#2405](https://github.com/OmniSharp/omnisharp-vscode/pull/2405), [omnisharp-roslyn#1239](https://github.com/OmniSharp/omnisharp-roslyn/pull/1239)) #### Options diff --git a/package.json b/package.json index ce4b68029..0b21546af 100644 --- a/package.json +++ b/package.json @@ -566,6 +566,11 @@ "default": true, "description": "Specifies whether the run and debug test CodeLens should be show be shown." }, + "csharp.maxProjectFileCountForDiagnosticAnalysis": { + "type": "number", + "default": 1000, + "description": "Specifies the maximum number of files for which diagnostics are reported for the whole workspace. If this limit is exceeded, diagnostics will be shown for currently opened files only. Specify 0 or less to disable the limit completely." + }, "omnisharp.path": { "type": [ "string", diff --git a/src/CSharpExtensionExports.ts b/src/CSharpExtensionExports.ts index f5eefaf41..51f992acb 100644 --- a/src/CSharpExtensionExports.ts +++ b/src/CSharpExtensionExports.ts @@ -3,6 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Advisor } from "./features/diagnosticsProvider"; + export default interface CSharpExtensionExports { initializationFinished: () => Promise; + + getAdvisor: () => Promise; } \ No newline at end of file diff --git a/src/features/diagnosticsProvider.ts b/src/features/diagnosticsProvider.ts index feacb7236..ccb7a8f10 100644 --- a/src/features/diagnosticsProvider.ts +++ b/src/features/diagnosticsProvider.ts @@ -13,6 +13,7 @@ import CompositeDisposable from '../CompositeDisposable'; import { IDisposable } from '../Disposable'; import { isVirtualCSharpDocument } from './virtualDocumentTracker'; import { TextDocument } from '../vscodeAdapter'; +import OptionProvider from '../observers/OptionProvider'; export class Advisor { @@ -21,7 +22,7 @@ export class Advisor { private _packageRestoreCounter: number = 0; private _projectSourceFileCounts: { [path: string]: number } = Object.create(null); - constructor(server: OmniSharpServer) { + constructor(server: OmniSharpServer, private optionProvider: OptionProvider) { this._server = server; let d1 = server.onProjectChange(this._onProjectChange, this); @@ -44,7 +45,7 @@ export class Advisor { public shouldValidateProject(): boolean { return this._isServerStarted() && !this._isRestoringPackages() - && !this._isHugeProject(); + && !this._isOverFileLimit(); } private _updateProjectFileCount(path: string, fileCount: number): void { @@ -99,15 +100,18 @@ export class Advisor { return this._server.isRunning(); } - private _isHugeProject(): boolean { - let sourceFileCount = 0; - for (let key in this._projectSourceFileCounts) { - sourceFileCount += this._projectSourceFileCounts[key]; - if (sourceFileCount > 1000) { - return true; + private _isOverFileLimit(): boolean { + let opts = this.optionProvider.GetLatestOptions(); + let fileLimit = opts.maxProjectFileCountForDiagnosticAnalysis; + if (fileLimit > 0) { + let sourceFileCount = 0; + for (let key in this._projectSourceFileCounts) { + sourceFileCount += this._projectSourceFileCounts[key]; + if (sourceFileCount > fileLimit) { + return true; + } } } - return false; } } diff --git a/src/main.ts b/src/main.ts index 6b5e7ba52..08fb6f193 100644 --- a/src/main.ts +++ b/src/main.ts @@ -30,7 +30,7 @@ import { ProjectStatusBarObserver } from './observers/ProjectStatusBarObserver'; import CSharpExtensionExports from './CSharpExtensionExports'; import { vscodeNetworkSettingsProvider, NetworkSettingsProvider } from './NetworkSettings'; import { ErrorMessageObserver } from './observers/ErrorMessageObserver'; -import OptionProvider from './observers/OptionProvider'; +import OptionProvider from './observers/OptionProvider'; import DotNetTestChannelObserver from './observers/DotnetTestChannelObserver'; import DotNetTestLoggerObserver from './observers/DotnetTestLoggerObserver'; import { ShowOmniSharpConfigChangePrompt } from './observers/OptionChangeObserver'; @@ -65,7 +65,7 @@ export async function activate(context: vscode.ExtensionContext): Promise { - let omniSharp = await omniSharpPromise; - await omniSharp.waitForEmptyEventQueue(); + let langService = await langServicePromise; + await langService.server.waitForEmptyEventQueue(); await coreClrDebugPromise; + }, + getAdvisor: async () => { + let langService = await langServicePromise; + return langService.advisor; } }; } diff --git a/src/omnisharp/extension.ts b/src/omnisharp/extension.ts index d5b39006a..3e4559c97 100644 --- a/src/omnisharp/extension.ts +++ b/src/omnisharp/extension.ts @@ -39,7 +39,10 @@ import { StructureProvider } from '../features/structureProvider'; import { OmniSharpMonoResolver } from './OmniSharpMonoResolver'; import { getMonoVersion } from '../utils/getMonoVersion'; -export let omnisharp: OmniSharpServer; +export interface ActivationResult { + readonly server: OmniSharpServer; + readonly advisor: Advisor; +} export async function activate(context: vscode.ExtensionContext, packageJSON: any, platformInfo: PlatformInformation, provider: NetworkSettingsProvider, eventStream: EventStream, optionProvider: OptionProvider, extensionPath: string) { const documentSelector: vscode.DocumentSelector = { @@ -49,8 +52,7 @@ export async function activate(context: vscode.ExtensionContext, packageJSON: an const options = optionProvider.GetLatestOptions(); let omnisharpMonoResolver = new OmniSharpMonoResolver(getMonoVersion); const server = new OmniSharpServer(vscode, provider, packageJSON, platformInfo, eventStream, optionProvider, extensionPath, omnisharpMonoResolver); - omnisharp = server; - const advisor = new Advisor(server); // create before server is started + const advisor = new Advisor(server, optionProvider); // create before server is started const disposables = new CompositeDisposable(); let localDisposables: CompositeDisposable; @@ -176,7 +178,7 @@ export async function activate(context: vscode.ExtensionContext, packageJSON: an context.subscriptions.push(disposables); - return new Promise(resolve => + return new Promise(resolve => server.onServerStart(e => - resolve(server))); + resolve({ server, advisor }))); } \ No newline at end of file diff --git a/src/omnisharp/options.ts b/src/omnisharp/options.ts index a42ef0254..a7d6e0cf5 100644 --- a/src/omnisharp/options.ts +++ b/src/omnisharp/options.ts @@ -26,7 +26,8 @@ export class Options { public razorDevMode: boolean, public razorPluginPath?: string, public defaultLaunchSolution?: string, - public monoPath?: string) { } + public monoPath?: string, + public maxProjectFileCountForDiagnosticAnalysis?: number | null) { } public static Read(vscode: vscode): Options { @@ -76,9 +77,11 @@ export class Options { const razorDevMode = !!razorConfig && razorConfig.get('devmode', false); const razorPluginPath = razorConfig ? razorConfig.get('plugin.path', undefined) : undefined; + const maxProjectFileCountForDiagnosticAnalysis = csharpConfig.get('maxProjectFileCountForDiagnosticAnalysis', 1000); + return new Options( - path, - useGlobalMono, + path, + useGlobalMono, waitForDebugger, loggingLevel, autoStart, @@ -97,6 +100,7 @@ export class Options { razorPluginPath, defaultLaunchSolution, monoPath, + maxProjectFileCountForDiagnosticAnalysis, ); } @@ -130,7 +134,7 @@ export class Options { return toUseGlobalMonoValue(omnisharpConfig.get('useMono')); } else if (csharpConfig.has('omnisharpUsesMono')) { - // BACKCOMPAT: If 'csharp.omnisharpUsesMono' setting was found, true maps to "always" and false maps to "auto" + // BACKCOMPAT: If 'csharp.omnisharpUsesMono' setting was found, true maps to "always" and false maps to "auto" return toUseGlobalMonoValue(csharpConfig.get('omnisharpUsesMono')); } else { diff --git a/test/integrationTests/advisor.integration.test.ts b/test/integrationTests/advisor.integration.test.ts new file mode 100644 index 000000000..02633495a --- /dev/null +++ b/test/integrationTests/advisor.integration.test.ts @@ -0,0 +1,81 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; + +import { expect } from 'chai'; +import * as path from 'path'; +import { activateCSharpExtension } from './integrationHelpers'; +import testAssetWorkspace from './testAssets/testAssetWorkspace'; + +import { Advisor } from '../../src/features/diagnosticsProvider'; + +const chai = require('chai'); +chai.use(require('chai-arrays')); +chai.use(require('chai-fs')); + +function setLimit(to: number | null) { + let csharpConfig = vscode.workspace.getConfiguration('csharp'); + return csharpConfig.update('maxProjectFileCountForDiagnosticAnalysis', to); +} + +suite(`Advisor ${testAssetWorkspace.description}`, function () { + let advisor: Advisor; + + suiteSetup(async function () { + await testAssetWorkspace.restore(); + let activationResult = await activateCSharpExtension(); + if (!activationResult) { + throw new Error('Cannot activate extension.'); + } else { + advisor = activationResult.advisor; + } + + let fileName = 'completion.cs'; + let dir = testAssetWorkspace.projects[0].projectDirectoryPath; + let fileUri = vscode.Uri.file(path.join(dir, fileName)); + await vscode.commands.executeCommand('vscode.open', fileUri); + }); + + suiteTeardown(async () => { + await testAssetWorkspace.cleanupWorkspace(); + }); + + test('Advisor.shouldValidateProject returns true when maxProjectFileCountForDiagnosticAnalysis is higher than the file count', async () => { + await setLimit(1000); + + expect(advisor.shouldValidateProject()).to.be.true; + }); + + test('Advisor.shouldValidateFiles returns true when maxProjectFileCountForDiagnosticAnalysis is higher than the file count', async () => { + await setLimit(1000); + + expect(advisor.shouldValidateFiles()).to.be.true; + }); + + test('Advisor.shouldValidateProject returns false when maxProjectFileCountForDiagnosticAnalysis is lower than the file count', async () => { + await setLimit(1); + + expect(advisor.shouldValidateProject()).to.be.false; + }); + + test('Advisor.shouldValidateFiles returns true when maxProjectFileCountForDiagnosticAnalysis is lower than the file count', async () => { + await setLimit(1); + + expect(advisor.shouldValidateFiles()).to.be.true; + }); + + test('Advisor.shouldValidateProject returns true when maxProjectFileCountForDiagnosticAnalysis is null', async () => { + await setLimit(null); + + expect(advisor.shouldValidateProject()).to.be.true; + }); + + test('Advisor.shouldValidateFiles returns true when maxProjectFileCountForDiagnosticAnalysis is null', async () => { + await setLimit(null); + + expect(advisor.shouldValidateFiles()).to.be.true; + }); +}); \ No newline at end of file diff --git a/test/integrationTests/integrationHelpers.ts b/test/integrationTests/integrationHelpers.ts index 382e5eb66..510172857 100644 --- a/test/integrationTests/integrationHelpers.ts +++ b/test/integrationTests/integrationHelpers.ts @@ -5,8 +5,13 @@ import * as vscode from 'vscode'; import CSharpExtensionExports from '../../src/CSharpExtensionExports'; +import { Advisor } from '../../src/features/diagnosticsProvider'; -export async function activateCSharpExtension(): Promise { +export interface ActivationResult { + readonly advisor: Advisor; +} + +export async function activateCSharpExtension(): Promise { const csharpExtension = vscode.extensions.getExtension("ms-vscode.csharp"); if (!csharpExtension.isActive) { @@ -16,8 +21,10 @@ export async function activateCSharpExtension(): Promise { try { await csharpExtension.exports.initializationFinished(); console.log("ms-vscode.csharp activated"); + return { advisor: await csharpExtension.exports.getAdvisor() }; } catch (err) { console.log(JSON.stringify(err)); + return undefined; } -} \ No newline at end of file +} diff --git a/test/integrationTests/testAssets/slnWithCsproj/.vscode/settings.json b/test/integrationTests/testAssets/slnWithCsproj/.vscode/settings.json index 887970772..c80abf42f 100644 --- a/test/integrationTests/testAssets/slnWithCsproj/.vscode/settings.json +++ b/test/integrationTests/testAssets/slnWithCsproj/.vscode/settings.json @@ -1,4 +1,5 @@ { "omnisharp.defaultLaunchSolution": "b_SecondInOrder_SlnFile.sln", "omnisharp.path": "latest", + "csharp.maxProjectFileCountForDiagnosticAnalysis": 1000, } \ No newline at end of file