Skip to content

Commit

Permalink
add 'ignoreGuardStatements' option
Browse files Browse the repository at this point in the history
  • Loading branch information
oguimbal committed Oct 30, 2020
1 parent 1b52fe7 commit f8f74c6
Show file tree
Hide file tree
Showing 4 changed files with 291 additions and 6 deletions.
2 changes: 2 additions & 0 deletions packages/istanbul-lib-instrument/src/instrumenter.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import readInitialCoverage from './read-coverage';
* @param {boolean} [opts.autoWrap=false] set to true to allow `return` statements outside of functions.
* @param {boolean} [opts.produceSourceMap=false] set to true to produce a source map for the instrumented code.
* @param {Array} [opts.ignoreClassMethods=[]] set to array of class method names to ignore for coverage.
* @param {Array} [opts.ignoreGuardStatements=[]] ignore all guard statements. Can contain any of 'returns', 'literalReturns', 'identifierReturns', 'voidReturns', 'throws', 'continues', 'breaks'
* @param {Function} [opts.sourceMapUrlCallback=null] a callback function that is called when a source map URL
* is found in the original code. This function is called with the source file name and the source map URL.
* @param {boolean} [opts.debug=false] - turn debugging on
Expand Down Expand Up @@ -77,6 +78,7 @@ class Instrumenter {
coverageGlobalScopeFunc:
opts.coverageGlobalScopeFunc,
ignoreClassMethods: opts.ignoreClassMethods,
ignoreGuardStatements: opts.ignoreGuardStatements,
inputSourceMap
});

Expand Down
63 changes: 59 additions & 4 deletions packages/istanbul-lib-instrument/src/visitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ class VisitState {
types,
sourceFilePath,
inputSourceMap,
ignoreClassMethods = []
ignoreClassMethods = [],
ignoreGuardStatements = []
) {
this.varName = genVar(sourceFilePath);
this.attrs = {};
Expand All @@ -36,6 +37,7 @@ class VisitState {
this.cov.inputSourceMap(inputSourceMap);
}
this.ignoreClassMethods = ignoreClassMethods;
this.ignoreGuardStatements = ignoreGuardStatements;
this.types = types;
this.sourceMappingURL = null;
}
Expand Down Expand Up @@ -409,11 +411,62 @@ function convertArrowExpression(path) {
}
}

function isIgnoredGuard(n) {
if (!this.ignoreGuardStatements
|| n.type !== 'BlockStatement'
|| !Array.isArray(this.ignoreGuardStatements)
|| !this.ignoreGuardStatements.length) {
return false;
}
if (n.body.length !== 1) {
// only ignore simple bodies (signle statement)
return false;
}
const stm = n.body[0];
if (stm.type === 'ThrowStatement') {
// ignore throws
return this.ignoreGuardStatements.includes('throws');
}
if (stm.type === 'ContinueStatement') {
// ignore continues
return this.ignoreGuardStatements.includes('continues');
}
if (stm.type === 'BreakStatement') {
// ignore continues
return this.ignoreGuardStatements.includes('breaks');
}
if (stm.type === 'ReturnStatement') {
if (!this.ignoreGuardStatements) {
return false;
}
if (this.ignoreGuardStatements.includes('returns')) {
// ignore all returns
return true;
}
if (!stm.argument) {
return this.ignoreGuardStatements.includes('literalReturns')
|| this.ignoreGuardStatements.includes('voidReturns');
}
switch (stm.argument.type) {
case 'NumericLiteral':
case 'BooleanLiteral':
case 'StringLiteral':
case 'NullLiteral':
return this.ignoreGuardStatements.includes('literalReturns');
case 'Identifier':
return stm.argument.name === 'undefined' && this.ignoreGuardStatements.includes('literalReturns')
|| this.ignoreGuardStatements.includes('identifierReturns');

}
}
return false;
}

