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

Commit

Permalink
Merge pull request #7 from Verseth/feature/quick_fixes
Browse files Browse the repository at this point in the history
Implement quick fixes for Rubocop
  • Loading branch information
LoranKloeze authored Aug 23, 2022
2 parents 9365fca + 4f88778 commit 7109905
Show file tree
Hide file tree
Showing 9 changed files with 772 additions and 120 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# 0.9.3 (not released yet)

- Introduce quick fix functionality that gives you the possibility to fix or ignore one or more errors (thanks [@Verseth](https://github.com/Verseth))
- Bugfix where even non-Ruby files were autocorrected (thanks [@Verseth](https://github.com/Verseth))

# 0.9.2
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ When autoCorrect is enabled, the history of changing file is broken.
- lint by executing the command "Ruby: lint by rubocop" (cmd+shift+p and type command)
- auto correct when saving a file
- auto correct command "Ruby: autocorrect by rubocop"
- quick fixes so you can fix or ignore an error [PR with explanation](https://github.com/LoranKloeze/vscode-ruby-rubocop-revived/pull/7)

### Exclude file

Expand Down
35 changes: 31 additions & 4 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ export function activate(context: vscode.ExtensionContext): void {
context.subscriptions.push(diag);

const rubocop = new Rubocop(diag);
const disposable = vscode.commands.registerCommand('ruby.rubocop', () => {
const disposable = vscode.commands.registerCommand('ruby.rubocop', (onComplete?: () => void) => {
const document = vscode.window.activeTextEditor.document;
rubocop.execute(document);
rubocop.execute(document, onComplete);
});

context.subscriptions.push(disposable);
Expand Down Expand Up @@ -52,12 +52,39 @@ export function activate(context: vscode.ExtensionContext): void {
rubocop.formattingProvider
);

vscode.languages.registerCodeActionsProvider(
'ruby',
rubocop.quickFixProvider
);
vscode.languages.registerCodeActionsProvider(
'gemfile',
rubocop.quickFixProvider
);

const autocorrectDisposable = vscode.commands.registerCommand(
'ruby.rubocop.autocorrect',
() => {
return rubocop.executeAutocorrect();
(...args) => {
rubocop.executeAutocorrect(
args,
() => vscode.commands.executeCommand('ruby.rubocop')
);
}
);

context.subscriptions.push(autocorrectDisposable);

const disableCopDisposable = vscode.commands.registerCommand(
'ruby.rubocop.disableCop',
(workspaceFolder?: vscode.WorkspaceFolder, copName?: string) => {
if(workspaceFolder === null || copName === null) return;

rubocop.disableCop(
workspaceFolder,
copName,
() => vscode.commands.executeCommand('ruby.rubocop')
);
}
);

context.subscriptions.push(disableCopDisposable);
}
45 changes: 45 additions & 0 deletions src/helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import * as fs from 'fs';
import * as path from 'path';
import * as vscode from 'vscode';

import { getConfig } from './configuration';

export function isFileUri(uri: vscode.Uri): boolean {
return uri.scheme === 'file';
}

export function getCurrentPath(fileUri: vscode.Uri): string {
const wsfolder = vscode.workspace.getWorkspaceFolder(fileUri);
return (wsfolder && wsfolder.uri.fsPath) || path.dirname(fileUri.fsPath);
}

// extract argument to an array
export function getCommandArguments(fileName: string): string[] {
let commandArguments = ['--stdin', fileName, '--force-exclusion'];
const extensionConfig = getConfig();
if (extensionConfig.configFilePath !== '') {
const found = [extensionConfig.configFilePath]
.concat(
(vscode.workspace.workspaceFolders || []).map((ws) =>
path.join(ws.uri.path, extensionConfig.configFilePath)
)
)
.filter((p: string) => fs.existsSync(p));

if (found.length == 0) {
vscode.window.showWarningMessage(
`${extensionConfig.configFilePath} file does not exist. Ignoring...`
);
} else {
if (found.length > 1) {
vscode.window.showWarningMessage(
`Found multiple files (${found}) will use ${found[0]}`
);
}
const config = ['--config', found[0]];
commandArguments = commandArguments.concat(config);
}
}

return commandArguments;
}
145 changes: 29 additions & 116 deletions src/rubocop.ts
Original file line number Diff line number Diff line change
@@ -1,124 +1,20 @@
import { RubocopOutput, RubocopFile, RubocopOffense } from './rubocopOutput';
import { TaskQueue, Task } from './taskQueue';
import * as cp from 'child_process';
import * as vscode from 'vscode';
import * as fs from 'fs';
import * as path from 'path';
import * as vscode from 'vscode';
import { getConfig, RubocopConfig } from './configuration';
import { ExecFileException } from 'child_process';

export class RubocopAutocorrectProvider
implements vscode.DocumentFormattingEditProvider
{
public provideDocumentFormattingEdits(
document: vscode.TextDocument
): vscode.TextEdit[] {
const config = getConfig();
try {
const args = [...getCommandArguments(document.fileName), '--autocorrect'];

if (config.useServer) {
args.push('--server');
}

const options = {
cwd: getCurrentPath(document.uri),
input: document.getText(),
};
let stdout;
if (config.useBundler) {
stdout = cp.execSync(`${config.command} ${args.join(' ')}`, options);
} else {
stdout = cp.execFileSync(config.command, args, options);
}

return this.onSuccess(document, stdout);
} catch (e) {
// if there are still some offences not fixed RuboCop will return status 1
if (e.status !== 1) {
vscode.window.showWarningMessage(
'An error occurred during auto-correction'
);
console.log(e);
return [];
} else {
return this.onSuccess(document, e.stdout);
}
}
}

// Output of auto-correction looks like this:
//
// {"metadata": ... {"offense_count":5,"target_file_count":1,"inspected_file_count":1}}====================
// def a
// 3
// end
//
// So we need to parse out the actual auto-corrected ruby
private onSuccess(document: vscode.TextDocument, stdout: Buffer) {
const stringOut = stdout.toString();
const autoCorrection = stringOut.match(
/^.*\n====================(?:\n|\r\n)([.\s\S]*)/m
);
if (!autoCorrection) {
throw new Error(`Error parsing auto-correction from CLI: ${stringOut}`);
}
return [
new vscode.TextEdit(this.getFullRange(document), autoCorrection.pop()),
];
}

private getFullRange(document: vscode.TextDocument): vscode.Range {
return new vscode.Range(
new vscode.Position(0, 0),
document.lineAt(document.lineCount - 1).range.end
);
}
}

function isFileUri(uri: vscode.Uri): boolean {
return uri.scheme === 'file';
}

function getCurrentPath(fileUri: vscode.Uri): string {
const wsfolder = vscode.workspace.getWorkspaceFolder(fileUri);
return (wsfolder && wsfolder.uri.fsPath) || path.dirname(fileUri.fsPath);
}

// extract argument to an array
function getCommandArguments(fileName: string): string[] {
let commandArguments = ['--stdin', fileName, '--force-exclusion'];
const extensionConfig = getConfig();
if (extensionConfig.configFilePath !== '') {
const found = [extensionConfig.configFilePath]
.concat(
(vscode.workspace.workspaceFolders || []).map((ws) =>
path.join(ws.uri.path, extensionConfig.configFilePath)
)
)
.filter((p: string) => fs.existsSync(p));

if (found.length == 0) {
vscode.window.showWarningMessage(
`${extensionConfig.configFilePath} file does not exist. Ignoring...`
);
} else {
if (found.length > 1) {
vscode.window.showWarningMessage(
`Found multiple files (${found}) will use ${found[0]}`
);
}
const config = ['--config', found[0]];
commandArguments = commandArguments.concat(config);
}
}

return commandArguments;
}
import { RubocopOutput, RubocopFile, RubocopOffense } from './rubocopOutput';
import { TaskQueue, Task } from './taskQueue';
import { getConfig, RubocopConfig } from './configuration';
import RubocopAutocorrectProvider from './rubocopAutocorrectProvider';
import { getCommandArguments, isFileUri, getCurrentPath } from './helper';
import RubocopQuickFixProvider from './rubocopQuickFixProvider';

export class Rubocop {
public config: RubocopConfig;
public formattingProvider: RubocopAutocorrectProvider;
public quickFixProvider: RubocopQuickFixProvider;
private diag: vscode.DiagnosticCollection;
private additionalArguments: string[];
private taskQueue: TaskQueue = new TaskQueue();
Expand All @@ -131,6 +27,19 @@ export class Rubocop {
this.additionalArguments = additionalArguments;
this.config = getConfig();
this.formattingProvider = new RubocopAutocorrectProvider();
this.quickFixProvider = new RubocopQuickFixProvider(this.diag);
}

public disableCop(workspaceFolder: vscode.WorkspaceFolder, copName: string, onComplete?: () => void): void {
const disableCopContent = `
${copName}:
Enabled: false
`;

const rubocopYamlPath = path.join(workspaceFolder.uri.fsPath, '.rubocop.yml')
fs.appendFile(rubocopYamlPath, disableCopContent, () => {
if(onComplete) onComplete();
});
}

public executeAutocorrectOnSave(): boolean {
Expand All @@ -142,11 +51,11 @@ export class Rubocop {
return this.executeAutocorrect();
}

public executeAutocorrect(): boolean {
vscode.window.activeTextEditor?.edit((editBuilder) => {
public executeAutocorrect(additionalArguments: string[] = [], onComplete?: () => void): boolean {
const promise = vscode.window.activeTextEditor?.edit((editBuilder) => {
const document = vscode.window.activeTextEditor.document;
const edits =
this.formattingProvider.provideDocumentFormattingEdits(document);
this.formattingProvider.getAutocorrectEdits(document, additionalArguments);
// We only expect one edit from our formatting provider.
if (edits.length === 1) {
const edit = edits[0];
Expand All @@ -159,6 +68,8 @@ export class Rubocop {
}
});

if(onComplete) promise.then(() => onComplete());

return true;
}

Expand Down Expand Up @@ -201,8 +112,10 @@ export class Rubocop {
loc.length + loc.column - 1
);
const sev = this.severity(offence.severity);
const message = `${offence.message} (${offence.severity}:${offence.cop_name})`;
const correctableString = offence.correctable ? '[Correctable]' : ''
const message = offence.message;
const diagnostic = new vscode.Diagnostic(range, message, sev);
diagnostic.source = `${correctableString}(${offence.severity}:${offence.cop_name})`
diagnostics.push(diagnostic);
});
entries.push([uri, diagnostics]);
Expand Down
77 changes: 77 additions & 0 deletions src/rubocopAutocorrectProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import * as vscode from 'vscode';
import * as cp from 'child_process';

import { getConfig } from './configuration';
import { getCommandArguments, getCurrentPath } from './helper';

export default class RubocopAutocorrectProvider
implements vscode.DocumentFormattingEditProvider
{
public provideDocumentFormattingEdits(document: vscode.TextDocument): vscode.TextEdit[] {
return this.getAutocorrectEdits(document);
}

public getAutocorrectEdits(document: vscode.TextDocument, additionalArguments: string[] = []): vscode.TextEdit[] {
const config = getConfig();
try {
const args = [...getCommandArguments(document.fileName), ...additionalArguments];
if(additionalArguments.length === 0) args.push('--autocorrect');

if (config.useServer) {
args.push('--server');
}

const options = {
cwd: getCurrentPath(document.uri),
input: document.getText(),
};
let stdout;
if (config.useBundler) {
stdout = cp.execSync(`${config.command} ${args.join(' ')}`, options);
} else {
stdout = cp.execFileSync(config.command, args, options);
}

return this.onSuccess(document, stdout);
} catch (e) {
// if there are still some offences not fixed RuboCop will return status 1
if (e.status !== 1) {
vscode.window.showWarningMessage(
'An error occurred during auto-correction'
);
console.log(e);
return [];
} else {
return this.onSuccess(document, e.stdout);
}
}
}

// Output of auto-correction looks like this:
//
// {"metadata": ... {"offense_count":5,"target_file_count":1,"inspected_file_count":1}}====================
// def a
// 3
// end
//
// So we need to parse out the actual auto-corrected ruby
private onSuccess(document: vscode.TextDocument, stdout: Buffer) {
const stringOut = stdout.toString();
const autoCorrection = stringOut.match(
/^.*\n====================(?:\n|\r\n)([.\s\S]*)/m
);
if (!autoCorrection) {
throw new Error(`Error parsing auto-correction from CLI: ${stringOut}`);
}
return [
new vscode.TextEdit(this.getFullRange(document), autoCorrection.pop()),
];
}

private getFullRange(document: vscode.TextDocument): vscode.Range {
return new vscode.Range(
new vscode.Position(0, 0),
document.lineAt(document.lineCount - 1).range.end
);
}
}
Loading

0 comments on commit 7109905

Please sign in to comment.