Skip to content

Commit

Permalink
Add relative lcov path support (#33)
Browse files Browse the repository at this point in the history
* fix changelog link

* create wrappers and interfaces

* use wrappers in a type safe way

* cleanup tests are increased type safety

* update wrapper interfaces

* add alternative source file compare

* add relative path option with multi os support

* add new setting to readme, cleanup english

* add tests to cover absolute and relative modes

* make readme more descriptive for alt option
  • Loading branch information
ryanluker committed Mar 23, 2017
1 parent 8ba8199 commit 07d5070
Show file tree
Hide file tree
Showing 11 changed files with 290 additions and 172 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
|Setting | Description
|--------|------------
|`coverage-gutters.lcovname`|Allows specification of a custom lcov file name
|`coverage-gutters.altSfCompare`|Uses a relative method of comparing lcov source file paths
|`coverage-gutters.highlightlight`|Changes the highlight for light themes
|`coverage-gutters.highlightdark`|Changes the Highlight for dark themes
|`coverage-gutters.gutterIconPathDark`|Relative path to an icon in the extension for dark themes
Expand All @@ -40,7 +41,7 @@ Some examples for the highlight colour are as follows:
- none ( just missing functionality :) )

## Release Notes
### [Changelog](CHANGELOG.mb)
### [Changelog](https://github.com/ryanluker/vscode-coverage-gutters/releases)

