📅 You're Invited: Meet the Socket team at RSAC (April 28 – May 1).RSVP
Socket
Sign inDemoInstall
Socket

eslint-plugin-unicorn

Package Overview
Dependencies
Maintainers
2
Versions
110
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

eslint-plugin-unicorn - npm Package Compare versions

Comparing version

to
58.0.0

7

index.js

@@ -7,4 +7,7 @@ import createDeprecatedRules from './rules/utils/create-deprecated-rules.js';

const deprecatedRules = createDeprecatedRules({
// {ruleId: ReplacementRuleId | ReplacementRuleId[]}, if no replacement, use `{ruleId: []}`
'no-instanceof-array': 'unicorn/no-instanceof-builtins',
// {ruleId: {message: string, replacedBy: string[]}}
'no-instanceof-array': {
message: 'Replaced by `unicorn/no-instanceof-builtins` which covers more cases.',
replacedBy: ['unicorn/no-instanceof-builtins'],
},
});

@@ -11,0 +14,0 @@

{
"name": "eslint-plugin-unicorn",
"version": "57.0.0",
"version": "58.0.0",
"description": "More than 100 powerful ESLint rules",

@@ -20,3 +20,3 @@ "license": "MIT",

"engines": {
"node": ">=18.18"
"node": "^18.20.0 || ^20.10.0 || >=21.0.0"
},

@@ -62,10 +62,11 @@ "scripts": {

"@babel/helper-validator-identifier": "^7.25.9",
"@eslint-community/eslint-utils": "^4.4.1",
"ci-info": "^4.1.0",
"@eslint-community/eslint-utils": "^4.5.1",
"@eslint/plugin-kit": "^0.2.7",
"ci-info": "^4.2.0",
"clean-regexp": "^1.0.0",
"core-js-compat": "^3.40.0",
"core-js-compat": "^3.41.0",
"esquery": "^1.6.0",
"globals": "^15.15.0",
"globals": "^16.0.0",
"indent-string": "^5.0.0",
"is-builtin-module": "^4.0.0",
"is-builtin-module": "^5.0.0",
"jsesc": "^3.1.0",

@@ -81,19 +82,18 @@ "pluralize": "^8.0.0",

"@babel/code-frame": "^7.26.2",
"@babel/core": "^7.26.9",
"@babel/eslint-parser": "^7.26.8",
"@eslint/eslintrc": "^3.2.0",
"@babel/core": "^7.26.10",
"@babel/eslint-parser": "^7.26.10",
"@eslint/eslintrc": "^3.3.0",
"@lubien/fixture-beta-package": "^1.0.0-beta.1",
"@typescript-eslint/parser": "^8.24.1",
"@typescript-eslint/parser": "^8.26.1",
"ava": "^6.2.0",
"c8": "^10.1.3",
"enquirer": "^2.4.1",
"eslint": "^9.20.1",
"eslint": "^9.22.0",
"eslint-ava-rule-tester": "^5.0.1",
"eslint-config-xo": "^0.46.0",
"eslint-doc-generator": "^2.0.2",
"eslint-doc-generator": "^2.1.2",
"eslint-plugin-eslint-plugin": "^6.4.0",
"eslint-plugin-internal-rules": "file:./scripts/internal-rules/",
"eslint-plugin-jsdoc": "^50.6.3",
"eslint-plugin-jsdoc": "^50.6.8",
"eslint-remote-tester": "^4.0.1",
"eslint-remote-tester-repositories": "^2.0.0",
"eslint-remote-tester-repositories": "^2.0.1",
"espree": "^10.3.0",

@@ -103,3 +103,3 @@ "listr2": "^8.2.5",

"markdownlint-cli": "^0.44.0",
"memoize": "^10.0.0",
"memoize": "^10.1.0",
"nano-spawn": "^0.2.0",

@@ -112,8 +112,8 @@ "node-style-text": "^0.0.7",

"pretty-ms": "^9.2.0",
"typescript": "^5.7.3",
"vue-eslint-parser": "^9.4.3",
"typescript": "^5.8.2",
"vue-eslint-parser": "^10.1.1",
"yaml": "^2.7.0"
},
"peerDependencies": {
"eslint": ">=9.20.0"
"eslint": ">=9.22.0"
},

@@ -120,0 +120,0 @@ "ava": {

@@ -52,4 +52,4 @@ # eslint-plugin-unicorn [![Coverage Status](https://codecov.io/gh/sindresorhus/eslint-plugin-unicorn/branch/main/graph/badge.svg)](https://codecov.io/gh/sindresorhus/eslint-plugin-unicorn/branch/main) [![npm version](https://img.shields.io/npm/v/eslint-plugin-unicorn.svg?style=flat)](https://npmjs.com/package/eslint-plugin-unicorn)

💼 [Configurations](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs) enabled in.\
✅ Set in the `recommended` [configuration](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs).\
💼 [Configurations](https://github.com/sindresorhus/eslint-plugin-unicorn#recommended-config) enabled in.\
✅ Set in the `recommended` [configuration](https://github.com/sindresorhus/eslint-plugin-unicorn#recommended-config).\
🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\

@@ -71,3 +71,3 @@ 💡 Manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).

| [error-message](docs/rules/error-message.md) | Enforce passing a `message` value when creating a built-in error. | ✅ | | |
| [escape-case](docs/rules/escape-case.md) | Require escape sequences to use uppercase values. | ✅ | 🔧 | |
| [escape-case](docs/rules/escape-case.md) | Require escape sequences to use uppercase or lowercase values. | ✅ | 🔧 | |
| [expiring-todo-comments](docs/rules/expiring-todo-comments.md) | Add expiration conditions to TODO comments. | ✅ | | |

@@ -74,0 +74,0 @@ | [explicit-length-check](docs/rules/explicit-length-check.md) | Enforce explicitly comparing the `length` or `size` property of a value. | ✅ | 🔧 | 💡 |

import {replaceTemplateElement} from './fix/index.js';
import {isRegexLiteral, isStringLiteral, isTaggedTemplateLiteral} from './ast/index.js';
const MESSAGE_ID = 'escape-case';
const MESSAGE_ID_UPPERCASE = 'escape-uppercase';
const MESSAGE_ID_LOWERCASE = 'escape-lowercase';
const messages = {
[MESSAGE_ID]: 'Use uppercase characters for the value of the escape sequence.',
[MESSAGE_ID_UPPERCASE]: 'Use uppercase characters for the value of the escape sequence.',
[MESSAGE_ID_LOWERCASE]: 'Use lowercase characters for the value of the escape sequence.',
};
const escapeWithLowercase = /(?<=(?:^|[^\\])(?:\\\\)*\\)(?<data>x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|u{[\dA-Fa-f]+})/g;
const escapePatternWithLowercase = /(?<=(?:^|[^\\])(?:\\\\)*\\)(?<data>x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|u{[\dA-Fa-f]+}|c[a-z])/g;
const getProblem = ({node, original, regex = escapeWithLowercase, fix}) => {
const fixed = original.replace(regex, data => data.slice(0, 1) + data.slice(1).toUpperCase());
const escapeCase = /(?<=(?:^|[^\\])(?:\\\\)*\\)(?<data>x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|u{[\dA-Fa-f]+})/g;
const escapePatternCase = /(?<=(?:^|[^\\])(?:\\\\)*\\)(?<data>x[\dA-Fa-f]{2}|u[\dA-Fa-f]{4}|u{[\dA-Fa-f]+}|c[A-Za-z])/g;
const getProblem = ({node, original, regex = escapeCase, lowercase, fix}) => {
const fixed = original.replace(regex, data => data[0] + data.slice(1)[lowercase ? 'toLowerCase' : 'toUpperCase']());

@@ -17,3 +19,3 @@ if (fixed !== original) {

node,
messageId: MESSAGE_ID,
messageId: lowercase ? MESSAGE_ID_LOWERCASE : MESSAGE_ID_UPPERCASE,
fix: fixer => fix ? fix(fixer, fixed) : fixer.replaceText(node, fixed),

@@ -26,2 +28,4 @@ };

const create = context => {
const lowercase = context.options[0] === 'lowercase';
context.on('Literal', node => {

@@ -32,2 +36,3 @@ if (isStringLiteral(node)) {

original: node.raw,
lowercase,
});

@@ -42,3 +47,4 @@ }

original: node.raw,
regex: escapePatternWithLowercase,
regex: escapePatternCase,
lowercase,
});

@@ -56,2 +62,3 @@ }

original: node.value.raw,
lowercase,
fix: (fixer, fixed) => replaceTemplateElement(fixer, node, fixed),

@@ -62,2 +69,8 @@ });

const schema = [
{
enum: ['uppercase', 'lowercase'],
},
];
/** @type {import('eslint').Rule.RuleModule} */

@@ -69,6 +82,8 @@ const config = {

docs: {
description: 'Require escape sequences to use uppercase values.',
description: 'Require escape sequences to use uppercase or lowercase values.',
recommended: true,
},
fixable: 'code',
schema,
defaultOptions: ['uppercase'],
messages,

@@ -75,0 +90,0 @@ },

@@ -17,3 +17,3 @@ import {getParenthesizedRange} from '../utils/parentheses.js';

tokenBefore
&& range[0] === tokenBefore.range[1]
&& range[0] === sourceCode.getRange(tokenBefore)[1]
&& isProblematicToken(tokenBefore)

@@ -28,3 +28,3 @@ ) {

tokenAfter
&& range[1] === tokenAfter.range[0]
&& range[1] === sourceCode.getRange(tokenAfter)[0]
&& isProblematicToken(tokenAfter)

@@ -31,0 +31,0 @@ ) {

export default function removeSpacesAfter(indexOrNodeOrToken, sourceCode, fixer) {
let index = indexOrNodeOrToken;
if (typeof indexOrNodeOrToken === 'object' && Array.isArray(indexOrNodeOrToken.range)) {
index = indexOrNodeOrToken.range[1];
if (typeof indexOrNodeOrToken === 'object') {
index = sourceCode.getRange(indexOrNodeOrToken)[1];
}

@@ -6,0 +6,0 @@

@@ -25,2 +25,3 @@ import isShorthandPropertyValue from '../utils/is-shorthand-property-value.js';

return fixer.replaceTextRange(
// eslint-disable-next-line internal/no-restricted-property-access
[identifier.range[0], identifier.typeAnnotation.range[0]],

@@ -27,0 +28,0 @@ `${replacement}${identifier.optional ? '?' : ''}`,

@@ -6,3 +6,5 @@ // Replace `StringLiteral` or `TemplateLiteral` node with raw text

[
// eslint-disable-next-line internal/no-restricted-property-access
node.range[0] + 1,
// eslint-disable-next-line internal/no-restricted-property-access
node.range[1] - 1,

@@ -9,0 +11,0 @@ ],

const replaceTemplateElement = (fixer, node, replacement) => {
// eslint-disable-next-line internal/no-restricted-property-access
const {range: [start, end], tail} = node;

@@ -3,0 +4,0 @@ return fixer.replaceTextRange(

@@ -0,1 +1,3 @@

import {ConfigCommentParser} from '@eslint/plugin-kit';
const MESSAGE_ID = 'no-abusive-eslint-disable';

@@ -6,4 +8,10 @@ const messages = {

const disableRegex = /^eslint-disable(?:-next-line|-line)?(?<ruleId>$|(?:\s+(?:@(?:[\w-]+\/){1,2})?[\w-]+)?)/;
// https://github.com/eslint/eslint/blob/ecd0ede7fd2ccbb4c0daf0e4732e97ea0f49db1b/lib/linter/linter.js#L509-L512
const eslintDisableDirectives = new Set([
'eslint-disable',
'eslint-disable-line',
'eslint-disable-next-line',
]);
let commentParser;
/** @param {import('eslint').Rule.RuleContext} context */

@@ -13,24 +21,28 @@ const create = context => ({

for (const comment of node.comments) {
const value = comment.value.trim();
const result = disableRegex.exec(value);
commentParser ??= new ConfigCommentParser();
const result = commentParser.parseDirective(comment.value);
if (
result // It's a eslint-disable comment
&& !result.groups.ruleId // But it did not specify any rules
) {
const {sourceCode} = context;
if (!(
// It's a eslint-disable comment
eslintDisableDirectives.has(result?.label)
// But it did not specify any rules
&& !result?.value
)) {
return;
}
yield {
// Can't set it at the given location as the warning
// will be ignored due to the disable comment
loc: {
start: {
...sourceCode.getLoc(comment).start,
column: -1,
},
end: sourceCode.getLoc(comment).end,
const {sourceCode} = context;
yield {
// Can't set it at the given location as the warning
// will be ignored due to the disable comment
loc: {
start: {
...sourceCode.getLoc(comment).start,
column: -1,
},
messageId: MESSAGE_ID,
};
}
end: sourceCode.getLoc(comment).end,
},
messageId: MESSAGE_ID,
};
}

@@ -37,0 +49,0 @@ },

@@ -41,3 +41,3 @@ const MESSAGE_ID_ERROR = 'no-accessor-recursion/error';

const isValidProperty = property =>
['Property', 'MethodDefinition'].includes(property.type)
['Property', 'MethodDefinition'].includes(property?.type)
&& !property.computed

@@ -44,0 +44,0 @@ && ['set', 'get'].includes(property.kind)

@@ -156,3 +156,3 @@ import {isSemicolonToken} from '@eslint-community/eslint-utils';

&& sourceCode.getLoc(body).start.line !== sourceCode.getLoc(parent).start.line
&& sourceCode.text.slice(classToken.range[1], body.range[0]).trim()
&& sourceCode.text.slice(sourceCode.getRange(classToken)[1], sourceCode.getRange(body)[0]).trim()
) {

@@ -159,0 +159,0 @@ yield fixer.replaceText(classToken, '{');

@@ -16,12 +16,5 @@ import {getStaticValue, getPropertyName} from '@eslint-community/eslint-utils';

const isPropertyThen = (node, context) => {
// `getPropertyName` throws on `({[Symbol.prototype]: 0})`
// https://github.com/eslint-community/eslint-utils/pull/182
try {
return getPropertyName(node, context.sourceCode.getScope(node)) === 'then';
} catch {}
const isPropertyThen = (node, context) =>
getPropertyName(node, context.sourceCode.getScope(node)) === 'then';
return false;
};
const cases = [

@@ -28,0 +21,0 @@ // `{then() {}}`,

@@ -71,4 +71,4 @@ import path from 'node:path';

const {browserlist, engines} = packageResult.packageJson;
return browserlist ?? engines;
const {browserslist, engines} = packageResult.packageJson;
return browserslist ?? engines;
}

@@ -131,6 +131,5 @@

const [, namespace, method = ''] = polyfill.feature.split('.');
const [, features] = Object.entries(coreJsEntries).find(
entry => entry[0] === `core-js/full/${namespace}${method && '/'}${method}`,
);
if (checkFeatures(features)) {
const features = coreJsEntries[`core-js/full/${namespace}${method && '/'}${method}`];
if (features && checkFeatures(features)) {
return {node, messageId: MESSAGE_ID_POLYFILL};

@@ -137,0 +136,0 @@ }

@@ -9,6 +9,10 @@ import {checkVueTemplate} from './utils/rule.js';

const fix = raw => {
/**
@param {string} raw
@param {Options} options
*/
const fix = (raw, {hexadecimalValue}) => {
let fixed = raw.toLowerCase();
if (fixed.startsWith('0x')) {
fixed = '0x' + fixed.slice(2).toUpperCase();
fixed = '0x' + fixed.slice(2)[hexadecimalValue === 'lowercase' ? 'toLowerCase' : 'toUpperCase']();
}

@@ -20,11 +24,15 @@

/** @param {import('eslint').Rule.RuleContext} context */
const create = () => ({
const create = context => ({
Literal(node) {
const {raw} = node;
/** @type {Options} */
const options = context.options[0] ?? {};
options.hexadecimalValue ??= 'uppercase';
let fixed = raw;
if (isNumberLiteral(node)) {
fixed = fix(raw);
fixed = fix(raw, options);
} else if (isBigIntLiteral(node)) {
fixed = fix(raw.slice(0, -1)) + 'n';
fixed = fix(raw.slice(0, -1), options) + 'n';
}

@@ -42,2 +50,18 @@

/** @typedef {Record<keyof typeof schema[0]["properties"], typeof caseEnum["enum"][number]>} Options */
const caseEnum = /** @type {const} */ ({
enum: ['uppercase', 'lowercase'],
});
const schema = [
{
type: 'object',
additionalProperties: false,
properties: {
hexadecimalValue: caseEnum,
},
},
];
/** @type {import('eslint').Rule.RuleModule} */

@@ -53,2 +77,6 @@ const config = {

fixable: 'code',
schema,
defaultOptions: [{
hexadecimalValue: 'uppercase',
}],
messages,

@@ -55,0 +83,0 @@ },

@@ -200,5 +200,5 @@ import {

&& tokenBefore.value === '-'
&& /^\s+$/.test(sourceCode.text.slice(tokenBefore.range[1], numberNode.range[0]))
&& /^\s+$/.test(sourceCode.text.slice(sourceCode.getRange(tokenBefore)[1], sourceCode.getRange(numberNode)[0]))
) {
yield fixer.removeRange([tokenBefore.range[1], numberNode.range[0]]);
yield fixer.removeRange([sourceCode.getRange(tokenBefore)[1], sourceCode.getRange(numberNode)[0]]);
}

@@ -279,3 +279,3 @@ }

const [, start] = getParenthesizedRange(sliceCall.arguments[0], sourceCode);
const [end] = sourceCode.getLastToken(sliceCall).range;
const [end] = sourceCode.getRange(sourceCode.getLastToken(sliceCall));
yield fixer.removeRange([start, end]);

@@ -282,0 +282,0 @@ }

@@ -8,2 +8,3 @@ import isBuiltinModule from 'is-builtin-module';

};
const NODE_PROTOCOL = 'node:';

@@ -31,8 +32,8 @@ const create = context => ({

if (
typeof value !== 'string'
|| value.startsWith('node:')
|| /^bun(?::|$)/.test(value)
|| !isBuiltinModule(value)
) {
if (!(
typeof value === 'string'
&& !value.startsWith(NODE_PROTOCOL)
&& isBuiltinModule(value)
&& isBuiltinModule(`${NODE_PROTOCOL}${value}`)
)) {
return;

@@ -47,3 +48,3 @@ }

/** @param {import('eslint').Rule.RuleFixer} fixer */
fix: fixer => fixer.insertTextAfterRange([insertPosition, insertPosition], 'node:'),
fix: fixer => fixer.insertTextAfterRange([insertPosition, insertPosition], NODE_PROTOCOL),
};

@@ -50,0 +51,0 @@ },

@@ -101,2 +101,3 @@ import {hasSideEffect} from '@eslint-community/eslint-utils';

const isNodeInsideNode = (inner, outer) =>
// eslint-disable-next-line internal/no-restricted-property-access
inner.range[0] >= outer.range[0] && inner.range[1] <= outer.range[1];

@@ -103,0 +104,0 @@ function hasBreakInside(breakStatements, node) {

@@ -255,2 +255,4 @@ /* eslint sort-keys: ["error", "asc", {"caseSensitive": false}] */

getStaticProps: true,
// The name iOS is a standard name for an OS
iOS: true,
// React PropTypes

@@ -257,0 +259,0 @@ // https://reactjs.org/docs/typechecking-with-proptypes.html

@@ -6,18 +6,25 @@ import packageJson from '../../package.json' with {type: 'json'};

/** @returns {{ [ruleName: string]: import('eslint').Rule.RuleModule }} */
export default function createDeprecatedRules(data) {
export default function createDeprecatedRules(rules) {
return Object.fromEntries(
Object.entries(data).map(([ruleId, replacedBy = []]) => [
ruleId,
{
create: () => ({}),
meta: {
docs: {
url: `${repoUrl}/blob/v${packageJson.version}/docs/deprecated-rules.md#${ruleId}`,
Object.entries(rules).map(([ruleId, deprecatedInfo]) => {
const url = `${repoUrl}/blob/v${packageJson.version}/docs/deprecated-rules.md#${ruleId}`;
return [
ruleId,
{
create: () => ({}),
meta: {
docs: {
description: deprecatedInfo.message,
url,
},
deprecated: {
message: deprecatedInfo.message,
url,
replacedBy: deprecatedInfo.replacedBy,
},
},
deprecated: true,
replacedBy: Array.isArray(replacedBy) ? replacedBy : [replacedBy],
},
},
]),
];
}),
);
}
const hasSameRange = (node1, node2) =>
node1
&& node2
// eslint-disable-next-line internal/no-restricted-property-access
&& node1.range[0] === node2.range[0]
// eslint-disable-next-line internal/no-restricted-property-access
&& node1.range[1] === node2.range[1];
export default hasSameRange;

@@ -49,4 +49,4 @@ import {isParenthesized, isOpeningParenToken, isClosingParenToken} from '@eslint-community/eslint-utils';

const parentheses = getParentheses(node, sourceCode);
const [start] = (parentheses[0] || node).range;
const [, end] = (parentheses.at(-1) || node).range;
const [start] = sourceCode.getRange(parentheses[0] ?? node);
const [, end] = sourceCode.getRange(parentheses.at(-1) ?? node);
return [start, end];

@@ -53,0 +53,0 @@ }