Skip to content
This repository was archived by the owner on Jul 15, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 5 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
6,898 changes: 6,898 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,16 @@
"command": "go.playground",
"title": "Go: Run on Go Playground",
"description": "Upload the current selection or file to the Go Playground"
},
{
"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
121 changes: 121 additions & 0 deletions src/goLint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import path = require('path');
import vscode = require('vscode');
import os = require('os');
import cp = require('child_process');
import { getToolsEnvVars, resolvePath, getBinPath, runTool, ICheckResult, handleDiagnosticErrors } from './util';
import { outputChannel } from './goStatus';
import { getGoRuntimePath } from './goPath';

/**
* Runs linter in the current package.
*/
export function lintCurrentPackage(): Promise<ICheckResult[]> {
let editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showInformationMessage('No editor is active.');
return;
}

let documentUri = editor ? editor.document.uri : null;
let goConfig = vscode.workspace.getConfiguration('go', documentUri);
outputChannel.clear();
goLint(documentUri, goConfig)
.then(warnings => handleDiagnosticErrors(editor ? editor.document : null, warnings, vscode.DiagnosticSeverity.Warning))
.catch(err => {
vscode.window.showInformationMessage('Error: ' + err);
});
}

/**
* Runs linter in all packages in the current workspace.
*/
export function lintWorkspace() {
let editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showInformationMessage('No editor is active.');
return;
}

let documentUri = editor ? editor.document.uri : null;
let goConfig = vscode.workspace.getConfiguration('go', documentUri);
outputChannel.clear();
goLint(documentUri, goConfig, true)
.then(warnings => handleDiagnosticErrors(editor ? editor.document : null, warnings, vscode.DiagnosticSeverity.Warning))
.catch(err => {
vscode.window.showInformationMessage('Error: ' + err);
});
}

/**
* 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