Skip to content

Commit

Permalink
ESLINTJS-50 Fix "sonarjs/prefer-enum-initializers" with newer version…
Browse files Browse the repository at this point in the history
…s of typescript-eslint (#4848)
  • Loading branch information
vdiez authored Sep 25, 2024
1 parent 766714f commit dfd58e7
Show file tree
Hide file tree
Showing 10 changed files with 230 additions and 28 deletions.
25 changes: 25 additions & 0 deletions its/eslint8-plugin-sonarjs/file.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ function S2376() {
}
}

function doSomething() {
doSmth();
}
switch (x) {
case 0:
doSomething();
case 1:
doSomething();
default:
doSomethingElse();
}

/*
function S125() {
Expand All @@ -45,3 +57,16 @@ function S125() {
*/

// if (something) {}
import { useEffect, useState } from 'react';

function Form() {
const [name, setName] = useState('Mary');
if (name !== '') {
useEffect(function persistForm() {
// Noncompliant {{React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render.}}
// ^^^^^^^^^
console.log('persistForm');
});
}
return 1;
}
78 changes: 78 additions & 0 deletions its/eslint8-plugin-sonarjs/file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// S4328
import * as Bar from 'bar';
import * as CrossSpawn from 'cross-spawn';

function S3776(foo) {
if (foo.bar) {
if (foo.baz) {
if (foo.qux) {
if (foo.fred) {
if (foo.thud) {
if (foo.abc) {
foo.bcd();
}
}
}
}
}
}
}

function S2703(foo) {
if (x == 0) {
x = 42;
}
}

function S2376() {
class C {
set m(a) {
this.a = a;
}
}
}

function doSomething() {
doSmth();
}
switch (x) {
case 0:
doSomething();
case 1:
doSomething();
default:
doSomethingElse();
}

/*
function S125() {
class C {
set m(a) {
this.a = a;
}
}
}
*/

// if (something) {}
import { useEffect, useState } from 'react';

function Form() {
const [name, setName] = useState('Mary');
if (name !== '') {
useEffect(function persistForm() {
// Noncompliant {{React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render.}}
// ^^^^^^^^^
console.log('persistForm');
});
}
return 1;
}

enum LogType {
LOG,
ERROR = 2,
WARN,
}
2 changes: 1 addition & 1 deletion its/eslint8-plugin-sonarjs/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ test('should work with ECMAScript modules config', async t => {
});

