UNPKG

9.66 kBJavaScriptView Raw
1var traverse = require('ast-traverse');
2var astUtils = require('sl-esprima-ast-utils');
3var globalErrorHandler = require('../global-error-handler');
4var babelParser = require("@babel/parser");
5var createNodeKey = require('./create-node-key').createNodeKey;
6var AstNodeTypes = require("./ast-node-types");
7var consoleFileLogger = require("../sl-logger.js").consoleFileLogger;
8var MethodSignature = require("./method-signature");
9var BranchSignature = require("./branch-signature");
10var globalMethodIndexer = require("./globalMethodIndexer");
11
12/**
13 * Traverse the AST and handle specific node types.
14 * Information about the Node Types can be found in: https://github.com/babel/babylon/blob/master/ast/spec.md
15 * @param absolutePtah
16 * @param relativePath
17 * @param content
18 * @param {*} sourceMaps
19 * @param isbranchCoverage
20 * @param opts
21 */
22var FileSignature = function (absolutePtah, relativePath, content, sourceMaps, opts, scmFilesContainer) {
23 this.absolutePath = absolutePtah;
24 this.relativePath = relativePath;
25 this.methods = [];
26 this.branches = [];
27 this.content = content;
28 this.sourceMaps = sourceMaps;
29 this.opts = opts;
30 this.scmFilesContainer = scmFilesContainer;
31 this.hasSourceMaps = !!sourceMaps;
32 this.logger = consoleFileLogger("FileSignature");
33 this.positionToMethodMap = {};
34 this.positionToBranchMap = {};
35 this.methodIndex = 0;
36 this.branchIndex = 0;
37};
38
39FileSignature.prototype._createSignature = function () {
40 this.prepareContent();
41 var ast = this.parseContentToAST();
42 this.traverseAST(ast);
43};
44
45FileSignature.prototype.createDTO = function(){
46 return {
47 methods: this.methods,
48 branches: this.branches,
49 filename: this.relativePath,
50 hasSourceMaps: this.hasSourceMaps
51 }
52};
53
54FileSignature.prototype.parseContentToAST = function () {
55 var sourceType = (this.opts && this.opts.es6Modules) ? 'module' : 'script';
56
57 var plugins = ["*", ["decorators", {decoratorsBeforeExport: true}]];
58 if (this.opts && this.opts.babylonPlugins) {
59 plugins = plugins.concat(this.opts.babylonPlugins);
60 }
61 return babelParser.parse(this.content, {
62 // parse in strict mode and allow module declarations
63 sourceType: sourceType,
64 plugins: plugins
65 })
66};
67FileSignature.prototype.traverseAST = function (ast) {
68 var context = this;
69 var nodeToParent = {};
70 var activeMethodKey = null;
71 var activeMethodIndex = null;
72 traverse(ast, {
73 pre: function (node, parentNode) {
74 var key = createNodeKey(node);
75 nodeToParent[key] = parentNode;
76 if (AstNodeTypes.isMethodNode(node)) {
77 // If there is no active method it means we are in global context.
78 if(activeMethodKey == null) {
79 activeMethodKey = createNodeKey(node);
80 }
81 globalMethodIndexer.incrementIndex();
82 /*
83 We set the active method index to point the current method index index.
84 This value sets the 'enclosingMethodIdx' in branches.
85 */
86 activeMethodIndex = globalMethodIndexer.getCurrentIndex();
87 context.addMethod(node, parentNode, nodeToParent);
88 } else if (context.opts.isBranchCoverage && AstNodeTypes.isBranchNode(node)) {
89 context.addBranch(node, parentNode, activeMethodKey == null, activeMethodIndex)
90 }
91 },
92 post: function (node) {
93 if(AstNodeTypes.isMethodNode(node)){
94 // If we are quiting method which is active, its mean we are in global context.
95 if(activeMethodKey === createNodeKey(node)) {
96 activeMethodKey = null;
97 }
98 /*
99 If a function contains branch after method, then the 'activeMethodIndex' will contains the index of
100 the inner method while the branch belongs to the parent method.
101 Here we decreasing the index after each method so the 'enclosingMethodIdx' of the branch will point to parent.
102 */
103 activeMethodIndex --;
104 }
105 }
106 });
107};
108FileSignature.prototype.addMethod = function (node, parentNode, nodeToParent) {
109 var method = MethodSignature.create(node, parentNode, nodeToParent, this.absolutePath,
110 this.relativePath, this.logger);
111 method.srcData = method.generatedData;
112 this.methods.push(method.createDTO());
113 this.positionToMethodMap[method.generatedData.position] = this.methodIndex;
114 this.methodIndex++;
115};
116FileSignature.prototype.addBranch = function (node, parentNode, isGlobalBranch, activeMethodIndex) {
117 var context = this;
118 var branches = [];
119 if (node.type !== AstNodeTypes.LogicalExpression) {
120 branches = branches.concat(this.getBranchesToAdd(node, isGlobalBranch, activeMethodIndex));
121 }
122
123 // If this node its not logical exp inside logical exp.
124 if (node.type == AstNodeTypes.LogicalExpression && (parentNode == undefined || parentNode.type !== AstNodeTypes.LogicalExpression)) {
125 branches = branches.concat(this.getBranchesToAdd(node, isGlobalBranch, activeMethodIndex));
126 }
127
128 branches.forEach(function (branch) {
129 branch.srcData = branch.generatedData;
130 context.branches.push(branch.createDTO());
131 context.positionToBranchMap[branch.generatedData.position] = context.branchIndex;
132 context.branchIndex ++;
133 })
134};
135FileSignature.prototype.prepareContent = function () {
136 this.content = trimShebang(this.content);
137};
138FileSignature.prototype.getBranchesToAdd = function (node, isGlobalBranch, activeMethodIndex) {
139 var branchesToAdd = [];
140 var context = this;
141 var nodeKey = createNodeKey(node)
142 switch (node.type) {
143 // Those types contains some kind of "if else" structure.
144 case AstNodeTypes.IfStatement:
145 case AstNodeTypes.ConditionalExpression:
146 var branchIndex = 0;
147
148 // consequent mean when condition equals true.
149 if (node.consequent !== null) {
150 var branch = BranchSignature.create(node.consequent, node, node.type, branchIndex++, this.absolutePath, this.relativePath,
151 isGlobalBranch, activeMethodIndex, this.logger);
152 branchesToAdd.push(branch);
153 }
154
155 // altrnate equivalent to "else".
156 var clonedNode = null;
157 if (node.alternate == null) {
158 clonedNode = createEmptyAlternate(node);
159 }
160 var nodeToAdd = clonedNode || node;
161 var branch = BranchSignature.create(nodeToAdd.alternate, nodeToAdd, node.type, branchIndex++, this.absolutePath, this.relativePath,
162 isGlobalBranch, activeMethodIndex, this.logger);
163 branchesToAdd.push(branch);
164 break;
165 case AstNodeTypes.SwitchStatement:
166 var branchIndex = 0;
167
168 // Create branch from each case statement.
169 node.cases.forEach(function (caseNode) {
170 if (caseNode !== null) {
171 var branch = BranchSignature.create(caseNode, node, node.type, branchIndex++, context.absolutePath, context.relativePath,
172 isGlobalBranch, activeMethodIndex, context.logger);
173 branchesToAdd.push(branch);
174 }
175 });
176 break;
177 case AstNodeTypes.LogicalExpression:
178 var branchIndex = 0;
179 var leaves = [];
180 findLogicalExpressionLeaves(node, leaves);
181 leaves.forEach(function (leafNode) {
182 if (leafNode !== null) {
183 var branch = BranchSignature.create(leafNode, node, node.type, branchIndex++, context.absolutePath, context.relativePath,
184 isGlobalBranch, activeMethodIndex, context.logger);
185 branchesToAdd.push(branch);
186 }
187 });
188 break;
189 }
190 return branchesToAdd;
191};
192
193
194FileSignature.create = function (absolutePtah, relativePath, content, sourceMaps, opts, scmFilesContainer) {
195 var signature = new this(absolutePtah, relativePath, content, sourceMaps, opts, scmFilesContainer);
196 signature._createSignature();
197 return signature;
198}
199
200// Brake the expression to pieces (every "or" will be different branch).
201function findLogicalExpressionLeaves(node, leaves) {
202 if (node.type === AstNodeTypes.LogicalExpression) {
203 findLogicalExpressionLeaves(node.left, leaves);
204 findLogicalExpressionLeaves(node.right, leaves);
205 } else {
206 leaves.push(node);
207 }
208}
209
210/**
211 * Taken from eslint - https://github.com/eslint/eslint/blob/183def6115cad6f17c82ef1c1a245eb22d0bee83/lib/eslint.js#L800
212 */
213function trimShebang(text) {
214 return text.replace(/^#!([^\r\n]+)/, function (match, captured) {
215 return "//" + captured;
216 });
217}
218
219/**
220 * When we have inner method we set it to empty block statement, because we want to calculate the hash and diff
221 * for outer method only.
222 * @param {*} methodNode
223 */
224
225function _createEmptyBlock() {
226 return {
227 type: AstNodeTypes.BlockStatement,
228 body: []
229 }
230}
231
232function createEmptyAlternate(node) {
233// Istanbul counts empty alternates and takes the consequent's location
234 var clonedNode = astUtils.clone(node);
235 clonedNode.alternate = _createEmptyBlock();
236
237 // Set alternate location to consequent location (same as istanbul).
238 clonedNode.alternate.loc = node.consequent !== null ? node.consequent.loc : node.loc;
239 return clonedNode;
240}
241
242module.exports = FileSignature;