Skip to content
Merged
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
9 changes: 6 additions & 3 deletions internal/linter/linter.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,8 @@ func RunLinterOnProgram(logLevel utils.LogLevel, program *compiler.Program, file
SourceFile: file,
})
},
ReportRangeWithSuggestions: func(textRange core.TextRange, msg rule.RuleMessage, suggestions ...rule.RuleSuggestion) {
ReportRangeWithSuggestions: func(textRange core.TextRange, msg rule.RuleMessage, suggestionsFn func() []rule.RuleSuggestion) {
suggestions := suggestionsFn()
onDiagnostic(rule.RuleDiagnostic{
RuleName: r.Name,
Range: textRange,
Expand All @@ -178,7 +179,8 @@ func RunLinterOnProgram(logLevel utils.LogLevel, program *compiler.Program, file
SourceFile: file,
})
},
ReportNodeWithFixes: func(node *ast.Node, msg rule.RuleMessage, fixes ...rule.RuleFix) {
ReportNodeWithFixes: func(node *ast.Node, msg rule.RuleMessage, fixesFn func() []rule.RuleFix) {
fixes := fixesFn()
onDiagnostic(rule.RuleDiagnostic{
RuleName: r.Name,
Range: utils.TrimNodeTextRange(file, node),
Expand All @@ -188,7 +190,8 @@ func RunLinterOnProgram(logLevel utils.LogLevel, program *compiler.Program, file
})
},

ReportNodeWithSuggestions: func(node *ast.Node, msg rule.RuleMessage, suggestions ...rule.RuleSuggestion) {
ReportNodeWithSuggestions: func(node *ast.Node, msg rule.RuleMessage, suggestionsFn func() []rule.RuleSuggestion) {
suggestions := suggestionsFn()
onDiagnostic(rule.RuleDiagnostic{
RuleName: r.Name,
Range: utils.TrimNodeTextRange(file, node),
Expand Down
16 changes: 9 additions & 7 deletions internal/rule/rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,19 +114,21 @@ type RuleContext struct {
Program *compiler.Program
TypeChecker *checker.Checker
ReportRange func(textRange core.TextRange, msg RuleMessage)
ReportRangeWithSuggestions func(textRange core.TextRange, msg RuleMessage, suggestions ...RuleSuggestion)
ReportRangeWithSuggestions func(textRange core.TextRange, msg RuleMessage, suggestionsFn func() []RuleSuggestion)
ReportNode func(node *ast.Node, msg RuleMessage)
ReportNodeWithFixes func(node *ast.Node, msg RuleMessage, fixes ...RuleFix)
ReportNodeWithSuggestions func(node *ast.Node, msg RuleMessage, suggestions ...RuleSuggestion)
ReportNodeWithFixes func(node *ast.Node, msg RuleMessage, fixesFn func() []RuleFix)
ReportNodeWithSuggestions func(node *ast.Node, msg RuleMessage, suggestionsFn func() []RuleSuggestion)
}

func ReportNodeWithFixesOrSuggestions(ctx RuleContext, node *ast.Node, fix bool, msg RuleMessage, suggestionMsg RuleMessage, fixes ...RuleFix) {
if fix {
ctx.ReportNodeWithFixes(node, msg, fixes...)
ctx.ReportNodeWithFixes(node, msg, func() []RuleFix { return fixes })
} else {
ctx.ReportNodeWithSuggestions(node, msg, RuleSuggestion{
Message: suggestionMsg,
FixesArr: fixes,
ctx.ReportNodeWithSuggestions(node, msg, func() []RuleSuggestion {
return []RuleSuggestion{{
Message: suggestionMsg,
FixesArr: fixes,
}}
})
}
}
30 changes: 17 additions & 13 deletions internal/rules/await_thenable/await_thenable.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,13 @@ var AwaitThenableRule = rule.Rule{
certainty := utils.NeedsToBeAwaited(ctx.TypeChecker, awaitArgument, awaitArgumentType)

if certainty == utils.TypeAwaitableNever {
ctx.ReportNodeWithSuggestions(node, buildAwaitMessage(), rule.RuleSuggestion{
Message: buildRemoveAwaitMessage(),
FixesArr: []rule.RuleFix{
rule.RuleFixRemoveRange(scanner.GetRangeOfTokenAtPosition(ctx.SourceFile, node.Pos())),
},
ctx.ReportNodeWithSuggestions(node, buildAwaitMessage(), func() []rule.RuleSuggestion {
return []rule.RuleSuggestion{{
Message: buildRemoveAwaitMessage(),
FixesArr: []rule.RuleFix{
rule.RuleFixRemoveRange(scanner.GetRangeOfTokenAtPosition(ctx.SourceFile, node.Pos())),
},
}}
})
}
},
Expand All @@ -80,13 +82,15 @@ var AwaitThenableRule = rule.Rule{
ctx.ReportRangeWithSuggestions(
utils.GetForStatementHeadLoc(ctx.SourceFile, node),
buildForAwaitOfNonAsyncIterableMessage(),
// Note that this suggestion causes broken code for sync iterables
// of promises, since the loop variable is not awaited.
rule.RuleSuggestion{
Message: buildConvertToOrdinaryForMessage(),
FixesArr: []rule.RuleFix{
rule.RuleFixRemove(ctx.SourceFile, stmt.AwaitModifier),
},
func() []rule.RuleSuggestion {
// Note that this suggestion causes broken code for sync iterables
// of promises, since the loop variable is not awaited.
return []rule.RuleSuggestion{{
Message: buildConvertToOrdinaryForMessage(),
FixesArr: []rule.RuleFix{
rule.RuleFixRemove(ctx.SourceFile, stmt.AwaitModifier),
},
}}
},
)
},
Expand Down Expand Up @@ -126,7 +130,7 @@ var AwaitThenableRule = rule.Rule{
})
}

ctx.ReportNodeWithSuggestions(init, buildAwaitUsingOfNonAsyncDisposableMessage(), suggestions...)
ctx.ReportNodeWithSuggestions(init, buildAwaitUsingOfNonAsyncDisposableMessage(), func() []rule.RuleSuggestion { return suggestions })
}
},
}
Expand Down
16 changes: 9 additions & 7 deletions internal/rules/no_array_delete/no_array_delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,15 @@ var NoArrayDeleteRule = rule.Rule{
leftBracketTokenRange := scanner.GetRangeOfTokenAtPosition(ctx.SourceFile, expressionRange.End())
rightBracketTokenRange := scanner.GetRangeOfTokenAtPosition(ctx.SourceFile, argumentRange.End())

ctx.ReportNodeWithSuggestions(node, buildNoArrayDeleteMessage(), rule.RuleSuggestion{
Message: buildUseSpliceMessage(),
FixesArr: []rule.RuleFix{
rule.RuleFixRemoveRange(deleteTokenRange),
rule.RuleFixReplaceRange(leftBracketTokenRange, ".splice("),
rule.RuleFixReplaceRange(rightBracketTokenRange, ", 1)"),
},
ctx.ReportNodeWithSuggestions(node, buildNoArrayDeleteMessage(), func() []rule.RuleSuggestion {
return []rule.RuleSuggestion{{
Message: buildUseSpliceMessage(),
FixesArr: []rule.RuleFix{
rule.RuleFixRemoveRange(deleteTokenRange),
rule.RuleFixReplaceRange(leftBracketTokenRange, ".splice("),
rule.RuleFixReplaceRange(rightBracketTokenRange, ", 1)"),
},
}}
})
},
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ var NoConfusingVoidExpressionRule = rule.Rule{
}

if opts.IgnoreVoidOperator {
ctx.ReportNodeWithFixes(node, buildInvalidVoidExprArrowWrapVoidMessage(), insertVoidFix())
ctx.ReportNodeWithFixes(node, buildInvalidVoidExprArrowWrapVoidMessage(), func() []rule.RuleFix { return []rule.RuleFix{insertVoidFix()} })
return
}

Expand All @@ -220,7 +220,7 @@ var NoConfusingVoidExpressionRule = rule.Rule{
}
}

ctx.ReportNodeWithFixes(node, buildInvalidVoidExprArrowMessage(), fixes...)
ctx.ReportNodeWithFixes(node, buildInvalidVoidExprArrowMessage(), func() []rule.RuleFix { return fixes })
return
}

Expand All @@ -234,7 +234,7 @@ var NoConfusingVoidExpressionRule = rule.Rule{
}

if opts.IgnoreVoidOperator {
ctx.ReportNodeWithFixes(node, buildInvalidVoidExprReturnWrapVoidMessage(), insertVoidFix())
ctx.ReportNodeWithFixes(node, buildInvalidVoidExprReturnWrapVoidMessage(), func() []rule.RuleFix { return []rule.RuleFix{insertVoidFix()} })
return
}

Expand All @@ -251,7 +251,7 @@ var NoConfusingVoidExpressionRule = rule.Rule{
fixes = append(fixes, rule.RuleFixReplaceRange(returnToken, replaceText))
}

ctx.ReportNodeWithFixes(node, buildInvalidVoidExprReturnLastMessage(), fixes...)
ctx.ReportNodeWithFixes(node, buildInvalidVoidExprReturnLastMessage(), func() []rule.RuleFix { return fixes })
return
}

Expand All @@ -272,14 +272,16 @@ var NoConfusingVoidExpressionRule = rule.Rule{
rule.RuleFixInsertAfter(invalidAncestor, " }"),
)
}
ctx.ReportNodeWithFixes(node, buildInvalidVoidExprReturnMessage(), fixes...)
ctx.ReportNodeWithFixes(node, buildInvalidVoidExprReturnMessage(), func() []rule.RuleFix { return fixes })
return
}

if opts.IgnoreVoidOperator {
ctx.ReportNodeWithSuggestions(node, buildInvalidVoidExprWrapVoidMessage(), rule.RuleSuggestion{
Message: buildVoidExprWrapVoidMessage(),
FixesArr: []rule.RuleFix{insertVoidFix()},
ctx.ReportNodeWithSuggestions(node, buildInvalidVoidExprWrapVoidMessage(), func() []rule.RuleSuggestion {
return []rule.RuleSuggestion{{
Message: buildVoidExprWrapVoidMessage(),
FixesArr: []rule.RuleFix{insertVoidFix()},
}}
})
return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ var NoDuplicateTypeConstituentsRule = rule.Rule{
}
}
}
ctx.ReportNodeWithFixes(constituentNode, message, fixes...)
ctx.ReportNodeWithFixes(constituentNode, message, func() []rule.RuleFix { return fixes })
}

var checkDuplicateRecursively func(
Expand Down
41 changes: 24 additions & 17 deletions internal/rules/no_floating_promises/no_floating_promises.go
Original file line number Diff line number Diff line change
Expand Up @@ -410,20 +410,25 @@ var NoFloatingPromisesRule = rule.Rule{
msg = buildFloatingVoidMessage()
}

ctx.ReportNodeWithSuggestions(node, msg, rule.RuleSuggestion{
Message: buildFloatingFixVoidMessage(),
FixesArr: (func() []rule.RuleFix {
if isHigherPrecedenceThanUnary(exprStatement.Expression) {
return []rule.RuleFix{rule.RuleFixInsertBefore(ctx.SourceFile, node, "void ")}
}
return []rule.RuleFix{
rule.RuleFixInsertBefore(ctx.SourceFile, node, "void ("),
rule.RuleFixInsertAfter(expression, ")"),
}
})(),
}, rule.RuleSuggestion{
Message: buildFloatingFixAwaitMessage(),
FixesArr: addAwait(expression, exprStatement),
ctx.ReportNodeWithSuggestions(node, msg, func() []rule.RuleSuggestion {
return []rule.RuleSuggestion{
{
Message: buildFloatingFixVoidMessage(),
FixesArr: func() []rule.RuleFix {
if isHigherPrecedenceThanUnary(exprStatement.Expression) {
return []rule.RuleFix{rule.RuleFixInsertBefore(ctx.SourceFile, node, "void ")}
}
return []rule.RuleFix{
rule.RuleFixInsertBefore(ctx.SourceFile, node, "void ("),
rule.RuleFixInsertAfter(expression, ")"),
}
}(),
},
{
Message: buildFloatingFixAwaitMessage(),
FixesArr: addAwait(expression, exprStatement),
},
}
})
} else {
var msg rule.RuleMessage
Expand All @@ -432,9 +437,11 @@ var NoFloatingPromisesRule = rule.Rule{
} else {
msg = buildFloatingMessage()
}
ctx.ReportNodeWithSuggestions(node, msg, rule.RuleSuggestion{
Message: buildFloatingFixAwaitMessage(),
FixesArr: addAwait(expression, exprStatement),
ctx.ReportNodeWithSuggestions(node, msg, func() []rule.RuleSuggestion {
return []rule.RuleSuggestion{{
Message: buildFloatingFixAwaitMessage(),
FixesArr: addAwait(expression, exprStatement),
}}
})
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,13 @@ var NoMeaninglessVoidOperatorRule = rule.Rule{
}

if mask&checker.TypeFlagsVoidLike != 0 {
ctx.ReportNodeWithFixes(node, buildMeaninglessVoidOperatorMessage(ctx.TypeChecker.TypeToString(argType)), fixRemoveVoidKeyword())
ctx.ReportNodeWithFixes(node, buildMeaninglessVoidOperatorMessage(ctx.TypeChecker.TypeToString(argType)), func() []rule.RuleFix { return []rule.RuleFix{fixRemoveVoidKeyword()} })
} else if *opts.CheckNever && mask&checker.TypeFlagsNever != 0 {
ctx.ReportNodeWithSuggestions(node, buildMeaninglessVoidOperatorMessage(ctx.TypeChecker.TypeToString(argType)), rule.RuleSuggestion{
Message: buildRemoveVoidMessage(),
FixesArr: []rule.RuleFix{fixRemoveVoidKeyword()},
ctx.ReportNodeWithSuggestions(node, buildMeaninglessVoidOperatorMessage(ctx.TypeChecker.TypeToString(argType)), func() []rule.RuleSuggestion {
return []rule.RuleSuggestion{{
Message: buildRemoveVoidMessage(),
FixesArr: []rule.RuleFix{fixRemoveVoidKeyword()},
}}
})
}
},
Expand Down
10 changes: 6 additions & 4 deletions internal/rules/no_misused_spread/no_misused_spread.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,9 +227,11 @@ var NoMisusedSpreadRule = rule.Rule{
}

if isPromise(ctx.Program, ctx.TypeChecker, t) {
ctx.ReportNodeWithSuggestions(node, buildNoPromiseSpreadInObjectMessage(), rule.RuleSuggestion{
Message: buildAddAwaitMessage(),
FixesArr: insertAwaitFix(ast.SkipParentheses(argument)),
ctx.ReportNodeWithSuggestions(node, buildNoPromiseSpreadInObjectMessage(), func() []rule.RuleSuggestion {
return []rule.RuleSuggestion{{
Message: buildAddAwaitMessage(),
FixesArr: insertAwaitFix(ast.SkipParentheses(argument)),
}}
})

return
Expand All @@ -242,7 +244,7 @@ var NoMisusedSpreadRule = rule.Rule{
}

if isMap(ctx.Program, ctx.TypeChecker, t) {
ctx.ReportNodeWithSuggestions(node, buildNoMapSpreadInObjectMessage(), getMapSpreadSuggestions(node, argument, t)...)
ctx.ReportNodeWithSuggestions(node, buildNoMapSpreadInObjectMessage(), func() []rule.RuleSuggestion { return getMapSpreadSuggestions(node, argument, t) })

return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ var NoUnnecessaryBooleanLiteralCompareRule = rule.Rule{
fixes = append(fixes, rule.RuleFixInsertBefore(ctx.SourceFile, mutatedNode, "("), rule.RuleFixInsertAfter(mutatedNode, " ?? true)"))
}

ctx.ReportNodeWithFixes(node, msg, fixes...)
ctx.ReportNodeWithFixes(node, msg, func() []rule.RuleFix { return fixes })
},
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ var NoUnnecessaryTypeArgumentsRule = rule.Rule{
} else {
removeRange = arg.Loc.WithPos(arguments.Nodes[i-1].End())
}
ctx.ReportNodeWithFixes(arg, buildUnnecessaryTypeParameterMessage(), rule.RuleFixRemoveRange(removeRange))
ctx.ReportNodeWithFixes(arg, buildUnnecessaryTypeParameterMessage(), func() []rule.RuleFix { return []rule.RuleFix{rule.RuleFixRemoveRange(removeRange)} })

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,15 +222,19 @@ var NoUnnecessaryTypeAssertionRule = rule.Rule{
s := scanner.GetScannerForSourceFile(ctx.SourceFile, expression.End())
asKeywordRange := s.TokenRange()

ctx.ReportNodeWithFixes(node, msg, rule.RuleFixRemoveRange(asKeywordRange), rule.RuleFixRemove(ctx.SourceFile, typeNode))
ctx.ReportNodeWithFixes(node, msg, func() []rule.RuleFix {
return []rule.RuleFix{rule.RuleFixRemoveRange(asKeywordRange), rule.RuleFixRemove(ctx.SourceFile, typeNode)}
})
} else {
s := scanner.GetScannerForSourceFile(ctx.SourceFile, node.Pos())
openingAngleBracket := s.TokenRange()
s.ResetPos(typeNode.End())
s.Scan()
closingAngleBracket := s.TokenRange()

ctx.ReportNodeWithFixes(node, msg, rule.RuleFixRemoveRange(openingAngleBracket.WithEnd(closingAngleBracket.End())))
ctx.ReportNodeWithFixes(node, msg, func() []rule.RuleFix {
return []rule.RuleFix{rule.RuleFixRemoveRange(openingAngleBracket.WithEnd(closingAngleBracket.End()))}
})
}
// TODO - add contextually unnecessary check for this
}
Expand All @@ -249,7 +253,7 @@ var NoUnnecessaryTypeAssertionRule = rule.Rule{

if ast.IsAssignmentExpression(node.Parent, true) {
if node.Parent.AsBinaryExpression().Left == node {
ctx.ReportNodeWithFixes(node, buildContextuallyUnnecessaryMessage(), buildRemoveExclamationFix())
ctx.ReportNodeWithFixes(node, buildContextuallyUnnecessaryMessage(), func() []rule.RuleFix { return []rule.RuleFix{buildRemoveExclamationFix()} })
}
// for all other = assignments we ignore non-null checks
// this is because non-null assertions can change the type-flow of the code
Expand All @@ -272,7 +276,7 @@ var NoUnnecessaryTypeAssertionRule = rule.Rule{
if ast.IsIdentifier(expression) && isPossiblyUsedBeforeAssigned(expression) {
return
}
ctx.ReportNodeWithFixes(node, buildUnnecessaryAssertionMessage(), buildRemoveExclamationFix())
ctx.ReportNodeWithFixes(node, buildUnnecessaryAssertionMessage(), func() []rule.RuleFix { return []rule.RuleFix{buildRemoveExclamationFix()} })
} else {
// we know it's a nullable type
// so figure out if the variable is used in a place that accepts nullable types
Expand Down Expand Up @@ -304,7 +308,7 @@ var NoUnnecessaryTypeAssertionRule = rule.Rule{
isValidVoid := !typeIncludesVoid || contextualTypeIncludesVoid

if isValidUndefined && isValidNull && isValidVoid {
ctx.ReportNodeWithFixes(node, buildContextuallyUnnecessaryMessage(), buildRemoveExclamationFix())
ctx.ReportNodeWithFixes(node, buildContextuallyUnnecessaryMessage(), func() []rule.RuleFix { return []rule.RuleFix{buildRemoveExclamationFix()} })
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,13 @@ var NonNullableTypeAssertionStyleRule = rule.Rule{
removeRange = node.Loc.WithEnd(expression.Pos())
}
if higherPrecedenceThanUnary {
ctx.ReportNodeWithFixes(node, buildPreferNonNullAssertionMessage(), rule.RuleFixRemoveRange(removeRange), rule.RuleFixInsertAfter(expression, "!"))
ctx.ReportNodeWithFixes(node, buildPreferNonNullAssertionMessage(), func() []rule.RuleFix {
return []rule.RuleFix{rule.RuleFixRemoveRange(removeRange), rule.RuleFixInsertAfter(expression, "!")}
})
} else {
ctx.ReportNodeWithFixes(node, buildPreferNonNullAssertionMessage(), rule.RuleFixRemoveRange(removeRange), rule.RuleFixInsertBefore(ctx.SourceFile, expression, "("), rule.RuleFixInsertAfter(expression, ")!"))
ctx.ReportNodeWithFixes(node, buildPreferNonNullAssertionMessage(), func() []rule.RuleFix {
return []rule.RuleFix{rule.RuleFixRemoveRange(removeRange), rule.RuleFixInsertBefore(ctx.SourceFile, expression, "("), rule.RuleFixInsertAfter(expression, ")!")}
})
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ var PreferReduceTypeParameterRule = rule.Rule{
if expr.TypeArguments == nil {
fixes = append(fixes, rule.RuleFixInsertAfter(callee, "<"+ctx.SourceFile.Text()[assertionType.Pos():assertionType.End()]+">"))
}
ctx.ReportNodeWithFixes(secondArg, buildPreferTypeParameterMessage(), fixes...)
ctx.ReportNodeWithFixes(secondArg, buildPreferTypeParameterMessage(), func() []rule.RuleFix { return fixes })
},
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ var PreferReturnThisTypeRule = rule.Rule{
}
}

ctx.ReportNodeWithFixes(node, buildUseThisTypeMessage(), rule.RuleFixReplace(ctx.SourceFile, node, "this"))
ctx.ReportNodeWithFixes(node, buildUseThisTypeMessage(), func() []rule.RuleFix { return []rule.RuleFix{rule.RuleFixReplace(ctx.SourceFile, node, "this")} })
}

return rule.RuleListeners{
Expand Down
Loading