Skip to content

Commit

Permalink
Add isMemberExpression to replace selectors (#2093)
Browse files Browse the repository at this point in the history
  • Loading branch information
fisker committed May 12, 2023
1 parent c2fc87f commit 2fa9941
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 22 deletions.
6 changes: 3 additions & 3 deletions rules/ast/call-or-new-expression.js
Expand Up @@ -11,7 +11,7 @@
allowSpreadElement?: boolean,
optional?: boolean,
} | string | string[]
} CallOrNewExpressionOptions
} CallOrNewExpressionCheckOptions
*/
function create(node, options, type) {
if (node?.type !== type) {
Expand Down Expand Up @@ -97,13 +97,13 @@ function create(node, options, type) {
}

/**
@param {CallOrNewExpressionOptions} [options]
@param {CallOrNewExpressionCheckOptions} [options]
@returns {boolean}
*/
const isCallExpression = (node, options) => create(node, options, 'CallExpression');

/**
@param {CallOrNewExpressionOptions} [options]
@param {CallOrNewExpressionCheckOptions} [options]
@returns {boolean}
*/
const isNewExpression = (node, options) => {
Expand Down
1 change: 1 addition & 0 deletions rules/ast/index.js
Expand Up @@ -23,4 +23,5 @@ module.exports = {
isUndefined: require('./is-undefined.js'),
isNewExpression: require('./call-or-new-expression.js').isNewExpression,
isCallExpression: require('./call-or-new-expression.js').isCallExpression,
isMemberExpression: require('./is-member-expression.js'),
};
94 changes: 94 additions & 0 deletions rules/ast/is-member-expression.js
@@ -0,0 +1,94 @@
'use strict';

/**
@param {
{
property?: string,
properties?: string[],
object?: string,
objects?: string[],
optional?: boolean,
computed?: boolean
} | string | string[]
} [options]
@returns {string}
*/
function isMemberExpression(node, options) {
if (node?.type !== 'MemberExpression') {
return false;
}

if (typeof options === 'string') {
options = {properties: [options]};
}

if (Array.isArray(options)) {
options = {properties: options};
}

let {
property,
properties,
object,
objects,
optional,
computed,
} = {
path: '',
property: '',
properties: [],
object: '',
...options,
};

if (property) {
properties = [property];
}

if (object) {
objects = [object];
}

if (
(computed === true && (node.computed !== computed))
|| (
computed === false
// `node.computed` can be `undefined` in some parsers
&& node.computed
)
|| (optional === true && (node.optional !== optional))
|| (
optional === false
// `node.optional` can be `undefined` in some parsers
&& node.optional
)
) {
return false;
}

if (
Array.isArray(properties)
&& properties.length > 0
&& (
node.property.type !== 'Identifier'
|| !properties.includes(node.property.name)
)
) {
return false;
}

if (
Array.isArray(objects)
&& objects.length > 0
&& (
node.object.type !== 'Identifier'
|| !objects.includes(node.object.name)
)
) {
return false;
}

return true;
}

module.exports = isMemberExpression;
17 changes: 11 additions & 6 deletions rules/explicit-length-check.js
Expand Up @@ -3,9 +3,8 @@ const {isParenthesized, getStaticValue} = require('@eslint-community/eslint-util
const {checkVueTemplate} = require('./utils/rule.js');
const isLogicalExpression = require('./utils/is-logical-expression.js');
const {isBooleanNode, getBooleanAncestor} = require('./utils/boolean.js');
const {memberExpressionSelector} = require('./selectors/index.js');
const {fixSpaceAroundKeyword} = require('./fix/index.js');
const {isLiteral} = require('./ast/index.js');
const {isLiteral, isMemberExpression} = require('./ast/index.js');

const TYPE_NON_ZERO = 'non-zero';
const TYPE_ZERO = 'zero';
Expand Down Expand Up @@ -45,8 +44,6 @@ const zeroStyle = {
test: node => isCompareRight(node, '===', 0),
};

const lengthSelector = memberExpressionSelector(['length', 'size']);

function getLengthCheckNode(node) {
node = node.parent;

Expand Down Expand Up @@ -142,11 +139,19 @@ function create(context) {
}

return {
[lengthSelector](lengthNode) {
if (lengthNode.object.type === 'ThisExpression') {
MemberExpression(memberExpression) {
if (
!isMemberExpression(memberExpression, {
properties: ['length', 'size'],
optional: false,
computed: false,
})
|| memberExpression.object.type === 'ThisExpression'
) {
return;
}

const lengthNode = memberExpression;
const staticValue = getStaticValue(lengthNode, sourceCode.getScope(lengthNode));
if (staticValue && (!Number.isInteger(staticValue.value) || staticValue.value < 0)) {
// Ignore known, non-positive-integer length properties.
Expand Down
16 changes: 13 additions & 3 deletions rules/prefer-dom-node-text-content.js
@@ -1,5 +1,5 @@
'use strict';
const {memberExpressionSelector} = require('./selectors/index.js');
const {isMemberExpression} = require('./ast/index.js');

const ERROR = 'error';
const SUGGESTION = 'suggestion';
Expand All @@ -8,7 +8,6 @@ const messages = {
[SUGGESTION]: 'Switch to `.textContent`.',
};

const memberExpressionPropertySelector = `${memberExpressionSelector({property: 'innerText', includeOptional: true})} > .property`;
const destructuringSelector = [
'ObjectPattern',
' > ',
Expand All @@ -22,7 +21,18 @@ const destructuringSelector = [

/** @param {import('eslint').Rule.RuleContext} context */
const create = () => ({
[memberExpressionPropertySelector](node) {
MemberExpression(memberExpression) {
if (
!isMemberExpression(memberExpression, {
property: 'innerText',
computed: false,
})
) {
return;
}

const node = memberExpression.property;

return {
node,
messageId: ERROR,
Expand Down
25 changes: 15 additions & 10 deletions rules/prefer-set-size.js
@@ -1,21 +1,13 @@
'use strict';
const {findVariable} = require('@eslint-community/eslint-utils');
const {memberExpressionSelector} = require('./selectors/index.js');
const {fixSpaceAroundKeyword} = require('./fix/index.js');
const {isNewExpression} = require('./ast/index.js');
const {isNewExpression, isMemberExpression} = require('./ast/index.js');

const MESSAGE_ID = 'prefer-set-size';
const messages = {
[MESSAGE_ID]: 'Prefer using `Set#size` instead of `Array#length`.',
};

const lengthAccessSelector = [
memberExpressionSelector('length'),
'[object.type="ArrayExpression"]',
'[object.elements.length=1]',
'[object.elements.0.type="SpreadElement"]',
].join('');

const isNewSet = node => isNewExpression(node, {name: 'Set'});

function isSet(node, scope) {
Expand Down Expand Up @@ -70,7 +62,20 @@ const create = context => {
const {sourceCode} = context;

return {
[lengthAccessSelector](node) {
MemberExpression(node) {
if (
!isMemberExpression(node, {
property: 'length',
optional: false,
computed: false,
})
|| node.object.type !== 'ArrayExpression'
|| node.object.elements.length !== 1
|| node.object.elements[0].type !== 'SpreadElement'
) {
return;
}

const maybeSet = node.object.elements[0].argument;
if (!isSet(maybeSet, sourceCode.getScope(maybeSet))) {
return;
Expand Down

0 comments on commit 2fa9941

Please sign in to comment.