Skip to content

Commit

Permalink
feat!: support source map with multiple sources (#102)
Browse files Browse the repository at this point in the history
Fixes #21

BREAKING CHANGE: source map files with multiple sources will now be  parsed differently than  source map files with a single source.
  • Loading branch information
MoLow committed Aug 2, 2020
1 parent 50c08cd commit d1f435c
Show file tree
Hide file tree
Showing 12 changed files with 1,352 additions and 50 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -3,3 +3,4 @@ coverage
.nyc_output
node_modules
.idea
.vscode
2 changes: 1 addition & 1 deletion index.d.ts
Expand Up @@ -19,6 +19,6 @@ declare class V8ToIstanbul {
toIstanbul(): CoverageMapData
}

declare function v8ToIstanbul(scriptPath: string, wrapperLength?: number, sources?: Sources): V8ToIstanbul
declare function v8ToIstanbul(scriptPath: string, wrapperLength?: number, sources?: Sources, excludePath?: (path) => 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) {
return new V8ToIstanbul(path, wrapperLength, sources)
module.exports = function (path, wrapperLength, sources, excludePath) {
return new V8ToIstanbul(path, wrapperLength, sources, excludePath)
}
1 change: 1 addition & 0 deletions lib/source.js
Expand Up @@ -86,6 +86,7 @@ module.exports = class CovSource {
}

return {
source: start.source,
startLine: start.line,
relStartCol: start.column,
endLine: end.line,
Expand Down
100 changes: 62 additions & 38 deletions lib/v8-to-istanbul.js
Expand Up @@ -20,17 +20,18 @@ const isNode8 = /^v8\./.test(process.version)
const cjsWrapperLength = isOlderNode10 ? require('module').wrapper[0].length : 0

module.exports = class V8ToIstanbul {
constructor (scriptPath, wrapperLength, sources) {
constructor (scriptPath, wrapperLength, sources, excludePath) {
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.sources = sources || {}
this.generatedLines = []
this.branches = []
this.functions = []
this.branches = {}
this.functions = {}
this.covSources = []
this.sourceMap = undefined
this.source = undefined
this.sourceTranspiled = undefined
}

Expand All @@ -44,8 +45,9 @@ module.exports = class V8ToIstanbul {

if (rawSourceMap) {
if (rawSourceMap.sourcemap.sources.length > 1) {
console.warn('v8-to-istanbul: source-mappings from one to many files not yet supported')
this.source = new CovSource(rawSource, this.wrapperLength)
this.sourceMap = await new SourceMapConsumer(rawSourceMap.sourcemap)
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)
} else {
this._rewritePath(rawSourceMap)
this.sourceMap = await new SourceMapConsumer(rawSourceMap.sourcemap)
Expand All @@ -63,11 +65,11 @@ module.exports = class V8ToIstanbul {
originalRawSource = await readFile(this.path, 'utf8')
}

this.source = new CovSource(originalRawSource, this.wrapperLength)
this.covSources = [{ source: new CovSource(originalRawSource, this.wrapperLength), path: this.sourceMap.sources[0] }]
this.sourceTranspiled = new CovSource(rawSource, this.wrapperLength)
}
} else {
this.source = new CovSource(rawSource, this.wrapperLength)
this.covSources = [{ source: new CovSource(rawSource, this.wrapperLength), path: this.path }]
}
}

Expand All @@ -86,26 +88,28 @@ module.exports = class V8ToIstanbul {
applyCoverage (blocks) {
blocks.forEach(block => {
block.ranges.forEach((range, i) => {
const {
startCol,
endCol
} = this._maybeRemapStartColEndCol(range)
const lines = this.source.lines.filter(line => {
const { startCol, endCol, path, covSource } = this._maybeRemapStartColEndCol(range)
if (this.excludePath(path)) {
return
}
const lines = covSource.lines.filter(line => {
// Upstream tooling can provide a block with the functionName
// (empty-report), this will result in a report that has all
// lines zeroed out.
if (block.functionName === '(empty-report)') {
line.count = 0
return true
}

return startCol <= line.endCol && endCol >= line.startCol
})
const startLineInstance = lines[0]
const endLineInstance = lines[lines.length - 1]

if (block.isBlockCoverage && lines.length) {
this.branches[path] = this.branches[path] || []
// record branches.
this.branches.push(new CovBranch(
this.branches[path].push(new CovBranch(
startLineInstance.line,
startCol - startLineInstance.startCol,
endLineInstance.line,
Expand All @@ -116,7 +120,8 @@ module.exports = class V8ToIstanbul {
// if block-level granularity is enabled, we we still create a single
// CovFunction tracking object for each set of ranges.
if (block.functionName && i === 0) {
this.functions.push(new CovFunction(
this.functions[path] = this.functions[path] || []
this.functions[path].push(new CovFunction(
block.functionName,
startLineInstance.line,
startCol - startLineInstance.startCol,
Expand All @@ -126,8 +131,9 @@ module.exports = class V8ToIstanbul {
))
}
} else if (block.functionName && lines.length) {
this.functions[path] = this.functions[path] || []
// record functions.
this.functions.push(new CovFunction(
this.functions[path].push(new CovFunction(
block.functionName,
startLineInstance.line,
startCol - startLineInstance.startCol,
Expand All @@ -148,6 +154,7 @@ module.exports = class V8ToIstanbul {
// if they are not invoked; line.ignore prevents a line from being
// set to 0, and is set if the special comment /* c8 ignore next */
// is used.

if (startCol <= line.startCol && endCol >= line.endCol && !line.ignore) {
line.count = range.count
}
Expand All @@ -157,75 +164,92 @@ module.exports = class V8ToIstanbul {
}

_maybeRemapStartColEndCol (range) {
let startCol = Math.max(0, range.startOffset - this.source.wrapperLength)
let endCol = Math.min(this.source.eof, range.endOffset - this.source.wrapperLength)
let covSource = this.covSources[0].source
let startCol = Math.max(0, range.startOffset - covSource.wrapperLength)
let endCol = Math.min(covSource.eof, range.endOffset - covSource.wrapperLength)
let path = this.path

if (this.sourceMap) {
startCol = Math.max(0, range.startOffset - this.sourceTranspiled.wrapperLength)
endCol = Math.min(this.sourceTranspiled.eof, range.endOffset - this.sourceTranspiled.wrapperLength)

const { startLine, relStartCol, endLine, relEndCol } = this.sourceTranspiled.offsetToOriginalRelative(
const { startLine, relStartCol, endLine, relEndCol, source } = this.sourceTranspiled.offsetToOriginalRelative(
this.sourceMap,
startCol,
endCol
)

const matchingSource = this.covSources.find(covSource => covSource.path === source)
covSource = matchingSource ? matchingSource.source : this.covSources[0].source
path = matchingSource ? matchingSource.path : this.covSources[0].path;

// next we convert these relative positions back to absolute positions
// in the original source (which is the format expected in the next step).
startCol = this.source.relativeToOffset(startLine, relStartCol)
endCol = this.source.relativeToOffset(endLine, relEndCol)
startCol = covSource.relativeToOffset(startLine, relStartCol)
endCol = covSource.relativeToOffset(endLine, relEndCol)
}

return {
path,
covSource,
startCol,
endCol
}
}

getInnerIstanbul (source, path) {
if (this.excludePath(path)) {
return
}

return {
[path]: {
path,
...this._statementsToIstanbul(source, path),
...this._branchesToIstanbul(source, path),
...this._functionsToIstanbul(source, path)
}
}
}

toIstanbul () {
const istanbulInner = Object.assign(
{ path: this.path },
this._statementsToIstanbul(),
this._branchesToIstanbul(),
this._functionsToIstanbul()
)
const istanbulOuter = {}
istanbulOuter[this.path] = istanbulInner
return istanbulOuter
return this.covSources.reduce((istanbulOuter, { source, path }) => Object.assign(istanbulOuter, this.getInnerIstanbul(source, path)), {})
}

_statementsToIstanbul () {
_statementsToIstanbul (source, path) {
const statements = {
statementMap: {},
s: {}
}
this.source.lines.forEach((line, index) => {
source.lines.forEach((line, index) => {
statements.statementMap[`${index}`] = line.toIstanbul()
statements.s[`${index}`] = line.count
})
return statements
}

_branchesToIstanbul () {
_branchesToIstanbul (source, path) {
const branches = {
branchMap: {},
b: {}
}
this.branches.forEach((branch, index) => {
const ignore = this.source.lines[branch.startLine - 1].ignore
this.branches[path] = this.branches[path] || []
this.branches[path].forEach((branch, index) => {
const ignore = source.lines[branch.startLine - 1].ignore
branches.branchMap[`${index}`] = branch.toIstanbul()
branches.b[`${index}`] = [ignore ? 1 : branch.count]
})
return branches
}

_functionsToIstanbul () {
_functionsToIstanbul (source, path) {
const functions = {
fnMap: {},
f: {}
}
this.functions.forEach((fn, index) => {
const ignore = this.source.lines[fn.startLine - 1].ignore
this.functions[path] = this.functions[path] || []
this.functions[path].forEach((fn, index) => {
const ignore = source.lines[fn.startLine - 1].ignore
functions.fnMap[`${index}`] = fn.toIstanbul()
functions.f[`${index}`] = ignore ? 1 : fn.count
})
Expand Down

0 comments on commit d1f435c

Please sign in to comment.