mirror of
https://github.com/dlang-community/D-Scanner.git
synced 2025-04-29 14:50:01 +03:00
Replace libdparse with DMD in LineLengthCheck (#134)
* Replace libdparse with DMD in LineLengthCheck * Fix windows line counting * Fix gdc compilation
This commit is contained in:
parent
f95acb4c79
commit
ee6acfb749
2 changed files with 113 additions and 143 deletions
|
@ -6,176 +6,143 @@
|
||||||
module dscanner.analysis.line_length;
|
module dscanner.analysis.line_length;
|
||||||
|
|
||||||
import dscanner.analysis.base;
|
import dscanner.analysis.base;
|
||||||
|
import dmd.tokens : Token, TOK;
|
||||||
import dparse.ast;
|
|
||||||
import dparse.lexer;
|
|
||||||
|
|
||||||
import std.typecons : tuple, Tuple;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks for lines longer than `max_line_length` characters
|
* Checks for lines longer than `max_line_length` characters
|
||||||
*/
|
*/
|
||||||
final class LineLengthCheck : BaseAnalyzer
|
extern (C++) class LineLengthCheck : BaseAnalyzerDmd
|
||||||
{
|
{
|
||||||
mixin AnalyzerInfo!"long_line_check";
|
mixin AnalyzerInfo!"long_line_check";
|
||||||
|
private enum KEY = "dscanner.style.long_line";
|
||||||
|
immutable string msg;
|
||||||
|
|
||||||
///
|
private Token[] tokens;
|
||||||
this(BaseAnalyzerArguments args, int maxLineLength)
|
private immutable int maxLineLength;
|
||||||
|
private uint currentLine = 1;
|
||||||
|
private int currentLineLen;
|
||||||
|
|
||||||
|
extern (D) this(string fileName, bool skipTests = false, int maxLineLength = 120)
|
||||||
{
|
{
|
||||||
super(args);
|
|
||||||
this.maxLineLength = maxLineLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
override void visit(const Module)
|
|
||||||
{
|
|
||||||
size_t endColumn;
|
|
||||||
lastErrorLine = ulong.max;
|
|
||||||
foreach (i, token; tokens)
|
|
||||||
{
|
|
||||||
immutable info = tokenLength(token, i > 0 ? tokens[i - 1].line : 0);
|
|
||||||
if (info.multiLine)
|
|
||||||
endColumn = checkMultiLineToken(token, endColumn);
|
|
||||||
else if (info.newLine)
|
|
||||||
endColumn = info.length + token.column - 1;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
immutable wsChange = i > 0
|
|
||||||
? token.column - (tokens[i - 1].column + tokenByteLength(tokens[i - 1]))
|
|
||||||
: 0;
|
|
||||||
endColumn += wsChange + info.length;
|
|
||||||
}
|
|
||||||
if (endColumn > maxLineLength)
|
|
||||||
triggerError(token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
alias visit = BaseAnalyzer.visit;
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
ulong lastErrorLine = ulong.max;
|
|
||||||
|
|
||||||
void triggerError(ref const Token tok)
|
|
||||||
{
|
|
||||||
import std.algorithm : max;
|
|
||||||
|
|
||||||
if (tok.line != lastErrorLine)
|
|
||||||
{
|
|
||||||
addErrorMessage([0, 0], tok.line, [maxLineLength, max(maxLineLength + 1, tok.column + 1)], KEY, message);
|
|
||||||
lastErrorLine = tok.line;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool isLineSeparator(dchar c)
|
|
||||||
{
|
|
||||||
import std.uni : lineSep, paraSep;
|
|
||||||
return c == lineSep || c == '\n' || c == '\v' || c == '\r' || c == paraSep;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t checkMultiLineToken()(auto ref const Token tok, size_t startColumn = 0)
|
|
||||||
{
|
|
||||||
import std.utf : byDchar;
|
|
||||||
|
|
||||||
auto col = startColumn;
|
|
||||||
foreach (c; tok.text.byDchar)
|
|
||||||
{
|
|
||||||
if (isLineSeparator(c))
|
|
||||||
{
|
|
||||||
if (col > maxLineLength)
|
|
||||||
triggerError(tok);
|
|
||||||
col = 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
col += getEditorLength(c);
|
|
||||||
}
|
|
||||||
return col;
|
|
||||||
}
|
|
||||||
|
|
||||||
unittest
|
|
||||||
{
|
|
||||||
assert(new LineLengthCheck(BaseAnalyzerArguments.init, 120).checkMultiLineToken(Token(tok!"stringLiteral", " ", 0, 0, 0)) == 8);
|
|
||||||
assert(new LineLengthCheck(BaseAnalyzerArguments.init, 120).checkMultiLineToken(Token(tok!"stringLiteral", " \na", 0, 0, 0)) == 2);
|
|
||||||
assert(new LineLengthCheck(BaseAnalyzerArguments.init, 120).checkMultiLineToken(Token(tok!"stringLiteral", " \n ", 0, 0, 0)) == 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
static size_t tokenByteLength()(auto ref const Token tok)
|
|
||||||
{
|
|
||||||
return tok.text is null ? str(tok.type).length : tok.text.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
unittest
|
|
||||||
{
|
|
||||||
assert(tokenByteLength(Token(tok!"stringLiteral", "aaa", 0, 0, 0)) == 3);
|
|
||||||
assert(tokenByteLength(Token(tok!"stringLiteral", "Дистан", 0, 0, 0)) == 12);
|
|
||||||
// tabs and whitespace
|
|
||||||
assert(tokenByteLength(Token(tok!"stringLiteral", " ", 0, 0, 0)) == 1);
|
|
||||||
assert(tokenByteLength(Token(tok!"stringLiteral", " ", 0, 0, 0)) == 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
// D Style defines tabs to have a width of four spaces
|
|
||||||
static size_t getEditorLength(C)(C c)
|
|
||||||
{
|
|
||||||
if (c == '\t')
|
|
||||||
return 4;
|
|
||||||
else
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
alias TokenLength = Tuple!(size_t, "length", bool, "newLine", bool, "multiLine");
|
|
||||||
static TokenLength tokenLength()(auto ref const Token tok, size_t prevLine)
|
|
||||||
{
|
|
||||||
import std.utf : byDchar;
|
|
||||||
|
|
||||||
size_t length;
|
|
||||||
const newLine = tok.line > prevLine;
|
|
||||||
bool multiLine;
|
|
||||||
if (tok.text is null)
|
|
||||||
length += str(tok.type).length;
|
|
||||||
else
|
|
||||||
foreach (c; tok.text.byDchar)
|
|
||||||
{
|
|
||||||
if (isLineSeparator(c))
|
|
||||||
{
|
|
||||||
length = 1;
|
|
||||||
multiLine = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
length += getEditorLength(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
return TokenLength(length, newLine, multiLine);
|
|
||||||
}
|
|
||||||
|
|
||||||
unittest
|
|
||||||
{
|
|
||||||
assert(tokenLength(Token(tok!"stringLiteral", "aaa", 0, 0, 0), 0).length == 3);
|
|
||||||
assert(tokenLength(Token(tok!"stringLiteral", "Дистан", 0, 0, 0), 0).length == 6);
|
|
||||||
// tabs and whitespace
|
|
||||||
assert(tokenLength(Token(tok!"stringLiteral", " ", 0, 0, 0), 0).length == 4);
|
|
||||||
assert(tokenLength(Token(tok!"stringLiteral", " ", 0, 0, 0), 0).length == 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
import std.conv : to;
|
import std.conv : to;
|
||||||
|
|
||||||
string message() const
|
super(fileName, skipTests);
|
||||||
{
|
this.maxLineLength = maxLineLength;
|
||||||
return "Line is longer than " ~ to!string(maxLineLength) ~ " characters";
|
msg = "Line is longer than " ~ to!string(maxLineLength) ~ " characters";
|
||||||
|
|
||||||
|
lexFile();
|
||||||
|
checkFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
enum string KEY = "dscanner.style.long_line";
|
private void lexFile()
|
||||||
const int maxLineLength;
|
{
|
||||||
|
import dscanner.utils : readFile;
|
||||||
|
import dmd.errorsink : ErrorSinkNull;
|
||||||
|
import dmd.globals : global;
|
||||||
|
import dmd.lexer : Lexer;
|
||||||
|
|
||||||
|
auto bytes = readFile(fileName) ~ '\0';
|
||||||
|
|
||||||
|
__gshared ErrorSinkNull errorSinkNull;
|
||||||
|
if (!errorSinkNull)
|
||||||
|
errorSinkNull = new ErrorSinkNull;
|
||||||
|
|
||||||
|
scope lexer = new Lexer(null, cast(char*) bytes, 0, bytes.length, true,
|
||||||
|
true, true, errorSinkNull, &global.compileEnv);
|
||||||
|
|
||||||
|
while (lexer.token.value != TOK.endOfFile)
|
||||||
|
{
|
||||||
|
lexer.nextToken();
|
||||||
|
tokens ~= lexer.token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkFile()
|
||||||
|
{
|
||||||
|
import std.conv : to;
|
||||||
|
|
||||||
|
foreach (i, token; tokens)
|
||||||
|
{
|
||||||
|
switch (token.value)
|
||||||
|
{
|
||||||
|
case TOK.whitespace:
|
||||||
|
switch (token.ptr[0])
|
||||||
|
{
|
||||||
|
case '\t':
|
||||||
|
currentLineLen += 4;
|
||||||
|
break;
|
||||||
|
case '\r':
|
||||||
|
break;
|
||||||
|
case '\n', '\v':
|
||||||
|
checkCurrentLineLength();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
for (auto p = token.ptr; *p == ' '; p++)
|
||||||
|
currentLineLen++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TOK.comment:
|
||||||
|
if (i == tokens.length - 1)
|
||||||
|
skipComment(to!string(token.ptr));
|
||||||
|
else
|
||||||
|
skipComment(token.ptr[0 .. tokens[i + 1].ptr - token.ptr]);
|
||||||
|
break;
|
||||||
|
case TOK.string_:
|
||||||
|
if (i == tokens.length - 1)
|
||||||
|
checkStringLiteral(to!string(token.ptr));
|
||||||
|
else
|
||||||
|
checkStringLiteral(token.ptr[0 .. tokens[i + 1].ptr - token.ptr]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
currentLineLen += token.toString().length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extern (D) void skipComment(const(char)[] commentStr)
|
||||||
|
{
|
||||||
|
import std.utf : byDchar;
|
||||||
|
|
||||||
|
foreach (dchar c; commentStr.byDchar)
|
||||||
|
if (c == '\n' || c == '\v')
|
||||||
|
checkCurrentLineLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
private extern (D) void checkStringLiteral(const(char)[] str)
|
||||||
|
{
|
||||||
|
import std.utf : byDchar;
|
||||||
|
|
||||||
|
foreach (dchar c; str.byDchar)
|
||||||
|
{
|
||||||
|
if (c == '\t')
|
||||||
|
currentLineLen += 4;
|
||||||
|
else if (c == '\n' || c == '\v')
|
||||||
|
checkCurrentLineLength();
|
||||||
|
else if (c != '\r')
|
||||||
|
currentLineLen++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void checkCurrentLineLength()
|
||||||
|
{
|
||||||
|
if (currentLineLen > maxLineLength)
|
||||||
|
addErrorMessage(cast(ulong) currentLine, 0uL, KEY, msg);
|
||||||
|
|
||||||
|
currentLine++;
|
||||||
|
currentLineLen = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@system unittest
|
@system unittest
|
||||||
{
|
{
|
||||||
import dscanner.analysis.config : Check, StaticAnalysisConfig, disabledConfig;
|
import dscanner.analysis.config : Check, StaticAnalysisConfig, disabledConfig;
|
||||||
import dscanner.analysis.helpers : assertAnalyzerWarnings;
|
import dscanner.analysis.helpers : assertAnalyzerWarningsDMD;
|
||||||
import std.stdio : stderr;
|
import std.stdio : stderr;
|
||||||
|
|
||||||
StaticAnalysisConfig sac = disabledConfig();
|
StaticAnalysisConfig sac = disabledConfig();
|
||||||
sac.long_line_check = Check.enabled;
|
sac.long_line_check = Check.enabled;
|
||||||
|
|
||||||
assertAnalyzerWarnings(q{
|
assertAnalyzerWarningsDMD(q{
|
||||||
Window window = Platform.instance.createWindow("Дистанционное управление сварочным оборудованием ", null);
|
Window window = Platform.instance.createWindow("Дистанционное управление сварочным оборудованием ", null);
|
||||||
Window window = Platform.instance.createWindow("Дистанционное управление сварочным оборудованием ", null); // [warn]: Line is longer than 120 characters
|
Window window = Platform.instance.createWindow("Дистанционное управление сварочным оборудованием ", null); // [warn]: Line is longer than 120 characters
|
||||||
unittest {
|
unittest {
|
||||||
|
@ -188,12 +155,13 @@ assert("foo" == "foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
|
||||||
}
|
}
|
||||||
}c, sac);
|
}c, sac);
|
||||||
|
|
||||||
// TODO: libdparse counts columns bytewise
|
assertAnalyzerWarningsDMD(q{
|
||||||
//assert("foo" == "boooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo5");
|
assert("foo" == "boooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo5");
|
||||||
//assert("foo" == "booooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo6"); // [warn]: Line is longer than 120 characters
|
assert("foo" == "booooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo6"); // [warn]: Line is longer than 120 characters
|
||||||
|
}c, sac);
|
||||||
|
|
||||||
// reduced from std/regex/internal/thompson.d
|
// reduced from std/regex/internal/thompson.d
|
||||||
assertAnalyzerWarnings(q{
|
assertAnalyzerWarningsDMD(q{
|
||||||
// whitespace on purpose, do not remove!
|
// whitespace on purpose, do not remove!
|
||||||
mixin(`case IR.`~e~`:
|
mixin(`case IR.`~e~`:
|
||||||
opCacheTrue[pc] = &Ops!(true).op!(IR.`~e~`);
|
opCacheTrue[pc] = &Ops!(true).op!(IR.`~e~`);
|
||||||
|
@ -208,7 +176,7 @@ assert("foo" == "foooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
|
||||||
|
|
||||||
// Test customizing max_line_length.
|
// Test customizing max_line_length.
|
||||||
sac.max_line_length = 115;
|
sac.max_line_length = 115;
|
||||||
assertAnalyzerWarnings(q{
|
assertAnalyzerWarningsDMD(q{
|
||||||
Window window = Platform.instance.createWindow("Дистанционное управлсварочным оборудованием ", null);
|
Window window = Platform.instance.createWindow("Дистанционное управлсварочным оборудованием ", null);
|
||||||
Window window = Platform.instance.createWindow("Дистанционное управлсварочным оборудованием ", null); // [warn]: Line is longer than 115 characters
|
Window window = Platform.instance.createWindow("Дистанционное управлсварочным оборудованием ", null); // [warn]: Line is longer than 115 characters
|
||||||
unittest {
|
unittest {
|
||||||
|
|
|
@ -841,11 +841,6 @@ private BaseAnalyzer[] getAnalyzersForModuleAndConfig(string fileName,
|
||||||
checks ~= new UndocumentedDeclarationCheck(args.setSkipTests(
|
checks ~= new UndocumentedDeclarationCheck(args.setSkipTests(
|
||||||
analysisConfig.undocumented_declaration_check == Check.skipTests && !ut));
|
analysisConfig.undocumented_declaration_check == Check.skipTests && !ut));
|
||||||
|
|
||||||
if (moduleName.shouldRun!LineLengthCheck(analysisConfig))
|
|
||||||
checks ~= new LineLengthCheck(args.setSkipTests(
|
|
||||||
analysisConfig.long_line_check == Check.skipTests && !ut),
|
|
||||||
analysisConfig.max_line_length);
|
|
||||||
|
|
||||||
if (moduleName.shouldRun!VcallCtorChecker(analysisConfig))
|
if (moduleName.shouldRun!VcallCtorChecker(analysisConfig))
|
||||||
checks ~= new VcallCtorChecker(args.setSkipTests(
|
checks ~= new VcallCtorChecker(args.setSkipTests(
|
||||||
analysisConfig.vcall_in_ctor == Check.skipTests && !ut));
|
analysisConfig.vcall_in_ctor == Check.skipTests && !ut));
|
||||||
|
@ -1362,6 +1357,13 @@ MessageSet analyzeDmd(string fileName, ASTCodegen.Module m, const char[] moduleN
|
||||||
config.has_public_example == Check.skipTests && !ut
|
config.has_public_example == Check.skipTests && !ut
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (moduleName.shouldRunDmd!LineLengthCheck(config))
|
||||||
|
visitors ~= new LineLengthCheck(
|
||||||
|
fileName,
|
||||||
|
config.long_line_check == Check.skipTests && !ut,
|
||||||
|
config.max_line_length
|
||||||
|
);
|
||||||
|
|
||||||
foreach (visitor; visitors)
|
foreach (visitor; visitors)
|
||||||
{
|
{
|
||||||
m.accept(visitor);
|
m.accept(visitor);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue