UNPKG

7.81 kBJavaScriptView Raw
1var path = require('path');
2var mkdirp = require('mkdirp');
3var once = require('once');
4var fs = require('fs');
5var SlJsInfra = require('sl-js-infra').SlJsInfra;
6var fileAndFolderUtils = require('../file-and-folder-utils');
7var ValidationUtils = SlJsInfra.Utils.ValidationUtils;
8
9/**
10 * This class responsible to instrument a single file with Sealights information.
11 * @param {* Instrumentation options} opts
12 * @param {* Header with Sealights config to inject inside a single file} preambleHeader
13 * @param {* An instance of Istanbul's instrumenter} instrumenter
14 * @param {* The file name} name
15 * @param {* A logger} logger
16 */
17function FileInstrumenter(opts, preambleHeader, instrumenter, name, IgnoredFilesHandler, logger) {
18 ValidationUtils.verifyNotNullOrEmpty(opts, "opts");
19 ValidationUtils.verifyNotNullOrEmpty(preambleHeader, "preambleHeader");
20 ValidationUtils.verifyNotNullOrEmpty(instrumenter, "instrumenter");
21 ValidationUtils.verifyNotNullOrEmpty(name, "name");
22 ValidationUtils.verifyNotNullOrEmpty(IgnoredFilesHandler, "IgnoredFilesHandler");
23 ValidationUtils.verifyNotNullOrEmpty(logger, "logger");
24
25 this.opts = opts;
26 this.preambleHeader = preambleHeader;
27 this.instrumenter = instrumenter;
28 this.name = name;
29 this.IgnoredFilesHandler = IgnoredFilesHandler;
30 this.logger = logger;
31}
32
33
34FileInstrumenter.prototype.processSingleFile = function (callback) {
35 ValidationUtils.verifyNotNullOrEmpty(callback, "callback");
36
37 this.logger.debug("Processing single file. File: '" + this.name + "'.");
38
39 var inputFile = this._getInputFile()
40 var outputFile = this._getOutputFile();
41 var inputFileExtension = path.extname(inputFile);
42 var isValidExtension = this.opts.extensionsToInstrument[inputFileExtension];
43 var shouldInstrumentFile = isValidExtension && this._isFileAcceptedBySlIgnore();
44 var outputDir = path.dirname(outputFile);
45 var _this = this;
46
47 callback = once(callback);
48 mkdirp.sync(outputDir);
49
50 if (fs.statSync(inputFile).isDirectory()) {
51 this.logger.info("Input file is a directory. No need to process");
52 return callback(null, this.name);
53 }
54
55 if (shouldInstrumentFile) {
56 return this._instrumentFile(inputFile, outputFile, this.name, this.preambleHeader, callback);
57 }else{
58 fileAndFolderUtils.copyFileAsIs(inputFile, outputFile, this.name, this.logger, function (err, name) {
59 if(err && name && _this.opts.onFileCopyError != null){
60 _this.opts.onFileCopyError(name, err);
61 }
62 else if (!err && name && _this.opts.onFileCopied != null) {
63 //We have a file name. Notify.
64 _this.opts.onFileCopied(name);
65 }
66 return callback(err, name);
67 });
68 }
69}
70
71
72FileInstrumenter.prototype._onBeforeInstrumentation = function (fileContent) {
73 fileContent = this._removeWebpackComments(fileContent);
74 return fileContent;
75}
76
77FileInstrumenter.prototype._onAfterInstrumentation = function (instrumentedFileContent) {
78 instrumentedFileContent = this._fixMissingSpaceAfterReturnStatement(instrumentedFileContent);
79 return instrumentedFileContent;
80}
81
82FileInstrumenter.prototype._handleInstrumentationError = function (message, err, name, callback) {
83 this.logger.error(message + " File: '" + name + "'. Error:", err);
84 if (this.opts.onFileWithInstrumentationError != null){
85 this.opts.onFileWithInstrumentationError({ file: name, error: err });
86 }
87
88 return callback(err, name);
89}
90
91FileInstrumenter.prototype._instrumentFile = function (inputFile, outputFile, name, preambleHeader, callback) {
92 this.logger.debug("Instrumenting file '" + inputFile + "'.");
93 var _this = this;
94 fs.readFile(inputFile, 'utf8', function (err, fileContents) {
95 if (err) {
96 return _this._handleInstrumentationError("Failed reading file contents.", err, name, callback);
97 }
98
99 fileContents = _this._onBeforeInstrumentation(fileContents);
100
101 _this.instrumenter.instrument(fileContents, inputFile, function (err, instrumentedContent){
102 return _this.instrumentationCallback(err, instrumentedContent, preambleHeader, name, outputFile, callback);
103 }, {
104 filename: name,
105 inputSourceMap: null
106 });
107 });
108}
109
110
111
112FileInstrumenter.prototype.instrumentationCallback = function (err, instrumentedContent, preambleHeader, name,
113 outputFile, callback) {
114 var _this = this;
115 if (err) {
116 return _this._handleInstrumentationError("Failed during instrumentation.", err, name, callback);
117 }
118 var newContents = preambleHeader.concat([instrumentedContent]).join("\n");
119 newContents = _this._onAfterInstrumentation(newContents)
120 fs.writeFile(outputFile, newContents, 'utf8', function (err) {
121 if (err) {
122 return _this._handleInstrumentationError("Failed while writing instrumented file.", err, name,
123 callback);
124 }
125 if (_this.opts.onFileInstrumented != null) {
126 _this.opts.onFileInstrumented(name);
127 }
128 return callback(null, name);
129 });
130}
131
132
133//**********************************************************/
134// Bug Fix: When instrumenting a 'React' based page, page loading breaks due to a missing space after a 'return' statement.
135//
136// Instead of having " return</div>" which breaks the page, we change it to
137// " return </div>"
138//**********************************************************/
139FileInstrumenter.prototype._fixMissingSpaceAfterReturnStatement = function (input) {
140 return input.replace(/;return</g, ";return <");
141}
142
143//**********************************************************/
144// Bug Fix: Webpack plugin breaks JavaScript code when require.ensure() is used (Duda Mobile)
145// See https://sealights.atlassian.net/browse/SLDEV-2115
146//
147// Basically this is a workaround for:
148// https://github.com/estools/escodegen/issues/336
149//**********************************************************/
150FileInstrumenter.prototype._removeWebpackComments = function (input) {
151 // This regex should capture block comment filled with stars, globally.
152 // Good:
153 // /**/
154 // /***/
155 // /*****************/
156 // Bad:
157 // /*/
158 // //
159 // /***a*/
160 //
161 var regex = new RegExp("\/[\*]{2,}\/", "g");
162
163 while (true) {
164 //Execute the regex, each time on a different part of the input string.
165 var currentMatch = regex.exec(input);
166 if (!currentMatch)
167 break;
168 var matchIndex = currentMatch.index;
169 var matchLength = currentMatch[0].length;
170 var whiteSpace = this._createWhiteSpace(matchLength);
171
172 //Replace the matching comment with whitespace.
173 input = this._replaceAt(input, matchIndex, whiteSpace);
174 }
175
176 return input;
177}
178
179FileInstrumenter.prototype._replaceAt = function (string, index, replacement) {
180 return string.substr(0, index) + replacement + string.substr(index + replacement.length);
181}
182
183FileInstrumenter.prototype._createWhiteSpace = function (length) {
184 var arr = new Array(++length);
185 return arr.join(" ")
186}
187
188FileInstrumenter.prototype._isFileAcceptedBySlIgnore = function () {
189 if (this.IgnoredFilesHandler.denies(this.name)){
190 this.logger.info("File '%s' ignored by .slignore, will be copied as is", this.name);
191 return false;
192 }
193 return true;
194}
195
196
197FileInstrumenter.prototype._getInputFile = function() {
198 var folder = this.opts.sourceRoot;
199 return path.resolve(folder, this.name);
200}
201
202FileInstrumenter.prototype._getOutputFile = function() {
203 var folder = this.opts.outputPath;
204 return path.resolve(folder, this.name);
205}
206
207
208module.exports = FileInstrumenter;