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

Fixes #316 Replace full path for vendor packages with relative path #491

Merged
merged 1 commit into from
Oct 17, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 72 additions & 12 deletions src/goImport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,91 @@ import vscode = require('vscode');
import cp = require('child_process');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

import { getBinPath } from './goPath';
import { parseFilePrelude } from './util';
import { promptForMissingTool } from './goInstallTools';
import { documentSymbols } from './goOutline';
import { promptForMissingTool, isVendorSupported } from './goInstallTools';
import path = require('path');

export function listPackages(excludeImportedPkgs: boolean = false): Thenable<string[]> {
let importsPromise = excludeImportedPkgs && vscode.window.activeTextEditor ? getImports(vscode.window.activeTextEditor.document.fileName) : Promise.resolve([]);
let pkgsPromise = new Promise<string[]>((resolve, reject) => {
let vendorSupportPromise = isVendorSupported();
let goPkgsPromise = new Promise<string[]>((resolve, reject) => {
cp.execFile(getBinPath('gopkgs'), [], (err, stdout, stderr) => {
if (err && (<any>err).code === 'ENOENT') {
promptForMissingTool('gopkgs');
return reject();
}
let lines = stdout.toString().split('\n');
let sortedlines = lines.sort().slice(1); // Drop the empty entry from the final '\n'
return resolve(sortedlines);
if (lines[lines.length - 1] === '') {
// Drop the empty entry from the final '\n'
lines.pop();
}
return resolve(lines);
});
});

return Promise.all<string[]>([importsPromise, pkgsPromise]).then(values => {
let imports = values[0];
let pkgs = values[1];
if (imports.length === 0) {
return pkgs;
}
return pkgs.filter(element => {
return imports.indexOf(element) === -1;
return vendorSupportPromise.then((vendorSupport: boolean) => {
return Promise.all<string[]>([goPkgsPromise, importsPromise]).then(values => {
let pkgs = values[0];
let importedPkgs = values [1];

if (!vendorSupport) {
if (importedPkgs.length > 0) {
pkgs = pkgs.filter(element => {
return importedPkgs.indexOf(element) === -1;
});
}
return pkgs.sort();
}

let currentFileDirPath = path.dirname(vscode.window.activeTextEditor.document.fileName);
let workspaces: string[] = process.env['GOPATH'].split(path.delimiter);
let currentWorkspace = path.join(workspaces[0], 'src');

// Workaround for issue in https://github.com/Microsoft/vscode/issues/9448#issuecomment-244804026
if (process.platform === 'win32') {
currentFileDirPath = currentFileDirPath.substr(0, 1).toUpperCase() + currentFileDirPath.substr(1);
}

// In case of multiple workspaces, find current workspace by checking if current file is
// under any of the workspaces in $GOPATH
for (let i = 1; i < workspaces.length; i++) {
let possibleCurrentWorkspace = path.join(workspaces[i], 'src');
if (currentFileDirPath.startsWith(possibleCurrentWorkspace)) {
// In case of nested workspaces, (example: both /Users/me and /Users/me/src/a/b/c are in $GOPATH)
// both parent & child workspace in the nested workspaces pair can make it inside the above if block
// Therefore, the below check will take longer (more specific to current file) of the two
if (possibleCurrentWorkspace.length > currentWorkspace.length) {
currentWorkspace = possibleCurrentWorkspace;
}
}
}

let pkgSet = new Set<string>();
pkgs.forEach(pkg => {
if (!pkg || importedPkgs.indexOf(pkg) > -1) {
return;
}

let magicVendorString = '/vendor/';
let vendorIndex = pkg.indexOf(magicVendorString);

// Check if current file and the vendor pkg belong to the same root project
// If yes, then vendor pkg can be replaced with its relative path to the "vendor" folder
if (vendorIndex > 0) {
let rootProjectForVendorPkg = path.join(currentWorkspace, pkg.substr(0, vendorIndex));
let relativePathForVendorPkg = pkg.substring(vendorIndex + magicVendorString.length);

if (relativePathForVendorPkg && currentFileDirPath.startsWith(rootProjectForVendorPkg)) {
pkgSet.add(relativePathForVendorPkg);
return;
}
}

// pkg is not a vendor project or is a vendor project not belonging to current project
pkgSet.add(pkg);
});

return Array.from(pkgSet).sort();
});
});
}
Expand Down
27 changes: 26 additions & 1 deletion src/goInstallTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ interface SemVersion {
}

let goVersion: SemVersion = null;
let vendorSupport: boolean = null;

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

export function setupGoPathAndOfferToInstallTools() {
updateGoPathGoRootFromConfig();
isVendorSupported();

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



export function getGoVersion(): Promise<SemVersion> {
if (goVersion) {
return Promise.resolve(goVersion);
Expand All @@ -209,4 +213,25 @@ export function getGoVersion(): Promise<SemVersion> {
return resolve(goVersion);
});
});
}
}

export function isVendorSupported(): Promise<boolean> {
if (vendorSupport != null) {
return Promise.resolve(vendorSupport);
}
return getGoVersion().then(version => {
switch (version.major) {
case 0:
vendorSupport = false;
break;
case 1:
vendorSupport = (version.minor > 5 || (version.minor === 5 && process.env['GO15VENDOREXPERIMENT'] === '1')) ? true : false;
break;
default:
vendorSupport = true;
break;
}
return vendorSupport;
});
}

89 changes: 79 additions & 10 deletions test/go.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import { getGoVersion } from '../src/goInstallTools';
import { documentSymbols } from '../src/goOutline';
import { listPackages } from '../src/goImport';
import { generateTestCurrentFile, generateTestCurrentPackage } from '../src/goGenerateTests';
import { getBinPath } from '../src/goPath';
import { isVendorSupported } from '../src/goInstallTools';

suite('Go Extension Tests', () => {
let gopath = process.env['GOPATH'];
Expand Down Expand Up @@ -83,17 +85,19 @@ encountered.
];
let uri = vscode.Uri.file(path.join(fixturePath, 'test.go'));
vscode.workspace.openTextDocument(uri).then((textDocument) => {
let promises = testCases.map(([position, expected]) =>
provider.provideCompletionItems(textDocument, position, null).then(items => {
let labels = items.map(x => x.label);
for (let entry of expected) {
if (labels.indexOf(entry) < 0) {
assert.fail('', entry, 'missing expected item in competion list');
return vscode.window.showTextDocument(textDocument).then(editor => {
let promises = testCases.map(([position, expected]) =>
provider.provideCompletionItems(textDocument, position, null).then(items => {
let labels = items.map(x => x.label);
for (let entry of expected) {
if (labels.indexOf(entry) < 0) {
assert.fail('', entry, 'missing expected item in competion list');
}
}
}
})
);
return Promise.all(promises);
})
);
return Promise.all(promises);
});
}, (err) => {
assert.ok(false, `error in OpenTextDocument ${err}`);
}).then(() => done(), done);
Expand Down Expand Up @@ -389,4 +393,69 @@ encountered.
});
}).then(() => done(), done);
});