test('should work with TSESLint config', async t => {
const result = spawn.sync('npx', ['eslint', '-c', 'tseslint.config.mjs', 'file.js'], {
const result = spawn.sync('npx', ['eslint', '-c', 'tseslint.config.mjs', 'file.ts'], {
cwd: __dirname,
encoding: 'utf-8',
});
Expand Down
5 changes: 5 additions & 0 deletions its/eslint8-plugin-sonarjs/tseslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@

import plugin from 'eslint-plugin-sonarjs';
import tseslint from 'typescript-eslint';
import parser from '@typescript-eslint/parser';

console.log(`Loaded ${Object.keys(plugin.configs.recommended.rules ?? {}).length} rules`);

export default tseslint.config(plugin.configs.recommended, {
rules: { 'sonarjs/accessor-pairs': 'error' },
files: ['**/*.ts'],
languageOptions: {
parser,
},
});
4 changes: 4 additions & 0 deletions its/eslint9-plugin-sonarjs/file.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
// S4328
import * as Bar from 'bar';
import * as CrossSpawn from 'cross-spawn';

function S3776(foo) {
if (foo.bar) {
if (foo.baz) {
Expand Down
78 changes: 78 additions & 0 deletions its/eslint9-plugin-sonarjs/file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// S4328
import * as Bar from 'bar';
import * as CrossSpawn from 'cross-spawn';

function S3776(foo) {
if (foo.bar) {
if (foo.baz) {
if (foo.qux) {
if (foo.fred) {
if (foo.thud) {
if (foo.abc) {
foo.bcd();
}
}
}
}
}
}
}

function S2703(foo) {
if (x == 0) {
x = 42;
}
}

function S2376() {
class C {
set m(a) {
this.a = a;
}
}
}

function doSomething() {
doSmth();
}
switch (x) {
case 0:
doSomething();
case 1:
doSomething();
default:
doSomethingElse();
}

/*
function S125() {
class C {
set m(a) {
this.a = a;
}
}
}
*/

// if (something) {}
import { useEffect, useState } from 'react';

function Form() {
const [name, setName] = useState('Mary');
if (name !== '') {
useEffect(function persistForm() {
// Noncompliant {{React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render.}}
// ^^^^^^^^^
console.log('persistForm');
});
}
return 1;
}

enum LogType {
LOG,
ERROR = 2,
WARN,
}
2 changes: 1 addition & 1 deletion its/eslint9-plugin-sonarjs/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ test('should work with ECMAScript modules config', async t => {
});

test('should work with TSESLint config', async t => {
const result = spawn.sync('npx', ['eslint', '-c', 'tseslint.config.mjs', 'file.js'], {
const result = spawn.sync('npx', ['eslint', '-c', 'tseslint.config.mjs', 'file.ts'], {
cwd: __dirname,
encoding: 'utf-8',
});
Expand Down
5 changes: 5 additions & 0 deletions its/eslint9-plugin-sonarjs/tseslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@

import plugin from 'eslint-plugin-sonarjs';
import tseslint from 'typescript-eslint';
import parser from '@typescript-eslint/parser';

console.log(`Loaded ${Object.keys(plugin.configs.recommended.rules ?? {}).length} rules`);

export default tseslint.config(plugin.configs.recommended, {
rules: { 'sonarjs/accessor-pairs': 'error' },
files: ['**/*.ts'],
languageOptions: {
parser,
},
});
5 changes: 1 addition & 4 deletions packages/jsts/src/rules/S6572/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,4 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { tsEslintRules } from '../typescript-eslint';
import { decorate } from './decorator';

export const rule = decorate(tsEslintRules['prefer-enum-initializers']);
export { rule } from './rule';
Original file line number Diff line number Diff line change
Expand Up @@ -19,39 +19,49 @@
*/
// https://sonarsource.github.io/rspec/#/rspec/S6572/javascript

import estree from 'estree';
import * as estree from 'estree';
import { Rule } from 'eslint';
import { TSESTree } from '@typescript-eslint/utils';
import { generateMeta, interceptReport, isNumberLiteral } from '../helpers';
import { generateMeta, isNumberLiteral } from '../helpers';
import { meta } from './meta';
import { tsEslintRules } from '../typescript-eslint';
const baseRuleModule = tsEslintRules['prefer-enum-initializers'];

// The core implementation of this rule reports all enums for which there is a member value that is
// not initialized explicitly. Here, the decorator's purpose is to restrict the scope of the rule only
// to enums that already initialize a subset of members and leave the remaining ones uninitialized, with
// the exception of those that enforce a numerical order by assigning a literal to the first member value.
// to enums that already initialize a subset of members and leave the remaining ones uninitialized, except
// those that enforce a numerical order by assigning a literal to the first member value.
// In other words, the decorated rule ignores enums that don't initialize any member value or those
// that initialize their first member with a number literal.
export function decorate(rule: Rule.RuleModule): Rule.RuleModule {
return interceptReport(
{
...rule,
meta: generateMeta(meta as Rule.RuleMetaData, {
...rule.meta!,
hasSuggestions: true,
}),
},
(context, descriptor) => {
const enumMember = (descriptor as any).node as TSESTree.TSEnumMember;
const enumDecl = enumMember.parent as TSESTree.TSEnumDeclaration;
if (anyInitialized(enumDecl) && !numericalOrder(enumDecl)) {
context.report(descriptor);
}
},
);
export const rule: Rule.RuleModule = {
meta: generateMeta(meta as Rule.RuleMetaData, {
...baseRuleModule.meta!,
hasSuggestions: true,
}),
create(context) {
const baseRuleListener = baseRuleModule.create(context) as unknown as {
TSEnumDeclaration: (node: Rule.Node) => void;
};
return {
TSEnumDeclaration: (node: Rule.Node) => {
const enumDecl = node as unknown as TSESTree.TSEnumDeclaration;
if (anyInitialized(enumDecl) && !numericalOrder(enumDecl)) {
baseRuleListener.TSEnumDeclaration(node);
}
},
};
},
};

function isEnumWithBody(
enumDecl: TSESTree.TSEnumDeclaration,
): enumDecl is TSESTree.TSEnumDeclaration & { body: { members: TSESTree.TSEnumMember[] } } {
return (enumDecl as any).body;
}

function anyInitialized(enumDecl: TSESTree.TSEnumDeclaration) {
return enumDecl.members.some(m => m.initializer !== undefined);
const members = isEnumWithBody(enumDecl) ? enumDecl.body.members : enumDecl.members;
return members.some(m => m.initializer !== undefined);
}

function numericalOrder(enumDecl: TSESTree.TSEnumDeclaration) {
Expand Down

0 comments on commit dfd58e7

Please sign in to comment.