microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
297822a1eaadca4f95c0a56cc22ee8f12d0362bd

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/intellisenseHelper.ts

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