eslint-plugin-unicorn
Advanced tools
Comparing version 39.0.0 to 40.0.0
@@ -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 @@ }; |
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
511445
15
193
16538
310
25
- Removedeslint-template-visitor@^2.3.2
- Removed@ampproject/remapping@2.3.0(transitive)
- Removed@babel/compat-data@7.25.4(transitive)
- Removed@babel/core@7.25.2(transitive)
- Removed@babel/eslint-parser@7.25.1(transitive)
- Removed@babel/generator@7.25.6(transitive)
- Removed@babel/helper-compilation-targets@7.25.2(transitive)
- Removed@babel/helper-module-imports@7.24.7(transitive)
- Removed@babel/helper-module-transforms@7.25.2(transitive)
- Removed@babel/helper-simple-access@7.24.7(transitive)
- Removed@babel/helper-string-parser@7.24.8(transitive)
- Removed@babel/helper-validator-option@7.24.8(transitive)
- Removed@babel/helpers@7.25.6(transitive)
- Removed@babel/parser@7.25.6(transitive)
- Removed@babel/template@7.25.0(transitive)
- Removed@babel/traverse@7.25.6(transitive)
- Removed@babel/types@7.25.6(transitive)
- Removed@jridgewell/gen-mapping@0.3.5(transitive)
- Removed@jridgewell/resolve-uri@3.1.2(transitive)
- Removed@jridgewell/set-array@1.2.1(transitive)
- Removed@jridgewell/sourcemap-codec@1.5.0(transitive)
- Removed@jridgewell/trace-mapping@0.3.25(transitive)
- Removed@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1(transitive)
- Removedbrowserslist@4.23.3(transitive)
- Removedcaniuse-lite@1.0.30001660(transitive)
- Removedconvert-source-map@2.0.0(transitive)
- Removedelectron-to-chromium@1.5.25(transitive)
- Removedescalade@3.2.0(transitive)
- Removedeslint-scope@5.1.1(transitive)
- Removedeslint-template-visitor@2.3.2(transitive)
- Removedestraverse@4.3.0(transitive)
- Removedgensync@1.0.0-beta.2(transitive)
- Removedglobals@11.12.0(transitive)
- Removedjsesc@2.5.2(transitive)
- Removedjson5@2.2.3(transitive)
- Removedlru-cache@5.1.1(transitive)
- Removedmultimap@1.1.0(transitive)
- Removednode-releases@2.0.18(transitive)
- Removedsemver@6.3.1(transitive)
- Removedto-fast-properties@2.0.0(transitive)
- Removedupdate-browserslist-db@1.1.0(transitive)
- Removedyallist@3.1.1(transitive)
Updatedci-info@^3.3.0
Updatedindent-string@^4.0.0
Updatedregexp-tree@^0.1.24