Skip to content

Commit

Permalink
Merge pull request #67 from mcarvin8/refactor/functions
Browse files Browse the repository at this point in the history
refactor: break into smaller functions
  • Loading branch information
mcarvin8 authored Dec 19, 2024
2 parents fbedda6 + 1f132d6 commit 8f2f578
Show file tree
Hide file tree
Showing 5 changed files with 281 additions and 211 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,14 +230,14 @@ and this format for Cobertura:
```xml
<?xml version="1.0" ?>
<!DOCTYPE coverage SYSTEM "http://cobertura.sourceforge.net/xml/coverage-04.dtd">
<coverage lines-valid="62" lines-covered="54" line-rate="0.871" branches-valid="0" branches-covered="0" branch-rate="1" timestamp="1734405832938" complexity="0" version="0.1">
<coverage lines-valid="62" lines-covered="54" line-rate="0.871" branches-valid="0" branches-covered="0" branch-rate="1" timestamp="1734621101529" complexity="0" version="0.1">
<sources>
<source>.</source>
</sources>
<packages>
<package name="main" line-rate="0.871" branch-rate="1">
<classes>
<class name="AccountTrigger" filename="test/baselines/triggers/AccountTrigger.trigger" line-rate="0.8710" branch-rate="1">
<class name="AccountTrigger" filename="packaged/triggers/AccountTrigger.trigger" line-rate="0.8710" branch-rate="1">
<methods/>
<lines>
<line number="52" hits="0" branch="false"/>
Expand Down Expand Up @@ -273,7 +273,7 @@ and this format for Cobertura:
<line number="27" hits="1" branch="false"/>
</lines>
</class>
<class name="AccountProfile" filename="test/baselines/classes/AccountProfile.cls" line-rate="0.8710" branch-rate="1">
<class name="AccountProfile" filename="force-app/main/default/classes/AccountProfile.cls" line-rate="0.8710" branch-rate="1">
<methods/>
<lines>
<line number="52" hits="0" branch="false"/>
Expand Down
14 changes: 14 additions & 0 deletions src/helpers/generateXml.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
'use strict';

import { create } from 'xmlbuilder2';
import { SonarCoverageObject, CoberturaCoverageObject } from './types.js';

export function generateXml(coverageObj: SonarCoverageObject | CoberturaCoverageObject, format: string): string {
let xml = create(coverageObj).end({ prettyPrint: true, indent: ' ', headless: format === 'cobertura' });

if (format === 'cobertura') {
xml = `<?xml version="1.0" ?>\n<!DOCTYPE coverage SYSTEM "http://cobertura.sourceforge.net/xml/coverage-04.dtd">\n${xml}`;
}

return xml;
}
225 changes: 127 additions & 98 deletions src/helpers/transformDeployCoverageReport.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,42 @@
'use strict';
/* eslint-disable no-await-in-loop */

import { create } from 'xmlbuilder2';
import {
DeployCoverageData,
SonarCoverageObject,
CoberturaCoverageObject,
SonarClass,
CoberturaClass,
CoberturaPackage,
} from './types.js';
import { getPackageDirectories } from './getPackageDirectories.js';
import { findFilePath } from './findFilePath.js';
import { setCoveredLinesSonar } from './setCoveredLinesSonar.js';
import { setCoveredLinesCobertura } from './setCoveredLinesCobertura.js';
import { normalizePathToUnix } from './normalizePathToUnix.js';
import { generateXml } from './generateXml.js';

export async function transformDeployCoverageReport(
data: DeployCoverageData,
format: string
): Promise<{ xml: string; warnings: string[]; filesProcessed: number }> {
if (!['sonar', 'cobertura'].includes(format)) {
throw new Error(`Unsupported format: ${format}`);
}

const warnings: string[] = [];
let filesProcessed: number = 0;
const { repoRoot, packageDirectories } = await getPackageDirectories();

if (format === 'sonar') {
const coverageObj: SonarCoverageObject = { coverage: { '@version': '1', file: [] } };

for (const fileName in data) {
if (!Object.hasOwn(data, fileName)) continue;
const fileInfo = data[fileName];
const formattedFileName = fileName.replace(/no-map[\\/]+/, '');
const relativeFilePath = await findFilePath(formattedFileName, packageDirectories, repoRoot);
if (relativeFilePath === undefined) {
warnings.push(`The file name ${formattedFileName} was not found in any package directory.`);
continue;
}
const uncoveredLines = Object.keys(fileInfo.s)
.filter((lineNumber) => fileInfo.s[lineNumber] === 0)
.map(Number);
const coveredLines = Object.keys(fileInfo.s)
.filter((lineNumber) => fileInfo.s[lineNumber] === 1)
.map(Number);

const fileObj: SonarClass = {
'@path': normalizePathToUnix(relativeFilePath),
lineToCover: uncoveredLines.map((lineNumber: number) => ({
'@lineNumber': lineNumber,
'@covered': 'false',
})),
};
// Initialize format-specific coverage objects
let coverageObj: SonarCoverageObject | CoberturaCoverageObject;

await setCoveredLinesSonar(coveredLines, uncoveredLines, repoRoot, relativeFilePath, fileObj);
filesProcessed++;
coverageObj.coverage.file.push(fileObj);
}
const xml = create(coverageObj).end({ prettyPrint: true, indent: ' ' });
return { xml, warnings, filesProcessed };
} else if (format === 'cobertura') {
const coberturaObj: CoberturaCoverageObject = {
if (format === 'sonar') {
coverageObj = {
coverage: { '@version': '1', file: [] },
} as SonarCoverageObject;
} else {
coverageObj = {
coverage: {
'@lines-valid': 0,
'@lines-covered': 0,
Expand All @@ -71,72 +50,122 @@ export async function transformDeployCoverageReport(
sources: { source: ['.'] },
packages: { package: [] },
},
};

// Single package for all classes
const packageObj = {
'@name': 'main',
'@line-rate': 0,
'@branch-rate': 1,
classes: { class: [] as CoberturaClass[] },
};
coberturaObj.coverage.packages.package.push(packageObj);

for (const fileName in data) {
if (!Object.hasOwn(data, fileName)) continue;
const fileInfo = data[fileName];
const formattedFileName = fileName.replace(/no-map[\\/]+/, '');
const relativeFilePath = await findFilePath(formattedFileName, packageDirectories, repoRoot);
if (relativeFilePath === undefined) {
warnings.push(`The file name ${formattedFileName} was not found in any package directory.`);
continue;
}
const uncoveredLines = Object.keys(fileInfo.s)
.filter((lineNumber) => fileInfo.s[lineNumber] === 0)
.map(Number);
const coveredLines = Object.keys(fileInfo.s)
.filter((lineNumber) => fileInfo.s[lineNumber] === 1)
.map(Number);

const classObj: CoberturaClass = {
'@name': formattedFileName,
'@filename': normalizePathToUnix(relativeFilePath),
'@line-rate': (coveredLines.length / (coveredLines.length + uncoveredLines.length)).toFixed(4),
'@branch-rate': '1',
methods: {},
lines: {
line: [
...uncoveredLines.map((lineNumber) => ({
'@number': lineNumber,
'@hits': 0,
'@branch': 'false',
})),
],
},
};

await setCoveredLinesCobertura(coveredLines, uncoveredLines, repoRoot, relativeFilePath, classObj);

// Update package and overall coverage metrics
coberturaObj.coverage['@lines-valid'] += uncoveredLines.length + coveredLines.length;
coberturaObj.coverage['@lines-covered'] += coveredLines.length;

packageObj.classes.class.push(classObj);
filesProcessed++;
}
} as CoberturaCoverageObject;
}

const packageObj =
format === 'cobertura'
? {
'@name': 'main',
'@line-rate': 0,
'@branch-rate': 1,
classes: { class: [] as CoberturaClass[] },
}
: null;

if (packageObj) {
(coverageObj as CoberturaCoverageObject).coverage.packages.package.push(packageObj);
}

for (const fileName in data) {
if (!Object.hasOwn(data, fileName)) continue;

// Update overall line-rate for the package
packageObj['@line-rate'] = Number(
(coberturaObj.coverage['@lines-covered'] / coberturaObj.coverage['@lines-valid']).toFixed(4)
);
coberturaObj.coverage['@line-rate'] = packageObj['@line-rate'];
const fileInfo = data[fileName];
const formattedFileName = fileName.replace(/no-map[\\/]+/, '');
const relativeFilePath = await findFilePath(formattedFileName, packageDirectories, repoRoot);

let xml = create(coberturaObj).end({ prettyPrint: true, indent: ' ', headless: true });
if (relativeFilePath === undefined) {
warnings.push(`The file name ${formattedFileName} was not found in any package directory.`);
continue;
}

const uncoveredLines = Object.keys(fileInfo.s)
.filter((lineNumber) => fileInfo.s[lineNumber] === 0)
.map(Number);
const coveredLines = Object.keys(fileInfo.s)
.filter((lineNumber) => fileInfo.s[lineNumber] === 1)
.map(Number);

if (format === 'sonar') {
await handleSonarFormat(
relativeFilePath,
uncoveredLines,
coveredLines,
repoRoot,
coverageObj as SonarCoverageObject
);
} else {
await handleCoberturaFormat(
relativeFilePath,
formattedFileName,
uncoveredLines,
coveredLines,
repoRoot,
coverageObj as CoberturaCoverageObject,
packageObj!
);
}

// Add DOCTYPE declaration at the beginning of the XML
xml = `<?xml version="1.0" ?>\n<!DOCTYPE coverage SYSTEM "http://cobertura.sourceforge.net/xml/coverage-04.dtd">\n${xml}`;
return { xml, warnings, filesProcessed };
filesProcessed++;
}

throw new Error(`Unsupported format: ${format}`);
const xml = generateXml(coverageObj, format);
return { xml, warnings, filesProcessed };
}

async function handleSonarFormat(
filePath: string,
uncoveredLines: number[],
coveredLines: number[],
repoRoot: string,
coverageObj: SonarCoverageObject
): Promise<void> {
const fileObj: SonarClass = {
'@path': normalizePathToUnix(filePath),
lineToCover: uncoveredLines.map((lineNumber) => ({
'@lineNumber': lineNumber,
'@covered': 'false',
})),
};

await setCoveredLinesSonar(coveredLines, uncoveredLines, repoRoot, filePath, fileObj);
coverageObj.coverage.file.push(fileObj);
}

async function handleCoberturaFormat(
filePath: string,
fileName: string,
uncoveredLines: number[],
coveredLines: number[],
repoRoot: string,
coverageObj: CoberturaCoverageObject,
packageObj: CoberturaPackage
): Promise<void> {
const classObj: CoberturaClass = {
'@name': fileName,
'@filename': normalizePathToUnix(filePath),
'@line-rate': (coveredLines.length / (coveredLines.length + uncoveredLines.length)).toFixed(4),
'@branch-rate': '1',
methods: {},
lines: {
line: [
...uncoveredLines.map((lineNumber) => ({
'@number': lineNumber,
'@hits': 0,
'@branch': 'false',
})),
],
},
};

await setCoveredLinesCobertura(coveredLines, uncoveredLines, repoRoot, filePath, classObj);

coverageObj.coverage['@lines-valid'] += uncoveredLines.length + coveredLines.length;
coverageObj.coverage['@lines-covered'] += coveredLines.length;
packageObj.classes.class.push(classObj);

packageObj['@line-rate'] = Number(
(coverageObj.coverage['@lines-covered'] / coverageObj.coverage['@lines-valid']).toFixed(4)
);
coverageObj.coverage['@line-rate'] = packageObj['@line-rate'];
}
Loading

0 comments on commit 8f2f578

Please sign in to comment.