1 | var fileAndFolderUtils = require('../file-and-folder-utils');
|
2 | var SlJsInfra = require('sl-js-infra').SlJsInfra;
|
3 | var IgnoredFilesHandler = SlJsInfra.IgnoredFilesHandler;
|
4 | var glob = require("glob");
|
5 | var path = require('path');
|
6 | var fs = require('fs');
|
7 | var ActualFile = require('../file-system/actual-file');
|
8 | var utils = require('../utils');
|
9 | var consoleFileLogger = require("../sl-logger.js").consoleFileLogger;
|
10 | var FileSignature = require('./file-signature.js');
|
11 | var GeneratedFileSignature = require('./generated-file-signature');
|
12 | var micromatch = require('micromatch');
|
13 | var MarkupFilesParser = require('../markup-files-parser');
|
14 | var DefaultValuesResolver = require('../default-values-resolver');
|
15 | var EntitiesMapper = require("../entities-mapper");
|
16 | var SourceDataEnricher = require("./source-data-enricher");
|
17 | var globalErrorHandler = require('../global-error-handler');
|
18 | var globalMethodIndexer = require("./globalMethodIndexer");
|
19 |
|
20 |
|
21 | var BRANCHES_KEY = "branches";
|
22 | var BRANCH_ELEMENT_TYPE = "branch";
|
23 | var METHODS_KEY = "methods";
|
24 | var METHOD_ELEMENT_TYPE = "method";
|
25 | var METHOD_INDEX_FOR_GLOBAL_BRANCHES = -1;
|
26 |
|
27 |
|
28 | function 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 = [];
|
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 |
|
52 | FilesMapping.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 |
|
89 | FilesMapping.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 |
|
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 |
|
114 | FilesMapping.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 |
|
132 | FilesMapping.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 |
|
151 | FilesMapping.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 |
|
158 | if (this.slIgnoreScan.denies(relativeFileName)) {
|
159 | this.log.info('Excluded [' + (fileIdx + 1) + '/' + this.matchedFiles.length + '] ' + fullFilename);
|
160 |
|
161 | return true;
|
162 | }
|
163 | return false;
|
164 | }
|
165 |
|
166 | function 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 |
|
174 | FilesMapping.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 |
|
210 | FilesMapping.prototype._createLogger = function (logger, className) {
|
211 | var log = logger ? logger.child({
|
212 | className: className
|
213 | }) : consoleFileLogger(className);
|
214 | return log;
|
215 | }
|
216 |
|
217 |
|
218 | FilesMapping.prototype._getIncludedMethods = function (fileSig) {
|
219 | if (!fileSig.methods)
|
220 | return [];
|
221 | return this._getIncludeElements(fileSig, METHODS_KEY, METHOD_ELEMENT_TYPE);;
|
222 | }
|
223 |
|
224 |
|
225 |
|
226 | FilesMapping.prototype._getIncludedBranches = function (fileSig) {
|
227 | if (!fileSig.branches)
|
228 | return []
|
229 | return this._getIncludeElements(fileSig, BRANCHES_KEY, BRANCH_ELEMENT_TYPE);
|
230 |
|
231 | }
|
232 |
|
233 | FilesMapping.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 |
|
246 | FilesMapping.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 |
|
257 | FilesMapping.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 |
|
263 | FilesMapping.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 |
|
272 | FilesMapping.prototype._createNewFileData = function (filename, absoluteFileName) {
|
273 | var gitPath = this._resolvePhysicalPath(absoluteFileName) || filename;
|
274 | var newFile = {
|
275 | logicalPath: filename,
|
276 | physicalPath: gitPath,
|
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 |
|
291 | FilesMapping.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 |
|
305 | FilesMapping.prototype._shouldAddCommitsArray = function () {
|
306 | return this.options && this.options.sendContributors && this.handlerParams.fileToCommitsMap;
|
307 | }
|
308 |
|
309 | FilesMapping.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 |
|
319 | FilesMapping.prototype._extractBranches = function (branch, method, file) {
|
320 | if (!branch) {
|
321 | return;
|
322 | }
|
323 |
|
324 |
|
325 |
|
326 | branch.originalFilename = method.originalFilename;
|
327 | file.branches.push(branch);
|
328 | }
|
329 |
|
330 | FilesMapping.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 |
|
339 | FilesMapping.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 |
|
347 | FilesMapping.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 |
|
368 | FilesMapping.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 |
|
382 | FilesMapping.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 |
|
394 | FilesMapping.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 |
|
402 | FilesMapping.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 |
|
418 | FilesMapping.prototype._createActualFile = function (filename, fullFilename) {
|
419 | return new ActualFile(filename, fullFilename)
|
420 | }
|
421 |
|
422 | FilesMapping.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 |
|
435 | FilesMapping.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 |
|
445 | FilesMapping.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 |
|
464 | }
|
465 | });
|
466 | return pathToSignatureMap;
|
467 | }
|
468 |
|
469 | FilesMapping.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 |
|
483 | FilesMapping.prototype.shouldParse = function(absolutePath){
|
484 | var tsFilesPattern = "**/*.ts";
|
485 | var includedPatterns = this.options.includedFiles.concat([tsFilesPattern]);
|
486 | return micromatch.isMatch(absolutePath, includedPatterns);
|
487 | }
|
488 |
|
489 | FilesMapping.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 | }
|
498 | module.exports = FilesMapping;
|