123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210 |
- 'use strict';
- const {isParenthesized, getStaticValue} = require('@eslint-community/eslint-utils');
- 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 TYPE_NON_ZERO = 'non-zero';
- const TYPE_ZERO = 'zero';
- const MESSAGE_ID_SUGGESTION = 'suggestion';
- const messages = {
- [TYPE_NON_ZERO]: 'Use `.{{property}} {{code}}` when checking {{property}} is not zero.',
- [TYPE_ZERO]: 'Use `.{{property}} {{code}}` when checking {{property}} is zero.',
- [MESSAGE_ID_SUGGESTION]: 'Replace `.{{property}}` with `.{{property}} {{code}}`.',
- };
- const isCompareRight = (node, operator, value) =>
- node.type === 'BinaryExpression'
- && node.operator === operator
- && isLiteral(node.right, value);
- const isCompareLeft = (node, operator, value) =>
- node.type === 'BinaryExpression'
- && node.operator === operator
- && isLiteral(node.left, value);
- const nonZeroStyles = new Map([
- [
- 'greater-than',
- {
- code: '> 0',
- test: node => isCompareRight(node, '>', 0),
- },
- ],
- [
- 'not-equal',
- {
- code: '!== 0',
- test: node => isCompareRight(node, '!==', 0),
- },
- ],
- ]);
- const zeroStyle = {
- code: '=== 0',
- test: node => isCompareRight(node, '===', 0),
- };
- const lengthSelector = memberExpressionSelector(['length', 'size']);
- function getLengthCheckNode(node) {
- node = node.parent;
- // Zero length check
- if (
- // `foo.length === 0`
- isCompareRight(node, '===', 0)
- // `foo.length == 0`
- || isCompareRight(node, '==', 0)
- // `foo.length < 1`
- || isCompareRight(node, '<', 1)
- // `0 === foo.length`
- || isCompareLeft(node, '===', 0)
- // `0 == foo.length`
- || isCompareLeft(node, '==', 0)
- // `1 > foo.length`
- || isCompareLeft(node, '>', 1)
- ) {
- return {isZeroLengthCheck: true, node};
- }
- // Non-Zero length check
- if (
- // `foo.length !== 0`
- isCompareRight(node, '!==', 0)
- // `foo.length != 0`
- || isCompareRight(node, '!=', 0)
- // `foo.length > 0`
- || isCompareRight(node, '>', 0)
- // `foo.length >= 1`
- || isCompareRight(node, '>=', 1)
- // `0 !== foo.length`
- || isCompareLeft(node, '!==', 0)
- // `0 !== foo.length`
- || isCompareLeft(node, '!=', 0)
- // `0 < foo.length`
- || isCompareLeft(node, '<', 0)
- // `1 <= foo.length`
- || isCompareLeft(node, '<=', 1)
- ) {
- return {isZeroLengthCheck: false, node};
- }
- return {};
- }
- function create(context) {
- const options = {
- 'non-zero': 'greater-than',
- ...context.options[0],
- };
- const nonZeroStyle = nonZeroStyles.get(options['non-zero']);
- const sourceCode = context.getSourceCode();
- function getProblem({node, isZeroLengthCheck, lengthNode, autoFix}) {
- const {code, test} = isZeroLengthCheck ? zeroStyle : nonZeroStyle;
- if (test(node)) {
- return;
- }
- let fixed = `${sourceCode.getText(lengthNode)} ${code}`;
- if (
- !isParenthesized(node, sourceCode)
- && node.type === 'UnaryExpression'
- && (node.parent.type === 'UnaryExpression' || node.parent.type === 'AwaitExpression')
- ) {
- fixed = `(${fixed})`;
- }
- const fix = function * (fixer) {
- yield fixer.replaceText(node, fixed);
- yield * fixSpaceAroundKeyword(fixer, node, sourceCode);
- };
- const problem = {
- node,
- messageId: isZeroLengthCheck ? TYPE_ZERO : TYPE_NON_ZERO,
- data: {code, property: lengthNode.property.name},
- };
- if (autoFix) {
- problem.fix = fix;
- } else {
- problem.suggest = [
- {
- messageId: MESSAGE_ID_SUGGESTION,
- fix,
- },
- ];
- }
- return problem;
- }
- return {
- [lengthSelector](lengthNode) {
- if (lengthNode.object.type === 'ThisExpression') {
- return;
- }
- const staticValue = getStaticValue(lengthNode, context.getScope());
- if (staticValue && (!Number.isInteger(staticValue.value) || staticValue.value < 0)) {
- // Ignore known, non-positive-integer length properties.
- return;
- }
- let node;
- let autoFix = true;
- let {isZeroLengthCheck, node: lengthCheckNode} = getLengthCheckNode(lengthNode);
- if (lengthCheckNode) {
- const {isNegative, node: ancestor} = getBooleanAncestor(lengthCheckNode);
- node = ancestor;
- if (isNegative) {
- isZeroLengthCheck = !isZeroLengthCheck;
- }
- } else {
- const {isNegative, node: ancestor} = getBooleanAncestor(lengthNode);
- if (isBooleanNode(ancestor)) {
- isZeroLengthCheck = isNegative;
- node = ancestor;
- } else if (isLogicalExpression(lengthNode.parent)) {
- isZeroLengthCheck = isNegative;
- node = lengthNode;
- autoFix = false;
- }
- }
- if (node) {
- return getProblem({node, isZeroLengthCheck, lengthNode, autoFix});
- }
- },
- };
- }
- const schema = [
- {
- type: 'object',
- additionalProperties: false,
- properties: {
- 'non-zero': {
- enum: [...nonZeroStyles.keys()],
- default: 'greater-than',
- },
- },
- },
- ];
- /** @type {import('eslint').Rule.RuleModule} */
- module.exports = {
- create: checkVueTemplate(create),
- meta: {
- type: 'problem',
- docs: {
- description: 'Enforce explicitly comparing the `length` or `size` property of a value.',
- },
- fixable: 'code',
- schema,
- messages,
- hasSuggestions: true,
- },
- };
|