index.js 5.1 KB

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