123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199 |
- "use strict";
- /*
- * eslint-plugin-sonarjs
- * Copyright (C) 2018-2021 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
- // https://sonarsource.github.io/rspec/#/rspec/S4158
- const utils_1 = require("../utils");
- const docs_url_1 = require("../utils/docs-url");
- // Methods that mutate the collection but can't add elements
- const nonAdditiveMutatorMethods = [
- // array methods
- 'copyWithin',
- 'pop',
- 'reverse',
- 'shift',
- 'sort',
- // map, set methods
- 'clear',
- 'delete',
- ];
- const accessorMethods = [
- // array methods
- 'concat',
- 'flat',
- 'flatMap',
- 'includes',
- 'indexOf',
- 'join',
- 'lastIndexOf',
- 'slice',
- 'toSource',
- 'toString',
- 'toLocaleString',
- // map, set methods
- 'get',
- 'has',
- ];
- const iterationMethods = [
- 'entries',
- 'every',
- 'filter',
- 'find',
- 'findIndex',
- 'forEach',
- 'keys',
- 'map',
- 'reduce',
- 'reduceRight',
- 'some',
- 'values',
- ];
- const strictlyReadingMethods = new Set([
- ...nonAdditiveMutatorMethods,
- ...accessorMethods,
- ...iterationMethods,
- ]);
- const rule = {
- meta: {
- messages: {
- reviewUsageOfIdentifier: 'Review this usage of "{{identifierName}}" as it can only be empty here.',
- },
- schema: [],
- type: 'problem',
- docs: {
- description: 'Empty collections should not be accessed or iterated',
- recommended: 'error',
- url: (0, docs_url_1.default)(__filename),
- },
- },
- create(context) {
- return {
- 'Program:exit': () => {
- reportEmptyCollectionsUsage(context.getScope(), context);
- },
- };
- },
- };
- function reportEmptyCollectionsUsage(scope, context) {
- if (scope.type !== 'global') {
- scope.variables.forEach(v => {
- reportEmptyCollectionUsage(v, context);
- });
- }
- scope.childScopes.forEach(childScope => {
- reportEmptyCollectionsUsage(childScope, context);
- });
- }
- function reportEmptyCollectionUsage(variable, context) {
- if (variable.references.length <= 1) {
- return;
- }
- if (variable.defs.some(d => d.type === 'Parameter' || d.type === 'ImportBinding')) {
- // Bound value initialized elsewhere, could be non-empty.
- return;
- }
- const readingUsages = [];
- let hasAssignmentOfEmptyCollection = false;
- for (const ref of variable.references) {
- if (ref.isWriteOnly()) {
- if (isReferenceAssigningEmptyCollection(ref)) {
- hasAssignmentOfEmptyCollection = true;
- }
- else {
- // There is at least one operation that might make the collection non-empty.
- // We ignore the order of usages, and consider all reads to be safe.
- return;
- }
- }
- else if (isReadingCollectionUsage(ref)) {
- readingUsages.push(ref);
- }
- else {
- // some unknown operation on the collection.
- // To avoid any FPs, we assume that it could make the collection non-empty.
- return;
- }
- }
- if (hasAssignmentOfEmptyCollection) {
- readingUsages.forEach(ref => {
- context.report({
- messageId: 'reviewUsageOfIdentifier',
- data: {
- identifierName: ref.identifier.name,
- },
- node: ref.identifier,
- });
- });
- }
- }
- function isReferenceAssigningEmptyCollection(ref) {
- const declOrExprStmt = (0, utils_1.findFirstMatchingAncestor)(ref.identifier, n => n.type === 'VariableDeclarator' || n.type === 'ExpressionStatement');
- if (declOrExprStmt) {
- if (declOrExprStmt.type === 'VariableDeclarator' && declOrExprStmt.init) {
- return isEmptyCollectionType(declOrExprStmt.init);
- }
- if (declOrExprStmt.type === 'ExpressionStatement') {
- const { expression } = declOrExprStmt;
- return (expression.type === 'AssignmentExpression' &&
- (0, utils_1.isReferenceTo)(ref, expression.left) &&
- isEmptyCollectionType(expression.right));
- }
- }
- return false;
- }
- function isEmptyCollectionType(node) {
- if (node && node.type === 'ArrayExpression') {
- return node.elements.length === 0;
- }
- else if (node && (node.type === 'CallExpression' || node.type === 'NewExpression')) {
- return (0, utils_1.isIdentifier)(node.callee, ...utils_1.collectionConstructor) && node.arguments.length === 0;
- }
- return false;
- }
- function isReadingCollectionUsage(ref) {
- return isStrictlyReadingMethodCall(ref) || isForIterationPattern(ref) || isElementRead(ref);
- }
- function isStrictlyReadingMethodCall(usage) {
- const { parent } = usage.identifier;
- if (parent && parent.type === 'MemberExpression') {
- const memberExpressionParent = parent.parent;
- if (memberExpressionParent && memberExpressionParent.type === 'CallExpression') {
- return (0, utils_1.isIdentifier)(parent.property, ...strictlyReadingMethods);
- }
- }
- return false;
- }
- function isForIterationPattern(ref) {
- const forInOrOfStatement = (0, utils_1.findFirstMatchingAncestor)(ref.identifier, n => n.type === 'ForOfStatement' || n.type === 'ForInStatement');
- return forInOrOfStatement && forInOrOfStatement.right === ref.identifier;
- }
- function isElementRead(ref) {
- const { parent } = ref.identifier;
- return parent && parent.type === 'MemberExpression' && parent.computed && !isElementWrite(parent);
- }
- function isElementWrite(memberExpression) {
- const ancestors = (0, utils_1.ancestorsChain)(memberExpression, new Set());
- const assignment = ancestors.find(n => n.type === 'AssignmentExpression');
- if (assignment && assignment.operator === '=') {
- return [memberExpression, ...ancestors].includes(assignment.left);
- }
- return false;
- }
- module.exports = rule;
- //# sourceMappingURL=no-empty-collection.js.map
|