@rehearsal/reporter
Advanced tools
Comparing version 1.0.4-beta to 1.0.4
@@ -1,3 +0,6 @@ | ||
import type { Report } from '../types'; | ||
export declare function jsonFormatter(report: Report): string; | ||
import type { Report, FormatterBase } from '../types.js'; | ||
export declare class JSONFormatter implements FormatterBase { | ||
static extension: string; | ||
static getReport(report: Report): string; | ||
} | ||
//# sourceMappingURL=json-formatter.d.ts.map |
@@ -1,8 +0,7 @@ | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.jsonFormatter = void 0; | ||
function jsonFormatter(report) { | ||
return JSON.stringify(report, null, 2); | ||
export class JSONFormatter { | ||
static getReport(report) { | ||
return JSON.stringify(report, null, 2); | ||
} | ||
} | ||
exports.jsonFormatter = jsonFormatter; | ||
JSONFormatter.extension = '.json'; | ||
//# sourceMappingURL=json-formatter.js.map |
@@ -1,3 +0,6 @@ | ||
import type { Report } from '../types'; | ||
export declare function mdFormatter(report: Report): string; | ||
import type { Report, FormatterBase } from '../types.js'; | ||
export declare class MarkdownFormatter implements FormatterBase { | ||
static extension: string; | ||
static getReport(report: Report): string; | ||
} | ||
//# sourceMappingURL=md-formatter.d.ts.map |
@@ -1,27 +0,28 @@ | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.mdFormatter = void 0; | ||
function mdFormatter(report) { | ||
const fileNames = [...new Set(report.items.map((item) => item.analysisTarget))]; | ||
let text = ``; | ||
text += `### Summary:\n`; | ||
text += `Typescript Version: ${report.summary.tsVersion}\n`; | ||
text += `Files Tested: ${fileNames.length}\n`; | ||
text += `\n`; | ||
text += `### Results:\n`; | ||
for (const fileName of fileNames) { | ||
const items = report.items.filter((item) => item.analysisTarget === fileName); | ||
const relativeFileName = fileName.replace(report.summary.basePath, ''); | ||
text += `\n`; | ||
text += `#### File: ${relativeFileName}, issues: ${items.length}:\n`; | ||
for (const item of items) { | ||
export class MarkdownFormatter { | ||
static getReport(report) { | ||
const fileNames = [...new Set(report.items.map((item) => item.analysisTarget))]; | ||
let text = ``; | ||
text += `### Summary:\n`; | ||
for (const block of report.summary) { | ||
text += `Project Name: ${block.projectName}\n`; | ||
text += `Typescript Version: ${block.tsVersion}\n`; | ||
text += `timestamp: ${block.timestamp}\n`; | ||
text += `\n`; | ||
text += `**${item.category} ${item.ruleId}**: 'NEED TO BE FIXED MANUALLY'\n`; | ||
text += `${item.hint}\n`; | ||
text += `Code: \`${item.nodeText}\`\n`; | ||
} | ||
text += `### Results:\n`; | ||
for (const fileName of fileNames) { | ||
const items = report.items.filter((item) => item.analysisTarget === fileName); | ||
text += `\n`; | ||
text += `#### File: ${fileName}, issues: ${items.length}:\n`; | ||
for (const item of items) { | ||
text += `\n`; | ||
text += `**${item.category} ${item.ruleId}**: 'REVIEW REQUIRED'\n`; | ||
text += `${item.hint}\n`; | ||
text += `Code: \`${item.nodeText}\`\n`; | ||
} | ||
} | ||
return text; | ||
} | ||
return text; | ||
} | ||
exports.mdFormatter = mdFormatter; | ||
MarkdownFormatter.extension = '.md'; | ||
//# sourceMappingURL=md-formatter.js.map |
@@ -0,4 +1,5 @@ | ||
import type { Report, FormatterBase } from '../types.js'; | ||
import type { Log } from 'sarif'; | ||
import type { Report } from '../types'; | ||
export declare class SarifFormatter { | ||
export declare class SarifFormatter implements FormatterBase { | ||
static extension: string; | ||
private report; | ||
@@ -11,4 +12,5 @@ private rules; | ||
constructor(report: Report); | ||
format(): string; | ||
static getReport(report: Report): string; | ||
buildLog(): Log; | ||
private format; | ||
private buildRun; | ||
@@ -22,4 +24,7 @@ private addRule; | ||
private artifactExists; | ||
private buildArtifact; | ||
private kindConverter; | ||
private levelConverter; | ||
private createRun; | ||
} | ||
export declare function sarifFormatter(report: Report): string; | ||
//# sourceMappingURL=sarif-formatter.d.ts.map |
@@ -1,5 +0,2 @@ | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.sarifFormatter = exports.SarifFormatter = void 0; | ||
class SarifFormatter { | ||
export class SarifFormatter { | ||
constructor(report) { | ||
@@ -13,5 +10,6 @@ this.rules = []; | ||
} | ||
format() { | ||
return JSON.stringify(this.buildLog(), null, 2); | ||
static getReport(report) { | ||
return new SarifFormatter(report).format(); | ||
} | ||
// only exposed for SonarqubeFormatter | ||
buildLog() { | ||
@@ -25,4 +23,7 @@ const runs = this.buildRun(); | ||
} | ||
format() { | ||
return JSON.stringify(this.buildLog(), null, 2); | ||
} | ||
buildRun() { | ||
const run = createRun(this.report); | ||
const run = this.createRun(this.report); | ||
for (const item of this.report.items) { | ||
@@ -34,6 +35,15 @@ this.addRule(item.ruleId, item.message); | ||
const { rules, artifacts, results } = this; | ||
const tool = Object.assign(Object.assign({}, run.tool), { driver: Object.assign(Object.assign({}, run.tool.driver), { rules }) }); | ||
return Object.assign(Object.assign({}, run), { tool, | ||
const tool = { | ||
...run.tool, | ||
driver: { | ||
...run.tool.driver, | ||
rules, | ||
}, | ||
}; | ||
return { | ||
...run, | ||
tool, | ||
artifacts, | ||
results }); | ||
results, | ||
}; | ||
} | ||
@@ -56,3 +66,3 @@ addRule(ruleId, message) { | ||
if (!this.artifactExists(fileName)) { | ||
const newArtifact = buildArtifact(fileName); | ||
const newArtifact = this.buildArtifact(fileName); | ||
this.artifacts.push(newArtifact); | ||
@@ -70,4 +80,4 @@ this.artifactIndexMap[fileName] = this.artifacts.length - 1; | ||
ruleIndex: this.ruleIndexMap[item.ruleId], | ||
level: levelConverter(item.category), | ||
kind: kindConverter(item.category), | ||
level: this.levelConverter(item.category), | ||
kind: this.kindConverter(item.category), | ||
message: { | ||
@@ -83,3 +93,2 @@ text: item.hint, | ||
buildLocation(item) { | ||
var _a, _b, _c, _d; | ||
const index = this.artifactIndexMap[item.analysisTarget]; | ||
@@ -93,6 +102,6 @@ return { | ||
region: { | ||
startLine: (_a = item.nodeLocation) === null || _a === void 0 ? void 0 : _a.startLine, | ||
startColumn: (_b = item.nodeLocation) === null || _b === void 0 ? void 0 : _b.startColumn, | ||
endLine: (_c = item.nodeLocation) === null || _c === void 0 ? void 0 : _c.endLine, | ||
endColumn: (_d = item.nodeLocation) === null || _d === void 0 ? void 0 : _d.endColumn, | ||
startLine: item.nodeLocation?.startLine, | ||
startColumn: item.nodeLocation?.startColumn, | ||
endLine: item.nodeLocation?.endLine, | ||
endColumn: item.nodeLocation?.endColumn, | ||
}, | ||
@@ -108,58 +117,55 @@ }, | ||
} | ||
} | ||
exports.SarifFormatter = SarifFormatter; | ||
function buildArtifact(fileName) { | ||
return { | ||
location: { | ||
uri: fileName, | ||
}, | ||
}; | ||
} | ||
function kindConverter(category) { | ||
switch (category) { | ||
case 'Warning': | ||
case 'Suggestion': | ||
case 'Message': | ||
return 'review'; | ||
default: | ||
return 'fail'; | ||
buildArtifact(fileName) { | ||
return { | ||
location: { | ||
uri: fileName, | ||
}, | ||
}; | ||
} | ||
} | ||
function levelConverter(category) { | ||
switch (category) { | ||
case 'Warning': | ||
return 'warning'; | ||
case 'Error': | ||
return 'error'; | ||
case 'Suggestion': | ||
return 'note'; | ||
case 'Message': | ||
return 'note'; | ||
default: | ||
return 'none'; | ||
kindConverter(category) { | ||
switch (category) { | ||
case 'Warning': | ||
case 'Suggestion': | ||
case 'Message': | ||
return 'review'; | ||
default: | ||
return 'fail'; | ||
} | ||
} | ||
} | ||
function createRun(report) { | ||
return { | ||
tool: { | ||
driver: { | ||
name: `${report.summary.commandName}`, | ||
informationUri: 'https://github.com/rehearsal-js/rehearsal-js', | ||
rules: [], | ||
levelConverter(category) { | ||
switch (category) { | ||
case 'Warning': | ||
return 'warning'; | ||
case 'Error': | ||
return 'error'; | ||
case 'Suggestion': | ||
return 'note'; | ||
case 'Message': | ||
return 'note'; | ||
default: | ||
return 'none'; | ||
} | ||
} | ||
createRun(report) { | ||
return { | ||
tool: { | ||
driver: { | ||
name: `rehearsal report`, | ||
informationUri: 'https://github.com/rehearsal-js/rehearsal-js', | ||
rules: [], | ||
}, | ||
}, | ||
}, | ||
artifacts: [], | ||
results: [], | ||
automationDetails: { | ||
description: { | ||
text: `This is the run of ${report.summary.commandName} on your product against TypeScript ${report.summary.tsVersion} at ${report.summary.timestamp}`, | ||
artifacts: [], | ||
results: [], | ||
automationDetails: { | ||
description: { | ||
//For sequential runs, the time difference between each run is minimal, and ts version should be the same. | ||
//So printing out the first timestamp and first ts version. | ||
text: `This is the result of running Rehearsal on your product against TypeScript ${report.summary[0].tsVersion} at ${report.summary[0].timestamp}`, | ||
}, | ||
}, | ||
}, | ||
}; | ||
}; | ||
} | ||
} | ||
function sarifFormatter(report) { | ||
const formatter = new SarifFormatter(report); | ||
return formatter.format(); | ||
} | ||
exports.sarifFormatter = sarifFormatter; | ||
SarifFormatter.extension = '.sarif'; | ||
//# sourceMappingURL=sarif-formatter.js.map |
@@ -1,3 +0,6 @@ | ||
import type { Report } from '../types'; | ||
export declare function sonarqubeFormatter(report: Report): string; | ||
import type { Report, FormatterBase } from '../types.js'; | ||
export declare class SonarqubeFormatter implements FormatterBase { | ||
static extension: string; | ||
static getReport(report: Report): string; | ||
} | ||
//# sourceMappingURL=sonarqube-formatter.d.ts.map |
@@ -1,60 +0,69 @@ | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.sonarqubeFormatter = void 0; | ||
const path_1 = require("path"); | ||
const sarif_formatter_1 = require("./sarif-formatter"); | ||
const SONARQUBE_SEVERITY = { | ||
notApplicable: 'INFO', | ||
pass: 'INFO', | ||
fail: 'BLOCKER', | ||
review: 'CRITICAL', | ||
open: 'MINOR', | ||
informational: 'INFO', | ||
}; | ||
const SONARQUBE_TYPE = { | ||
note: 'CODE_SMELL', | ||
warning: 'CODE_SMELL', | ||
error: 'BUG', | ||
}; | ||
function getPhysicalLocation(result) { | ||
var _a; | ||
return (_a = (result.locations && result.locations[0].physicalLocation)) !== null && _a !== void 0 ? _a : undefined; | ||
} | ||
function getFilePath(physicalLocation) { | ||
var _a, _b; | ||
return (_b = (_a = physicalLocation === null || physicalLocation === void 0 ? void 0 : physicalLocation.artifactLocation) === null || _a === void 0 ? void 0 : _a.uri) !== null && _b !== void 0 ? _b : ''; | ||
} | ||
// SonarQube Formatter will convert to SARIF first and then convert to SonarQube format | ||
// We have to assume the default Report shape | ||
function sonarqubeFormatter(report) { | ||
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k; | ||
const issues = []; | ||
const log = new sarif_formatter_1.SarifFormatter(report).buildLog(); | ||
const results = log.runs[0].results || []; | ||
if (results.length > 0) { | ||
for (const result of results) { | ||
const physicalLocation = getPhysicalLocation(result); | ||
const filePath = physicalLocation ? getFilePath(physicalLocation) : ''; | ||
const absolutePath = (0, path_1.isAbsolute)(filePath) ? filePath : (0, path_1.resolve)(process.cwd(), filePath); | ||
issues.push({ | ||
engineId: 'rehearsal-ts', | ||
ruleId: result.ruleId, | ||
severity: SONARQUBE_SEVERITY[(_a = result.kind) !== null && _a !== void 0 ? _a : 'notApplicable'], | ||
type: SONARQUBE_TYPE[result.level], | ||
primaryLocation: { | ||
message: (_b = result.message.text) !== null && _b !== void 0 ? _b : '', | ||
filePath: absolutePath, | ||
textRange: { | ||
startLine: (_d = (_c = physicalLocation === null || physicalLocation === void 0 ? void 0 : physicalLocation.region) === null || _c === void 0 ? void 0 : _c.startLine) !== null && _d !== void 0 ? _d : 0, | ||
startColumn: (_f = (_e = physicalLocation === null || physicalLocation === void 0 ? void 0 : physicalLocation.region) === null || _e === void 0 ? void 0 : _e.startColumn) !== null && _f !== void 0 ? _f : 0, | ||
endLine: (_h = (_g = physicalLocation === null || physicalLocation === void 0 ? void 0 : physicalLocation.region) === null || _g === void 0 ? void 0 : _g.endLine) !== null && _h !== void 0 ? _h : 0, | ||
endColumn: (_k = (_j = physicalLocation === null || physicalLocation === void 0 ? void 0 : physicalLocation.region) === null || _j === void 0 ? void 0 : _j.endColumn) !== null && _k !== void 0 ? _k : 0, | ||
import { isAbsolute, resolve } from 'node:path'; | ||
import { SarifFormatter } from './sarif-formatter.js'; | ||
export class SonarqubeFormatter { | ||
// SonarQube Formatter will convert to SARIF first and then convert to SonarQube format | ||
// We have to assume the default Report shape | ||
static getReport(report) { | ||
const issues = []; | ||
const log = new SarifFormatter(report).buildLog(); | ||
const results = log.runs[0].results || []; | ||
const SONARQUBE_SEVERITY = { | ||
notApplicable: 'INFO', | ||
pass: 'INFO', | ||
fail: 'BLOCKER', | ||
review: 'CRITICAL', | ||
open: 'MINOR', | ||
informational: 'INFO', | ||
}; | ||
const SONARQUBE_TYPE = { | ||
note: 'CODE_SMELL', | ||
warning: 'CODE_SMELL', | ||
error: 'BUG', | ||
}; | ||
function getPhysicalLocation(result) { | ||
return (result.locations && result.locations[0].physicalLocation) ?? undefined; | ||
} | ||
function getFilePath(physicalLocation) { | ||
return physicalLocation?.artifactLocation?.uri ?? ''; | ||
} | ||
// We bump column and line numbers by 1 for sarif reader. | ||
// Now we revert back the numbers for sonarqube for column. | ||
function decrementByOne(key, location) { | ||
const region = location?.region; | ||
if (region && Number.isInteger(region[key]) && region[key] > 1) { | ||
return region[key] - 1; | ||
} | ||
return 0; | ||
} | ||
function getTextRange(location) { | ||
return { | ||
startLine: location?.region?.startLine || 0, | ||
startColumn: decrementByOne('startColumn', location), | ||
endLine: location?.region?.endLine || 0, | ||
endColumn: decrementByOne('endColumn', location), | ||
}; | ||
} | ||
if (results.length > 0) { | ||
for (const result of results) { | ||
const physicalLocation = getPhysicalLocation(result); | ||
const filePath = physicalLocation ? getFilePath(physicalLocation) : ''; | ||
const absolutePath = isAbsolute(filePath) ? filePath : resolve(process.cwd(), filePath); | ||
const textRange = getTextRange(physicalLocation); | ||
issues.push({ | ||
engineId: 'rehearsal-ts', | ||
ruleId: result.ruleId, | ||
severity: SONARQUBE_SEVERITY[result.kind ?? 'notApplicable'], | ||
type: SONARQUBE_TYPE[result.level], | ||
primaryLocation: { | ||
message: result.message.text ?? '', | ||
filePath: absolutePath, | ||
textRange, | ||
}, | ||
}, | ||
}); | ||
}); | ||
} | ||
} | ||
return JSON.stringify({ issues }, null, 2) || ''; | ||
} | ||
return JSON.stringify({ issues }, null, 2) || ''; | ||
} | ||
exports.sonarqubeFormatter = sonarqubeFormatter; | ||
SonarqubeFormatter.extension = '.sonarqube.json'; | ||
//# sourceMappingURL=sonarqube-formatter.js.map |
@@ -1,8 +0,5 @@ | ||
export { Reporter } from './reporter'; | ||
export { jsonFormatter } from './formatters/json-formatter'; | ||
export { mdFormatter } from './formatters/md-formatter'; | ||
export { sarifFormatter } from './formatters/sarif-formatter'; | ||
export { sonarqubeFormatter } from './formatters/sonarqube-formatter'; | ||
export { normalizeFilePath } from './normalize-paths'; | ||
export type { Location, Report, ReportItem, ReportSummary, ReportFormatter } from './types'; | ||
export * from './formatters/index.js'; | ||
export { Reporter } from './reporter.js'; | ||
export { ReportItemType } from './types.js'; | ||
export type { Location, Report, ReportItem, ReportSummary, ReportFormatter, Formatters, } from './types.js'; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -1,16 +0,4 @@ | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.normalizeFilePath = exports.sonarqubeFormatter = exports.sarifFormatter = exports.mdFormatter = exports.jsonFormatter = exports.Reporter = void 0; | ||
var reporter_1 = require("./reporter"); | ||
Object.defineProperty(exports, "Reporter", { enumerable: true, get: function () { return reporter_1.Reporter; } }); | ||
var json_formatter_1 = require("./formatters/json-formatter"); | ||
Object.defineProperty(exports, "jsonFormatter", { enumerable: true, get: function () { return json_formatter_1.jsonFormatter; } }); | ||
var md_formatter_1 = require("./formatters/md-formatter"); | ||
Object.defineProperty(exports, "mdFormatter", { enumerable: true, get: function () { return md_formatter_1.mdFormatter; } }); | ||
var sarif_formatter_1 = require("./formatters/sarif-formatter"); | ||
Object.defineProperty(exports, "sarifFormatter", { enumerable: true, get: function () { return sarif_formatter_1.sarifFormatter; } }); | ||
var sonarqube_formatter_1 = require("./formatters/sonarqube-formatter"); | ||
Object.defineProperty(exports, "sonarqubeFormatter", { enumerable: true, get: function () { return sonarqube_formatter_1.sonarqubeFormatter; } }); | ||
var normalize_paths_1 = require("./normalize-paths"); | ||
Object.defineProperty(exports, "normalizeFilePath", { enumerable: true, get: function () { return normalize_paths_1.normalizeFilePath; } }); | ||
export * from './formatters/index.js'; | ||
export { Reporter } from './reporter.js'; | ||
export { ReportItemType } from './types.js'; | ||
//# sourceMappingURL=index.js.map |
@@ -1,9 +0,9 @@ | ||
import { type Report, type ReportFormatter, type ReportItem, type Location, type LintErrorLike } from './types'; | ||
import type { Report, ReportItem, Location, LintErrorLike, Run, Formatters } from './types.js'; | ||
import type { DiagnosticWithLocation, Node } from 'typescript'; | ||
import type { Logger } from 'winston'; | ||
type ReporterMeta = { | ||
projectName: string; | ||
basePath: string; | ||
commandName: string; | ||
projectRootDir: string; | ||
tsVersion: string; | ||
stemName?: string; | ||
previousFixedCount?: number; | ||
}; | ||
@@ -16,4 +16,7 @@ /** | ||
report: Report; | ||
private logger?; | ||
constructor(meta: ReporterMeta, logger?: Logger); | ||
currentRun: Run; | ||
lastRun: Run | undefined; | ||
private uniqueFiles; | ||
private stemName; | ||
constructor(meta: ReporterMeta); | ||
getFileNames(): string[]; | ||
@@ -24,25 +27,26 @@ getItemsByAnalysisTarget(fileName: string): ReportItem[]; | ||
*/ | ||
addSummary(key: string, value: unknown): void; | ||
addToRunSummary(key: string, value: unknown): void; | ||
/** | ||
* Appends am information about provided diagnostic and related node to the report | ||
* Appends a information about provided TS diagnostic and related node to the report | ||
*/ | ||
addTSItem(diagnostic: DiagnosticWithLocation, node?: Node, triggeringLocation?: Location, hint?: string, helpUrl?: string, hintAdded?: boolean): void; | ||
addLintItem(fileName: string, lintError: LintErrorLike): void; | ||
incrementFixedItemCount(): void; | ||
addTSItemToRun(diagnostic: DiagnosticWithLocation, node?: Node, triggeringLocation?: Location, hint?: string, helpUrl?: string, hintAdded?: boolean): void; | ||
/** | ||
* Prints the current report using provided formatter (ex. json, pull-request etc.) | ||
* Appends a information about provided Glint diagnostic and related node to the report | ||
*/ | ||
print(file: string, formatter: ReportFormatter): string; | ||
addGlintItemToRun(diagnostic: DiagnosticWithLocation, node?: Node, triggeringLocation?: Location, hint?: string, helpUrl?: string, hintAdded?: boolean): void; | ||
addLintItemToRun(fileName: string, lintError: LintErrorLike): void; | ||
incrementRunFixedItemCount(): void; | ||
saveCurrentRunToReport(timestamp?: string): void; | ||
/** | ||
* Saves the current report information to the file in simple JSON format | ||
* to be able to load it later with 'load' function | ||
* Prints the reports using provided formatter | ||
* json is always printed and set as default formatter | ||
*/ | ||
save(file: string): void; | ||
/** | ||
* Loads the report exported by function 'save' from the file | ||
*/ | ||
load(file: string): Reporter; | ||
private static isReport; | ||
printReport(dirPath: string, formatterTypes?: Formatters[]): string[]; | ||
private mergeItems; | ||
private getFormatter; | ||
private resetCurrentRun; | ||
private getTimestamp; | ||
private normalizeFilePath; | ||
} | ||
export {}; | ||
//# sourceMappingURL=reporter.d.ts.map |
@@ -1,38 +0,36 @@ | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.Reporter = void 0; | ||
const fs_1 = require("fs"); | ||
const typescript_1 = require("typescript"); | ||
const types_1 = require("./types"); | ||
const normalize_paths_1 = require("./normalize-paths"); | ||
import { join, isAbsolute, relative } from 'node:path'; | ||
import { writeFileSync, existsSync, mkdirSync } from 'node:fs'; | ||
import ts from 'typescript'; | ||
import { JSONFormatter, MarkdownFormatter, SonarqubeFormatter, SarifFormatter, } from './formatters/index.js'; | ||
const { DiagnosticCategory, flattenDiagnosticMessageText, SyntaxKind } = ts; | ||
/** | ||
* Representation of diagnostic and migration report. | ||
*/ | ||
class Reporter { | ||
constructor(meta, logger) { | ||
const { projectName, basePath, commandName, tsVersion } = meta; | ||
this.basePath = basePath; | ||
this.logger = logger === null || logger === void 0 ? void 0 : logger.child({ service: 'rehearsal-reporter' }); | ||
export class Reporter { | ||
constructor(meta) { | ||
const { projectName, projectRootDir, tsVersion, previousFixedCount } = meta; | ||
// do not include extension in the stemName | ||
this.stemName = meta.stemName || 'rehearsal-report'; | ||
this.basePath = projectRootDir; | ||
this.report = { | ||
summary: { | ||
projectName: projectName, | ||
tsVersion: tsVersion, | ||
timestamp: new Date().toLocaleString('en-US', { | ||
year: 'numeric', | ||
month: 'numeric', | ||
day: 'numeric', | ||
hourCycle: 'h24', | ||
hour: '2-digit', | ||
minute: '2-digit', | ||
second: '2-digit', | ||
}), | ||
basePath: basePath, | ||
commandName: commandName, | ||
summary: [], | ||
fixedItemCount: previousFixedCount || 0, | ||
items: [], | ||
}; | ||
this.uniqueFiles = []; | ||
// runSummary !== summary, | ||
// summary is a list of all runs | ||
// runSummary is a summary of the current run | ||
this.currentRun = { | ||
runSummary: { | ||
projectName, | ||
tsVersion, | ||
timestamp: '', | ||
}, | ||
fixedItemCount: 0, | ||
items: [], | ||
fixedItemCount: 0, | ||
}; | ||
} | ||
getFileNames() { | ||
return [...new Set(this.report.items.map((item) => item.analysisTarget))]; | ||
return this.uniqueFiles; | ||
} | ||
@@ -45,19 +43,19 @@ getItemsByAnalysisTarget(fileName) { | ||
*/ | ||
addSummary(key, value) { | ||
this.report.summary[key] = value; | ||
addToRunSummary(key, value) { | ||
this.currentRun.runSummary[key] = value; | ||
} | ||
/** | ||
* Appends am information about provided diagnostic and related node to the report | ||
* Appends a information about provided TS diagnostic and related node to the report | ||
*/ | ||
addTSItem(diagnostic, node, triggeringLocation, hint = '', helpUrl = '', hintAdded = true) { | ||
this.report.items.push({ | ||
analysisTarget: (0, normalize_paths_1.normalizeFilePath)(this.basePath, diagnostic.file.fileName), | ||
type: types_1.ReportItemType.ts, | ||
addTSItemToRun(diagnostic, node, triggeringLocation, hint = '', helpUrl = '', hintAdded = true) { | ||
this.currentRun.items.push({ | ||
analysisTarget: this.normalizeFilePath(this.basePath, diagnostic.file.fileName), | ||
type: 0, | ||
ruleId: `TS${diagnostic.code}`, | ||
category: typescript_1.DiagnosticCategory[diagnostic.category], | ||
message: (0, typescript_1.flattenDiagnosticMessageText)(diagnostic.messageText, '. '), | ||
category: DiagnosticCategory[diagnostic.category], | ||
message: flattenDiagnosticMessageText(diagnostic.messageText, '. ').replace(this.basePath, '.'), | ||
hint: hint, | ||
hintAdded, | ||
nodeKind: node ? typescript_1.SyntaxKind[node.kind] : undefined, | ||
nodeText: node === null || node === void 0 ? void 0 : node.getText(), | ||
nodeKind: node ? SyntaxKind[node.kind] : undefined, | ||
nodeText: node?.getText(), | ||
helpUrl, | ||
@@ -67,8 +65,26 @@ nodeLocation: triggeringLocation || undefined, | ||
} | ||
addLintItem(fileName, lintError) { | ||
var _a; | ||
this.report.items.push({ | ||
analysisTarget: (0, normalize_paths_1.normalizeFilePath)(this.basePath, fileName), | ||
type: types_1.ReportItemType.lint, | ||
ruleId: lintError.ruleId || '', | ||
/** | ||
* Appends a information about provided Glint diagnostic and related node to the report | ||
*/ | ||
addGlintItemToRun(diagnostic, node, triggeringLocation, hint = '', helpUrl = '', hintAdded = true) { | ||
this.currentRun.items.push({ | ||
analysisTarget: this.normalizeFilePath(this.basePath, diagnostic.file.fileName), | ||
type: 2, | ||
ruleId: `Glint${diagnostic.code}`, | ||
category: DiagnosticCategory[diagnostic.category], | ||
message: flattenDiagnosticMessageText(diagnostic.messageText, '. ').replace(this.basePath, '.'), | ||
hint: hint, | ||
hintAdded, | ||
nodeKind: node ? SyntaxKind[node.kind] : undefined, | ||
nodeText: node?.getText(), | ||
helpUrl, | ||
nodeLocation: triggeringLocation || undefined, | ||
}); | ||
} | ||
addLintItemToRun(fileName, lintError) { | ||
this.currentRun.items.push({ | ||
analysisTarget: this.normalizeFilePath(this.basePath, fileName), | ||
type: 1, | ||
// errors generated by rules or errors generated by Eslint core | ||
ruleId: lintError.ruleId || 'error-generated-by-Eslint-core', | ||
category: 'Error', | ||
@@ -81,56 +97,114 @@ message: lintError.message, | ||
helpUrl: '', | ||
// When the error is generated by Eslint core, line and column from eslint are undefined | ||
// The fallback values are to prevent errors thrown by Sonarqube, e.g. endColumn has to be bigger than startColumn | ||
nodeLocation: { | ||
startLine: lintError.line, | ||
startColumn: lintError.column, | ||
endLine: lintError.line, | ||
endColumn: (_a = lintError.endColumn) !== null && _a !== void 0 ? _a : 0, | ||
startLine: lintError.line || 1, | ||
startColumn: lintError.column || 1, | ||
endLine: lintError.line || 1, | ||
endColumn: lintError.endColumn || 2, | ||
}, | ||
}); | ||
} | ||
incrementFixedItemCount() { | ||
this.report.fixedItemCount++; | ||
incrementRunFixedItemCount() { | ||
this.currentRun.fixedItemCount++; | ||
} | ||
/** | ||
* Prints the current report using provided formatter (ex. json, pull-request etc.) | ||
*/ | ||
print(file, formatter) { | ||
const report = formatter(this.report); | ||
if (file) { | ||
(0, fs_1.writeFileSync)(file, report); | ||
} | ||
return report; | ||
saveCurrentRunToReport(timestamp) { | ||
this.currentRun.runSummary.timestamp = timestamp || this.getTimestamp(); | ||
this.report.summary = [...this.report.summary, { ...this.currentRun.runSummary }]; | ||
this.report.fixedItemCount += this.currentRun.fixedItemCount; | ||
const { uniqueFiles, items } = this.mergeItems(); | ||
this.report.items = items; | ||
this.uniqueFiles = uniqueFiles; | ||
this.lastRun = { ...this.currentRun }; | ||
this.resetCurrentRun(); | ||
} | ||
/** | ||
* Saves the current report information to the file in simple JSON format | ||
* to be able to load it later with 'load' function | ||
* Prints the reports using provided formatter | ||
* json is always printed and set as default formatter | ||
*/ | ||
save(file) { | ||
var _a; | ||
this.print(file, (report) => JSON.stringify(report, null, 2)); | ||
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.info(`Report saved to: ${file}.`); | ||
printReport(dirPath, formatterTypes = ['json']) { | ||
const reports = []; | ||
// always print json report | ||
formatterTypes = Array.from(new Set([...formatterTypes, 'json'])); | ||
formatterTypes.forEach((format) => { | ||
const [formatter, fileExtension] = this.getFormatter(format); | ||
const report = formatter(this.report); | ||
reports.push(report); | ||
const reportPath = join(dirPath, `${this.stemName}${fileExtension}`); | ||
if (!existsSync(dirPath)) { | ||
mkdirSync(dirPath, { recursive: true }); | ||
} | ||
// write each file as formatted to disk | ||
writeFileSync(reportPath, report); | ||
}); | ||
return reports; | ||
} | ||
/** | ||
* Loads the report exported by function 'save' from the file | ||
*/ | ||
load(file) { | ||
var _a, _b, _c, _d; | ||
if (!(0, fs_1.existsSync)(file)) { | ||
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.error(`Report file not found: ${file}.`); | ||
mergeItems() { | ||
const fileSet = new Set(); | ||
for (const item of this.currentRun.items) { | ||
if (!fileSet.has(item.analysisTarget)) { | ||
fileSet.add(item.analysisTarget); | ||
} | ||
} | ||
(_b = this.logger) === null || _b === void 0 ? void 0 : _b.info(`Report file found: ${file}.`); | ||
const content = (0, fs_1.readFileSync)(file, 'utf-8'); | ||
const report = JSON.parse(content); | ||
if (!Reporter.isReport(report)) { | ||
(_c = this.logger) === null || _c === void 0 ? void 0 : _c.error(`Report not loaded: wrong file format`); | ||
return this; | ||
const items = [...this.currentRun.items]; | ||
for (const item of this.report.items) { | ||
if (!fileSet.has(item.analysisTarget)) { | ||
fileSet.add(item.analysisTarget); | ||
items.push(item); | ||
} | ||
} | ||
this.report = report; | ||
(_d = this.logger) === null || _d === void 0 ? void 0 : _d.info(`Report loaded from file.`); | ||
return this; | ||
const uniqueFiles = Array.from(fileSet); | ||
return { uniqueFiles, items }; | ||
} | ||
static isReport(report) { | ||
return report && report.summary !== undefined && report.items !== undefined; | ||
getFormatter(formatterType) { | ||
// get the appropriate report from static methods of formatter | ||
switch (formatterType) { | ||
case 'json': | ||
return [ | ||
(report) => JSONFormatter.getReport(report), | ||
JSONFormatter.extension, | ||
]; | ||
case 'sonarqube': | ||
return [ | ||
(report) => SonarqubeFormatter.getReport(report), | ||
SonarqubeFormatter.extension, | ||
]; | ||
case 'md': | ||
return [ | ||
(report) => MarkdownFormatter.getReport(report), | ||
MarkdownFormatter.extension, | ||
]; | ||
case 'sarif': | ||
return [ | ||
(report) => SarifFormatter.getReport(report), | ||
SarifFormatter.extension, | ||
]; | ||
default: | ||
break; | ||
} | ||
throw new Error(`Unknown formatter: ${formatterType}`); | ||
} | ||
resetCurrentRun() { | ||
this.currentRun.runSummary.timestamp = ''; | ||
this.currentRun.fixedItemCount = 0; | ||
this.currentRun.items = []; | ||
} | ||
getTimestamp() { | ||
return new Date().toLocaleString('en-US', { | ||
year: 'numeric', | ||
month: 'numeric', | ||
day: 'numeric', | ||
hourCycle: 'h24', | ||
hour: '2-digit', | ||
minute: '2-digit', | ||
second: '2-digit', | ||
}); | ||
} | ||
normalizeFilePath(basePath, filepath) { | ||
if (isAbsolute(filepath)) { | ||
return relative(basePath, filepath); | ||
} | ||
return filepath; | ||
} | ||
} | ||
exports.Reporter = Reporter; | ||
//# sourceMappingURL=reporter.js.map |
@@ -0,7 +1,6 @@ | ||
export type Formatters = 'json' | 'sonarqube' | 'md' | 'sarif'; | ||
export type ReportSummary = Record<string, unknown> & { | ||
projectName: string; | ||
basePath: string; | ||
tsVersion: string; | ||
timestamp: string; | ||
commandName: string; | ||
}; | ||
@@ -16,3 +15,4 @@ export interface Location { | ||
ts = 0, | ||
lint = 1 | ||
lint = 1, | ||
glint = 2 | ||
} | ||
@@ -40,15 +40,24 @@ export type ReportItem = { | ||
ruleId: string | null; | ||
line: number; | ||
column: number; | ||
line?: number; | ||
column?: number; | ||
nodeType?: string; | ||
messageId?: string; | ||
endLine?: number | undefined; | ||
endColumn?: number | undefined; | ||
endLine?: number; | ||
endColumn?: number; | ||
} | ||
export type Report = { | ||
summary: ReportSummary; | ||
summary: ReportSummary[]; | ||
fixedItemCount: number; | ||
items: ReportItem[]; | ||
fixedItemCount: number; | ||
}; | ||
export type ReportFormatter = (report: Report) => string; | ||
export interface Run { | ||
runSummary: ReportSummary; | ||
fixedItemCount: number; | ||
items: ReportItem[]; | ||
} | ||
export declare class FormatterBase { | ||
static extension: string; | ||
static getReport(_report: Report): string; | ||
} | ||
//# sourceMappingURL=types.d.ts.map |
@@ -1,9 +0,14 @@ | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.ReportItemType = void 0; | ||
var ReportItemType; | ||
export var ReportItemType; | ||
(function (ReportItemType) { | ||
ReportItemType[ReportItemType["ts"] = 0] = "ts"; | ||
ReportItemType[ReportItemType["lint"] = 1] = "lint"; | ||
})(ReportItemType = exports.ReportItemType || (exports.ReportItemType = {})); | ||
ReportItemType[ReportItemType["glint"] = 2] = "glint"; | ||
})(ReportItemType || (ReportItemType = {})); | ||
// ts doesnt allow for static properties on interfaces yet | ||
export class FormatterBase { | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
static getReport(_report) { | ||
return ''; | ||
} | ||
} | ||
//# sourceMappingURL=types.js.map |
{ | ||
"name": "@rehearsal/reporter", | ||
"version": "1.0.4-beta", | ||
"version": "1.0.4", | ||
"description": "Rehearsal Reporter", | ||
@@ -15,2 +15,10 @@ "keywords": [ | ||
"license": "BSD-2-Clause", | ||
"type": "module", | ||
"exports": { | ||
".": { | ||
"types": "./dist/src/index.d.ts", | ||
"import": "./dist/src/index.js" | ||
}, | ||
"./*": "./*" | ||
}, | ||
"main": "dist/src/index.js", | ||
@@ -22,16 +30,15 @@ "types": "dist/src/index.d.ts", | ||
"dependencies": { | ||
"winston": "^3.6.0" | ||
"winston": "^3.8.2" | ||
}, | ||
"devDependencies": { | ||
"@types/debug": "^4.1.7", | ||
"@types/eslint": "^8.4.1", | ||
"@types/lodash.get": "^4.4.7", | ||
"@types/sarif": "^2.1.4", | ||
"fs-extra": "^10.1.0", | ||
"lodash.get": "^4.4.2", | ||
"typescript": "^4.9.4", | ||
"vitest": "^0.23.4", | ||
"vitest-mock-extended": "^0.1.15" | ||
"@vitest/coverage-c8": "^0.30.1", | ||
"fs-extra": "^11.1.1", | ||
"vitest": "^0.30.0", | ||
"vitest-mock-extended": "^1.1.3" | ||
}, | ||
"packageManager": "pnpm@7.12.1", | ||
"peerDependencies": { | ||
"typescript": "^5.1" | ||
}, | ||
"packageManager": "pnpm@8.2.0", | ||
"engines": { | ||
@@ -46,6 +53,5 @@ "node": ">=14.16.0" | ||
"lint": "npm-run-all lint:*", | ||
"lint:eslint": "eslint --fix . --ext .ts", | ||
"lint:tsc-src": "tsc --noEmit", | ||
"lint:tsc-test": "tsc --noEmit --project test/tsconfig.json", | ||
"test": "vitest --run", | ||
"test": "vitest --run --config ./vitest.config.ts --coverage", | ||
"test:watch": "vitest --coverage --watch", | ||
@@ -52,0 +58,0 @@ "version": "pnpm version" |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
90150
5
648
1
0
Yes
2
1
+ Addedtypescript@5.7.3(transitive)
Updatedwinston@^3.8.2