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

Commit

Permalink
Replace full path for vendor packages with relative path (#491)
Browse files Browse the repository at this point in the history
  • Loading branch information
ramya-rao-a authored Oct 17, 2016
1 parent 68d5596 commit 06eabf7
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 23 deletions.
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');
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> {
let goRuntimePath = getGoRuntimePath();

Expand All @@ -216,4 +220,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);
});
});

0 comments on commit 06eabf7

Please sign in to comment.