eslint-plugin-unicorn
Advanced tools
Comparing version 8.0.2 to 9.0.0
@@ -42,3 +42,5 @@ 'use strict'; | ||
'unicorn/prefer-add-event-listener': 'error', | ||
'unicorn/prefer-event-key': 'error', | ||
'unicorn/prefer-exponentiation-operator': 'error', | ||
'unicorn/prefer-flat-map': 'error', | ||
'unicorn/prefer-includes': 'error', | ||
@@ -45,0 +47,0 @@ 'unicorn/prefer-node-append': 'error', |
{ | ||
"name": "eslint-plugin-unicorn", | ||
"version": "8.0.2", | ||
"version": "9.0.0", | ||
"description": "Various awesome ESLint rules", | ||
@@ -13,3 +13,3 @@ "license": "MIT", | ||
"engines": { | ||
"node": ">=6" | ||
"node": ">=8" | ||
}, | ||
@@ -48,14 +48,15 @@ "scripts": { | ||
"devDependencies": { | ||
"ava": "^1.1.0", | ||
"ava": "^1.4.1", | ||
"babel-eslint": "^10.0.0", | ||
"chalk": "^2.4.2", | ||
"coveralls": "^3.0.0", | ||
"del": "^3.0.0", | ||
"del": "^4.1.1", | ||
"eslint": "^5.12.0", | ||
"eslint-ava-rule-tester": "^3.0.0", | ||
"eslint-plugin-eslint-plugin": "2.1.0", | ||
"execa": "^1.0.0", | ||
"listr": "^0.14.1", | ||
"nyc": "^13.1.0", | ||
"nyc": "^14.1.1", | ||
"pify": "^4.0.1", | ||
"tempy": "^0.2.1", | ||
"tempy": "^0.3.0", | ||
"xo": "^0.24.0" | ||
@@ -70,3 +71,19 @@ }, | ||
] | ||
}, | ||
"xo": { | ||
"plugins": [ | ||
"eslint-plugin" | ||
], | ||
"extends": [ | ||
"plugin:eslint-plugin/all" | ||
], | ||
"overrides": [ | ||
{ | ||
"files": "rules/utils/*.js", | ||
"rules": { | ||
"eslint-plugin/require-meta-docs-url": "off" | ||
} | ||
} | ||
] | ||
} | ||
} |
@@ -60,3 +60,5 @@ # eslint-plugin-unicorn [![Build Status](https://travis-ci.org/sindresorhus/eslint-plugin-unicorn.svg?branch=master)](https://travis-ci.org/sindresorhus/eslint-plugin-unicorn) [![Coverage Status](https://coveralls.io/repos/github/sindresorhus/eslint-plugin-unicorn/badge.svg?branch=master)](https://coveralls.io/github/sindresorhus/eslint-plugin-unicorn?branch=master) | ||
"unicorn/prefer-add-event-listener": "error", | ||
"unicorn/prefer-event-key": "error", | ||
"unicorn/prefer-exponentiation-operator": "error", | ||
"unicorn/prefer-flat-map": "error", | ||
"unicorn/prefer-includes": "error", | ||
@@ -92,5 +94,5 @@ "unicorn/prefer-node-append": "error", | ||
- [no-console-spaces](docs/rules/no-console-spaces.md) - Do not use leading/trailing space between `console.log` parameters. *(fixable)* | ||
- [no-fn-reference-in-iterator](docs/rules/no-fn-reference-in-iterator.md) - Prevents passing a function reference directly to iterator methods. *(fixable)* | ||
- [no-for-loop](docs/rules/no-for-loop.md) - Do not use a `for` loop that can be replaced with a `for-of` loop. *(fixable)* | ||
- [no-hex-escape](docs/rules/no-hex-escape.md) - Enforce the use of unicode escapes instead of hexadecimal escapes. *(fixable)* | ||
- [no-fn-reference-in-iterator](docs/rules/no-fn-reference-in-iterator.md) - Prevent passing a function reference directly to iterator methods. *(fixable)* | ||
- [no-for-loop](docs/rules/no-for-loop.md) - Do not use a `for` loop that can be replaced with a `for-of` loop. *(partly fixable)* | ||
- [no-hex-escape](docs/rules/no-hex-escape.md) - Enforce the use of Unicode escapes instead of hexadecimal escapes. *(fixable)* | ||
- [no-new-buffer](docs/rules/no-new-buffer.md) - Enforce the use of `Buffer.from()` and `Buffer.alloc()` instead of the deprecated `new Buffer()`. *(fixable)* | ||
@@ -103,13 +105,15 @@ - [no-process-exit](docs/rules/no-process-exit.md) - Disallow `process.exit()`. | ||
- [number-literal-case](docs/rules/number-literal-case.md) - Enforce lowercase identifier and uppercase value for number literals. *(fixable)* | ||
- [prefer-add-event-listener](docs/rules/prefer-add-event-listener.md) - Prefer `addEventListener` over `on`-functions. *(fixable)* | ||
- [prefer-add-event-listener](docs/rules/prefer-add-event-listener.md) - Prefer `.addEventListener()` and `.removeEventListener()` over `on`-functions. *(partly fixable)* | ||
- [prefer-event-key](docs/rules/prefer-event-key.md) - Prefer `KeyboardEvent#key` over `KeyboardEvent#keyCode`. *(partly fixable)* | ||
- [prefer-exponentiation-operator](docs/rules/prefer-exponentiation-operator.md) - Prefer the exponentiation operator over `Math.pow()` *(fixable)* | ||
- [prefer-flat-map](docs/rules/prefer-flat-map.md) - Prefer `.flatMap(…)` over `.map(…).flat()`. *(fixable)* | ||
- [prefer-includes](docs/rules/prefer-includes.md) - Prefer `.includes()` over `.indexOf()` when checking for existence or non-existence. *(fixable)* | ||
- [prefer-node-append](docs/rules/prefer-node-append.md) - Prefer `append` over `appendChild`. *(fixable)* | ||
- [prefer-node-remove](docs/rules/prefer-node-remove.md) - Prefer `remove` over `parentNode.removeChild` and `parentElement.removeChild`. *(fixable)* | ||
- [prefer-query-selector](docs/rules/prefer-query-selector.md) - Prefer `querySelector` over `getElementById`, `querySelectorAll` over `getElementsByClassName` and `getElementsByTagName`. *(partly fixable)* | ||
- [prefer-node-append](docs/rules/prefer-node-append.md) - Prefer `Node#append()` over `Node#appendChild()`. *(fixable)* | ||
- [prefer-node-remove](docs/rules/prefer-node-remove.md) - Prefer `node.remove()` over `parentNode.removeChild(node)` and `parentElement.removeChild(node)`. *(fixable)* | ||
- [prefer-query-selector](docs/rules/prefer-query-selector.md) - Prefer `.querySelector()` over `.getElementById()`, `.querySelectorAll()` over `.getElementsByClassName()` and `.getElementsByTagName()`. *(partly fixable)* | ||
- [prefer-spread](docs/rules/prefer-spread.md) - Prefer the spread operator over `Array.from()`. *(fixable)* | ||
- [prefer-starts-ends-with](docs/rules/prefer-starts-ends-with.md) - Prefer `String#startsWith` & `String#endsWith` over more complex alternatives. | ||
- [prefer-text-content](docs/rules/prefer-text-content.md) - Prefer `textContent` over `innerText`. *(fixable)* | ||
- [prefer-starts-ends-with](docs/rules/prefer-starts-ends-with.md) - Prefer `String#startsWith()` & `String#endsWith()` over more complex alternatives. | ||
- [prefer-text-content](docs/rules/prefer-text-content.md) - Prefer `.textContent` over `.innerText`. *(fixable)* | ||
- [prefer-type-error](docs/rules/prefer-type-error.md) - Enforce throwing `TypeError` in type checking conditions. *(fixable)* | ||
- [prevent-abbreviations](docs/rules/prevent-abbreviations.md) - Prevent abbreviations *(partly fixable)* | ||
- [prevent-abbreviations](docs/rules/prevent-abbreviations.md) - Prevent abbreviations. *(partly fixable)* | ||
- [regex-shorthand](docs/rules/regex-shorthand.md) - Enforce the use of regex shorthands to improve readability. *(fixable)* | ||
@@ -142,9 +146,12 @@ - [throw-new-error](docs/rules/throw-new-error.md) - Require `new` when throwing an error. *(fixable)* | ||
- [Sindre Sorhus](https://github.com/sindresorhus) | ||
- [Jeroen Engels](https://github.com/jfmengels) | ||
- [Sam Verschueren](https://github.com/SamVerschueren) | ||
- [futpib](https://github.com/futpib) | ||
###### Former | ||
- [Jeroen Engels](https://github.com/jfmengels) | ||
## License | ||
MIT |
'use strict'; | ||
const astUtils = require('eslint-ast-utils'); | ||
const avoidCapture = require('./utils/avoid-capture'); | ||
const getDocsUrl = require('./utils/get-docs-url'); | ||
@@ -28,19 +29,12 @@ | ||
// TODO: Use `./utils/avoid-capture.js` instead | ||
function indexifyName(name, scope) { | ||
const variables = scope.variableScope.set; | ||
const create = context => { | ||
const { | ||
ecmaVersion | ||
} = context.parserOptions; | ||
let index = 1; | ||
while (variables.has(index === 1 ? name : name + index)) { | ||
index++; | ||
} | ||
return name + (index === 1 ? '' : index); | ||
} | ||
const create = context => { | ||
const options = Object.assign({}, { | ||
const options = { | ||
name: 'error', | ||
caughtErrorsIgnorePattern: '^_$' | ||
}, context.options[0]); | ||
caughtErrorsIgnorePattern: '^_$', | ||
...context.options[0] | ||
}; | ||
@@ -99,4 +93,5 @@ const {scopeManager} = context.getSourceCode(); | ||
const errName = indexifyName(name, context.getScope()); | ||
push(params.length === 0 || params[0].name === errName || errName); | ||
const scope = context.getScope(); | ||
const errorName = avoidCapture(name, [scope.variableScope], ecmaVersion); | ||
push(params.length === 0 || params[0].name === errorName || errorName); | ||
} | ||
@@ -122,3 +117,4 @@ }, | ||
const errName = indexifyName(name, context.getScope()); | ||
const scope = context.getScope(); | ||
const errName = avoidCapture(name, [scope.variableScope], ecmaVersion); | ||
push(node.param.name === errName || errName); | ||
@@ -125,0 +121,0 @@ }, |
@@ -54,3 +54,3 @@ 'use strict'; | ||
node: expressionNode.parent, | ||
message: 'Pass a message to the error constructor' | ||
message: 'Pass a message to the error constructor.' | ||
}); | ||
@@ -62,3 +62,3 @@ } | ||
node: expressionNode.parent, | ||
message: 'Error message should not be an empty string' | ||
message: 'Error message should not be an empty string.' | ||
}); | ||
@@ -65,0 +65,0 @@ } |
@@ -9,3 +9,3 @@ 'use strict'; | ||
const pascalCase = str => upperfirst(camelCase(str)); | ||
const pascalCase = string => upperfirst(camelCase(string)); | ||
const numberRegex = /(\d+)/; | ||
@@ -16,13 +16,13 @@ const PLACEHOLDER = '\uFFFF\uFFFF\uFFFF'; | ||
function ignoreNumbers(fn) { | ||
return str => { | ||
return string => { | ||
const stack = []; | ||
let execResult = numberRegex.exec(str); | ||
let execResult = numberRegex.exec(string); | ||
while (execResult) { | ||
stack.push(execResult[0]); | ||
str = str.replace(execResult[0], PLACEHOLDER); | ||
execResult = numberRegex.exec(str); | ||
string = string.replace(execResult[0], PLACEHOLDER); | ||
execResult = numberRegex.exec(string); | ||
} | ||
let withCase = fn(str); | ||
let withCase = fn(string); | ||
@@ -56,6 +56,29 @@ while (stack.length > 0) { | ||
/** | ||
Get the cases specified by the option. | ||
@param {unknown} context | ||
@returns {string[]} The chosen cases. | ||
*/ | ||
function getChosenCases(context) { | ||
const option = context.options[0] || {}; | ||
if (option.case) { | ||
return [option.case]; | ||
} | ||
if (option.cases) { | ||
const cases = Object.keys(option.cases) | ||
.filter(cases => option.cases[cases]); | ||
return cases.length > 0 ? cases : ['kebabCase']; | ||
} | ||
return ['kebabCase']; | ||
} | ||
function fixFilename(chosenCase, filename) { | ||
return filename | ||
.split('.') | ||
.map(ignoreNumbers(chosenCase.fn)) | ||
.map(ignoreNumbers(cases[chosenCase].fn)) | ||
.join('.'); | ||
@@ -73,9 +96,27 @@ } | ||
/** | ||
Turns `[a, b, c]` into `a, b, or c`. | ||
@param {string[]} words | ||
@returns {string} | ||
*/ | ||
function englishishJoinWords(words) { | ||
if (words.length === 1) { | ||
return words[0]; | ||
} | ||
if (words.length === 2) { | ||
return `${words[0]} or ${words[1]}`; | ||
} | ||
words = words.slice(); | ||
const last = words.pop(); | ||
return `${words.join(', ')}, or ${last}`; | ||
} | ||
const create = context => { | ||
const options = context.options[0] || {}; | ||
const chosenCases = getChosenCases(context); | ||
const filenameWithExtension = context.getFilename(); | ||
const chosenCase = cases[options.case || 'kebabCase']; | ||
const filenameWithExt = context.getFilename(); | ||
if (filenameWithExt === '<text>') { | ||
if (filenameWithExtension === '<text>') { | ||
return {}; | ||
@@ -86,4 +127,4 @@ } | ||
Program: node => { | ||
const extension = path.extname(filenameWithExt); | ||
const filename = path.basename(filenameWithExt, extension); | ||
const extension = path.extname(filenameWithExtension); | ||
const filename = path.basename(filenameWithExtension, extension); | ||
@@ -95,9 +136,13 @@ if (filename + extension === 'index.js') { | ||
const splitName = splitFilename(filename); | ||
const fixedFilename = fixFilename(chosenCase, splitName.trailing); | ||
const renameFilename = splitName.leading + fixedFilename + extension; | ||
const fixedFilenames = chosenCases.map(case_ => fixFilename(case_, splitName.trailing)); | ||
const renamedFilenames = fixedFilenames.map(x => splitName.leading + x + extension); | ||
if (fixedFilename !== splitName.trailing) { | ||
if (!fixedFilenames.includes(splitName.trailing)) { | ||
context.report({ | ||
node, | ||
message: `Filename is not in ${chosenCase.name}. Rename it to \`${renameFilename}\`.` | ||
messageId: chosenCases.length > 1 ? 'renameToCases' : 'renameToCase', | ||
data: { | ||
chosenCases: englishishJoinWords(chosenCases.map(x => cases[x].name)), | ||
renamedFilenames: englishishJoinWords(renamedFilenames.map(x => `\`${x}\``)) | ||
} | ||
}); | ||
@@ -110,13 +155,39 @@ } | ||
const schema = [{ | ||
type: 'object', | ||
properties: { | ||
case: { | ||
enum: [ | ||
'camelCase', | ||
'snakeCase', | ||
'kebabCase', | ||
'pascalCase' | ||
] | ||
oneOf: [ | ||
{ | ||
properties: { | ||
case: { | ||
enum: [ | ||
'camelCase', | ||
'snakeCase', | ||
'kebabCase', | ||
'pascalCase' | ||
] | ||
} | ||
}, | ||
additionalProperties: false | ||
}, | ||
{ | ||
properties: { | ||
cases: { | ||
properties: { | ||
camelCase: { | ||
type: 'boolean' | ||
}, | ||
snakeCase: { | ||
type: 'boolean' | ||
}, | ||
kebabCase: { | ||
type: 'boolean' | ||
}, | ||
pascalCase: { | ||
type: 'boolean' | ||
} | ||
}, | ||
additionalProperties: false | ||
} | ||
}, | ||
additionalProperties: false | ||
} | ||
} | ||
] | ||
}]; | ||
@@ -131,4 +202,8 @@ | ||
}, | ||
schema | ||
schema, | ||
messages: { | ||
renameToCase: 'Filename is not in {{chosenCases}}. Rename it to {{renamedFilenames}}.', | ||
renameToCases: 'Filename is not in {{chosenCases}}. Rename it to {{renamedFilenames}}.' | ||
} | ||
} | ||
}; |
'use strict'; | ||
const getDocsUrl = require('./utils/get-docs-url'); | ||
const regexp = /^(@.*?\/.*?|[./]+?.*?)(?:\/(?:index(?:\.js)?)?)$/; | ||
const isImportingIndex = m => regexp.test(m); | ||
const normalize = m => m.replace(regexp, '$1'); | ||
const regexp = /^(@.*?\/.*?|[./]+?.*?)(?:\/(\.|(?:index(?:\.js)?))?)$/; | ||
const isImportingIndex = value => regexp.test(value); | ||
const normalize = value => value.replace(regexp, '$1'); | ||
const importIndex = (context, node, m) => { | ||
if (isImportingIndex(m.value)) { | ||
const importIndex = (context, node, argument) => { | ||
if (isImportingIndex(argument.value)) { | ||
context.report({ | ||
node, | ||
message: 'Do not reference the index file directly', | ||
fix: fixer => fixer.replaceText(m, `'${normalize(m.value)}'`) | ||
message: 'Do not reference the index file directly.', | ||
fix: fixer => fixer.replaceText(argument, `'${normalize(argument.value)}'`) | ||
}); | ||
@@ -15,0 +15,0 @@ } |
@@ -36,6 +36,6 @@ 'use strict'; | ||
const numberOfArgs = getNumberOfArguments(node); | ||
const arg = node.arguments[0]; | ||
const argString = numberOfArgs === 1 ? 'x' : 'a, b'; | ||
const argument = node.arguments[0]; | ||
const argumentString = numberOfArgs === 1 ? 'x' : 'a, b'; | ||
return fixer => fixer.replaceText(arg, `${numberOfArgs === 1 ? argString : `(${argString})`} => ${parseArgument(context, arg)}(${argString})`); | ||
return fixer => fixer.replaceText(argument, `${numberOfArgs === 1 ? argumentString : `(${argumentString})`} => ${parseArgument(context, argument)}(${argumentString})`); | ||
}; | ||
@@ -42,0 +42,0 @@ |
'use strict'; | ||
const getDocsUrl = require('./utils/get-docs-url'); | ||
const defaultElementName = 'element'; | ||
const isLiteralValue = value => node => node && node.type === 'Literal' && node.value === value; | ||
@@ -88,50 +89,2 @@ const isLiteralZero = isLiteralValue(0); | ||
const isMatchingMemberExpression = (memberExpression, objectIdentifierName, propertyIdentifierName) => { | ||
if (!memberExpression || memberExpression.type !== 'MemberExpression') { | ||
return false; | ||
} | ||
const {object, property} = memberExpression; | ||
return isIdentifierWithName(object, objectIdentifierName) && | ||
isIdentifierWithName(property, propertyIdentifierName); | ||
}; | ||
const getElementIdentifierInfo = (forStatement, arrayIdentifierName, indexIdentifierName) => { | ||
const {body} = forStatement; | ||
if (!body || | ||
body.type !== 'BlockStatement' | ||
) { | ||
return; | ||
} | ||
const [elementVariableDeclaration] = body.body; | ||
if (!elementVariableDeclaration || | ||
elementVariableDeclaration.type !== 'VariableDeclaration' | ||
) { | ||
return; | ||
} | ||
if (elementVariableDeclaration.declarations.length !== 1) { | ||
return; | ||
} | ||
const [elementVariableDeclarator] = elementVariableDeclaration.declarations; | ||
if (elementVariableDeclarator.id.type !== 'Identifier') { | ||
return; | ||
} | ||
if (!isMatchingMemberExpression(elementVariableDeclarator.init, arrayIdentifierName, indexIdentifierName)) { | ||
return; | ||
} | ||
return { | ||
elementVariableDeclaration, | ||
elementIdentifierName: elementVariableDeclarator.id.name | ||
}; | ||
}; | ||
const isLiteralOnePlusIdentifierWithName = (node, identifierName) => { | ||
@@ -170,16 +123,50 @@ if (node && node.type === 'BinaryExpression' && node.operator === '+') { | ||
const isOnlyArrayOfIndexVariableRead = (arrayReferences, indexIdentifierName) => { | ||
return arrayReferences.every(reference => { | ||
const node = reference.identifier.parent; | ||
if (node.type !== 'MemberExpression') { | ||
return false; | ||
} | ||
if (node.property.name !== indexIdentifierName) { | ||
return false; | ||
} | ||
if (node.parent.type === 'AssignmentExpression' && node.parent.left === node) { | ||
return false; | ||
} | ||
return true; | ||
}); | ||
}; | ||
const getRemovalRange = (node, sourceCode) => { | ||
const nodeText = sourceCode.getText(node); | ||
const {line} = sourceCode.getLocFromIndex(node.range[0]); | ||
const lineText = sourceCode.lines[line - 1]; | ||
const declarationNode = node.parent; | ||
const isOnlyNodeOnItsLine = lineText.trim() === nodeText; | ||
if (isOnlyNodeOnItsLine) { | ||
return [ | ||
if (declarationNode.declarations.length === 1) { | ||
const {line} = sourceCode.getLocFromIndex(declarationNode.range[0]); | ||
const lineText = sourceCode.lines[line - 1]; | ||
const isOnlyNodeOnLine = lineText.trim() === sourceCode.getText(declarationNode); | ||
return isOnlyNodeOnLine ? [ | ||
sourceCode.getIndexFromLoc({line, column: 0}), | ||
sourceCode.getIndexFromLoc({line: line + 1, column: 0}) | ||
] : declarationNode.range; | ||
} | ||
const index = declarationNode.declarations.indexOf(node); | ||
if (index === 0) { | ||
return [ | ||
node.range[0], | ||
declarationNode.declarations[1].range[0] | ||
]; | ||
} | ||
return node.range; | ||
return [ | ||
declarationNode.declarations[index - 1].range[1], | ||
node.range[1] | ||
]; | ||
}; | ||
@@ -225,8 +212,20 @@ | ||
const isIndexVariableUsedElsewhereInTheLoopBody = (indexVariable, bodyScope) => { | ||
const isIndexVariableUsedElsewhereInTheLoopBody = (indexVariable, bodyScope, arrayIdentifierName) => { | ||
const inBodyReferences = indexVariable.references.filter(reference => scopeContains(bodyScope, reference.from)); | ||
// One reference in the body would be the one in the element variable declaration like `const el = arr[i]`. | ||
// Any reference besides that should retain the index variable. | ||
return inBodyReferences.length > 1; | ||
const referencesOtherThanArrayAccess = inBodyReferences.filter(reference => { | ||
const node = reference.identifier.parent; | ||
if (node.type !== 'MemberExpression') { | ||
return true; | ||
} | ||
if (node.object.name !== arrayIdentifierName) { | ||
return true; | ||
} | ||
return false; | ||
}); | ||
return referencesOtherThanArrayAccess.length > 0; | ||
}; | ||
@@ -249,2 +248,12 @@ | ||
const getReferencesInChildScopes = (scope, name) => { | ||
const references = scope.references.filter(reference => reference.identifier.name === name); | ||
return [ | ||
...references, | ||
...scope.childScopes | ||
.map(s => getReferencesInChildScopes(s, name)) | ||
.reduce((acc, scopeReferences) => [...acc, ...scopeReferences], []) | ||
]; | ||
}; | ||
const create = context => { | ||
@@ -272,10 +281,25 @@ const sourceCode = context.getSourceCode(); | ||
const elementIdentifierInfo = getElementIdentifierInfo(node, arrayIdentifierName, indexIdentifierName); | ||
if (!node.body || node.body.type !== 'BlockStatement') { | ||
return; | ||
} | ||
if (!elementIdentifierInfo) { | ||
const forScope = scopeManager.acquire(node); | ||
const bodyScope = scopeManager.acquire(node.body); | ||
const indexVariable = resolveIdentifierName(indexIdentifierName, bodyScope); | ||
if (isIndexVariableAssignedToInTheLoopBody(indexVariable, bodyScope)) { | ||
return; | ||
} | ||
const {elementIdentifierName, elementVariableDeclaration} = elementIdentifierInfo; | ||
const arrayReferences = getReferencesInChildScopes(bodyScope, arrayIdentifierName); | ||
if (arrayReferences.length === 0) { | ||
return; | ||
} | ||
if (!isOnlyArrayOfIndexVariableRead(arrayReferences, indexIdentifierName)) { | ||
return; | ||
} | ||
const problem = { | ||
@@ -286,17 +310,23 @@ node, | ||
const forScope = scopeManager.acquire(node); | ||
const bodyScope = scopeManager.acquire(node.body); | ||
const elementReference = arrayReferences.find(reference => { | ||
const node = reference.identifier.parent; | ||
const indexVariable = resolveIdentifierName(indexIdentifierName, bodyScope); | ||
const elementVariable = resolveIdentifierName(elementIdentifierName, bodyScope); | ||
if (node.parent.type !== 'VariableDeclarator') { | ||
return false; | ||
} | ||
const shouldFix = !someVariablesLeakOutOfTheLoop(node, [indexVariable, elementVariable], forScope) && | ||
!isIndexVariableAssignedToInTheLoopBody(indexVariable, bodyScope); | ||
return true; | ||
}); | ||
const elementNode = elementReference && elementReference.identifier.parent.parent; | ||
const elementIdentifierName = elementNode && elementNode.id.name; | ||
const elementVariable = elementIdentifierName && resolveIdentifierName(elementIdentifierName, bodyScope); | ||
const shouldFix = !someVariablesLeakOutOfTheLoop(node, [indexVariable, elementVariable].filter(Boolean), forScope); | ||
if (shouldFix) { | ||
problem.fix = fixer => { | ||
const shouldGenerateIndex = isIndexVariableUsedElsewhereInTheLoopBody(indexVariable, bodyScope); | ||
const shouldGenerateIndex = isIndexVariableUsedElsewhereInTheLoopBody(indexVariable, bodyScope, arrayIdentifierName); | ||
const index = indexIdentifierName; | ||
const element = elementIdentifierName; | ||
const element = elementIdentifierName || defaultElementName; | ||
const array = arrayIdentifierName; | ||
@@ -313,5 +343,11 @@ | ||
], replacement), | ||
...arrayReferences.map(reference => { | ||
if (reference === elementReference) { | ||
return undefined; | ||
} | ||
fixer.removeRange(getRemovalRange(elementVariableDeclaration, sourceCode)) | ||
]; | ||
return fixer.replaceText(reference.identifier.parent, element); | ||
}), | ||
elementNode && fixer.removeRange(getRemovalRange(elementNode, sourceCode)) | ||
].filter(Boolean); | ||
}; | ||
@@ -318,0 +354,0 @@ } |
@@ -10,3 +10,3 @@ 'use strict'; | ||
node, | ||
message: 'Use unicode escapes instead of hexadecimal escapes.', | ||
message: 'Use Unicode escapes instead of hexadecimal escapes.', | ||
fix: fixer => fixer.replaceTextRange([node.start, node.end], fixedValue) | ||
@@ -13,0 +13,0 @@ }); |
@@ -11,3 +11,3 @@ 'use strict'; | ||
let processEventHandler = null; | ||
let processEventHandler; | ||
@@ -34,3 +34,3 @@ return { | ||
if (node === processEventHandler) { | ||
processEventHandler = null; | ||
processEventHandler = undefined; | ||
} | ||
@@ -37,0 +37,0 @@ } |
@@ -31,5 +31,4 @@ 'use strict'; | ||
let pattern = null; | ||
let flags = null; | ||
let pattern; | ||
let flags; | ||
if (hasRegExp) { | ||
@@ -36,0 +35,0 @@ ({pattern} = args[0].regex); |
@@ -10,5 +10,5 @@ 'use strict'; | ||
const indicator = value[1].toLowerCase(); | ||
const val = value.slice(2).toUpperCase(); | ||
const newValue = value.slice(2).toUpperCase(); | ||
return `0${indicator}${val}`; | ||
return `0${indicator}${newValue}`; | ||
}; | ||
@@ -15,0 +15,0 @@ |
@@ -15,14 +15,31 @@ 'use strict'; | ||
const parseArgument = (context, arg) => { | ||
if (arg.type === 'Identifier') { | ||
return arg.name; | ||
const parseArgument = (source, arg) => { | ||
const text = source.getText(arg); | ||
switch (arg.type) { | ||
case 'Identifier': | ||
return arg.name; | ||
case 'Literal': | ||
return text; | ||
case 'CallExpression': | ||
return text; | ||
case 'UnaryExpression': | ||
return text; | ||
default: | ||
// Handle cases like Math.pow(2, 2-1); | ||
return `(${text})`; | ||
} | ||
return context.getSourceCode().getText(arg); | ||
}; | ||
const fix = (context, node, fixer) => { | ||
const base = parseArgument(context, node.arguments[0]); | ||
const exponent = parseArgument(context, node.arguments[1]); | ||
const source = context.getSourceCode(); | ||
const comments = source.getCommentsInside(node); | ||
if (comments && comments.length > 0) { | ||
return; | ||
} | ||
const base = parseArgument(source, node.arguments[0]); | ||
const exponent = parseArgument(source, node.arguments[1]); | ||
const replacement = `${base} ** ${exponent}`; | ||
@@ -29,0 +46,0 @@ |
'use strict'; | ||
const getDocsUrl = require('./utils/get-docs-url'); | ||
const isMethodNamed = require('./utils/is-method-named'); | ||
const isIndexOf = node => { | ||
return ( | ||
node.type === 'CallExpression' && | ||
node.callee.type === 'MemberExpression' && | ||
node.callee.property.type === 'Identifier' && | ||
node.callee.property.name === 'indexOf' | ||
); | ||
}; | ||
const isNegativeOne = (operator, value) => operator === '-' && value === 1; | ||
const getSourceCode = (context, node) => ( | ||
context.getSourceCode().getText(node) | ||
); | ||
const report = (context, node, target, pattern) => { | ||
const sourceCode = context.getSourceCode(); | ||
const memberExpressionNode = target.parent; | ||
const dotToken = sourceCode.getTokenBefore(memberExpressionNode.property); | ||
const targetSource = sourceCode.getText().slice(memberExpressionNode.range[0], dotToken.range[0]); | ||
const patternSource = sourceCode.getText(pattern); | ||
const report = (context, node, target, pattern) => { | ||
const targetSource = getSourceCode(context, target); | ||
const patternSource = getSourceCode(context, pattern); | ||
context.report({ | ||
@@ -37,3 +29,3 @@ node, | ||
if (isIndexOf(left)) { | ||
if (isMethodNamed(left, 'indexOf')) { | ||
const target = left.callee.object; | ||
@@ -40,0 +32,0 @@ const pattern = left.arguments[0]; |
@@ -14,3 +14,3 @@ 'use strict'; | ||
node, | ||
message: 'Prefer `append` over `appendChild`', | ||
message: 'Prefer `Node#append()` over `Node#appendChild()`.', | ||
fix: fixer => fixer.replaceText(callee.property, 'append') | ||
@@ -17,0 +17,0 @@ }); |
@@ -70,3 +70,3 @@ 'use strict'; | ||
node, | ||
message: `Prefer \`remove\` over \`${callerName}.removeChild\``, | ||
message: `Prefer \`${argumentName}.remove()\` over \`${callerName}.removeChild(${argumentName})\`.`, | ||
fix: fixer => fixer.replaceText(node, `${argumentName}.remove()`) | ||
@@ -73,0 +73,0 @@ }); |
@@ -12,2 +12,3 @@ 'use strict'; | ||
const getReplacementForClass = value => value.match(/\S+/g).map(e => `.${e}`).join(''); | ||
const getQuotedReplacement = (node, value) => { | ||
@@ -98,3 +99,3 @@ const leftQuote = node.raw.charAt(0); | ||
node, | ||
message: `Prefer \`${preferedSelector}\` over \`${identifierName}\`.` | ||
message: `Prefer \`.${preferedSelector}()\` over \`.${identifierName}()\`.` | ||
}; | ||
@@ -101,0 +102,0 @@ |
@@ -15,10 +15,10 @@ 'use strict'; | ||
const isArrayLike = arg => arg && arg.type !== 'ObjectExpression'; | ||
const isArrayLike = argument => argument && argument.type !== 'ObjectExpression'; | ||
const parseArgument = (context, arg) => { | ||
if (arg.type === 'Identifier') { | ||
return arg.name; | ||
const parseArgument = (context, argument) => { | ||
if (argument.type === 'Identifier') { | ||
return argument.name; | ||
} | ||
return context.getSourceCode().getText(arg); | ||
return context.getSourceCode().getText(argument); | ||
}; | ||
@@ -25,0 +25,0 @@ |
'use strict'; | ||
const getDocsUrl = require('./utils/get-docs-url'); | ||
const doesNotContain = (string, chars) => chars.every(char => !string.includes(char)); | ||
const doesNotContain = (string, characters) => characters.every(character => !string.includes(character)); | ||
@@ -40,3 +40,3 @@ const isSimpleString = string => doesNotContain( | ||
node, | ||
message: 'Prefer `String#startsWith` over a regex with `^`.' | ||
message: 'Prefer `String#startsWith()` over a regex with `^`.' | ||
}); | ||
@@ -46,3 +46,3 @@ } else if (pattern.endsWith('$') && isSimpleString(pattern.slice(0, -1))) { | ||
node, | ||
message: 'Prefer `String#endsWith` over a regex with `$`.' | ||
message: 'Prefer `String#endsWith()` over a regex with `$`.' | ||
}); | ||
@@ -49,0 +49,0 @@ } |
'use strict'; | ||
const getDocsUrl = require('./utils/get-docs-url'); | ||
const message = 'Prefer `textContent` over `innerText`.'; | ||
const message = 'Prefer `.textContent` over `.innerText`.'; | ||
@@ -6,0 +6,0 @@ const create = context => { |
@@ -255,7 +255,8 @@ 'use strict'; | ||
/* | ||
* This function has terrible big O complexity, so we limit it by `limit`. | ||
* This is fine since result of the function is used only to check if there is zero, one or more | ||
* replacements and when formating the message. | ||
* Example: `[[1, 2], [3, 4]]` -> `[[1, 3], [1, 4], [2, 3], [2, 4]]` | ||
*/ | ||
This function has terrible big O complexity, so we limit it by `limit`. | ||
This is fine since result of the function is used only to check if there is zero, one or more replacements and when formating the message. | ||
Example: `[[1, 2], [3, 4]]` → `[[1, 3], [1, 4], [2, 3], [2, 4]]` | ||
*/ | ||
const getWordByWordReplacementsCombinations = (wordByWordReplacements, limit = 16) => { | ||
@@ -262,0 +263,0 @@ if (wordByWordReplacements.length === 0) { |
@@ -24,3 +24,3 @@ 'use strict'; | ||
message, | ||
fix: fixer => fixer.replaceTextRange(node.range, `/${newPattern}/${flags}`) | ||
fix: fixer => fixer.replaceText(node, `/${newPattern}/${flags}`) | ||
}); | ||
@@ -38,5 +38,4 @@ } | ||
let oldPattern = null; | ||
let flags = null; | ||
let oldPattern; | ||
let flags; | ||
if (hasRegExp) { | ||
@@ -57,3 +56,3 @@ oldPattern = args[0].regex.pattern; | ||
} else { | ||
// Escape backslash and apostrophe because we wrap the result in single quotes. | ||
// Escape backslash and apostrophe because we wrap the result in single quotes | ||
fixed = (newPattern || '').replace(/\\/, '\\\\'); | ||
@@ -67,3 +66,3 @@ fixed = fixed.replace(/'/, '\''); | ||
message, | ||
fix: fixer => fixer.replaceTextRange(args[0].range, fixed) | ||
fix: fixer => fixer.replaceText(args[0], fixed) | ||
}); | ||
@@ -70,0 +69,0 @@ } |
@@ -8,6 +8,6 @@ 'use strict'; | ||
ThrowStatement: node => { | ||
const arg = node.argument; | ||
const error = arg.callee; | ||
const {argument} = node; | ||
const error = argument.callee; | ||
if (arg.type === 'CallExpression' && customError.test(error.name)) { | ||
if (argument.type === 'CallExpression' && customError.test(error.name)) { | ||
context.report({ | ||
@@ -14,0 +14,0 @@ node, |
@@ -42,26 +42,25 @@ 'use strict'; | ||
/** | ||
* 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`. | ||
* @return {boolean} - `true` if the `indexifiedName` is ok. | ||
*/ | ||
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 undescores to make the name unique. | ||
*/ | ||
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 undescores to make the name unique. | ||
*/ | ||
module.exports = (name, scopes, ecmaVersion, isSafe = alwaysTrue) => { | ||
@@ -68,0 +67,0 @@ const isStrict = someScopeIsStrict(scopes); |
'use strict'; | ||
const path = require('path'); | ||
const pkg = require('../../package'); | ||
const packageJson = require('../../package'); | ||
@@ -9,3 +9,3 @@ const repoUrl = 'https://github.com/sindresorhus/eslint-plugin-unicorn'; | ||
const ruleName = path.basename(filename, '.js'); | ||
return `${repoUrl}/blob/v${pkg.version}/docs/rules/${ruleName}.md`; | ||
return `${repoUrl}/blob/v${packageJson.version}/docs/rules/${ruleName}.md`; | ||
}; |
'use strict'; | ||
/** | ||
* Finds a variable named `name` in the scope `scope` (or it's parents). | ||
* | ||
* @param {string} name - The variable name to be resolve. | ||
* @param {Scope} scope - The scope to look for the variable in. | ||
* @returns {?Variable} - The found variable, if any. | ||
*/ | ||
Finds a variable named `name` in the scope `scope` (or it's parents). | ||
@param {string} name - The variable name to be resolve. | ||
@param {Scope} scope - The scope to look for the variable in. | ||
@returns {Variable?} - The found variable, if any. | ||
*/ | ||
module.exports = (name, scope) => { | ||
@@ -11,0 +11,0 @@ while (scope) { |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
120453
46
3732
154
14