Software zum Installieren eines Smart-Mirror Frameworks , zum Nutzen von hochschulrelevanten Informationen, auf einem Raspberry-Pi.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

v8-to-istanbul.js 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. const assert = require('assert')
  2. const convertSourceMap = require('convert-source-map')
  3. const { dirname, isAbsolute, join, resolve } = require('path')
  4. const { fileURLToPath } = require('url')
  5. const CovBranch = require('./branch')
  6. const CovFunction = require('./function')
  7. const CovSource = require('./source')
  8. const compatError = Error(`requires Node.js ${require('../package.json').engines.node}`)
  9. let readFile = () => { throw compatError }
  10. try {
  11. readFile = require('fs').promises.readFile
  12. } catch (_err) {
  13. // most likely we're on an older version of Node.js.
  14. }
  15. const { SourceMapConsumer } = require('source-map')
  16. const isOlderNode10 = /^v10\.(([0-9]\.)|(1[0-5]\.))/u.test(process.version)
  17. const isNode8 = /^v8\./.test(process.version)
  18. // Injected when Node.js is loading script into isolate pre Node 10.16.x.
  19. // see: https://github.com/nodejs/node/pull/21573.
  20. const cjsWrapperLength = isOlderNode10 ? require('module').wrapper[0].length : 0
  21. module.exports = class V8ToIstanbul {
  22. constructor (scriptPath, wrapperLength, sources, excludePath) {
  23. assert(typeof scriptPath === 'string', 'scriptPath must be a string')
  24. assert(!isNode8, 'This module does not support node 8 or lower, please upgrade to node 10')
  25. this.path = parsePath(scriptPath)
  26. this.wrapperLength = wrapperLength === undefined ? cjsWrapperLength : wrapperLength
  27. this.excludePath = excludePath || (() => false)
  28. this.sources = sources || {}
  29. this.generatedLines = []
  30. this.branches = {}
  31. this.functions = {}
  32. this.covSources = []
  33. this.rawSourceMap = undefined
  34. this.sourceMap = undefined
  35. this.sourceTranspiled = undefined
  36. // Indicate that this report was generated with placeholder data from
  37. // running --all:
  38. this.all = false
  39. }
  40. async load () {
  41. const rawSource = this.sources.source || await readFile(this.path, 'utf8')
  42. this.rawSourceMap = this.sources.sourceMap ||
  43. // if we find a source-map (either inline, or a .map file) we load
  44. // both the transpiled and original source, both of which are used during
  45. // the backflips we perform to remap absolute to relative positions.
  46. convertSourceMap.fromSource(rawSource) || convertSourceMap.fromMapFileSource(rawSource, dirname(this.path))
  47. if (this.rawSourceMap) {
  48. if (this.rawSourceMap.sourcemap.sources.length > 1) {
  49. this.sourceMap = await new SourceMapConsumer(this.rawSourceMap.sourcemap)
  50. this.covSources = this.sourceMap.sourcesContent.map((rawSource, i) => ({ source: new CovSource(rawSource, this.wrapperLength), path: this.sourceMap.sources[i] }))
  51. this.sourceTranspiled = new CovSource(rawSource, this.wrapperLength)
  52. } else {
  53. const candidatePath = this.rawSourceMap.sourcemap.sources.length >= 1 ? this.rawSourceMap.sourcemap.sources[0] : this.rawSourceMap.sourcemap.file
  54. this.path = this._resolveSource(this.rawSourceMap, candidatePath || this.path)
  55. this.sourceMap = await new SourceMapConsumer(this.rawSourceMap.sourcemap)
  56. let originalRawSource
  57. if (this.sources.sourceMap && this.sources.sourceMap.sourcemap && this.sources.sourceMap.sourcemap.sourcesContent && this.sources.sourceMap.sourcemap.sourcesContent.length === 1) {
  58. // If the sourcesContent field has been provided, return it rather than attempting
  59. // to load the original source from disk.
  60. // TODO: investigate whether there's ever a case where we hit this logic with 1:many sources.
  61. originalRawSource = this.sources.sourceMap.sourcemap.sourcesContent[0]
  62. } else if (this.sources.originalSource) {
  63. // Original source may be populated on the sources object.
  64. originalRawSource = this.sources.originalSource
  65. } else if (this.sourceMap.sourcesContent && this.sourceMap.sourcesContent[0]) {
  66. // perhaps we loaded sourcesContent was populated by an inline source map, or .map file?
  67. // TODO: investigate whether there's ever a case where we hit this logic with 1:many sources.
  68. originalRawSource = this.sourceMap.sourcesContent[0]
  69. } else {
  70. // We fallback to reading the original source from disk.
  71. originalRawSource = await readFile(this.path, 'utf8')
  72. }
  73. this.covSources = [{ source: new CovSource(originalRawSource, this.wrapperLength), path: this.path }]
  74. this.sourceTranspiled = new CovSource(rawSource, this.wrapperLength)
  75. }
  76. } else {
  77. this.covSources = [{ source: new CovSource(rawSource, this.wrapperLength), path: this.path }]
  78. }
  79. }
  80. destroy () {
  81. if (this.sourceMap) {
  82. this.sourceMap.destroy()
  83. this.sourceMap = undefined
  84. }
  85. }
  86. _resolveSource (rawSourceMap, sourcePath) {
  87. if (sourcePath.startsWith('file://')) {
  88. return fileURLToPath(sourcePath)
  89. }
  90. sourcePath = sourcePath.replace(/^webpack:\/\//, '')
  91. const sourceRoot = rawSourceMap.sourcemap.sourceRoot ? rawSourceMap.sourcemap.sourceRoot.replace('file://', '') : ''
  92. const candidatePath = join(sourceRoot, sourcePath)
  93. if (isAbsolute(candidatePath)) {
  94. return candidatePath
  95. } else {
  96. return resolve(dirname(this.path), candidatePath)
  97. }
  98. }
  99. applyCoverage (blocks) {
  100. blocks.forEach(block => {
  101. block.ranges.forEach((range, i) => {
  102. const { startCol, endCol, path, covSource } = this._maybeRemapStartColEndCol(range)
  103. if (this.excludePath(path)) {
  104. return
  105. }
  106. const lines = covSource.lines.filter(line => {
  107. // Upstream tooling can provide a block with the functionName
  108. // (empty-report), this will result in a report that has all
  109. // lines zeroed out.
  110. if (block.functionName === '(empty-report)') {
  111. line.count = 0
  112. this.all = true
  113. return true
  114. }
  115. return startCol < line.endCol && endCol >= line.startCol
  116. })
  117. const startLineInstance = lines[0]
  118. const endLineInstance = lines[lines.length - 1]
  119. if (block.isBlockCoverage && lines.length) {
  120. this.branches[path] = this.branches[path] || []
  121. // record branches.
  122. this.branches[path].push(new CovBranch(
  123. startLineInstance.line,
  124. startCol - startLineInstance.startCol,
  125. endLineInstance.line,
  126. endCol - endLineInstance.startCol,
  127. range.count
  128. ))
  129. // if block-level granularity is enabled, we we still create a single
  130. // CovFunction tracking object for each set of ranges.
  131. if (block.functionName && i === 0) {
  132. this.functions[path] = this.functions[path] || []
  133. this.functions[path].push(new CovFunction(
  134. block.functionName,
  135. startLineInstance.line,
  136. startCol - startLineInstance.startCol,
  137. endLineInstance.line,
  138. endCol - endLineInstance.startCol,
  139. range.count
  140. ))
  141. }
  142. } else if (block.functionName && lines.length) {
  143. this.functions[path] = this.functions[path] || []
  144. // record functions.
  145. this.functions[path].push(new CovFunction(
  146. block.functionName,
  147. startLineInstance.line,
  148. startCol - startLineInstance.startCol,
  149. endLineInstance.line,
  150. endCol - endLineInstance.startCol,
  151. range.count
  152. ))
  153. }
  154. // record the lines (we record these as statements, such that we're
  155. // compatible with Istanbul 2.0).
  156. lines.forEach(line => {
  157. // make sure branch spans entire line; don't record 'goodbye'
  158. // branch in `const foo = true ? 'hello' : 'goodbye'` as a
  159. // 0 for line coverage.
  160. //
  161. // All lines start out with coverage of 1, and are later set to 0
  162. // if they are not invoked; line.ignore prevents a line from being
  163. // set to 0, and is set if the special comment /* c8 ignore next */
  164. // is used.
  165. if (startCol <= line.startCol && endCol >= line.endCol && !line.ignore) {
  166. line.count = range.count
  167. }
  168. })
  169. })
  170. })
  171. }
  172. _maybeRemapStartColEndCol (range) {
  173. let covSource = this.covSources[0].source
  174. let startCol = Math.max(0, range.startOffset - covSource.wrapperLength)
  175. let endCol = Math.min(covSource.eof, range.endOffset - covSource.wrapperLength)
  176. let path = this.path
  177. if (this.sourceMap) {
  178. startCol = Math.max(0, range.startOffset - this.sourceTranspiled.wrapperLength)
  179. endCol = Math.min(this.sourceTranspiled.eof, range.endOffset - this.sourceTranspiled.wrapperLength)
  180. const { startLine, relStartCol, endLine, relEndCol, source } = this.sourceTranspiled.offsetToOriginalRelative(
  181. this.sourceMap,
  182. startCol,
  183. endCol
  184. )
  185. const matchingSource = this.covSources.find(covSource => covSource.path === source)
  186. covSource = matchingSource ? matchingSource.source : this.covSources[0].source
  187. path = matchingSource ? matchingSource.path : this.covSources[0].path
  188. // next we convert these relative positions back to absolute positions
  189. // in the original source (which is the format expected in the next step).
  190. startCol = covSource.relativeToOffset(startLine, relStartCol)
  191. endCol = covSource.relativeToOffset(endLine, relEndCol)
  192. }
  193. return {
  194. path,
  195. covSource,
  196. startCol,
  197. endCol
  198. }
  199. }
  200. getInnerIstanbul (source, path) {
  201. // We apply the "Resolving Sources" logic (as defined in
  202. // sourcemaps.info/spec.html) as a final step for 1:many source maps.
  203. // for 1:1 source maps, the resolve logic is applied while loading.
  204. //
  205. // TODO: could we move the resolving logic for 1:1 source maps to the final
  206. // step as well? currently this breaks some tests in c8.
  207. let resolvedPath = path
  208. if (this.rawSourceMap && this.rawSourceMap.sourcemap.sources.length > 1) {
  209. resolvedPath = this._resolveSource(this.rawSourceMap, path)
  210. }
  211. if (this.excludePath(resolvedPath)) {
  212. return
  213. }
  214. return {
  215. [resolvedPath]: {
  216. path: resolvedPath,
  217. all: this.all,
  218. ...this._statementsToIstanbul(source, path),
  219. ...this._branchesToIstanbul(source, path),
  220. ...this._functionsToIstanbul(source, path)
  221. }
  222. }
  223. }
  224. toIstanbul () {
  225. return this.covSources.reduce((istanbulOuter, { source, path }) => Object.assign(istanbulOuter, this.getInnerIstanbul(source, path)), {})
  226. }
  227. _statementsToIstanbul (source, path) {
  228. const statements = {
  229. statementMap: {},
  230. s: {}
  231. }
  232. source.lines.forEach((line, index) => {
  233. statements.statementMap[`${index}`] = line.toIstanbul()
  234. statements.s[`${index}`] = line.count
  235. })
  236. return statements
  237. }
  238. _branchesToIstanbul (source, path) {
  239. const branches = {
  240. branchMap: {},
  241. b: {}
  242. }
  243. this.branches[path] = this.branches[path] || []
  244. this.branches[path].forEach((branch, index) => {
  245. const srcLine = source.lines[branch.startLine - 1]
  246. const ignore = srcLine === undefined ? true : srcLine.ignore
  247. branches.branchMap[`${index}`] = branch.toIstanbul()
  248. branches.b[`${index}`] = [ignore ? 1 : branch.count]
  249. })
  250. return branches
  251. }
  252. _functionsToIstanbul (source, path) {
  253. const functions = {
  254. fnMap: {},
  255. f: {}
  256. }
  257. this.functions[path] = this.functions[path] || []
  258. this.functions[path].forEach((fn, index) => {
  259. const srcLine = source.lines[fn.startLine - 1]
  260. const ignore = srcLine === undefined ? true : srcLine.ignore
  261. functions.fnMap[`${index}`] = fn.toIstanbul()
  262. functions.f[`${index}`] = ignore ? 1 : fn.count
  263. })
  264. return functions
  265. }
  266. }
  267. function parsePath (scriptPath) {
  268. return scriptPath.startsWith('file://') ? fileURLToPath(scriptPath) : scriptPath
  269. }