Skip to content

Commit

Permalink
feat(format): expose configuration for customisation
Browse files Browse the repository at this point in the history
  • Loading branch information
JamieMason committed Aug 2, 2019
1 parent 9d037b5 commit 4468586
Show file tree
Hide file tree
Showing 7 changed files with 197 additions and 74 deletions.
3 changes: 2 additions & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
"arrowParens": "always",
"printWidth": 80,
"proseWrap": "always",
"singleQuote": true
"singleQuote": true,
"trailingComma": "all"
}
41 changes: 40 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,46 @@ npm install --save-dev eslint eslint-formatter-git-log
## 🕹 Usage

```
eslint --format=git-log file.js
eslint --format 'git-log' file.js
```

## ⚖️ Configuration

This formatter is written to be as customisable as possible. To create a
customised version of the formatter you can create a file somewhere in your
project which follows the structure below.

For this example I am using the default values. You do not need to provide a
value for every configuration item, only those you want to change.

```js
const gitLogFormatter = require('eslint-formatter-git-log');
const chalk = require('chalk');

module.exports = gitLogFormatter.withConfig({
style: {
error: chalk.red,
filePath: chalk.underline,
warning: chalk.yellow,
location: chalk.dim,
rule: chalk.dim,
commit: chalk.magenta,
date: chalk.greenBright,
email: chalk.blueBright,
},
gutter: ' ',
label: {
error: 'error',
warning: 'warning',
},
locationColumnWidth: 8,
});
```

Then point at your custom formatter instead of the default like so:

```
eslint --format './path-to-your-custom-formatter.js' file.js
```

