Socket
Socket
Sign inDemoInstall

eslint-plugin-unicorn

Package Overview
Dependencies
Maintainers
1
Versions
105
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 39.0.0 to 40.0.0

rules/fix/replace-argument.js

6

configs/recommended.js

@@ -43,2 +43,3 @@ 'use strict';

'unicorn/no-static-only-class': 'error',
'unicorn/no-thenable': 'error',
'unicorn/no-this-assignment': 'error',

@@ -50,2 +51,3 @@ 'unicorn/no-unreadable-array-destructuring': 'error',

'unicorn/no-useless-length-check': 'error',
'unicorn/no-useless-promise-resolve-reject': 'error',
'unicorn/no-useless-spread': 'error',

@@ -73,2 +75,3 @@ 'unicorn/no-useless-undefined': 'error',

'unicorn/prefer-includes': 'error',
'unicorn/prefer-json-parse-buffer': 'error',
'unicorn/prefer-keyboard-event-key': 'error',

@@ -82,4 +85,2 @@ 'unicorn/prefer-math-trunc': 'error',

'unicorn/prefer-object-from-entries': 'error',
// TODO: Enable this by default when targeting Node.js 16.
'unicorn/prefer-object-has-own': 'off',
'unicorn/prefer-optional-catch-binding': 'error',

@@ -103,2 +104,3 @@ 'unicorn/prefer-prototype-methods': 'error',

'unicorn/prevent-abbreviations': 'error',
'unicorn/relative-url-style': 'error',
'unicorn/require-array-join-separator': 'error',

@@ -105,0 +107,0 @@ 'unicorn/require-number-to-fixed-digits-argument': 'error',

@@ -18,2 +18,3 @@ 'use strict';

'prefer-node-remove': 'unicorn/prefer-dom-node-remove',
'prefer-object-has-own': 'prefer-object-has-own',
'prefer-replace-all': 'unicorn/prefer-string-replace-all',

@@ -20,0 +21,0 @@ 'prefer-starts-ends-with': 'unicorn/prefer-string-starts-ends-with',

