Skip to content
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

feat: show diagnostics #11954

Merged
merged 6 commits into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions packages/api/src/qm/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,11 @@ export interface UserInteraction {
selectFileOrInput?(
config: SingleFileOrInputConfig
): Promise<Result<InputResult<string>, FxError>>;

/**
* Supports in VSC only for now. Show diagnostic message in editor.
*/
showDiagnosticInfo?(diagnostics: IDiagnosticInfo[]): void;
}

export interface IProgressHandler {
Expand All @@ -429,3 +434,70 @@ export interface IProgressHandler {
*/
end: (success: boolean, hideAfterFinish?: boolean) => Promise<void>;
}

export enum DiagnosticSeverity {
/**
* Something not allowed by the rules of a language or other means.
*/
Error = 0,

/**
* Something suspicious but allowed.
*/
Warning = 1,

/**
* Something to inform about but not a problem.
*/
Information = 2,

/**
* Something to hint to a better way of doing it, like proposing
* a refactoring.
*/
Hint = 3,
}

export interface IDiagnosticInfo {
/**
* Path of file where diagnostic shows.
*/
filePath: string;
/**
* Line number where diagnostic info starts.
*/
startLine: number;
/**
* Index of the beginning character where diagnostic info shows
*/
startIndex: number;
/**
* Line number where diagnostic info ends.
*/
endLine: number;
/**
* Index of the end character where diagnostic info ends.
*/
endIndex: number;
/**
* Message.
*/
message: string;
/**
* Severity.
*/
severity: DiagnosticSeverity;
/**
* A code or identifier for this diagnostic.
*/
code?: {
/**
* Value.
*/
value: string;
/**
* Link to open with more information about the diagnostic error.
*/
link: string;
};
}
1 change: 1 addition & 0 deletions packages/fx-core/src/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export class FeatureFlagName {
static readonly NewGenerator = "TEAMSFX_NEW_GENERATOR";
static readonly SMEOAuth = "SME_OAUTH";
static readonly CustomizeGpt = "TEAMSFX_DECLARATIVE_COPILOT";
static readonly ShowDiagnostics = "TEAMSFX_SHOW_DIAGNOSTICS";
}

export function getAllowedAppMaps(): Record<string, string> {
Expand Down
4 changes: 4 additions & 0 deletions packages/fx-core/src/common/featureFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ export class FeatureFlags {
};
static readonly SMEOAuth = { name: FeatureFlagName.SMEOAuth, defaultValue: "false" };
static readonly CustomizeGpt = { name: FeatureFlagName.CustomizeGpt, defaultValue: "false" };
static readonly ShowDiagnostics = {
name: FeatureFlagName.ShowDiagnostics,
defaultValue: "false",
};
}

export class FeatureFlagManager {
Expand Down
7 changes: 5 additions & 2 deletions packages/vscode-extension/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import * as exp from "./exp";
import { TreatmentVariableValue, TreatmentVariables } from "./exp/treatmentVariables";
import { FeatureFlags } from "./featureFlags";
import {
diagnosticCollection,
initializeGlobalVariables,
isExistingUser,
isOfficeAddInProject,
Expand Down Expand Up @@ -309,7 +310,7 @@ function activateTeamsFxRegistration(context: vscode.ExtensionContext) {
);

if (vscode.workspace.isTrusted) {
registerCodelensAndHoverProviders(context);
registerLanguageFeatures(context);
}

registerDebugConfigProviders(context);
Expand Down Expand Up @@ -1001,7 +1002,7 @@ async function setTDPIntegrationEnabledContext() {
);
}

function registerCodelensAndHoverProviders(context: vscode.ExtensionContext) {
function registerLanguageFeatures(context: vscode.ExtensionContext) {
// Setup CodeLens provider for userdata file
const codelensProvider = new CryptoCodeLensProvider();
const envDataSelector = {
Expand Down Expand Up @@ -1160,6 +1161,8 @@ function registerCodelensAndHoverProviders(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.languages.registerCodeLensProvider(yamlFileSelector, yamlCodelensProvider)
);

context.subscriptions.push(diagnosticCollection);
}

function registerOfficeDevCodeLensProviders(context: vscode.ExtensionContext) {
Expand Down
5 changes: 5 additions & 0 deletions packages/vscode-extension/src/globalVariables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export let defaultExtensionLogPath: string;
export let commandIsRunning = false;
export let core: FxCore;
export let tools: Tools;
export let diagnosticCollection: vscode.DiagnosticCollection; // Collection of diagnositcs after running app validation.

if (vscode.workspace && vscode.workspace.workspaceFolders) {
if (vscode.workspace.workspaceFolders.length > 0) {
Expand Down Expand Up @@ -87,3 +88,7 @@ export function setTools(toolsInstance: Tools) {
export function setCore(coreInstance: FxCore) {
core = coreInstance;
}

export function setDiagnosticCollection(collection: vscode.DiagnosticCollection) {
diagnosticCollection = collection;
}
54 changes: 53 additions & 1 deletion packages/vscode-extension/src/qm/vsc_ui.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import { commands, ExtensionContext, extensions } from "vscode";
import {
commands,
Diagnostic,
ExtensionContext,
extensions,
Uri,
Range,
Position,
languages,
} from "vscode";

import {
err,
FxError,
IDiagnosticInfo,
InputResult,
ok,
Result,
Expand All @@ -27,6 +37,9 @@ import {
TelemetryEvent,
TelemetryProperty,
} from "../telemetry/extTelemetryEvents";
import { diagnosticCollection, setDiagnosticCollection } from "../globalVariables";
import { featureFlagManager } from "@microsoft/teamsfx-core";
import { FeatureFlags } from "@microsoft/teamsfx-core";

export class TTKLocalizer implements Localizer {
loadingOptionsPlaceholder(): string {
Expand Down Expand Up @@ -136,6 +149,45 @@ export class VsCodeUI extends VSCodeUI {
}
return res;
}

showDiagnosticInfo(diagnostics: IDiagnosticInfo[]): void {
if (!featureFlagManager.getBooleanValue(FeatureFlags.ShowDiagnostics)) {
return;
}
if (!diagnosticCollection) {
const collection = languages.createDiagnosticCollection("teamstoolkit");
setDiagnosticCollection(collection);
} else {
diagnosticCollection.clear();
}
const diagnosticMap: Map<string, Diagnostic[]> = new Map();
for (const diagnostic of diagnostics) {
let diagnosticsOfFile = diagnosticMap.get(diagnostic.filePath);
if (!diagnosticsOfFile) {
diagnosticsOfFile = [];
diagnosticMap.set(diagnostic.filePath, diagnosticsOfFile);
}

const diagnosticInVSC = new Diagnostic(
new Range(
new Position(diagnostic.startLine, diagnostic.startIndex),
new Position(diagnostic.endLine, diagnostic.endIndex)
),
diagnostic.message,
diagnostic.severity
);
if (diagnostic.code) {
diagnosticInVSC.code = {
value: diagnostic.code.value,
target: Uri.parse(diagnostic.code.link),
};
}
diagnosticsOfFile.push(diagnosticInVSC);
}
diagnosticMap.forEach((diags, filePath) => {
diagnosticCollection.set(Uri.file(filePath), diags);
});
}
}

export function initVSCodeUI(context: ExtensionContext) {
Expand Down
7 changes: 6 additions & 1 deletion packages/vscode-extension/test/mocks/vscode-mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ class MockClipboard {
}

export function initialize() {
generateMock("languages");
generateMock("debug");
generateMock("scm");
generateNotebookMocks();
Expand Down Expand Up @@ -195,6 +194,12 @@ mockedVSCode.extensions = {
all: [],
};

(mockedVSCode as any).languages = {
createDiagnosticCollection: () => {},
registerCodeLensProvider: () => {},
registerHoverProvider: () => {},
};

// Setup commands APIs
mockedVSCode.commands = {
executeCommand: () => {
Expand Down
83 changes: 83 additions & 0 deletions packages/vscode-extension/test/qm/vsc_ui.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import * as sinon from "sinon";
import { stubInterface } from "ts-sinon";
import {
commands,
DiagnosticCollection,
Disposable,
ExtensionContext,
languages,
QuickInputButton,
QuickPick,
Terminal,
Expand All @@ -30,6 +32,8 @@ import { FxQuickPickItem, sleep, UserCancelError } from "@microsoft/vscode-ui";
import { VsCodeUI } from "../../src/qm/vsc_ui";
import { ExtTelemetry } from "../../src/telemetry/extTelemetry";
import { VsCodeLogProvider } from "../../src/commonlib/log";
import { featureFlagManager } from "@microsoft/teamsfx-core";
import * as globalVariables from "../../src/globalVariables";

describe("UI Unit Tests", async () => {
afterEach(() => {
Expand Down Expand Up @@ -939,4 +943,83 @@ describe("UI Unit Tests", async () => {
}
});
});

describe("showDiagnosticInfo", () => {
const sandbox = sinon.createSandbox();
let collection: DiagnosticCollection | undefined;

afterEach(() => {
sandbox.restore();
globalVariables.setDiagnosticCollection(undefined as unknown as DiagnosticCollection);
});

it("do nothing if feature flag is disabled", () => {
sandbox.stub(featureFlagManager, "getBooleanValue").returns(false);
const ui = new VsCodeUI(<ExtensionContext>{});
ui.showDiagnosticInfo([]);
});

it("show diagnostics first time if feature flag is enabled", () => {
const records: [string, { message: string }][] = [];
sandbox.stub(featureFlagManager, "getBooleanValue").returns(true);
collection = {
set: (filePath: string, diag: { message: string }) => {
records.push([filePath, diag]);
},
} as unknown as DiagnosticCollection;

sandbox.stub(languages, "createDiagnosticCollection").returns(collection as any);
const ui = new VsCodeUI(<ExtensionContext>{});

ui.showDiagnosticInfo([
{
startIndex: 0,
startLine: 1,
endIndex: 10,
endLine: 10,
severity: 2,
filePath: "test",
message: "error",
},
]);

expect(globalVariables.diagnosticCollection).not.undefined;
expect(records.length).equals(1);
});

it("show diagnostics not first time if feature flag is enabled", () => {
const records: [string, { message: string }][] = [];
sandbox.stub(featureFlagManager, "getBooleanValue").returns(true);
collection = {
clear: () => {
return;
},
set: (filePath: string, diag: { message: string }) => {
records.push([filePath, diag]);
},
} as unknown as DiagnosticCollection;

globalVariables.setDiagnosticCollection(collection);
const ui = new VsCodeUI(<ExtensionContext>{});

ui.showDiagnosticInfo([
{
startIndex: 0,
startLine: 1,
endIndex: 10,
endLine: 10,
severity: 2,
filePath: "test",
message: "error",
code: {
value: "test",
link: "https://test.com",
},
},
]);

expect(globalVariables.diagnosticCollection).not.undefined;
expect(records.length).equals(1);
});
});
});
Loading