Skip to content

Commit

Permalink
feat: exclude lines that are missing from source maps
Browse files Browse the repository at this point in the history
  • Loading branch information
AriPerkkio committed Mar 28, 2024
1 parent 43c85d1 commit fe075af
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 13 deletions.
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
11 changes: 6 additions & 5 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
96 changes: 96 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,101 @@ 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

0 comments on commit fe075af

Please sign in to comment.