Skip to content

Commit

Permalink
New Rule: enforce-min-coverage-comments-sync (#277)
Browse files Browse the repository at this point in the history
* initial updateCommentThreshold

* on second thought it should be separate rule

enforce-min-coverage-comments-sync

* create not under meta

* fix func name

* introduce getMinCoverageDirectiveCommentNodeAndPercent

* fix works!?

TODO: how to revert test file after spec fixes it?

* 4.5.0-pre0.1

* floor

* remove comment if coverage above global threshold

* add coverage-sync-* examples

* add to codebases

* Update format.spec.js.snap

* handle expected fixes

* no js ext on fixed examples

so they won't be fixed themselves

* percent is 33 apparently ok flow

* version

* remove leftover copypasta from enforce-min-coverage

* 0.4

* address pr review comments
  • Loading branch information
TSMMark authored Nov 15, 2021
1 parent cbaa467 commit 0e692aa
Show file tree
Hide file tree
Showing 10 changed files with 147 additions and 5 deletions.
3 changes: 3 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
src/
flow-typed/
test/
69 changes: 69 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,24 @@ function createFilteredErrorRule(filter: (CollectOutputElement) => any): (contex
};
}

const MIN_COVERAGE_DIRECTIVE_COMMENT_PATTERN =
/(\s*eslint\s*['"]flowtype-errors\/enforce-min-coverage['"]\s*:\s*\[\s*(?:2|['"]error['"])\s*,\s*)(\d+)(\]\s*)/

function getMinCoverageDirectiveCommentNodeAndPercent(sourceCode) {
let commentNode
let minPercent
// eslint-disable-next-line no-restricted-syntax
for (const comment of sourceCode.getAllComments()) {
const match = comment.value.match(MIN_COVERAGE_DIRECTIVE_COMMENT_PATTERN)
if (match && match[2]) {
commentNode = comment
minPercent = parseInt(match[2], 10)
break
}
}
return [commentNode, minPercent]
}

const getCoverage = (context, node) => {
const source = context.getSourceCode();
const info = lookupInfo(context, source, node);
Expand Down Expand Up @@ -242,6 +260,57 @@ export default {
},
};
},
'enforce-min-coverage-comments-sync': {
meta: {
fixable: 'code',
},
create: function enforceMinCoverageCommentsSync(
context: EslintContext
): ReturnRule {
return {
Program(node: Object) {
const res = getCoverage(context, node);
if (!res) {
return;
}

const sourceCode = context.getSourceCode()
const [minCoverageDirectiveCommentNode, requiredCoverage] = getMinCoverageDirectiveCommentNodeAndPercent(sourceCode)
if (!minCoverageDirectiveCommentNode || !requiredCoverage) {
return;
}

// Get global requiredCoverage outside the inline module comment.
const enforceMinCoverage = context.options[0];
// If flow coverage is >=updateCommentThreshold% greater than allowed, update the eslint comment.
const updateCommentThreshold = context.options[1];
const { coveredCount, uncoveredCount } = res.coverageInfo;

/* eslint prefer-template: 0 */
const percentage = Number(
Math.round(
(coveredCount / (coveredCount + uncoveredCount)) * 10000
) + 'e-2'
);

if (percentage - requiredCoverage > updateCommentThreshold) {
context.report({
loc: res.program.loc,
message: `Expected coverage comment to be within ${updateCommentThreshold}% of ${requiredCoverage}%, but is: ${percentage}%`,
fix(fixer) {
if (percentage >= enforceMinCoverage) {
// If coverage >= global required amount, remove comment entirely.
return fixer.replaceText(minCoverageDirectiveCommentNode, '')
}

return fixer.replaceText(minCoverageDirectiveCommentNode, minCoverageDirectiveCommentNode.value.replace(MIN_COVERAGE_DIRECTIVE_COMMENT_PATTERN, `/*$1${Math.floor(percentage)}$3*/`))
}
});
}
},
};
},
},
'show-errors': (createFilteredErrorRule(
({ level }) => level !== FlowSeverity.Warning
) : (context: EslintContext) => ReturnRule),
Expand Down
4 changes: 4 additions & 0 deletions test/__snapshots__/format.spec.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ exports[`Check codebases coverage-ok - eslint should give expected output 1`] =

exports[`Check codebases coverage-ok2 - eslint should give expected output 1`] = `""`;

exports[`Check codebases coverage-sync-remove - eslint should give expected output 1`] = `""`;

exports[`Check codebases coverage-sync-update - eslint should give expected output 1`] = `""`;

exports[`Check codebases flow-pragma-1 - eslint should give expected output 1`] = `
"
./example.js
Expand Down
4 changes: 4 additions & 0 deletions test/codebases/coverage-sync-remove/.flowconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[options]
esproposal.class_static_fields=enable
esproposal.class_instance_fields=enable
esproposal.export_star_as=enable
8 changes: 8 additions & 0 deletions test/codebases/coverage-sync-remove/example.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// @flow


let x: number = 100;
let x2: number = 100;
let x3: number = 100;
let x4;
let x5;
8 changes: 8 additions & 0 deletions test/codebases/coverage-sync-remove/example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// @flow
/* eslint "flowtype-errors/enforce-min-coverage": [2, 30] */

let x: number = 100;
let x2: number = 100;
let x3: number = 100;
let x4;
let x5;
4 changes: 4 additions & 0 deletions test/codebases/coverage-sync-update/.flowconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[options]
esproposal.class_static_fields=enable
esproposal.class_instance_fields=enable
esproposal.export_star_as=enable
8 changes: 8 additions & 0 deletions test/codebases/coverage-sync-update/example.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// @flow
/* eslint "flowtype-errors/enforce-min-coverage": [2, 33] */

let x: number = 100;
let x2;
let x3;
let x4;
let x5;
8 changes: 8 additions & 0 deletions test/codebases/coverage-sync-update/example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// @flow
/* eslint "flowtype-errors/enforce-min-coverage": [2, 5] */

let x: number = 100;
let x2;
let x3;
let x4;
let x5;
36 changes: 31 additions & 5 deletions test/format.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable no-nested-ternary */
import path from 'path';
import { expect as chaiExpect } from 'chai';
import { readFileSync, writeFileSync, unlinkSync } from 'fs';
import { existsSync, readFileSync, writeFileSync, unlinkSync } from 'fs';
// $FlowIgnore
import execa from 'execa';
import { collect } from '../src/collect';
Expand Down Expand Up @@ -60,7 +61,7 @@ describe('Format', () => {
const ESLINT_PATH = path.resolve('./node_modules/eslint/bin/eslint.js');

async function runEslint(cwd) {
const result = await execa(ESLINT_PATH, ['**/*.{js,vue}'], { cwd, stripEof: false });
const result = await execa(ESLINT_PATH, ['**/*.{js,vue}', '--fix'], { cwd, stripEof: false });
result.stdout = result.stdout && result.stdout.toString();
result.stderr = result.stderr && result.stderr.toString();
return result;
Expand All @@ -72,6 +73,8 @@ const codebases = [
'coverage-fail2',
'coverage-ok',
'coverage-ok2',
'coverage-sync-remove',
'coverage-sync-update',
'flow-pragma-1',
'flow-pragma-2',
'html-support',
Expand All @@ -86,7 +89,7 @@ const codebases = [
'uncovered-example'
];

const eslintConfig = (enforceMinCoverage, checkUncovered, html) => `
const eslintConfig = (enforceMinCoverage, updateCommentThreshold, checkUncovered, html) => `
const Module = require('module');
const path = require('path');
const original = Module._resolveFilename;
Expand Down Expand Up @@ -119,8 +122,15 @@ const eslintConfig = (enforceMinCoverage, checkUncovered, html) => `
}
},
rules: {
${enforceMinCoverage
? `'flowtype-errors/enforce-min-coverage': [2, ${enforceMinCoverage}],` : ``}
${updateCommentThreshold
? [
`'flowtype-errors/enforce-min-coverage': [2, ${enforceMinCoverage}],`,
`'flowtype-errors/enforce-min-coverage-comments-sync': [2, ${enforceMinCoverage}, ${updateCommentThreshold}],`,
].join('\n')
: enforceMinCoverage
? `'flowtype-errors/enforce-min-coverage': [2, ${enforceMinCoverage}],`
: ``
}
${checkUncovered ? `'flowtype-errors/uncovered': 2,` : ''}
'flowtype-errors/show-errors': 2,
'flowtype-errors/show-warnings': 1
Expand All @@ -144,13 +154,20 @@ describe('Check codebases', () => {
// eslint-disable-next-line no-loop-func
it(`${title} - eslint should give expected output`, async() => {
const fullFolder = path.resolve(`./test/codebases/${folder}`);
const exampleJsFilePath = path.resolve(`./test/codebases/${folder}/example.js`);
const exampleJsFixedFilePath = path.resolve(`./test/codebases/${folder}/example.fixed`);
const hasFix = existsSync(exampleJsFixedFilePath)
const configPath = path.resolve(fullFolder, '.eslintrc.js');

const contentsBefore = hasFix && readFileSync(exampleJsFilePath, 'utf8')
const contentsExpected = hasFix && readFileSync(exampleJsFixedFilePath, 'utf8')

// Write config file
writeFileSync(
configPath,
eslintConfig(
folder.match(/^coverage-/) ? 50 : 0,
folder.match(/^coverage-sync/) ? 10 : 0,
!!folder.match(/^uncovered-/),
/html-support/.test(folder)
)
Expand All @@ -164,6 +181,11 @@ describe('Check codebases', () => {
'gm'
); // Escape regexp

const contentsAfter = hasFix && readFileSync(exampleJsFilePath, 'utf8')

// Revert the file to before it was fixed, since this file is checked into git.
if (hasFix && contentsBefore !== contentsAfter) writeFileSync(exampleJsFilePath, contentsBefore)

// Strip root from filenames
expect(
stdout.replace(regexp, match =>
Expand All @@ -173,6 +195,10 @@ describe('Check codebases', () => {

expect(stderr).toEqual('');

if (hasFix) {
expect(contentsAfter).toEqual(contentsExpected);
}

// Clean up
unlinkSync(configPath);
});
Expand Down

0 comments on commit 0e692aa

Please sign in to comment.