-
Notifications
You must be signed in to change notification settings - Fork 28
/
parseJavaScriptDeps.ts
144 lines (126 loc) · 4.4 KB
/
parseJavaScriptDeps.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
import { parse } from './jsParser';
import {
Program,
SimpleCallExpression,
ArrayExpression,
Literal,
} from 'estree';
import * as esquery from 'esquery';
import { ParserResult } from './types';
/**
* @summary Statically analyze a JavaScript file for all
* declared dependencies. Supports:
* - AMD `define` calls with deps array
* - AMD `require` calls with deps array
* @param fromPHP If true, will use a loose parser that can better
* handle PHP interpolations in the code
*/
export function parseJavaScriptDeps(
input: string,
fromPHP?: boolean,
): ParserResult {
const ast = parse(input, { loose: !!fromPHP });
const defineData = getAMDDefineDeps(ast);
const asyncRequireData = getAMDAsyncRequireDeps(ast);
const syncRequireData = getAMDSyncRequireDeps(ast);
const deps = Array.from(
new Set([
...defineData.deps,
...asyncRequireData.deps,
...syncRequireData.deps,
]),
);
const incompleteAnalysis =
defineData.incompleteAnalysis ||
asyncRequireData.incompleteAnalysis ||
syncRequireData.incompleteAnalysis;
return {
deps,
incompleteAnalysis,
};
}
/**
* @summary Statically analyze dependencies for AMD `define` calls.
* Supports both named and anonymous modules
*/
function getAMDDefineDeps(ast: Program) {
const selector = 'CallExpression[callee.name=define]';
const defineCalls = esquery.query(ast, selector) as SimpleCallExpression[];
let incompleteAnalysis = false;
const deps: string[] = [];
for (const call of defineCalls) {
const [firstArg, secondArg] = call.arguments;
// Anonymous AMD module with dependencies
if (firstArg && firstArg.type === 'ArrayExpression') {
const results = extractDepsFromArrayExpression(firstArg);
deps.push(...results.deps);
if (results.incompleteAnalysis) incompleteAnalysis = true;
}
// Named AMD module with dependencies
if (secondArg && secondArg.type === 'ArrayExpression') {
const results = extractDepsFromArrayExpression(secondArg);
deps.push(...results.deps);
if (results.incompleteAnalysis) incompleteAnalysis = true;
}
}
return { deps, incompleteAnalysis };
}
/**
* @summary Statically analyze dependencies for AMD `require` calls.
* Supports the following forms:
* - require(['dep'], function(dep) {})
* - require(['dep']);
*/
function getAMDAsyncRequireDeps(ast: Program) {
const selector =
'CallExpression[callee.name=require][arguments.0.type=ArrayExpression]';
const requireCalls = esquery.query(ast, selector) as SimpleCallExpression[];
let incompleteAnalysis = false;
const deps: string[] = [];
for (const call of requireCalls) {
const firstArg = call.arguments[0] as ArrayExpression;
const results = extractDepsFromArrayExpression(firstArg);
deps.push(...results.deps);
if (results.incompleteAnalysis) incompleteAnalysis = true;
}
return { deps, incompleteAnalysis };
}
function getAMDSyncRequireDeps(ast: Program) {
const selector =
'CallExpression[callee.name=define] CallExpression[callee.name=require][arguments.0.type=Literal]';
const syncRequireCalls = esquery.query(
ast,
selector,
) as SimpleCallExpression[];
let incompleteAnalysis = false;
const deps: string[] = [];
for (const call of syncRequireCalls) {
const firstArg = call.arguments[0] as Literal;
if (typeof firstArg.value === 'string') {
deps.push(firstArg.value);
} else {
incompleteAnalysis = true;
}
}
return { deps, incompleteAnalysis };
}
/**
* @summary Statically analzye dependencies in an array literal.
* Marks as "incompleteAnalysis" for any dep that is not a string literal
*/
function extractDepsFromArrayExpression(node: ArrayExpression) {
const deps: string[] = [];
let incompleteAnalysis = false;
for (const e of node.elements) {
if (e.type === 'Literal' && typeof e.value === 'string') {
deps.push(e.value);
} else {
incompleteAnalysis = true;
}
}
return { deps, incompleteAnalysis };
}