forked from sindresorhus/eslint-plugin-unicorn
-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
avoid-capture.js
105 lines (83 loc) · 3.15 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
'use strict';
const reservedWords = require('reserved-words');
const resolveVariableName = require('./resolve-variable-name.js');
const indexifyName = (name, index) => name + '_'.repeat(index);
const scopeHasArgumentsSpecial = scope => {
while (scope) {
/* istanbul ignore next: `someScopeHasVariableName` seems already handle this */
if (scope.taints.get('arguments')) {
return true;
}
scope = scope.upper;
}
return false;
};
const someScopeHasVariableName = (name, scopes) => scopes.some(scope => resolveVariableName(name, scope));
const someScopeIsStrict = scopes => scopes.some(scope => scope.isStrict);
const nameCollidesWithArgumentsSpecial = (name, scopes, isStrict) => {
if (name !== 'arguments') {
return false;
}
return isStrict ?? scopes.some(scope => scopeHasArgumentsSpecial(scope));
};
/*
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, scopes) => scopes.some(scope =>
scope.references.some(reference => reference.identifier && reference.identifier.name === name && !reference.resolved) ??
isUnresolvedName(name, scope.childScopes)
);
const isSafeName = (name, scopes, ecmaVersion, isStrict) => {
ecmaVersion = Math.min(6, ecmaVersion); // 6 is the latest version understood by `reservedWords`
return (
!someScopeHasVariableName(name, scopes) &&
!reservedWords.check(name, ecmaVersion, isStrict) &&
!nameCollidesWithArgumentsSpecial(name, scopes, isStrict) &&
!isUnresolvedName(name, scopes)
);
};
const alwaysTrue = () => true;
/**
Rule-specific name check function.
@callback isSafe
@param {string} indexifiedName - The generated candidate name.
@param {Scope[]} scopes - The same list of scopes you pass to `avoidCapture`.
@returns {boolean} - `true` if the `indexifiedName` 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 {number} ecmaVersion - The language version, get it from `context.parserOptions.ecmaVersion`.
@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, ecmaVersion, isSafe = alwaysTrue) => {
const isStrict = someScopeIsStrict(scopes);
let index = 0;
let indexifiedName = indexifyName(name, index);
while (
!isSafeName(indexifiedName, scopes, ecmaVersion, isStrict) ||
!isSafe(indexifiedName, scopes)
) {
index++;
indexifiedName = indexifyName(name, index);
}
return indexifiedName;
};