src/goLint.ts (124 lines of code) (raw):

/*--------------------------------------------------------- * 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 { CommandFactory } from './commands'; import { getGoConfig, getGoplsConfig } from './config'; import { toolExecutionEnvironment } from './goEnv'; import { diagnosticsStatusBarItem, outputChannel } from './goStatus'; import { goplsStaticcheckEnabled } from './goTools'; import { getWorkspaceFolderPath, handleDiagnosticErrors, ICheckResult, resolvePath, runTool } from './util'; /** * Runs linter on the current file, package or workspace. */ export function lintCode(scope?: string): CommandFactory { return (ctx, goCtx) => () => { const editor = vscode.window.activeTextEditor; if (scope !== 'workspace') { if (!editor) { vscode.window.showInformationMessage('No editor is active, cannot find current package to lint'); return; } if (editor.document.languageId !== 'go') { 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 : undefined; const goConfig = getGoConfig(documentUri); const goplsConfig = getGoplsConfig(documentUri); outputChannel.clear(); // Ensures stale output from lint on save is cleared diagnosticsStatusBarItem.show(); diagnosticsStatusBarItem.text = 'Linting...'; goLint(documentUri, goConfig, goplsConfig, scope) .then((warnings) => { handleDiagnosticErrors( goCtx, editor ? editor.document : undefined, warnings, goCtx.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 | undefined, goConfig: vscode.WorkspaceConfiguration, goplsConfig: vscode.WorkspaceConfiguration, scope?: string ): Promise<ICheckResult[]> { const lintTool = goConfig['lintTool'] || 'staticcheck'; if (lintTool === 'staticcheck' && goplsStaticcheckEnabled(goConfig, goplsConfig)) { return Promise.resolve([]); } 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 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 === '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 (args.indexOf('--issues-exit-code=') === -1) { // adds an explicit no-error-code return argument, to avoid npm error // message detection logic. See golang/vscode-go/issues/411 args.push('--issues-exit-code=0'); } } 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;