Skip to content

Commit bae6f5f

Browse files
committed
[json] "Fetch Online Package Info" feature doesn't work when npm is not present. Fixes microsoft#77066
1 parent 2de9a36 commit bae6f5f

2 files changed

Lines changed: 81 additions & 53 deletions

File tree

extensions/npm/src/features/jsonContributions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export interface IJSONContribution {
2525
getDocumentSelector(): DocumentSelector;
2626
getInfoContribution(fileName: string, location: Location): Thenable<MarkedString[] | null> | null;
2727
collectPropertySuggestions(fileName: string, location: Location, currentWord: string, addValue: boolean, isLast: boolean, result: ISuggestionsCollector): Thenable<any> | null;
28-
collectValueSuggestions(fileName: string, location: Location, result: ISuggestionsCollector): Thenable<any> | null;
28+
collectValueSuggestions(fileName: string, location: Location, result: ISuggestionsCollector): Thenable<any>;
2929
collectDefaultSuggestions(fileName: string, result: ISuggestionsCollector): Thenable<any>;
3030
resolveSuggestion?(item: CompletionItem): Thenable<CompletionItem | null> | null;
3131
}
@@ -164,4 +164,4 @@ export class JSONCompletionItemProvider implements CompletionItemProvider {
164164
}
165165
}
166166

167-
export const xhrDisabled = () => Promise.reject({ responseText: 'Use of online resources is disabled.' });
167+
export const xhrDisabled = () => Promise.reject({ responseText: 'Use of online resources is disabled.' });

extensions/npm/src/features/packageJSONContribution.ts

Lines changed: 79 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -182,46 +182,38 @@ export class PackageJSONContribution implements IJSONContribution {
182182
return Promise.resolve(null);
183183
}
184184

