eslint-plugin-unicorn
Advanced tools
Comparing version 26.0.1 to 27.0.0
@@ -58,2 +58,4 @@ 'use strict'; | ||
'unicorn/no-array-callback-reference': 'error', | ||
'unicorn/no-array-for-each': 'error', | ||
'unicorn/no-array-push-push': 'error', | ||
'unicorn/no-array-reduce': 'error', | ||
@@ -73,2 +75,3 @@ 'unicorn/no-console-spaces': 'error', | ||
'unicorn/no-process-exit': 'error', | ||
'unicorn/no-this-assignment': 'error', | ||
'unicorn/no-unreadable-array-destructuring': 'error', | ||
@@ -75,0 +78,0 @@ 'unicorn/no-unsafe-regex': 'off', |
{ | ||
"name": "eslint-plugin-unicorn", | ||
"version": "26.0.1", | ||
"version": "27.0.0", | ||
"description": "Various awesome ESLint rules", | ||
@@ -19,3 +19,3 @@ "license": "MIT", | ||
"create-rule": "node ./scripts/create-rule.js", | ||
"lint": "node ./test/lint/lint.js", | ||
"run-rules-on-codebase": "node ./test/run-rules-on-codebase/lint.js", | ||
"integration": "node ./test/integration/test.js", | ||
@@ -41,3 +41,2 @@ "smoke": "eslint-remote-tester --config ./test/smoke/eslint-remote-tester.config.js" | ||
"clean-regexp": "^1.0.0", | ||
"eslint-ast-utils": "^1.1.0", | ||
"eslint-template-visitor": "^2.2.2", | ||
@@ -49,3 +48,3 @@ "eslint-utils": "^2.1.0", | ||
"read-pkg-up": "^7.0.1", | ||
"regexp-tree": "^0.1.21", | ||
"regexp-tree": "^0.1.22", | ||
"reserved-words": "^0.1.2", | ||
@@ -58,2 +57,4 @@ "safe-regex": "^2.1.1", | ||
"@babel/code-frame": "7.12.11", | ||
"@babel/core": "7.12.10", | ||
"@babel/eslint-parser": "7.12.1", | ||
"@lubien/fixture-beta-package": "^1.0.0-beta.1", | ||
@@ -105,3 +106,3 @@ "@typescript-eslint/parser": "^4.12.0", | ||
"ignores": [ | ||
"test/integration/{fixtures,unicorn}/**", | ||
"test/integration/{fixtures,fixtures-local}/**", | ||
".cache-eslint-remote-tester", | ||
@@ -108,0 +109,0 @@ "eslint-remote-tester-results" |
@@ -52,2 +52,4 @@ # eslint-plugin-unicorn [![Coverage Status](https://codecov.io/gh/sindresorhus/eslint-plugin-unicorn/branch/master/graph/badge.svg)](https://codecov.io/gh/sindresorhus/eslint-plugin-unicorn/branch/master) | ||
"unicorn/no-array-callback-reference": "error", | ||
"unicorn/no-array-for-each": "error", | ||
"unicorn/no-array-push-push": "error", | ||
"unicorn/no-array-reduce": "error", | ||
@@ -67,2 +69,3 @@ "unicorn/no-console-spaces": "error", | ||
"unicorn/no-process-exit": "error", | ||
"unicorn/no-this-assignment": "error", | ||
"unicorn/no-unreadable-array-destructuring": "error", | ||
@@ -130,2 +133,4 @@ "unicorn/no-unsafe-regex": "off", | ||
- [no-array-callback-reference](docs/rules/no-array-callback-reference.md) - Prevent passing a function reference directly to iterator methods. | ||
- [no-array-for-each](docs/rules/no-array-for-each.md) - Prefer `for…of` over `Array#forEach(…)`. *(partly fixable)* | ||
- [no-array-push-push](docs/rules/no-array-push-push.md) - Enforce combining multiple `Array#push()` into one call. *(partly fixable)* | ||
- [no-array-reduce](docs/rules/no-array-reduce.md) - Disallow `Array#reduce()` and `Array#reduceRight()`. | ||
@@ -140,7 +145,8 @@ - [no-console-spaces](docs/rules/no-console-spaces.md) - Do not use leading/trailing space between `console.log` parameters. *(fixable)* | ||
- [no-new-array](docs/rules/no-new-array.md) - Disallow `new Array()`. *(partly 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)* | ||
- [no-new-buffer](docs/rules/no-new-buffer.md) - Enforce the use of `Buffer.from()` and `Buffer.alloc()` instead of the deprecated `new Buffer()`. *(partly fixable)* | ||
- [no-null](docs/rules/no-null.md) - Disallow the use of the `null` literal. | ||
- [no-object-as-default-parameter](docs/rules/no-object-as-default-parameter.md) - Disallow the use of objects as default parameters. | ||
- [no-process-exit](docs/rules/no-process-exit.md) - Disallow `process.exit()`. | ||
- [no-unreadable-array-destructuring](docs/rules/no-unreadable-array-destructuring.md) - Disallow unreadable array destructuring. | ||
- [no-this-assignment](docs/rules/no-this-assignment.md) - Disallow assigning `this` to a variable. | ||
- [no-unreadable-array-destructuring](docs/rules/no-unreadable-array-destructuring.md) - Disallow unreadable array destructuring. *(partly fixable)* | ||
- [no-unsafe-regex](docs/rules/no-unsafe-regex.md) - Disallow unsafe regular expressions. | ||
@@ -174,3 +180,3 @@ - [no-unused-properties](docs/rules/no-unused-properties.md) - Disallow unused object properties. | ||
- [prefer-set-has](docs/rules/prefer-set-has.md) - Prefer `Set#has()` over `Array#includes()` when checking for existence or non-existence. *(fixable)* | ||
- [prefer-spread](docs/rules/prefer-spread.md) - Prefer the spread operator over `Array.from()`. *(fixable)* | ||
- [prefer-spread](docs/rules/prefer-spread.md) - Prefer the spread operator over `Array.from()` and `Array#concat()`. *(partly fixable)* | ||
- [prefer-string-replace-all](docs/rules/prefer-string-replace-all.md) - Prefer `String#replaceAll()` over regex searches with the global flag. *(fixable)* | ||
@@ -177,0 +183,0 @@ - [prefer-string-slice](docs/rules/prefer-string-slice.md) - Prefer `String#slice()` over `String#substr()` and `String#substring()`. *(partly fixable)* |
@@ -5,2 +5,3 @@ 'use strict'; | ||
const isShadowed = require('./utils/is-shadowed'); | ||
const switchNewExpressionToCallExpression = require('./utils/switch-new-expression-to-call-expression'); | ||
@@ -16,2 +17,4 @@ const messages = { | ||
const create = context => { | ||
const sourceCode = context.getSourceCode(); | ||
return { | ||
@@ -42,4 +45,4 @@ CallExpression: node => { | ||
NewExpression: node => { | ||
const {callee, range} = node; | ||
const {name, range: calleeRange} = callee; | ||
const {callee} = node; | ||
const {name} = callee; | ||
@@ -54,6 +57,5 @@ if (disallowNew.has(name) && !isShadowed(context.getScope(), callee)) { | ||
if (name !== 'String' && name !== 'Boolean' && name !== 'Number') { | ||
problem.fix = fixer => fixer.removeRange([ | ||
range[0], | ||
calleeRange[0] | ||
]); | ||
problem.fix = function * (fixer) { | ||
yield * switchNewExpressionToCallExpression(node, sourceCode, fixer); | ||
}; | ||
} | ||
@@ -60,0 +62,0 @@ |
@@ -28,3 +28,7 @@ 'use strict'; | ||
['flatMap'], | ||
['forEach'], | ||
[ | ||
'forEach', { | ||
returnsUndefined: true | ||
} | ||
], | ||
['map'], | ||
@@ -62,2 +66,3 @@ [ | ||
extraSelector: '', | ||
returnsUndefined: false, | ||
...options | ||
@@ -110,3 +115,3 @@ }; | ||
const {parameters, minParameters} = options; | ||
const {parameters, minParameters, returnsUndefined} = options; | ||
for (let parameterLength = minParameters; parameterLength <= parameters.length; parameterLength++) { | ||
@@ -130,3 +135,5 @@ const suggestionParameters = parameters.slice(0, parameterLength).join(', '); | ||
node, | ||
`(${suggestionParameters}) => ${nodeText}(${suggestionParameters})` | ||
returnsUndefined ? | ||
`(${suggestionParameters}) => { ${nodeText}(${suggestionParameters}); }` : | ||
`(${suggestionParameters}) => ${nodeText}(${suggestionParameters})` | ||
); | ||
@@ -133,0 +140,0 @@ } |
'use strict'; | ||
const {isParenthesized, isOpeningParenToken, isClosingParenToken} = require('eslint-utils'); | ||
const getDocumentationUrl = require('./utils/get-documentation-url'); | ||
const replaceNodeOrTokenAndSpacesBefore = require('./utils/replace-node-or-token-and-spaces-before'); | ||
const isInstanceofToken = token => token.value === 'instanceof' && token.type === 'Keyword'; | ||
const MESSAGE_ID = 'no-instanceof-array'; | ||
@@ -22,6 +26,19 @@ const messages = { | ||
messageId: MESSAGE_ID, | ||
fix: fixer => fixer.replaceText( | ||
node, | ||
`Array.isArray(${sourceCode.getText(node.left)})` | ||
) | ||
* fix(fixer) { | ||
const {left, right} = node; | ||
let leftStartNodeOrToken = left; | ||
let leftEndNodeOrToken = left; | ||
if (isParenthesized(left, sourceCode)) { | ||
leftStartNodeOrToken = sourceCode.getTokenBefore(left, isOpeningParenToken); | ||
leftEndNodeOrToken = sourceCode.getTokenAfter(left, isClosingParenToken); | ||
} | ||
yield fixer.insertTextBefore(leftStartNodeOrToken, 'Array.isArray('); | ||
yield fixer.insertTextAfter(leftEndNodeOrToken, ')'); | ||
const instanceofToken = sourceCode.getTokenAfter(left, isInstanceofToken); | ||
yield * replaceNodeOrTokenAndSpacesBefore(instanceofToken, '', fixer, sourceCode); | ||
yield * replaceNodeOrTokenAndSpacesBefore(right, '', fixer, sourceCode); | ||
} | ||
}) | ||
@@ -28,0 +45,0 @@ }; |
'use strict'; | ||
const {getStaticValue} = require('eslint-utils'); | ||
const getDocumentationUrl = require('./utils/get-documentation-url'); | ||
const switchNewExpressionToCallExpression = require('./utils/switch-new-expression-to-call-expression'); | ||
const MESSAGE_ID = 'no-new-buffer'; | ||
const ERROR = 'error'; | ||
const ERROR_UNKNOWN = 'error-unknown'; | ||
const SUGGESTION = 'suggestion'; | ||
const messages = { | ||
[MESSAGE_ID]: '`new Buffer()` is deprecated, use `Buffer.{{method}}()` instead.' | ||
[ERROR]: '`new Buffer()` is deprecated, use `Buffer.{{method}}()` instead.', | ||
[ERROR_UNKNOWN]: '`new Buffer()` is deprecated, use `Buffer.alloc()` or `Buffer.from()` instead.', | ||
[SUGGESTION]: 'Switch to `Buffer.{{method}}()`.' | ||
}; | ||
const inferMethod = arguments_ => { | ||
if (arguments_.length > 0) { | ||
const [firstArgument] = arguments_; | ||
const inferMethod = (bufferArguments, scope) => { | ||
if (bufferArguments.length !== 1) { | ||
return 'from'; | ||
} | ||
const [firstArgument] = bufferArguments; | ||
if (firstArgument.type === 'SpreadElement') { | ||
return; | ||
} | ||
if (firstArgument.type === 'ArrayExpression' || firstArgument.type === 'TemplateLiteral') { | ||
return 'from'; | ||
} | ||
const staticResult = getStaticValue(firstArgument, scope); | ||
if (staticResult) { | ||
const {value} = staticResult; | ||
if (typeof value === 'number') { | ||
return 'alloc'; | ||
} | ||
if ( | ||
firstArgument.type === 'Literal' && | ||
typeof firstArgument.value === 'number' | ||
typeof value === 'string' || | ||
Array.isArray(value) | ||
) { | ||
return 'alloc'; | ||
return 'from'; | ||
} | ||
} | ||
return 'from'; | ||
}; | ||
function fix(node, sourceCode, method) { | ||
return function * (fixer) { | ||
yield fixer.insertTextAfter(node.callee, `.${method}`); | ||
yield * switchNewExpressionToCallExpression(node, sourceCode, fixer); | ||
}; | ||
} | ||
const create = context => { | ||
const sourceCode = context.getSourceCode(); | ||
return { | ||
'NewExpression[callee.name="Buffer"]': node => { | ||
const method = inferMethod(node.arguments); | ||
const range = [ | ||
node.range[0], | ||
node.callee.range[1] | ||
]; | ||
const method = inferMethod(node.arguments, context.getScope()); | ||
context.report({ | ||
node, | ||
messageId: MESSAGE_ID, | ||
data: {method}, | ||
fix: fixer => fixer.replaceTextRange(range, `Buffer.${method}`) | ||
}); | ||
if (method) { | ||
context.report({ | ||
node, | ||
messageId: ERROR, | ||
data: {method}, | ||
fix: fix(node, sourceCode, method) | ||
}); | ||
} else { | ||
context.report({ | ||
node, | ||
messageId: ERROR_UNKNOWN, | ||
suggest: [ | ||
'from', | ||
'alloc' | ||
].map(method => ({ | ||
messageId: SUGGESTION, | ||
data: {method}, | ||
fix: fix(node, sourceCode, method) | ||
})) | ||
}); | ||
} | ||
} | ||
@@ -39,0 +80,0 @@ }; |
'use strict'; | ||
const {isParenthesized} = require('eslint-utils'); | ||
const getDocumentationUrl = require('./utils/get-documentation-url'); | ||
const shouldAddParenthesesToMemberExpressionObject = require('./utils/should-add-parentheses-to-member-expression-object'); | ||
@@ -13,10 +15,49 @@ const MESSAGE_ID = 'no-unreadable-array-destructuring'; | ||
const create = context => { | ||
const sourceCode = context.getSourceCode(); | ||
return { | ||
'ArrayPattern[elements.length>=3]': node => { | ||
if (node.elements.some((element, index, array) => isCommaFollowedWithComma(element, index, array))) { | ||
context.report({ | ||
node, | ||
messageId: MESSAGE_ID | ||
}); | ||
'ArrayPattern[elements.length>=3]'(node) { | ||
const {elements, parent} = node; | ||
if (!elements.some((element, index, elements) => isCommaFollowedWithComma(element, index, elements))) { | ||
return; | ||
} | ||
const problem = { | ||
node, | ||
messageId: MESSAGE_ID | ||
}; | ||
const nonNullElements = elements.filter(node => node !== null); | ||
if ( | ||
parent.type === 'VariableDeclarator' && | ||
parent.id === node && | ||
nonNullElements.length === 1 | ||
) { | ||
const [element] = nonNullElements; | ||
if (element.type !== 'AssignmentPattern') { | ||
problem.fix = function * (fixer) { | ||
const index = elements.indexOf(element); | ||
const isSlice = element.type === 'RestElement'; | ||
const variable = isSlice ? element.argument : element; | ||
yield fixer.replaceText(node, sourceCode.getText(variable)); | ||
const code = isSlice ? `.slice(${index})` : `[${index}]`; | ||
const array = parent.init; | ||
if ( | ||
!isParenthesized(array, sourceCode) && | ||
shouldAddParenthesesToMemberExpressionObject(array, sourceCode) | ||
) { | ||
yield fixer.insertTextBefore(array, '('); | ||
yield fixer.insertTextAfter(parent, `)${code}`); | ||
} else { | ||
yield fixer.insertTextAfter(parent, code); | ||
} | ||
}; | ||
} | ||
} | ||
context.report(problem); | ||
} | ||
@@ -33,4 +74,5 @@ }; | ||
}, | ||
messages | ||
messages, | ||
fixable: 'code' | ||
} | ||
}; |
@@ -121,14 +121,14 @@ 'use strict'; | ||
const checkProperties = (objectExpression, references, path = []) => { | ||
objectExpression.properties.forEach(property => { | ||
for (const property of objectExpression.properties) { | ||
const {key} = property; | ||
if (!key) { | ||
return; | ||
continue; | ||
} | ||
if (propertyKeysEqual(key, specialProtoPropertyKey)) { | ||
return; | ||
continue; | ||
} | ||
const nextPath = path.concat(key); | ||
const nextPath = [...path, key]; | ||
@@ -191,3 +191,3 @@ const nextReferences = references | ||
checkProperty(property, nextReferences, nextPath); | ||
}); | ||
} | ||
}; | ||
@@ -220,7 +220,11 @@ | ||
const checkVariables = scope => { | ||
scope.variables.forEach(variable => checkVariable(variable)); | ||
for (const variable of scope.variables) { | ||
checkVariable(variable); | ||
} | ||
}; | ||
const checkChildScopes = scope => { | ||
scope.childScopes.forEach(scope => checkScope(scope)); | ||
for (const childScope of scope.childScopes) { | ||
checkScope(childScope); | ||
} | ||
}; | ||
@@ -227,0 +231,0 @@ |
'use strict'; | ||
const {defaultsDeep, fromPairs} = require('lodash'); | ||
const {fromPairs} = require('lodash'); | ||
const getDocumentationUrl = require('./utils/get-documentation-url'); | ||
@@ -50,8 +50,3 @@ | ||
function format(value, options) { | ||
const { | ||
prefix = '', | ||
data | ||
} = value.match(/^(?<prefix>0[box])?(?<data>.*)$/i).groups; | ||
function format(value, {prefix, data}, options) { | ||
const formatOption = options[prefix.toLowerCase()]; | ||
@@ -74,14 +69,40 @@ | ||
const defaultOptions = { | ||
hexadecimal: {minimumDigits: 0, groupLength: 2}, | ||
binary: {minimumDigits: 0, groupLength: 4}, | ||
octal: {minimumDigits: 0, groupLength: 4}, | ||
hexadecimal: {minimumDigits: 0, groupLength: 2}, | ||
number: {minimumDigits: 5, groupLength: 3} | ||
}; | ||
const create = context => { | ||
const rawOptions = defaultsDeep({}, context.options[0], defaultOptions); | ||
const { | ||
onlyIfContainsSeparator, | ||
binary, | ||
octal, | ||
hexadecimal, | ||
number | ||
} = { | ||
onlyIfContainsSeparator: false, | ||
...context.options[0] | ||
}; | ||
const options = { | ||
'0b': rawOptions.binary, | ||
'0o': rawOptions.octal, | ||
'0x': rawOptions.hexadecimal, | ||
'': rawOptions.number | ||
'0b': { | ||
onlyIfContainsSeparator, | ||
...defaultOptions.binary, | ||
...binary | ||
}, | ||
'0o': { | ||
onlyIfContainsSeparator, | ||
...defaultOptions.octal, | ||
...octal | ||
}, | ||
'0x': { | ||
onlyIfContainsSeparator, | ||
...defaultOptions.hexadecimal, | ||
...hexadecimal | ||
}, | ||
'': { | ||
onlyIfContainsSeparator, | ||
...defaultOptions.number, | ||
...number | ||
} | ||
}; | ||
@@ -106,4 +127,12 @@ | ||
const formatted = format(number.replace(/_/g, ''), options) + suffix; | ||
const strippedNumber = number.replace(/_/g, ''); | ||
const {prefix = '', data} = strippedNumber.match(/^(?<prefix>0[box])?(?<data>.*)$/i).groups; | ||
const {onlyIfContainsSeparator} = options[prefix.toLowerCase()]; | ||
if (onlyIfContainsSeparator && !raw.includes('_')) { | ||
return; | ||
} | ||
const formatted = format(strippedNumber, {prefix, data}, options) + suffix; | ||
if (raw !== formatted) { | ||
@@ -123,2 +152,5 @@ context.report({ | ||
properties: { | ||
onlyIfContainsSeparator: { | ||
type: 'boolean' | ||
}, | ||
minimumDigits: { | ||
@@ -140,5 +172,11 @@ type: 'integer', | ||
type: 'object', | ||
properties: fromPairs( | ||
Object.entries(defaultOptions).map(([type, options]) => [type, formatOptionsSchema(options)]) | ||
), | ||
properties: { | ||
...fromPairs( | ||
Object.entries(defaultOptions).map(([type, options]) => [type, formatOptionsSchema(options)]) | ||
), | ||
onlyIfContainsSeparator: { | ||
type: 'boolean', | ||
default: false | ||
} | ||
}, | ||
additionalProperties: false | ||
@@ -145,0 +183,0 @@ }]; |
@@ -70,3 +70,3 @@ 'use strict'; | ||
if (node.type === 'Literal') { | ||
return node.value === null || Boolean(node.value.trim()); | ||
return node.raw === 'null' || (typeof node.value === 'string' && Boolean(node.value.trim())); | ||
} | ||
@@ -73,0 +73,0 @@ |
'use strict'; | ||
const astUtils = require('eslint-ast-utils'); | ||
const getDocumentationUrl = require('./utils/get-documentation-url'); | ||
const isLiteralValue = require('./utils/is-literal-value'); | ||
const getPropertyName = require('./utils/get-property-name'); | ||
@@ -29,3 +29,3 @@ const MESSAGE_ID = 'prefer-reflect-apply'; | ||
if ( | ||
astUtils.getPropertyName(node.callee) === 'apply' && | ||
getPropertyName(node.callee) === 'apply' && | ||
node.arguments.length === 2 && | ||
@@ -45,5 +45,5 @@ isApplySignature(node.arguments[0], node.arguments[1]) | ||
if ( | ||
astUtils.getPropertyName(node.callee) === 'call' && | ||
astUtils.getPropertyName(node.callee.object) === 'apply' && | ||
astUtils.getPropertyName(node.callee.object.object) === 'prototype' && | ||
getPropertyName(node.callee) === 'call' && | ||
getPropertyName(node.callee.object) === 'apply' && | ||
getPropertyName(node.callee.object.object) === 'prototype' && | ||
node.callee.object.object.object && | ||
@@ -50,0 +50,0 @@ node.callee.object.object.object.type === 'Identifier' && |
'use strict'; | ||
const {isParenthesized, getStaticValue, isCommaToken} = require('eslint-utils'); | ||
const getDocumentationUrl = require('./utils/get-documentation-url'); | ||
const methodSelector = require('./utils/method-selector'); | ||
const needsSemicolon = require('./utils/needs-semicolon'); | ||
const getParentheses = require('./utils/get-parentheses'); | ||
const shouldAddParenthesesToSpreadElementArgument = require('./utils/should-add-parentheses-to-spread-element-argument'); | ||
const MESSAGE_ID = 'prefer-spread'; | ||
const ERROR_ARRAY_FROM = 'array-from'; | ||
const ERROR_ARRAY_CONCAT = 'array-concat'; | ||
const SUGGESTION_CONCAT_ARGUMENT_IS_SPREADABLE = 'argument-is-spreadable'; | ||
const SUGGESTION_CONCAT_ARGUMENT_IS_NOT_SPREADABLE = 'argument-is-not-spreadable'; | ||
const messages = { | ||
[MESSAGE_ID]: 'Prefer the spread operator over `Array.from()`.' | ||
[ERROR_ARRAY_FROM]: 'Prefer the spread operator over `Array.from(…)`.', | ||
[ERROR_ARRAY_CONCAT]: 'Prefer the spread operator over `Array#concat(…)`.', | ||
[SUGGESTION_CONCAT_ARGUMENT_IS_SPREADABLE]: 'First argument is an `array`.', | ||
[SUGGESTION_CONCAT_ARGUMENT_IS_NOT_SPREADABLE]: 'First argument is not an `array`.' | ||
}; | ||
const selector = [ | ||
const arrayFromCallSelector = [ | ||
methodSelector({ | ||
@@ -22,2 +31,191 @@ object: 'Array', | ||
const arrayConcatCallSelector = [ | ||
methodSelector({ | ||
name: 'concat' | ||
}), | ||
`:not(${ | ||
[ | ||
'Literal', | ||
'TemplateLiteral' | ||
].map(type => `[callee.object.type="${type}"]`).join(', ') | ||
})` | ||
].join(''); | ||
const isArrayLiteral = node => node.type === 'ArrayExpression'; | ||
const isArrayLiteralHasTrailingComma = (node, sourceCode) => { | ||
if (node.elements.length === 0) { | ||
return false; | ||
} | ||
return isCommaToken(sourceCode.getLastToken(node, 1)); | ||
}; | ||
const getParenthesizedRange = (node, sourceCode) => { | ||
const [firstToken = node, lastToken = node] = getParentheses(node, sourceCode); | ||
const [start] = firstToken.range; | ||
const [, end] = lastToken.range; | ||
return [start, end]; | ||
}; | ||
function fixConcat(node, sourceCode, fixableArguments) { | ||
const array = node.callee.object; | ||
const concatCallArguments = node.arguments; | ||
const arrayParenthesizedRange = getParenthesizedRange(array, sourceCode); | ||
const arrayIsArrayLiteral = isArrayLiteral(array); | ||
const arrayHasTrailingComma = arrayIsArrayLiteral && isArrayLiteralHasTrailingComma(array, sourceCode); | ||
const getRangeAfterArray = () => { | ||
const [, start] = arrayParenthesizedRange; | ||
const [, end] = node.range; | ||
return [start, end]; | ||
}; | ||
const getArrayLiteralElementsText = (node, keepTrailingComma) => { | ||
if ( | ||
!keepTrailingComma && | ||
isArrayLiteralHasTrailingComma(node, sourceCode) | ||
) { | ||
const start = node.range[0] + 1; | ||
const end = sourceCode.getLastToken(node, 1).range[0]; | ||
return sourceCode.text.slice(start, end); | ||
} | ||
return sourceCode.getText(node, -1, -1); | ||
}; | ||
const getFixedText = () => { | ||
const nonEmptyArguments = fixableArguments | ||
.filter(({node, isArrayLiteral}) => (!isArrayLiteral || node.elements.length > 0)); | ||
const lastArgument = nonEmptyArguments[nonEmptyArguments.length - 1]; | ||
let text = nonEmptyArguments | ||
.map(({node, isArrayLiteral, isSpreadable}) => { | ||
if (isArrayLiteral) { | ||
return getArrayLiteralElementsText(node, node === lastArgument.node); | ||
} | ||
const [start, end] = getParenthesizedRange(node, sourceCode); | ||
let text = sourceCode.text.slice(start, end); | ||
if (isSpreadable) { | ||
if ( | ||
!isParenthesized(node, sourceCode) && | ||
shouldAddParenthesesToSpreadElementArgument(node) | ||
) { | ||
text = `(${text})`; | ||
} | ||
text = `...${text}`; | ||
} | ||
return text || ' '; | ||
}) | ||
.join(', '); | ||
if (!text) { | ||
return ''; | ||
} | ||
if (arrayIsArrayLiteral) { | ||
if (array.elements.length > 0) { | ||
text = ` ${text}`; | ||
if (!arrayHasTrailingComma) { | ||
text = `,${text}`; | ||
} | ||
if ( | ||
arrayHasTrailingComma && | ||
(!lastArgument.isArrayLiteral || !isArrayLiteralHasTrailingComma(lastArgument.node, sourceCode)) | ||
) { | ||
text = `${text},`; | ||
} | ||
} | ||
} else { | ||
text = `, ${text}`; | ||
} | ||
return text; | ||
}; | ||
function removeArguments(fixer) { | ||
const [firstArgument] = concatCallArguments; | ||
const lastArgument = concatCallArguments[fixableArguments.length - 1]; | ||
const [start] = getParenthesizedRange(firstArgument, sourceCode); | ||
let [, end] = sourceCode.getTokenAfter(lastArgument, isCommaToken).range; | ||
const textAfter = sourceCode.text.slice(end); | ||
const [leadingSpaces] = textAfter.match(/^\s*/); | ||
end += leadingSpaces.length; | ||
return fixer.replaceTextRange([start, end], ''); | ||
} | ||
return function * (fixer) { | ||
// Fixed code always starts with `[` | ||
if ( | ||
!arrayIsArrayLiteral && | ||
needsSemicolon(sourceCode.getTokenBefore(node), sourceCode, '[') | ||
) { | ||
yield fixer.insertTextBefore(node, ';'); | ||
} | ||
yield ( | ||
concatCallArguments.length - fixableArguments.length === 0 ? | ||
fixer.replaceTextRange(getRangeAfterArray(), '') : | ||
removeArguments(fixer) | ||
); | ||
const text = getFixedText(); | ||
if (arrayIsArrayLiteral) { | ||
const closingBracketToken = sourceCode.getLastToken(array); | ||
yield fixer.insertTextBefore(closingBracketToken, text); | ||
} else { | ||
// The array is already accessing `.concat`, there should not any case need add extra `()` | ||
yield fixer.insertTextBeforeRange(arrayParenthesizedRange, '[...'); | ||
yield fixer.insertTextAfterRange(arrayParenthesizedRange, text); | ||
yield fixer.insertTextAfterRange(arrayParenthesizedRange, ']'); | ||
} | ||
}; | ||
} | ||
const getConcatArgumentSpreadable = (node, scope) => { | ||
if (node.type === 'SpreadElement') { | ||
return; | ||
} | ||
if (isArrayLiteral(node)) { | ||
return {node, isArrayLiteral: true}; | ||
} | ||
const result = getStaticValue(node, scope); | ||
if (!result) { | ||
return; | ||
} | ||
const isSpreadable = Array.isArray(result.value); | ||
return {node, isSpreadable}; | ||
}; | ||
function getConcatFixableArguments(argumentsList, scope) { | ||
const fixableArguments = []; | ||
for (const node of argumentsList) { | ||
const result = getConcatArgumentSpreadable(node, scope); | ||
if (result) { | ||
fixableArguments.push(result); | ||
} else { | ||
break; | ||
} | ||
} | ||
return fixableArguments; | ||
} | ||
const create = context => { | ||
@@ -28,6 +226,6 @@ const sourceCode = context.getSourceCode(); | ||
return { | ||
[selector](node) { | ||
[arrayFromCallSelector](node) { | ||
context.report({ | ||
node, | ||
messageId: MESSAGE_ID, | ||
messageId: ERROR_ARRAY_FROM, | ||
fix: fixer => { | ||
@@ -47,2 +245,57 @@ const [arrayLikeArgument, mapFn, thisArgument] = node.arguments.map(node => getSource(node)); | ||
}); | ||
}, | ||
[arrayConcatCallSelector](node) { | ||
const scope = context.getScope(); | ||
const staticResult = getStaticValue(node.callee.object, scope); | ||
if (staticResult && !Array.isArray(staticResult.value)) { | ||
return; | ||
} | ||
const problem = { | ||
node: node.callee.property, | ||
messageId: ERROR_ARRAY_CONCAT | ||
}; | ||
const fixableArguments = getConcatFixableArguments(node.arguments, scope); | ||
if (fixableArguments.length > 0 || node.arguments.length === 0) { | ||
problem.fix = fixConcat(node, sourceCode, fixableArguments); | ||
context.report(problem); | ||
return; | ||
} | ||
const [firstArgument, ...restArguments] = node.arguments; | ||
if (firstArgument.type === 'SpreadElement') { | ||
context.report(problem); | ||
return; | ||
} | ||
const fixableArgumentsAfterFirstArgument = getConcatFixableArguments(restArguments, scope); | ||
problem.suggest = [ | ||
{ | ||
messageId: SUGGESTION_CONCAT_ARGUMENT_IS_SPREADABLE, | ||
isSpreadable: true | ||
}, | ||
{ | ||
messageId: SUGGESTION_CONCAT_ARGUMENT_IS_NOT_SPREADABLE, | ||
isSpreadable: false | ||
} | ||
].map(({messageId, isSpreadable}) => ({ | ||
messageId, | ||
fix: fixConcat( | ||
node, | ||
sourceCode, | ||
// When apply suggestion, we also merge fixable arguments after the first one | ||
[ | ||
{ | ||
node: firstArgument, | ||
isSpreadable | ||
}, | ||
...fixableArgumentsAfterFirstArgument | ||
] | ||
) | ||
})); | ||
context.report(problem); | ||
} | ||
@@ -49,0 +302,0 @@ }; |
@@ -59,2 +59,3 @@ 'use strict'; | ||
const create = context => { | ||
const onlySingleLine = context.options[0] === 'only-single-line'; | ||
const sourceCode = context.getSourceCode(); | ||
@@ -80,2 +81,7 @@ const scopeToNamesGeneratedByFixer = new WeakMap(); | ||
const isSingleLineNode = node => { | ||
const [start, end] = node.range.map(index => sourceCode.getLocFromIndex(index)); | ||
return start.line === end.line; | ||
}; | ||
function merge(options, mergeOptions) { | ||
@@ -194,2 +200,9 @@ const { | ||
if ( | ||
onlySingleLine && | ||
[consequent, alternate, node.test].some(node => !isSingleLineNode(node)) | ||
) { | ||
return; | ||
} | ||
const result = merge({node, consequent, alternate}, { | ||
@@ -205,3 +218,2 @@ checkThrowStatement: true, | ||
const scope = context.getScope(); | ||
const sourceCode = context.getSourceCode(); | ||
@@ -259,2 +271,9 @@ context.report({ | ||
const schema = [ | ||
{ | ||
enum: ['always', 'only-single-line'], | ||
default: 'always' | ||
} | ||
]; | ||
module.exports = { | ||
@@ -270,4 +289,5 @@ create, | ||
}, | ||
schema, | ||
fixable: 'code' | ||
} | ||
}; |
'use strict'; | ||
const path = require('path'); | ||
const astUtils = require('eslint-ast-utils'); | ||
const {defaultsDeep, upperFirst, lowerFirst} = require('lodash'); | ||
@@ -392,4 +391,6 @@ | ||
message.push(`Please rename the ${nameTypeText} \`${discouragedName}\`.`); | ||
message.push(`Suggested names are: ${replacementsText}.`); | ||
message.push( | ||
`Please rename the ${nameTypeText} \`${discouragedName}\`.`, | ||
`Suggested names are: ${replacementsText}.` | ||
); | ||
} | ||
@@ -434,2 +435,12 @@ | ||
const isStaticRequire = node => Boolean( | ||
node && | ||
node.callee && | ||
node.callee.type === 'Identifier' && | ||
node.callee.name === 'require' && | ||
node.arguments.length === 1 && | ||
node.arguments[0].type === 'Literal' && | ||
typeof node.arguments[0].value === 'string' | ||
); | ||
const isDefaultOrNamespaceImportName = identifier => { | ||
@@ -462,3 +473,3 @@ if ( | ||
identifier.parent.id === identifier && | ||
astUtils.isStaticRequire(identifier.parent.init) | ||
isStaticRequire(identifier.parent.init) | ||
) { | ||
@@ -573,3 +584,3 @@ return true; | ||
identifiers: variable.identifiers, | ||
references: variable.references.concat(outerClassVariable.references) | ||
references: [...variable.references, ...outerClassVariable.references] | ||
}; | ||
@@ -645,3 +656,6 @@ | ||
const scopes = variable.references.map(reference => reference.from).concat(variable.scope); | ||
const scopes = [ | ||
...variable.references.map(reference => reference.from), | ||
variable.scope | ||
]; | ||
variableReplacements.samples = variableReplacements.samples.map( | ||
@@ -679,7 +693,11 @@ name => avoidCapture(name, scopes, ecmaVersion, isSafeName) | ||
const checkVariables = scope => { | ||
scope.variables.forEach(variable => checkPossiblyWeirdClassVariable(variable)); | ||
for (const variable of scope.variables) { | ||
checkPossiblyWeirdClassVariable(variable); | ||
} | ||
}; | ||
const checkChildScopes = scope => { | ||
scope.childScopes.forEach(scope => checkScope(scope)); | ||
for (const childScope of scope.childScopes) { | ||
checkScope(childScope); | ||
} | ||
}; | ||
@@ -686,0 +704,0 @@ |
'use strict'; | ||
const {uniq} = require('lodash'); | ||
const {uniq, flatten} = require('lodash'); | ||
const getReferences = scope => uniq( | ||
scope.references.concat( | ||
...scope.childScopes.map(scope => getReferences(scope)) | ||
) | ||
); | ||
const getReferences = scope => uniq([ | ||
...scope.references, | ||
...flatten(scope.childScopes.map(scope => getReferences(scope))) | ||
]); | ||
module.exports = getReferences; |
'use strict'; | ||
const isSameNode = require('./is-same-node'); | ||
const hasSameRange = require('./has-same-range'); | ||
@@ -9,4 +9,9 @@ module.exports = identifier => | ||
identifier.parent.parent.type === 'Property' && | ||
isSameNode(identifier, identifier.parent.parent.key) && | ||
( | ||
identifier === identifier.parent.parent.key || | ||
// In `babel-eslint` parent.key is not reference of identifier, #444 | ||
// issue https://github.com/babel/babel-eslint/issues/809 | ||
hasSameRange(identifier, identifier.parent.parent.key) | ||
) && | ||
identifier.parent.parent.value === identifier.parent && | ||
identifier.parent.parent.shorthand; |
'use strict'; | ||
module.exports = (node, value) => node && node.type === 'Literal' && node.value === value; | ||
module.exports = (node, value) => { | ||
if (!node || node.type !== 'Literal') { | ||
return false; | ||
} | ||
if (value === null) { | ||
return node.raw === 'null'; | ||
} | ||
return node.value === value; | ||
}; |
'use strict'; | ||
const isSameNode = require('./is-same-node'); | ||
/** | ||
@@ -13,3 +11,3 @@ * Finds the eslint-scope reference in the given scope. | ||
const references = scope.references | ||
.filter(reference => isSameNode(reference.identifier, node)); | ||
.filter(reference => reference.identifier === node); | ||
@@ -16,0 +14,0 @@ if (references.length === 1) { |
'use strict'; | ||
const isSameNode = require('./is-same-node'); | ||
const hasSameRange = require('./has-same-range'); | ||
@@ -8,2 +8,7 @@ module.exports = identifier => | ||
identifier.parent.shorthand && | ||
isSameNode(identifier, identifier.parent.key); | ||
( | ||
identifier === identifier.parent.key || | ||
// In `babel-eslint` parent.key is not reference of identifier, #444 | ||
// issue https://github.com/babel/babel-eslint/issues/809 | ||
hasSameRange(identifier, identifier.parent.key) | ||
); |
@@ -11,3 +11,4 @@ 'use strict'; | ||
max, | ||
property = '' | ||
property = '', | ||
includeOptional = false | ||
} = { | ||
@@ -24,6 +25,9 @@ min: 0, | ||
`[${prefix}callee.type="MemberExpression"]`, | ||
`[${prefix}callee.computed=false]`, | ||
`[${prefix}callee.property.type="Identifier"]` | ||
]; | ||
if (!includeOptional) { | ||
selector.push(`[${prefix}callee.computed=false]`); | ||
} | ||
if (name) { | ||
@@ -42,4 +46,6 @@ selector.push(`[${prefix}callee.property.name="${name}"]`); | ||
if (object) { | ||
selector.push(`[${prefix}callee.object.type="Identifier"]`); | ||
selector.push(`[${prefix}callee.object.name="${object}"]`); | ||
selector.push( | ||
`[${prefix}callee.object.type="Identifier"]`, | ||
`[${prefix}callee.object.name="${object}"]` | ||
); | ||
} | ||
@@ -46,0 +52,0 @@ |
'use strict'; | ||
const {isOpeningParenToken, isClosingParenToken} = require('eslint-utils'); | ||
const isNewExpressionWithParentheses = require('./is-new-expression-with-parentheses'); | ||
@@ -14,23 +14,2 @@ // Determine whether this node is a decimal integer literal. | ||
/** | ||
Determine if a constructor function is newed-up with parens. | ||
@param {Node} node - The `NewExpression` node to be checked. | ||
@param {SourceCode} sourceCode - The source code object. | ||
@returns {boolean} True if the constructor is called with parens. | ||
Copied from https://github.com/eslint/eslint/blob/cc4871369645c3409dc56ded7a555af8a9f63d51/lib/rules/no-extra-parens.js#L252 | ||
*/ | ||
function isNewExpressionWithParentheses(node, sourceCode) { | ||
if (node.arguments.length > 0) { | ||
return true; | ||
} | ||
const [penultimateToken, lastToken] = sourceCode.getLastTokens(node, 2); | ||
// The expression should end with its own parens, for example, `new new Foo()` is not a new expression with parens. | ||
return isOpeningParenToken(penultimateToken) && | ||
isClosingParenToken(lastToken) && | ||
node.callee.range[1] < node.range[1]; | ||
} | ||
/** | ||
Check if parentheses should to be added to a `node` when it's used as an `object` of `MemberExpression`. | ||
@@ -37,0 +16,0 @@ |
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
326484
13
119
10633
221
23
- Removedeslint-ast-utils@^1.1.0
- Removedeslint-ast-utils@1.1.0(transitive)
- Removedlodash.get@4.4.2(transitive)
- Removedlodash.zip@4.2.0(transitive)
Updatedregexp-tree@^0.1.22