Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ import (
"github.com/web-infra-dev/rslint/internal/plugins/typescript/rules/use_unknown_in_catch_callback_variable"
"github.com/web-infra-dev/rslint/internal/rule"
"github.com/web-infra-dev/rslint/internal/rules/dot_notation"
"github.com/web-infra-dev/rslint/internal/rules/no_unreachable"
"github.com/web-infra-dev/rslint/internal/rules/no_unreachable_loop"
"github.com/web-infra-dev/rslint/internal/rules/no_unsafe_finally"
)

// RslintConfig represents the top-level configuration array
Expand Down Expand Up @@ -326,6 +329,13 @@ func (config RslintConfig) GetRulesForFile(filePath string) map[string]*RuleConf
func RegisterAllRules() {
registerAllTypeScriptEslintPluginRules()
registerAllEslintImportPluginRules()
registerAllCoreEslintRules()
}

func registerAllCoreEslintRules() {
GlobalRuleRegistry.Register("no-unreachable", no_unreachable.NoUnreachableRule)
GlobalRuleRegistry.Register("no-unreachable-loop", no_unreachable_loop.NoUnreachableLoopRule)
GlobalRuleRegistry.Register("no-unsafe-finally", no_unsafe_finally.NoUnsafeFinallyRule)
}

// registerAllTypeScriptEslintPluginRules registers all available rules in the global registry
Expand Down
11 changes: 11 additions & 0 deletions internal/rules/fixtures/fixtures.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package fixtures

import (
"path"
"runtime"
)

func GetRootDir() string {
_, filename, _, _ := runtime.Caller(0)
return path.Dir(filename)
}
127 changes: 127 additions & 0 deletions internal/rules/no_unreachable/no_unreachable.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package no_unreachable

import (
"github.com/microsoft/typescript-go/shim/ast"
"github.com/web-infra-dev/rslint/internal/rule"
)

// NoUnreachableRule implements the no-unreachable rule
// Disallow unreachable code after return, throw, continue, or break
var NoUnreachableRule = rule.Rule{
Name: "no-unreachable",
Run: run,
}

func run(ctx rule.RuleContext, options any) rule.RuleListeners {
return rule.RuleListeners{
ast.KindBlock: func(node *ast.Node) {
block := node.AsBlock()
if block == nil || block.Statements == nil {
return
}

statements := block.Statements.Nodes
for i := 0; i < len(statements)-1; i++ {
stmt := statements[i]

// Check if this statement causes control flow to exit
if isControlFlowExit(*stmt) {

Check failure on line 28 in internal/rules/no_unreachable/no_unreachable.go

View workflow job for this annotation

GitHub Actions / Test Go (ubuntu-latest, 1.25.0)

cannot use *stmt (variable of struct type "github.com/microsoft/typescript-go/internal/ast".Node) as *"github.com/microsoft/typescript-go/shim/ast".Node value in argument to isControlFlowExit
// Check if there are statements after this one
nextStmt := statements[i+1]

// Report unreachable code on the next statement
ctx.ReportNode(*nextStmt, rule.RuleMessage{

Check failure on line 33 in internal/rules/no_unreachable/no_unreachable.go

View workflow job for this annotation

GitHub Actions / Test Go (ubuntu-latest, 1.25.0)

cannot use *nextStmt (variable of struct type "github.com/microsoft/typescript-go/internal/ast".Node) as *"github.com/microsoft/typescript-go/shim/ast".Node value in argument to ctx.ReportNode
Id: "unreachableCode",
Description: "Unreachable code.",
})

// Only report once per unreachable segment
break
}
}
},
ast.KindSwitchStatement: func(node *ast.Node) {
switchStmt := node.AsSwitchStatement()
if switchStmt == nil || switchStmt.CaseBlock == nil {
return
}

caseBlock := switchStmt.CaseBlock.AsCaseBlock()
if caseBlock == nil || caseBlock.Clauses == nil {
return
}

// Check each case/default clause for unreachable code
for _, clause := range caseBlock.Clauses.Nodes {
clauseNode := clause.AsCaseOrDefaultClause()
if clauseNode == nil || clauseNode.Statements == nil {
continue
}

statements := clauseNode.Statements.Nodes
for i := 0; i < len(statements)-1; i++ {
stmt := statements[i]

if isControlFlowExit(*stmt) {

Check failure on line 65 in internal/rules/no_unreachable/no_unreachable.go

View workflow job for this annotation

GitHub Actions / Test Go (ubuntu-latest, 1.25.0)

cannot use *stmt (variable of struct type "github.com/microsoft/typescript-go/internal/ast".Node) as *"github.com/microsoft/typescript-go/shim/ast".Node value in argument to isControlFlowExit
nextStmt := statements[i+1]
ctx.ReportNode(*nextStmt, rule.RuleMessage{

Check failure on line 67 in internal/rules/no_unreachable/no_unreachable.go

View workflow job for this annotation

GitHub Actions / Test Go (ubuntu-latest, 1.25.0)

cannot use *nextStmt (variable of struct type "github.com/microsoft/typescript-go/internal/ast".Node) as *"github.com/microsoft/typescript-go/shim/ast".Node value in argument to ctx.ReportNode
Id: "unreachableCode",
Description: "Unreachable code.",
})
break
}
}
}
},
}
}

