UNPKG

wct-istanbub

Version:

Instanbuljs coverage reporting for projects being tested by web-component-tester

169 lines (144 loc) 5.52 kB
const minimatch = require('minimatch'); const fs = require('fs'); const path = require('path'); const scriptHook = require('html-script-hook'); const polymerBuild = require('polymer-build'); const browserCapabilities = require('browser-capabilities'); const getCompileTarget = require('polyserve/lib/get-compile-target.js'); const istanbulInstrumenter = require('istanbul-lib-instrument'); const getPackageName = require('web-component-tester/runner/config.js').getPackageName; const defaultPlugins = [ 'importMeta', 'asyncGenerators', 'dynamicImport', 'objectRestSpread', 'optionalCatchBinding', 'flow', 'jsx' ]; // istanbul let instrumenter; // helpers let cache = {}; function createInstrumenter(plugins) { instrumenter = new istanbulInstrumenter.createInstrumenter({ autoWrap: true, coverageVariable: 'WCT.share.__coverage__', embedSource: true, compact: false, preserveComments: false, produceSourceMap: false, ignoreClassMethods: undefined, esModules: true, plugins: [...new Set(plugins ? defaultPlugins.concat(plugins) : defaultPlugins)] }); } function replaceCoverage(code) { return code.replace('coverage = global[gcv] || (global[gcv] = {});', 'coverage = global.WCT.share.__coverage__ || (global.WCT.share.__coverage__ = {});'); } function transform(req, body, packageName, filePath, npm, root, componentUrl, moduleResolution, isComponentRequestOverride) { const capabilities = browserCapabilities.browserCapabilities(req.get('user-agent')); const compileTarget = getCompileTarget.getCompileTarget(capabilities, 'auto'); const options = { compileTarget, transformModules: !capabilities.has('modules'), }; return polymerBuild.jsTransform(body, { compile: options.compileTarget, transformModulesToAmd: options.transformModules ? 'auto' : false, moduleResolution: moduleResolution ? moduleResolution : npm ? 'node' : 'none', filePath, isComponentRequest: isComponentRequestOverride === undefined ? req.baseUrl === componentUrl : isComponentRequestOverride, packageName, componentDir: npm ? path.join(root, 'node_modules') : path.join(root, 'bower_components'), rootDir: process.cwd() }); } function instrumentFile(path, req, html) { const asset = req.url; if (fs.existsSync(path)) { if (!cache[asset]) { code = fs.readFileSync(path, 'utf8'); cache[asset] = html ? scriptHook(code, { scriptCallback: instrumentScript }) : instrumenter.instrumentSync(code, path, getSourceMap(code, path)); } function instrumentScript(code) { return instrumenter.instrumentSync(code, path); } } else { return ''; } return cache[asset]; } /** * Try to get source map for given code * @param {string} code Code to get source map for * @param {string} path Path to code */ function getSourceMap(code, path) { let map = undefined; const mapMatch = /\/\/# sourceMappingURL=([^\s]+.js.map)$/.exec(code); if (mapMatch != null && path != null) { const mapPath = path.split('/').slice(0, -1).join('/') + '/' + mapMatch[1]; if (fs.existsSync(mapPath)) { try { const rawMap = fs.readFileSync(mapPath, 'utf8'); map = JSON.parse(rawMap); } catch (_) {} } } return map; } /** * Middleware that serves an instrumented asset based on user * configuration of coverage */ function coverageMiddleware(root, options, emitter) { options.root = options.root || process.cwd(); const basename = getPackageName(options); const basepath = path.join(emitter.options.clientOptions.root, basename); createInstrumenter(options.babelPlugins); return function (req, res, next) { let blacklist = options.exclude || ['**/test/**']; let whitelist = options.include || []; if (!options.ignoreBasePath) { blacklist = blacklist.map(x => path.join(basepath, x)); whitelist = whitelist.map(x => path.join(basepath, x)); } const re = new RegExp(`^\/[^/]+\/${basename.replace('/', '\/')}`); const absolutePath = req.url.replace(re, root); if (match(req.url, whitelist) && !match(req.url, blacklist)) { if (absolutePath.match(/\.(j|e)s$/)) { emitter.emit('log:debug', 'coverage', 'instrument', req.url); let code = instrumentFile(absolutePath, req); res.type('application/javascript'); return res.send(transform(req, replaceCoverage(code), basename, absolutePath, options.npm, root, emitter.options.clientOptions.root, options.moduleResolution, options.isComponentRequestOverride)); } else if (absolutePath.match(/\.htm(l)?$/)) { emitter.emit('log:debug', 'coverage', 'instrument', req.url); let html = instrumentFile(absolutePath, req, true); return res.send(replaceCoverage(html)); } emitter.emit('log:debug', 'coverage', 'skip whitelisted', req.url); return next(); } else { emitter.emit('log:debug', 'coverage', 'skip ', req.url); return next(); } }; } /** * Clears the instrumented code cache */ function cacheClear() { cache = {}; } /** * Returns true if the supplied string mini-matches any of the supplied patterns */ function match(str, rules) { return rules.some((rule) => minimatch(str, rule)); } module.exports = { middleware: coverageMiddleware, cacheClear: cacheClear }