1 | 'use strict';
|
2 |
|
3 | var fs = require('fs');
|
4 | var globby = require('globby');
|
5 |
|
6 | var acorn = require('acorn/dist/acorn_loose');
|
7 | var walker = require('acorn/dist/walk');
|
8 |
|
9 |
|
10 | var visitors = {};
|
11 | var handlers = Object.assign({}, walker.base);
|
12 | Object.keys(walker.base).forEach(function (key) {
|
13 | visitors[key] = visitNode;
|
14 | });
|
15 |
|
16 |
|
17 | var featureParser = require('./featureTestParser');
|
18 |
|
19 |
|
20 | var featuresUsed = new Set();
|
21 | var variables = new Map();
|
22 | var objects = void 0;
|
23 | var methods = void 0;
|
24 |
|
25 | function 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 |
|
37 | function 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 |
|
50 |
|
51 | function 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 |
|
74 | function 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 |
|
89 |
|
90 | function 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 |
|
104 | if (node.type === 'AssignmentExpression') {
|
105 | return registerVarVal(node.left, node.right);
|
106 | }
|
107 |
|
108 | var caller = node.callee;
|
109 |
|
110 |
|
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 |
|
119 | if (!obj && !prop) {
|
120 | if (objects.has(caller.name)) {
|
121 | featuresUsed.add(caller.name);
|
122 | }
|
123 | return;
|
124 | }
|
125 |
|
126 |
|
127 | if (objects.has(obj.name)) {
|
128 | featuresUsed.add(obj.name);
|
129 | }
|
130 |
|
131 |
|
132 |
|
133 | if (!prop || prop && !methods.has(prop.name)) {
|
134 | return;
|
135 | }
|
136 |
|
137 | var method = prop.name;
|
138 |
|
139 |
|
140 | if (obj.type === 'ArrayExpression') {
|
141 | if (objects.has('Array')) {
|
142 | featuresUsed.add('Array.' + method);
|
143 | }
|
144 |
|
145 |
|
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 |
|
165 | function parseFile(filePath) {
|
166 | return new Promise(function (resolve, reject) {
|
167 |
|
168 | fs.readFile(filePath, function (err, content) {
|
169 | return err ? reject(err) : resolve(content);
|
170 | });
|
171 | }).
|
172 |
|
173 | then(function (content) {
|
174 | return acorn.parse_dammit(content.toString(), {});
|
175 | })
|
176 |
|
177 | .then(function (ast) {
|
178 | return walker.simple(ast, visitors, handlers);
|
179 | });
|
180 | }
|
181 |
|
182 | module.exports = function detectFeatures(globs) {
|
183 |
|
184 | featuresUsed.clear();
|
185 |
|
186 | return featureParser.read()
|
187 |
|
188 | .then(function (feats) {
|
189 | objects = feats.objects;
|
190 | methods = feats.methods;
|
191 |
|
192 | return globby(globs);
|
193 | })
|
194 |
|
195 | .then(function (files) {
|
196 | return Promise.all(files.map(function (file) {
|
197 | return parseFile(file);
|
198 | }));
|
199 | })
|
200 |
|
201 | .then(function () {
|
202 | return Array.from(featuresUsed);
|
203 | });
|
204 | };
|