## ❓ Getting Help
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
},
"devDependencies": {
"@types/eslint": "4.16.6",
"@types/jest": "24.0.15",
"@types/node": "12.0.10",
"@types/jest": "24.0.16",
"@types/node": "12.6.9",
"eslint": "5.16.0",
"jest": "24.8.0",
"prettier": "1.18.2",
"rimraf": "2.6.3",
"ts-jest": "24.0.2",
"tslint": "5.18.0",
"typescript": "3.5.2"
"typescript": "3.5.3"
},
"files": [
"dist"
Expand Down
173 changes: 125 additions & 48 deletions src/formatters/git-log.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import chalk from 'chalk';
import { execSync } from 'child_process';
import { getIn } from './lib/get-in';

export interface EslintMessage {
type DeepPartial<T> = {
[P in keyof T]?: DeepPartial<T[P]>;
};

interface EslintMessage {
column: number;
endColumn?: number;
endLine?: number;
Expand All @@ -17,7 +22,7 @@ export interface EslintMessage {
severity: number;
}

export interface EslintResult {
interface EslintResult {
errorCount: number;
filePath: string;
fixableErrorCount: number;
Expand All @@ -28,51 +33,123 @@ export interface EslintResult {
source?: string;
}

export const gitLogFormatter = (results: EslintResult[]) => {
const {
dim,
red,
underline,
yellow,
magenta,
greenBright,
blueBright
} = chalk;
const errorLabel = 'error';
const warningLabel = 'warning';
const GUTTER = ' ';
const WARNING = yellow(warningLabel);
const ERROR = red(errorLabel);
interface FinalConfig {
/** Whitespace to insert between items when formatting */
gutter: string;
/** Translations for plain text used when formatting */
label: {
/** @example "error" */
error: string;
/** @example "warning" */
warning: string;
};
/** Increase if you have files with 1000s of lines */
locationColumnWidth: number;
/** Which methods of https://github.com/chalk/chalk to use when formatting */
style: {
/** @example "error" */
error: typeof chalk;
/** @example "/Users/guybrush/Dev/grogrates/src/index.js" */
filePath: typeof chalk;
/** @example "warning" */
warning: typeof chalk;
/** @example "161:12" */
location: typeof chalk;
/** @example "no-process-exit" */
rule: typeof chalk;
/** @example "bda304e570" */
commit: typeof chalk;
/** @example "(1 year, 2 months ago)" */
date: typeof chalk;
/** @example "<[email protected]>" */
email: typeof chalk;
};
}

export type Config = DeepPartial<FinalConfig>;

export interface GitLogFormatter {
(results: EslintResult[]): string;
defaultConfig: Config;
withConfig: CreateGitLogFormatter;
}

export type CreateGitLogFormatter = (config: Config) => GitLogFormatter;

export const defaultConfig: FinalConfig = Object.freeze({
gutter: ' ',
label: {
error: 'error',
warning: 'warning',
},
locationColumnWidth: 8,
style: {
error: chalk.red,
filePath: chalk.underline,
warning: chalk.yellow,
location: chalk.dim,
rule: chalk.dim,
commit: chalk.magenta,
date: chalk.greenBright,
email: chalk.blueBright,
},
});

/** Create an instance of the Formatter with your own alternative config */
export const createGitLogFormatter: CreateGitLogFormatter = (config) => {
const formatter = (results: EslintResult[]) => {
const getConfig = (path: string) =>
getIn(path, config) || getIn(path, defaultConfig);

return results.reduce((output, { filePath, messages }) => {
if (messages.length > 0) {
output += `\n${underline(filePath)}\n`;
messages.forEach(
({ ruleId, severity, message, line, column, endLine }) => {
const command = `git blame --date=relative --show-email -L ${line},${endLine} ${filePath}`;
const blame = execSync(command, { encoding: 'utf8' });
const rawLocation = `${line}:${column}`;
const status = severity === 1 ? WARNING : ERROR;
const location = dim(rawLocation);
const rule = ruleId ? dim(ruleId) : '';
const commitMatch = blame.match(/^[^ ]+/) || [''];
const dateMatch = blame.match(/> (.+ ago)/) || ['', ''];
const emailMatch = blame.match(/<([^>]+)>/) || ['', ''];
const commit = magenta(`${commitMatch[0]}`);
const date = greenBright(`(${dateMatch[1].trim()})`);
const email = blueBright(`<${emailMatch[1]}>`);
const locationColumnWith = 8;
const rightAlignLocations = ' '.repeat(
locationColumnWith - rawLocation.length
);
const leftAlignCommitsWithStatuses = ' '.repeat(
rightAlignLocations.length + rawLocation.length + GUTTER.length
);
output += `${rightAlignLocations}${location}${GUTTER}${status}${GUTTER}${message}${GUTTER}${rule}\n`;
output += `${leftAlignCommitsWithStatuses}${commit} ${email} ${date}\n`;
}
);
}
return output;
}, '');
const gutter = getConfig('gutter');
const locationColumnWidth = getConfig('locationColumnWidth');
const errorLabel = getConfig('label.error');
const warningLabel = getConfig('label.warning');
const styledError = getConfig('style.error');
const styledFilePath = getConfig('style.filePath');
const styledWarning = getConfig('style.warning');
const styledLocation = getConfig('style.location');
const styledRule = getConfig('style.rule');
const styledCommit = getConfig('style.commit');
const styledDate = getConfig('style.date');
const styledEmail = getConfig('style.email');
const WARNING = styledWarning(warningLabel);
const ERROR = styledError(errorLabel);

return results.reduce((output, { filePath, messages }) => {
if (messages.length > 0) {
output += `\n${styledFilePath(filePath)}\n`;
messages.forEach(
({ ruleId, severity, message, line, column, endLine }) => {
const command = `git blame --date=relative --show-email -L ${line},${endLine} ${filePath}`;
const blame = execSync(command, { encoding: 'utf8' });
const rawLocation = `${line}:${column}`;
const status = severity === 1 ? WARNING : ERROR;
const commitMatch = blame.match(/^[^ ]+/) || [''];
const dateMatch = blame.match(/> (.+ ago)/) || ['', ''];
const emailMatch = blame.match(/<([^>]+)>/) || ['', ''];
const rightAlignLocations = ' '.repeat(
locationColumnWidth - rawLocation.length,
);
const leftAlignCommitsWithStatuses = ' '.repeat(
rightAlignLocations.length + rawLocation.length + gutter.length,
);
const location = styledLocation(rawLocation);
const rule = ruleId ? styledRule(ruleId) : '';
const commit = styledCommit(`${commitMatch[0]}`);
const date = styledDate(`(${dateMatch[1].trim()})`);
const email = styledEmail(`<${emailMatch[1]}>`);
output += `${rightAlignLocations}${location}${gutter}${status}${gutter}${message}${gutter}${rule}\n`;
output += `${leftAlignCommitsWithStatuses}${commit} ${email} ${date}\n`;
},
);
}
return output;
}, '');
};
formatter.defaultConfig = defaultConfig;
formatter.withConfig = createGitLogFormatter;
return formatter;
};

export const gitLogFormatter = createGitLogFormatter(defaultConfig);
14 changes: 14 additions & 0 deletions src/formatters/lib/get-in.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const isWalkable = (value: any) =>
value !== null && typeof value !== 'undefined';

const getChild = (parent: any, child: any): any =>
isWalkable(parent) ? parent[child] : undefined;

export const getIn = (
pathToValue: string | number,
owner?: any,
defaultValue?: any,
) => {
const value = `${pathToValue}`.split('.').reduce(getChild, owner);
return value !== undefined ? value : defaultValue;
};
3 changes: 1 addition & 2 deletions tslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
"no-var-requires": [false],
"object-literal-key-quotes": false,
"object-literal-sort-keys": [false],
"quotemark": [true, "single"],
"trailing-comma": [false]
"quotemark": [true, "single"]
}
}
31 changes: 12 additions & 19 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -356,10 +356,10 @@
resolved "https://registry.yarnpkg.com/@types/jest-diff/-/jest-diff-20.0.1.tgz#35cc15b9c4f30a18ef21852e255fdb02f6d59b89"
integrity sha512-yALhelO3i0hqZwhjtcr6dYyaLoCHbAMshwtj6cGxTvHZAKXHsYGdff6E8EPw3xLKY0ELUTQ69Q1rQiJENnccMA==

"@types/[email protected].15":
version "24.0.15"
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-24.0.15.tgz#6c42d5af7fe3b44ffff7cc65de7bf741e8fa427f"
integrity sha512-MU1HIvWUme74stAoc3mgAi+aMlgKOudgEvQDIm1v4RkrDudBh1T+NFp5sftpBAdXdx1J0PbdpJ+M2EsSOi1djA==
"@types/[email protected].16":
version "24.0.16"
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-24.0.16.tgz#8d3e406ec0f0dc1688d6711af3062ff9bd428066"
integrity sha512-JrAiyV+PPGKZzw6uxbI761cHZ0G7QMOHXPhtSpcl08rZH6CswXaaejckn3goFKmF7M3nzEoJ0lwYCbqLMmjziQ==
dependencies:
"@types/jest-diff" "*"

Expand All @@ -368,10 +368,10 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.3.tgz#bdfd69d61e464dcc81b25159c270d75a73c1a636"
integrity sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==

"@types/node@12.0.10":
version "12.0.10"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.0.10.tgz#51babf9c7deadd5343620055fc8aff7995c8b031"
integrity sha512-LcsGbPomWsad6wmMNv7nBLw7YYYyfdYcz6xryKYQhx89c3XXan+8Q6AJ43G5XDIaklaVkK3mE4fCb0SBvMiPSQ==
"@types/node@12.6.9":
version "12.6.9"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.6.9.tgz#ffeee23afdc19ab16e979338e7b536fdebbbaeaf"
integrity sha512-+YB9FtyxXGyD54p8rXwWaN1EWEyar5L58GlGWgtH2I9rGmLGBQcw63+0jw+ujqVavNuO47S1ByAjm9zdHMnskw==

"@types/stack-utils@^1.0.1":
version "1.0.1"
Expand Down Expand Up @@ -2409,13 +2409,6 @@ lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14:
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==

[email protected]:
version "3.0.0"
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4"
integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==
dependencies:
chalk "^2.4.2"

loose-envify@^1.0.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
Expand Down Expand Up @@ -3781,10 +3774,10 @@ type-check@~0.3.2:
dependencies:
prelude-ls "~1.1.2"

[email protected].2:
version "3.5.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.2.tgz#a09e1dc69bc9551cadf17dba10ee42cf55e5d56c"
integrity sha512-7KxJovlYhTX5RaRbUdkAXN1KUZ8PwWlTzQdHV6xNqvuFOs7+WBo10TQUqT19Q/Jz2hk5v9TQDIhyLhhJY4p5AA==
[email protected].3:
version "3.5.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977"
integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==

uglify-js@^3.1.4:
version "3.6.0"
Expand Down

0 comments on commit 4468586

Please sign in to comment.