From 4be729f89113ce6c833279ff89990f8e578ccda1 Mon Sep 17 00:00:00 2001 From: Andrii Kucherenko Date: Thu, 15 Nov 2018 17:01:27 +0200 Subject: [PATCH] refactor(reporter): Add statistic to reporter interface --- README.md | 110 ++++++++++++++++++++++++++- src/index.ts | 8 +- src/interfaces/reporter.interface.ts | 3 +- src/jscpd.ts | 6 +- src/reporters/console.ts | 17 ++--- src/reporters/json.ts | 9 +-- src/reporters/silent.ts | 20 ++--- 7 files changed, 132 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 1a0783dc..34ecd85b 100644 --- a/README.md +++ b/README.md @@ -26,18 +26,29 @@ The jscpd tool implements [Rabin-Karp](https://en.wikipedia.org/wiki/Rabin%E2%80 - Generate JSON report - Integrate with CI systems, use thresholds for level of duplications - The powerful [API](docs/api.md) for extend functionality and usage + +## What is new in jscpd v1.0.0? + + - Powerful development [API](docs/api.md) + - Supports more formats (moved source code tokenizer from CodeMirror to Prism.js) + - Add blamed lines to JSON report + - Default config file is `.jscpd.json`, no more `.cpd.yaml` + - Detect different formats in one file, like js scripts in html tags + - Allow to use multiple cli options for parameters like `jscpd --ignore tests,build` + - Allow multiple paths for detection like `jscpd ./src ./tests ./docs` + - Statistic of detection + - Use patterns form `.gitignore` for ignoring detection ## Getting started ### Usage ```bash -$ npx jscpd@1.0.0-rc.3 /path/to/source +$ npx jscpd@1.0.0-rc.4 /path/to/source ``` - or ```bash -$ npm install -g jscpd@1.0.0-rc.3 +$ npm install -g jscpd@1.0.0-rc.4 $ jscpd /path/to/code ``` @@ -236,6 +247,99 @@ cpd.detectInFiles(['./src', './tests']) [Progamming API](docs/api.md) +## Reporters + +### PMD CPD XML +```xml + + + + + + + + + + + + +``` + +### JSON reporters +```json +{ + "duplications": [{ + "format": "javascript", + "lines": 27, + "fragment": "...code fragment... ", + "tokens": 0, + "firstFile": { + "name": "tests/fixtures/javascript/file2.js", + "start": 1, + "end": 27, + "startLoc": { + "line": 1, + "column": 1 + }, + "endLoc": { + "line": 27, + "column": 2 + } + }, + "secondFile": { + "name": "tests/fixtures/javascript/file1.js", + "start": 1, + "end": 24, + "startLoc": { + "line": 1, + "column": 1 + }, + "endLoc": { + "line": 24, + "column": 2 + } + } + }], + "statistic": { + "detectionDate": "2018-11-09T15:32:02.397Z", + "formats": { + "javascript": { + "sources": { + "/path/to/file": { + "lines": 24, + "sources": 1, + "clones": 1, + "duplicatedLines": 26, + "percentage": 45.33, + "newDuplicatedLines": 0, + "newClones": 0 + } + }, + "total": { + "lines": 297, + "sources": 1, + "clones": 1, + "duplicatedLines": 26, + "percentage": 45.33, + "newDuplicatedLines": 0, + "newClones": 0 + } + } + }, + "total": { + "lines": 297, + "sources": 6, + "clones": 5, + "duplicatedLines": 26, + "percentage": 45.33, + "newDuplicatedLines": 0, + "newClones": 0 + }, + "threshold": 10 + } +} +``` + ## Contributors This project exists thanks to all the people who contribute. diff --git a/src/index.ts b/src/index.ts index 63db02da..3e1fb518 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,7 @@ -export { - MATCH_SOURCE_EVENT, - CLONE_FOUND_EVENT, - SOURCE_SKIPPED_EVENT, - END_EVENT -} from './events'; +export { MATCH_SOURCE_EVENT, CLONE_FOUND_EVENT, SOURCE_SKIPPED_EVENT, END_EVENT } from './events'; export { IReporter } from './interfaces/reporter.interface'; export { IMode } from './interfaces/mode.type'; export { IOptions } from './interfaces/options.interface'; export { IClone } from './interfaces/clone.interface'; +export { IStatistic } from './interfaces/statistic.interface'; export { JSCPD, getStoreManager } from './jscpd'; diff --git a/src/interfaces/reporter.interface.ts b/src/interfaces/reporter.interface.ts index f607d9ec..f8aa3f59 100644 --- a/src/interfaces/reporter.interface.ts +++ b/src/interfaces/reporter.interface.ts @@ -1,7 +1,8 @@ import EventEmitter = require('eventemitter3'); import { IClone } from './clone.interface'; +import { IStatistic } from './statistic.interface'; export interface IReporter { attach(eventEmitter: EventEmitter): void; - report(clones: IClone[]): void; + report(clones?: IClone[], statistic?: IStatistic): void; } diff --git a/src/jscpd.ts b/src/jscpd.ts index 1a40880f..aa85faea 100644 --- a/src/jscpd.ts +++ b/src/jscpd.ts @@ -13,11 +13,12 @@ import { IListener } from './interfaces/listener.interface'; import { IOptions } from './interfaces/options.interface'; import { IReporter } from './interfaces/reporter.interface'; import { ISourceOptions } from './interfaces/source-options.interface'; +import { IStatistic } from './interfaces/statistic.interface'; import { IToken } from './interfaces/token/token.interface'; import { getRegisteredListeners, registerListenerByName } from './listeners'; import { getModeHandler } from './modes'; import { getRegisteredReporters, registerReportersByName } from './reporters'; -import { SOURCES_DB } from './stores/models'; +import { SOURCES_DB, STATISTIC_DB } from './stores/models'; import { StoreManager, StoresManager } from './stores/stores-manager'; import { createTokensMaps, tokenize } from './tokenizer'; import { getFormatByFile } from './tokenizer/formats'; @@ -194,8 +195,9 @@ export class JSCPD { } private generateReports(clones: IClone[]) { + const statistic: IStatistic = StoresManager.getStore(STATISTIC_DB).get(getOption('executionId', this.options)); Object.values(getRegisteredReporters()).map((reporter: IReporter) => { - reporter.report(clones); + reporter.report(clones, statistic); }); } } diff --git a/src/reporters/console.ts b/src/reporters/console.ts index efe3a3e6..0b5e0d07 100644 --- a/src/reporters/console.ts +++ b/src/reporters/console.ts @@ -1,29 +1,26 @@ import Table from 'cli-table3'; import { bold, red } from 'colors/safe'; -import { IOptions, IReporter } from '..'; +import { IClone, IOptions, IReporter } from '..'; import { CLONE_FOUND_EVENT, JscpdEventEmitter } from '../events'; -import { IClone } from '../interfaces/clone.interface'; -import { IStatisticRow } from '../interfaces/statistic.interface'; -import { STATISTIC_DB } from '../stores/models'; -import { StoresManager } from '../stores/stores-manager'; +import { IStatistic, IStatisticRow } from '../interfaces/statistic.interface'; import { getPathConsoleString, getSourceLocation } from '../utils'; -import { getOption } from '../utils/options'; export class ConsoleReporter implements IReporter { - constructor(protected options: IOptions) {} + constructor(protected options: IOptions) { + } public attach(eventEmitter: JscpdEventEmitter): void { eventEmitter.on(CLONE_FOUND_EVENT, this.cloneFound.bind(this)); } - public report() { - const statistic = StoresManager.getStore(STATISTIC_DB).get(getOption('executionId', this.options)); + public report(...args: [any, IStatistic]) { + const [, statistic]: [any, IStatistic] = args; if (statistic) { const table: any[] = new Table({ head: ['Format', 'Files analyzed', 'Total lines', 'Clones found', 'Duplicated lines', '%'] }); Object.keys(statistic.formats) - .filter(format => statistic.formats[format].sources as boolean) + .filter(format => statistic.formats[format].sources) .forEach((format: string) => { table.push(this.convertStatisticToArray(format, statistic.formats[format].total)); }); diff --git a/src/reporters/json.ts b/src/reporters/json.ts index f1508572..e6dd5038 100644 --- a/src/reporters/json.ts +++ b/src/reporters/json.ts @@ -1,12 +1,9 @@ import { writeFileSync } from 'fs'; import { ensureDirSync } from 'fs-extra'; -import { IOptions, IReporter } from '..'; +import { IClone, IOptions, IReporter } from '..'; import { IBlamedLines } from '../interfaces/blame.interface'; -import { IClone } from '../interfaces/clone.interface'; import { IStatistic } from '../interfaces/statistic.interface'; import { ITokenLocation } from '../interfaces/token/token-location.interface'; -import { STATISTIC_DB } from '../stores/models'; -import { StoresManager } from '../stores/stores-manager'; import { getPath } from '../utils'; import { getOption } from '../utils/options'; @@ -48,9 +45,7 @@ export class JsonReporter implements IReporter { public attach(): void {} - public report(clones: IClone[]): void { - const statistic: IStatistic = StoresManager.getStore(STATISTIC_DB).get(getOption('executionId', this.options)); - + public report(clones: IClone[], statistic: IStatistic): void { if (statistic) { this.json.statistics = statistic; } diff --git a/src/reporters/silent.ts b/src/reporters/silent.ts index ca3a87fc..a4d7a67a 100644 --- a/src/reporters/silent.ts +++ b/src/reporters/silent.ts @@ -1,23 +1,19 @@ import { bold } from 'colors/safe'; -import { IOptions, IReporter } from '..'; +import { IClone, IReporter } from '..'; import { IStatistic } from '../interfaces/statistic.interface'; -import { STATISTIC_DB } from '../stores/models'; -import { StoresManager } from '../stores/stores-manager'; -import { getOption } from '../utils/options'; export class SilentReporter implements IReporter { - constructor(private options: IOptions) {} - public attach(): void {} + public attach(): void { + } - public report() { - const statistic: IStatistic = StoresManager.getStore(STATISTIC_DB).get(getOption('executionId', this.options)); + public report(clones: IClone[], statistic: IStatistic) { if (statistic) { console.log( - `Duplications detection: Found ${bold(statistic.total.clones.toString())} ` + - `exact clones with ${bold(statistic.total.duplicatedLines.toString())}(${statistic.total.percentage}%) ` + - `duplicated lines in ${bold(statistic.total.sources.toString())} ` + - `(${Object.keys(statistic.formats).length} formats) files.` + `Duplications detection: Found ${bold(clones.length.toString())} ` + + `exact clones with ${bold(statistic.total.duplicatedLines.toString())}(${statistic.total.percentage}%) ` + + `duplicated lines in ${bold(statistic.total.sources.toString())} ` + + `(${Object.keys(statistic.formats).length} formats) files.` ); } }