-
-
Notifications
You must be signed in to change notification settings - Fork 353
/
avoid-capture.js
146 lines (130 loc) · 3.27 KB
/
avoid-capture.js
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
145
146
'use strict';
const {
isIdentifierName,
isStrictReservedWord,
isKeyword
} = require('@babel/helper-validator-identifier');
const resolveVariableName = require('./resolve-variable-name.js');
// https://github.com/microsoft/TypeScript/issues/2536#issuecomment-87194347
const typescriptReservedWords = new Set([
'break',
'case',
'catch',
'class',
'const',
'continue',
'debugger',
'default',
'delete',
'do',
'else',
'enum',
'export',
'extends',
'false',
'finally',
'for',
'function',
'if',
'import',
'in',
'instanceof',
'new',
'null',
'return',
'super',
'switch',
'this',
'throw',
'true',
'try',
'typeof',
'var',
'void',
'while',
'with',
'as',
'implements',
'interface',
'let',
'package',
'private',
'protected',
'public',
'static',
'yield',
'any',
'boolean',
'constructor',
'declare',
'get',
'module',
'require',
'number',
'set',
'string',
'symbol',
'type',
'from',
'of'
]);
// Copied from https://github.com/babel/babel/blob/fce35af69101c6b316557e28abf60bdbf77d6a36/packages/babel-types/src/validators/isValidIdentifier.ts#L7
// Use this function instead of `require('@babel/types').isIdentifier`, since `@babel/helper-validator-identifier` package is much smaller
const isValidIdentifier = name =>
typeof name === 'string' &&
!isKeyword(name) &&
!isStrictReservedWord(name, true) &&
isIdentifierName(name) &&
name !== 'arguments' &&
!typescriptReservedWords.has(name);
/*
Unresolved reference is probably from the global scope. We should avoid using that name.
For example, like `foo` and `bar` below.
```
function unicorn() {
return foo;
}
function unicorn() {
return function() {
return bar;
};
}
```
*/
const isUnresolvedName = (name, scope) =>
scope.references.some(reference => reference.identifier && reference.identifier.name === name && !reference.resolved) ||
scope.childScopes.some(scope => isUnresolvedName(name, scope));
const isSafeName = (name, scopes) =>
!scopes.some(scope => resolveVariableName(name, scope) || isUnresolvedName(name, scope));
const alwaysTrue = () => true;
/**
Rule-specific name check function.
@callback isSafe
@param {string} name - The generated candidate name.
@param {Scope[]} scopes - The same list of scopes you pass to `avoidCapture`.
@returns {boolean} - `true` if the `name` is ok.
*/
/**
Generates a unique name prefixed with `name` such that:
- it is not defined in any of the `scopes`,
- it is not a reserved word,
- it is not `arguments` in strict scopes (where `arguments` is not allowed),
- it does not collide with the actual `arguments` (which is always defined in function scopes).
Useful when you want to rename a variable (or create a new variable) while being sure not to shadow any other variables in the code.
@param {string} name - The desired name for a new variable.
@param {Scope[]} scopes - The list of scopes the new variable will be referenced in.
@param {isSafe} [isSafe] - Rule-specific name check function.
@returns {string} - Either `name` as is, or a string like `${name}_` suffixed with underscores to make the name unique.
*/
module.exports = (name, scopes, isSafe = alwaysTrue) => {
if (!isValidIdentifier(name)) {
name += '_';
if (!isValidIdentifier(name)) {
return;
}
}
while (!isSafeName(name, scopes) || !isSafe(name, scopes)) {
name += '_';
}
return name;
};