Skip to content

Commit

Permalink
Merge pull request #9 from mstankowiak/master
Browse files Browse the repository at this point in the history
Formatting as inspections - fixes #7
  • Loading branch information
ThaNarie authored Mar 12, 2019
2 parents 1877c95 + a4abf6b commit 7c63d48
Show file tree
Hide file tree
Showing 8 changed files with 282 additions and 103 deletions.
26 changes: 23 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,22 +87,39 @@ There are several ways that you can configure tslint-teamcity.
You don't have to configure anything by default, you just have the option to if you would like.
Settings are looked for in the following priority:

#### 1. From your package.json
If you have a package.json file in the current directory, you can add an extra "eslint-teamcity" property to it:
#### 1. As a second argument
If you run tslint-teamcity-reporter by requiring it in your code, you can pass a second argument to the function:
```js
import { Formatter } from 'tslint-teamcity-reporter';

const formatter = new Formatter();
const options = {
reporter: 'inspections',
reportName: 'My TSLint Violations',
errorStatisticsName: 'My TSLint Error Count',
warningStatisticsName: 'My TSLint Warning Count',
};
console.log(formatter.format(tslintFailures, options));
```

#### 2. From your package.json
If you have a package.json file in the current directory, you can add an extra "tslint-teamcity" property to it:

```json
{
"tslint-teamcity": {
"reporter": "inspections",
"report-name": "My TSLint Violations",
"error-statistics-name": "My TSLint Error Count",
"warning-statistics-name": "My TSLint Warning Count"
}
}
```

#### 2. ENV variables
#### 3. ENV variables

```sh
export TSLINT_TEAMCITY_REPORTER="inspections"
export TSLINT_TEAMCITY_REPORT_NAME="My Formatting Problems"
export TSLINT_TEAMCITY_ERROR_STATISTICS_NAME="My Error Count"
export TSLINT_TEAMCITY_WARNING_STATISTICS_NAME="My Warning Count"
Expand All @@ -114,6 +131,9 @@ You can also output your current settings to the log if you set:
export TSLINT_TEAMCITY_DISPLAY_CONFIG=true
```

#### Output type
By default, the output is displayed as tests on a TeamCity build (`"reporter": "errors"`). You can change it to be displayed as "Inspections" in a separate tab by setting the `"reporter": "inspections"` option.


## Building

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "tslint-teamcity-reporter",
"version": "3.1.0",
"version": "3.2.0",
"description": "A TSLint formatter/reporter for use in TeamCity which groups by files using TeamCity Test Suite",
"main": "./index.js",
"types": "./index.d.ts",
Expand Down
103 changes: 17 additions & 86 deletions src/lib/Reporter.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as path from 'path';
import { AbstractFormatter } from 'tslint/lib/language/formatter/abstractFormatter';
import { RuleFailure, IFormatterMetadata } from 'tslint';
import { getOutputMessage, getUserConfig, TeamCityMessages } from './util';
import { getUserConfig } from './util';
import { formatAsInspections } from './formatters/inspections';
import { formatAsTests } from './formatters/errors';

