Skip to content
Open
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
6 changes: 5 additions & 1 deletion internal/linter/linter.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ func RunLinterInProgram(program *compiler.Program, allowFiles []string, skipFile
var childVisitor ast.Visitor
var patternVisitor func(node *ast.Node)
patternVisitor = func(node *ast.Node) {
runListeners(rule.WildcardTokenKind, node)
runListeners(node.Kind, node)
kind := rule.ListenerOnAllowPattern(node.Kind)
runListeners(kind, node)
Expand All @@ -200,8 +201,10 @@ func RunLinterInProgram(program *compiler.Program, allowFiles []string, skipFile

runListeners(rule.ListenerOnExit(kind), node)
runListeners(rule.ListenerOnExit(node.Kind), node)
runListeners(rule.WildcardExitTokenKind, node)
}
childVisitor = func(node *ast.Node) bool {
runListeners(rule.WildcardTokenKind, node)
runListeners(node.Kind, node)

switch node.Kind {
Expand All @@ -222,10 +225,11 @@ func RunLinterInProgram(program *compiler.Program, allowFiles []string, skipFile
}

runListeners(rule.ListenerOnExit(node.Kind), node)
runListeners(rule.WildcardExitTokenKind, node)

return false
}
file.Node.ForEachChild(childVisitor)
patternVisitor(&file.Node)
clear(registeredListeners)
}

Expand Down
3 changes: 2 additions & 1 deletion internal/plugins/import/plugin.go
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
package import_plugin
const PLUGIN_NAME = "eslint-plugin-import"

const PLUGIN_NAME = "eslint-plugin-import"
12 changes: 12 additions & 0 deletions internal/plugins/react_hooks/all.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package react_hooks_plugin

import (
"github.com/web-infra-dev/rslint/internal/plugins/react_hooks/rules/rules_of_hooks"
"github.com/web-infra-dev/rslint/internal/rule"
)

func GetAllRules() []rule.Rule {
return []rule.Rule{
rules_of_hooks.RulesOfHooksRule,
}
}
141 changes: 141 additions & 0 deletions internal/plugins/react_hooks/code_path_analysis/break_context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package code_path_analysis

type BreakContext struct {
upper *BreakContext
breakable bool
label string
brokenForkContext *ForkContext
}

func NewBreakContext(state *CodePathState, breakable bool, label string) *BreakContext {
return &BreakContext{
upper: state.breakContext,
breakable: breakable,
label: label,
brokenForkContext: NewEmptyForkContext(state.forkContext, nil /*forkLeavingPath*/),
}
}

// Creates new context for BreakStatement.
func (s *CodePathState) PushBreakContext(breakable bool, label string) *BreakContext {
s.breakContext = NewBreakContext(s, breakable, label)
return s.breakContext
}

// Removes the top item of the break context stack.
func (s *CodePathState) PopBreakContext() *BreakContext {
context := s.breakContext
forkContext := s.forkContext

s.breakContext = context.upper

// Process this context here for other than switches and loops.
if !context.breakable {
brokenForkContext := context.brokenForkContext

if !brokenForkContext.IsEmpty() {
brokenForkContext.Add(forkContext.Head())
forkContext.ReplaceHead(brokenForkContext.MakeNext(0, -1))
}
}

return context
}

// Makes a path for a `break` statement.
// It registers the head segment to a context of `break`.
// It makes new unreachable segment, then it set the head with the segment.
func (s *CodePathState) MakeBreak(label string) {
forkContext := s.forkContext

if !forkContext.IsReachable() {
return
}

context := s.getBreakContext(label)

if context != nil {
context.brokenForkContext.Add(forkContext.Head())
}

forkContext.ReplaceHead(forkContext.MakeUnreachable(-1, -1))
}

func (s *CodePathState) getBreakContext(label string) *BreakContext {
context := s.breakContext

for context != nil {
if label == "" && context.breakable {
return context
} else if context.label == label {
return context
}

context = context.upper
}

return nil
}

// Makes a path for a `continue` statement.
//
// It makes a looping path.
// It makes new unreachable segment, then it set the head with the segment.
func (s *CodePathState) MakeContinue(label string) {
forkContext := s.forkContext

if !forkContext.IsReachable() {
return
}

context := s.getContinueContext(label)

if context != nil {
if context.continueDestSegments != nil {
s.MakeLooped(forkContext.Head(), context.continueDestSegments)

// If the context is a for-in/of loop, this effects a break also.
if context.kind == ForInStatement || context.kind == ForOfStatement {
context.brokenForkContext.Add(forkContext.Head())
}
} else {
context.continueForkContext.Add(forkContext.Head())
}
}
forkContext.ReplaceHead(forkContext.MakeUnreachable(-1, -1))
}

// Gets a loop-context for a `continue` statement.
func (s *CodePathState) getContinueContext(label string) *LoopContext {
if label == "" {
return s.loopContext
}

context := s.loopContext
for context != nil {
if context.label == label {
return context
}
context = context.upper
}

return nil
}

// Makes a path for a `return` statement.
//
// It registers the head segment to a context of `return`.
// It makes new unreachable segment, then it set the head with the segment.
func (s *CodePathState) MakeReturn() {
forkContext := s.forkContext

if forkContext.IsReachable() {
returnCtx := s.getReturnContext()
if returnCtx != nil {
returnCtx.returnedForkContext.Add(forkContext.Head())
} else {
s.addReturnedSegments(forkContext.Head())
}
forkContext.ReplaceHead(forkContext.MakeUnreachable(-1, -1))
}
}
51 changes: 51 additions & 0 deletions internal/plugins/react_hooks/code_path_analysis/chain_context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package code_path_analysis

type ChainContext struct {
upper *ChainContext
countChoiceContext int
}

func NewChainContext(state *CodePathState) *ChainContext {
return &ChainContext{
upper: state.chainContext,
countChoiceContext: 0,
}
}

// Push a new `ChainExpression` context to the stack.
// This method is called on entering to each `ChainExpression` node.
// This context is used to count forking in the optional chain then merge them on the exiting from the `ChainExpression` node.
func (s *CodePathState) PushChainContext() {
s.chainContext = NewChainContext(s)
}

// Pop a `ChainExpression` context from the stack.
// This method is called on exiting from each `ChainExpression` node.
// This merges all forks of the last optional chaining.
func (s *CodePathState) PopChainContext() {
context := s.chainContext
s.chainContext = context.upper

// pop all choice contexts of this.
for i := context.countChoiceContext; i > 0; i-- {
s.PopChoiceContext()
}
}

// Create a choice context for optional access.
// This method is called on entering to each `(Call|Member)Expression[optional=true]` node.
// This creates a choice context as similar to `LogicalExpression[operator="??"]` node.
func (s *CodePathState) MakeOptionalNode() {
if s.chainContext != nil {
s.chainContext.countChoiceContext += 1
s.PushChoiceContext("??", false)
}
}

// Create a fork.
// This method is called on entering to the `arguments|property` property of each `(Call|Member)Expression` node.
func (s *CodePathState) MakeOptionalRight() {
if s.chainContext != nil {
s.MakeLogicalRight()
}
}
Loading