Skip to content

Commit

Permalink
feat: added support for MemberExpressions for ignoreTernaryTests option
Browse files Browse the repository at this point in the history
  • Loading branch information
jguddas committed May 19, 2022
1 parent 6484cc0 commit d21395f
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 39 deletions.
107 changes: 88 additions & 19 deletions packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts
Expand Up @@ -87,11 +87,12 @@ export default util.createRule<Options, MessageIds>({
return;
}

let identifier: TSESTree.Identifier;
let identifier: TSESTree.Identifier | TSESTree.MemberExpression;
let alternate: TSESTree.Expression;
let requiredOperator: '!==' | '===';
if (
node.consequent.type === AST_NODE_TYPES.Identifier &&
(node.consequent.type === AST_NODE_TYPES.Identifier ||
node.consequent.type === AST_NODE_TYPES.MemberExpression) &&
((node.test.type === AST_NODE_TYPES.BinaryExpression &&
(node.test.operator === '!==' || node.test.operator === '!=')) ||
(node.test.type === AST_NODE_TYPES.LogicalExpression &&
Expand All @@ -102,7 +103,10 @@ export default util.createRule<Options, MessageIds>({
identifier = node.consequent;
alternate = node.alternate;
requiredOperator = '!==';
} else if (node.alternate.type === AST_NODE_TYPES.Identifier) {
} else if (
node.alternate.type === AST_NODE_TYPES.Identifier ||
node.alternate.type === AST_NODE_TYPES.MemberExpression
) {
identifier = node.alternate;
alternate = node.consequent;
requiredOperator = '===';
Expand Down Expand Up @@ -138,7 +142,10 @@ export default util.createRule<Options, MessageIds>({
fix(fixer: TSESLint.RuleFixer): TSESLint.RuleFix {
return fixer.replaceText(
node,
`${identifier.name} ?? ${sourceCode.text.slice(
`${sourceCode.text.slice(
identifier.range[0],
identifier.range[1],
)} ?? ${sourceCode.text.slice(
alternate.range[0],
alternate.range[1],
)}`,
Expand Down Expand Up @@ -282,7 +289,7 @@ function isFixableExplicitTernary({
node,
}: {
requiredOperator: '!==' | '===';
identifier: TSESTree.Identifier;
identifier: TSESTree.Identifier | TSESTree.MemberExpression;
node: TSESTree.ConditionalExpression;
}): boolean {
if (node.test.type !== AST_NODE_TYPES.LogicalExpression) {
Expand All @@ -306,10 +313,7 @@ function isFixableExplicitTernary({
return false;
}

const isIdentifier = (
i: TSESTree.Expression | TSESTree.PrivateIdentifier,
): boolean =>
i.type === AST_NODE_TYPES.Identifier && i.name === identifier.name;
const isIdentifier = isEqualIndentierCurry(identifier);

const hasUndefinedCheck =
(isIdentifier(left.left) && isUndefined(left.right)) ||
Expand Down Expand Up @@ -340,7 +344,7 @@ function isFixableLooseTernary({
requiredOperator,
}: {
requiredOperator: '!==' | '===';
identifier: TSESTree.Identifier;
identifier: TSESTree.Identifier | TSESTree.MemberExpression;
node: TSESTree.ConditionalExpression;
}): boolean {
if (node.test.type !== AST_NODE_TYPES.BinaryExpression) {
Expand All @@ -355,10 +359,7 @@ function isFixableLooseTernary({
return false;
}

const isIdentifier = (
i: TSESTree.Expression | TSESTree.PrivateIdentifier,
): boolean =>
i.type === AST_NODE_TYPES.Identifier && i.name === identifier.name;
const isIdentifier = isEqualIndentierCurry(identifier);

if (isIdentifier(right) && (isNull(left) || isUndefined(left))) {
return true;
Expand Down Expand Up @@ -387,7 +388,7 @@ function isFixableImplicitTernary({
parserServices: ReturnType<typeof util.getParserServices>;
checker: ts.TypeChecker;
requiredOperator: '!==' | '===';
identifier: TSESTree.Identifier;
identifier: TSESTree.Identifier | TSESTree.MemberExpression;
node: TSESTree.ConditionalExpression;
}): boolean {
if (node.test.type !== AST_NODE_TYPES.BinaryExpression) {
Expand All @@ -397,10 +398,7 @@ function isFixableImplicitTernary({
if (operator !== requiredOperator) {
return false;
}
const isIdentifier = (
i: TSESTree.Expression | TSESTree.PrivateIdentifier,
): boolean =>
i.type === AST_NODE_TYPES.Identifier && i.name === identifier.name;
const isIdentifier = isEqualIndentierCurry(identifier);

const i = isIdentifier(left) ? left : isIdentifier(right) ? right : null;
if (!i) {
Expand Down Expand Up @@ -436,6 +434,77 @@ function isFixableImplicitTernary({
return false;
}

function isEqualIndentierCurry(
a: TSESTree.Identifier | TSESTree.MemberExpression,
) {
if (a.type === AST_NODE_TYPES.Identifier) {
return function (b: any): boolean {
if (a.type !== b.type) {
return false;
}
return !!a.name && !!b.name && a.name === b.name;
};
}
return function (b: any): boolean {
if (a.type !== b.type) {
return false;
}
return isEqualMemberExpression(a, b);
};
}
function isEqualMemberExpression(
a: TSESTree.MemberExpression,
b: TSESTree.MemberExpression,
): boolean {
return (
isEqualMemberExpressionProperty(a.property, b.property) &&
isEqualMemberExpressionObject(a.object, b.object)
);
}

function isEqualMemberExpressionProperty(
a: TSESTree.MemberExpression['property'],
b: TSESTree.MemberExpression['property'],
): boolean {
if (a.type !== b.type) {
return false;
}
if (a.type === AST_NODE_TYPES.ThisExpression) {
return true;
}
if (
a.type === AST_NODE_TYPES.Literal ||
a.type === AST_NODE_TYPES.Identifier
) {
return (
// @ts-ignore
(!!a.name && !!b.name && a.name === b.name) ||
// @ts-ignore
(!!a.value && !!b.value && a.value === b.value)
);
}
if (a.type === AST_NODE_TYPES.MemberExpression) {
return isEqualMemberExpression(a, b as typeof a);
}
return false;
}

function isEqualMemberExpressionObject(a: any, b: any): boolean {
if (a.type !== b.type) {
return false;
}
if (a.type === AST_NODE_TYPES.ThisExpression) {
return true;
}
if (a.type === AST_NODE_TYPES.Identifier) {
return a.name === b.name;
}
if (a.type === AST_NODE_TYPES.MemberExpression) {
return isEqualMemberExpression(a, b);
}
return false;
}

function isUndefined(
i: TSESTree.Expression | TSESTree.PrivateIdentifier,
): boolean {
Expand Down
Expand Up @@ -244,26 +244,49 @@ x ?? 'foo';
'x != null ? x : y;',
'x == undefined ? y : x;',
'x == null ? y : x;',
].map(code => ({
code,
output: null,
options: [{ ignoreTernaryTests: false }] as const,
errors: [
{
messageId: 'preferNullishOverTernary' as const,
line: 1,
column: 1,
endLine: 1,
endColumn: code.length,
suggestions: [
{
messageId: 'suggestNullish' as const,
output: 'x ?? y;',
},
],
},
],
})),
].flatMap(code => [
{
code,
output: null,
options: [{ ignoreTernaryTests: false }] as const,
errors: [
{
messageId: 'preferNullishOverTernary' as const,
line: 1,
column: 1,
endLine: 1,
endColumn: code.length,
suggestions: [
{
messageId: 'suggestNullish' as const,
output: 'x ?? y;',
},
],
},
],
},
{
code: code.replace(/x/g, 'x.z[1][this[this.o]]["3"][a.b.c]'),
output: null,
options: [{ ignoreTernaryTests: false }] as const,
errors: [
{
messageId: 'preferNullishOverTernary' as const,
line: 1,
column: 1,
endLine: 1,
endColumn: code.replace(/x/g, 'x.z[1][this[this.o]]["3"][a.b.c]')
.length,
suggestions: [
{
messageId: 'suggestNullish' as const,
output: 'x.z[1][this[this.o]]["3"][a.b.c] ?? y;',
},
],
},
],
},
]),

...[
`
Expand Down

0 comments on commit d21395f

Please sign in to comment.