gulp-validateImports.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  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. mainSearch:
  73. for (let index = 0; index < lines.length; index++) {
  74. let line = lines[index];
  75. let module = null, externalModule = null;
  76. // Find Imports.
  77. if (line.indexOf("import") > -1) {
  78. let regexTypeImport = new RegExp(`import .* from ['"](.*)['"];`, "g");
  79. let match = regexTypeImport.exec(line);
  80. if (match) {
  81. module = match[1];
  82. }
  83. else {
  84. let regexSideEffectImport = new RegExp(`import \\(*['"](.*)['"]\\)*;`, "g");
  85. let matchSideEffects = regexSideEffectImport.exec(line);
  86. if (matchSideEffects) {
  87. module = matchSideEffects[1];
  88. }
  89. else {
  90. continue;
  91. }
  92. }
  93. // Checks if line is about external module
  94. if (options.externals) {
  95. for (let ext in options.externals) {
  96. if (line.indexOf(ext) > -1) {
  97. externalModule = ext;
  98. break;
  99. }
  100. }
  101. }
  102. if (options.uncheckedLintImports) {
  103. for (let ext of options.uncheckedLintImports) {
  104. if (line.indexOf(ext) > -1) {
  105. continue mainSearch;
  106. }
  107. }
  108. }
  109. // Check if path is correct internal.
  110. if (externalModule) {
  111. const splitter = module.indexOf("/");
  112. if (splitter === -1 && module !== "babylonjs-gltf2interface") {
  113. errors.push(`Line ${index + 1} Import ${module} needs to be relative.`);
  114. }
  115. const baseModule = module.substring(0, splitter);
  116. if (mapping[baseModule]) {
  117. const configName = mapping[baseModule];
  118. const directory = config[configName].computed.srcDirectory;
  119. module = module.substring(splitter);
  120. validatePath(fileLocation, directory, module, index + 1, errors, false);
  121. }
  122. }
  123. else {
  124. // Check Relative.
  125. if (!module.startsWith(".")) {
  126. errors.push(`Line ${index + 1} Import ${module} needs to be relative.`);
  127. }
  128. else {
  129. const directory = path.dirname(fileLocation);
  130. validatePath(fileLocation, directory, module, index + 1, errors, false);
  131. }
  132. }
  133. }
  134. // Find Exports.
  135. if (options.isCore && line.indexOf("export") > -1) {
  136. let regexTypeExport = new RegExp(`export .* from ['"](.*)['"];`, "g");
  137. let match = regexTypeExport.exec(line);
  138. if (match) {
  139. module = match[1];
  140. // Checks if line is about external module
  141. if (options.externals) {
  142. for (let ext in options.externals) {
  143. if (line.indexOf(ext) > -1) {
  144. externalModule = ext;
  145. break;
  146. }
  147. }
  148. }
  149. // Check if path is correct internal.
  150. if (externalModule) {
  151. const splitter = module.indexOf("/");
  152. const baseModule = module.substring(0, splitter);
  153. if (mapping[baseModule]) {
  154. const configName = mapping[baseModule];
  155. const directory = config[configName].computed.srcDirectory;
  156. module = module.substring(splitter);
  157. validatePath(fileLocation, directory, module, index + 1, errors, true);
  158. }
  159. }
  160. else {
  161. // Check Relative.
  162. if (!module.startsWith(".")) {
  163. errors.push(`Line ${index + 1} Export ${module} needs to be relative.`);
  164. }
  165. else {
  166. const directory = path.dirname(fileLocation);
  167. validatePath(fileLocation, directory, module, index + 1, errors, true);
  168. }
  169. }
  170. }
  171. }
  172. }
  173. return errors;
  174. }
  175. function gulpValidateImports(options) {
  176. var globalErrors = [];
  177. return through.obj(function(file, enc, cb) {
  178. if (file.isNull()) {
  179. cb(null, file);
  180. return;
  181. }
  182. if (file.isStream()) {
  183. cb(new PluginError("Validate imports", "Streaming not supported."));
  184. }
  185. let data = file.contents.toString();
  186. let result = validateImports(data, file.path, options);
  187. if (result.length > 0) {
  188. for (let error of result) {
  189. globalErrors.push({
  190. message: error,
  191. path: file.path
  192. });
  193. }
  194. }
  195. return cb();
  196. },
  197. function endStream(cb) {
  198. if (globalErrors.length > 0) {
  199. for (let error of globalErrors) {
  200. colorConsole.error(error.message + " " + error.path);
  201. }
  202. colorConsole.error(`Import validation failed with ${globalErrors.length} errors.`);
  203. var finalMessage = new PluginError('gulp-validateImports', `gulp-validateImports: ${globalErrors.length} errors found.`);
  204. this.emit('error', finalMessage);
  205. }
  206. cb();
  207. });
  208. }
  209. module.exports = gulpValidateImports;