eslint-plugin-unicorn
Version:
Various awesome ESLint rules
106 lines (83 loc) • 3.23 kB
JavaScript
const reservedWords = require('reserved-words');
const resolveVariableName = require('./resolve-variable-name');
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;
};
;