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

Commit

Permalink
add fillstruct tool (#1506)
Browse files Browse the repository at this point in the history
* add fillstruct tool

* Refactoring

* add tests and remove config

* Fix tests

* Use best guess of start line when selection spans multiple lines

* Remove unused code

* Fix linting errors
  • Loading branch information
Robin Bartholdson authored and ramya-rao-a committed Feb 19, 2018
1 parent de7f7a8 commit 4884626
Show file tree
Hide file tree
Showing 11 changed files with 234 additions and 2 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Expand Up @@ -50,6 +50,7 @@ install:
- go get -u -v github.com/acroca/go-symbols
- go get -u -v github.com/cweill/gotests/...
- go get -u -v github.com/haya14busa/goplay/cmd/goplay
- go get -u -v github.com/davidrjenni/reftools/cmd/fillstruct
- GO15VENDOREXPERIMENT=1
- if [[ "$(go version)" =~ "go version go1.5" ]]; then echo skipping gometalinter; else go get -u -v github.com/alecthomas/gometalinter; fi
- if [[ "$(go version)" =~ "go version go1.5" ]]; then echo skipping gometalinter; else gometalinter --install; fi
Expand Down
2 changes: 2 additions & 0 deletions README.md
Expand Up @@ -27,6 +27,7 @@ This extension adds rich language support for the Go language to VS Code, includ
- Run Tests under the cursor, in current file, in current package, in the whole workspace (using `go test`)
- Show code coverage
- Generate method stubs for interfaces (using `impl`)
- Fill struct literals with default values (using `fillstruct`)
- [_partially implemented_] Debugging (using `delve`)
- Upload to the Go Playground (using `goplay`)

Expand Down Expand Up @@ -106,6 +107,7 @@ In addition to integrated editing features, the extension also provides several
* `Go: Add Tags` Adds configured tags to selected struct fields.
* `Go: Remove Tags` Removes configured tags from selected struct fields.
* `Go: Generate Interface Stubs` Generates method stubs for given interface
* `Go: Fill Struct` Fills struct literal with default values
* `Go: Run on Go Playground` Upload the current selection or file to the Go Playground

You can access all of the above commands from the command pallet (`Cmd+Shift+P` or `Ctrl+Shift+P`).
Expand Down
15 changes: 15 additions & 0 deletions package.json
Expand Up @@ -174,6 +174,11 @@
"title": "Go: Remove Tags From Struct Fields",
"description": "Remove tags configured in go.removeTags setting from selected struct using gomodifytags"
},
{
"command": "go.fill.struct",
"title": "Go: Fill struct",
"description": "Fill a struct literal with default values"
},
{
"command": "go.show.commands",
"title": "Go: Show All Commands...",
Expand Down Expand Up @@ -829,6 +834,11 @@
"default": true,
"description": "If true, adds command to remove configured tags from struct fields to the editor context menu"
},
"fillStruct": {
"type": "boolean",
"default": true,
"description": "If true, adds command to fill struct literal with default values to the editor context menu"
},
"testAtCursor": {
"type": "boolean",
"default": true,
Expand Down Expand Up @@ -920,6 +930,11 @@
"command": "go.remove.tags",
"group": "Go group 1"
},
{
"when": "editorTextFocus && config.go.editorContextMenuCommands.fillStruct && resourceLangId == go",
"command": "go.fill.struct",
"group": "Go group 1"
},
{
"when": "editorTextFocus && config.go.editorContextMenuCommands.testAtCursor && resourceLangId == go && !config.editor.codeLens",
"command": "go.test.cursor",
Expand Down
95 changes: 95 additions & 0 deletions src/goFillStruct.ts
@@ -0,0 +1,95 @@
/*---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------*/

'use strict';

import vscode = require('vscode');
import { byteOffsetAt, getBinPath, getFileArchive, getToolsEnvVars } from './util';
import cp = require('child_process');
import { promptForMissingTool } from './goInstallTools';

// Interface for the output from fillstruct
interface GoFillStructOutput {
start: number;
end: number;
code: string;
}

export function runFillStruct(editor: vscode.TextEditor) {
let args = getCommonArgs(editor);
if (!args) {
return;
}

return execFillStruct(editor, args);
}

function getCommonArgs(editor: vscode.TextEditor): string[] {
if (!editor) {
vscode.window.showInformationMessage('No editor is active.');
return;
}
if (!editor.document.fileName.endsWith('.go')) {
vscode.window.showInformationMessage('Current file is not a Go file.');
return;
}
let args = ['-modified', '-file', editor.document.fileName];
if (editor.selection.isEmpty) {
let offset = byteOffsetAt(editor.document, editor.selection.start);
args.push('-offset');
args.push(offset.toString());
} else {
args.push('-line');
args.push(`${editor.selection.start.line + 1}`);
}
return args;
}

function getTabsCount(editor: vscode.TextEditor): number {
let startline = editor.selection.start.line;
let tabs = editor.document.lineAt(startline).text.match('^\t*');
return tabs.length;
}

function execFillStruct(editor: vscode.TextEditor, args: string[]): Promise<void> {
let fillstruct = getBinPath('fillstruct');
let input = getFileArchive(editor.document);
let tabsCount = getTabsCount(editor);

return new Promise<void>((resolve, reject) => {
let p = cp.execFile(fillstruct, args, { env: getToolsEnvVars() }, (err, stdout, stderr) => {
try {
if (err && (<any>err).code === 'ENOENT') {
promptForMissingTool('fillstruct');
return reject();
}
if (err) {
vscode.window.showInformationMessage(`Cannot fill struct: ${stderr}`);
return reject();
}

let output = <GoFillStructOutput[]>JSON.parse(stdout);

if (output.length === 0) {
vscode.window.showInformationMessage(`Got empty fillstruct output`);
return reject();
}

let indent = '\t'.repeat(tabsCount);

editor.edit(editBuilder => {
output.forEach((structToFill) => {
const out = structToFill.code.replace(/\n/g, '\n' + indent);
const rangeToReplace = new vscode.Range(editor.document.positionAt(structToFill.start),
editor.document.positionAt(structToFill.end));
editBuilder.replace(rangeToReplace, out);
});
}).then(() => resolve());
} catch (e) {
reject(e);
}
});
p.stdin.end(input);
});
}
6 changes: 4 additions & 2 deletions src/goInstallTools.ts
Expand Up @@ -38,7 +38,8 @@ const allTools: { [key: string]: string } = {
'gometalinter': 'github.com/alecthomas/gometalinter',
'megacheck': 'honnef.co/go/tools/...',
'go-langserver': 'github.com/sourcegraph/go-langserver',
'dlv': 'github.com/derekparker/delve/cmd/dlv'
'dlv': 'github.com/derekparker/delve/cmd/dlv',
'fillstruct': 'github.com/davidrjenni/reftools/cmd/fillstruct'
};

// Tools used explicitly by the basic features of the extension
Expand Down Expand Up @@ -71,7 +72,8 @@ function getTools(goVersion: SemVersion): string[] {
'gorename',
'gomodifytags',
'goplay',
'impl'
'impl',
'fillstruct'
];

if (goLiveErrorsEnabled()) {
Expand Down
5 changes: 5 additions & 0 deletions src/goMain.ts
Expand Up @@ -34,6 +34,7 @@ import { isGoPathSet, getBinPath, sendTelemetryEvent, getExtensionCommands, getG
import { LanguageClient } from 'vscode-languageclient';
import { clearCacheForTools } from './goPath';
import { addTags, removeTags } from './goModifytags';
import { runFillStruct } from './goFillStruct';
import { parseLiveFile } from './goLiveErrors';
import { GoReferencesCodeLensProvider } from './goReferencesCodelens';
import { implCursor } from './goImpl';
Expand Down Expand Up @@ -188,6 +189,10 @@ export function activate(ctx: vscode.ExtensionContext): void {
removeTags(args);
}));

ctx.subscriptions.push(vscode.commands.registerCommand('go.fill.struct', () => {
runFillStruct(vscode.window.activeTextEditor);
}));

ctx.subscriptions.push(vscode.commands.registerCommand('go.impl.cursor', () => {
implCursor();
}));
Expand Down
19 changes: 19 additions & 0 deletions test/fixtures/fillStruct/golden_1.go
@@ -0,0 +1,19 @@
package main

import "time"

type Struct struct {
String string
Number int
Float float64
Time time.Time
}

func main() {
myStruct := Struct{
String: "",
Number: 0,
Float: 0.0,
Time: time.Time{},
}
}
14 changes: 14 additions & 0 deletions test/fixtures/fillStruct/golden_2.go
@@ -0,0 +1,14 @@
package main

import (
"net/http"
)

func main() {
_ = http.Client{
Transport: nil,
CheckRedirect: nil,
Jar: nil,
Timeout: 0,
}
}
14 changes: 14 additions & 0 deletions test/fixtures/fillStruct/input_1.go
@@ -0,0 +1,14 @@
package main

import "time"

type Struct struct {
String string
Number int
Float float64
Time time.Time
}

func main() {
myStruct := Struct{}
}
9 changes: 9 additions & 0 deletions test/fixtures/fillStruct/input_2.go
@@ -0,0 +1,9 @@
package main

import (
"net/http"
)

func main() {
_ = http.Client{}
}
56 changes: 56 additions & 0 deletions test/go.test.ts
Expand Up @@ -25,6 +25,9 @@ import { getAllPackages } from '../src/goPackages';
import { getImportPath } from '../src/util';
import { goPlay } from '../src/goPlayground';
import { goLint } from '../src/goLint';
import { runFillStruct } from '../src/goFillStruct';
import { print } from 'util';
import { TextDocument } from 'vscode-languageserver-types/lib/main';

suite('Go Extension Tests', () => {
let gopath = process.env['GOPATH'];
Expand Down Expand Up @@ -61,6 +64,11 @@ suite('Go Extension Tests', () => {
fs.copySync(path.join(fixtureSourcePath, 'importTest', 'noimports.go'), path.join(fixturePath, 'importTest', 'noimports.go'));
fs.copySync(path.join(fixtureSourcePath, 'importTest', 'groupImports.go'), path.join(fixturePath, 'importTest', 'groupImports.go'));
fs.copySync(path.join(fixtureSourcePath, 'importTest', 'singleImports.go'), path.join(fixturePath, 'importTest', 'singleImports.go'));
fs.copySync(path.join(fixtureSourcePath, 'fillStruct', 'input_1.go'), path.join(fixturePath, 'fillStruct', 'input_1.go'));
fs.copySync(path.join(fixtureSourcePath, 'fillStruct', 'golden_1.go'), path.join(fixturePath, 'fillStruct', 'golden_1.go'));
fs.copySync(path.join(fixtureSourcePath, 'fillStruct', 'input_2.go'), path.join(fixturePath, 'fillStruct', 'input_2.go'));
fs.copySync(path.join(fixtureSourcePath, 'fillStruct', 'golden_2.go'), path.join(fixturePath, 'fillStruct', 'golden_2.go'));
fs.copySync(path.join(fixtureSourcePath, 'fillStruct', 'input_2.go'), path.join(fixturePath, 'fillStruct', 'input_3.go'));
});

suiteTeardown(() => {
Expand Down Expand Up @@ -957,4 +965,52 @@ It returns the number of bytes written and any write error encountered.
}).then(() => done(), done);
});

test('Fill struct', (done) => {
let uri = vscode.Uri.file(path.join(fixturePath, 'fillStruct', 'input_1.go'));
let golden = fs.readFileSync(path.join(fixturePath, 'fillStruct', 'golden_1.go'), 'utf-8');

vscode.workspace.openTextDocument(uri).then((textDocument) => {
return vscode.window.showTextDocument(textDocument).then(editor => {
let selection = new vscode.Selection(12, 15, 12, 15);
editor.selection = selection;
return runFillStruct(editor).then(() => {
assert.equal(vscode.window.activeTextEditor.document.getText(), golden);
return Promise.resolve();
});
});
}).then(() => done(), done);
});

test('Fill struct - select line', (done) => {
let uri = vscode.Uri.file(path.join(fixturePath, 'fillStruct', 'input_2.go'));
let golden = fs.readFileSync(path.join(fixturePath, 'fillStruct', 'golden_2.go'), 'utf-8');

vscode.workspace.openTextDocument(uri).then((textDocument) => {
return vscode.window.showTextDocument(textDocument).then(editor => {
let selection = new vscode.Selection(7, 0, 7, 10);
editor.selection = selection;
return runFillStruct(editor).then(() => {
assert.equal(vscode.window.activeTextEditor.document.getText(), golden);
return Promise.resolve();
});
});
}).then(() => done(), done);
});

test('Fill struct – select non-struct line', (done) => {
let uri = vscode.Uri.file(path.join(fixturePath, 'fillStruct', 'input_3.go'));
// Should return same output as input.
let golden = fs.readFileSync(path.join(fixturePath, 'fillStruct', 'input_3.go'), 'utf-8');

vscode.workspace.openTextDocument(uri).then((textDocument) => {
return vscode.window.showTextDocument(textDocument).then(editor => {
let selection = new vscode.Selection(0, 0, 0, 0);
editor.selection = selection;
return runFillStruct(editor).then(() => {
assert.equal(vscode.window.activeTextEditor.document.getText(), golden);
return Promise.resolve();
});
});
}).then(() => done(), done);
});
});

0 comments on commit 4884626

Please sign in to comment.