blob: 2eb80ea9cb1c9e327709e5a17f39cff314d54ef8 [file] [log] [blame]
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE in the project root for license information.
*--------------------------------------------------------*/
import path = require('path');
import vscode = require('vscode');
import { toolExecutionEnvironment } from './goEnv';
import { lintDiagnosticCollection } from './goMain';
import { diagnosticsStatusBarItem, outputChannel } from './goStatus';
import {
getGoConfig,
getToolsGopath,
getWorkspaceFolderPath,
handleDiagnosticErrors,
ICheckResult,
resolvePath,
runTool
} from './util';
/**
* Runs linter on the current file, package or workspace.
*/
export function lintCode(scope?: string) {
const editor = vscode.window.activeTextEditor;
if (!editor && scope !== 'workspace') {
vscode.window.showInformationMessage('No editor is active, cannot find current package to lint');
return;
}
if (editor.document.languageId !== 'go' && scope !== 'workspace') {
vscode.window.showInformationMessage(
'File in the active editor is not a Go file, cannot find current package to lint'
);
return;
}
const documentUri = editor ? editor.document.uri : null;
const goConfig = getGoConfig(documentUri);
outputChannel.clear(); // Ensures stale output from lint on save is cleared
diagnosticsStatusBarItem.show();
diagnosticsStatusBarItem.text = 'Linting...';
goLint(documentUri, goConfig, scope)
.then((warnings) => {
handleDiagnosticErrors(editor ? editor.document : null, warnings, lintDiagnosticCollection);
diagnosticsStatusBarItem.hide();
})
.catch((err) => {
vscode.window.showInformationMessage('Error: ' + err);
diagnosticsStatusBarItem.text = 'Linting Failed';
});
}
/**
* 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 scope Scope in which to run the linter.
*/
export function goLint(
fileUri: vscode.Uri,
goConfig: vscode.WorkspaceConfiguration,
scope?: string
): Promise<ICheckResult[]> {
epoch++;
const closureEpoch = epoch;
if (tokenSource) {
if (running) {
tokenSource.cancel();
}
tokenSource.dispose();
}
tokenSource = new vscode.CancellationTokenSource();
const currentWorkspace = getWorkspaceFolderPath(fileUri);
const cwd = scope === 'workspace' && currentWorkspace ? currentWorkspace : path.dirname(fileUri.fsPath);
if (!path.isAbsolute(cwd)) {
return Promise.resolve([]);
}
const lintTool = goConfig['lintTool'] || 'golint';
const lintFlags: string[] = goConfig['lintFlags'] || [];
const lintEnv = toolExecutionEnvironment();
const args: string[] = [];
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('--config=') || flag.startsWith('-config=')) {
let configFilePath = flag.substr(flag.indexOf('=') + 1).trim();
if (!configFilePath) {
return;
}
configFilePath = resolvePath(configFilePath);
args.push(`${flag.substr(0, flag.indexOf('=') + 1)}${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 + getToolsGopath();
}
}
if (lintTool === 'golangci-lint') {
if (args.indexOf('run') === -1) {
args.unshift('run');
}
if (args.indexOf('--print-issued-lines=false') === -1) {
// print only file:number:column
args.push('--print-issued-lines=false');
}
if (args.indexOf('--out-format=colored-line-number') === -1) {
// print file:number:column.
// Explicit override in case .golangci.yml calls for a format we don't understand
args.push('--out-format=colored-line-number');
}
}
if (scope === 'workspace' && currentWorkspace) {
args.push('./...');
outputChannel.appendLine(`Starting linting the current workspace at ${currentWorkspace}`);
} else if (scope === 'file') {
args.push(fileUri.fsPath);
outputChannel.appendLine(`Starting linting the current file at ${fileUri.fsPath}`);
} else {
outputChannel.appendLine(`Starting linting the current package at ${cwd}`);
}
running = true;
const lintPromise = runTool(args, cwd, 'warning', false, lintTool, lintEnv, false, tokenSource.token).then(
(result) => {
if (closureEpoch === epoch) {
running = false;
}
return result;
}
);
return lintPromise;
}
let epoch = 0;
let tokenSource: vscode.CancellationTokenSource;
let running = false;