microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
0.4.3

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/intellisenseHelper.ts

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