Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
4 changes: 3 additions & 1 deletion plugins/vscode/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
out
node_modules
node_modules
.vscode-test
yarn-error.log
17 changes: 9 additions & 8 deletions plugins/vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"homepage": "https://github.com/ndmitchell/ghcid",
"author": "Neil Mitchell, Chris Wendt",
"engines": {
"vscode": "^1.13.0"
"vscode": "^1.76.0"
},
"license": "BSD-3-Clause",
"repository": {
Expand Down Expand Up @@ -47,15 +47,16 @@
"scripts": {
"vscode:prepublish": "tsc -p ./",
"compile": "tsc -watch -p ./",
"postinstall": "node ./node_modules/vscode/bin/install",
"test": "node ./node_modules/vscode/bin/test"
"test": "node out/test/runTest.js"
},
"devDependencies": {
"@types/mocha": "^2.2.48",
"@types/glob": "^8.1.0",
"@types/mocha": "^10.0.1",
"@types/node": "^6.14.9",
"mocha": "^5.2.0",
"typescript": "^3.8.3",
"vsce": "^1.74.0",
"vscode": "^1.1.26"
"@types/vscode": "^1.76.0",
"@vscode/test-electron": "^2.3.0",
"mocha": "^10.2.0",
"typescript": "^4.9.5",
"vsce": "^1.74.0"
}
}
64 changes: 41 additions & 23 deletions plugins/vscode/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,12 @@ export function parseGhcidOutput(dir : string, s : string) : [vscode.Uri, vscode
function clean(lines: string[]): string[] {
const newlines: string[] = []
for (const line of lines) {
if (/In the/.test(line)) break
// "In the expression: ..."
if (/In the/.test(line)) continue
if (/In a stmt/.test(line)) continue

if (line.match(/\s*\|$/)) break
if (line.match(/(\d+)?\s*\|/)) break
// Line annotations like "57 | ..."
if (line.match(/^(\d+)?\s+\|/)) break

newlines.push(line)
}
Expand All @@ -62,31 +64,48 @@ export function parseGhcidOutput(dir : string, s : string) : [vscode.Uri, vscode
let r2 = /(..[^:]+):([0-9]+):([0-9]+)-([0-9]+):/
let r3 = /(..[^:]+):\(([0-9]+),([0-9]+)\)-\(([0-9]+),([0-9]+)\):/
var m : RegExpMatchArray;
let f = (l1,c1,l2,c2) => {
let range = new vscode.Range(parseInt(m[l1])-1,parseInt(m[c1])-1,parseInt(m[l2])-1,parseInt(m[c2]));
let file = vscode.Uri.file(path.isAbsolute(m[1]) ? m[1] : path.join(dir, m[1]));
var s = xs[0].substring(m[0].length).trim();
let mkDiagnostic = (range: vscode.Range): [vscode.Uri, vscode.Diagnostic] => {
const file = m[1].replace(/\\/g, '/');
let uri = vscode.Uri.file(path.isAbsolute(file) ? file : path.join(dir, file));
var s = xs[0].slice(m[0].length).trim();
let i = s.indexOf(':');
var sev = vscode.DiagnosticSeverity.Error;
if (i !== -1) {
if (s.substr(0, i).toLowerCase() == 'warning')
if (s.slice(0, i).toLowerCase() == 'warning')
sev = vscode.DiagnosticSeverity.Warning;
s = s.substr(i+1).trim();
s = s.slice(i+1).trim();
}
let msg = [].concat(/^\s*$/.test(s) ? [] : [s], xs.slice(1));
return [pair(file, new vscode.Diagnostic(range, dedent(msg).join('\n'), sev))];
let msg = [].concat(/^\s*$/.test(s) ? [] : [s], clean(xs).slice(1));
return pair(uri, new vscode.Diagnostic(range, dedent(msg).join('\n'), sev));
};
if (xs[0].startsWith("All good"))
return [];
if (m = xs[0].match(r1))
return f(2,3,2,3);
if (m = xs[0].match(r1)){
// Try to infer the range from the annotation (if present). Example:
//
// |
// 57 | forkIO $ print "hello"
// | ^^^^^
for (let i = 1; i < xs.length; i++) {
const gutter = xs[i].match(/^\d+ \| /)
if (gutter) {
const match = xs[i+1].match(/\^+/)
if (match) {
const start = match.index - gutter[0].length
const end = start + match[0].length
return [mkDiagnostic(new vscode.Range(parseInt(m[2])-1,start,parseInt(m[2])-1,end))]
}
}
}
return [mkDiagnostic(new vscode.Range(parseInt(m[2])-1,parseInt(m[3])-1,parseInt(m[2])-1,parseInt(m[3])))];
}
if (m = xs[0].match(r2))
return f(2,3,2,4);
return [mkDiagnostic(new vscode.Range(parseInt(m[2])-1,parseInt(m[3])-1,parseInt(m[2])-1,parseInt(m[4])))];
if (m = xs[0].match(r3))
return f(2,3,4,5);
return [[new vscode.Uri(), new vscode.Diagnostic(new vscode.Range(0,0,0,0), dedent(xs).join('\n'))]];
return [mkDiagnostic(new vscode.Range(parseInt(m[2])-1,parseInt(m[3])-1,parseInt(m[4])-1,parseInt(m[5])))];
return [[vscode.Uri.parse('untitled:ghcid-errors'), new vscode.Diagnostic(new vscode.Range(0,0,0,0), dedent(xs).join('\n'))]];
}
return [].concat(... split(lines(s)).map(clean).map(parse));
return [].concat(... split(lines(s)).map(parse));
}

function groupDiagnostic(xs : [vscode.Uri, vscode.Diagnostic[]][]) : [vscode.Uri, vscode.Diagnostic[]][] {
Expand Down Expand Up @@ -185,12 +204,11 @@ export async function activate(context: vscode.ExtensionContext) {

let ghcidCommand : string = vscode.workspace.getConfiguration('ghcid').get('command');

let opts : vscode.TerminalOptions =
os.type().startsWith("Windows") ?
{shellPath: "cmd.exe", shellArgs: ["/k", ghcidCommand]} :
{shellPath: ghcidCommand, shellArgs: []};
opts.name = "ghcid";
opts.shellArgs.push("--outputfile=" + file);
let opts : vscode.TerminalOptions = {
name: "ghcid",
shellPath: os.type().startsWith("Windows") ? "cmd.exe" : ghcidCommand,
shellArgs: [...(os.type().startsWith("Windows") ? ["/k", ghcidCommand] : []), "--outputfile=" + file]
}
oldTerminal = vscode.window.createTerminal(opts);
oldTerminal.show();
return watchOutput(vscode.workspace.rootPath, file);
Expand Down
62 changes: 41 additions & 21 deletions plugins/vscode/test/extension.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,46 @@ suite("Extension Tests", () => {

// Defines a Mocha unit test
test("parseGhcidOutput", () => {
let src =
["src\\Test.hs:81:11: error:"
," * No instance for (Num (IO [String])) arising from a use of `+'"
," * In a stmt of a 'do' block: xs <- getArgs + getArgs"
,"src\\General\\Binary.hs:15:1-22: warning: [-Wunused-imports]"
," The import of `Data.List.Extra' is redundant"
,"src\\General\\Binary.hs:17:1-23: warning: [-Wunused-imports]"
," The import of `Data.Tuple.Extra' is redundant"
,"C:\\src\\Development\\Shake\\Internal\\FileInfo.hs:(15,1)-(16,23): warning: [-Wunused-imports]"
," The import of `GHC.IO.Exception' is redundant"];
let want =
[["/src/Test.hs", [80,10,80,11], vscode.DiagnosticSeverity.Error]
,["/src/General/Binary.hs", [14,0,14,22], vscode.DiagnosticSeverity.Warning]
,["/src/General/Binary.hs", [16,0,16,23], vscode.DiagnosticSeverity.Warning]
,["/C:/src/Development/Shake/Internal/FileInfo.hs", [14,0,15,23], vscode.DiagnosticSeverity.Warning]];
let res = myExtension.parseGhcidOutput("", src.join("\r\n"));
let got = res.map(x =>
[ x[0].path
, [x[1].range.start.line, x[1].range.start.character, x[1].range.end.line, x[1].range.end.character]
, x[1].severity]);
assert.deepStrictEqual(got, want);
const tests = [
{ src: `
src\\Test.hs:81:11: error:
* No instance for (Num (IO [String])) arising from a use of \`+'
* In a stmt of a 'do' block: xs <- getArgs + getArgs
src\\General\\Binary.hs:15:1-22: warning: [-Wunused-imports]
The import of \`Data.List.Extra' is redundant
src\\General\\Binary.hs:17:1-23: warning: [-Wunused-imports]
The import of \`Data.Tuple.Extra' is redundant
C:\\src\\Development\\Shake\\Internal\\FileInfo.hs:(15,1)-(16,23): warning: [-Wunused-imports]
The import of \`GHC.IO.Exception' is redundant
`.replace(/\n/g, '\r\n'),
want:
[["/src/Test.hs", [80,10,80,11], vscode.DiagnosticSeverity.Error]
,["/src/General/Binary.hs", [14,0,14,22], vscode.DiagnosticSeverity.Warning]
,["/src/General/Binary.hs", [16,0,16,23], vscode.DiagnosticSeverity.Warning]
,["/C:/src/Development/Shake/Internal/FileInfo.hs", [14,0,15,23], vscode.DiagnosticSeverity.Warning]]
}, {
src: `
/project/Main.hs:57:35: error:
Variable not in scope:
source
:: Int
|
57 | run source srv
| ^^^^^^
`,
want: [
["/project/Main.hs", [56,12,56,18], vscode.DiagnosticSeverity.Error]
]
}
];

for (const test of tests) {
let res = myExtension.parseGhcidOutput("", test.src);
let got = res.map(x =>
[ x[0].path
, [x[1].range.start.line, x[1].range.start.character, x[1].range.end.line, x[1].range.end.character]
, x[1].severity]);
assert.deepStrictEqual(got, test.want);
}
});
});
53 changes: 34 additions & 19 deletions plugins/vscode/test/index.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,37 @@
//
// PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING
//
// This file is providing the test runner to use when running extension tests.
// By default the test runner in use is Mocha based.
//
// You can provide your own test runner if you want to override it by exporting
// a function run(testRoot: string, clb: (error:Error) => void) that the extension
// host can call to run the tests. The test runner is expected to use console.log
// to report the results back to the caller. When the tests are finished, return
// a possible error to the callback or null if none.
import * as path from 'path';
import * as Mocha from 'mocha';
import * as glob from 'glob';

var testRunner = require('vscode/lib/testrunner');
export function run(): Promise<void> {
// Create the mocha test
const mocha = new Mocha({
ui: 'tdd',
color: true
});

// You can directly control Mocha options by uncommenting the following lines
// See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info
testRunner.configure({
ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.)
useColors: true // colored output from test results
});
const testsRoot = path.resolve(__dirname, '..');

module.exports = testRunner;
return new Promise((c, e) => {
glob('**/**.test.js', { cwd: testsRoot }, (err, files) => {
if (err) {
return e(err);
}

// Add files to the test suite
files.forEach(f => mocha.addFile(path.resolve(testsRoot, f)));

try {
// Run the mocha test
mocha.run(failures => {
if (failures > 0) {
e(new Error(`${failures} tests failed.`));
} else {
c();
}
});
} catch (err) {
e(err);
}
});
});
}
23 changes: 23 additions & 0 deletions plugins/vscode/test/runTest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as path from 'path';

import { runTests } from '@vscode/test-electron';

async function main() {
try {
// The folder containing the Extension Manifest package.json
// Passed to `--extensionDevelopmentPath`
const extensionDevelopmentPath = path.resolve(__dirname, '../../');

// The path to the extension test script
// Passed to --extensionTestsPath
const extensionTestsPath = path.resolve(__dirname, './index');

// Download VS Code, unzip it and run the integration test
await runTests({ extensionDevelopmentPath, extensionTestsPath });
} catch (err) {
console.error('Failed to run tests');
process.exit(1);
}
}

main();
Loading