Skip to content
This repository was archived by the owner on Jul 15, 2023. It is now read-only.

Commit 8f5bcf1

Browse files
committed
Replace full path for vendor packages with relative path
Better comments Add missing sort
1 parent b780d41 commit 8f5bcf1

3 files changed

Lines changed: 165 additions & 13 deletions

File tree

src/goImport.ts

Lines changed: 72 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,91 @@ import vscode = require('vscode');
99
import cp = require('child_process');
1010
import { getBinPath } from './goPath';
1111
import { parseFilePrelude } from './util';
12-
import { promptForMissingTool } from './goInstallTools';
1312
import { documentSymbols } from './goOutline';
13+
import { promptForMissingTool, isVendorSupported } from './goInstallTools';
14+
import path = require('path');
1415

1516
export function listPackages(excludeImportedPkgs: boolean = false): Thenable<string[]> {
1617
let importsPromise = excludeImportedPkgs && vscode.window.activeTextEditor ? getImports(vscode.window.activeTextEditor.document.fileName) : Promise.resolve([]);
17-
let pkgsPromise = new Promise<string[]>((resolve, reject) => {
18+
let vendorSupportPromise = isVendorSupported();
19+
let goPkgsPromise = new Promise<string[]>((resolve, reject) => {
1820
cp.execFile(getBinPath('gopkgs'), [], (err, stdout, stderr) => {
1921
if (err && (<any>err).code === 'ENOENT') {
2022
promptForMissingTool('gopkgs');
2123
return reject();
2224
}
2325
let lines = stdout.toString().split('\n');
24-
let sortedlines = lines.sort().slice(1); // Drop the empty entry from the final '\n'
25-
return resolve(sortedlines);
26+
if (lines[lines.length - 1] === '') {
27+
// Drop the empty entry from the final '\n'
28+
lines.pop();
29+
}
30+
return resolve(lines);
2631
});
2732
});
2833

29-
return Promise.all<string[]>([importsPromise, pkgsPromise]).then(values => {
30-
let imports = values[0];
31-
let pkgs = values[1];
32-
if (imports.length === 0) {
33-
return pkgs;
34-
}
35-
return pkgs.filter(element => {
36-
return imports.indexOf(element) === -1;
34+
return vendorSupportPromise.then((vendorSupport: boolean) => {
35+
return Promise.all<string[]>([goPkgsPromise, importsPromise]).then(values => {
36+
let pkgs = values[0];
37+
let importedPkgs = values [1];
38+
39+
if (!vendorSupport) {
40+
if (importedPkgs.length > 0) {
41+
pkgs = pkgs.filter(element => {
42+
return importedPkgs.indexOf(element) === -1;
43+
});
44+
}
45+
return pkgs.sort();
46+
}
47+
48+
let currentFileDirPath = path.dirname(vscode.window.activeTextEditor.document.fileName);
49+
let workspaces: string[] = process.env['GOPATH'].split(path.delimiter);
50+
let currentWorkspace = path.join(workspaces[0], 'src');
51+
52+
// Workaround for issue in https://github.com/Microsoft/vscode/issues/9448#issuecomment-244804026
53+
if (process.platform === 'win32') {
54+
currentFileDirPath = currentFileDirPath.substr(0, 1).toUpperCase() + currentFileDirPath.substr(1);
55+
}
56+
57+
// In case of multiple workspaces, find current workspace by checking if current file is
58+
// under any of the workspaces in $GOPATH
59+
for (let i = 1; i < workspaces.length; i++) {
60+
let possibleCurrentWorkspace = path.join(workspaces[i], 'src');
61+
if (currentFileDirPath.startsWith(possibleCurrentWorkspace)) {
62+
// In case of nested workspaces, (example: both /Users/me and /Users/me/src/a/b/c are in $GOPATH)
63+
// both parent & child workspace in the nested workspaces pair can make it inside the above if block
64+
// Therefore, the below check will take longer (more specific to current file) of the two
65+
if (possibleCurrentWorkspace.length > currentWorkspace.length) {
66+
currentWorkspace = possibleCurrentWorkspace;
67+
}
68+
}
69+
}
70+
71+
let pkgSet = new Set<string>();
72+
pkgs.forEach(pkg => {
73+
if (!pkg || importedPkgs.indexOf(pkg) > -1) {
74+
return;
75+
}
76+
77+
let magicVendorString = '/vendor/';
78+
let vendorIndex = pkg.indexOf(magicVendorString);
79+
80+
// Check if current file and the vendor pkg belong to the same root project
81+
// If yes, then vendor pkg can be replaced with its relative path to the "vendor" folder
82+
if (vendorIndex > 0) {
83+
let rootProjectForVendorPkg = path.join(currentWorkspace, pkg.substr(0, vendorIndex));
84+
let relativePathForVendorPkg = pkg.substring(vendorIndex + magicVendorString.length);
85+
86+
if (relativePathForVendorPkg && currentFileDirPath.startsWith(rootProjectForVendorPkg)) {
87+
pkgSet.add(relativePathForVendorPkg);
88+
return;
89+
}
90+
}
91+
92+
// pkg is not a vendor project or is a vendor project not belonging to current project
93+
pkgSet.add(pkg);
94+
});
95+
96+
return Array.from(pkgSet).sort();
3797
});
3898
});
3999
}

src/goInstallTools.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ interface SemVersion {
2020
}
2121

2222
let goVersion: SemVersion = null;
23+
let vendorSupport: boolean = null;
2324

2425
function getTools(): { [key: string]: string } {
2526
let goConfig = vscode.workspace.getConfiguration('go');
@@ -142,6 +143,7 @@ export function updateGoPathGoRootFromConfig() {
142143

143144
export function setupGoPathAndOfferToInstallTools() {
144145
updateGoPathGoRootFromConfig();
146+
isVendorSupported();
145147

146148
if (!process.env['GOPATH']) {
147149
let info = 'GOPATH is not set as an environment variable or via `go.gopath` setting in Code';
@@ -193,6 +195,8 @@ function getMissingTools(): Promise<string[]> {
193195
});
194196
}
195197

198+
199+
196200
export function getGoVersion(): Promise<SemVersion> {
197201
if (goVersion) {
198202
return Promise.resolve(goVersion);
@@ -209,4 +213,25 @@ export function getGoVersion(): Promise<SemVersion> {
209213
return resolve(goVersion);
210214
});
211215
});
212-
}
216+
}
217+
218+
export function isVendorSupported(): Promise<boolean> {
219+
if (vendorSupport != null) {
220+
return Promise.resolve(vendorSupport);
221+
}
222+
return getGoVersion().then(version => {
223+
switch (version.major) {
224+
case 0:
225+
vendorSupport = false;
226+
break;
227+
case 1:
228+
vendorSupport = (version.minor > 5 || (version.minor === 5 && process.env['GO15VENDOREXPERIMENT'] === '1')) ? true : false;
229+
break;
230+
default:
231+
vendorSupport = true;
232+
break;
233+
}
234+
return vendorSupport;
235+
});
236+
}
237+

test/go.test.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import { getGoVersion } from '../src/goInstallTools';
1919
import { documentSymbols } from '../src/goOutline';
2020
import { listPackages } from '../src/goImport';
2121
import { generateTestCurrentFile, generateTestCurrentPackage } from '../src/goGenerateTests';
22+
import { getBinPath } from '../src/goPath';
23+
import { isVendorSupported } from '../src/goInstallTools';
2224

2325
suite('Go Extension Tests', () => {
2426
let gopath = process.env['GOPATH'];
@@ -389,4 +391,69 @@ encountered.
389391
});
390392
}).then(() => done(), done);
391393
});
394+
395+
test('Replace vendor packages with relative path', (done) => {
396+
// This test needs a go project that has vendor folder and vendor packages
397+
// Since the Go extension takes a dependency on the godef tool at github.com/rogpeppe/godef
398+
// which has vendor packages, we are using it here to test the "replace vendor packages with relative path" feature.
399+
// If the extension ever stops depending on godef tool or if godef ever stops having vendor packages, then this test
400+
// will fail and will have to be replaced with any other go project with vendor packages
401+
402+
let vendorSupportPromise = isVendorSupported();
403+
let filePath = path.join(process.env['GOPATH'], 'src', 'github.com', 'rogpeppe', 'godef', 'go', 'ast', 'ast.go');
404+
let vendorPkgsFullPath = [
405+
'github.com/rogpeppe/godef/vendor/9fans.net/go/acme',
406+
'github.com/rogpeppe/godef/vendor/9fans.net/go/plan9',
407+
'github.com/rogpeppe/godef/vendor/9fans.net/go/plan9/client'
408+
];
409+
let vendorPkgsRelativePath = [
410+
'9fans.net/go/acme',
411+
'9fans.net/go/plan9',
412+
'9fans.net/go/plan9/client'
413+
];
414+
415+
vendorSupportPromise.then((vendorSupport: boolean) => {
416+
let gopkgsPromise = new Promise<string[]>((resolve, reject) => {
417+
cp.execFile(getBinPath('gopkgs'), [], (err, stdout, stderr) => {
418+
let pkgs = stdout.split('\n').sort().slice(1);
419+
if (vendorSupport) {
420+
vendorPkgsFullPath.forEach(pkg => {
421+
assert.equal(pkgs.indexOf(pkg) > -1, true, `Package not found by goPkgs: ${pkg}`);
422+
});
423+
vendorPkgsRelativePath.forEach(pkg => {
424+
assert.equal(pkgs.indexOf(pkg), -1, `Relative path to vendor package ${pkg} should not be returned by gopkgs command`);
425+
});
426+
}
427+
return resolve(pkgs);
428+
});
429+
});
430+
431+
let listPkgPromise: Thenable<string[]> = vscode.workspace.openTextDocument(vscode.Uri.file(filePath)).then(document => {
432+
return vscode.window.showTextDocument(document).then(editor => {
433+
return listPackages().then(pkgs => {
434+
if (vendorSupport) {
435+
vendorPkgsRelativePath.forEach(pkg => {
436+
assert.equal(pkgs.indexOf(pkg) > -1, true, `Relative path for vendor package ${pkg} not found`);
437+
});
438+
vendorPkgsFullPath.forEach(pkg => {
439+
assert.equal(pkgs.indexOf(pkg), -1, `Full path for vendor package ${pkg} should be shown by listPackages method`);
440+
});
441+
}
442+
return Promise.resolve(pkgs);
443+
});
444+
});
445+
});
446+
447+
return Promise.all<string[]>([gopkgsPromise, listPkgPromise]).then((values: string[][]) => {
448+
if (!vendorSupport) {
449+
let originalPkgs = values[0];
450+
let updatedPkgs = values[1];
451+
assert.equal(originalPkgs.length, updatedPkgs.length);
452+
for (let index = 0; index < originalPkgs.length; index++) {
453+
assert.equal(updatedPkgs[index], originalPkgs[index]);
454+
}
455+
}
456+
});
457+
}).then(() => done(), done);
458+
});
392459
});

0 commit comments

Comments
 (0)