Skip to content

Commit

Permalink
Fix FP S6606 (prefer-nullish-coalescing): When the first argument i…
Browse files Browse the repository at this point in the history
…s boolean | undefined

Also when first argument is `string | undefined` and `number | undefined`
  • Loading branch information
ericmorand-sonarsource committed Feb 12, 2024
1 parent a8d3908 commit a0017dc
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/jsts/src/rules/S6606/fixtures/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// intentionally left empty
5 changes: 5 additions & 0 deletions packages/jsts/src/rules/S6606/fixtures/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"compilerOptions": {
"strictNullChecks": true
}
}
20 changes: 20 additions & 0 deletions packages/jsts/src/rules/S6606/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* SonarQube JavaScript Plugin
* Copyright (C) 2011-2024 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.
*/
export * from './rule';
55 changes: 55 additions & 0 deletions packages/jsts/src/rules/S6606/rule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* SonarQube JavaScript Plugin
* Copyright (C) 2011-2024 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.
*/
import { tsEslintRules } from '../typescript-eslint';
import { type Rule } from 'eslint';
import {
getTypeFromTreeNode,
interceptReport,
isNullOrUndefinedType,
isObjectType,
isPrimitiveType,
} from '../helpers';
import { LogicalExpression } from 'estree';

const preferNullishCoalescingRule = tsEslintRules['prefer-nullish-coalescing'];

export const rule = interceptReport(preferNullishCoalescingRule, (context, reportDescriptor) => {
const { node: token, messageId } = reportDescriptor as Rule.ReportDescriptor & {
node: Rule.Node;
messageId: string;
};

if (messageId === 'preferNullishOverOr') {
const services = context.sourceCode.parserServices;
const node = context.sourceCode.getNodeByRangeIndex(token.range![0]) as LogicalExpression;
const leftOperand = node.left;
const leftOperandType = getTypeFromTreeNode(leftOperand, services);

if (
leftOperandType.isUnion() &&
leftOperandType.types.some(isNullOrUndefinedType) &&
(leftOperandType.types.some(isPrimitiveType) || leftOperandType.types.some(isObjectType))
) {
return;
}
}

context.report(reportDescriptor);
});
102 changes: 102 additions & 0 deletions packages/jsts/src/rules/S6606/unit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* SonarQube JavaScript Plugin
* Copyright (C) 2011-2024 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.
*/
import { rule } from './rule';
import { RuleTester } from 'eslint';
import path from 'path';

const ruleTester = new RuleTester({
parser: require.resolve('@typescript-eslint/parser'),
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
project: `./tsconfig.json`,
tsconfigRootDir: path.join(__dirname, 'fixtures'),
},
});

ruleTester.run('S6606', rule, {
valid: [
{
code: `
function foo(value: string) {
return value || 'default';
}
`,
filename: path.join(__dirname, 'fixtures/index.ts'),
},
{
code: `
function foo(value: string | number) {
return value || 'default';
}
`,
filename: path.join(__dirname, 'fixtures/index.ts'),
},
{
code: `
function foo(value: string | null) {
return value || 'default';
}
`,
filename: path.join(__dirname, 'fixtures/index.ts'),
},
{
code: `
function foo(value: number | null) {
return value || 'default';
}
`,
filename: path.join(__dirname, 'fixtures/index.ts'),
},
{
code: `
function foo(value: bigint | null) {
return value || 'default';
}
`,
filename: path.join(__dirname, 'fixtures/index.ts'),
},
{
code: `
function foo(value: boolean | null) {
return value || 'default';
}
`,
filename: path.join(__dirname, 'fixtures/index.ts'),
},
{
code: `
function foo(value: { baz: number } | null) {
return value || 'default';
}
`,
filename: path.join(__dirname, 'fixtures/index.ts'),
},
{
code: `
function foo(value: Date | null) {
return value || 'default';
}
`,
filename: path.join(__dirname, 'fixtures/index.ts'),
},
],
invalid: [],
});
18 changes: 18 additions & 0 deletions packages/jsts/src/rules/helpers/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,3 +291,21 @@ export function isBooleanLiteralType(type: ts.Type): type is ts.Type & {
export function isBooleanTrueType(type: ts.Type) {
return isBooleanLiteralType(type) && type.intrinsicName === 'true';
}

export function isPrimitiveType({ flags }: ts.Type) {
return (
flags & ts.TypeFlags.BooleanLike ||
flags & ts.TypeFlags.BigIntLike ||
flags & ts.TypeFlags.NumberLike ||
flags & ts.TypeFlags.StringLike ||
flags & ts.TypeFlags.EnumLike
);
}

export function isNullOrUndefinedType({ flags }: ts.Type) {
return flags & ts.TypeFlags.Null || flags & ts.TypeFlags.Undefined;
}

export function isObjectType({ flags }: ts.Type) {
return flags & ts.TypeFlags.Object;
}

0 comments on commit a0017dc

Please sign in to comment.