gulp-validateImports.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. // Gulp Tools
  2. var fs = require("fs");
  3. var path = require("path");
  4. var through = require('through2');
  5. var PluginError = require('plugin-error');
  6. var colorConsole = require("../../NodeHelpers/colorConsole");
  7. var config = require("../../Config/config");
  8. const indexExlclusion = ["States", "EmitterTypes"];
  9. const forbiddenImports = ["meshBuilder"];
  10. const mapping = { };
  11. config.modules.forEach(moduleName => {
  12. mapping[config[moduleName].build.umd.packageName] = moduleName;
  13. });
  14. var validatePath = function(fileLocation, directory, module, lineNumber, errors, isExport) {
  15. let expressionType = isExport ? "Export" : "Import";
  16. let internalModulePath = path.join(directory, module + ".ts");
  17. // Check .ts path.
  18. if (!fs.existsSync(internalModulePath)) {
  19. internalModulePath = path.join(directory, module + ".tsx");
  20. // Check .tsx path.
  21. if (!fs.existsSync(internalModulePath)) {
  22. // If not found, check index.ts for legacy and index files.
  23. if (fileLocation.indexOf("legacy") > -1 || fileLocation.indexOf("index") > -1) {
  24. internalModulePath = path.join(directory, module, "index.ts");
  25. if (!fs.existsSync(internalModulePath)) {
  26. errors.push(`Line ${lineNumber} ${expressionType} from folder only allows if index is present. ${module}`);
  27. }
  28. }
  29. else {
  30. errors.push(`Line ${lineNumber} ${expressionType}s ${module} needs to be full path (not from directory) for tree shaking.`);
  31. }
  32. }
  33. }
  34. if (internalModulePath.indexOf("index.") > -1) {
  35. if (fileLocation.indexOf("legacy") === -1) {
  36. if (isExport) {
  37. internalModulePath = path.join(directory, module + ".ts");
  38. // Check .ts path.
  39. if (!fs.existsSync(internalModulePath)) {
  40. errors.push(`Line ${lineNumber} Exports ${module} should be from the full path including index.`);
  41. }
  42. }
  43. else {
  44. let excluded = false;
  45. for (let exclusion of indexExlclusion) {
  46. if (internalModulePath.indexOf(exclusion) > -1) {
  47. excluded = true;
  48. break;
  49. }
  50. }
  51. if (!excluded && fileLocation.indexOf("index.ts") === -1) {
  52. errors.push(`Line ${lineNumber} Imports ${module} should not be from index for tree shaking.`);
  53. }
  54. }
  55. }
  56. }
  57. if (!isExport) {
  58. for (let forbiddenImport of forbiddenImports) {
  59. if (module.endsWith(forbiddenImport)) {
  60. errors.push(`Line ${lineNumber} ${expressionType}s ${module} is forbidden for tree shaking.`);
  61. }
  62. }
  63. }
  64. }
  65. var validateImports = function(data, fileLocation, options) {
  66. var str = "" + data;
  67. var errors = [];
  68. // Start process by extracting all lines.
  69. let lines = str.split('\n');
  70. // Let's go line by line and check if we have special folder replacements
  71. // Replaces declare module '...'; by declare module 'babylonjs/...'; for instance
  72. for (let index = 0; index < lines.length; index++) {
  73. let line = lines[index];
  74. let module = null, externalModule = null;
  75. // Find Imports.
  76. if (line.indexOf("import") > -1) {
  77. let regexTypeImport = new RegExp(`import .* from ['"](.*)['"];`, "g");
  78. let match = regexTypeImport.exec(line);
  79. if (match) {
  80. module = match[1];
  81. }
  82. else {
  83. let regexSideEffectImport = new RegExp(`import \\(*['"](.*)['"]\\)*;`, "g");
  84. let matchSideEffects = regexSideEffectImport.exec(line);
  85. if (matchSideEffects) {
  86. module = matchSideEffects[1];
  87. }
  88. else {
  89. continue;
  90. }
  91. }
  92. // Checks if line is about external module
  93. if (options.externals) {
  94. for (let ext in options.externals) {
  95. if (line.indexOf(ext) > -1) {
  96. externalModule = ext;
  97. break;
  98. }
  99. }
  100. }
  101. // Check if path is correct internal.
  102. if (externalModule) {
  103. const splitter = module.indexOf("/");
  104. const baseModule = module.substring(0, splitter);
  105. if (mapping[baseModule]) {
  106. const configName = mapping[baseModule];
  107. const directory = config[configName].computed.srcDirectory;
  108. module = module.substring(splitter);
  109. validatePath(fileLocation, directory, module, index + 1, errors, false);
  110. }
  111. }
  112. else {
  113. // Check Relative.
  114. if (!module.startsWith(".")) {
  115. errors.push(`Line ${index + 1} Import ${module} needs to be relative.`);
  116. }
  117. else {
  118. const directory = path.dirname(fileLocation);
  119. validatePath(fileLocation, directory, module, index + 1, errors, false);
  120. }
  121. }
  122. }
  123. // Find Exports.
  124. if (options.isCore && line.indexOf("export") > -1) {
  125. let regexTypeExport = new RegExp(`export .* from ['"](.*)['"];`, "g");
  126. let match = regexTypeExport.exec(line);
  127. if (match) {
  128. module = match[1];
  129. // Checks if line is about external module
  130. if (options.externals) {
  131. for (let ext in options.externals) {
  132. if (line.indexOf(ext) > -1) {
  133. externalModule = ext;
  134. break;
  135. }
  136. }
  137. }
  138. // Check if path is correct internal.
  139. if (externalModule) {
  140. const splitter = module.indexOf("/");
  141. const baseModule = module.substring(0, splitter);
  142. if (mapping[baseModule]) {
  143. const configName = mapping[baseModule];
  144. const directory = config[configName].computed.srcDirectory;
  145. module = module.substring(splitter);
  146. validatePath(fileLocation, directory, module, index + 1, errors, true);
  147. }
  148. }
  149. else {
  150. // Check Relative.
  151. if (!module.startsWith(".")) {
  152. errors.push(`Line ${index + 1} Export ${module} needs to be relative.`);
  153. }
  154. else {
  155. const directory = path.dirname(fileLocation);
  156. validatePath(fileLocation, directory, module, index + 1, errors, true);
  157. }
  158. }
  159. }
  160. }
  161. }
  162. return errors;
  163. }
  164. function gulpValidateImports(options) {
  165. var globalErrors = [];
  166. return through.obj(function (file, enc, cb) {
  167. if (file.isNull()) {
  168. cb(null, file);
  169. return;
  170. }
  171. if (file.isStream()) {
  172. cb(new PluginError("Validate imports", "Streaming not supported."));
  173. }
  174. let data = file.contents.toString();
  175. let result = validateImports(data, file.path, options);
  176. if (result.length > 0) {
  177. for (let error of result) {
  178. globalErrors.push({
  179. message: error,
  180. path: file.path
  181. });
  182. }
  183. }
  184. return cb();
  185. },
  186. function endStream(cb) {
  187. if (globalErrors.length > 0) {
  188. for (let error of globalErrors) {
  189. colorConsole.error(error.message + " " + error.path);
  190. }
  191. colorConsole.error(`Import validation failed with ${globalErrors.length} errors.`);
  192. var finalMessage = new PluginError('gulp-validateImports', `gulp-validateImports: ${globalErrors.length} errors found.`);
  193. this.emit('error', finalMessage);
  194. }
  195. cb();
  196. });
  197. }
  198. module.exports = gulpValidateImports;