Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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