185-
public collectValueSuggestions(
186-
_fileName: string,
187-
location: Location,
188-
result: ISuggestionsCollector
189-
): Thenable<any> | null {
185+
public async collectValueSuggestions(_fileName: string, location: Location, result: ISuggestionsCollector): Promise<any> {
190186
if (!this.onlineEnabled()) {
191187
return null;
192188
}
193189

194190
if ((location.matches(['dependencies', '*']) || location.matches(['devDependencies', '*']) || location.matches(['optionalDependencies', '*']) || location.matches(['peerDependencies', '*']))) {
195191
const currentKey = location.path[location.path.length - 1];
196192
if (typeof currentKey === 'string') {
197-
return this.npmView(currentKey).then(info => {
198-
const latest = info.distTagsLatest;
199-
if (latest) {
200-
let name = JSON.stringify(latest);
201-
let proposal = new CompletionItem(name);
202-
proposal.kind = CompletionItemKind.Property;
203-
proposal.insertText = name;
204-
proposal.documentation = localize('json.npm.latestversion', 'The currently latest version of the package');
205-
result.add(proposal);
193+
const info = await this.fetchPackageInfo(currentKey);
194+
if (info && info.distTagsLatest) {
206195

207-
name = JSON.stringify('^' + latest);
208-
proposal = new CompletionItem(name);
209-
proposal.kind = CompletionItemKind.Property;
210-
proposal.insertText = name;
211-
proposal.documentation = localize('json.npm.majorversion', 'Matches the most recent major version (1.x.x)');
212-
result.add(proposal);
196+
let name = JSON.stringify(info.distTagsLatest);
197+
let proposal = new CompletionItem(name);
198+
proposal.kind = CompletionItemKind.Property;
199+
proposal.insertText = name;
200+
proposal.documentation = localize('json.npm.latestversion', 'The currently latest version of the package');
201+
result.add(proposal);
213202

214-
name = JSON.stringify('~' + latest);
215-
proposal = new CompletionItem(name);
216-
proposal.kind = CompletionItemKind.Property;
217-
proposal.insertText = name;
218-
proposal.documentation = localize('json.npm.minorversion', 'Matches the most recent minor version (1.2.x)');
219-
result.add(proposal);
220-
}
221-
return 0;
222-
}, () => {
223-
return 0;
224-
});
203+
name = JSON.stringify('^' + info.distTagsLatest);
204+
proposal = new CompletionItem(name);
205+
proposal.kind = CompletionItemKind.Property;
206+
proposal.insertText = name;
207+
proposal.documentation = localize('json.npm.majorversion', 'Matches the most recent major version (1.x.x)');
208+
result.add(proposal);
209+
210+
name = JSON.stringify('~' + info.distTagsLatest);
211+
proposal = new CompletionItem(name);
212+
proposal.kind = CompletionItemKind.Property;
213+
proposal.insertText = name;
214+
proposal.documentation = localize('json.npm.minorversion', 'Matches the most recent minor version (1.2.x)');
215+
result.add(proposal);
216+
}
225217
}
226218
}
227219
return null;
@@ -243,39 +235,75 @@ export class PackageJSONContribution implements IJSONContribution {
243235
return null;
244236
}
245237

246-
private getInfo(pack: string): Thenable<string[]> {
247-
return this.npmView(pack).then(info => {
238+
private async getInfo(pack: string): Promise<string[]> {
239+
let info = await this.fetchPackageInfo(pack);
240+
if (info) {
248241
const result: string[] = [];
249242
result.push(info.description || '');
250243
result.push(info.distTagsLatest ? localize('json.npm.version.hover', 'Latest version: {0}', info.distTagsLatest) : '');
251244
result.push(info.homepage || '');
252245
return result;
253-
}, () => {
254-
return [];
255-
});
246+
}
247+
248+
return [];
249+
}
250+
251+
private async fetchPackageInfo(pack: string): Promise<ViewPackageInfo | undefined> {
252+
let info = await this.npmView(pack);
253+
if (!info) {
254+
info = await this.npmjsView(pack);
255+
}
256+
return info;
256257
}
257258

258-
private npmView(pack: string): Promise<ViewPackageInfo> {
259-
return new Promise((resolve, reject) => {
259+
260+
private npmView(pack: string): Promise<ViewPackageInfo | undefined> {
261+
return new Promise((resolve, _reject) => {
260262
const command = 'npm view --json ' + pack + ' description dist-tags.latest homepage';
261263
cp.exec(command, (error, stdout) => {
262-
if (error) {
263-
return reject();
264-
}
265-
try {
266-
const content = JSON.parse(stdout);
267-
resolve({
268-
description: content['description'],
269-
distTagsLatest: content['dist-tags.latest'],
270-
homepage: content['homepage']
271-
});
272-
} catch (e) {
273-
reject();
264+
if (!error) {
265+
try {
266+
const content = JSON.parse(stdout);
267+
resolve({
268+
description: content['description'],
269+
distTagsLatest: content['dist-tags.latest'],
270+
homepage: content['homepage']
271+
});
272+
return;
273+
} catch (e) {
274+
// ignore
275+
}
274276
}
277+
resolve(undefined);
275278
});
276279
});
277280
}
278281

282+
private async npmjsView(pack: string): Promise<ViewPackageInfo | undefined> {
283+
const queryUrl = 'https://registry.npmjs.org/' + encodeURIComponent(pack).replace(/%40/g, '@');
284+
try {
285+
const success = await this.xhr({
286+
url: queryUrl,
287+
agent: USER_AGENT
288+
});
289+
const obj = JSON.parse(success.responseText);
290+
if (obj) {
291+
const latest = obj && obj['dist-tags'] && obj['dist-tags']['latest'];
292+
if (latest) {
293+
return {
294+
description: obj.description || '',
295+
distTagsLatest: latest,
296+
homepage: obj.homepage || ''
297+
};
298+
}
299+
}
300+
}
301+
catch (e) {
302+
//ignore
303+
}
304+
return undefined;
305+
}
306+
279307
public getInfoContribution(_fileName: string, location: Location): Thenable<MarkedString[] | null> | null {
280308
if ((location.matches(['dependencies', '*']) || location.matches(['devDependencies', '*']) || location.matches(['optionalDependencies', '*']) || location.matches(['peerDependencies', '*']))) {
281309
const pack = location.path[location.path.length - 1];
@@ -327,4 +355,4 @@ interface ViewPackageInfo {
327355
description: string;
328356
distTagsLatest?: string;
329357
homepage?: string;
330-
}
358+
}

0 commit comments

Comments
 (0)