This repository has been archived by the owner on Nov 11, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 34
/
Warning.scala
406 lines (400 loc) · 21.4 KB
/
Warning.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
/**
* Copyright 2012 Foursquare Labs, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.psywerx.hairyfotr
sealed abstract class Warning(val message: String) {
def name: String = toString.takeWhile(_ != '(')
}
final object Warning {
val All = Seq[Warning](
AssigningOptionToNull,
AvoidOptionCollectionSize,
AvoidOptionMethod("", ""),
AvoidOptionStringSize,
BigDecimalNumberFormat,
BigDecimalPrecisionLoss,
CloseSourceFile,
ContainsTypeMismatch("", ""),
NumberInstanceOf(""),
DecomposingEmptyCollection("", ""),
ModuloByOne,
DivideByOne,
DivideByZero,
ZeroDivideBy,
DuplicateIfBranches,
DuplicateKeyInMap,
TransformNotMap(""),
IdenticalCaseBodies(""),
IdenticalCaseConditions,
IdenticalIfCondition,
IdenticalIfElseCondition,
IdenticalStatements,
IndexingWithNegativeNumber,
InefficientUseOfListSize("", "", ""),
IntDivisionAssignedToFloat,
InvalidParamToRandomNextInt,
InvalidStringConversion(""),
InvalidStringFormat(""),
InvariantCondition(true, ""),
InvariantExtrema(true, true),
InvariantReturn("", ""),
JavaConverters,
LikelyIndexOutOfBounds(""),
MalformedSwap,
MergeNestedIfs,
OnceEvaluatedStatementsInBlockReturningFunction,
OperationAlwaysProducesZero(""),
OptionOfOption,
PassPartialFunctionDirectly(""),
UnitImplicitOrdering(""),
PatternMatchConstant,
PossibleLossOfPrecision(""),
PreferIfToBooleanMatch,
ProducesEmptyCollection,
ReflexiveAssignment,
ReflexiveComparison,
RegexWarning("", true),
StringMultiplicationByNonPositive,
UndesirableTypeInference(""),
UnextendedSealedTrait,
UnlikelyEquality("lhs", "rhs", "=="),
UnnecessaryElseBranch,
UnnecessaryMethodCall(""),
UnnecessaryReturn,
UnnecessaryStringIsEmpty,
UnnecessaryStringNonEmpty,
UnusedForLoopIteratorValue,
UnusedParameter(Seq("unusedParameter"), "methodName"),
UseHypot,
UseLog1p,
UseLog10,
UseCbrt,
UseSqrt,
SuspiciousPow(0),
UseExp,
UseExpm1,
UseAbsNotSqrtSquare,
UseConditionDirectly(true),
UseIfExpression(""),
MergeMaps,
FuncFirstThenMap(""),
FilterFirstThenSort,
UseMinOrMaxNotSort("", "", "", ""),
UseMapNotFlatMap(""),
UseFilterNotFlatMap(""),
UseFlattenNotFilterOption("", "", ""),
UseCountNotFilterLength("", ""),
UseExistsNotCountCompare(""),
UseGetOrElseOnOption(""),
UseExistsNotFindIsDefined("", ("", false), ""),
UseExistsNotFilterIsEmpty("", Nil, false, "", ""),
UseFindNotFilterHead(""),
UseContainsNotExistsEquals("", "", "", ""),
UseQuantifierFuncNotFold("", "", ""),
UseFuncNotReduce("", "", ""),
UseFuncNotFold("", "", ""),
UseFuncNotReverse("", ""),
UseHeadNotApply(""),
UseHeadOptionNotIf(""),
UseInitNotReverseTailReverse(""),
UseTakeRightNotReverseTakeReverse(""),
UseLastNotApply(""),
UseLastNotReverseHead("", true),
UseLastOptionNotIf(""),
UseIsNanNotNanComparison,
UseIsNanNotSelfComparison,
UseOptionGetOrElse("", ""),
UseOptionOrNull("", ""),
UseSignum,
UseUntilNotToMinusOne,
UseZipWithIndexNotZipIndices(""),
VariableAssignedUnusedValue(""),
WrapNullWithOption,
YodaConditions,
UnsafeAbs(""),
TypeToType(""),
EmptyStringInterpolator,
UnlikelyToString(""),
UnthrownException,
SuspiciousMatches(""),
IfDoWhile,
FloatingPointNumericRange,
PassingNullIntoOption,
UseGetOrElseNotPatMatch(""),
UseOrElseNotPatMatch(""),
UseOptionFlatMapNotPatMatch(""),
UseOptionMapNotPatMatch(""),
UseOptionFlattenNotPatMatch,
UseOptionForeachNotPatMatch(""),
UseOptionIsDefinedNotPatMatch,
UseOptionIsEmptyNotPatMatch,
UseOptionForallNotPatMatch(""),
UseOptionExistsNotPatMatch("")
)
val AllNames = All.map(_.name)
val NameToWarning: Map[String, Warning] = All.map(w => w.name -> w).toMap
}
case object UnextendedSealedTrait extends
Warning("This sealed trait is never extended")
case class UnlikelyEquality(lhs: String, rhs: String, op: String) extends
Warning(s"Comparing with $op on instances of unrelated types ${(lhs, rhs).toString}.")
case object UseLog1p extends
Warning("Use math.log1p(x), instead of math.log(1 + x) for added accuracy when x is near 0.")
case object UseLog10 extends
Warning("Use math.log10(x), instead of log(x)/log(10) for improved accuracy.")
case object UseExpm1 extends
Warning("Use math.expm1(x), instead of math.exp(x) - 1 for added accuracy when x is near 0.")
case object UseHypot extends
Warning("Use math.hypot(x, y), instead of sqrt(x^2 + y^2) for improved accuracy (but diminished performance).")
case object UseCbrt extends
Warning("Use math.cbrt(x), instead of pow(x, 1/3) for improved accuracy and performance.")
case object UseSqrt extends
Warning("Use math.sqrt(x), instead of pow(x, 1/2) for improved accuracy and performance.")
case class SuspiciousPow(pow: Double) extends
Warning(s"This use of pow is suspicious, since the power is $pow.")
case object UseExp extends
Warning("Use math.exp(x), instead of pow(E, x) for improved performance.")
case object UseAbsNotSqrtSquare extends
Warning("Use math.abs(x) instead of math.sqrt(x^2).")
case object UseIsNanNotSelfComparison extends
Warning("Use x.isNan instead of comparing x to itself.") // Turn all these x-es into varName
case object UseIsNanNotNanComparison extends
Warning("Use x.isNan instead of comparing it to NaN (NaN == NaN is false, so this is always wrong).") // Error
case object UseSignum extends
Warning("Did you mean to use the signum function here? (signum also avoids division by zero exceptions).")
case object BigDecimalNumberFormat extends
Warning("This BigDecimal constructor will likely throw a NumberFormatException.") // Likely Exception
case object BigDecimalPrecisionLoss extends
Warning("Possible loss of precision - use a string constant.")
case object ReflexiveAssignment extends
Warning("Assigning a variable to itself?")
case object CloseSourceFile extends
Warning("You should close the file stream after use. (Streams get garbage collected, but it is possible to open too many at once)")
case object JavaConverters extends
Warning("Consider using the explicit collection.JavaConverters instead of implicit conversions in collection.JavaConversions.")
case class ContainsTypeMismatch(seqType: String, targetType: String) extends
Warning(s"$seqType.contains($targetType) will probably return false, since the collection and target element are of unrelated types.")
case class NumberInstanceOf(tpe: String) extends
Warning(s"Use to$tpe instead of asInstanceOf[$tpe].")
case object PatternMatchConstant extends
Warning("Pattern matching on a constant value.")
case object PreferIfToBooleanMatch extends
Warning("Pattern matching on Boolean is probably better written as an if statement.")
case class IdenticalCaseBodies(n: String) extends
Warning(s"Bodies of $n neighbouring cases are identical and could be merged.")
case object IdenticalCaseConditions extends
Warning("Identical case condition detected above. This case will never match.") // Why doesn't compiler catch this?
case object ReflexiveComparison extends
Warning("Same expression on both sides of the comparison.")
case object YodaConditions extends
Warning("Yoda conditions using you are.")
case class UseConditionDirectly(negated: Boolean = false) extends
Warning(s"""Remove the if expression and use the ${if (negated) "negated " else ""}condition directly.""")
case class UseIfExpression(varName: String) extends
Warning(s"Assign the result of the if expression to variable $varName directly.")
case object UnnecessaryElseBranch extends
Warning("Since the if branch always returns, the code from the else branch can be moved out to reduce nesting.")
case object DuplicateIfBranches extends
Warning("If statement branches have the same structure.")
case object IdenticalIfElseCondition extends
Warning("This condition has appeared earlier in the if-else chain and will never hold here. (except for side-effecting conditions)")
case object MergeNestedIfs extends
Warning("These two nested ifs can be merged into one.")
case class VariableAssignedUnusedValue(varName: String) extends
Warning(s"Variable $varName has an unused value before this reassign.")
case object MalformedSwap extends
Warning("Did you mean to swap these two variables?")
case object IdenticalIfCondition extends
Warning("Two subsequent ifs have the same condition. (check if value has changed in between)")
case object IdenticalStatements extends
Warning("You're doing the exact same thing twice or more.")
case object IndexingWithNegativeNumber extends
Warning("Using a negative index for a collection.")
case object OptionOfOption extends
Warning("Why would you need an Option of an Option?")
case class UndesirableTypeInference(inferredType: String) extends
Warning(s"Inferred type $inferredType. (This might not be what you've intended)")
case object AssigningOptionToNull extends
Warning("You probably meant None, not null.")
case object WrapNullWithOption extends
Warning("Replace if with Option(...), which automatically wraps null to None.")
case object AvoidOptionStringSize extends
Warning("Did you mean to take the size of the string inside the Option?")
case object AvoidOptionCollectionSize extends
Warning("Did you mean to take the size of the collection inside the Option?")
case class UseGetOrElseOnOption(varname: String) extends
Warning(s"Use $varname.getOrElse(...) instead of $varname.orElse(Some(...)).get.")
case class UseOptionOrNull(varname: String, insteadOf: String) extends
Warning(s"Use $varname.orNull or $varname.getOrElse(null) instead of if ($insteadOf) $varname.get else null.")
case class UseOptionGetOrElse(varname: String, insteadOf: String) extends
Warning(s"Use $varname.getOrElse(...) instead of if ($insteadOf) $varname.get else ...")
case class UseExistsNotFindIsDefined(varName: String, replacement: (String, Boolean), isEmpty_isDefined: String) extends
Warning(s"""Use ${Utils.toBang(replacement._2)}$varName.${replacement._1}(...) instead of $varName.find(...).$isEmpty_isDefined.""")
case class UseExistsNotFilterIsEmpty(varName: String, replacements: Seq[(String, Boolean, Boolean)], stmtNegated: Boolean, filter: String, empty: String) extends
Warning(s"""Use ${
replacements.map{
case (rep, negStmt, negCond) => s"${Utils.toBang(negStmt)}$varName.$rep(${Utils.toBang(negCond)}...)"
}.mkString(" or ")
} instead of ${Utils.toBang(stmtNegated)}$varName.$filter(...).$empty.""")
case class UseFindNotFilterHead(varName: String) extends
Warning(s"Unless there are side-effects, $varName.filter(...).headOption can be replaced by $varName.find(...).")
case class UseContainsNotExistsEquals(colName: String, valCmp: String, val1: String, val2: String) extends
Warning(s"Use $colName.contains($valCmp) instead of $colName.exists($val1 == $val2)")
case class UseQuantifierFuncNotFold(varName: String, method: String, func: String) extends
Warning(s"Unless there are side-effects, this $varName.$func can be replaced by $varName.$method.")
case class UseFuncNotReduce(varName: String, f: String, func: String) extends
Warning(s"Use $varName.$f instead of $varName.$func.")
case class UseFuncNotFold(varName: String, f: String, func: String) extends
Warning(s"Use $varName.$f instead of $varName.$func.")
case object MergeMaps extends
Warning("Merge these two map operations.")
case class FuncFirstThenMap(methName: String) extends
Warning(s"Use method $methName first, then map.")
case object FilterFirstThenSort extends
Warning("Filter collection first, then sort it.")
case class UseMinOrMaxNotSort(colName: String, sortFunc: String, op: String, replacement: String) extends
Warning(s"Use $colName.$replacement instead of $colName.$sortFunc.$op.")
case class UseMapNotFlatMap(varName: String) extends
Warning(s"Use $varName.map(x => if (...) y else z) instead of $varName.flatMap(x => if (...) Collection(y) else Collection(z)).") // Clean up warning
case class UseFilterNotFlatMap(varName: String) extends
Warning(s"Use $varName.filter(x => condition) instead of $varName.flatMap(x => if (condition) ... else ...).") // Clean up warning
case class AvoidOptionMethod(method: String, explanation: String = "") extends
Warning(s"Using Option.$method is not recommended. $explanation")
case class TransformNotMap(varName: String) extends
Warning(s"Use $varName.transform(...) instead of col = $varName.map(...).")
case object DuplicateKeyInMap extends
Warning("This key has already been defined, and will override the previous mapping.")
case class InefficientUseOfListSize(varName: String, replacement: String, func: String) extends
Warning(s"Use $varName.$replacement instead of comparing to $varName.$func. ($varName is a List, $func takes O(n) time)")
case object OnceEvaluatedStatementsInBlockReturningFunction extends
Warning("You're passing a block that returns a function. The statements in this block, except the last one, will only be executed once.")
case object IntDivisionAssignedToFloat extends
Warning("Integer division detected in an expression assigned to a floating point variable.")
case class UseFlattenNotFilterOption(varName: String, func1: String, func2: String) extends
Warning(s"Use $varName.flatten instead of $varName.$func1(_.$func2).map(_.get).")
case class UseCountNotFilterLength(varName: String, func: String) extends
Warning(s"Use $varName.count(...) instead of $varName.filter(...).$func")
case class UseExistsNotCountCompare(varName: String) extends
Warning(s"Use $varName.exists(...) instead of $varName.count(...) compare.")
case class PassPartialFunctionDirectly(matchVar: String) extends
Warning(s"You can pass the partial function in directly. (Remove `$matchVar match {`).")
case class UnitImplicitOrdering(function: String) extends
Warning(s"Unit is returned here, so this $function will always return the first element.")
case class RegexWarning(errorMessage: String, error: Boolean = true) extends
Warning("Regex pattern "+(if (error) "syntax error" else "warning")+s": $errorMessage.")
case class InvariantCondition(always: Boolean, doWhat: String) extends
Warning(s"""This condition will ${ if (always) "always" else "never" } $doWhat.""")
case class DecomposingEmptyCollection(method: String, collection: String = "collection") extends
Warning(s"Taking the $method of an empty $collection.")
case class InvariantExtrema(max: Boolean, returnsFirst: Boolean) extends
Warning(s"""This ${ if (max) "max" else "min" } will always return the ${ if (returnsFirst) "first" else "second" } value""")
case class UnnecessaryMethodCall(method: String) extends
Warning(s"This $method is always unnecessary.")
case object ProducesEmptyCollection extends
Warning("The resulting collection will always be empty.")
case class OperationAlwaysProducesZero(operation: String) extends //TODO: merge with InvariantReturn?
Warning(s"This $operation will always return 0.")
case object ModuloByOne extends
Warning("Taking the modulo by one will return zero.")
case object DivideByOne extends
Warning("Dividing by one will return the original number.")
case object DivideByZero extends
Warning("Possible division by zero.")
case object ZeroDivideBy extends
Warning("Division of zero will return zero.")
case object UseUntilNotToMinusOne extends
Warning("Use (low until high) instead of (low to high-1).")
case object InvalidParamToRandomNextInt extends
Warning("The parameter of this .nextInt might be lower than 1 here.") // Likely Exception
case object UnusedForLoopIteratorValue extends
Warning("Iterator value is not used in the for loop's body.")
case object StringMultiplicationByNonPositive extends
Warning("Multiplying a string with a value <= 0 will result in an empty string.")
case class LikelyIndexOutOfBounds(direction: String) extends
Warning(s"You will likely use a $direction index.")
case object UnnecessaryReturn extends
Warning("Scala has implicit return. You don't need a return statement at the end of a method.")
case class InvariantReturn(structure: String, returnValue: String) extends
Warning(s"This $structure always returns the same value: $returnValue.")
case class UnusedParameter(parameters: Seq[String], method: String) extends
Warning("Parameter" +
(parameters match { case Seq(p) => " " + p + " is" case _ => "s (" + parameters.mkString(", ") + ") are" }) +
s" not used in method $method.")
case class InvalidStringFormat(errorMessage: String, exception: Boolean = true) extends
Warning(if (exception) s"This string format will throw: $errorMessage" else s"$errorMessage") // Likely Exception
case class InvalidStringConversion(conversionType: String) extends
Warning(s"This String $conversionType conversion will likely fail.") // Likely Exception
case object UnnecessaryStringNonEmpty extends
Warning("This string will never be empty.")
case object UnnecessaryStringIsEmpty extends
Warning("This string will always be empty.")
case class PossibleLossOfPrecision(improvement: String) extends
Warning(s"Possible loss of precision. $improvement")
case class UnsafeAbs(improvement: String) extends
Warning(s"Possibly unsafe use of abs. $improvement.")
case class TypeToType(tpe: String) extends
Warning(s"Using to$tpe on something that is already of type $tpe.")
case object EmptyStringInterpolator extends
Warning("This string interpolation has no arguments.")
case class UnlikelyToString(tpe: String) extends
Warning(s"Turning object of type $tpe into string is likely unintended.")
case object UnthrownException extends
Warning("This exception was likely meant to be thrown here.")
case class SuspiciousMatches(msg: String) extends
Warning(msg)
case object PassingNullIntoOption extends
Warning("Passing null into an Option parameter is likely wrong.")
case object IfDoWhile extends
Warning("The if and the do-while loop have the same condition. Use a while loop.")
case object FloatingPointNumericRange extends
Warning("Avoid NumericRange with floating point numbers, as results may differ depending on which methods are used to materialize it (apply vs. foreach).")
case class UseInitNotReverseTailReverse(varName: String) extends
Warning(s"$varName.reverse.tail.reverse can be replaced by $varName.init.")
case class UseTakeRightNotReverseTakeReverse(varName: String) extends
Warning(s"$varName.reverse.take(...).reverse can be replaced by $varName.takeRight(...).")
case class UseLastNotReverseHead(varName: String, option: Boolean) extends
Warning(s"""$varName.reverse.head${if (option) "Option" else ""} can be replaced by $varName.last${if (option) "Option" else ""}.""")
case class UseFuncNotReverse(varName: String, func: String) extends
Warning(s"""$varName.reverse.$func can be replaced by $varName.reverse${func.capitalize}.""")
case class UseHeadNotApply(varName: String) extends
Warning(s"""It is idiomatic to use $varName.head instead of $varName(0) for List""")
case class UseLastNotApply(varName: String) extends
Warning(s"""It is idiomatic to use $varName.last instead of $varName($varName.length - 1) for List""")
case class UseHeadOptionNotIf(varName: String) extends
Warning(s"""if ($varName.nonEmpty) Some($varName.head) else None can be replaced by $varName.headOption""")
case class UseLastOptionNotIf(varName: String) extends
Warning(s"""if ($varName.nonEmpty) Some($varName.last) else None can be replaced by $varName.lastOption""")
case class UseZipWithIndexNotZipIndices(varName: String) extends
Warning(s"""$varName.zip($varName.indices) can be replaced with $varName.zipWithIndex""")
case class UseGetOrElseNotPatMatch(expr: String) extends
Warning(s"""... match { Some(x) => x; None => $expr} can be replaced with .getOrElse($expr)""")
case class UseOrElseNotPatMatch(expr: String) extends
Warning(s"""... match { Some(x) => Some(x); None => $expr} can be replaced with .orElse($expr)""")
case class UseOptionFlatMapNotPatMatch(expr: String) extends
Warning(s"""... match { Some(x) => $expr; None => None} can be replaced with .flatMap($expr)""")
case class UseOptionMapNotPatMatch(expr: String) extends
Warning(s"""... match { Some(x) => Some($expr); None => None} can be replaced with .map($expr)""")
case object UseOptionFlattenNotPatMatch extends
Warning("""... match { Some(x) => x; None => None} can be replaced with .flatten""")
case class UseOptionForeachNotPatMatch(expr: String) extends
Warning(s"""... match { Some(x) => $expr; None => {} } can be replaced with .foreach($expr)""")
case object UseOptionIsDefinedNotPatMatch extends
Warning("""... match { Some(x) => true; None => false} can be replaced with .isDefined""")
case object UseOptionIsEmptyNotPatMatch extends
Warning("""... match { Some(x) => false; None => true} can be replaced with .isEmpty""")
case class UseOptionForallNotPatMatch(expr: String) extends
Warning(s"""... match { Some(x) => true; None => $expr} can be replaced with .forall($expr)""")
case class UseOptionExistsNotPatMatch(expr: String) extends
Warning(s"""... match { Some(x) => false; None => $expr} can be replaced with .exists($expr)""")