UNPKG

19.7 kBJavaScriptView Raw
1var fileAndFolderUtils = require('../file-and-folder-utils');
2var SlJsInfra = require('sl-js-infra').SlJsInfra;
3var IgnoredFilesHandler = SlJsInfra.IgnoredFilesHandler;
4var glob = require("glob");
5var path = require('path');
6var fs = require('fs');
7var ActualFile = require('../file-system/actual-file');
8var utils = require('../utils');
9var consoleFileLogger = require("../sl-logger.js").consoleFileLogger;
10var FileSignature = require('./file-signature.js');
11var GeneratedFileSignature = require('./generated-file-signature');
12var micromatch = require('micromatch');
13var MarkupFilesParser = require('../markup-files-parser');
14var DefaultValuesResolver = require('../default-values-resolver');
15var EntitiesMapper = require("../entities-mapper");
16var SourceDataEnricher = require("./source-data-enricher");
17var globalErrorHandler = require('../global-error-handler');
18var globalMethodIndexer = require("./globalMethodIndexer");
19
20
21var BRANCHES_KEY = "branches";
22var BRANCH_ELEMENT_TYPE = "branch";
23var METHODS_KEY = "methods";
24var METHOD_ELEMENT_TYPE = "method";
25var METHOD_INDEX_FOR_GLOBAL_BRANCHES = -1;
26
27// TODO: refactor this class and difine the exec responsibility.
28function FilesMapping(handlerParams, slMapper, agentEventsController, logger) {
29 this.handlerParams = handlerParams || {};
30 this.options = this.handlerParams.buildArguments;
31 this.finalOutput = this.handlerParams.finalOutput;
32 this.matchedFiles = [];
33 this.fileDataByFileName = {};
34 this.fileSignatures = [];
35 this.handlerParams.processedFiles = [];
36 this.erroredFiles = []; //Relative names
37 this.finalFilesData = [];
38 this.logger = logger;
39 this.log = this._createLogger(logger, "FilesMapping");
40 this.agentEventsController = agentEventsController;
41 this.slIgnoreScan = this._createSlIgnore(IgnoredFilesHandler.SCAN_IGNORE_FILE_NAME);
42 this.slIgnoreGenerated = this._createSlIgnore(IgnoredFilesHandler.GENERATED_IGNORE_FILE_NAME);
43 this.projectRoot = this._resolveProjectRoot();
44 this.shortNameToFileMapper = null;
45 this.slMapper = slMapper;
46 this.counters = {
47 methods: 0,
48 branches: 0
49 };
50}
51
52FilesMapping.prototype.process = function () {
53 var context = this
54 var promise = new Promise(function (resolve) {
55 context.log.info("Starting to scan the workspace.");
56 context._scanWorkspaceForIncludedFilesAndPopulateFilesArray();
57 context.matchedFiles.forEach(function (matchedFile, fileIdx) {
58 var isLastFile = (fileIdx == context.matchedFiles.length - 1);
59 context._fillFilesData(matchedFile, fileIdx, isLastFile);
60 });
61 context.fileDataByFileName = {};
62 context.enrichBuildData();
63 context.fileSignatures.forEach(function (fileSignature) {
64 context._mapToServerEntities(fileSignature.createDTO());
65 });
66 if (context.fileSignatures.length > 0) {
67 context.log.info("Processed the following files:");
68 context.fileSignatures.forEach(function (file, idx) {
69 context.log.info("\t[" + idx + "] Filename: '" + file.relativePath + "'");
70 });
71 }
72 var isSuccess = true;
73 if (context.erroredFiles.length) {
74 var message = context._getErroredFilesMessage();
75 context.log.warn(message);
76 isSuccess = false;
77 }
78 if (SlJsInfra.SlEnvVars.isUseNewUniqueId()) {
79 context._mapShortNamesToFiles();
80 context._convertUniqueIds();
81 }
82 context.finalOutput.files = context.finalFilesData;
83 context.finalOutput.counters = context.counters;
84 return resolve(isSuccess);
85 });
86 return promise;
87}
88
89FilesMapping.prototype._mapToServerEntities = function(fileSignature){
90 var context = this;
91 fileSignature.methods.forEach(function(m){
92 if(context.slMapper) {
93 context.slMapper.mapMethod(m);
94 }
95 var methodFile = context._getCreateFileData(m.srcData.relativeFilename, m.srcData.absoluteFilename);
96 methodFile.methods.push(EntitiesMapper.toServerMethod(m));
97 context.counters.methods++;
98 })
99 fileSignature.branches.forEach(function(b){
100 if(context.slMapper) {
101 context.slMapper.mapBranch(b);
102 }
103 // If containing method is ignored (auto generated code) set it to global branch.
104 if(globalMethodIndexer.isMethodIgnored(b.enclosingMethodIdx)){
105 b.enclosingMethodIdx = METHOD_INDEX_FOR_GLOBAL_BRANCHES;
106 }
107 var branchesFile = context._getCreateFileData(b.srcData.relativeFilename , b.srcData.absoluteFilename);
108 branchesFile.branches.push(EntitiesMapper.toServerBranch(b));
109 context.counters.branches++;
110 })
111}
112
113/* TODO: Split this method into two. For more info: "https://github.com/Sealights/SL.OnPremise.CIA/pull/169" */
114FilesMapping.prototype._scanWorkspaceForIncludedFilesAndPopulateFilesArray = function () {
115 var context = this
116 context.options.includedFiles.forEach(function (inclusionPattern) {
117 var matchedFiles = glob.sync(inclusionPattern,
118 {
119 cwd: context.options.workspacepath,
120 ignore: context.options.excludedFiles
121 });
122
123 matchedFiles.forEach(function (matchedFile) {
124 if (!context.fileDataByFileName[matchedFile]) {
125 context.fileDataByFileName[matchedFile] = true;
126 context.matchedFiles.push(matchedFile);
127 }
128 });
129 });
130}
131
132FilesMapping.prototype._fillFilesData = function (matchedFile, fileIdx, isLastFile) {
133 var absolutePath = path.resolve(this.options.workspacepath, matchedFile);
134 absolutePath = utils.adjustPathSlashes(absolutePath);
135 var shouldSkipCurrFile = this._shouldSkipFile(absolutePath, matchedFile, fileIdx);
136 if (shouldSkipCurrFile)
137 return;
138 this.handlerParams.processedFiles.push(matchedFile);
139 this.log.info('Processing [' + (fileIdx + 1) + '/' + this.matchedFiles.length + '] ' + absolutePath);
140 var fileSig = this._generateFileSignature(absolutePath);
141 if (!fileSig.hasError) {
142 fileSig.methods = this._getIncludedMethods(fileSig);
143 fileSig.branches = this._getIncludedBranches(fileSig);
144 this.fileSignatures.push(fileSig);
145 }
146 else {
147 this.erroredFiles.push(matchedFile);
148 }
149}
150
151FilesMapping.prototype._shouldSkipFile = function (fullFilename, relativeFileName, fileIdx) {
152 var filePath = fs.statSync(fullFilename);
153 if (!filePath.isFile()) {
154 this.log.info(relativeFileName + " is not a file.")
155 return true;
156 }
157 // If the file is filtered by the .slignore file:
158 if (this.slIgnoreScan.denies(relativeFileName)) {
159 this.log.info('Excluded [' + (fileIdx + 1) + '/' + this.matchedFiles.length + '] ' + fullFilename);
160 // Ignored by .slignore, skip it.
161 return true;
162 }
163 return false;
164}
165
166function createFileSignature(absolutePath, relativePath, fileContent, sourceMaps, opts, scmFilesContainer) {
167 if(sourceMaps) {
168 return GeneratedFileSignature.create(absolutePath, relativePath, fileContent, sourceMaps, opts, scmFilesContainer);
169 }else {
170 return FileSignature.create(absolutePath, relativePath, fileContent, sourceMaps, opts, scmFilesContainer);
171 }
172}
173
174FilesMapping.prototype._generateFileSignature = function (absolutePath) {
175 var relativePath = absolutePath.replace(this.projectRoot, "");
176 if(relativePath.indexOf("/") === 0) {
177 relativePath = relativePath.substring(1);
178 }
179 var isBranchCoverage = this.handlerParams.cfg.useBranchCoverage || this.options.usebranchcoverage;
180 var fileObject = this._createActualFile(relativePath, absolutePath);
181 var fileContent = fileObject.getContent();
182 var sourceMaps = fileObject.readSourceMaps(this.logger);
183
184 if (this._isMarkupFile(relativePath)) {
185 fileContent = this._getJavaScriptFromMarkupFile(absolutePath, fileContent);
186 }
187 var opts = {
188 es6Modules: true,
189 babylonPlugins: ["classProperties"].concat(this.options.babylonPlugins),
190 projectRoot: this.options.projectRoot,
191 scmRootDir: this.handlerParams.scmRootDir,
192 isBranchCoverage: this.handlerParams.cfg.useBranchCoverage || this.options.usebranchcoverage
193 };
194 var extension = path.extname(absolutePath);
195 if(extension === ".ts" || extension === ".tsx"){
196 opts.babylonPlugins.push("typescript")
197 }
198 try {
199 var fileSignature = createFileSignature(absolutePath, relativePath, fileContent, sourceMaps, opts, this.handlerParams.scmFilesContainer);
200 return fileSignature;
201 }catch (e) {
202 this.logger.error("Failed to create signature for '%s'. Error '%s'" , absolutePath, e.message);
203 this.logger.debug(e.stack);
204 return {
205 hasError: true
206 };
207 }
208};
209
210FilesMapping.prototype._createLogger = function (logger, className) {
211 var log = logger ? logger.child({
212 className: className
213 }) : consoleFileLogger(className);
214 return log;
215}
216
217
218FilesMapping.prototype._getIncludedMethods = function (fileSig) {
219 if (!fileSig.methods)
220 return [];
221 return this._getIncludeElements(fileSig, METHODS_KEY, METHOD_ELEMENT_TYPE);;
222}
223
224
225
226FilesMapping.prototype._getIncludedBranches = function (fileSig) {
227 if (!fileSig.branches)
228 return []
229 return this._getIncludeElements(fileSig, BRANCHES_KEY, BRANCH_ELEMENT_TYPE);
230
231}
232
233FilesMapping.prototype._getIncludeElements = function (fileSig, key, elementType) {
234 var includedElements = [];
235 var context = this;
236 fileSig[key].forEach(function (element) {
237 if (fileSig.hasSourceMaps) {
238 context._handleGeneratedElement(element, includedElements, elementType);
239 } else {
240 includedElements.push(element);
241 }
242 });
243 return includedElements;
244}
245
246FilesMapping.prototype._handleGeneratedElement = function (element, elementsArr, elementType) {
247 if (this.slIgnoreGenerated.accepts(element.srcData.relativeFilename)) {
248 elementsArr.push(element);
249 } else {
250 if(elementType === METHOD_ELEMENT_TYPE){
251 globalMethodIndexer.setIgnoredMethod(element.idxInMapping);
252 }
253 this.log.info('Rejecting ' + elementType + ' from file: ' + element.srcData.relativeFilename);
254 }
255}
256
257FilesMapping.prototype._getErroredFilesMessage = function () {
258 return "The following files failed to be parsed and will not be sent to the server:\n" +
259 this.erroredFiles.join('\n') +
260 "\n\t(Hint: if the file use the es6 module syntax, pass the \"--es6Modules\" switch)";
261}
262
263FilesMapping.prototype._getCreateFileData = function (filename, absoluteFileName) {
264 if (!this.fileDataByFileName[filename]) {
265 var fileData = this._createNewFileData(filename, absoluteFileName);
266 this.fileDataByFileName[filename] = fileData;
267 this.finalFilesData.push(fileData);
268 }
269 return this.fileDataByFileName[filename];
270}
271
272FilesMapping.prototype._createNewFileData = function (filename, absoluteFileName) {
273 var gitPath = this._resolvePhysicalPath(absoluteFileName) || filename;
274 var newFile = {
275 logicalPath: filename,
276 physicalPath: gitPath, //Relative path to git folder
277 methods: [],
278 lines: [],
279 branches: []
280 };
281
282 if (this._shouldAddCommitsArray()) {
283 newFile.commitIndexes = this.handlerParams.fileToCommitsMap[gitPath] || [];
284 } else {
285 this._logCommitsArrayNotFound();
286 }
287
288 return newFile;
289}
290
291FilesMapping.prototype._resolvePhysicalPath = function (absoluteFileName) {
292 if (!this.handlerParams.scmRootDir) {
293 this.logger.debug("scmRootDir not found");
294 return;
295 }
296 absoluteFileName = utils.adjustPathSlashes(absoluteFileName);
297 var scmRootDir = utils.adjustPathSlashes(this.handlerParams.scmRootDir);
298 var physicalPath = absoluteFileName.replace(scmRootDir, "");
299 if (physicalPath.indexOf("/") === 0) {
300 physicalPath = physicalPath.substring(1);
301 }
302 return physicalPath;
303};
304
305FilesMapping.prototype._shouldAddCommitsArray = function () {
306 return this.options && this.options.sendContributors && this.handlerParams.fileToCommitsMap;
307}
308
309FilesMapping.prototype._logCommitsArrayNotFound = function () {
310 var fieldsMap = {
311 opts: !this.options,
312 sendContributors: this.options && !this.options.sendContributors,
313 fileToCommitsMap: !this.handlerParams.fileToCommitsMap
314 }
315 var missing = utils.getMissingProperties(fieldsMap);
316 this.log.error("Cannot add commits indexes, missing fields: " + missing);
317}
318
319FilesMapping.prototype._extractBranches = function (branch, method, file) {
320 if (!branch) {
321 return;
322 }
323 //TODO: Hack! Currently the branches which we collect are inside method.
324 //When we'll support global branches, we should get the "original file name"
325 //in the same time we create the branch data object.
326 branch.originalFilename = method.originalFilename;
327 file.branches.push(branch);
328}
329
330FilesMapping.prototype._createSlIgnore = function (filename) {
331 var workspacePath = this.options.workspacepath;
332 var lookInParentFolders = !!this.options.instrumentForBrowsers;
333 var slIgnoreScanFilePath = fileAndFolderUtils.findFileRecursively(filename, workspacePath, lookInParentFolders, this.logger);
334 var slIgnoreScan = new IgnoredFilesHandler(slIgnoreScanFilePath, this.options.outputpath, filename, this.log);
335 this.submitIgnorePatternToCockpit(slIgnoreScan, filename)
336 return slIgnoreScan;
337}
338
339FilesMapping.prototype._mapShortNamesToFiles = function () {
340 this.shortNameToFileMapper = new SlJsInfra.ShortNameToFileMapper(this.logger);
341 var context = this;
342 this.finalFilesData.forEach(function (file) {
343 context.shortNameToFileMapper.addFile(file.physicalPath)
344 });
345}
346
347FilesMapping.prototype._convertUniqueIds = function () {
348 if (!this.shortNameToFileMapper) {
349 this.logger.error("Short name map not intilized.");
350 return;
351 }
352 var context = this;
353 this.finalFilesData.forEach(function (file) {
354 var shortName = context.shortNameToFileMapper.resolveShortNameForFile(file.physicalPath);
355 var converter = new SlJsInfra.UniqueIdConverter(shortName, context.logger);
356 converter.setAndInitFile(file);
357 converter.process();
358 var map = converter.uniqueIdsMap;
359 if (Array.isArray(file.methods)) {
360 file.methods = context._updateUniqueIds(file.methods, map);
361 }
362 if (Array.isArray(file.branches)) {
363 file.branches = context._updateUniqueIds(file.branches, map);
364 }
365 })
366}
367
368FilesMapping.prototype._updateUniqueIds = function (arr, uniqueIdMap) {
369 var newArr = []
370 for (var i = 0; i < arr.length; i++) {
371 var current = arr[i];
372 var oldUniqueId = current.uniqueId;
373 if (uniqueIdMap[oldUniqueId]) {
374 current.uniqueId = uniqueIdMap[oldUniqueId];
375 delete current.parentPosition;
376 newArr.push(current);
377 }
378 }
379 return newArr;
380}
381
382FilesMapping.prototype._getJavaScriptFromMarkupFile = function (fullname, fileContent) {
383 var handler = new MarkupFilesParser(fileContent, fullname, this.logger);
384 handler.extractScriptTags();
385 var scriptTags = handler.scriptTags;
386 if (!scriptTags || scriptTags.length === 0) {
387 this.logger.info("No script tags found in '%s'", fullname);
388 return { hasError: true };
389 }
390 var jsCodeInFile = handler.getAllJsInFile();
391 return jsCodeInFile;
392};
393
394FilesMapping.prototype._isMarkupFile = function (filename) {
395 var dvr = new DefaultValuesResolver();
396 var fileExtensions = dvr.getFileExtensions();
397 var extension = path.extname(filename);
398 var result = (utils.isArrayContainsMarkupFile(fileExtensions) && utils.isMarkupFile(extension));
399 return result;
400}
401
402FilesMapping.prototype._resolveProjectRoot = function () {
403 var projectRoot;
404 if (this.options.projectRoot) {
405 projectRoot = this.options.projectRoot;
406 this.logger.info("Project root set to '%s' from 'sourceRoot'", projectRoot)
407 } else if (this.handlerParams.scmRootDir) {
408 projectRoot = this.handlerParams.scmRootDir;
409 this.logger.info("Project root set to '%s' same as git root", projectRoot)
410 } else {
411 projectRoot = process.cwd();
412 this.logger.info("Project root set to '%s' same as CWD", projectRoot);
413 }
414 return utils.adjustPathSlashes(projectRoot);
415}
416
417//This method was added for testing.
418FilesMapping.prototype._createActualFile = function (filename, fullFilename) {
419 return new ActualFile(filename, fullFilename)
420}
421
422FilesMapping.prototype.enrichBuildData = function () {
423 var context = this;
424 var fileToSignatureMap = this.createSourcePathToSignatureMap();
425 this.fileSignatures.forEach(function (signature) {
426 if(signature instanceof GeneratedFileSignature){
427 var relevantSourceSignatures = context.extractSourceSignatures(fileToSignatureMap, signature);
428 context.enrichSingleFile(signature, relevantSourceSignatures);
429 }
430 })
431
432};
433
434
435FilesMapping.prototype.enrichSingleFile = function (generatedSignature, sourceSignaturesArr) {
436 try {
437 var sourceDataEnricher = new SourceDataEnricher(generatedSignature, sourceSignaturesArr, this.logger);
438 sourceDataEnricher.enrichData();
439 } catch (e) {
440 this.logger.info("Error while enriching build data for '%s'. Error: '%s'", generatedSignature.absoluteFilename, e);
441 globalErrorHandler.setLastError(e)
442 }
443}
444
445FilesMapping.prototype.createSourcePathToSignatureMap = function () {
446 var context = this;
447 var pathToSignatureMap = {};
448 var sourceFilesPath = [];
449 this.fileSignatures.forEach(function (sig) {
450 if(sig instanceof GeneratedFileSignature) {
451 sourceFilesPath = sourceFilesPath.concat(sig.getSourceFilesPath());
452 }
453 });
454 sourceFilesPath = sourceFilesPath.filter(utils.distinctArray);
455 sourceFilesPath = sourceFilesPath.filter(fs.existsSync);
456 sourceFilesPath.forEach(function (filePath) {
457 if (context.shouldParse(filePath)) {
458 var sourceSig = context._generateFileSignature(filePath);
459 if (!sourceSig.hasError) {
460 pathToSignatureMap[filePath] = sourceSig;
461 }
462 } else{
463 //TODO: Report do OBD
464 }
465 });
466 return pathToSignatureMap;
467}
468
469FilesMapping.prototype.extractSourceSignatures = function(sourceSignatures, generatedSignature){
470 var context = this;
471 var sourcesPath = generatedSignature.getSourceFilesPath();
472 var relevantSignatures = [];
473 sourcesPath.forEach(function (sourcePath) {
474 if(sourceSignatures[sourcePath]) {
475 relevantSignatures.push(sourceSignatures[sourcePath]);
476 }else {
477 context.logger.info("Could not find source signature for '%s'", sourcePath)
478 }
479 })
480 return relevantSignatures;
481}
482
483FilesMapping.prototype.shouldParse = function(absolutePath){
484 var tsFilesPattern = "**/*.ts";
485 var includedPatterns = this.options.includedFiles.concat([tsFilesPattern]);
486 return micromatch.isMatch(absolutePath, includedPatterns);
487}
488
489FilesMapping.prototype.submitIgnorePatternToCockpit = function(ignoredFilesHandler, filename){
490 var formattedPattern = ignoredFilesHandler.getFormattedPattern();
491 if(!formattedPattern){
492 return;
493 }
494 var ignoreFile = ignoredFilesHandler.isUsingNycConfig ? "NYC configuration" : filename;
495 var messaage = "Using the following ignore pattern from " + ignoreFile + ": " + ignoredFilesHandler.getFormattedPattern();
496 this.agentEventsController.submitMessage(messaage);
497}
498module.exports = FilesMapping;