index.js 7.28 KB
const path = require('path')
import { realpathSync } from 'fs'
import { execFileSync, execSync } from 'child_process'
import { declare } from '@babel/helper-plugin-utils'
// import { programVisitor } from './istanlibment'
import { programVisitor } from 'istanbul-lib-instrument'
import TestExclude from 'test-exclude'
import schema from '@istanbuljs/schema'
import parseDiffData from 'parse-diff'

function getRealpath(n) {
  try {
    return realpathSync(n) || /* istanbul ignore next */ n
  } catch (e) {
    /* istanbul ignore next */
    return n
  }
}

const memoize = new Map()
/* istanbul ignore next */
const memosep = path.sep === '/' ? ':' : ';'

function loadNycConfig (cwd, opts) {
  let memokey = cwd
  const args = [
    path.resolve(__dirname, 'load-nyc-config-sync.js'),
    cwd
  ]

  if ('nycrcPath' in opts) {
    args.push(opts.nycrcPath)

    memokey += memosep + opts.nycrcPath
  }

  /* execFileSync is expensive, avoid it if possible! */
  if (memoize.has(memokey)) {
    return memoize.get(memokey)
  }

  const result = JSON.parse(execFileSync(process.execPath, args))
  const error = result['load-nyc-config-sync-error']
  if (error) {
    throw new Error(error)
  }

  const config = {
    ...schema.defaults.babelPluginIstanbul,
    cwd,
    ...result
  }
  memoize.set(memokey, config)
  return config
}

function findConfig (opts) {
  const cwd = getRealpath(opts.cwd || process.env.NYC_CWD || /* istanbul ignore next */ process.cwd())
  const keys = Object.keys(opts)
  const ignored = Object.keys(opts).filter(s => s === 'nycrcPath' || s === 'cwd')
  if (keys.length > ignored.length) {
    // explicitly configuring options in babel
    // takes precedence.
    return {
      ...schema.defaults.babelPluginIstanbul,
      cwd,
      ...opts
    }
  }

  if (ignored.length === 0 && process.env.NYC_CONFIG) {
    // defaults were already applied by nyc
    return JSON.parse(process.env.NYC_CONFIG)
  }

  return loadNycConfig(cwd, opts)
}

function makeShouldSkip () {
  let exclude

  return function shouldSkip (file, nycConfig) {
    if (!exclude || (exclude.cwd !== nycConfig.cwd)) {
      exclude = new TestExclude({
        cwd: nycConfig.cwd,
        include: nycConfig.include,
        exclude: nycConfig.exclude,
        extension: nycConfig.extension,
        // Make sure this is true unless explicitly set to `false`. `undefined` is still `true`.
        excludeNodeModules: nycConfig.excludeNodeModules !== false
      })
    }

    return !exclude.shouldInstrument(file)
  }
}
export default declare(api => {
  api.assertVersion(7)
  const shouldSkip = makeShouldSkip()
  const t = api.types
  return {
    visitor: {
      Program: {
        enter(path) {
          // 筛选后的数据增量文件
          let diffScreen = null
          this.__dv__ = null
          this.nycConfig = findConfig(this.opts)
          const realPath = getRealpath(this.file.opts.filename)
          const { instrmenttation } = this.nycConfig // npm引入参数
          let IS_PITCHING_PILE_DATA = false
          let INCREMENT_DATA = false
          let BRANCH_DATA = 'origin/master'
          let processData = {}
          Object.keys(process.env).forEach(item => {
            let isItem =
              typeof process.env[item] === 'string' ? Boolean(process.env[item]) : process.env[item]
            if (item.indexOf('INCREMENT')) {
              processData.INCREMENT = isItem
            }
            if (item.indexOf('IS_PITCHING_PILE')) {
              processData.IS_PITCHING_PILE = isItem
            }
            if (item.indexOf('PROSRC')) {
              processData.PROSRC = process.env[item]
            }
            // Object.keys(processData).length
          })
          // 全局变量优先
          if (Object.keys(processData).length) {
            BRANCH_DATA = processData.BRANCH
            INCREMENT_DATA = processData.INCREMENT
            IS_PITCHING_PILE_DATA = processData.IS_PITCHING_PILE
          } else if (Object.keys(instrmenttation).length) {
            BRANCH_DATA = instrmenttation.BRANCH
            INCREMENT_DATA = instrmenttation.INCREMENT
            IS_PITCHING_PILE_DATA = instrmenttation.IS_PITCHING_PILE
          }
          // instrumenttation (branch: git diff 跟那个分支对比, increment 是否开启增量代码检测) 拿配置到底是全量代码还是增量代码
          if (!IS_PITCHING_PILE_DATA) {
            return
          }
          // const changeList = []
          // 下面的是零时的
          // const gitDiffCode = execSync(`git diff ${branch}`)
          // const diffData = parseDiffData(gitDiffCode.toString())
          // diffScreen = diffData.find(item => {
          //   return realPath.indexOf(item.to) > -1
          // })
          // 后面解开
          if (INCREMENT_DATA && BRANCH_DATA) {
            const gitDiffCode = execSync(`git diff ${BRANCH_DATA}`)
            const diffData = parseDiffData(gitDiffCode.toString())
            diffScreen = diffData.find(item => {
              return realPath.indexOf(item.to) > -1
            })
          }
          if (shouldSkip(realPath, this.nycConfig)) {
            return
          }
          let { inputSourceMap } = this.opts
          if (this.opts.useInlineSourceMaps) {
            if (!inputSourceMap && this.file.inputMap) {
              inputSourceMap = this.file.inputMap.sourcemap
            }
          }
          const visitorOptions = {}
          Object.entries(schema.defaults.instrumentVisitor).forEach(([name, defaultValue]) => {
            if (name in this.nycConfig) {
              visitorOptions[name] = this.nycConfig[name]
            } else {
              visitorOptions[name] = schema.defaults.instrumentVisitor[name]
            }
          })
          // 后增加|| !increment这个条件判断条件后面加上
            // if (diffScreen !== null && increment) {
              // if (diffScreen) {
              // if (Array.isArray(diffScreen && diffScreen.chunks)) {
              //   diffScreen.chunks.forEach(item => {
              //     let changes = Array.isArray(item.changes) ? item.changes : []
              //     changes.forEach(items => {
              //       if (items && items.add) {
              //         changeList.push(items.ln)
              //       }
              //     })
              //   })
              // }
              // changeList
              this.__dv__ = programVisitor(t, realPath, {
                ...visitorOptions,
                inputSourceMap
              })
              this.__dv__.enter(path)
            // } else {
            //   this.__dv__ = programVisitor(t, realPath, {
            //     ...visitorOptions,
            //     inputSourceMap
            //   })
            //   this.__dv__.enter(path)
            // }
        },
        exit(path) {
          if (!this.__dv__) {
            return
          }
          const result = this.__dv__.exit(path)
          if (this.opts.onCover) {
            this.opts.onCover(getRealpath(this.file.opts.filename), result.fileCoverage)
          }
        }
      }
    }
  }
})

// 增量代码babel引入方法 babel.js 或 babel.config.js
// 'plugins': [
//   ['@hliang/babel-plugin-transfrom-modules-commonjs', {
//     extension: ['.js', '.vue'],
//     instrmenttation: {
//       increment: true, // 是否全量插桩
//       branch: 'origin/master' // 对比分支
//     }
//   }]
// ]