eslint-plugin-sonarjs
Version:
SonarJS rules for ESLint
149 lines (148 loc) • 6.05 kB
JavaScript
/*
* SonarQube JavaScript Plugin
* Copyright (C) 2011-2025 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 Sonar Source-Available License Version 1, as published by SonarSource SA.
*
* 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 Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
// https://sonarsource.github.io/rspec/#/rspec/S6353/javascript
Object.defineProperty(exports, "__esModule", { value: true });
exports.rule = void 0;
const index_js_1 = require("../helpers/index.js");
const meta_js_1 = require("./meta.js");
const rule_template_js_1 = require("../helpers/regex/rule-template.js");
exports.rule = (0, rule_template_js_1.createRegExpRule)(context => {
let flags;
return {
onRegExpLiteralEnter: (node) => {
({ flags } = node);
},
onCharacterClassEnter: (node) => {
checkBulkyAnyCharacterClass(node, flags, context);
checkBulkyNumericCharacterClass(node, context);
checkBulkyAlphaNumericCharacterClass(node, context);
},
onQuantifierEnter: (node) => {
checkBulkyQuantifier(node, context);
},
};
}, (0, index_js_1.generateMeta)(meta_js_1.meta));
function checkBulkyAnyCharacterClass(node, flags, context) {
if (node.negate || node.elements.length !== 2) {
return;
}
let hasLowerEscapeW = false;
let hasUpperEscapeW = false;
let hasLowerEscapeD = false;
let hasUpperEscapeD = false;
let hasLowerEscapeS = false;
let hasUpperEscapeS = false;
node.elements.forEach(element => {
hasLowerEscapeW ||=
element.type === 'CharacterSet' && element.kind === 'word' && !element.negate;
hasUpperEscapeW ||=
element.type === 'CharacterSet' && element.kind === 'word' && element.negate;
hasLowerEscapeD ||=
element.type === 'CharacterSet' && element.kind === 'digit' && !element.negate;
hasUpperEscapeD ||=
element.type === 'CharacterSet' && element.kind === 'digit' && element.negate;
hasLowerEscapeS ||=
element.type === 'CharacterSet' && element.kind === 'space' && !element.negate;
hasUpperEscapeS ||=
element.type === 'CharacterSet' && element.kind === 'space' && element.negate;
});
const isBulkyAnyCharacterClass = (hasLowerEscapeW && hasUpperEscapeW) ||
(hasLowerEscapeD && hasUpperEscapeD) ||
(hasLowerEscapeS && hasUpperEscapeS && flags.dotAll);
if (isBulkyAnyCharacterClass) {
context.reportRegExpNode({
message: `Use concise character class syntax '.' instead of '${node.raw}'.`,
node: context.node,
regexpNode: node,
});
}
}
function checkBulkyNumericCharacterClass(node, context) {
if (node.elements.length === 1) {
const [element] = node.elements;
const hasDigit = element.type === 'CharacterClassRange' && element.raw === '0-9';
if (hasDigit) {
const expected = node.negate ? '\\D' : '\\d';
const actual = node.raw;
context.reportRegExpNode({
message: `Use concise character class syntax '${expected}' instead of '${actual}'.`,
node: context.node,
regexpNode: node,
});
}
}
}
function checkBulkyAlphaNumericCharacterClass(node, context) {
if (node.elements.length === 4) {
let hasDigit = false, hasLowerCase = false, hasUpperCase = false, hasUnderscore = false;
for (const element of node.elements) {
hasDigit ||= element.type === 'CharacterClassRange' && element.raw === '0-9';
hasLowerCase ||= element.type === 'CharacterClassRange' && element.raw === 'a-z';
hasUpperCase ||= element.type === 'CharacterClassRange' && element.raw === 'A-Z';
hasUnderscore ||= element.type === 'Character' && element.raw === '_';
}
if (hasDigit && hasLowerCase && hasUpperCase && hasUnderscore) {
const expected = node.negate ? '\\W' : '\\w';
const actual = node.raw;
context.reportRegExpNode({
message: `Use concise character class syntax '${expected}' instead of '${actual}'.`,
node: context.node,
regexpNode: node,
});
}
}
}
function checkBulkyQuantifier(node, context) {
const { raw } = node;
let message;
let bulkyQuantifier;
if (/\{0,1\}\??$/.test(raw)) {
bulkyQuantifier = { concise: '?', verbose: '{0,1}' };
}
else if (/\{0,0\}\??$/.test(raw)) {
message = `Remove redundant ${node.element.raw}{0,0}.`;
}
else if (/\{0\}\??$/.test(raw)) {
message = `Remove redundant ${node.element.raw}{0}.`;
}
else if (/\{1,1\}\??$/.test(raw)) {
message = 'Remove redundant quantifier {1,1}.';
}
else if (/\{1\}\??$/.test(raw)) {
message = 'Remove redundant quantifier {1}.';
}
else if (/\{0,\}\??$/.test(raw)) {
bulkyQuantifier = { concise: '*', verbose: '{0,}' };
}
else if (/\{1,\}\??$/.test(raw)) {
bulkyQuantifier = { concise: '+', verbose: '{1,}' };
}
else if (/\{(\d+),\1\}\??$/.test(raw)) {
bulkyQuantifier = { concise: `{${node.min}}`, verbose: `{${node.min},${node.min}}` };
}
if (bulkyQuantifier) {
message = `Use concise quantifier syntax '${bulkyQuantifier.concise}' instead of '${bulkyQuantifier.verbose}'.`;
}
if (message) {
context.reportRegExpNode({
message,
node: context.node,
regexpNode: node,
});
}
}
;