Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
b0ac596
Merge pull request #1 from microsoft/main
mzhongl524 Nov 22, 2024
3fcbe52
Merge branch 'microsoft:main' into main
mzhongl524 Nov 25, 2024
c109816
Merge branch 'microsoft:main' into main
mzhongl524 Dec 6, 2024
23f0108
Merge branch 'microsoft:main' into main
mzhongl524 Dec 12, 2024
6f04f65
Merge branch 'microsoft:main' into main
mzhongl524 Dec 23, 2024
688ca03
Merge branch 'microsoft:main' into main
mzhongl524 Dec 24, 2024
de3725e
Merge branch 'microsoft:main' into main
mzhongl524 Dec 30, 2024
8130ffb
Merge branch 'microsoft:main' into main
mzhongl524 Jan 3, 2025
beb6433
Merge branch 'microsoft:main' into main
mzhongl524 Jan 15, 2025
8a501f2
Merge branch 'microsoft:main' into main
mzhongl524 Jan 15, 2025
6e1ab2d
Merge branch 'microsoft:main' into main
mzhongl524 Jan 23, 2025
a35052f
Merge branch 'microsoft:main' into main
mzhongl524 Jan 24, 2025
be57c13
Merge branch 'microsoft:main' into main
mzhongl524 Feb 5, 2025
3a9dc7a
Merge branch 'microsoft:main' into main
mzhongl524 Feb 10, 2025
ad7dff7
Merge branch 'microsoft:main' into main
mzhongl524 Feb 10, 2025
f8e8f2f
Merge branch 'microsoft:main' into main
mzhongl524 Feb 12, 2025
7a700c8
Merge branch 'microsoft:main' into main
mzhongl524 Feb 14, 2025
5099583
Merge branch 'microsoft:main' into main
mzhongl524 Feb 18, 2025
a45dbd5
Merge branch 'microsoft:main' into main
mzhongl524 Feb 25, 2025
68cc91d
Merge branch 'microsoft:main' into main
mzhongl524 Feb 25, 2025
48a44ad
Merge branch 'microsoft:main' into main
mzhongl524 Feb 27, 2025
b9283c4
Merge branch 'microsoft:main' into main
mzhongl524 Feb 28, 2025
4286411
Merge branch 'microsoft:main' into main
mzhongl524 Feb 28, 2025
e549035
Merge branch 'microsoft:main' into main
mzhongl524 Mar 5, 2025
9717310
Merge branch 'microsoft:main' into main
mzhongl524 Mar 6, 2025
75260f8
Merge branch 'microsoft:main' into main
mzhongl524 Mar 10, 2025
18b1595
Merge branch 'microsoft:main' into main
mzhongl524 Mar 11, 2025
51078a7
Merge branch 'microsoft:main' into main
mzhongl524 Mar 13, 2025
75ae17a
Merge branch 'microsoft:main' into main
mzhongl524 Mar 17, 2025
da28d95
Merge branch 'microsoft:main' into main
mzhongl524 Mar 18, 2025
4afbb98
Merge branch 'microsoft:main' into main
mzhongl524 Mar 25, 2025
463bdd5
Merge branch 'microsoft:main' into main
mzhongl524 Mar 27, 2025
c105d86
Merge branch 'microsoft:main' into main
mzhongl524 Mar 27, 2025
39dc63e
Merge branch 'microsoft:main' into main
mzhongl524 Mar 31, 2025
eda8815
install the packages for unrecognized import
mzhongl524 Mar 31, 2025
ddb0c11
Merge branch 'microsoft:main' into main
mzhongl524 Apr 1, 2025
88a65dc
updated
mzhongl524 Apr 8, 2025
dccb3dc
Merge branch 'microsoft:main' into main
mzhongl524 Apr 8, 2025
af9903b
Merge branch 'microsoft:main' into main
mzhongl524 Apr 10, 2025
655c92e
updated
mzhongl524 Apr 11, 2025
2dddaf5
Merge branch 'microsoft:main' into main
mzhongl524 Apr 15, 2025
857b72b
Merge branch 'microsoft:main' into main
mzhongl524 Apr 16, 2025
41f34d9
Merge branch 'microsoft:main' into main
mzhongl524 Apr 17, 2025
f4dd38f
Merge branch 'microsoft:main' into main
mzhongl524 Apr 18, 2025
4fd48e5
Merge branch 'microsoft:main' into main
mzhongl524 Apr 21, 2025
9870818
Merge branch 'microsoft:main' into main
mzhongl524 Apr 22, 2025
9bf8cdc
Merge branch 'microsoft:main' into main
mzhongl524 Apr 25, 2025
4c93b9f
Merge branch 'main' into install-packages-for-unrecognized-import
mzhongl524 Apr 28, 2025
b261216
Merge branch 'main' into install-packages-for-unrecognized-import
mzhongl524 Apr 29, 2025
bf86de8
updated
mzhongl524 Apr 29, 2025
a3225b7
Merge branch 'main' into install-packages-for-unrecognized-import
mzhongl524 Apr 29, 2025
ba7f3da
updated
mzhongl524 Apr 30, 2025
00c3a0c
updated
mzhongl524 May 6, 2025
5c9feb4
updated
mzhongl524 May 6, 2025
1d55f2b
updated
mzhongl524 May 7, 2025
3bb7975
updated
mzhongl524 May 8, 2025
f17c37d
Merge branch 'main' into install-packages-for-unrecognized-import
mzhongl524 May 8, 2025
2fe1bad
Create install-packages-for-unrecognized-import-2025-4-8-7-32-21.md
mzhongl524 May 9, 2025
c0a2a92
updated
mzhongl524 May 12, 2025
02829a8
Merge branch 'install-packages-for-unrecognized-import' of https://gi…
mzhongl524 May 12, 2025
b25753b
updated
mzhongl524 May 12, 2025
2168df4
updated
mzhongl524 May 16, 2025
a4eac0b
Merge branch 'main' into install-packages-for-unrecognized-import
mzhongl524 May 16, 2025
0a23e3e
updated
mzhongl524 May 16, 2025
99559f4
updated
mzhongl524 May 20, 2025
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking
changeKind: feature
packages:
- "@typespec/compiler"
- typespec-vscode
---

