microsoft/vscode-react-native

Public

mirrored fromhttps://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
dev/v-peq/removeNode10TodoComments

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

src/extension/commands/util/command.ts

155lines · modecode

1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT license. See LICENSE file in the project root for details.
3
4import assert = require("assert");
5import * as vscode from "vscode";
6import { EntryPointHandler } from "../../../common/entryPointHandler";
7import { ErrorHelper } from "../../../common/error/errorHelper";
8import { InternalError } from "../../../common/error/internalError";
9import { InternalErrorCode } from "../../../common/error/internalErrorCode";
10import { AppLauncher } from "../../appLauncher";
11import { PlatformType } from "../../launchArgs";
12import { OutputChannelLogger } from "../../log/OutputChannelLogger";
13import { TelemetryGenerator } from "../../../common/telemetryGenerators";
14import { selectProject } from ".";
15
16export abstract class Command<ArgT extends unknown[] = never[]> {
17 private static instances = new Map<Command, unknown>();
18
19 static formInstance<T extends { prototype: unknown }>(this: T): T["prototype"] {
20 // 'any' because TypeScript is wrong
21 // workaround from https://github.com/microsoft/TypeScript/issues/5863
22 const that = this as any;
23 const result = Command.instances.get(that) || new that();
24 Command.instances.set(that, result);
25 return result;
26 }
27
28 abstract readonly codeName: string;
29 abstract readonly label: string;
30 abstract readonly error: InternalError;
31
32 get platform(): string {
33 return (
34 [
35 PlatformType.Android,
36 PlatformType.iOS,
37 PlatformType.Exponent,
38 PlatformType.macOS,
39 PlatformType.Windows,
40 ].find(it => this.codeName.toLowerCase().includes(it.toLowerCase())) || ""
41 );
42 }
43
44 private entryPointHandler?: EntryPointHandler;
45
46 /** Initialize project property before executing command */
47 protected requiresProject = true;
48
49 /** Throw an Error if workspace is not trusted before executing command */
50 protected requiresTrust = true;
51
52 protected project?: AppLauncher;
53
54 protected constructor() {}
55
56 protected createHandler(fn = this.baseFn.bind(this)): (...args: ArgT) => Promise<void> {
57 return async (...args: ArgT): Promise<void> => {
58 assert(this.entryPointHandler, "this.entryPointHandler is not defined");
59
60 const resultFn = async (generator: TelemetryGenerator) => {
61 try {
62 await this.onBeforeExecute(...args);
63 await fn.bind(this)(...args);
64 } catch (error) {
65 if (
66 error instanceof InternalError &&
67 error.errorCode === InternalErrorCode.CommandCanceled
68 ) {
69 generator.addError(error);
70 return;
71 }
72 throw error;
73 }
74 };
75
76 OutputChannelLogger.getMainChannel().debug(`Run command: ${this.codeName}`);
77
78 await this.entryPointHandler.runFunctionWExtProps(
79 `commandPalette.${this.codeName}`,
80 {
81 platform: {
82 value: this.platform,
83 isPii: false,
84 },
85 },
86 this.error,
87 resultFn.bind(this),
88 );
89 };
90 }
91
92 // eslint-disable-next-line @typescript-eslint/no-unused-vars
93 protected async onBeforeExecute(...args: ArgT): Promise<void> {
94 if (this.requiresProject) {
95 this.project = await this.selectProject();
96 }
97
98 if (this.requiresTrust && !isWorkspaceTrusted()) {
99 throw ErrorHelper.getInternalError(
100 InternalErrorCode.WorkspaceIsNotTrusted,
101 this.project?.getPackager().getProjectPath() || undefined,
102 this.label,
103 );
104 }
105
106 function isWorkspaceTrusted() {
107 // Remove after updating supported VS Code engine version to 1.57.0
108 if (typeof (vscode.workspace as any).isTrusted === "boolean") {
109 return (vscode.workspace as any).isTrusted;
110 }
111
112 return true;
113 }
114 }
115
116 protected async selectProject(): Promise<AppLauncher> {
117 try {
118 return await selectProject();
119 } catch (error) {
120 if (
121 error instanceof InternalError &&
122 error.errorCode === InternalErrorCode.UserInputCanceled
123 ) {
124 throw ErrorHelper.getNestedError(
125 error,
126 InternalErrorCode.CommandCanceled,
127 this.label,
128 );
129 }
130 throw error;
131 }
132 }
133
134 abstract baseFn(...args: ArgT): Promise<void>;
135
136 /** Execute base command without telemetry */
137 async executeLocally(...args: ArgT): Promise<void> {
138 await this.onBeforeExecute(...args);
139 await this.baseFn(...args);
140 }
141
142 public register = (() => {
143 let isCalled = false;
144 return (entryPointHandler: EntryPointHandler) => {
145 this.entryPointHandler = entryPointHandler;
146
147 assert(!isCalled, "Command can only be registered once");
148 isCalled = true;
149 return vscode.commands.registerCommand(
150 `reactNative.${this.codeName}`,
151 this.createHandler(),
152 );
153 };
154 })();
155}
156