gulp-validateImports.js 9.0 KB

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