{
"name": "eslint-plugin-unicorn",
"version": "39.0.0",
"version": "40.0.0",
"description": "Various awesome ESLint rules",

@@ -17,16 +17,17 @@ "license": "MIT",

"scripts": {
"create-rule": "node ./scripts/create-rule.mjs && npm run generate-rules-table && npm run generate-usage-example",
"fix": "run-p --continue-on-error fix:*",
"fix:js": "npm run lint:js -- --fix",
"fix:md": "npm run lint:md -- --fix",
"generate-rules-table": "node ./scripts/generate-rules-table.mjs",
"generate-usage-example": "node ./scripts/generate-usage-example.mjs",
"integration": "node ./test/integration/test.mjs",
"lint": "run-p --continue-on-error lint:*",
"lint:js": "xo",
"lint:md": "markdownlint \"**/*.md\"",
"test": "npm-run-all --continue-on-error lint test:*",
"test:js": "nyc ava",
"create-rule": "node ./scripts/create-rule.mjs && npm run generate-rules-table && npm run generate-usage-example",
"lint:package-json": "npmPkgJsonLint .",
"run-rules-on-codebase": "node ./test/run-rules-on-codebase/lint.mjs",
"integration": "node ./test/integration/test.mjs",
"smoke": "eslint-remote-tester --config ./test/smoke/eslint-remote-tester.config.js",
"generate-rules-table": "node ./scripts/generate-rules-table.mjs",
"generate-usage-example": "node ./scripts/generate-usage-example.mjs"
"test": "npm-run-all --continue-on-error lint test:*",
"test:js": "nyc ava"
},

@@ -49,9 +50,8 @@ "files": [

"dependencies": {
"@babel/helper-validator-identifier": "^7.14.9",
"ci-info": "^3.2.0",
"@babel/helper-validator-identifier": "^7.15.7",
"ci-info": "^3.3.0",
"clean-regexp": "^1.0.0",
"eslint-template-visitor": "^2.3.2",
"eslint-utils": "^3.0.0",
"esquery": "^1.4.0",
"indent-string": "4",
"indent-string": "^4.0.0",
"is-builtin-module": "^3.1.0",

@@ -61,3 +61,3 @@ "lodash": "^4.17.21",

"read-pkg-up": "^7.0.1",
"regexp-tree": "^0.1.23",
"regexp-tree": "^0.1.24",
"safe-regex": "^2.1.1",

@@ -68,26 +68,27 @@ "semver": "^7.3.5",

"devDependencies": {
"@babel/code-frame": "^7.14.5",
"@babel/core": "^7.15.5",
"@babel/eslint-parser": "^7.16.0",
"@babel/code-frame": "^7.16.0",
"@babel/core": "^7.16.5",
"@babel/eslint-parser": "^7.16.5",
"@lubien/fixture-beta-package": "^1.0.0-beta.1",
"@typescript-eslint/parser": "^5.2.0",
"@typescript-eslint/parser": "^5.7.0",
"ava": "^3.15.0",
"chalk": "^4.1.2",
"enquirer": "2.3.6",
"eslint": "^8.0.0",
"chalk": "^5.0.0",
"enquirer": "^2.3.6",
"eslint": "^8.5.0",
"eslint-ava-rule-tester": "^4.0.0",
"eslint-plugin-eslint-plugin": "^4.0.2",
"eslint-plugin-eslint-plugin": "^4.1.0",
"eslint-remote-tester": "^2.0.1",
"eslint-remote-tester-repositories": "^0.0.3",
"execa": "^5.1.1",
"execa": "^6.0.0",
"listr": "^0.14.3",
"lodash-es": "4.17.21",
"markdownlint-cli": "^0.29.0",
"lodash-es": "^4.17.21",
"markdownlint-cli": "^0.30.0",
"mem": "^9.0.1",
"npm-package-json-lint": "^5.4.2",
"npm-run-all": "^4.1.5",
"nyc": "^15.1.0",
"outdent": "^0.8.0",
"typescript": "^4.4.2",
"vue-eslint-parser": "^8.0.0",
"xo": "^0.46.3"
"typescript": "^4.5.4",
"vue-eslint-parser": "^8.0.1",
"xo": "^0.47.0"
},

@@ -94,0 +95,0 @@ "peerDependencies": {

@@ -77,2 +77,3 @@ # 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)

"unicorn/no-static-only-class": "error",
"unicorn/no-thenable": "error",
"unicorn/no-this-assignment": "error",

@@ -84,2 +85,3 @@ "unicorn/no-unreadable-array-destructuring": "error",

"unicorn/no-useless-length-check": "error",
"unicorn/no-useless-promise-resolve-reject": "error",
"unicorn/no-useless-spread": "error",

@@ -106,2 +108,3 @@ "unicorn/no-useless-undefined": "error",

"unicorn/prefer-includes": "error",
"unicorn/prefer-json-parse-buffer": "error",
"unicorn/prefer-keyboard-event-key": "error",

@@ -115,3 +118,2 @@ "unicorn/prefer-math-trunc": "error",

"unicorn/prefer-object-from-entries": "error",
"unicorn/prefer-object-has-own": "off",
"unicorn/prefer-optional-catch-binding": "error",

@@ -133,2 +135,3 @@ "unicorn/prefer-prototype-methods": "error",

"unicorn/prevent-abbreviations": "error",
"unicorn/relative-url-style": "error",
"unicorn/require-array-join-separator": "error",

@@ -196,2 +199,3 @@ "unicorn/require-number-to-fixed-digits-argument": "error",

| [no-static-only-class](docs/rules/no-static-only-class.md) | Forbid classes that only have static members. | ✅ | 🔧 | |
| [no-thenable](docs/rules/no-thenable.md) | Disallow `then` property. | ✅ | | |
| [no-this-assignment](docs/rules/no-this-assignment.md) | Disallow assigning `this` to a variable. | ✅ | | |

@@ -203,2 +207,3 @@ | [no-unreadable-array-destructuring](docs/rules/no-unreadable-array-destructuring.md) | Disallow unreadable array destructuring. | ✅ | 🔧 | |

| [no-useless-length-check](docs/rules/no-useless-length-check.md) | Disallow useless array length check. | ✅ | 🔧 | |
| [no-useless-promise-resolve-reject](docs/rules/no-useless-promise-resolve-reject.md) | Disallow returning/yielding `Promise.resolve/reject()` in async functions or promise callbacks | ✅ | 🔧 | |
| [no-useless-spread](docs/rules/no-useless-spread.md) | Disallow unnecessary spread. | ✅ | 🔧 | |

@@ -220,3 +225,3 @@ | [no-useless-undefined](docs/rules/no-useless-undefined.md) | Disallow useless `undefined`. | ✅ | 🔧 | |

| [prefer-dom-node-append](docs/rules/prefer-dom-node-append.md) | Prefer `Node#append()` over `Node#appendChild()`. | ✅ | 🔧 | |
| [prefer-dom-node-dataset](docs/rules/prefer-dom-node-dataset.md) | Prefer using `.dataset` on DOM elements over `.setAttribute(…)`. | ✅ | 🔧 | |
| [prefer-dom-node-dataset](docs/rules/prefer-dom-node-dataset.md) | Prefer using `.dataset` on DOM elements over calling attribute methods. | ✅ | 🔧 | |
| [prefer-dom-node-remove](docs/rules/prefer-dom-node-remove.md) | Prefer `childNode.remove()` over `parentNode.removeChild(childNode)`. | ✅ | 🔧 | 💡 |

@@ -226,2 +231,3 @@ | [prefer-dom-node-text-content](docs/rules/prefer-dom-node-text-content.md) | Prefer `.textContent` over `.innerText`. | ✅ | | 💡 |

| [prefer-includes](docs/rules/prefer-includes.md) | Prefer `.includes()` over `.indexOf()` and `Array#some()` when checking for existence or non-existence. | ✅ | 🔧 | 💡 |
| [prefer-json-parse-buffer](docs/rules/prefer-json-parse-buffer.md) | Prefer reading a JSON file as a buffer. | ✅ | 🔧 | |
| [prefer-keyboard-event-key](docs/rules/prefer-keyboard-event-key.md) | Prefer `KeyboardEvent#key` over `KeyboardEvent#keyCode`. | ✅ | 🔧 | |

@@ -235,3 +241,2 @@ | [prefer-math-trunc](docs/rules/prefer-math-trunc.md) | Enforce the use of `Math.trunc` instead of bitwise operators. | ✅ | 🔧 | 💡 |

| [prefer-object-from-entries](docs/rules/prefer-object-from-entries.md) | Prefer using `Object.fromEntries(…)` to transform a list of key-value pairs into an object. | ✅ | 🔧 | |
| [prefer-object-has-own](docs/rules/prefer-object-has-own.md) | Prefer `Object.hasOwn(…)` over `Object.prototype.hasOwnProperty.call(…)`. | | 🔧 | |
| [prefer-optional-catch-binding](docs/rules/prefer-optional-catch-binding.md) | Prefer omitting the `catch` binding parameter. | ✅ | 🔧 | |

@@ -253,2 +258,3 @@ | [prefer-prototype-methods](docs/rules/prefer-prototype-methods.md) | Prefer borrowing methods from the prototype instead of the instance. | ✅ | 🔧 | |

| [prevent-abbreviations](docs/rules/prevent-abbreviations.md) | Prevent abbreviations. | ✅ | 🔧 | |
| [relative-url-style](docs/rules/relative-url-style.md) | Enforce consistent relative URL style. | ✅ | 🔧 | |
| [require-array-join-separator](docs/rules/require-array-join-separator.md) | Enforce using the separator argument with `Array#join()`. | ✅ | 🔧 | |

@@ -255,0 +261,0 @@ | [require-number-to-fixed-digits-argument](docs/rules/require-number-to-fixed-digits-argument.md) | Enforce using the digits argument with `Number#toFixed()`. | ✅ | 🔧 | |

@@ -17,3 +17,3 @@ 'use strict';

const PLACEHOLDER_REGEX = new RegExp(PLACEHOLDER, 'i');
const isIgnoredChar = char => !/^[a-z\d-_$]$/i.test(char);
const isIgnoredChar = char => !/^[a-z\d-_]$/i.test(char);
const ignoredByDefault = new Set(['index.js', 'index.mjs', 'index.cjs', 'index.ts', 'index.tsx', 'index.vue']);

@@ -20,0 +20,0 @@ const isLowerCase = string => string === string.toLowerCase();

@@ -10,2 +10,3 @@ 'use strict';

removeArgument: require('./remove-argument.js'),
replaceArgument: require('./replace-argument.js'),
switchNewExpressionToCallExpression: require('./switch-new-expression-to-call-expression.js'),

@@ -21,2 +22,3 @@ switchCallExpressionToNewExpression: require('./switch-call-expression-to-new-expression.js'),

fixSpaceAroundKeyword: require('./fix-space-around-keywords.js'),
replaceStringLiteral: require('./replace-string-literal.js'),
};
'use strict';
const {defaultsDeep} = require('lodash');
const {getStringIfConstant} = require('eslint-utils');
const eslintTemplateVisitor = require('eslint-template-visitor');
const {callExpressionSelector} = require('./selectors/index.js');

@@ -134,21 +133,15 @@

const templates = eslintTemplateVisitor({
parserOptions: {
sourceType: 'module',
ecmaVersion: 2018,
},
});
const assignedDynamicImportSelector = [
'VariableDeclarator',
'[init.type="AwaitExpression"]',
'[init.argument.type="ImportExpression"]',
].join('');
const variableDeclarationVariable = templates.variableDeclarationVariable();
const assignmentTargetVariable = templates.variable();
const moduleNameVariable = templates.variable();
const assignedRequireSelector = [
'VariableDeclarator',
'[init.type="CallExpression"]',
'[init.callee.type="Identifier"]',
'[init.callee.name="require"]',
].join('');
const assignedDynamicImportTemplate = templates.template`async () => {
${variableDeclarationVariable} ${assignmentTargetVariable} = await import(${moduleNameVariable});
}`.narrow('BlockStatement > :has(AwaitExpression)');
const assignedRequireTemplate = templates.template`
${variableDeclarationVariable} ${assignmentTargetVariable} = require(${moduleNameVariable});
`;
/** @param {import('eslint').Rule.RuleContext} context */

@@ -239,5 +232,5 @@ const create = context => {

[assignedDynamicImportTemplate](node) {
const assignmentTargetNode = assignedDynamicImportTemplate.context.getMatch(assignmentTargetVariable);
const moduleNameNode = assignedDynamicImportTemplate.context.getMatch(moduleNameVariable);
[assignedDynamicImportSelector](node) {
const assignmentTargetNode = node.id;
const moduleNameNode = node.init.argument.source;
const moduleName = getStringIfConstant(moduleNameNode, context.getScope());

@@ -293,5 +286,5 @@

[assignedRequireTemplate](node) {
const assignmentTargetNode = assignedRequireTemplate.context.getMatch(assignmentTargetVariable);
const moduleNameNode = assignedRequireTemplate.context.getMatch(moduleNameVariable);
[assignedRequireSelector](node) {
const assignmentTargetNode = node.id;
const moduleNameNode = node.init.arguments[0];
const moduleName = getStringIfConstant(moduleNameNode, context.getScope());

@@ -311,3 +304,3 @@

return templates.visitor(visitor);
return visitor;
};

@@ -314,0 +307,0 @@

@@ -52,10 +52,6 @@ 'use strict';

'flatMap',
{
ignore: [
'Boolean',
],
},
],
[
'forEach', {
'forEach',
{
returnsUndefined: true,

@@ -69,3 +65,7 @@ },

ignore: [
'String',
'Number',
'BigInt',
'Boolean',
'Symbol',
],

@@ -72,0 +72,0 @@ },

@@ -365,2 +365,4 @@ 'use strict';

'R',
// https://www.npmjs.com/package/p-iteration
'pIteration',
];

@@ -367,0 +369,0 @@

@@ -46,3 +46,11 @@ 'use strict';

};
const ignoredObjects = ['stream', 'this', 'this.stream', ...ignore];
const ignoredObjects = [
'stream',
'this',
'this.stream',
'process.stdin',
'process.stdout',
'process.stderr',
...ignore,
];
const sourceCode = context.getSourceCode();

@@ -49,0 +57,0 @@

'use strict';
const getPropertyName = require('./utils/get-property-name.js');
const getKeyName = require('./utils/get-key-name.js');

@@ -20,3 +20,3 @@ const MESSAGE_ID = 'no-document-cookie';

[selector](node) {
if (getPropertyName(node, context.getScope()) !== 'cookie') {
if (getKeyName(node, context.getScope()) !== 'cookie') {
return;

@@ -23,0 +23,0 @@ }

@@ -5,2 +5,3 @@ 'use strict';

const {newExpressionSelector} = require('./selectors/index.js');
const isNumber = require('./utils/is-number.js');

@@ -52,26 +53,26 @@ const MESSAGE_ID_ERROR = 'error';

const result = getStaticValue(argumentNode, context.getScope());
const fromLengthText = `Array.from(${text === 'length' ? '{length}' : `{length: ${text}}`})`;
if (isNumber(argumentNode, context.getScope())) {
problem.fix = fixer => fixer.replaceText(node, fromLengthText);
return problem;
}
const onlyElementText = `${maybeSemiColon}[${text}]`;
// We don't know the argument is number or not
if (result === null) {
problem.suggest = [
{
messageId: MESSAGE_ID_LENGTH,
fix: fixer => fixer.replaceText(node, fromLengthText),
},
{
messageId: MESSAGE_ID_ONLY_ELEMENT,
fix: fixer => fixer.replaceText(node, onlyElementText),
},
];
const result = getStaticValue(argumentNode, context.getScope());
if (result !== null && typeof result.value !== 'number') {
problem.fix = fixer => fixer.replaceText(node, onlyElementText);
return problem;
}
problem.fix = fixer => fixer.replaceText(
node,
typeof result.value === 'number' ? fromLengthText : onlyElementText,
);
// We don't know the argument is number or not
problem.suggest = [
{
messageId: MESSAGE_ID_LENGTH,
fix: fixer => fixer.replaceText(node, fromLengthText),
},
{
messageId: MESSAGE_ID_ONLY_ELEMENT,
fix: fixer => fixer.replaceText(node, onlyElementText),
},
];
return problem;

@@ -78,0 +79,0 @@ }

@@ -5,2 +5,3 @@ 'use strict';

const {switchNewExpressionToCallExpression} = require('./fix/index.js');
const isNumber = require('./utils/is-number.js');

@@ -30,9 +31,9 @@ const ERROR = 'error';

if (isNumber(firstArgument, scope)) {
return 'alloc';
}
const staticResult = getStaticValue(firstArgument, scope);
if (staticResult) {
const {value} = staticResult;
if (typeof value === 'number') {
return 'alloc';
}
if (

@@ -39,0 +40,0 @@ typeof value === 'string'

@@ -13,2 +13,3 @@ 'use strict';

message: 'Note that there is difference between `SharedWorker#onmessage` and `SharedWorker#addEventListener(\'message\')`.',
error: 'Note that there is difference between `{window,element}.onerror` and `{window,element}.addEventListener(\'error\')`.',
};

@@ -141,2 +142,5 @@

extra = extraMessages.message;
} else if (eventTypeName === 'error') {
// Disable `onerror` fix, see #1493
extra = extraMessages.error;
} else {

@@ -143,0 +147,0 @@ fix = fixer => fixCode(fixer, context.getSourceCode(), node, memberExpression);

'use strict';
const isValidVariableName = require('./utils/is-valid-variable-name.js');
const quoteString = require('./utils/quote-string.js');
const {methodCallSelector} = require('./selectors/index.js');
const {methodCallSelector, matches} = require('./selectors/index.js');
const MESSAGE_ID = 'prefer-dom-node-dataset';
const messages = {
[MESSAGE_ID]: 'Prefer `.dataset` over `setAttribute(…)`.',
[MESSAGE_ID]: 'Prefer `.dataset` over `{{method}}(…)`.',
};
const selector = [
methodCallSelector({
method: 'setAttribute',
argumentsLength: 2,
}),
matches([
methodCallSelector({method: 'setAttribute', argumentsLength: 2}),
methodCallSelector({methods: ['getAttribute', 'removeAttribute', 'hasAttribute'], argumentsLength: 1}),
]),
'[arguments.0.type="Literal"]',
].join('');
const parseNodeText = (context, argument) => context.getSourceCode().getText(argument);
const dashToCamelCase = string => string.replace(/-[a-z]/g, s => s[1].toUpperCase());
const fix = (context, node, fixer) => {
const [nameNode, valueNode] = node.arguments;
const calleeObject = parseNodeText(context, node.callee.object);
const name = dashToCamelCase(nameNode.value.slice(5));
const value = parseNodeText(context, valueNode);
const replacement = `${calleeObject}.dataset${
isValidVariableName(name)
? `.${name}`
: `[${quoteString(name, nameNode.raw.charAt(0))}]`
} = ${value}`;
return fixer.replaceText(node, replacement);
};
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
[selector](node) {
const name = node.arguments[0].value;
const [nameNode] = node.arguments;
let attributeName = nameNode.value;
if (typeof name !== 'string' || !name.startsWith('data-') || name === 'data-') {
if (typeof attributeName !== 'string') {
return;
}
attributeName = attributeName.toLowerCase();
if (!attributeName.startsWith('data-')) {
return;
}
const method = node.callee.property.name;
const name = dashToCamelCase(attributeName.slice(5));
const sourceCode = context.getSourceCode();
let text = '';
const datasetText = `${sourceCode.getText(node.callee.object)}.dataset`;
switch (method) {
case 'setAttribute':
case 'getAttribute':
case 'removeAttribute': {
text = isValidVariableName(name) ? `.${name}` : `[${quoteString(name, nameNode.raw.charAt(0))}]`;
text = `${datasetText}${text}`;
if (method === 'setAttribute') {
text += ` = ${sourceCode.getText(node.arguments[1])}`;
} else if (method === 'removeAttribute') {
text = `delete ${text}`;
}
/*
For non-exists attribute, `element.getAttribute('data-foo')` returns `null`,
but `element.dataset.foo` returns `undefined`, switch to suggestions if necessary
*/
break;
}
case 'hasAttribute':
text = `Object.hasOwn(${datasetText}, ${quoteString(name, nameNode.raw.charAt(0))})`;
break;
// No default
}
return {
node,
messageId: MESSAGE_ID,
fix: fixer => fix(context, node, fixer),
data: {method},
fix: fixer => fixer.replaceText(node, text),
};

@@ -62,3 +83,3 @@ },

docs: {
description: 'Prefer using `.dataset` on DOM elements over `.setAttribute(…)`.',
description: 'Prefer using `.dataset` on DOM elements over calling attribute methods.',
},

@@ -65,0 +86,0 @@ fixable: 'code',

@@ -15,2 +15,16 @@ 'use strict';

// Default import/export can be `Identifier`, have to use `Symbol.for`
const DEFAULT_SPECIFIER_NAME = Symbol.for('default');
const NAMESPACE_SPECIFIER_NAME = Symbol('NAMESPACE_SPECIFIER_NAME');
const getSpecifierName = node => {
switch (node.type) {
case 'Identifier':
return Symbol.for(node.name);
case 'Literal':
return node.value;
// No default
}
};
function * removeSpecifier(node, fixer, sourceCode) {

@@ -78,4 +92,14 @@ const {parent} = node;

function getSourceAndAssertionsText(declaration, sourceCode) {
const keywordFromToken = sourceCode.getTokenBefore(
declaration.source,
token => token.type === 'Identifier' && token.value === 'from',
);
const [start] = keywordFromToken.range;
const [, end] = declaration.range;
return sourceCode.text.slice(start, end);
}
function getFixFunction({
context,
sourceCode,
imported,

@@ -86,6 +110,5 @@ exported,

}) {
const sourceCode = context.getSourceCode();
const sourceNode = imported.declaration.source;
const importDeclaration = imported.declaration;
const sourceNode = importDeclaration.source;
const sourceValue = sourceNode.value;
const sourceText = sourceCode.getText(sourceNode);
const exportDeclaration = exportDeclarations.find(({source}) => source.value === sourceValue);

@@ -95,11 +118,11 @@

return function * (fixer) {
if (imported.name === '*') {
if (imported.name === NAMESPACE_SPECIFIER_NAME) {
yield fixer.insertTextAfter(
program,
`\nexport * as ${exported.name} from ${sourceText};`,
`\nexport * as ${exported.text} ${getSourceAndAssertionsText(importDeclaration, sourceCode)}`,
);
} else {
const specifier = exported.name === imported.name
? exported.name
: `${imported.name} as ${exported.name}`;
? exported.text
: `${imported.text} as ${exported.text}`;

@@ -119,3 +142,3 @@ if (exportDeclaration) {

program,
`\nexport {${specifier}} from ${sourceText};`,
`\nexport {${specifier}} ${getSourceAndAssertionsText(importDeclaration, sourceCode)}`,
);

@@ -133,18 +156,3 @@ }

function getImportedName(specifier) {
switch (specifier.type) {
case 'ImportDefaultSpecifier':
return 'default';
case 'ImportSpecifier':
return specifier.imported.name;
case 'ImportNamespaceSpecifier':
return '*';
// No default
}
}
function getExported(identifier, context) {
function getExported(identifier, context, sourceCode) {
const {parent} = identifier;

@@ -155,3 +163,4 @@ switch (parent.type) {

node: parent,
name: 'default',
name: DEFAULT_SPECIFIER_NAME,
text: 'default',
};

@@ -162,3 +171,4 @@

node: parent,
name: parent.exported.name,
name: getSpecifierName(parent.exported),
text: sourceCode.getText(parent.exported),
};

@@ -180,3 +190,4 @@

node: parent.parent.parent,
name: parent.id.name,
name: Symbol.for(parent.id.name),
text: sourceCode.getText(parent.id),
};

@@ -207,6 +218,5 @@ }

function getImported(variable) {
const specifier = variable.identifiers[0].parent;
return {
name: getImportedName(specifier),
function getImported(variable, sourceCode) {
const specifier = variable.defs[0].node;
const result = {
node: specifier,

@@ -216,8 +226,33 @@ declaration: specifier.parent,

};
switch (specifier.type) {
case 'ImportDefaultSpecifier':
return {
name: DEFAULT_SPECIFIER_NAME,
text: 'default',
...result,
};
case 'ImportSpecifier':
return {
name: getSpecifierName(specifier.imported),
text: sourceCode.getText(specifier.imported),
...result,
};
case 'ImportNamespaceSpecifier':
return {
name: NAMESPACE_SPECIFIER_NAME,
text: '*',
...result,
};
// No default
}
}
function getExports(imported, context) {
function getExports(imported, context, sourceCode) {
const exports = [];
for (const {identifier} of imported.variable.references) {
const exported = getExported(identifier, context);
const exported = getExported(identifier, context, sourceCode);

@@ -236,3 +271,3 @@ if (!exported) {

*/
if (imported.name === '*' && exported.name === 'default') {
if (imported.name === NAMESPACE_SPECIFIER_NAME && exported.name === DEFAULT_SPECIFIER_NAME) {
continue;

@@ -262,2 +297,3 @@ }

function create(context) {
const sourceCode = context.getSourceCode();
const {ignoreUsedVariables} = {ignoreUsedVariables: false, ...context.options[0]};

@@ -277,14 +313,19 @@ const importDeclarations = new Set();

for (const importDeclaration of importDeclarations) {
const variables = context.getDeclaredVariables(importDeclaration)
.map(variable => {
const imported = getImported(variable);
const exports = getExports(imported, context);
let variables = context.getDeclaredVariables(importDeclaration);
return {
variable,
imported,
exports,
};
});
if (variables.some(variable => variable.defs.length !== 1 || variable.defs[0].parent !== importDeclaration)) {
continue;
}
variables = variables.map(variable => {
const imported = getImported(variable, sourceCode);
const exports = getExports(imported, context, sourceCode);
return {
variable,
imported,
exports,
};
});
if (

@@ -306,7 +347,7 @@ ignoreUsedVariables

data: {
exported: exported.name,
exported: exported.text,
},
};
const fix = getFixFunction({
context,
sourceCode,
imported,

@@ -313,0 +354,0 @@ exported,

@@ -64,11 +64,11 @@ 'use strict';

const fix = function * (fixer) {
let fixed = mathTruncFunctionCall(left);
const fixed = mathTruncFunctionCall(left);
if (isAssignment) {
// TODO[@fisker]: Improve this fix, don't touch left
fixed = `${sourceCode.getText(left)} = ${fixed}`;
const operatorToken = sourceCode.getTokenAfter(left, token => token.type === 'Punctuator' && token.value === operator);
yield fixer.replaceText(operatorToken, '=');
yield fixer.replaceText(right, fixed);
} else {
yield * fixSpaceAroundKeyword(fixer, node, sourceCode);
yield fixer.replaceText(node, fixed);
}
yield fixer.replaceText(node, fixed);
};

@@ -75,0 +75,0 @@

'use strict';
const isBuiltinModule = require('is-builtin-module');
const {matches, STATIC_REQUIRE_SOURCE_SELECTOR} = require('./selectors/index.js');
const {replaceStringLiteral} = require('./fix/index.js');

@@ -38,3 +39,2 @@ const MESSAGE_ID = 'prefer-node-protocol';

const firstCharacterIndex = node.range[0] + 1;
return {

@@ -45,3 +45,3 @@ node,

/** @param {import('eslint').Rule.RuleFixer} fixer */
fix: fixer => fixer.insertTextBeforeRange([firstCharacterIndex, firstCharacterIndex], 'node:'),
fix: fixer => replaceStringLiteral(fixer, node, 'node:', 0, 0),
};

@@ -48,0 +48,0 @@ },

@@ -8,3 +8,3 @@ 'use strict';

} = require('./selectors/index.js');
const getPropertyName = require('./utils/get-property-name.js');
const getKeyName = require('./utils/get-key-name.js');
const {fixSpaceAroundKeyword} = require('./fix/index.js');

@@ -45,3 +45,3 @@

const constructorName = node.object.type === 'ArrayExpression' ? 'Array' : 'Object';
const methodName = getPropertyName(node, context.getScope());
const methodName = getKeyName(node, context.getScope());

@@ -48,0 +48,0 @@ return {

'use strict';
const isLiteralValue = require('./utils/is-literal-value.js');
const getPropertyName = require('./utils/get-property-name.js');
const getKeyName = require('./utils/get-key-name.js');
const {not, methodCallSelector} = require('./selectors/index.js');

@@ -34,3 +34,3 @@

if (
getPropertyName(node.callee) === 'apply'
getKeyName(node.callee) === 'apply'
&& node.arguments.length === 2

@@ -50,5 +50,5 @@ && isApplySignature(node.arguments[0], node.arguments[1])

if (
getPropertyName(node.callee) === 'call'
&& getPropertyName(node.callee.object) === 'apply'
&& getPropertyName(node.callee.object.object) === 'prototype'
getKeyName(node.callee) === 'call'
&& getKeyName(node.callee.object) === 'apply'
&& getKeyName(node.callee.object.object) === 'prototype'
&& node.callee.object.object.object

@@ -55,0 +55,0 @@ && node.callee.object.object.object.type === 'Identifier'

'use strict';
const eslintTemplateVisitor = require('eslint-template-visitor');
const {getParenthesizedText} = require('./utils/parentheses.js');
const {getStaticValue} = require('eslint-utils');
const {getParenthesizedText, getParenthesizedRange} = require('./utils/parentheses.js');
const {methodCallSelector} = require('./selectors/index.js');
const isNumber = require('./utils/is-number.js');
const {replaceArgument} = require('./fix/index.js');

@@ -12,10 +15,8 @@ const MESSAGE_ID_SUBSTR = 'substr';

const templates = eslintTemplateVisitor();
const selector = methodCallSelector({
methods: ['substr', 'substring'],
includeOptionalMember: true,
includeOptionalCall: true,
});
const objectVariable = templates.variable();
const argumentsVariable = templates.spreadVariable();
const substrCallTemplate = templates.template`${objectVariable}.substr(${argumentsVariable})`;
const substringCallTemplate = templates.template`${objectVariable}.substring(${argumentsVariable})`;
const isLiteralNumber = node => node && node.type === 'Literal' && typeof node.value === 'number';

@@ -42,145 +43,136 @@

const isLikelyNumeric = node => isLiteralNumber(node) || isLengthProperty(node);
function * fixSubstrArguments({node, fixer, context, abort}) {
const argumentNodes = node.arguments;
const [firstArgument, secondArgument] = argumentNodes;
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => {
if (!secondArgument) {
return;
}
const scope = context.getScope();
const sourceCode = context.getSourceCode();
const firstArgumentStaticResult = getStaticValue(firstArgument, scope);
const secondArgumentRange = getParenthesizedRange(secondArgument, sourceCode);
const replaceSecondArgument = text => replaceArgument(fixer, secondArgument, text, sourceCode);
return templates.visitor({
[substrCallTemplate](node) {
const objectNode = substrCallTemplate.context.getMatch(objectVariable);
const argumentNodes = substrCallTemplate.context.getMatch(argumentsVariable);
if (firstArgumentStaticResult && firstArgumentStaticResult.value === 0) {
if (isLiteralNumber(secondArgument) || isLengthProperty(secondArgument)) {
return;
}
const problem = {
node,
messageId: MESSAGE_ID_SUBSTR,
};
if (typeof getNumericValue(secondArgument) === 'number') {
yield replaceSecondArgument(Math.max(0, getNumericValue(secondArgument)));
return;
}
const firstArgument = argumentNodes[0] ? sourceCode.getText(argumentNodes[0]) : undefined;
const secondArgument = argumentNodes[1] ? sourceCode.getText(argumentNodes[1]) : undefined;
yield fixer.insertTextBeforeRange(secondArgumentRange, 'Math.max(0, ');
yield fixer.insertTextAfterRange(secondArgumentRange, ')');
return;
}
let sliceArguments;
if (argumentNodes.every(node => isLiteralNumber(node))) {
yield replaceSecondArgument(firstArgument.value + secondArgument.value);
return;
}
switch (argumentNodes.length) {
case 0: {
sliceArguments = [];
break;
}
if (argumentNodes.every(node => isNumber(node, context.getScope()))) {
const firstArgumentText = getParenthesizedText(firstArgument, sourceCode);
case 1: {
sliceArguments = [firstArgument];
break;
}
yield fixer.insertTextBeforeRange(secondArgumentRange, `${firstArgumentText} + `);
return;
}
case 2: {
if (firstArgument === '0') {
sliceArguments = [firstArgument];
if (isLiteralNumber(secondArgument) || isLengthProperty(argumentNodes[1])) {
sliceArguments.push(secondArgument);
} else if (typeof getNumericValue(argumentNodes[1]) === 'number') {
sliceArguments.push(Math.max(0, getNumericValue(argumentNodes[1])));
} else {
sliceArguments.push(`Math.max(0, ${secondArgument})`);
}
} else if (
isLiteralNumber(argumentNodes[0])
&& isLiteralNumber(argumentNodes[1])
) {
sliceArguments = [
firstArgument,
argumentNodes[0].value + argumentNodes[1].value,
];
} else if (
isLikelyNumeric(argumentNodes[0])
&& isLikelyNumeric(argumentNodes[1])
) {
sliceArguments = [firstArgument, firstArgument + ' + ' + secondArgument];
}
return abort();
}
break;
}
// No default
}
function * fixSubstringArguments({node, fixer, context, abort}) {
const sourceCode = context.getSourceCode();
const [firstArgument, secondArgument] = node.arguments;
if (sliceArguments) {
const objectText = getParenthesizedText(objectNode, sourceCode);
const optionalMemberSuffix = node.callee.optional ? '?' : '';
const optionalCallSuffix = node.optional ? '?.' : '';
const firstNumber = firstArgument ? getNumericValue(firstArgument) : undefined;
const firstArgumentText = getParenthesizedText(firstArgument, sourceCode);
const replaceFirstArgument = text => replaceArgument(fixer, firstArgument, text, sourceCode);
problem.fix = fixer => fixer.replaceText(node, `${objectText}${optionalMemberSuffix}.slice${optionalCallSuffix}(${sliceArguments.join(', ')})`);
}
if (!secondArgument) {
if (isLengthProperty(firstArgument)) {
return;
}
context.report(problem);
},
if (firstNumber !== undefined) {
yield replaceFirstArgument(Math.max(0, firstNumber));
return;
}
[substringCallTemplate](node) {
const objectNode = substringCallTemplate.context.getMatch(objectVariable);
const argumentNodes = substringCallTemplate.context.getMatch(argumentsVariable);
const firstArgumentRange = getParenthesizedRange(firstArgument, sourceCode);
yield fixer.insertTextBeforeRange(firstArgumentRange, 'Math.max(0, ');
yield fixer.insertTextAfterRange(firstArgumentRange, ')');
return;
}
const problem = {
node,
messageId: MESSAGE_ID_SUBSTRING,
};
const secondNumber = getNumericValue(secondArgument);
const secondArgumentText = getParenthesizedText(secondArgument, sourceCode);
const replaceSecondArgument = text => replaceArgument(fixer, secondArgument, text, sourceCode);
const firstArgument = argumentNodes[0] ? sourceCode.getText(argumentNodes[0]) : undefined;
const secondArgument = argumentNodes[1] ? sourceCode.getText(argumentNodes[1]) : undefined;
if (firstNumber !== undefined && secondNumber !== undefined) {
const argumentsValue = [Math.max(0, firstNumber), Math.max(0, secondNumber)];
if (firstNumber > secondNumber) {
argumentsValue.reverse();
}
const firstNumber = argumentNodes[0] ? getNumericValue(argumentNodes[0]) : undefined;
if (argumentsValue[0] !== firstNumber) {
yield replaceFirstArgument(argumentsValue[0]);
}
let sliceArguments;
if (argumentsValue[1] !== secondNumber) {
yield replaceSecondArgument(argumentsValue[1]);
}
switch (argumentNodes.length) {
case 0: {
sliceArguments = [];
break;
}
return;
}
case 1: {
if (firstNumber !== undefined) {
sliceArguments = [Math.max(0, firstNumber)];
} else if (isLengthProperty(argumentNodes[0])) {
sliceArguments = [firstArgument];
} else {
sliceArguments = [`Math.max(0, ${firstArgument})`];
}
if (firstNumber === 0 || secondNumber === 0) {
yield replaceFirstArgument(0);
yield replaceSecondArgument(`Math.max(0, ${firstNumber === 0 ? secondArgumentText : firstArgumentText})`);
return;
}
break;
}
// As values aren't Literal, we can not know whether secondArgument will become smaller than the first or not, causing an issue:
// .substring(0, 2) and .substring(2, 0) returns the same result
// .slice(0, 2) and .slice(2, 0) doesn't return the same result
// There's also an issue with us now knowing whether the value will be negative or not, due to:
// .substring() treats a negative number the same as it treats a zero.
// The latter issue could be solved by wrapping all dynamic numbers in Math.max(0, <value>), but the resulting code would not be nice
case 2: {
const secondNumber = getNumericValue(argumentNodes[1]);
return abort();
}
if (firstNumber !== undefined && secondNumber !== undefined) {
sliceArguments = firstNumber > secondNumber
? [Math.max(0, secondNumber), Math.max(0, firstNumber)]
: [Math.max(0, firstNumber), Math.max(0, secondNumber)];
} else if (firstNumber === 0 || secondNumber === 0) {
sliceArguments = [0, `Math.max(0, ${firstNumber === 0 ? secondArgument : firstArgument})`];
} else {
// As values aren't Literal, we can not know whether secondArgument will become smaller than the first or not, causing an issue:
// .substring(0, 2) and .substring(2, 0) returns the same result
// .slice(0, 2) and .slice(2, 0) doesn't return the same result
// There's also an issue with us now knowing whether the value will be negative or not, due to:
// .substring() treats a negative number the same as it treats a zero.
// The latter issue could be solved by wrapping all dynamic numbers in Math.max(0, <value>), but the resulting code would not be nice
}
/** @param {import('eslint').Rule.RuleContext} context */
const create = context => ({
[selector](node) {
const method = node.callee.property.name;
break;
return {
node,
messageId: method,
* fix(fixer, {abort}) {
yield fixer.replaceText(node.callee.property, 'slice');
if (node.arguments.length === 0) {
return;
}
// No default
}
if (sliceArguments) {
const objectText = getParenthesizedText(objectNode, sourceCode);
const optionalMemberSuffix = node.callee.optional ? '?' : '';
const optionalCallSuffix = node.optional ? '?.' : '';
if (
node.arguments.length > 2
|| node.arguments.some(node => node.type === 'SpreadElement')
) {
return abort();
}
problem.fix = fixer => fixer.replaceText(node, `${objectText}${optionalMemberSuffix}.slice${optionalCallSuffix}(${sliceArguments.join(', ')})`);
}
const fixArguments = method === 'substr' ? fixSubstrArguments : fixSubstringArguments;
yield * fixArguments({node, fixer, context, abort});
},
};
},
});
context.report(problem);
},
});
};
/** @type {import('eslint').Rule.RuleModule} */

@@ -187,0 +179,0 @@ module.exports = {

@@ -16,3 +16,5 @@ 'use strict';

const topLevelCallExpression = 'Program > ExpressionStatement > CallExpression[optional!=true].expression';
const promiseMethods = ['then', 'catch', 'finally'];
const topLevelCallExpression = 'CallExpression:not(:function *)';
const iife = [

@@ -31,3 +33,4 @@ topLevelCallExpression,

path: 'callee',
properties: ['then', 'catch', 'finally'],
properties: promiseMethods,
includeOptional: true,
}),

@@ -40,2 +43,18 @@ ].join('');

const isPromiseMethodCalleeObject = node =>
node.parent.type === 'MemberExpression'
&& node.parent.object === node
&& !node.parent.computed
&& node.parent.property.type === 'Identifier'
&& promiseMethods.includes(node.parent.property.name)
&& node.parent.parent.type === 'CallExpression'
&& node.parent.parent.callee === node.parent;
const isAwaitArgument = node => {
if (node.parent.type === 'ChainExpression') {
node = node.parent;
}
return node.parent.type === 'AwaitExpression' && node.parent.argument === node;
};
/** @param {import('eslint').Rule.RuleContext} context */

@@ -45,2 +64,6 @@ function create(context) {

[promise](node) {
if (isPromiseMethodCalleeObject(node) || isAwaitArgument(node)) {
return;
}
return {

@@ -52,2 +75,6 @@ node: node.callee.property,

[iife](node) {
if (isPromiseMethodCalleeObject(node) || isAwaitArgument(node)) {
return;
}
return {

@@ -60,2 +87,6 @@ node,

[identifier](node) {
if (isPromiseMethodCalleeObject(node) || isAwaitArgument(node)) {
return;
}
const variable = findVariable(context.getScope(), node.callee);

@@ -62,0 +93,0 @@ if (!variable || variable.defs.length !== 1) {

@@ -6,2 +6,3 @@ 'use strict';

const {replaceTemplateElement} = require('./fix/index.js');
const {callExpressionSelector, methodCallSelector} = require('./selectors/index.js');

@@ -13,2 +14,8 @@ const MESSAGE_ID_IMPROPERLY_INDENTED_TEMPLATE = 'template-indent';

const jestInlineSnapshotSelector = [
callExpressionSelector({name: 'expect', path: 'callee.object', argumentsLength: 1}),
methodCallSelector({method: 'toMatchInlineSnapshot', argumentsLength: 1}),
' > TemplateLiteral.arguments:first-child',
].join('');
/** @param {import('eslint').Rule.RuleContext} context */

@@ -20,3 +27,3 @@ const create = context => {

functions: ['dedent', 'stripIndent'],
selectors: [],
selectors: [jestInlineSnapshotSelector],
comments: ['HTML', 'indent'],

@@ -23,0 +30,0 @@ ...context.options[0],

@@ -8,2 +8,30 @@ 'use strict';

class FixAbortError extends Error {}
const fixOptions = {
abort() {
throw new FixAbortError('Fix aborted.');
},
};
function wrapFixFunction(fix) {
return fixer => {
const result = fix(fixer, fixOptions);
if (result && isIterable(result)) {
try {
return [...result];
} catch (error) {
if (error instanceof FixAbortError) {
return;
}
/* istanbul ignore next: Safe */
throw error;
}
}
return result;
};
}
function reportListenerProblems(listener, context) {

@@ -22,7 +50,16 @@ // Listener arguments can be `codePath, node` or `node`

// TODO: Allow `fix` function to abort
for (const problem of problems) {
if (problem) {
context.report(problem);
if (problem.fix) {
problem.fix = wrapFixFunction(problem.fix);
}
if (Array.isArray(problem.suggest)) {
for (const suggest of problem.suggest) {
if (suggest.fix) {
suggest.fix = wrapFixFunction(suggest.fix);
}
}
}
context.report(problem);
}

@@ -29,0 +66,0 @@ };

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc