microsoft/vscode-react-native

Public

mirrored from https://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
274b561c0cbc309bc164cdaebfb6d63d30b22e60

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/intellisenseHelper.ts

298lines · 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 {FileSystem} from "../common/node/fileSystem";
5import * as path from "path";
6import * as Q from "q";
7import * as vscode from "vscode";
8import * as semver from "semver";
9import {Telemetry} from "../common/telemetry";
10import {TelemetryHelper} from "../common/telemetryHelper";
11import {CommandExecutor} from "../common/commandExecutor";
12import {TsConfigHelper} from "./tsconfigHelper";
13import {SettingsHelper} from "./settingsHelper";
14import {HostPlatform} from "../common/hostPlatform";
15import {Log} from "../common/log/log";
16import {LogLevel} from "../common/log/logHelper";
17
18
19
20interface IInstallProps {
21 installed: boolean;
22 version: string;
23}
24
25export class IntellisenseHelper {
26
27 private static s_typeScriptVersion = "1.8.2"; // preferred version of TypeScript for legacy VSCode installs
28 private static s_vsCodeVersion = "0.10.10-insider"; // preferred version of VSCode (current is 0.10.9, 0.10.10-insider+ will include native TypeScript support)
29 // note: semver considers "x.x.x-<string>" to be < "x.x.x"" - so we include insider here as the
30 // insider build is less than the release build of 0.10.10 and we will support it.
31
32 /**
33 * Helper method that configures the workspace for Salsa intellisense.
34 */
35 public static setupReactNativeIntellisense(): Q.Promise<void> {
36 // Telemetry - Send Salsa Environment setup information
37 const tsSalsaEnvSetup = TelemetryHelper.createTelemetryEvent("RNIntellisense");
38 TelemetryHelper.addTelemetryEventProperty(tsSalsaEnvSetup, "TsSalsaEnvSetup", !!process.env.VSCODE_TSJS, false);
39 Telemetry.send(tsSalsaEnvSetup);
40
41 const configureWorkspace = Q({})
42 .then(() => TsConfigHelper.createTsConfigIfNotPresent())
43 .then(() => IntellisenseHelper.installReactNativeTypings());
44
45 // The actions taken in the promise chain below may result in requring a restart.
46 const configureTypescript = Q(false)
47 .then((isRestartRequired: boolean) => IntellisenseHelper.enableSalsa(isRestartRequired))
48 .then((isRestartRequired: boolean) => IntellisenseHelper.verifyInstallTypeScript(isRestartRequired))
49 .then((isRestartRequired: boolean) => IntellisenseHelper.configureWorkspaceSettings(isRestartRequired))
50 .then((isRestartRequired: boolean) => IntellisenseHelper.warnIfRestartIsRequired(isRestartRequired))
51 .catch((err: any) => {
52 Log.logError("Error while setting up IntelliSense: " + err);
53 return Q.reject<void>(err);
54 });
55
56 /* TODO #83: Refactor this code to
57 Q.all([enableSalsa(), installTypescript(), configureWorkspace()])
58 .then((result) => warnIfRestartIsRequired(result.any((x) => x)))
59 */
60 return Q.all([configureWorkspace, configureTypescript]).then(() => { });
61 }
62
63 /**
64 * Helper method that install typings for React Native.
65 */
66 public static installReactNativeTypings(): Q.Promise<void> {
67 const typingsSource = path.resolve(__dirname, "..", "..", "ReactTypings");
68 const reactTypings = path.resolve(typingsSource, "react");
69 const reactNativeTypings = path.resolve(typingsSource, "react-native");
70 const typingsIndex = path.resolve(typingsSource, "react-native.d.ts.index");
71
72 const typingsDestination = path.resolve(vscode.workspace.rootPath, ".vscode", "typings");
73 const reactTypingsDestination = path.resolve(typingsDestination, "react");
74 const reactNativeTypingsDestination = path.resolve(typingsDestination, "react-native");
75 const typingsIndexDestination = path.resolve(vscode.workspace.rootPath, "typings");
76 const typingIndexFinalPath = path.resolve(typingsIndexDestination, "react-native.d.ts");
77
78 let fileSystem = new FileSystem();
79
80 const createTypingsDirectoryIfNeeded = fileSystem.directoryExists(typingsDestination).
81 then((exists) => {
82 if (!exists) {
83 return fileSystem.makeDirectoryRecursiveSync(typingsDestination);
84 }
85 });
86 const copyReactTypingsIfNeeded = fileSystem.directoryExists(reactTypingsDestination)
87 .then((exists) => {
88 if (!exists) {
89 return fileSystem.copyRecursive(reactTypings, reactTypingsDestination);
90 }
91 });
92 const copyReactNativeTypingsIfNeeded = fileSystem.directoryExists(reactNativeTypingsDestination)
93 .then((exists) => {
94 if (!exists) {
95 return fileSystem.copyRecursive(reactNativeTypings, reactNativeTypingsDestination);
96 }
97 });
98
99 const copyTypingsIndexIfNeeded = fileSystem.directoryExists(typingsIndexDestination)
100 .then((exists) => {
101 if (!exists) {
102 return fileSystem.makeDirectoryRecursiveSync(typingsIndexDestination);
103 }
104 })
105 .then(() => fileSystem.exists(typingIndexFinalPath))
106 .then((exists) => {
107 if (!exists) {
108 return fileSystem.copyFile(typingsIndex, typingIndexFinalPath);
109 }
110 });
111
112 return Q.all([
113 createTypingsDirectoryIfNeeded,
114 copyReactTypingsIfNeeded,
115 copyReactNativeTypingsIfNeeded,
116 copyTypingsIndexIfNeeded,
117 ]).then(() => { });
118 }
119
120 /**
121 * Helper method that verifies the correct version of TypeScript is installed.
122 * If using a newer version of VSCode TypeScript is installed by default and no
123 * action is needed. If using an older version, verify that the correct TS version is
124 * installed, if not install it.
125 */
126 public static verifyInstallTypeScript(isRestartRequired: boolean): Q.Promise<boolean> {
127
128 if (IntellisenseHelper.isSalsaSupported()) {
129 // this is the correct version of vscode, which includes TypeScript (Salsa) support, nothing to do here
130 return Q.resolve<boolean>(isRestartRequired);
131 }
132
133 return IntellisenseHelper.getInstalledTypeScriptVersion()
134 .then(function(installProps: IInstallProps) {
135
136 if (installProps.installed === true) {
137
138 if (semver.neq(IntellisenseHelper.s_typeScriptVersion, installProps.version)) {
139 Log.logInternalMessage(LogLevel.Debug, "TypeScript is installed with the wrong version: " + installProps.version);
140 return true;
141 } else {
142 Log.logInternalMessage(LogLevel.Debug, "Installed TypeScript version is correct");
143 return false;
144 }
145 } else {
146 Log.logInternalMessage(LogLevel.Debug, "TypeScript is not installed");
147 return true;
148 }
149 })
150 .then((install: boolean) => {
151
152 if (install) {
153 let installPath: string = path.resolve(HostPlatform.getUserHomePath(), ".vscode");
154 let runArguments: string[] = [];
155 let npmCommand: string = HostPlatform.getNpmCliCommand("npm");
156 runArguments.push("install");
157 runArguments.push("--prefix " + installPath);
158 runArguments.push("typescript@" + IntellisenseHelper.s_typeScriptVersion);
159
160 return new CommandExecutor(installPath).spawn(npmCommand, runArguments)
161 .then(() => {
162 return true;
163 })
164 .catch((err: any) => {
165 Log.logError("Error attempting to install TypeScript: " + err);
166 return Q.reject<boolean>(err);
167 });
168
169 } else {
170 return isRestartRequired;
171 }
172 });
173 }
174
175
176
177 public static configureWorkspaceSettings(isRestartRequired: boolean): boolean {
178 let typeScriptLibPath: string = path.resolve(IntellisenseHelper.getTypeScriptInstallPath(), "lib");
179 const tsdkPath = SettingsHelper.getTypeScriptTsdk();
180
181 if (IntellisenseHelper.isSalsaSupported()) {
182 if (tsdkPath === typeScriptLibPath) {
183 // Note: In previous releases of VSCode (< 0.10.10) the Salsa TypeScript
184 // IntelliSense was not enabled by default, this extension would install
185 // Salsa itself, and update the settings to point at that. Here we
186 // attempt to reset that value to null if it still points to the previous
187 // installed (and no longer valid) version of TypeScript.
188 SettingsHelper.notifyUserToRemoveTSDKFromSettingsJson(tsdkPath);
189 // We are already telling the user to restart. No need to show another message.
190 return false;
191 }
192 } else {
193 if (tsdkPath === null) {
194 SettingsHelper.notifyUserToAddTSDKInSettingsJson(typeScriptLibPath);
195 // We are already telling the user to restart. No need to show another message.
196 return false;
197 }
198 }
199 return isRestartRequired;
200 }
201
202 public static warnIfRestartIsRequired(isRestartRequired: boolean): Q.Promise<void> {
203 if (isRestartRequired) {
204 vscode.window.showInformationMessage("React Native intellisense was successfully configured for this project. Restart to enable it.");
205 }
206
207 return;
208 }
209
210 /**
211 * Helper method that sets the environment variable and informs the user they need to restart
212 * in order to enable the Salsa intellisense.
213 */
214 public static enableSalsa(isRestartRequired: boolean): Q.Promise<boolean> {
215 if (!IntellisenseHelper.isSalsaSupported() && !process.env.VSCODE_TSJS) {
216 return Q({})
217 .then(() => HostPlatform.setEnvironmentVariable("VSCODE_TSJS", "1"))
218 .then(() => { return true; });
219 }
220
221 return Q(isRestartRequired);
222 }
223
224 /**
225 * Simple check to see if the TypeScript package is in the expected location (where we installed it)
226 */
227 private static isTypeScriptInstalled(): Q.Promise<boolean> {
228 let fileSystem: FileSystem = new FileSystem();
229 let installPath: string = path.join(IntellisenseHelper.getTypeScriptInstallPath(), "lib");
230 return fileSystem.exists(installPath);
231 }
232
233 /**
234 * Checks for the existance of our installed TypeScript package, if it exists also determine its version
235 */
236 private static getInstalledTypeScriptVersion(): Q.Promise<IInstallProps> {
237 return IntellisenseHelper.isTypeScriptInstalled()
238 .then((installed: boolean) => {
239 let installProps: IInstallProps = {
240 installed: installed,
241 version: "",
242 };
243
244 if (installed === true) {
245 Log.logInternalMessage(LogLevel.Debug, "TypeScript is installed - checking version");
246 return IntellisenseHelper.readPackageJson()
247 .then((version: string) => {
248 installProps.version = version;
249 return installProps;
250 });
251 } else {
252 return installProps;
253 }
254 });
255 }
256
257 /**
258 * Read the package.json from the TypeScript install path and return the version if it's available
259 */
260 private static readPackageJson(): Q.Promise<string> {
261 let packageFilePath: string = path.join(IntellisenseHelper.getTypeScriptInstallPath(), "package.json");
262 let fileSystem = new FileSystem();
263
264 return fileSystem.exists(packageFilePath)
265 .then(function(exists: boolean): Q.Promise<string> {
266 if (!exists) {
267 return Q.reject<string>("package.json not found at:" + packageFilePath);
268 }
269
270 return fileSystem.readFile(packageFilePath, "utf-8");
271 })
272 .then(function(jsonContents: string): Q.Promise<any> {
273 let data = JSON.parse(jsonContents);
274 return data.version;
275 })
276 .catch((err: any) => {
277 Log.logError("Error while processing package.json: " + err);
278 return "0.0.0";
279 });
280 }
281
282 /**
283 * Simple helper to get the TypeScript install path
284 */
285 private static getTypeScriptInstallPath(): string {
286
287 let codePath: string = path.resolve(HostPlatform.getUserHomePath(), ".vscode");
288 let typeScriptLibPath: string = path.join(codePath, "node_modules", "typescript");
289 return typeScriptLibPath;
290 }
291
292 /**
293 * Simple helper to determine if the current version of VSCode supports TypeScript (Salsa) or better
294 */
295 private static isSalsaSupported(): boolean {
296 return semver.gte(vscode.version, IntellisenseHelper.s_vsCodeVersion, true);
297 }
298}
299