Repository: codecov/uploader
Pull request #869 (2022-09-01 03:47:37)

feat: create fixes on c-like and php files

codecov[bot]: # [Codecov](https://app.codecov.io/gh/codecov/uploader/compare/869?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=codecov) Report
> Merging [#869](https://app.codecov.io/gh/codecov/uploader/compare/869?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=codecov) (aeedfa1) into [main](https://codecov.io/gh/codecov/uploader/commit/d73502ce0fb0c32bd99e98236a23c4dec3f18365?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=codecov) (d73502c) will **decrease** coverage by `0.30%`.
> The diff coverage is `82.50%`.

```diff
@@            Coverage Diff             @@
##             main     #869      +/-   ##
==========================================
- Coverage   91.87%   91.56%   -0.31%     
==========================================
  Files          34       35       +1     
  Lines        1181     1221      +40     
  Branches      244      249       +5     
==========================================
+ Hits         1085     1118      +33     
- Misses         64       70       +6     
- Partials       32       33       +1     
```

| Flag | Coverage Δ | |
|---|---|---|
| alpine | `91.56% <82.50%> (-0.31%)` | :arrow_down: |
| alpine-proxy | `91.56% <82.50%> (-0.31%)` | :arrow_down: |
| alpine-without-git | `91.56% <82.50%> (-0.31%)` | :arrow_down: |
| linux | `91.56% <82.50%> (-0.31%)` | :arrow_down: |
| linux-without-git | `91.56% <82.50%> (-0.31%)` | :arrow_down: |
| macos | `91.56% <82.50%> (-0.31%)` | :arrow_down: |
| macos-without-git | `91.56% <82.50%> (-0.31%)` | :arrow_down: |
| windows | `91.56% <82.50%> (-0.31%)` | :arrow_down: |
| windows-without-git | `?` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=codecov#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://app.codecov.io/gh/codecov/uploader/compare/869?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=codecov) | Coverage Δ | |
|---|---|---|
| [src/helpers/cli.ts](https://app.codecov.io/gh/codecov/uploader/compare/869/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=codecov#diff-c3JjL2hlbHBlcnMvY2xpLnRz) | `100.00% <ø> (ø)` | |
| [src/index.ts](https://app.codecov.io/gh/codecov/uploader/compare/869/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=codecov#diff-c3JjL2luZGV4LnRz) | `71.34% <12.50%> (-3.02%)` | :arrow_down: |
| [src/helpers/fixes.ts](https://app.codecov.io/gh/codecov/uploader/compare/869/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=codecov#diff-c3JjL2hlbHBlcnMvZml4ZXMudHM=) | `100.00% <100.00%> (ø)` | |


Patch:
diff --git a/.gitignore b/.gitignore
index 9d8191b..28edf13 100644
--- a/.gitignore
+++ b/.gitignore
@@ -73,3 +73,5 @@ codecov.exe
 .coverage
 # File genrated as part of XCode tests
 coverage-report-test.json
+
+*.coverage.txt
diff --git a/src/helpers/cli.ts b/src/helpers/cli.ts
index 312fb52..be84807 100644
--- a/src/helpers/cli.ts
+++ b/src/helpers/cli.ts
@@ -178,6 +178,7 @@ const args: ICLIArgument[] = [
     name: 'feature',
     type: 'string',
     description: `Toggle functionalities. Separate multiple ones by comma: -X network,search
+      -X fixes         Enable file fixes to ignore common lines from coverage (e.g. blank lines or empty brackets)
       -X network       Disable uploading the file network
       -X search        Disable searching for coverage files`,
   },
diff --git a/src/helpers/fixes.ts b/src/helpers/fixes.ts
new file mode 100644
index 0000000..6f031d6
--- /dev/null
+++ b/src/helpers/fixes.ts
@@ -0,0 +1,70 @@
+import fs from 'fs'
+import readline from 'readline'
+
+import { getAllFiles } from './files'
+import { UploadLogger } from './logger'
+
+export const FIXES_HEADER = '# path=fixes\n'
+
+export async function generateFixes(projectRoot: string): Promise<string> {
+  // Fake out the UploaderArgs as they are not needed
+  const allFiles = await getAllFiles(projectRoot, projectRoot, {
+    flags: '',
+    slug: '',
+    upstream: '',
+  })
+
+  const allAdjustments: string[] = []
+  const EMPTYLINE = /^\s*$/mg
+  // { or }
+  const SYNTAXBRACKET = /^\s*[{}]\s*(\/\/.*)?$/m
+  // [ or ]
+  const SYNTAXLIST = /^\s*[[\]]\s*(\/\/.*)?$/m
+
+  for (const file of allFiles) {
+    let lineAdjustments: string[] = []
+
+    if (
+        file.match(/\.c$/) ||
+        file.match(/\.cpp$/) ||
+        file.match(/\.h$/) ||
+        file.match(/\.hpp$/) ||
+        file.match(/\.m$/) ||
+        file.match(/\.swift$/) ||
+        file.match(/\.vala$/)
+    ) {
+      lineAdjustments = await getMatchedLines(file, [EMPTYLINE, SYNTAXBRACKET])
+    } else if (
+        file.match(/\.php$/)
+    ) {
+      lineAdjustments = await getMatchedLines(file, [SYNTAXBRACKET, SYNTAXLIST])
+    }
+
+    if (lineAdjustments.length > 0) {
+      UploadLogger.verbose(`Matched file ${file} for adjustments: ${lineAdjustments.join(',')}`)
+      allAdjustments.push(`${file}:${lineAdjustments.join(',')}\n`)
+    }
+  }
+  return allAdjustments.join('')
+}
+
+async function getMatchedLines(file: string, matchers: RegExp[]): Promise<string[]> {
+  const fileStream = fs.createReadStream(file)
+  const rl = readline.createInterface({
+    input: fileStream,
+    crlfDelay: Infinity
+  });
+
+  const matchedLines: string[] = []
+  let lineNumber = 1
+
+  for await (const line of rl) {
+    for (const matcher of matchers) {
+      if (line.match(matcher)) {
+        matchedLines.push(lineNumber.toString())
+      }
+    }
+    lineNumber++
+  }
+  return matchedLines
+}
diff --git a/src/index.ts b/src/index.ts
index 333bb51..3c721d9 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -23,6 +23,7 @@ import {
   removeFile,
 } from './helpers/files'
 import { generateCoveragePyFile } from './helpers/coveragepy'
+import { generateFixes, FIXES_HEADER } from './helpers/fixes'
 import { generateGcovCoverageFiles } from './helpers/gcov'
 import { generateXcodeCoverageFiles } from './helpers/xcode'
 import { argAsArray } from './helpers/util'
@@ -322,6 +323,16 @@ export async function main(
     uploadFileChunks.push(Buffer.from(MARKER_ENV_END))
   }
 
+  // Fixes
+  if (args.feature && args.feature.split(',').includes('fixes') === true) {
+    info('Generating file fixes...')
+    const fixes = await generateFixes(projectRoot)
+    uploadFileChunks.push(Buffer.from(FIXES_HEADER))
+    uploadFileChunks.push(Buffer.from(fixes))
+    uploadFileChunks.push(Buffer.from(MARKER_ENV_END))
+    info('Finished generating file fixes')
+  }
+
   const uploadFile = Buffer.concat(uploadFileChunks)
   const gzippedFile = zlib.gzipSync(uploadFile)
 


Test patch:
diff --git a/test/fixtures/fixes/example.php b/test/fixtures/fixes/example.php
new file mode 100644
index 0000000..004ce62
--- /dev/null
+++ b/test/fixtures/fixes/example.php
@@ -0,0 +1,20 @@
+<?php namespace Example;
+
+class Example
+{
+    public static function go()
+    {
+        $test_array = [
+          1,
+          2,
+          3,
+        ]
+
+        if (false) {
+            return true;
+        }
+
+        return false;
+
+    }
+}
diff --git a/test/helpers/fixes.test.ts b/test/helpers/fixes.test.ts
new file mode 100644
index 0000000..c98d89f
--- /dev/null
+++ b/test/helpers/fixes.test.ts
@@ -0,0 +1,66 @@
+import td from 'testdouble'
+import childProcess from 'child_process'
+
+import * as fixesHelpers from '../../src/helpers/fixes'
+
+describe('Fixes Helpers', () => {
+  afterEach(() => {
+    td.reset()
+  })
+
+  it('provides no fixes if none are applicable', async () => {
+    const files = ['package.json']
+    td.replace(childProcess, 'spawnSync', () => {
+      return {
+        stdout: files.join('\n'),
+        status: 0,
+        error: undefined,
+      }
+    })
+    expect(
+      await fixesHelpers.generateFixes('.')
+    ).toBe('')
+  })
+
+  it('provides proper fixes for c-like files', async () => {
+    const files = ['test/fixtures/gcov/main.c']
+    td.replace(childProcess, 'spawnSync', () => {
+      return {
+        stdout: files.join('\n'),
+        status: 0,
+        error: undefined,
+      }
+    })
+    expect(
+      await fixesHelpers.generateFixes('.')
+    ).toBe('test/fixtures/gcov/main.c:2,4,11,12,13,14,16,20\n')
+  })
+
+  it('provides proper fixes for php-like files', async () => {
+    const files = ['test/fixtures/fixes/example.php']
+    td.replace(childProcess, 'spawnSync', () => {
+      return {
+        stdout: files.join('\n'),
+        status: 0,
+        error: undefined,
+      }
+    })
+    expect(
+      await fixesHelpers.generateFixes('.')
+    ).toBe('test/fixtures/fixes/example.php:4,6,11,15,19,20\n')
+  })
+
+  it('provides multiple fixes for files', async () => {
+    const files = ['test/fixtures/fixes/example.php', 'test/fixtures/gcov/main.c']
+    td.replace(childProcess, 'spawnSync', () => {
+      return {
+        stdout: files.join('\n'),
+        status: 0,
+        error: undefined,
+      }
+    })
+    expect(
+      await fixesHelpers.generateFixes('.')
+    ).toBe('test/fixtures/fixes/example.php:4,6,11,15,19,20\ntest/fixtures/gcov/main.c:2,4,11,12,13,14,16,20\n')
+  })
+})
diff --git a/test/index.test.ts b/test/index.test.ts
index 04979a0..fb965a7 100644
--- a/test/index.test.ts
+++ b/test/index.test.ts
@@ -509,4 +509,20 @@ describe('Uploader Core', () => {
       expect.stringMatching(/<<<<<< network/),
     )
   })
+
+  it('Can create fixes', async () => {
+    await app.main({
+      name: 'customname',
+      token: 'abcdefg',
+      url: 'https://codecov.io',
+      dryRun: 'true',
+      feature: 'fixes',
+      flags: '',
+      slug: '',
+      upstream: ''
+    })
+    expect(console.log).toHaveBeenCalledWith(
+      expect.stringMatching(/# path=fixes\ntest\/fixtures\/fixes\/example.php:4,6,11,15,19,20\ntest\/fixtures\/gcov\/main.c:2,4,11,12,13,14,16,20/)
+    )
+  })
 })