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

Added commands "Go Lint Package" and "Go Lint Workspace" #1258

Merged
merged 6 commits into from
Nov 9, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,16 @@
"command": "go.get.package",
"title": "Go: Get Package",
"description": "Run `go get -v` on the package on the current line."
},
{
"command": "go.lint.package",
"title": "Go: Lint Package",
"description": "Run linter in the package of the current file."
},
{
"command": "go.lint.workspace",
"title": "Go: Lint Workspace",
"description": "Run linter in the current workspace."
}
],
"debuggers": [
Expand Down
133 changes: 4 additions & 129 deletions src/goCheck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ import { getCoverage } from './goCover';
import { outputChannel } from './goStatus';
import { promptForMissingTool } from './goInstallTools';
import { goTest } from './testUtils';
import { getBinPath, parseFilePrelude, getCurrentGoPath, getToolsEnvVars, resolvePath } from './util';
import { getBinPath, parseFilePrelude, getCurrentGoPath, getToolsEnvVars, resolvePath, ICheckResult, runTool } from './util';
import { getNonVendorPackages } from './goPackages';
import { getTestFlags } from './testUtils';
import { goLint } from './goLint';

let statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
statusBarItem.command = 'go.test.showOutput';
Expand All @@ -30,90 +31,6 @@ export function removeTestStatus(e: vscode.TextDocumentChangeEvent) {
statusBarItem.text = '';
}

export interface ICheckResult {
file: string;
line: number;
msg: string;
severity: string;
}

/**
* Runs given Go tool and returns errors/warnings that can be fed to the Problems Matcher
* @param args Arguments to be passed while running given tool
* @param cwd cwd that will passed in the env object while running given tool
* @param severity error or warning
* @param useStdErr If true, the stderr of the output of the given tool will be used, else stdout will be used
* @param toolName The name of the Go tool to run. If none is provided, the go runtime itself is used
* @param printUnexpectedOutput If true, then output that doesnt match expected format is printed to the output channel
*/
function runTool(args: string[], cwd: string, severity: string, useStdErr: boolean, toolName: string, env: any, printUnexpectedOutput?: boolean): Promise<ICheckResult[]> {
let goRuntimePath = getGoRuntimePath();
let cmd = toolName ? getBinPath(toolName) : goRuntimePath;
return new Promise((resolve, reject) => {
cp.execFile(cmd, args, { env: env, cwd: cwd }, (err, stdout, stderr) => {
try {
if (err && (<any>err).code === 'ENOENT') {
// Since the tool is run on save which can be frequent
// we avoid sending explicit notification if tool is missing
console.log(`Cannot find ${toolName ? toolName : goRuntimePath}`);
return resolve([]);
}
if (err && stderr && !useStdErr) {
outputChannel.appendLine(['Error while running tool:', cmd, ...args].join(' '));
outputChannel.appendLine(stderr);
return resolve([]);
}
let lines = (useStdErr ? stderr : stdout).toString().split('\n');
outputChannel.appendLine(['Finished running tool:', cmd, ...args].join(' '));

let ret: ICheckResult[] = [];
let unexpectedOutput = false;
let atleastSingleMatch = false;
for (let i = 0; i < lines.length; i++) {
if (lines[i][0] === '\t' && ret.length > 0) {
ret[ret.length - 1].msg += '\n' + lines[i];
continue;
}
let match = /^([^:]*: )?((.:)?[^:]*):(\d+)(:(\d+)?)?:(?:\w+:)? (.*)$/.exec(lines[i]);
if (!match) {
if (printUnexpectedOutput && useStdErr && stderr) unexpectedOutput = true;
continue;
}
atleastSingleMatch = true;
let [_, __, file, ___, lineStr, ____, charStr, msg] = match;
let line = +lineStr;

// Building skips vendor folders,
// But vet and lint take in directories and not import paths, so no way to skip them
// So prune out the results from vendor folders herehere.
if (!path.isAbsolute(file) && (file.startsWith(`vendor${path.sep}`) || file.indexOf(`${path.sep}vendor${path.sep}`) > -1)) {
continue;
}

file = path.resolve(cwd, file);
ret.push({ file, line, msg, severity });
outputChannel.appendLine(`${file}:${line}: ${msg}`);
}
if (!atleastSingleMatch && unexpectedOutput && vscode.window.activeTextEditor) {
outputChannel.appendLine(stderr);
if (err) {
ret.push({
file: vscode.window.activeTextEditor.document.fileName,
line: 1,
msg: stderr,
severity: 'error'
});
}
}
outputChannel.appendLine('');
resolve(ret);
} catch (e) {
reject(e);
}
});
});
}

export function check(fileUri: vscode.Uri, goConfig: vscode.WorkspaceConfiguration): Promise<ICheckResult[]> {
outputChannel.clear();
let runningToolsPromises = [];
Expand Down Expand Up @@ -220,50 +137,8 @@ export function check(fileUri: vscode.Uri, goConfig: vscode.WorkspaceConfigurati
}

if (!!goConfig['lintOnSave'] && goConfig['lintOnSave'] !== 'off') {
let lintTool = goConfig['lintTool'] || 'golint';
let lintFlags: string[] = goConfig['lintFlags'] || [];
let lintEnv = Object.assign({}, env);
let args = [];
let configFlag = '--config=';
lintFlags.forEach(flag => {
// --json is not a valid flag for golint and in gometalinter, it is used to print output in json which we dont want
if (flag === '--json') {
return;
}
if (flag.startsWith(configFlag)) {
let configFilePath = flag.substr(configFlag.length);
configFilePath = resolvePath(configFilePath);
args.push(`${configFlag}${configFilePath}`);
return;
}
args.push(flag);
});
if (lintTool === 'gometalinter') {
if (args.indexOf('--aggregate') === -1) {
args.push('--aggregate');
}
if (goConfig['toolsGopath']) {
// gometalinter will expect its linters to be in the GOPATH
// So add the toolsGopath to GOPATH
lintEnv['GOPATH'] += path.delimiter + goConfig['toolsGopath'];
}
}

let lintWorkDir = cwd;

if (goConfig['lintOnSave'] === 'workspace' && currentWorkspace) {
args.push('./...');
lintWorkDir = currentWorkspace;
}

runningToolsPromises.push(runTool(
args,
lintWorkDir,
'warning',
false,
lintTool,
lintEnv
));
let lintWorkspace = goConfig['lintOnSave'] === 'workspace';
runningToolsPromises.push(goLint(fileUri, goConfig, lintWorkspace));
}

if (!!goConfig['vetOnSave'] && goConfig['vetOnSave'] !== 'off') {
Expand Down
109 changes: 109 additions & 0 deletions src/goLint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import path = require('path');
import vscode = require('vscode');
import os = require('os');
import cp = require('child_process');
import { getToolsEnvVars, resolvePath, getBinPath, runTool, ICheckResult } from './util';
import { outputChannel } from './goStatus';
import { getGoRuntimePath } from './goPath';

/**
* Runs linter in the current package.
*
* @param fileUri Document uri.
* @param goConfig Configuration for the Go extension.
*/
export function lintCurrentPackage(fileUri: vscode.Uri, goConfig: vscode.WorkspaceConfiguration): Promise<ICheckResult[]> {
let editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showInformationMessage('No editor is active.');
return Promise.resolve([]);
}

outputChannel.clear();
return goLint(fileUri, goConfig);
}

/**
* Runs linter in all packages in the current workspace.
*
* @param fileUri Document uri.
* @param goConfig Configuration for the Go extension.
*/
export function lintWorkspace(fileUri: vscode.Uri, goConfig: vscode.WorkspaceConfiguration): Promise<ICheckResult[]> {
outputChannel.clear();
return goLint(fileUri, goConfig, true);
}

/**
* Runs linter and presents the output in the 'Go' channel and in the diagnostic collections.
*
* @param fileUri Document uri.
* @param goConfig Configuration for the Go extension.
* @param lintWorkspace If true runs linter in all workspace.
*/
export function goLint(fileUri: vscode.Uri, goConfig: vscode.WorkspaceConfiguration, lintWorkspace?: boolean): Promise<ICheckResult[]> {
let lintTool = goConfig['lintTool'] || 'golint';
let lintFlags: string[] = goConfig['lintFlags'] || [];
let lintEnv = Object.assign({}, getToolsEnvVars());
let args = [];
let configFlag = '--config=';
lintFlags.forEach(flag => {
// --json is not a valid flag for golint and in gometalinter, it is used to print output in json which we dont want
if (flag === '--json') {
return;
}
if (flag.startsWith(configFlag)) {
let configFilePath = flag.substr(configFlag.length);
configFilePath = resolvePath(configFilePath);
args.push(`${configFlag}${configFilePath}`);
return;
}
args.push(flag);
});
if (lintTool === 'gometalinter') {
if (args.indexOf('--aggregate') === -1) {
args.push('--aggregate');
}
if (goConfig['toolsGopath']) {
// gometalinter will expect its linters to be in the GOPATH
// So add the toolsGopath to GOPATH
lintEnv['GOPATH'] += path.delimiter + goConfig['toolsGopath'];
}
}

let lintWorkDir: string;

if (lintWorkspace) {
let currentWorkspace: string;
if (fileUri) {
let workspace = vscode.workspace.getWorkspaceFolder(fileUri);
if (workspace) {
currentWorkspace = workspace.uri.fsPath;
}
}

if (!currentWorkspace) {
// finding workspace root path
let folders = vscode.workspace.workspaceFolders;
if (folders && folders.length) {
currentWorkspace = folders[0].uri.fsPath;
} else {
return Promise.resolve([]);
}
}

lintWorkDir = currentWorkspace;
args.push('./...');
} else {
lintWorkDir = path.dirname(fileUri.fsPath);
}

return runTool(
args,
lintWorkDir,
'warning',
false,
lintTool,
lintEnv
);
}
Loading