export class Formatter extends AbstractFormatter {
/* tslint:disable:object-literal-sort-keys */
Expand All @@ -24,95 +25,25 @@ export class Formatter extends AbstractFormatter {
};
/* tslint:enable:object-literal-sort-keys */

public format(failures: RuleFailure[]): string {
const config = getUserConfig({});
const { reportName } = config;
public format(failures: RuleFailure[], config: { [key: string]: string } = {}): string {
const userConfig = getUserConfig(config);

if (process.env.TSLINT_TEAMCITY_DISPLAY_CONFIG) {
// tslint:disable-next-line no-console
console.info(`Running TSLint Teamcity with config: ${JSON.stringify(config, null, 4)}`);
console.info(`Running TSLint Teamcity with config: ${JSON.stringify(userConfig, null, 4)}`);
}

const output = [];
let errorCount = 0;
let warningCount = 0;

output.push(getOutputMessage(TeamCityMessages.TEST_SUITE_STARTED, { report: reportName }));

// group failures per file, instead of reporting each failure individually
const failuresByFile = failures.reduce<{
[key: string]: { filePath?: string; messages?: Array<RuleFailure> };
}>((acc, f) => {
const file = f.getFileName();
if (!acc[file]) acc[file] = { filePath: file, messages: [] };
acc[file].messages.push(f);
return acc;
}, {});

Object.values(failuresByFile).forEach(result => {
const filePath = path.relative(process.cwd(), result.filePath);
output.push(
getOutputMessage(TeamCityMessages.TEST_STARTED, { report: reportName, file: filePath }),
);

const errorsList = [];
const warningsList = [];

result.messages.forEach(failure => {
const startPos = failure.getStartPosition().getLineAndCharacter();
const formattedMessage = `line ${startPos.line}, col ${
startPos.character
}, ${failure.getFailure()} (${failure.getRuleName()})`;

const isError = failure.getRuleSeverity() === 'error';
if (!isError)
{
warningsList.push(formattedMessage);
warningCount += 1;
} else
{
errorsList.push(formattedMessage);
errorCount += 1;
}
});

// Group errors and warnings together per file
if (errorsList.length) {
const errors = errorsList.join('\n');
output.push(
getOutputMessage(TeamCityMessages.TEST_FAILED, {
errors,
report: reportName,
file: filePath,
}),
);
}

if (warningsList.length) {
const warnings = warningsList.join('\n');
output.push(
getOutputMessage(TeamCityMessages.TEST_STD_OUT, {
warnings,
report: reportName,
file: filePath,
}),
);
}

output.push(
getOutputMessage(TeamCityMessages.TEST_FINISHED, { report: reportName, file: filePath }),
);
});

output.push(getOutputMessage(TeamCityMessages.TEST_SUITE_FINISHED, { report: reportName }));

output.push(
...(<Array<string>>getOutputMessage(TeamCityMessages.BUILD_STATISTIC_VALUE, {
[config.errorStatisticsName]: errorCount,
[config.warningStatisticsName]: warningCount,
})),
);
let outputMessage = '';
switch (userConfig.reporter.toLowerCase()) {
case 'inspections':
outputMessage = formatAsInspections(failures, userConfig);
break;
case 'errors':
default:
outputMessage = formatAsTests(failures, userConfig);
break;
}

return output.join('\n');
return outputMessage;
}
}
88 changes: 88 additions & 0 deletions src/lib/formatters/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import * as path from 'path';

import { RuleFailure } from 'tslint';
import { getOutputMessage, TeamCityMessages } from '../util';

export function formatAsTests(failures: RuleFailure[], config: { [key: string]: string }) {
const { reportName } = config;

const output = [];
let errorCount = 0;
let warningCount = 0;

output.push(getOutputMessage(TeamCityMessages.TEST_SUITE_STARTED, { report: reportName }));

// group failures per file, instead of reporting each failure individually
const failuresByFile = failures.reduce<{
[key: string]: { filePath?: string; messages?: Array<RuleFailure> };
}>((acc, f) => {
const file = f.getFileName();
if (!acc[file]) acc[file] = { filePath: file, messages: [] };
acc[file].messages.push(f);
return acc;
}, {});

Object.values(failuresByFile).forEach(result => {
const filePath = path.relative(process.cwd(), result.filePath);
output.push(
getOutputMessage(TeamCityMessages.TEST_STARTED, { report: reportName, file: filePath }),
);

const errorsList = [];
const warningsList = [];

result.messages.forEach(failure => {
const startPos = failure.getStartPosition().getLineAndCharacter();
const formattedMessage = `line ${startPos.line}, col ${
startPos.character
}, ${failure.getFailure()} (${failure.getRuleName()})`;

const isError = failure.getRuleSeverity() === 'error';
if (!isError) {
warningsList.push(formattedMessage);
warningCount += 1;
} else {
errorsList.push(formattedMessage);
errorCount += 1;
}
});

// Group errors and warnings together per file
if (errorsList.length) {
const errors = errorsList.join('\n');
output.push(
getOutputMessage(TeamCityMessages.TEST_FAILED, {
errors,
report: reportName,
file: filePath,
}),
);
}

if (warningsList.length) {
const warnings = warningsList.join('\n');
output.push(
getOutputMessage(TeamCityMessages.TEST_STD_OUT, {
warnings,
report: reportName,
file: filePath,
}),
);
}

output.push(
getOutputMessage(TeamCityMessages.TEST_FINISHED, { report: reportName, file: filePath }),
);
});

output.push(getOutputMessage(TeamCityMessages.TEST_SUITE_FINISHED, { report: reportName }));

output.push(
...(<Array<string>>getOutputMessage(TeamCityMessages.BUILD_STATISTIC_VALUE, {
[config.errorStatisticsName]: errorCount,
[config.warningStatisticsName]: warningCount,
})),
);

return output.join('\n');
}
62 changes: 62 additions & 0 deletions src/lib/formatters/inspections.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import * as path from 'path';

import { RuleFailure } from 'tslint';
import { getOutputMessage, TeamCityMessages } from '../util';

export function formatAsInspections(failures: RuleFailure[], config: { [key: string]: string }) {
const { reportName } = config;

const output = [];
let errorCount = 0;
let warningCount = 0;

// group failures per file, instead of reporting each failure individually
const failuresByRule = failures.reduce<{
[key: string]: { ruleName?: string; messages?: Array<RuleFailure> };
}>((acc, f) => {
const ruleName = f.getRuleName();
if (!acc[ruleName]) acc[ruleName] = { ruleName, messages: [] };
acc[ruleName].messages.push(f);
return acc;
}, {});

Object.values(failuresByRule).forEach(result => {
output.push(
getOutputMessage(TeamCityMessages.INSPECTION_TYPE, { reportName, ruleName: result.ruleName }),
);

result.messages.forEach(failure => {
const filePath = path.relative(process.cwd(), failure.getFileName());
const startPos = failure.getStartPosition().getLineAndCharacter();
const formattedMessage = `line ${startPos.line}, col ${
startPos.character
}, ${failure.getFailure()}`;

const isError = failure.getRuleSeverity() === 'error';
const severity = isError ? 'ERROR' : 'WARNING';
if (isError) {
errorCount += 1;
} else {
warningCount += 1;
}
output.push(
getOutputMessage(TeamCityMessages.INSPECTION, {
formattedMessage,
filePath,
severity,
ruleName: result.ruleName,
line: startPos.line,
}),
);
});
});

output.push(
...(<Array<string>>getOutputMessage(TeamCityMessages.BUILD_STATISTIC_VALUE, {
[config.errorStatisticsName]: errorCount,
[config.warningStatisticsName]: warningCount,
})),
);

return output.join('\n');
}
Loading

0 comments on commit 7c63d48

Please sign in to comment.