Install packages for unrecognized import via npm command
5 changes: 4 additions & 1 deletion packages/compiler/src/server/serverlib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -681,7 +681,10 @@ export function createServer(host: ServerHost): Server {
diagnostic.severity = DiagnosticSeverity.Hint;
}
}
diagnostic.data = { id: diagnosticIdCounter++ };
diagnostic.data = {
id: diagnosticIdCounter++,
file: diagDocument.uri,
};
const diagnostics = diagnosticMap.get(diagDocument);
compilerAssert(
diagnostics,
Expand Down
117 changes: 105 additions & 12 deletions packages/typespec-vscode/src/code-action-provider.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { PackageJson } from "@typespec/compiler";
import vscode from "vscode";
import { CommandName } from "./types.js";
import logger from "./log/logger.js";
import { getDirectoryPath, isPathAbsolute } from "./path-utils.js";
import { CodeActionCommand } from "./types.js";
import { searchAndLoadPackageJson } from "./utils.js";

export function createCodeActionProvider() {
return vscode.languages.registerCodeActionsProvider(
Expand All @@ -17,20 +21,21 @@ export function createCodeActionProvider() {
export class TypeSpecCodeActionProvider implements vscode.CodeActionProvider {
public static readonly providedCodeActionKinds = [vscode.CodeActionKind.QuickFix];

provideCodeActions(
public async provideCodeActions(
_document: vscode.TextDocument,
_range: vscode.Range | vscode.Selection,
context: vscode.CodeActionContext,
_token: vscode.CancellationToken,
): vscode.CodeAction[] {
// for each diagnostic entry that has the matching `code`, create a code action command
// A CodeAction will only be created if it is a TypeSpec diagnostic and code is an object and has a target attribute
// target attribute is the URL to open

// target is a Uri type, which corresponds to diagnostic.codeDescription.href in compiler
// When target is empty, it does not exist in the code object, so the code action will not be created
): Promise<vscode.CodeAction[]> {
const actions: vscode.CodeAction[] = [];
context.diagnostics.forEach((diagnostic) => {

for (const diagnostic of context.diagnostics) {
// for each diagnostic entry that has the matching `code`, create a code action command
// A CodeAction will only be created if it is a TypeSpec diagnostic and code is an object and has a target attribute
// target attribute is the URL to open

// target is a Uri type, which corresponds to diagnostic.codeDescription.href in compiler
// When target is empty, it does not exist in the code object, so the code action will not be created
if (
diagnostic.source === "TypeSpec" &&
diagnostic.code &&
Expand All @@ -46,10 +51,98 @@ export class TypeSpecCodeActionProvider implements vscode.CodeActionProvider {
),
);
}
});

// When the corresponding node dependency package is not installed,
// consider generating a quick fix to install via npm command
if (
diagnostic.source === "TypeSpec" &&
diagnostic.code &&
diagnostic.code === "import-not-found" &&
"data" in diagnostic &&
diagnostic.data &&
typeof diagnostic.data === "object" &&
"file" in diagnostic.data
) {
// The message content is `Couldn't resolve import "@typespec/http"`.
// The compiler's diagnostics do not provide a specific attribute to obtain the package name,
// so a regular expression is used to extract the package name within the double quotes,
// if no match is reached, the original string is returned
const targetPackage = diagnostic.message.replace(/.*"([^"]+)".*/, "$1");
logger.debug(`The target package name is '${targetPackage}'.`);

const packageFile = diagnostic.data.file as string;
const packageFileUri = vscode.Uri.parse(packageFile);
const { packageJsonFolder, packageJson } = await searchAndLoadPackageJson(
getDirectoryPath(packageFileUri.fsPath),
);

const pkgNameInPkgFile = this.isPackageInPackageJson(targetPackage, packageJson);

// Whether there is or not the package.json file, the installation command will be created;
// when there is no package.json file, the user will be given a clear message prompt.
actions.push(
this.createInstallPackageCodeAction(
diagnostic,
targetPackage,
pkgNameInPkgFile,
packageJsonFolder,
),
);
}
}

return actions;
}

/**
* Determines whether the given packageName is in the package.json file.
*
* - targetPackage is not a relative or absolute path
* - if it's listed in the package.json's dependencies, devDependencies, or peerDependencies.
*
* @param targetPackage - The name of the package to check.
* @param packageJson - The parsed package.json file content or undefined if not available.
* @returns True if packageName is in package.json file, false otherwise.
*/
private isPackageInPackageJson(
targetPackage: string,
packageJson: PackageJson | undefined,
): boolean {
if (
!targetPackage.startsWith("./") &&
!targetPackage.startsWith("../") &&
!isPathAbsolute(targetPackage) &&
packageJson
) {
return !!(
(packageJson.peerDependencies && targetPackage in packageJson.peerDependencies) ||
(packageJson.dependencies && targetPackage in packageJson.dependencies) ||
(packageJson.devDependencies && targetPackage in packageJson.devDependencies)
);
}

return false;
}

private createInstallPackageCodeAction(
diagnostic: vscode.Diagnostic,
pkgName: string,
pkgNameInPkgFile: boolean,
projectFolder?: string,
): vscode.CodeAction {
const action = new vscode.CodeAction(
`Install "${pkgName}" using npm`,
vscode.CodeActionKind.QuickFix,
);
action.command = {
command: CodeActionCommand.NpmInstallPackage,
title: diagnostic.message,
arguments: [projectFolder, pkgName, pkgNameInPkgFile],
};
action.diagnostics = [diagnostic];
return action;
}

private createOpenUrlCodeAction(
diagnostic: vscode.Diagnostic,
url: string,
Expand All @@ -61,7 +154,7 @@ export class TypeSpecCodeActionProvider implements vscode.CodeActionProvider {
vscode.CodeActionKind.QuickFix,
);
action.command = {
command: CommandName.OpenUrl,
command: CodeActionCommand.OpenUrl,
title: diagnostic.message,
arguments: [url],
};
Expand Down
38 changes: 36 additions & 2 deletions packages/typespec-vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import telemetryClient from "./telemetry/telemetry-client.js";
import { OperationTelemetryEvent, TelemetryEventName } from "./telemetry/telemetry-event.js";
import { TspLanguageClient } from "./tsp-language-client.js";
import {
CodeActionCommand,
CommandName,
InstallGlobalCliCommandArgs,
RestartServerCommandArgs,
Expand All @@ -25,7 +26,7 @@ import {
SettingName,
} from "./types.js";
import { installCompilerWithUi } from "./typespec-utils.js";
import { isWhitespaceStringOrUndefined } from "./utils.js";
import { isWhitespaceStringOrUndefined, spawnExecutionAndLogToOutput } from "./utils.js";
import { createTypeSpecProject } from "./vscode-cmd/create-tsp-project.js";
import { emitCode } from "./vscode-cmd/emit-code/emit-code.js";
import { importFromOpenApi3 } from "./vscode-cmd/import-from-openapi3.js";
Expand Down Expand Up @@ -63,7 +64,40 @@ export async function activate(context: ExtensionContext) {
);

context.subscriptions.push(
commands.registerCommand(CommandName.OpenUrl, (url: string) => {
commands.registerCommand(
CodeActionCommand.NpmInstallPackage,
async (projectFolder: string | undefined, pkgName: string, pkgNameInPkgFile: boolean) => {
try {
if (projectFolder) {
await spawnExecutionAndLogToOutput(
"npm",
pkgNameInPkgFile ? ["install"] : ["install", pkgName],
projectFolder,
);
} else {
logger.error(
"No package.json file was found, and the dependency package could not be installed",
[],
{
showPopup: true,
},
);
}
} catch (error) {
logger.error(
"Failed to execute npm install, please check the output for details",
[error],
{
showPopup: true,
},
);
}
},
),
);

context.subscriptions.push(
commands.registerCommand(CodeActionCommand.OpenUrl, (url: string) => {
try {
vscode.env.openExternal(vscode.Uri.parse(url));
} catch (error) {
Expand Down
6 changes: 5 additions & 1 deletion packages/typespec-vscode/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@ export const enum CommandName {
RestartServer = "typespec.restartServer",
InstallGlobalCompilerCli = "typespec.installGlobalCompilerCli",
CreateProject = "typespec.createProject",
OpenUrl = "typespec.openUrl",
EmitCode = "typespec.emitCode",
ImportFromOpenApi3 = "typespec.importFromOpenApi3",
ShowOpenApi3 = "typespec.showOpenApi3",
}

export const enum CodeActionCommand {
OpenUrl = "typespec.openUrl",
NpmInstallPackage = "typespec.npmInstallPackage",
}

export type RestartServerCommandResult = Result<TspLanguageClient>;

export interface BaseCommandArgs {
Expand Down
Loading