index.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  1. 'use strict';
  2. const path = require('path');
  3. const { promisify } = require('util');
  4. const glob = promisify(require('glob'));
  5. const minimatch = require('minimatch');
  6. const { defaults } = require('@istanbuljs/schema');
  7. const isOutsideDir = require('./is-outside-dir');
  8. class TestExclude {
  9. constructor(opts = {}) {
  10. Object.assign(
  11. this,
  12. {relativePath: true},
  13. defaults.testExclude
  14. );
  15. for (const [name, value] of Object.entries(opts)) {
  16. if (value !== undefined) {
  17. this[name] = value;
  18. }
  19. }
  20. if (typeof this.include === 'string') {
  21. this.include = [this.include];
  22. }
  23. if (typeof this.exclude === 'string') {
  24. this.exclude = [this.exclude];
  25. }
  26. if (typeof this.extension === 'string') {
  27. this.extension = [this.extension];
  28. } else if (this.extension.length === 0) {
  29. this.extension = false;
  30. }
  31. if (this.include && this.include.length > 0) {
  32. this.include = prepGlobPatterns([].concat(this.include));
  33. } else {
  34. this.include = false;
  35. }
  36. if (
  37. this.excludeNodeModules &&
  38. !this.exclude.includes('**/node_modules/**')
  39. ) {
  40. this.exclude = this.exclude.concat('**/node_modules/**');
  41. }
  42. this.exclude = prepGlobPatterns([].concat(this.exclude));
  43. this.handleNegation();
  44. }
  45. /* handle the special case of negative globs
  46. * (!**foo/bar); we create a new this.excludeNegated set
  47. * of rules, which is applied after excludes and we
  48. * move excluded include rules into this.excludes.
  49. */
  50. handleNegation() {
  51. const noNeg = e => e.charAt(0) !== '!';
  52. const onlyNeg = e => e.charAt(0) === '!';
  53. const stripNeg = e => e.slice(1);
  54. if (Array.isArray(this.include)) {
  55. const includeNegated = this.include.filter(onlyNeg).map(stripNeg);
  56. this.exclude.push(...prepGlobPatterns(includeNegated));
  57. this.include = this.include.filter(noNeg);
  58. }
  59. this.excludeNegated = this.exclude.filter(onlyNeg).map(stripNeg);
  60. this.exclude = this.exclude.filter(noNeg);
  61. this.excludeNegated = prepGlobPatterns(this.excludeNegated);
  62. }
  63. shouldInstrument(filename, relFile) {
  64. if (
  65. this.extension &&
  66. !this.extension.some(ext => filename.endsWith(ext))
  67. ) {
  68. return false;
  69. }
  70. let pathToCheck = filename;
  71. if (this.relativePath) {
  72. relFile = relFile || path.relative(this.cwd, filename);
  73. // Don't instrument files that are outside of the current working directory.
  74. if (isOutsideDir(this.cwd, filename)) {
  75. return false;
  76. }
  77. pathToCheck = relFile.replace(/^\.[\\/]/, ''); // remove leading './' or '.\'.
  78. }
  79. const dot = { dot: true };
  80. const matches = pattern => minimatch(pathToCheck, pattern, dot);
  81. return (
  82. (!this.include || this.include.some(matches)) &&
  83. (!this.exclude.some(matches) || this.excludeNegated.some(matches))
  84. );
  85. }
  86. globSync(cwd = this.cwd) {
  87. const globPatterns = getExtensionPattern(this.extension || []);
  88. const globOptions = { cwd, nodir: true, dot: true };
  89. /* If we don't have any excludeNegated then we can optimize glob by telling
  90. * it to not iterate into unwanted directory trees (like node_modules). */
  91. if (this.excludeNegated.length === 0) {
  92. globOptions.ignore = this.exclude;
  93. }
  94. return glob
  95. .sync(globPatterns, globOptions)
  96. .filter(file => this.shouldInstrument(path.resolve(cwd, file)));
  97. }
  98. async glob(cwd = this.cwd) {
  99. const globPatterns = getExtensionPattern(this.extension || []);
  100. const globOptions = { cwd, nodir: true, dot: true };
  101. /* If we don't have any excludeNegated then we can optimize glob by telling
  102. * it to not iterate into unwanted directory trees (like node_modules). */
  103. if (this.excludeNegated.length === 0) {
  104. globOptions.ignore = this.exclude;
  105. }
  106. const list = await glob(globPatterns, globOptions);
  107. return list.filter(file => this.shouldInstrument(path.resolve(cwd, file)));
  108. }
  109. }
  110. function prepGlobPatterns(patterns) {
  111. return patterns.reduce((result, pattern) => {
  112. // Allow gitignore style of directory exclusion
  113. if (!/\/\*\*$/.test(pattern)) {
  114. result = result.concat(pattern.replace(/\/$/, '') + '/**');
  115. }
  116. // Any rules of the form **/foo.js, should also match foo.js.
  117. if (/^\*\*\//.test(pattern)) {
  118. result = result.concat(pattern.replace(/^\*\*\//, ''));
  119. }
  120. return result.concat(pattern);
  121. }, []);
  122. }
  123. function getExtensionPattern(extension) {
  124. switch (extension.length) {
  125. case 0:
  126. return '**';
  127. case 1:
  128. return `**/*${extension[0]}`;
  129. default:
  130. return `**/*{${extension.join()}}`;
  131. }
  132. }
  133. module.exports = TestExclude;