UNPKG

5.06 kBJavaScriptView Raw
1'use strict';
2
3var fs = require('fs');
4var globby = require('globby');
5
6var acorn = require('acorn/dist/acorn_loose');
7var walker = require('acorn/dist/walk');
8
9// Prepare the walk visitors and handlers
10var visitors = {};
11var handlers = Object.assign({}, walker.base);
12Object.keys(walker.base).forEach(function (key) {
13 visitors[key] = visitNode;
14});
15
16// Get the browser test object
17var featureParser = require('./featureTestParser');
18
19// Declare the initial mappings
20var featuresUsed = new Set();
21var variables = new Map();
22var objects = void 0;
23var methods = void 0;
24
25function flatten(arr) {
26 return arr.reduce(function (a, v) {
27 if (!Array.isArray(v)) {
28 a.push(v);
29 } else {
30 a = a.concat(flatten(v));
31 }
32
33 return a;
34 }, []);
35}
36
37function nodeName(node) {
38 var name = null;
39
40 if (node.name) {
41 name = node.name;
42 } else if (node.type === 'MemberExpression') {
43 name = node.property ? node.property.name : null;
44 }
45
46 return name;
47}
48
49// We register an array of possible values for a given variable
50// - This doesn't look at context or other as we just want potential object names
51function registerVarVal(varNode, valNode) {
52 if (!varNode || !valNode) {
53 return;
54 }
55
56 var isNew = valNode.type === 'NewExpression';
57 var isVar = valNode.type === 'Identifier';
58
59 var varName = nodeName(varNode);
60 var valName = nodeName(isNew ? valNode.callee : valNode);
61
62 if (!(isVar || isNew) || !valName) {
63 return;
64 }
65
66 var savedVals = variables.get(varName) || [];
67 valName = (valName ? [valName] : []).filter(function (v) {
68 return v !== varName;
69 });
70 variables.set(varName, savedVals.concat(valName));
71}
72
73// Get Variable values so we can match for object names
74function getVarVal(varName) {
75 var _var = variables.get(varName);
76 if (!_var) {
77 return null;
78 }
79
80 return flatten(_var.map(function (val) {
81 if (!variables.has(val)) {
82 return val;
83 }
84 return getVarVal(val);
85 }));
86}
87
88// Parser methods that determines which methods we are actually using
89// (compared to the features test defined in 'features')
90function visitNode(node) {
91 if (node.type === 'MemberExpression') {
92 var propName = nodeName(node);
93 if (!propName || !node.object || !objects.has(propName)) {
94 return;
95 }
96 return featuresUsed.add(propName);
97 }
98
99 if (node.type === 'VariableDeclarator') {
100 return registerVarVal(node.id, node.init);
101 }
102
103 // Log the various object assignments to determine the Object type of the variables
104 if (node.type === 'AssignmentExpression') {
105 return registerVarVal(node.left, node.right);
106 }
107
108 var caller = node.callee;
109
110 // We only care about objects, method calls and some properties
111 if (!caller || ['NewExpression', 'CallExpression'].indexOf(node.type) < 0) {
112 return;
113 }
114
115 var obj = caller.object;
116 var prop = caller.property;
117
118 // Function call have neither object nor property
119 if (!obj && !prop) {
120 if (objects.has(caller.name)) {
121 featuresUsed.add(caller.name);
122 }
123 return;
124 }
125
126 // If we need to test for the object, add it to the list
127 if (objects.has(obj.name)) {
128 featuresUsed.add(obj.name);
129 }
130
131 // If it is just a function call or if the property is not one we need to
132 // check for, then just move on to the next node
133 if (!prop || prop && !methods.has(prop.name)) {
134 return;
135 }
136
137 var method = prop.name;
138
139 // For arrays we need to test for the function
140 if (obj.type === 'ArrayExpression') {
141 if (objects.has('Array')) {
142 featuresUsed.add('Array.' + method);
143 }
144
145 // If the determined object has the method in question, then add that check
146 } else {
147 var methodObjects = methods.get(method);
148 var varObjects = getVarVal(obj.name) || obj.name;
149 if (!varObjects) {
150 return;
151 }
152 if (!Array.isArray(varObjects)) {
153 varObjects = [varObjects];
154 }
155
156 varObjects.filter(function (objName) {
157 return methodObjects.indexOf(objName) > -1;
158 }).forEach(function (objName) {
159 return featuresUsed.add(objName + '.' + method);
160 });
161 }
162}
163
164// Parse a given file for any features used
165function parseFile(filePath) {
166 return new Promise(function (resolve, reject) {
167 // first read the file
168 fs.readFile(filePath, function (err, content) {
169 return err ? reject(err) : resolve(content);
170 });
171 }).
172 // Then parse the content
173 then(function (content) {
174 return acorn.parse_dammit(content.toString(), {});
175 })
176 // Then walk the AST
177 .then(function (ast) {
178 return walker.simple(ast, visitors, handlers);
179 });
180}
181
182module.exports = function detectFeatures(globs) {
183 // Clear the tests before parsing the files
184 featuresUsed.clear();
185
186 return featureParser.read()
187 // Get the files requested
188 .then(function (feats) {
189 objects = feats.objects;
190 methods = feats.methods;
191
192 return globby(globs);
193 })
194 // Parse them
195 .then(function (files) {
196 return Promise.all(files.map(function (file) {
197 return parseFile(file);
198 }));
199 })
200 // Return the test results
201 .then(function () {
202 return Array.from(featuresUsed);
203 });
204};