// isControlFlowExit checks if a statement causes control flow to exit
// (return, throw, break, continue)
func isControlFlowExit(stmt *ast.Node) bool {
kind := stmt.Kind

switch kind {
case ast.KindReturnStatement, ast.KindThrowStatement,
ast.KindBreakStatement, ast.KindContinueStatement:
return true

case ast.KindIfStatement:
// If statement exits control flow if both branches exit
ifStmt := stmt.AsIfStatement()
if ifStmt == nil {
return false
}

// Check if both then and else branches exist and both exit
thenExits := ifStmt.ThenStatement != nil && statementExits(ifStmt.ThenStatement)
elseExits := ifStmt.ElseStatement != nil && statementExits(ifStmt.ElseStatement)

return thenExits && elseExits

case ast.KindBlock:
// A block exits if it contains an exiting statement
block := stmt.AsBlock()
if block == nil || block.Statements == nil {
return false
}

for _, s := range block.Statements.Nodes {
if isControlFlowExit(*s) {

Check failure on line 110 in internal/rules/no_unreachable/no_unreachable.go

View workflow job for this annotation

GitHub Actions / Test Go (ubuntu-latest, 1.25.0)

cannot use *s (variable of struct type "github.com/microsoft/typescript-go/internal/ast".Node) as *"github.com/microsoft/typescript-go/shim/ast".Node value in argument to isControlFlowExit
return true
}
}
return false

default:
return false
}
}

// statementExits checks if a statement guarantees control flow exit
func statementExits(stmt *ast.Node) bool {
if stmt == nil {
return false
}
return isControlFlowExit(stmt)
}
89 changes: 89 additions & 0 deletions internal/rules/no_unreachable/no_unreachable_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package no_unreachable

import (
"github.com/web-infra-dev/rslint/internal/rule_tester"
"github.com/web-infra-dev/rslint/internal/rules/fixtures"
"testing"
)

func TestNoUnreachableRule(t *testing.T) {
rule_tester.RunRuleTester(
fixtures.GetRootDir(),
"tsconfig.json",
t,
&NoUnreachableRule,
[]rule_tester.ValidTestCase{
{Code: `function foo() { return; }`},
{Code: `function foo() { throw new Error(); }`},
{Code: `function foo() { if (true) { return; } else { return; } }`},
{Code: `function foo() { while (true) { break; } return; }`},
{Code: `function foo() { for(;;) { break; } return; }`},
{Code: `function foo() { switch (foo) { case 1: return; } return; }`},
{Code: `function foo() { try { return; } catch (e) { return; } }`},
{Code: `function foo() { return; /* comment */ }`},
{Code: `function foo() { var x = 1; throw new Error(); }`},
},
[]rule_tester.InvalidTestCase{
{
Code: `function foo() { return; x = 1; }`,
Errors: []rule_tester.InvalidTestCaseError{
{
MessageId: "unreachableCode",
Line: 1,
Column: 26,
},
},
},
{
Code: `function foo() { throw new Error(); var x = 1; }`,
Errors: []rule_tester.InvalidTestCaseError{
{
MessageId: "unreachableCode",
Line: 1,
Column: 37,
},
},
},
{
Code: `function foo() { { return; x = 1; } }`,
Errors: []rule_tester.InvalidTestCaseError{
{
MessageId: "unreachableCode",
Line: 1,
Column: 28,
},
},
},
{
Code: `function foo() { while(true) { break; x = 1; } }`,
Errors: []rule_tester.InvalidTestCaseError{
{
MessageId: "unreachableCode",
Line: 1,
Column: 39,
},
},
},
{
Code: `function foo() { while(true) { continue; x = 1; } }`,
Errors: []rule_tester.InvalidTestCaseError{
{
MessageId: "unreachableCode",
Line: 1,
Column: 42,
},
},
},
{
Code: `switch (foo) { case 1: return; var x; }`,
Errors: []rule_tester.InvalidTestCaseError{
{
MessageId: "unreachableCode",
Line: 1,
Column: 32,
},
},
},
},
)
}
Loading
Loading