123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183 |
- 'use strict';
- const {
- getNegativeIndexLengthNode,
- removeLengthNode,
- } = require('./shared/negative-index.js');
- const typedArray = require('./shared/typed-array.js');
- const {isLiteral} = require('./ast/index.js');
- const MESSAGE_ID = 'prefer-negative-index';
- const messages = {
- [MESSAGE_ID]: 'Prefer negative index over length minus index for `{{method}}`.',
- };
- const methods = new Map([
- [
- 'slice',
- {
- argumentsIndexes: [0, 1],
- supportObjects: new Set([
- 'Array',
- 'String',
- 'ArrayBuffer',
- ...typedArray,
- // `{Blob,File}#slice()` are not generally used
- // 'Blob'
- // 'File'
- ]),
- },
- ],
- [
- 'splice',
- {
- argumentsIndexes: [0],
- supportObjects: new Set([
- 'Array',
- ]),
- },
- ],
- [
- 'at',
- {
- argumentsIndexes: [0],
- supportObjects: new Set([
- 'Array',
- 'String',
- ...typedArray,
- ]),
- },
- ],
- ]);
- const getMemberName = node => {
- const {type, property} = node;
- if (
- type === 'MemberExpression'
- && property.type === 'Identifier'
- ) {
- return property.name;
- }
- };
- function parse(node) {
- const {callee, arguments: originalArguments} = node;
- let method = callee.property.name;
- let target = callee.object;
- let argumentsNodes = originalArguments;
- if (methods.has(method)) {
- return {
- method,
- target,
- argumentsNodes,
- };
- }
- if (method !== 'call' && method !== 'apply') {
- return;
- }
- const isApply = method === 'apply';
- method = getMemberName(callee.object);
- if (!methods.has(method)) {
- return;
- }
- const {supportObjects} = methods.get(method);
- const parentCallee = callee.object.object;
- if (
- // [].{slice,splice}
- (
- parentCallee.type === 'ArrayExpression'
- && parentCallee.elements.length === 0
- )
- // ''.slice
- || (
- method === 'slice'
- && isLiteral(parentCallee, '')
- )
- // {Array,String...}.prototype.slice
- // Array.prototype.splice
- || (
- getMemberName(parentCallee) === 'prototype'
- && parentCallee.object.type === 'Identifier'
- && supportObjects.has(parentCallee.object.name)
- )
- ) {
- [target] = originalArguments;
- if (isApply) {
- const [, secondArgument] = originalArguments;
- if (!secondArgument || secondArgument.type !== 'ArrayExpression') {
- return;
- }
- argumentsNodes = secondArgument.elements;
- } else {
- argumentsNodes = originalArguments.slice(1);
- }
- return {
- method,
- target,
- argumentsNodes,
- };
- }
- }
- /** @param {import('eslint').Rule.RuleContext} context */
- const create = context => ({
- 'CallExpression[callee.type="MemberExpression"]'(node) {
- const parsed = parse(node);
- if (!parsed) {
- return;
- }
- const {
- method,
- target,
- argumentsNodes,
- } = parsed;
- const {argumentsIndexes} = methods.get(method);
- const removableNodes = argumentsIndexes
- .map(index => getNegativeIndexLengthNode(argumentsNodes[index], target))
- .filter(Boolean);
- if (removableNodes.length === 0) {
- return;
- }
- return {
- node,
- messageId: MESSAGE_ID,
- data: {method},
- * fix(fixer) {
- const sourceCode = context.getSourceCode();
- for (const node of removableNodes) {
- yield removeLengthNode(node, fixer, sourceCode);
- }
- },
- };
- },
- });
- /** @type {import('eslint').Rule.RuleModule} */
- module.exports = {
- create,
- meta: {
- type: 'suggestion',
- docs: {
- description: 'Prefer negative index over `.length - index` for `{String,Array,TypedArray}#{slice,at}()` and `Array#splice()`.',
- },
- fixable: 'code',
- messages,
- },
- };
|