feat(lint): simplify TS boolean expressions
MODIFY
internal/compiler/lint/rules/index.ts
internal/compiler/lint/rules/index.ts
-/* GENERATED:START(hash:975209047ce41adf41a399951c4d0e439e9f6633,id:main) Everything below is automatically generated. DO NOT MODIFY. Run `./rome run scripts/generated-files/lint-rules` to update. */
+/* GENERATED:START(hash:067905d26092d9f43a3792c9d668e05b7c1656fc,id:main) Everything below is automatically generated. DO NOT MODIFY. Run `./rome run scripts/generated-files/lint-rules` to update. */
import useClosingNonVoid from "./html/useClosingNonVoid";
import noArguments from "./js/noArguments";
import noAsyncPromiseExecutor from "./js/noAsyncPromiseExecutor";
@@ -110,6 +110,7 @@ import noPosixInRegularExpression from "./regex/noPosixInRegularExpression";
import noReferenceToNonExistingGroup from "./regex/noReferenceToNonExistingGroup";
import noExplicitAny from "./ts/noExplicitAny";
import useInterfaces from "./ts/useInterfaces";
+import useSimplifiedBooleanExpression from "./ts/useSimplifiedBooleanExpression";
import {AnyVisitor} from "@internal/compiler";
export const lintTransforms: Map<LintRuleName, AnyVisitor> = new Map();
@@ -254,6 +255,10 @@ lintTransforms.set(
);
lintTransforms.set("ts/noExplicitAny", noExplicitAny);
lintTransforms.set("ts/useInterfaces", useInterfaces);
+lintTransforms.set(
+ "ts/useSimplifiedBooleanExpression",
+ useSimplifiedBooleanExpression,
+);
export const lintRuleNames: Array<LintRuleName> = [
"html/useClosingNonVoid",
@@ -367,6 +372,7 @@ export const lintRuleNames: Array<LintRuleName> = [
"regex/noReferenceToNonExistingGroup",
"ts/noExplicitAny",
"ts/useInterfaces",
+ "ts/useSimplifiedBooleanExpression",
];
export type LintRuleName =
@@ -480,5 +486,6 @@ export type LintRuleName =
| "regex/noPosixInRegularExpression"
| "regex/noReferenceToNonExistingGroup"
| "ts/noExplicitAny"
- | "ts/useInterfaces";
+ | "ts/useInterfaces"
+ | "ts/useSimplifiedBooleanExpression";
/* GENERATED:END(id:main) */

MODIFY
internal/compiler/lint/rules/js/useSimplifiedBooleanExpression.ts
internal/compiler/lint/rules/js/useSimplifiedBooleanExpression.ts
@@ -13,47 +13,7 @@ export default createVisitor({
enter(path) {
const { node } = path;
- if (node.type === "JSBinaryExpression") {
- if (
- node.operator === "===" ||
- node.operator === "==" ||
- node.operator === "!==" ||
- node.operator === "!="
- ) {
- // if the operator is "not equal" (exclusive) or one of the operands is "false"
- // we consider the expression to be "negated"
- let negated = node.operator === "!==" || node.operator === "!=";
-
- if (node.left.type === "JSBooleanLiteral") {
- return simplifyBinaryExpressionDiagnostic(
- path,
- node.left,
- node.right,
- negated
- );
- }
- if (node.right.type === "JSBooleanLiteral") {
- return simplifyBinaryExpressionDiagnostic(
- path,
- node.right,
- node.left,
- negated
- );
- }
- }
- } else if (
- node.type === "JSUnaryExpression" &&
- node.operator === "!" &&
- node.argument.type === "JSUnaryExpression" &&
- node.argument.operator === "!"
- ) {
- return path.addFixableDiagnostic(
- {
- fixed: signals.replace({ ...node.argument.argument }),
- },
- descriptions.LINT.JS_USE_SIMPLIFIED_BOOLEAN_EXPRESSION
- );
- } else if (node.type === "JSLogicalExpression") {
+ if (node.type === "JSLogicalExpression") {
if (node.operator === "&&") {
if (node.left.type === "JSBooleanLiteral") {
return simplifyAndExpression(path, node.left, node.right);
@@ -98,36 +58,6 @@ export default createVisitor({
},
});
-function simplifyBinaryExpressionDiagnostic(
- path: Path,
- toRemove: JSBooleanLiteral,
- expression: AnyJSExpression,
- negated: boolean
-) {
- const shouldNegate = xor(negated, !toRemove.value);
- return path.addFixableDiagnostic(
- {
- fixed: signals.replace(createSimpleExpression(expression, shouldNegate)),
- },
- descriptions.LINT.JS_USE_SIMPLIFIED_BOOLEAN_EXPRESSION
- );
-}
-
-function createSimpleExpression(
- expression: AnyJSExpression,
- shouldNegate: boolean
-): AnyJSExpression {
- return shouldNegate
- ? {
- type: "JSUnaryExpression",
- operator: "!",
- argument: expression,
- }
- : {
- ...expression,
- };
-}
-
function simplifyAndExpression(
path: Path,
literal: JSBooleanLiteral,
@@ -200,7 +130,3 @@ interface DeMorganExpression extends JSLogicalExpression {
left: NegatedExpression;
right: NegatedExpression;
}
-
-function xor(x: boolean, y: boolean) {
- return x ? !y : y;
-}

ADD
internal/compiler/lint/rules/ts/useSimplifiedBooleanExpression.ts
+import { AnyJSExpression, AnyNode } from "@internal/ast";
+import { createVisitor, Path, Scope, signals } from "@internal/compiler";
+import { descriptions } from "@internal/diagnostics";
+import { JSBooleanLiteral } from "@internal/ast";
+import { resolveIndirection } from "@internal/js-ast-utils";
+
+export default createVisitor({
+ name: "ts/useSimplifiedBooleanExpression",
+ enter(path) {
+ const { node, scope } = path;
+
+ if (node.type === "JSBinaryExpression") {
+ if (node.operator === "===" || node.operator === "!==") {
+ // if the operator is "not equal" (exclusive) or one of the operands is "false"
+ // we consider the expression to be "negated"
+ let negated = node.operator === "!==";
+
+ if (node.left.type === "JSBooleanLiteral") {
+ const {
+ node: resolvedNode,
+ scope: resolvedScope,
+ } = resolveIndirection(node.right, scope);
+
+ if (isBooleanType(resolvedNode, resolvedScope)) {
+ return simplifyBinaryExpression(
+ path,
+ node.left,
+ node.right,
+ negated
+ );
+ }
+ }
+ if (node.right.type === "JSBooleanLiteral") {
+ const {
+ node: resolvedNode,
+ scope: resolvedScope,
+ } = resolveIndirection(node.left, scope);
+
+ if (isBooleanType(resolvedNode, resolvedScope)) {
+ return simplifyBinaryExpression(
+ path,
+ node.right,
+ node.left,
+ negated
+ );
+ }
+ }
+ }
+ } else if (
+ node.type === "JSUnaryExpression" &&
+ node.operator === "!" &&
+ node.argument.type === "JSUnaryExpression" &&
+ node.argument.operator === "!"
+ ) {
+ const realArgument = node.argument.argument;
+ const { node: resolvedNode, scope: resolvedScope } = resolveIndirection(
+ realArgument,
+ scope
+ );
+ if (isBooleanType(resolvedNode, resolvedScope)) {
+ return path.addFixableDiagnostic(
+ {
+ fixed: signals.replace({ ...node.argument.argument }),
+ },
+ descriptions.LINT.JS_USE_SIMPLIFIED_BOOLEAN_EXPRESSION
+ );
+ }
+ }
+
+ return signals.retain;
+ },
+});
+
+function simplifyBinaryExpression(
+ path: Path,
+ toRemove: JSBooleanLiteral,
+ expression: AnyJSExpression,
+ negated: boolean
+) {
+ const shouldNegate = xor(negated, !toRemove.value);
+ return path.addFixableDiagnostic(
+ {
+ fixed: signals.replace(createSimpleExpression(expression, shouldNegate)),
+ },
+ descriptions.LINT.TS_USE_SIMPLIFIED_BOOLEAN_EXPRESSION
+ );
+}
+
+function createSimpleExpression(
+ expression: AnyJSExpression,
+ shouldNegate: boolean
+): AnyJSExpression {
+ return shouldNegate
+ ? {
+ type: "JSUnaryExpression",
+ operator: "!",
+ argument: expression,
+ }
+ : {
+ ...expression,
+ };
+}
+
+function isBooleanType(node: AnyNode, scope: Scope): boolean {
+ if (node.type === "JSBooleanLiteral") return true;
+ if (node.type === "JSLogicalExpression") return true;
+
+ if (node.type === "JSReferenceIdentifier") {
+ const binding = scope.getBinding(node.name);
+ return (
+ binding?.node.type === "JSBindingIdentifier" &&
+ (binding.node.meta?.typeAnnotation?.type ===
+ "TSBooleanKeywordTypeAnnotation" ||
+ binding.node.meta?.typeAnnotation?.type ===
+ "TSBooleanLiteralTypeAnnotation")
+ );
+ }
+
+ return false;
+}
+
+function xor(x: boolean, y: boolean) {
+ return x ? !y : y;
+}

MODIFY
internal/diagnostics/categories.ts
internal/diagnostics/categories.ts
@@ -109,7 +109,7 @@ export type DiagnosticCategoryPrefix =
| "typeCheck"
| "v8";
-/* GENERATED:START(hash:ad06cb0919fbdc43e38e4a4a4079e71e0701b17a,id:main) Everything below is automatically generated. DO NOT MODIFY. Run `./rome run scripts/generated-files/lint-rules` to update. */
+/* GENERATED:START(hash:b0511b655f548dfe017bf72d78ef1f822d359eba,id:main) Everything below is automatically generated. DO NOT MODIFY. Run `./rome run scripts/generated-files/lint-rules` to update. */
export type DiagnosticLintCategory =
| "lint/html/useClosingNonVoid"
| "lint/js/noArguments"
@@ -221,5 +221,6 @@ export type DiagnosticLintCategory =
| "lint/regex/noPosixInRegularExpression"
| "lint/regex/noReferenceToNonExistingGroup"
| "lint/ts/noExplicitAny"
- | "lint/ts/useInterfaces";
+ | "lint/ts/useInterfaces"
+ | "lint/ts/useSimplifiedBooleanExpression";
/* GENERATED:END(id:main) */

MODIFY
internal/diagnostics/descriptions/lint.ts
internal/diagnostics/descriptions/lint.ts
@@ -1150,4 +1150,8 @@ export const lint = createDiagnosticsCategory({
category: "lint/js/useSimplifiedBooleanExpression",
message: markup`Boolean expression contains unnecessary complexity.`,
},
+ TS_USE_SIMPLIFIED_BOOLEAN_EXPRESSION: {
+ category: "lint/ts/useSimplifiedBooleanExpression",
+ message: markup`Boolean expression contains unnecessary complexity.`,
+ },
});

feat(lint): simplify TS boolean expressions