diff --git a/internal/config/config.go b/internal/config/config.go index 749304bb..93f9e7bb 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -32,6 +32,7 @@ import ( "github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_redundant_type_constituents" "github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_require_imports" "github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_unnecessary_boolean_literal_compare" + "github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_unnecessary_condition" "github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_unnecessary_template_expression" "github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_unnecessary_type_arguments" "github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/no_unnecessary_type_assertion" @@ -354,6 +355,7 @@ func registerAllTypeScriptEslintPluginRules() { GlobalRuleRegistry.Register("@typescript-eslint/no-redundant-type-constituents", no_redundant_type_constituents.NoRedundantTypeConstituentsRule) GlobalRuleRegistry.Register("@typescript-eslint/no-require-imports", no_require_imports.NoRequireImportsRule) GlobalRuleRegistry.Register("@typescript-eslint/no-unnecessary-boolean-literal-compare", no_unnecessary_boolean_literal_compare.NoUnnecessaryBooleanLiteralCompareRule) + GlobalRuleRegistry.Register("@typescript-eslint/no-unnecessary-condition", no_unnecessary_condition.NoUnnecessaryConditionRule) GlobalRuleRegistry.Register("@typescript-eslint/no-unnecessary-template-expression", no_unnecessary_template_expression.NoUnnecessaryTemplateExpressionRule) GlobalRuleRegistry.Register("@typescript-eslint/no-unnecessary-type-arguments", no_unnecessary_type_arguments.NoUnnecessaryTypeArgumentsRule) GlobalRuleRegistry.Register("@typescript-eslint/no-unnecessary-type-assertion", no_unnecessary_type_assertion.NoUnnecessaryTypeAssertionRule) diff --git a/internal/plugins/import/plugin.go b/internal/plugins/import/plugin.go index 187adec4..1baaec4f 100644 --- a/internal/plugins/import/plugin.go +++ b/internal/plugins/import/plugin.go @@ -1,2 +1,3 @@ package import_plugin -const PLUGIN_NAME = "eslint-plugin-import" \ No newline at end of file + +const PLUGIN_NAME = "eslint-plugin-import" diff --git a/internal/plugins/import/rules/no_self_import/no_self_import_test.go b/internal/plugins/import/rules/no_self_import/no_self_import_test.go index a6a85725..b619d4c1 100644 --- a/internal/plugins/import/rules/no_self_import/no_self_import_test.go +++ b/internal/plugins/import/rules/no_self_import/no_self_import_test.go @@ -3,9 +3,9 @@ package no_self_import_test import ( "testing" + "github.com/web-infra-dev/rslint/internal/plugins/import/fixtures" "github.com/web-infra-dev/rslint/internal/plugins/import/rules/no_self_import" "github.com/web-infra-dev/rslint/internal/rule_tester" - "github.com/web-infra-dev/rslint/internal/plugins/import/fixtures" ) func TestNoSelfImportRule(t *testing.T) { diff --git a/internal/plugins/typescript/rules/no_duplicate_type_constituents/no_duplicate_type_constituents_test.go b/internal/plugins/typescript/rules/no_duplicate_type_constituents/no_duplicate_type_constituents_test.go index d0ef6d53..c4be72e1 100644 --- a/internal/plugins/typescript/rules/no_duplicate_type_constituents/no_duplicate_type_constituents_test.go +++ b/internal/plugins/typescript/rules/no_duplicate_type_constituents/no_duplicate_type_constituents_test.go @@ -150,7 +150,7 @@ type T = Record; }, }, []rule_tester.InvalidTestCase{ { - Only: true, + Only: true, Code: "type T = 1 | 1;", Output: []string{"type T = 1 ;"}, Errors: []rule_tester.InvalidTestCaseError{ diff --git a/internal/plugins/typescript/rules/no_explicit_any/no_explicit_any.go b/internal/plugins/typescript/rules/no_explicit_any/no_explicit_any.go index 83a90d41..35de3de5 100644 --- a/internal/plugins/typescript/rules/no_explicit_any/no_explicit_any.go +++ b/internal/plugins/typescript/rules/no_explicit_any/no_explicit_any.go @@ -73,7 +73,7 @@ func isAnyInRestParameter(node *ast.Node) bool { // Check if the any keyword is inside a rest parameter with array type // We need to check if the any is part of an array type in a rest parameter // Valid patterns to ignore: ...args: any[], ...args: readonly any[], ...args: Array, ...args: ReadonlyArray - + // First check if we're inside an ArrayType inArrayType := false for p := node.Parent; p != nil; p = p.Parent { @@ -92,11 +92,11 @@ func isAnyInRestParameter(node *ast.Node) bool { } } } - + if !inArrayType { return false } - + // Then check if we're in a rest parameter for p := node.Parent; p != nil; p = p.Parent { if p.Kind == ast.KindParameter { diff --git a/internal/plugins/typescript/rules/no_unnecessary_condition/no_unnecessary_condition.go b/internal/plugins/typescript/rules/no_unnecessary_condition/no_unnecessary_condition.go new file mode 100644 index 00000000..de94c5cd --- /dev/null +++ b/internal/plugins/typescript/rules/no_unnecessary_condition/no_unnecessary_condition.go @@ -0,0 +1,471 @@ +package no_unnecessary_condition + +import ( + "github.com/microsoft/typescript-go/shim/ast" + "github.com/microsoft/typescript-go/shim/checker" + "github.com/web-infra-dev/rslint/internal/rule" + "github.com/web-infra-dev/rslint/internal/utils" +) + +type NoUnnecessaryConditionOptions struct { + AllowConstantLoopConditions *string `json:"allowConstantLoopConditions,omitempty"` + AllowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing *bool `json:"allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing,omitempty"` + CheckTypePredicates *bool `json:"checkTypePredicates,omitempty"` +} + +func parseOptions(options any) NoUnnecessaryConditionOptions { + opts := NoUnnecessaryConditionOptions{ + AllowConstantLoopConditions: utils.Ref("never"), + AllowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing: utils.Ref(false), + CheckTypePredicates: utils.Ref(false), + } + + if options == nil { + return opts + } + + // Handle direct map format + if m, ok := options.(map[string]any); ok { + parseOptionsFromMap(m, &opts) + return opts + } + + // Handle array format: [{ option: value }] + if arr, ok := options.([]any); ok { + if len(arr) > 0 { + if m, ok := arr[0].(map[string]any); ok { + parseOptionsFromMap(m, &opts) + } + } + } + + return opts +} + +func parseOptionsFromMap(m map[string]any, opts *NoUnnecessaryConditionOptions) { + if v, ok := m["allowConstantLoopConditions"]; ok { + // Can be boolean or string + switch val := v.(type) { + case bool: + if val { + opts.AllowConstantLoopConditions = utils.Ref("always") + } else { + opts.AllowConstantLoopConditions = utils.Ref("never") + } + case string: + opts.AllowConstantLoopConditions = utils.Ref(val) + } + } + if v, ok := m["allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing"].(bool); ok { + opts.AllowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing = utils.Ref(v) + } + if v, ok := m["checkTypePredicates"].(bool); ok { + opts.CheckTypePredicates = utils.Ref(v) + } +} + +// Rule message builders +func buildAlwaysFalsyMessage() rule.RuleMessage { + return rule.RuleMessage{ + Id: "alwaysFalsy", + Description: "Unnecessary conditional, value is always falsy.", + } +} + +func buildAlwaysTruthyMessage() rule.RuleMessage { + return rule.RuleMessage{ + Id: "alwaysTruthy", + Description: "Unnecessary conditional, value is always truthy.", + } +} + +func buildNeverMessage() rule.RuleMessage { + return rule.RuleMessage{ + Id: "never", + Description: "Unnecessary conditional, value is `never`.", + } +} + +func buildAlwaysNullishMessage() rule.RuleMessage { + return rule.RuleMessage{ + Id: "alwaysNullish", + Description: "Unnecessary conditional, left-hand side of `??` operator is always `null` or `undefined`.", + } +} + +func buildNeverNullishMessage() rule.RuleMessage { + return rule.RuleMessage{ + Id: "neverNullish", + Description: "Unnecessary conditional, expected left-hand side of `??` operator to be possibly null or undefined.", + } +} + +func buildNoStrictNullCheckMessage() rule.RuleMessage { + return rule.RuleMessage{ + Id: "noStrictNullCheck", + Description: "This rule requires the `strictNullChecks` compiler option to be turned on to function correctly.", + } +} + +// Type checking utilities using the correct RSLint APIs +func isNeverType(typeOfNode *checker.Type) bool { + return utils.IsTypeFlagSet(typeOfNode, checker.TypeFlagsNever) +} + +func isNullType(typeOfNode *checker.Type) bool { + return utils.IsTypeFlagSet(typeOfNode, checker.TypeFlagsNull) +} + +func isUndefinedType(typeOfNode *checker.Type) bool { + return utils.IsTypeFlagSet(typeOfNode, checker.TypeFlagsUndefined) +} + +func isVoidType(typeOfNode *checker.Type) bool { + return utils.IsTypeFlagSet(typeOfNode, checker.TypeFlagsVoid) +} + +// Check if type could be nullish (null | undefined) +func isPossiblyNullish(typeOfNode *checker.Type) bool { + if isNullType(typeOfNode) || isUndefinedType(typeOfNode) || isVoidType(typeOfNode) { + return true + } + + // For union types, check if any constituent could be nullish + if utils.IsUnionType(typeOfNode) { + for _, unionType := range utils.UnionTypeParts(typeOfNode) { + if isPossiblyNullish(unionType) { + return true + } + } + } + return false +} + +// isTypeNeverNullish checks if a type can never be null or undefined +func isTypeNeverNullish(t *checker.Type, typeChecker *checker.Checker) bool { + if t == nil { + return false + } + + // Check for any or unknown types - these could be nullish + flags := checker.Type_flags(t) + if flags&(checker.TypeFlagsAny|checker.TypeFlagsUnknown) != 0 { + return false + } + + // Check if the type itself is null, undefined, or void + if flags&(checker.TypeFlagsNull|checker.TypeFlagsUndefined|checker.TypeFlagsVoid) != 0 { + return false + } + + // For union types, check if any constituent could be nullish + if utils.IsUnionType(t) { + for _, unionType := range t.Types() { + typeFlags := checker.Type_flags(unionType) + if typeFlags&(checker.TypeFlagsNull|checker.TypeFlagsUndefined|checker.TypeFlagsVoid) != 0 { + return false + } + } + } + + // If we get here, the type cannot be nullish + return true +} + +// isAlwaysTruthy checks if a type is always truthy (cannot be falsy) +func isAlwaysTruthy(t *checker.Type) bool { + if t == nil { + return false + } + + flags := checker.Type_flags(t) + + // Any and unknown could be falsy + if flags&(checker.TypeFlagsAny|checker.TypeFlagsUnknown) != 0 { + return false + } + + // Never type cannot have a value + if flags&checker.TypeFlagsNever != 0 { + return false + } + + // These types are always falsy or could be falsy + if flags&(checker.TypeFlagsNull|checker.TypeFlagsUndefined|checker.TypeFlagsVoid) != 0 { + return false + } + + // Check for union types - all parts must be truthy + if utils.IsUnionType(t) { + for _, unionType := range t.Types() { + if !isAlwaysTruthy(unionType) { + return false + } + } + return true + } + + // Boolean type (not literal) can be true or false, so not always truthy + if flags&checker.TypeFlagsBoolean != 0 { + return false + } + + // Boolean literals - check if it's the 'true' literal + if flags&checker.TypeFlagsBooleanLiteral != 0 { + if utils.IsIntrinsicType(t) { + intrinsic := t.AsIntrinsicType() + if intrinsic != nil && intrinsic.IntrinsicName() == "true" { + return true + } + } + return false + } + + // Number literals could be 0, -0, or NaN (falsy values) + if flags&checker.TypeFlagsNumberLiteral != 0 { + // Would need to check the actual value + // For now, conservatively return false + return false + } + + // String literals could be "" (falsy) + if flags&checker.TypeFlagsStringLiteral != 0 { + // Would need to check for empty string + // For now, conservatively return false + return false + } + + // BigInt literals could be 0n (falsy) + if flags&checker.TypeFlagsBigIntLiteral != 0 { + // Would need to check for 0n + return false + } + + // Object types are always truthy + if flags&checker.TypeFlagsObject != 0 { + return true + } + + // For the purpose of this rule, non-nullable primitive types are considered "always truthy" + // This is not technically correct from a JavaScript perspective (empty string, 0, NaN are falsy), + // but matches the TypeScript ESLint rule behavior which flags these as unnecessary conditions + // when they are non-nullable types + if flags&checker.TypeFlagsString != 0 { + return true + } + + // Number type - treat as always truthy for non-nullable numbers + if flags&checker.TypeFlagsNumber != 0 { + return true + } + + // BigInt type - treat as always truthy for non-nullable bigints + if flags&checker.TypeFlagsBigInt != 0 { + return true + } + + // ESSymbol is always truthy + if flags&checker.TypeFlagsESSymbol != 0 { + return true + } + + return false +} + +// isAlwaysFalsy checks if a type is always falsy +func isAlwaysFalsy(t *checker.Type) bool { + if t == nil { + return false + } + + flags := checker.Type_flags(t) + + // Null, undefined, and void are always falsy + if flags&(checker.TypeFlagsNull|checker.TypeFlagsUndefined|checker.TypeFlagsVoid) != 0 { + return true + } + + // Check for literal false + if flags&checker.TypeFlagsBooleanLiteral != 0 { + if utils.IsIntrinsicType(t) { + intrinsic := t.AsIntrinsicType() + if intrinsic != nil && intrinsic.IntrinsicName() == "false" { + return true + } + } + } + + // Would need to check for literal 0, -0, NaN, "", 0n + // For now, we don't mark these as always falsy + + return false +} + +// checkCondition checks if a condition is unnecessary (always true/false/never) +func checkCondition(ctx rule.RuleContext, node *ast.Node, isNegated bool) { + if node == nil { + return + } + + // Get the type of the condition expression + conditionType := ctx.TypeChecker.GetTypeAtLocation(node) + if conditionType == nil { + return + } + + // Check for never type + if isNeverType(conditionType) { + ctx.ReportNode(node, buildNeverMessage()) + return + } + + // Check for always truthy + if isAlwaysTruthy(conditionType) { + ctx.ReportNode(node, buildAlwaysTruthyMessage()) + return + } + + // Check for always falsy + if isAlwaysFalsy(conditionType) { + ctx.ReportNode(node, buildAlwaysFalsyMessage()) + return + } +} + +// isBooleanOperator checks if a token kind represents a boolean comparison operator +func isBooleanOperator(kind ast.Kind) bool { + switch kind { + case ast.KindEqualsEqualsToken, ast.KindEqualsEqualsEqualsToken, + ast.KindExclamationEqualsToken, ast.KindExclamationEqualsEqualsToken, + ast.KindLessThanToken, ast.KindLessThanEqualsToken, + ast.KindGreaterThanToken, ast.KindGreaterThanEqualsToken: + return true + } + return false +} + +var NoUnnecessaryConditionRule = rule.CreateRule(rule.Rule{ + Name: "no-unnecessary-condition", + Run: func(ctx rule.RuleContext, options any) rule.RuleListeners { + opts := parseOptions(options) + + // Check for strict null checks + compilerOptions := ctx.Program.Options() + isStrictNullChecks := utils.IsStrictCompilerOptionEnabled( + compilerOptions, + compilerOptions.StrictNullChecks, + ) + + if !isStrictNullChecks && !*opts.AllowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing { + // Report at the beginning of the file + ctx.ReportNode(&ast.Node{}, buildNoStrictNullCheckMessage()) + return rule.RuleListeners{} + } + + return rule.RuleListeners{ + // If statement conditions + ast.KindIfStatement: func(node *ast.Node) { + ifStmt := node.AsIfStatement() + if ifStmt != nil { + checkCondition(ctx, ifStmt.Expression, false) + } + }, + + // While loop conditions + ast.KindWhileStatement: func(node *ast.Node) { + whileStmt := node.AsWhileStatement() + if whileStmt != nil && whileStmt.Expression != nil { + // Handle constant loop conditions + if *opts.AllowConstantLoopConditions != "never" { + // Check if it's a constant condition + typeOfCondition := ctx.TypeChecker.GetTypeAtLocation(whileStmt.Expression) + if typeOfCondition != nil { + flags := checker.Type_flags(typeOfCondition) + // Check for literal true/false + if flags&checker.TypeFlagsBooleanLiteral != 0 { + if utils.IsIntrinsicType(typeOfCondition) { + intrinsic := typeOfCondition.AsIntrinsicType() + if intrinsic != nil && (intrinsic.IntrinsicName() == "true" || intrinsic.IntrinsicName() == "false") { + // Skip checking constant boolean literals in loops when allowed + return + } + } + } + } + } + checkCondition(ctx, whileStmt.Expression, false) + } + }, + + // For loop conditions + ast.KindForStatement: func(node *ast.Node) { + forStmt := node.AsForStatement() + if forStmt != nil && forStmt.Condition != nil { + checkCondition(ctx, forStmt.Condition, false) + } + }, + + // Do-while loop conditions + ast.KindDoStatement: func(node *ast.Node) { + doStmt := node.AsDoStatement() + if doStmt != nil { + checkCondition(ctx, doStmt.Expression, false) + } + }, + + // Conditional expressions (ternary) + ast.KindConditionalExpression: func(node *ast.Node) { + condExpr := node.AsConditionalExpression() + if condExpr != nil { + checkCondition(ctx, condExpr.Condition, false) + } + }, + + // Binary expressions (comparisons and logical expressions) + ast.KindBinaryExpression: func(node *ast.Node) { + binExpr := node.AsBinaryExpression() + if binExpr != nil { + // Handle logical AND/OR + if binExpr.OperatorToken.Kind == ast.KindAmpersandAmpersandToken || + binExpr.OperatorToken.Kind == ast.KindBarBarToken { + checkCondition(ctx, binExpr.Left, false) + return + } + + // Handle nullish coalescing operator (??) + if binExpr.OperatorToken.Kind == ast.KindQuestionQuestionToken { + leftType := ctx.TypeChecker.GetTypeAtLocation(binExpr.Left) + if leftType != nil { + // Check if left side can never be nullish (null or undefined) + if isTypeNeverNullish(leftType, ctx.TypeChecker) { + ctx.ReportNode(binExpr.Left, buildNeverNullishMessage()) + } + // Check if left side is always nullish + if isAlwaysFalsy(leftType) && isPossiblyNullish(leftType) { + ctx.ReportNode(binExpr.Left, buildAlwaysNullishMessage()) + } + } + return + } + + // Handle boolean comparisons + if isBooleanOperator(binExpr.OperatorToken.Kind) { + leftType := ctx.TypeChecker.GetTypeAtLocation(binExpr.Left) + rightType := ctx.TypeChecker.GetTypeAtLocation(binExpr.Right) + + if leftType != nil && rightType != nil { + // Check if both sides are literal types + // This would require extracting literal values from types + // For now, skip this complex comparison logic + // A full implementation would check for literal type comparisons like: + // if (true === true) -> always true + // if (1 === 2) -> always false + // TODO: Implement literal type comparison logic + _, _ = leftType, rightType + } + } + } + }, + } + }, +}) diff --git a/internal/plugins/typescript/rules/no_unnecessary_condition/no_unnecessary_condition_test.go b/internal/plugins/typescript/rules/no_unnecessary_condition/no_unnecessary_condition_test.go new file mode 100644 index 00000000..16793991 --- /dev/null +++ b/internal/plugins/typescript/rules/no_unnecessary_condition/no_unnecessary_condition_test.go @@ -0,0 +1,110 @@ +package no_unnecessary_condition + +import ( + "testing" + + "github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/fixtures" + "github.com/web-infra-dev/rslint/internal/rule_tester" +) + +func TestNoUnnecessaryConditionRule(t *testing.T) { + rule_tester.RunRuleTester(fixtures.GetRootDir(), "tsconfig.json", t, &NoUnnecessaryConditionRule, + []rule_tester.ValidTestCase{ + // Valid cases - conditions that are necessary + {Code: ` +declare const x: string | null; +if (x) { + console.log(x); +}`}, + {Code: ` +declare const x: number | undefined; +while (x) { + console.log(x); +}`}, + {Code: ` +declare const x: boolean; +if (x) { + console.log('true'); +}`}, + // Valid with allowConstantLoopConditions + { + Code: `while (true) { break; }`, + Options: map[string]any{"allowConstantLoopConditions": true}, + }, + }, + []rule_tester.InvalidTestCase{ + // Always truthy conditions + { + Code: ` +declare const x: string; +if (x) { + console.log(x); +}`, + Errors: []rule_tester.InvalidTestCaseError{{ + MessageId: "alwaysTruthy", + Line: 3, + Column: 5, + }}, + }, + // Always falsy conditions + { + Code: ` +declare const x: null; +if (x) { + console.log(x); +}`, + Errors: []rule_tester.InvalidTestCaseError{{ + MessageId: "alwaysFalsy", + Line: 3, + Column: 5, + }}, + }, + // Never type + { + Code: ` +declare const x: never; +if (x) { + console.log(x); +}`, + Errors: []rule_tester.InvalidTestCaseError{{ + MessageId: "never", + Line: 3, + Column: 5, + }}, + }, + // Unnecessary nullish coalescing + { + Code: ` +declare const x: string; +const y = x ?? 'default';`, + Errors: []rule_tester.InvalidTestCaseError{{ + MessageId: "neverNullish", + Line: 3, + Column: 11, + }}, + }, + }, + ) +} + +func TestNoUnnecessaryConditionRuleWithStrictNullChecks(t *testing.T) { + rule_tester.RunRuleTester(fixtures.GetRootDir(), "tsconfig.json", t, &NoUnnecessaryConditionRule, + []rule_tester.ValidTestCase{ + { + Code: `declare const x: any; if (x) { }`, + Options: map[string]any{"allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing": true}, + }, + }, + []rule_tester.InvalidTestCase{ + { + Code: `const x = null; if (x) { }`, + Options: map[string]any{"allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing": true}, + Errors: []rule_tester.InvalidTestCaseError{{ + MessageId: "alwaysFalsy", + Line: 1, + Column: 21, + }}, + }, + }, + ) +} diff --git a/internal/plugins/typescript/rules/no_unnecessary_type_assertion/no_unnecessary_type_assertion.go b/internal/plugins/typescript/rules/no_unnecessary_type_assertion/no_unnecessary_type_assertion.go index 7e17a8a5..3626b361 100644 --- a/internal/plugins/typescript/rules/no_unnecessary_type_assertion/no_unnecessary_type_assertion.go +++ b/internal/plugins/typescript/rules/no_unnecessary_type_assertion/no_unnecessary_type_assertion.go @@ -219,12 +219,12 @@ var NoUnnecessaryTypeAssertionRule = rule.CreateRule(rule.Rule{ if node.Kind == ast.KindAsExpression { s := scanner.GetScannerForSourceFile(ctx.SourceFile, expression.End()) asKeywordRange := s.TokenRange() - + sourceText := ctx.SourceFile.Text() startPos := asKeywordRange.Pos() - + if startPos > expression.End() && sourceText[startPos-1] == ' ' { - if startPos-1 == expression.End() || (startPos-2 >= 0 && sourceText[startPos-2] != ' ') { + if startPos-1 == expression.End() || (startPos-2 >= 0 && sourceText[startPos-2] != ' ') { startPos-- } } diff --git a/internal/utils/create_program.go b/internal/utils/create_program.go index 257ee212..0af7f2a4 100644 --- a/internal/utils/create_program.go +++ b/internal/utils/create_program.go @@ -17,7 +17,7 @@ import ( func CreateCompilerHost(cwd string, fs vfs.FS) compiler.CompilerHost { defaultLibraryPath := bundled.LibPath() var extendedConfigCache collections.SyncMap[tspath.Path, *tsoptions.ExtendedConfigCacheEntry] - return compiler.NewCompilerHost(cwd, fs, defaultLibraryPath, &extendedConfigCache,nil) + return compiler.NewCompilerHost(cwd, fs, defaultLibraryPath, &extendedConfigCache, nil) } func CreateProgram(singleThreaded bool, fs vfs.FS, cwd string, tsconfigPath string, host compiler.CompilerHost) (*compiler.Program, error) { diff --git a/packages/rslint-test-tools/rule-manifest.json b/packages/rslint-test-tools/rule-manifest.json index 4431c2df..41724f2b 100644 --- a/packages/rslint-test-tools/rule-manifest.json +++ b/packages/rslint-test-tools/rule-manifest.json @@ -147,6 +147,12 @@ "status": "partial-test", "failing_case": [] }, + { + "name": "no-unnecessary-condition", + "group": "@typescript-eslint", + "status": "partial-test", + "failing_case": [] + }, { "name": "no-unnecessary-template-expression", "group": "@typescript-eslint", diff --git a/rslint.json b/rslint.json index 78ac8fdf..fa3ec33c 100644 --- a/rslint.json +++ b/rslint.json @@ -51,6 +51,7 @@ "@typescript-eslint/no-require-imports": "error", "@typescript-eslint/no-namespace": "error", "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/no-unnecessary-condition": "warn", "@typescript-eslint/prefer-nullish-coalescing": "warn", "@typescript-eslint/no-misused-promises": "warn", "@typescript-eslint/no-unused-vars": "warn",