function coverIfBranches(path) {
const n = path.node;
const hint = this.hintFor(n);
const ignoreIf = hint === 'if';
const ignoreElse = hint === 'else';
const ignoreIf = hint === 'if' || isIgnoredGuard.call(this, n.consequent);
const ignoreElse = hint === 'else' || isIgnoredGuard.call(this, n.alternate);
const branch = this.cov.newBranch('if', n.loc);

if (ignoreIf) {
Expand Down Expand Up @@ -595,6 +648,7 @@ function shouldIgnoreFile(programNode) {
* @param {string} [opts.coverageGlobalScope=this] the global coverage variable scope.
* @param {boolean} [opts.coverageGlobalScopeFunc=true] use an evaluated function to find coverageGlobalScope.
* @param {Array} [opts.ignoreClassMethods=[]] names of methods to ignore by default on classes.
* @param {Array} [opts.ignoreGuardStatements=[]] ignore all guard statements. Can contain any of 'returns', 'literalReturns', 'identifierReturns', 'voidReturns', 'throws', 'continues', 'breaks'
* @param {object} [opts.inputSourceMap=undefined] the input source map, that maps the uninstrumented code back to the
* original code.
*/
Expand All @@ -608,7 +662,8 @@ function programVisitor(types, sourceFilePath = 'unknown.js', opts = {}) {
types,
sourceFilePath,
opts.inputSourceMap,
opts.ignoreClassMethods
opts.ignoreClassMethods,
opts.ignoreGuardStatements
);
return {
enter(path) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@ function instrument(code, inputSourceMap) {
};
}

const instrumented = instrument(`console.log('basic test');`);

it('should not alter already instrumented code', () => {
const instrumented = instrument(`console.log('basic test');`);
const result = instrument(instrumented.code, instrumented.sourceMap);
[instrumented, result].forEach(({ sourceMap }) => {
// XXX Ignore source-map difference caused by:
Expand Down
229 changes: 229 additions & 0 deletions packages/istanbul-lib-instrument/test/specs/ignored-guards.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
---
name: ignores throw
code: |
output = -1;
if (args[0] > args [1]) throw new Error('Error !')
instrumentOpts:
ignoreGuardStatements: ["throws"]
tests:
- name: default test
args: [10, 20]
out: -1
lines: { "1": 1, "2": 1 }
branches: { "0": [1] }
statements: { "0": 1, "1": 1 }

---
name: does not ignores throw
code: |
output = -1;
if (args[0] > args [1]) throw new Error('Error !')
instrumentOpts:
ignoreGuardStatements: ["returns"]
tests:
- name: default test
args: [10, 20]
out: -1
lines: { "1": 1, "2": 1 }
branches: { "0": [0, 1] }
statements: { "0": 1, "1": 1, "2": 0 }

---
name: ignores void return
code: |
output = -1;
if (args[0] > args [1]) return
instrumentOpts:
ignoreGuardStatements: ["voidReturns"]
tests:
- name: default test
args: [10, 20]
out: -1
lines: { "1": 1, "2": 1 }
branches: { "0": [1] }
statements: { "0": 1, "1": 1 }

---
name: ignores continue
code: |
output = 3;
while (--output>0) {
if (args[0] > args [1]) continue
}
instrumentOpts:
ignoreGuardStatements: ["continues"]
tests:
- name: default test
args: [10, 20]
out: 0
lines: { "1": 1, "2": 1, "3": 2 }
branches: { "0": [2] }
statements: { "0": 1, "1": 1, "2": 2 }

---
name: ignores break
code: |
output = 3;
while (--output>0) {
if (args[0] > args [1]) break
}
instrumentOpts:
ignoreGuardStatements: ["breaks"]
tests:
- name: default test
args: [10, 20]
out: 0
lines: { "1": 1, "2": 1, "3": 2 }
branches: { "0": [2] }
statements: { "0": 1, "1": 1, "2": 2 }


---
name: ignores numeric literal returns
code: |
output = -1;
if (args[0] > args [1]) return 0
instrumentOpts:
ignoreGuardStatements: ["literalReturns"]
tests:
- name: default test
args: [10, 20]
out: -1
lines: { "1": 1, "2": 1 }
branches: { "0": [1] }
statements: { "0": 1, "1": 1 }

---
name: does not ignore numeric literal returns
code: |
output = -1;
if (args[0] > args [1]) return 0
instrumentOpts:
ignoreGuardStatements: ["voidReturns"]
tests:
- name: default test
args: [10, 20]
out: -1
lines: { "1": 1, "2": 1 }
branches: { "0": [0, 1] }
statements: { "0": 1, "1": 1, "2": 0 }

---
name: ignores bool literal returns
code: |
output = -1;
if (args[0] > args [1]) return false
instrumentOpts:
ignoreGuardStatements: ["literalReturns"]
tests:
- name: default test
args: [10, 20]
out: -1
lines: { "1": 1, "2": 1 }
branches: { "0": [1] }
statements: { "0": 1, "1": 1 }

---
name: ignores string literal returns
code: |
output = -1;
if (args[0] > args [1]) return 'nope'
instrumentOpts:
ignoreGuardStatements: ["literalReturns"]
tests:
- name: default test
args: [10, 20]
out: -1
lines: { "1": 1, "2": 1 }
branches: { "0": [1] }
statements: { "0": 1, "1": 1 }

---
name: ignores null literal returns
code: |
output = -1;
if (args[0] > args [1]) return null
instrumentOpts:
ignoreGuardStatements: ["literalReturns"]
tests:
- name: default test
args: [10, 20]
out: -1
lines: { "1": 1, "2": 1 }
branches: { "0": [1] }
statements: { "0": 1, "1": 1 }

---
name: ignores undefined literal returns
code: |
output = -1;
if (args[0] > args [1]) return undefined
instrumentOpts:
ignoreGuardStatements: ["literalReturns"]
tests:
- name: default test
args: [10, 20]
out: -1
lines: { "1": 1, "2": 1 }
branches: { "0": [1] }
statements: { "0": 1, "1": 1 }

---
name: ignores identifier literal returns
code: |
output = -1;
if (args[0] > args [1]) return output
instrumentOpts:
ignoreGuardStatements: ["identifierReturns"]
tests:
- name: default test
args: [10, 20]
out: -1
lines: { "1": 1, "2": 1 }
branches: { "0": [1] }
statements: { "0": 1, "1": 1 }

---
name: ignores complex return
code: |
output = -1;
if (args[0] > args [1]) return args[0]
instrumentOpts:
ignoreGuardStatements: ["returns"]
tests:
- name: default test
args: [10, 20]
out: -1
lines: { "1": 1, "2": 1 }
branches: { "0": [1] }
statements: { "0": 1, "1": 1 }

---
name: does not ignore complex return
code: |
output = -1;
if (args[0] > args [1]) return args[0]
instrumentOpts:
ignoreGuardStatements: ["literalReturns"]
tests:
- name: default test
args: [10, 20]
out: -1
lines: { "1": 1, "2": 1 }
branches: { "0": [0, 1] }
statements: { "0": 1, "1": 1, "2": 0 }

---
name: simple if single line does not ignore statement
code: |
output = -1;
if (args[0] > args [1]) output = args[0];
instrumentOpts:
ignoreGuardStatements: ["returns"]
tests:
- name: default test
args: [10, 20]
out: -1
lines: { "1": 1, "2": 1 }
branches: { "0": [0, 1] }
statements: { "0": 1, "1": 1, "2": 0 }

0 comments on commit f8f74c6

Please sign in to comment.