1 | /*
|
---|
2 | MIT License http://www.opensource.org/licenses/mit-license.php
|
---|
3 | Author Tobias Koppers @sokra
|
---|
4 | */
|
---|
5 |
|
---|
6 | "use strict";
|
---|
7 |
|
---|
8 | const Template = require("../Template");
|
---|
9 |
|
---|
10 | /** @typedef {import("eslint-scope").Scope} Scope */
|
---|
11 | /** @typedef {import("eslint-scope").Reference} Reference */
|
---|
12 | /** @typedef {import("eslint-scope").Variable} Variable */
|
---|
13 | /** @typedef {import("estree").Node} Node */
|
---|
14 | /** @typedef {import("../javascript/JavascriptParser").Range} Range */
|
---|
15 | /** @typedef {import("../javascript/JavascriptParser").Program} Program */
|
---|
16 | /** @typedef {Set<string>} UsedNames */
|
---|
17 |
|
---|
18 | const DEFAULT_EXPORT = "__WEBPACK_DEFAULT_EXPORT__";
|
---|
19 | const NAMESPACE_OBJECT_EXPORT = "__WEBPACK_NAMESPACE_OBJECT__";
|
---|
20 |
|
---|
21 | /**
|
---|
22 | * @param {Variable} variable variable
|
---|
23 | * @returns {Reference[]} references
|
---|
24 | */
|
---|
25 | const getAllReferences = variable => {
|
---|
26 | let set = variable.references;
|
---|
27 | // Look for inner scope variables too (like in class Foo { t() { Foo } })
|
---|
28 | const identifiers = new Set(variable.identifiers);
|
---|
29 | for (const scope of variable.scope.childScopes) {
|
---|
30 | for (const innerVar of scope.variables) {
|
---|
31 | if (innerVar.identifiers.some(id => identifiers.has(id))) {
|
---|
32 | set = set.concat(innerVar.references);
|
---|
33 | break;
|
---|
34 | }
|
---|
35 | }
|
---|
36 | }
|
---|
37 | return set;
|
---|
38 | };
|
---|
39 |
|
---|
40 | /**
|
---|
41 | * @param {Node | Node[]} ast ast
|
---|
42 | * @param {Node} node node
|
---|
43 | * @returns {undefined | Node[]} result
|
---|
44 | */
|
---|
45 | const getPathInAst = (ast, node) => {
|
---|
46 | if (ast === node) {
|
---|
47 | return [];
|
---|
48 | }
|
---|
49 |
|
---|
50 | const nr = /** @type {Range} */ (node.range);
|
---|
51 |
|
---|
52 | /**
|
---|
53 | * @param {Node} n node
|
---|
54 | * @returns {Node[] | undefined} result
|
---|
55 | */
|
---|
56 | const enterNode = n => {
|
---|
57 | if (!n) return;
|
---|
58 | const r = n.range;
|
---|
59 | if (r && r[0] <= nr[0] && r[1] >= nr[1]) {
|
---|
60 | const path = getPathInAst(n, node);
|
---|
61 | if (path) {
|
---|
62 | path.push(n);
|
---|
63 | return path;
|
---|
64 | }
|
---|
65 | }
|
---|
66 | };
|
---|
67 |
|
---|
68 | if (Array.isArray(ast)) {
|
---|
69 | for (let i = 0; i < ast.length; i++) {
|
---|
70 | const enterResult = enterNode(ast[i]);
|
---|
71 | if (enterResult !== undefined) return enterResult;
|
---|
72 | }
|
---|
73 | } else if (ast && typeof ast === "object") {
|
---|
74 | const keys =
|
---|
75 | /** @type {Array<keyof Node>} */
|
---|
76 | (Object.keys(ast));
|
---|
77 | for (let i = 0; i < keys.length; i++) {
|
---|
78 | // We are making the faster check in `enterNode` using `n.range`
|
---|
79 | const value =
|
---|
80 | ast[
|
---|
81 | /** @type {Exclude<keyof Node, "range" | "loc" | "leadingComments" | "trailingComments">} */
|
---|
82 | (keys[i])
|
---|
83 | ];
|
---|
84 | if (Array.isArray(value)) {
|
---|
85 | const pathResult = getPathInAst(value, node);
|
---|
86 | if (pathResult !== undefined) return pathResult;
|
---|
87 | } else if (value && typeof value === "object") {
|
---|
88 | const enterResult = enterNode(value);
|
---|
89 | if (enterResult !== undefined) return enterResult;
|
---|
90 | }
|
---|
91 | }
|
---|
92 | }
|
---|
93 | };
|
---|
94 |
|
---|
95 | /**
|
---|
96 | * @param {string} oldName old name
|
---|
97 | * @param {UsedNames} usedNamed1 used named 1
|
---|
98 | * @param {UsedNames} usedNamed2 used named 2
|
---|
99 | * @param {string} extraInfo extra info
|
---|
100 | * @returns {string} found new name
|
---|
101 | */
|
---|
102 | function findNewName(oldName, usedNamed1, usedNamed2, extraInfo) {
|
---|
103 | let name = oldName;
|
---|
104 |
|
---|
105 | if (name === DEFAULT_EXPORT) {
|
---|
106 | name = "";
|
---|
107 | }
|
---|
108 | if (name === NAMESPACE_OBJECT_EXPORT) {
|
---|
109 | name = "namespaceObject";
|
---|
110 | }
|
---|
111 |
|
---|
112 | // Remove uncool stuff
|
---|
113 | extraInfo = extraInfo.replace(
|
---|
114 | /\.+\/|(\/index)?\.([a-zA-Z0-9]{1,4})($|\s|\?)|\s*\+\s*\d+\s*modules/g,
|
---|
115 | ""
|
---|
116 | );
|
---|
117 |
|
---|
118 | const splittedInfo = extraInfo.split("/");
|
---|
119 | while (splittedInfo.length) {
|
---|
120 | name = splittedInfo.pop() + (name ? `_${name}` : "");
|
---|
121 | const nameIdent = Template.toIdentifier(name);
|
---|
122 | if (
|
---|
123 | !usedNamed1.has(nameIdent) &&
|
---|
124 | (!usedNamed2 || !usedNamed2.has(nameIdent))
|
---|
125 | )
|
---|
126 | return nameIdent;
|
---|
127 | }
|
---|
128 |
|
---|
129 | let i = 0;
|
---|
130 | let nameWithNumber = Template.toIdentifier(`${name}_${i}`);
|
---|
131 | while (
|
---|
132 | usedNamed1.has(nameWithNumber) ||
|
---|
133 | // eslint-disable-next-line no-unmodified-loop-condition
|
---|
134 | (usedNamed2 && usedNamed2.has(nameWithNumber))
|
---|
135 | ) {
|
---|
136 | i++;
|
---|
137 | nameWithNumber = Template.toIdentifier(`${name}_${i}`);
|
---|
138 | }
|
---|
139 | return nameWithNumber;
|
---|
140 | }
|
---|
141 |
|
---|
142 | /**
|
---|
143 | * @param {Scope | null} s scope
|
---|
144 | * @param {UsedNames} nameSet name set
|
---|
145 | * @param {TODO} scopeSet1 scope set 1
|
---|
146 | * @param {TODO} scopeSet2 scope set 2
|
---|
147 | */
|
---|
148 | const addScopeSymbols = (s, nameSet, scopeSet1, scopeSet2) => {
|
---|
149 | let scope = s;
|
---|
150 | while (scope) {
|
---|
151 | if (scopeSet1.has(scope)) break;
|
---|
152 | if (scopeSet2.has(scope)) break;
|
---|
153 | scopeSet1.add(scope);
|
---|
154 | for (const variable of scope.variables) {
|
---|
155 | nameSet.add(variable.name);
|
---|
156 | }
|
---|
157 | scope = scope.upper;
|
---|
158 | }
|
---|
159 | };
|
---|
160 |
|
---|
161 | const RESERVED_NAMES = new Set(
|
---|
162 | [
|
---|
163 | // internal names (should always be renamed)
|
---|
164 | DEFAULT_EXPORT,
|
---|
165 | NAMESPACE_OBJECT_EXPORT,
|
---|
166 |
|
---|
167 | // keywords
|
---|
168 | "abstract,arguments,async,await,boolean,break,byte,case,catch,char,class,const,continue",
|
---|
169 | "debugger,default,delete,do,double,else,enum,eval,export,extends,false,final,finally,float",
|
---|
170 | "for,function,goto,if,implements,import,in,instanceof,int,interface,let,long,native,new,null",
|
---|
171 | "package,private,protected,public,return,short,static,super,switch,synchronized,this,throw",
|
---|
172 | "throws,transient,true,try,typeof,var,void,volatile,while,with,yield",
|
---|
173 |
|
---|
174 | // commonjs/amd
|
---|
175 | "module,__dirname,__filename,exports,require,define",
|
---|
176 |
|
---|
177 | // js globals
|
---|
178 | "Array,Date,eval,function,hasOwnProperty,Infinity,isFinite,isNaN,isPrototypeOf,length,Math",
|
---|
179 | "NaN,name,Number,Object,prototype,String,Symbol,toString,undefined,valueOf",
|
---|
180 |
|
---|
181 | // browser globals
|
---|
182 | "alert,all,anchor,anchors,area,assign,blur,button,checkbox,clearInterval,clearTimeout",
|
---|
183 | "clientInformation,close,closed,confirm,constructor,crypto,decodeURI,decodeURIComponent",
|
---|
184 | "defaultStatus,document,element,elements,embed,embeds,encodeURI,encodeURIComponent,escape",
|
---|
185 | "event,fileUpload,focus,form,forms,frame,innerHeight,innerWidth,layer,layers,link,location",
|
---|
186 | "mimeTypes,navigate,navigator,frames,frameRate,hidden,history,image,images,offscreenBuffering",
|
---|
187 | "open,opener,option,outerHeight,outerWidth,packages,pageXOffset,pageYOffset,parent,parseFloat",
|
---|
188 | "parseInt,password,pkcs11,plugin,prompt,propertyIsEnum,radio,reset,screenX,screenY,scroll",
|
---|
189 | "secure,select,self,setInterval,setTimeout,status,submit,taint,text,textarea,top,unescape",
|
---|
190 | "untaint,window",
|
---|
191 |
|
---|
192 | // window events
|
---|
193 | "onblur,onclick,onerror,onfocus,onkeydown,onkeypress,onkeyup,onmouseover,onload,onmouseup,onmousedown,onsubmit"
|
---|
194 | ]
|
---|
195 | .join(",")
|
---|
196 | .split(",")
|
---|
197 | );
|
---|
198 |
|
---|
199 | /**
|
---|
200 | * @param {Map<string, { usedNames: UsedNames, alreadyCheckedScopes: Set<TODO> }>} usedNamesInScopeInfo used names in scope info
|
---|
201 | * @param {string} module module identifier
|
---|
202 | * @param {string} id export id
|
---|
203 | * @returns {{ usedNames: UsedNames, alreadyCheckedScopes: Set<TODO> }} info
|
---|
204 | */
|
---|
205 | const getUsedNamesInScopeInfo = (usedNamesInScopeInfo, module, id) => {
|
---|
206 | const key = `${module}-${id}`;
|
---|
207 | let info = usedNamesInScopeInfo.get(key);
|
---|
208 | if (info === undefined) {
|
---|
209 | info = {
|
---|
210 | usedNames: new Set(),
|
---|
211 | alreadyCheckedScopes: new Set()
|
---|
212 | };
|
---|
213 | usedNamesInScopeInfo.set(key, info);
|
---|
214 | }
|
---|
215 | return info;
|
---|
216 | };
|
---|
217 |
|
---|
218 | module.exports = {
|
---|
219 | getUsedNamesInScopeInfo,
|
---|
220 | findNewName,
|
---|
221 | getAllReferences,
|
---|
222 | getPathInAst,
|
---|
223 | NAMESPACE_OBJECT_EXPORT,
|
---|
224 | DEFAULT_EXPORT,
|
---|
225 | RESERVED_NAMES,
|
---|
226 | addScopeSymbols
|
---|
227 | };
|
---|