UNPKG

eslint-plugin-unicorn

Version:
106 lines (83 loc) 3.23 kB
'use strict'; 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; };