Use DMD in NumberStyleCheck (#88)

* Replace libdparse with DMD in NumberStyleCheck

* Fix re-lexing for windows (CRLF terminators) files

* Improve unit test
This commit is contained in:
Vladiwostok 2024-03-05 10:53:12 +02:00 committed by Vladiwostok
parent 860ddf1994
commit 8b7612d76a
2 changed files with 64 additions and 39 deletions

View File

@ -5,60 +5,83 @@
module dscanner.analysis.numbers; module dscanner.analysis.numbers;
import std.stdio;
import std.regex;
import dparse.ast;
import dparse.lexer;
import dscanner.analysis.base; import dscanner.analysis.base;
import dscanner.analysis.helpers; import dmd.tokens : TOK;
import dsymbol.scope_ : Scope; import std.conv;
import std.regex;
/** /**
* Checks for long and hard-to-read number literals * Checks for long and hard-to-read number literals
*/ */
final class NumberStyleCheck : BaseAnalyzer extern (C++) class NumberStyleCheck(AST) : BaseAnalyzerDmd
{ {
public: alias visit = BaseAnalyzerDmd.visit;
alias visit = BaseAnalyzer.visit;
mixin AnalyzerInfo!"number_style_check"; mixin AnalyzerInfo!"number_style_check";
/** private enum KEY = "dscanner.style.number_literals";
* Constructs the style checker with the given file name. private enum string MSG = "Use underscores to improve number constant readability.";
*/
this(BaseAnalyzerArguments args) private auto badBinaryRegex = ctRegex!(`^0b[01]{9,}`);
private auto badDecimalRegex = ctRegex!(`^\d{5,}`);
extern (D) this(string fileName, bool skipTests = false)
{ {
super(args); super(fileName, skipTests);
} }
override void visit(const Token t) override void visit(AST.IntegerExp intExpr)
{ {
import std.algorithm : startsWith; import dscanner.utils : readFile;
import dmd.errorsink : ErrorSinkNull;
import dmd.globals : global;
import dmd.lexer : Lexer;
if (isNumberLiteral(t.type) && !t.text.startsWith("0x") auto bytes = readFile(fileName) ~ '\0';
&& ((t.text.startsWith("0b") && !t.text.matchFirst(badBinaryRegex) bytes = bytes[intExpr.loc.fileOffset .. $];
.empty) || !t.text.matchFirst(badDecimalRegex).empty))
__gshared ErrorSinkNull errorSinkNull;
if (!errorSinkNull)
errorSinkNull = new ErrorSinkNull;
scope lexer = new Lexer(null, cast(char*) bytes, 0, bytes.length, 0, 0, errorSinkNull, &global.compileEnv);
auto tokenValue = lexer.nextToken();
bool isInt = false;
while (tokenValue != TOK.semicolon && tokenValue != TOK.endOfFile)
{ {
addErrorMessage(t, KEY, if (isIntegerLiteral(tokenValue))
"Use underscores to improve number constant readability."); {
isInt = true;
break;
}
tokenValue = lexer.nextToken();
} }
if (!isInt)
return;
auto tokenText = to!string(lexer.token.ptr);
if (!matchFirst(tokenText, badDecimalRegex).empty || !matchFirst(tokenText, badBinaryRegex).empty)
addErrorMessage(intExpr.loc.linnum, intExpr.loc.charnum, KEY, MSG);
} }
private: private bool isIntegerLiteral(TOK token)
{
enum string KEY = "dscanner.style.number_literals"; return token >= TOK.int32Literal && token <= TOK.uns128Literal;
}
auto badBinaryRegex = ctRegex!(`^0b[01]{9,}`);
auto badDecimalRegex = ctRegex!(`^\d{5,}`);
} }
unittest unittest
{ {
import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig; import dscanner.analysis.config : StaticAnalysisConfig, Check, disabledConfig;
import dscanner.analysis.helpers : assertAnalyzerWarningsDMD;
import std.stdio : stderr;
StaticAnalysisConfig sac = disabledConfig(); StaticAnalysisConfig sac = disabledConfig();
sac.number_style_check = Check.enabled; sac.number_style_check = Check.enabled;
assertAnalyzerWarnings(q{ assertAnalyzerWarningsDMD(q{
void testNumbers() void testNumbers()
{ {
int a; int a;
@ -66,12 +89,12 @@ unittest
a = 10; // ok a = 10; // ok
a = 100; // ok a = 100; // ok
a = 1000; // ok a = 1000; // ok
a = 10000; /+ a = 10_00; // ok
^^^^^ [warn]: Use underscores to improve number constant readability. +/ a = 10_000; // ok
a = 100000; /+ a = 100_000; // ok
^^^^^^ [warn]: Use underscores to improve number constant readability. +/ a = 10000; // [warn]: Use underscores to improve number constant readability.
a = 1000000; /+ a = 100000; // [warn]: Use underscores to improve number constant readability.
^^^^^^^ [warn]: Use underscores to improve number constant readability. +/ a = 1000000; // [warn]: Use underscores to improve number constant readability.
} }
}c, sac); }c, sac);

View File

@ -855,10 +855,6 @@ private BaseAnalyzer[] getAnalyzersForModuleAndConfig(string fileName,
checks ~= new MismatchedArgumentCheck(args.setSkipTests( checks ~= new MismatchedArgumentCheck(args.setSkipTests(
analysisConfig.mismatched_args_check == Check.skipTests && !ut)); analysisConfig.mismatched_args_check == Check.skipTests && !ut));
if (moduleName.shouldRun!NumberStyleCheck(analysisConfig))
checks ~= new NumberStyleCheck(args.setSkipTests(
analysisConfig.number_style_check == Check.skipTests && !ut));
if (moduleName.shouldRun!StyleChecker(analysisConfig)) if (moduleName.shouldRun!StyleChecker(analysisConfig))
checks ~= new StyleChecker(args.setSkipTests( checks ~= new StyleChecker(args.setSkipTests(
analysisConfig.style_check == Check.skipTests && !ut)); analysisConfig.style_check == Check.skipTests && !ut));
@ -1345,6 +1341,12 @@ MessageSet analyzeDmd(string fileName, ASTCodegen.Module m, const char[] moduleN
config.redundant_storage_classes == Check.skipTests && !ut config.redundant_storage_classes == Check.skipTests && !ut
); );
if (moduleName.shouldRunDmd!(NumberStyleCheck!ASTCodegen)(config))
visitors ~= new NumberStyleCheck!ASTCodegen(
fileName,
config.number_style_check == Check.skipTests && !ut
);
foreach (visitor; visitors) foreach (visitor; visitors)
{ {
m.accept(visitor); m.accept(visitor);