Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: exclude lines that are missing from source maps #244

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion index.d.ts
Expand Up @@ -20,6 +20,6 @@ declare class V8ToIstanbul {
toIstanbul(): CoverageMapData
}

declare function v8ToIstanbul(scriptPath: string, wrapperLength?: number, sources?: Sources, excludePath?: (path: string) => boolean): V8ToIstanbul
declare function v8ToIstanbul(scriptPath: string, wrapperLength?: number, sources?: Sources, excludePath?: (path: string) => boolean, excludeEmptyLines?: boolean): V8ToIstanbul

export = v8ToIstanbul
4 changes: 2 additions & 2 deletions index.js
@@ -1,5 +1,5 @@
const V8ToIstanbul = require('./lib/v8-to-istanbul')

module.exports = function (path, wrapperLength, sources, excludePath) {
return new V8ToIstanbul(path, wrapperLength, sources, excludePath)
module.exports = function (path, wrapperLength, sources, excludePath, excludeEmptyLines) {
return new V8ToIstanbul(path, wrapperLength, sources, excludePath, excludeEmptyLines)
}
30 changes: 25 additions & 5 deletions lib/source.js
@@ -1,23 +1,31 @@
const CovLine = require('./line')
const { sliceRange } = require('./range')
const { originalPositionFor, generatedPositionFor, GREATEST_LOWER_BOUND, LEAST_UPPER_BOUND } = require('@jridgewell/trace-mapping')
const { originalPositionFor, generatedPositionFor, eachMapping, GREATEST_LOWER_BOUND, LEAST_UPPER_BOUND } = require('@jridgewell/trace-mapping')

module.exports = class CovSource {
constructor (sourceRaw, wrapperLength) {
constructor (sourceRaw, wrapperLength, traceMap) {
sourceRaw = sourceRaw ? sourceRaw.trimEnd() : ''
this.lines = []
this.eof = sourceRaw.length
this.shebangLength = getShebangLength(sourceRaw)
this.wrapperLength = wrapperLength - this.shebangLength
this._buildLines(sourceRaw)
this._buildLines(sourceRaw, traceMap)
}

_buildLines (source) {
_buildLines (source, traceMap) {
let position = 0
let ignoreCount = 0
let ignoreAll = false
const linesToCover = traceMap && this._parseLinesToCover(traceMap)

for (const [i, lineStr] of source.split(/(?<=\r?\n)/u).entries()) {
const line = new CovLine(i + 1, position, lineStr)
const lineNumber = i + 1
const line = new CovLine(lineNumber, position, lineStr)

if (linesToCover && !linesToCover.has(lineNumber)) {
line.ignore = true
}

if (ignoreCount > 0) {
line.ignore = true
ignoreCount--
Expand Down Expand Up @@ -125,6 +133,18 @@ module.exports = class CovSource {
if (this.lines[line - 1] === undefined) return this.eof
return Math.min(this.lines[line - 1].startCol + relCol, this.lines[line - 1].endCol)
}

_parseLinesToCover (traceMap) {
const linesToCover = new Set()

eachMapping(traceMap, (mapping) => {
if (mapping.originalLine !== null) {
linesToCover.add(mapping.originalLine)
}
})

return linesToCover
}
}

// this implementation is pulled over from istanbul-lib-sourcemap:
Expand Down
17 changes: 10 additions & 7 deletions lib/v8-to-istanbul.js
Expand Up @@ -25,12 +25,13 @@ const isNode8 = /^v8\./.test(process.version)
const cjsWrapperLength = isOlderNode10 ? require('module').wrapper[0].length : 0

module.exports = class V8ToIstanbul {
constructor (scriptPath, wrapperLength, sources, excludePath) {
constructor (scriptPath, wrapperLength, sources, excludePath, excludeEmptyLines) {
assert(typeof scriptPath === 'string', 'scriptPath must be a string')
assert(!isNode8, 'This module does not support node 8 or lower, please upgrade to node 10')
this.path = parsePath(scriptPath)
this.wrapperLength = wrapperLength === undefined ? cjsWrapperLength : wrapperLength
this.excludePath = excludePath || (() => false)
this.excludeEmptyLines = excludeEmptyLines === true
this.sources = sources || {}
this.generatedLines = []
this.branches = {}
Expand Down Expand Up @@ -58,8 +59,8 @@ module.exports = class V8ToIstanbul {
if (!this.sourceMap.sourcesContent) {
this.sourceMap.sourcesContent = await this.sourcesContentFromSources()
}
this.covSources = this.sourceMap.sourcesContent.map((rawSource, i) => ({ source: new CovSource(rawSource, this.wrapperLength), path: this.sourceMap.sources[i] }))
this.sourceTranspiled = new CovSource(rawSource, this.wrapperLength)
this.covSources = this.sourceMap.sourcesContent.map((rawSource, i) => ({ source: new CovSource(rawSource, this.wrapperLength, this.excludeEmptyLines ? this.sourceMap : null), path: this.sourceMap.sources[i] }))
this.sourceTranspiled = new CovSource(rawSource, this.wrapperLength, this.excludeEmptyLines ? this.sourceMap : null)
} else {
const candidatePath = this.rawSourceMap.sourcemap.sources.length >= 1 ? this.rawSourceMap.sourcemap.sources[0] : this.rawSourceMap.sourcemap.file
this.path = this._resolveSource(this.rawSourceMap, candidatePath || this.path)
Expand All @@ -82,8 +83,8 @@ module.exports = class V8ToIstanbul {
// We fallback to reading the original source from disk.
originalRawSource = await readFile(this.path, 'utf8')
}
this.covSources = [{ source: new CovSource(originalRawSource, this.wrapperLength), path: this.path }]
this.sourceTranspiled = new CovSource(rawSource, this.wrapperLength)
this.covSources = [{ source: new CovSource(originalRawSource, this.wrapperLength, this.excludeEmptyLines ? this.sourceMap : null), path: this.path }]
this.sourceTranspiled = new CovSource(rawSource, this.wrapperLength, this.excludeEmptyLines ? this.sourceMap : null)
}
} else {
this.covSources = [{ source: new CovSource(rawSource, this.wrapperLength), path: this.path }]
Expand Down Expand Up @@ -281,8 +282,10 @@ module.exports = class V8ToIstanbul {
s: {}
}
source.lines.forEach((line, index) => {
statements.statementMap[`${index}`] = line.toIstanbul()
statements.s[`${index}`] = line.ignore ? 1 : line.count
if (!line.ignore) {
statements.statementMap[`${index}`] = line.toIstanbul()
statements.s[`${index}`] = line.count
}
})
return statements
}
Expand Down
22 changes: 0 additions & 22 deletions tap-snapshots/test/v8-to-istanbul.js.test.cjs
Expand Up @@ -385,8 +385,6 @@ Object {
"31": 1,
"32": 1,
"33": 1,
"34": 1,
"35": 1,
"36": 1,
"37": 1,
"4": 1,
Expand Down Expand Up @@ -677,26 +675,6 @@ Object {
"line": 34,
},
},
"34": Object {
"end": Object {
"column": 22,
"line": 35,
},
"start": Object {
"column": 0,
"line": 35,
},
},
"35": Object {
"end": Object {
"column": 28,
"line": 36,
},
"start": Object {
"column": 0,
"line": 36,
},
},
"36": Object {
"end": Object {
"column": 1,
Expand Down
13 changes: 11 additions & 2 deletions test/fixtures/scripts/ignored.lines.js
@@ -1,4 +1,13 @@
/* c8 ignore next 3 */
function sum(a, b) {
if(a === 2 && b === 2) {
return 4;
}

/* v8 ignore start */ // Line 6
if (a === '10') {
return add(a, b)
}
/* v8 ignore stop */ // Line 10

return a + b;
};
};
95 changes: 95 additions & 0 deletions test/source.js
Expand Up @@ -2,6 +2,7 @@

const CovSource = require('../lib/source')
const { TraceMap } = require('@jridgewell/trace-mapping')
const assert = require('assert')

require('tap').mochaGlobals()
require('should')
Expand Down Expand Up @@ -68,6 +69,100 @@ describe('Source', () => {
})
source.offsetToOriginalRelative(sourceMap, 25, 97).should.deepEqual({})
})

it('ignores empty lines', () => {
const sourceRaw = `\
function add(a: number, b: number) {
/**
* Multi
* line
* comment
*/
if (a === 2 && b === 3) {
// This line should NOT be covered
return 5;
}

type TypescriptTypings = 1 | 2;

if (a === 1 && b === 1) {
// Comment
return 2;
}

interface MoreCompileTimeCode {
should: {
be: {
excluded: true;
};
};
}

return a + b;
}

add(2, 5);
`
const sourceMap = new TraceMap({
version: 3,
file: 'index.js',
sourceRoot: '',
sources: [
'../src/index.ts'
],
names: [],
mappings: ';AAAA,SAAS,GAAG,CAAC,CAAS,EAAE,CAAS;IAM/B,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAEvB,OAAO,CAAC,CAAC;IACX,CAAC;IAID,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAEvB,OAAO,CAAC,CAAC;IACX,CAAC;IAUD,OAAO,CAAC,GAAG,CAAC,CAAC;AACf,CAAC;AAED,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC'
}
)

const source = new CovSource(sourceRaw, 0, sourceMap)
const lines = source.lines

assert.equal(lines[0].ignore, false)

// Multiline comment
assert.equal(lines[1].ignore, true)
assert.equal(lines[2].ignore, true)
assert.equal(lines[3].ignore, true)
assert.equal(lines[4].ignore, true)
assert.equal(lines[5].ignore, true)

// If-block with inline comment
assert.equal(lines[6].ignore, false)
assert.equal(lines[7].ignore, true)
assert.equal(lines[8].ignore, false)
assert.equal(lines[9].ignore, false)
assert.equal(lines[10].ignore, true)

// Typescript Type
assert.equal(lines[11].ignore, true)

// If-block with inline comment
assert.equal(lines[12].ignore, true)
assert.equal(lines[13].ignore, false)
assert.equal(lines[14].ignore, true)
assert.equal(lines[15].ignore, false)
assert.equal(lines[16].ignore, false)
assert.equal(lines[17].ignore, true)

// Typescript interface
assert.equal(lines[18].ignore, true)
assert.equal(lines[19].ignore, true)
assert.equal(lines[20].ignore, true)
assert.equal(lines[21].ignore, true)
assert.equal(lines[22].ignore, true)
assert.equal(lines[23].ignore, true)
assert.equal(lines[24].ignore, true)
assert.equal(lines[25].ignore, true)

// Return statement + function closing
assert.equal(lines[26].ignore, false)
assert.equal(lines[27].ignore, false)

// Function call
assert.equal(lines[28].ignore, true)
assert.equal(lines[29].ignore, false)
})
})

for (const prefix of ['c8', 'v8']) {
Expand Down
16 changes: 14 additions & 2 deletions test/v8-to-istanbul.js
Expand Up @@ -140,7 +140,7 @@ ${'//'}${'#'} sourceMappingURL=data:application/json;base64,${base64Sourcemap}
Object.keys(v8ToIstanbul.toIstanbul()).should.eql(['/src/index.ts', '/src/utils.ts'].map(path.normalize))
})

it('ignore hint marks statements of uncovered file as covered', async () => {
it('ignore hint marks statements of uncovered file as excluded', async () => {
const filename = require.resolve('./fixtures/scripts/ignored.lines.js')
const source = readFileSync(filename, 'utf-8')
const v8ToIstanbul = new V8ToIstanbul(pathToFileURL(filename).href)
Expand All @@ -163,7 +163,19 @@ ${'//'}${'#'} sourceMappingURL=data:application/json;base64,${base64Sourcemap}
const coverageMap = v8ToIstanbul.toIstanbul()
const { s } = coverageMap[filename]

assert.deepStrictEqual(s, { 0: 1, 1: 1, 2: 1, 3: 1 })
assert.equal(s[0], 0)
assert.equal(s[1], 0)
assert.equal(s[2], 0)
assert.equal(s[3], 0)
assert.equal(s[4], 0)
assert.equal(s[5], undefined) // Line 6
assert.equal(s[6], undefined)
assert.equal(s[7], undefined)
assert.equal(s[8], undefined)
assert.equal(s[9], undefined) // Line 10
assert.equal(s[10], 0)
assert.equal(s[11], 0)
assert.equal(s[12], 0)
})
})

Expand Down