## Contribution Guidelines
- test backed code changes
Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@
"default": "lcov.info",
"description": "name of your lcov file"
},
"coverage-gutters.altSfCompare": {
"type": "boolean",
"default": false,
"description": "uses a relative method of comparing lcov source file paths"
},
"coverage-gutters.highlightlight": {
"type": "string",
"default": "rgba(166, 220, 142, 0.75)",
Expand Down
40 changes: 18 additions & 22 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,59 @@
'use strict';

import {VscodeInterface} from "./wrappers/vscode";
import {TextEditorDecorationType, ExtensionContext} from "vscode";

export interface configStore {
lcovFileName: string,
coverageDecorationType: TextEditorDecorationType,
gutterDecorationType: TextEditorDecorationType
lcovFileName: string;
coverageDecorationType: TextEditorDecorationType;
gutterDecorationType: TextEditorDecorationType;
altSfCompare: boolean;
}

export class Config {
private createTextEditorDecorationType;
private executeCommand;
private workspaceConfig;
private vscode: VscodeInterface;
private context: ExtensionContext;

private lcovFileName: string;
private coverageDecorationType: TextEditorDecorationType;
private gutterDecorationType: TextEditorDecorationType;
private altSfCompare: boolean;

constructor(
createTextEditorDecorationType,
executeCommand,
workspaceConfig,
context: ExtensionContext
) {
this.createTextEditorDecorationType = createTextEditorDecorationType;
this.executeCommand = executeCommand;
this.workspaceConfig = workspaceConfig;
constructor(vscode: VscodeInterface, context: ExtensionContext) {
this.vscode = vscode;
this.context = context;
}

public get(): configStore {
return {
lcovFileName: this.lcovFileName,
coverageDecorationType: this.coverageDecorationType,
gutterDecorationType: this.gutterDecorationType
gutterDecorationType: this.gutterDecorationType,
altSfCompare: this.altSfCompare
}
}

public setup(): configStore {
//Customizable UI configurations
const rootCustomConfig = this.workspaceConfig("coverage-gutters.customizable");
const rootCustomConfig = this.vscode.getConfiguration("coverage-gutters.customizable");
const configsCustom = Object.keys(rootCustomConfig);
for(let element of configsCustom) {
this.executeCommand(
this.vscode.executeCommand(
"setContext",
"config.coverage-gutters.customizable." + element,
rootCustomConfig.get(element));
}

//Basic configurations
const rootConfig = this.workspaceConfig("coverage-gutters");
const rootConfig = this.vscode.getConfiguration("coverage-gutters");
this.lcovFileName = rootConfig.get("lcovname") as string;
this.altSfCompare = rootConfig.get("altSfCompare") as boolean;

const coverageLightBackgroundColour = rootConfig.get("highlightlight") as string;
const coverageDarkBackgroundColour = rootConfig.get("highlightdark") as string;
const gutterIconPathDark = rootConfig.get("gutterIconPathDark") as string;
const gutterIconPathLight = rootConfig.get("gutterIconPathLight") as string;

this.coverageDecorationType = this.createTextEditorDecorationType({
this.coverageDecorationType = this.vscode.createTextEditorDecorationType({
isWholeLine: true,
light: {
backgroundColor: coverageLightBackgroundColour
Expand All @@ -67,7 +63,7 @@ export class Config {
}
});

this.gutterDecorationType = this.createTextEditorDecorationType({
this.gutterDecorationType = this.vscode.createTextEditorDecorationType({
light: {
gutterIconPath: this.context.asAbsolutePath(gutterIconPathLight)
},
Expand Down
31 changes: 12 additions & 19 deletions src/gutters.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,27 @@
'use strict';

import {
createTextEditorDecorationType,
executeCommand,
getConfiguration,
setDecorations,
findFiles
} from "./wrappers/vscode";
import {readFile} from "./wrappers/fs";
import {vscode} from "./wrappers/vscode";
import {fs} from "./wrappers/fs";
import {lcovParse} from "./wrappers/lcov-parse";
import {Range, window, ExtensionContext} from "vscode";
import {ExtensionContext, window} from "vscode";

import {Lcov, lcov} from "./lcov";
import {Indicators, indicators} from "./indicators";
import {Config, configStore} from "./config";

const vscodeImpl = new vscode();
const fsImpl = new fs();
const parseImpl = new lcovParse();

export class Gutters {
private configStore: configStore;
private lcov: lcov;
private indicators: indicators;

constructor(context: ExtensionContext) {
this.configStore = new Config(
createTextEditorDecorationType,
executeCommand,
getConfiguration,
context
).setup();
this.lcov = new Lcov(this.configStore, findFiles, readFile);
this.indicators = new Indicators(this.configStore, lcovParse, setDecorations);
this.configStore = new Config(vscodeImpl, context).setup();
this.lcov = new Lcov(this.configStore, vscodeImpl, fsImpl);
this.indicators = new Indicators(parseImpl, vscodeImpl, this.configStore);
}

public async displayCoverageForActiveFile() {
Expand All @@ -44,7 +37,7 @@ export class Gutters {
}

public dispose() {
setDecorations(this.configStore.coverageDecorationType, []);
setDecorations(this.configStore.gutterDecorationType, []);
vscodeImpl.setDecorations(this.configStore.coverageDecorationType, []);
vscodeImpl.setDecorations(this.configStore.gutterDecorationType, []);
}
}
56 changes: 44 additions & 12 deletions src/indicators.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,30 @@
"use strict";

import {configStore} from "./config";
import {LcovParseInterface} from "./wrappers/lcov-parse";
import {VscodeInterface} from "./wrappers/vscode";

import {Range} from "vscode";
import {Detail} from "lcov-parse";

export interface indicators {
render(lines: Array<Detail>): Promise<string>,
extract(lcovFile: string, file: string): Promise<Array<Detail>>
render(lines: Array<Detail>): Promise<string>;
extract(lcovFile: string, file: string): Promise<Array<Detail>>;
}

export class Indicators implements indicators{
private parseLcov;
private parse: LcovParseInterface;
private vscode: VscodeInterface;
private configStore: configStore;
private setDecorations;

constructor(configStore, parseLcov, setDecorations) {
constructor(
parse: LcovParseInterface,
vscode: VscodeInterface,
configStore: configStore
) {
this.parse = parse;
this.vscode = vscode;
this.configStore = configStore;
this.parseLcov = parseLcov;
this.setDecorations = setDecorations;
}

public render(lines: Detail[]): Promise<string> {
Expand All @@ -28,24 +35,49 @@ export class Indicators implements indicators{
renderLines.push(new Range(detail.line - 1, 0, detail.line - 1, 0));
}
});
this.setDecorations(this.configStore.coverageDecorationType, renderLines);
this.setDecorations(this.configStore.gutterDecorationType, renderLines);
this.vscode.setDecorations(this.configStore.coverageDecorationType, renderLines);
this.vscode.setDecorations(this.configStore.gutterDecorationType, renderLines);
return resolve();
});
}

public extract(lcovFile: string, file: string): Promise<Array<Detail>> {
return new Promise<Array<Detail>>((resolve, reject) => {
this.parseLcov(lcovFile, (err, data) => {
this.parse.source(lcovFile, (err, data) => {
if(err) return reject(err);
let section = data.find((section) => {
//prevent hazardous casing mishaps
return section.file.toLocaleLowerCase() === file.toLocaleLowerCase();
return this.compareFilePaths(section.file, file);
});

if(!section) return reject(new Error("No coverage for file!"));
return resolve(section.lines.details);
});
});
}

private compareFilePaths(lcovFile: string, file: string): boolean {
if(this.configStore.altSfCompare) {
//consider windows and linux file paths
const sourceFile = lcovFile.split(/[\\\/]/).reverse();
const openFile = file.split(/[\\\/]/).reverse();
const folderName = this.vscode.getRootPath().split(/[\\\/]/).reverse()[0];
let match = true;
let index = 0;

//work backwards from the file folder leaf to folder node
do {
if(sourceFile[index] === openFile[index]) {
index++;
} else {
match = false;
break;
}
} while(folderName !== openFile[index]);

return match;
} else {
//prevent hazardous casing mishaps
return lcovFile.toLocaleLowerCase() === file.toLocaleLowerCase();
}
}
}
24 changes: 15 additions & 9 deletions src/lcov.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
'use strict';
import {configStore} from "./config";
import {VscodeInterface} from "./wrappers/vscode";
import {FsInterface} from "./wrappers/fs";

export interface lcov {
find(): Promise<string>,
load(lcovPath: string): Promise<string>
find(): Promise<string>;
load(lcovPath: string): Promise<string>;
}

export class Lcov implements lcov {
private configStore: configStore;
private findFiles;
private readFile;
private vscode: VscodeInterface;
private fs: FsInterface;

constructor(configStore: configStore, findFiles, readFile) {
constructor(
configStore: configStore,
vscode: VscodeInterface,
fs: FsInterface
) {
this.configStore = configStore;
this.findFiles = findFiles;
this.readFile = readFile;
this.vscode = vscode;
this.fs = fs;
}

public find(): Promise<string> {
return new Promise((resolve, reject) => {
this.findFiles("**/" + this.configStore.lcovFileName, "**/node_modules/**", 1)
this.vscode.findFiles("**/" + this.configStore.lcovFileName, "**/node_modules/**", 1)
.then((uriLcov) => {
if(!uriLcov.length) return reject(new Error("Could not find a lcov file!"));
return resolve(uriLcov[0].fsPath);
Expand All @@ -29,7 +35,7 @@ export class Lcov implements lcov {

public load(lcovPath: string): Promise<string> {
return new Promise<string>((resolve, reject) => {
this.readFile(lcovPath, (err, data) => {
this.fs.readFile(lcovPath, (err, data) => {
if(err) return reject(err);
return resolve(data.toString());
});
Expand Down
13 changes: 10 additions & 3 deletions src/wrappers/fs.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import {readFile as readFileFS} from "fs";

export function readFile(filename: string, callback: (err: NodeJS.ErrnoException, data: Buffer) => void) {
return readFileFS(filename, callback);
}
export interface FsInterface {
readFile(filename: string, callback: (err: NodeJS.ErrnoException, data: Buffer) => void): void;
}

export class fs implements FsInterface {
public readFile(filename: string, callback: (err: NodeJS.ErrnoException, data: Buffer) => void): void {
return readFileFS(filename, callback);
}
}

12 changes: 9 additions & 3 deletions src/wrappers/lcov-parse.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
"use strict";

import {LcovSection, source} from "lcov-parse";
import {LcovSection, source as sourceLcovParse} from "lcov-parse";

export function lcovParse(file: string, cb: (err: Error, data: Array<LcovSection>) => void): void {
return source(file, cb);
export interface LcovParseInterface {
source(file: string, cb: (err: Error, data: Array<LcovSection>) => void): void;
}

export class lcovParse implements LcovParseInterface {
public source(file: string, cb: (err: Error, data: Array<LcovSection>) => void): void {
return sourceLcovParse(file, cb);
}
}
Loading

0 comments on commit 07d5070

Please sign in to comment.