test('Replace vendor packages with relative path', (done) => {
// This test needs a go project that has vendor folder and vendor packages
// Since the Go extension takes a dependency on the godef tool at github.com/rogpeppe/godef
// which has vendor packages, we are using it here to test the "replace vendor packages with relative path" feature.
// If the extension ever stops depending on godef tool or if godef ever stops having vendor packages, then this test
// will fail and will have to be replaced with any other go project with vendor packages

let vendorSupportPromise = isVendorSupported();
let filePath = path.join(process.env['GOPATH'], 'src', 'github.com', 'rogpeppe', 'godef', 'go', 'ast', 'ast.go');
let vendorPkgsFullPath = [
'github.com/rogpeppe/godef/vendor/9fans.net/go/acme',
'github.com/rogpeppe/godef/vendor/9fans.net/go/plan9',
'github.com/rogpeppe/godef/vendor/9fans.net/go/plan9/client'
];
let vendorPkgsRelativePath = [
'9fans.net/go/acme',
'9fans.net/go/plan9',
'9fans.net/go/plan9/client'
];

vendorSupportPromise.then((vendorSupport: boolean) => {
let gopkgsPromise = new Promise<string[]>((resolve, reject) => {
cp.execFile(getBinPath('gopkgs'), [], (err, stdout, stderr) => {
let pkgs = stdout.split('\n').sort().slice(1);
if (vendorSupport) {
vendorPkgsFullPath.forEach(pkg => {
assert.equal(pkgs.indexOf(pkg) > -1, true, `Package not found by goPkgs: ${pkg}`);
});
vendorPkgsRelativePath.forEach(pkg => {
assert.equal(pkgs.indexOf(pkg), -1, `Relative path to vendor package ${pkg} should not be returned by gopkgs command`);
});
}
return resolve(pkgs);
});
});

let listPkgPromise: Thenable<string[]> = vscode.workspace.openTextDocument(vscode.Uri.file(filePath)).then(document => {
return vscode.window.showTextDocument(document).then(editor => {
return listPackages().then(pkgs => {
if (vendorSupport) {
vendorPkgsRelativePath.forEach(pkg => {
assert.equal(pkgs.indexOf(pkg) > -1, true, `Relative path for vendor package ${pkg} not found`);
});
vendorPkgsFullPath.forEach(pkg => {
assert.equal(pkgs.indexOf(pkg), -1, `Full path for vendor package ${pkg} should be shown by listPackages method`);
});
}
return Promise.resolve(pkgs);
});
});
});

return Promise.all<string[]>([gopkgsPromise, listPkgPromise]).then((values: string[][]) => {
if (!vendorSupport) {
let originalPkgs = values[0];
let updatedPkgs = values[1];
assert.equal(originalPkgs.length, updatedPkgs.length);
for (let index = 0; index < originalPkgs.length; index++) {
assert.equal(updatedPkgs[index], originalPkgs[index]);
}
}
});
}).then(() => done(), done);
});
});