Skip to content

Commit 1e8f1ec

Browse files
Allow skipping checks with @("nolint(...)") and @NOLINT("...") (#936)
Co-authored-by: Axel Ricard <contact@axelricard.fr> Co-authored-by: WebFreak001 <gh@webfreak.org>
1 parent 69d824f commit 1e8f1ec

File tree

4 files changed

+368
-3
lines changed

4 files changed

+368
-3
lines changed

src/dscanner/analysis/base.d

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
module dscanner.analysis.base;
22

33
import dparse.ast;
4-
import dparse.lexer : IdType, str, Token;
4+
import dparse.lexer : IdType, str, Token, tok;
5+
import dscanner.analysis.nolint;
56
import dsymbol.scope_ : Scope;
67
import std.array;
78
import std.container;
@@ -405,6 +406,35 @@ public:
405406
unittest_.accept(this);
406407
}
407408

409+
/**
410+
* Visits a module declaration.
411+
*
412+
* When overriden, make sure to keep this structure
413+
*/
414+
override void visit(const(Module) mod)
415+
{
416+
if (mod.moduleDeclaration !is null)
417+
{
418+
with (noLint.push(NoLintFactory.fromModuleDeclaration(mod.moduleDeclaration)))
419+
mod.accept(this);
420+
}
421+
else
422+
{
423+
mod.accept(this);
424+
}
425+
}
426+
427+
/**
428+
* Visits a declaration.
429+
*
430+
* When overriden, make sure to disable and reenable error messages
431+
*/
432+
override void visit(const(Declaration) decl)
433+
{
434+
with (noLint.push(NoLintFactory.fromDeclaration(decl)))
435+
decl.accept(this);
436+
}
437+
408438
AutoFix.CodeReplacement[] resolveAutoFix(
409439
const Module mod,
410440
scope const(Token)[] tokens,
@@ -423,6 +453,7 @@ protected:
423453

424454
bool inAggregate;
425455
bool skipTests;
456+
NoLint noLint;
426457

427458
template visitTemplate(T)
428459
{
@@ -437,42 +468,58 @@ protected:
437468
deprecated("Use the overload taking start and end locations or a Node instead")
438469
void addErrorMessage(size_t line, size_t column, string key, string message)
439470
{
471+
if (noLint.containsCheck(key))
472+
return;
440473
_messages.insert(Message(fileName, line, column, key, message, getName()));
441474
}
442475

443476
void addErrorMessage(const BaseNode node, string key, string message, AutoFix[] autofixes = null)
444477
{
478+
if (noLint.containsCheck(key))
479+
return;
445480
addErrorMessage(Message.Diagnostic.from(fileName, node, message), key, autofixes);
446481
}
447482

448483
void addErrorMessage(const Token token, string key, string message, AutoFix[] autofixes = null)
449484
{
485+
if (noLint.containsCheck(key))
486+
return;
450487
addErrorMessage(Message.Diagnostic.from(fileName, token, message), key, autofixes);
451488
}
452489

453490
void addErrorMessage(const Token[] tokens, string key, string message, AutoFix[] autofixes = null)
454491
{
492+
if (noLint.containsCheck(key))
493+
return;
455494
addErrorMessage(Message.Diagnostic.from(fileName, tokens, message), key, autofixes);
456495
}
457496

458497
void addErrorMessage(size_t[2] index, size_t line, size_t[2] columns, string key, string message, AutoFix[] autofixes = null)
459498
{
499+
if (noLint.containsCheck(key))
500+
return;
460501
addErrorMessage(index, [line, line], columns, key, message, autofixes);
461502
}
462503

463504
void addErrorMessage(size_t[2] index, size_t[2] lines, size_t[2] columns, string key, string message, AutoFix[] autofixes = null)
464505
{
506+
if (noLint.containsCheck(key))
507+
return;
465508
auto d = Message.Diagnostic.from(fileName, index, lines, columns, message);
466509
_messages.insert(Message(d, key, getName(), autofixes));
467510
}
468511

469512
void addErrorMessage(Message.Diagnostic diagnostic, string key, AutoFix[] autofixes = null)
470513
{
514+
if (noLint.containsCheck(key))
515+
return;
471516
_messages.insert(Message(diagnostic, key, getName(), autofixes));
472517
}
473518

474519
void addErrorMessage(Message.Diagnostic diagnostic, Message.Diagnostic[] supplemental, string key, AutoFix[] autofixes = null)
475520
{
521+
if (noLint.containsCheck(key))
522+
return;
476523
_messages.insert(Message(diagnostic, supplemental, key, getName(), autofixes));
477524
}
478525

@@ -756,7 +803,7 @@ unittest
756803
testScopes(q{
757804
auto isNewScope = void;
758805
auto depth = 1;
759-
806+
760807
void foo() {
761808
isNewScope();
762809
isOldScope();

src/dscanner/analysis/nolint.d

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
module dscanner.analysis.nolint;
2+
3+
@safe:
4+
5+
import dparse.ast;
6+
import dparse.lexer;
7+
8+
import std.algorithm : canFind;
9+
import std.regex : matchAll, regex;
10+
import std.string : lastIndexOf, strip;
11+
import std.typecons;
12+
13+
struct NoLint
14+
{
15+
bool containsCheck(scope const(char)[] check) const
16+
{
17+
while (true)
18+
{
19+
if (disabledChecks.get((() @trusted => cast(string) check)(), 0) > 0)
20+
return true;
21+
22+
auto dot = check.lastIndexOf('.');
23+
if (dot == -1)
24+
break;
25+
check = check[0 .. dot];
26+
}
27+
return false;
28+
}
29+
30+
// automatic pop when returned value goes out of scope
31+
Poppable push(in Nullable!NoLint other) scope
32+
{
33+
if (other.isNull)
34+
return Poppable(null);
35+
36+
foreach (key, value; other.get.getDisabledChecks)
37+
this.disabledChecks[key] += value;
38+
39+
return Poppable(() => this.pop(other));
40+
}
41+
42+
package:
43+
const(int[string]) getDisabledChecks() const
44+
{
45+
return this.disabledChecks;
46+
}
47+
48+
void pushCheck(in string check)
49+
{
50+
disabledChecks[check]++;
51+
}
52+
53+
void merge(in Nullable!NoLint other)
54+
{
55+
if (other.isNull)
56+
return;
57+
58+
foreach (key, value; other.get.getDisabledChecks)
59+
this.disabledChecks[key] += value;
60+
}
61+
62+
private:
63+
void pop(in Nullable!NoLint other)
64+
{
65+
if (other.isNull)
66+
return;
67+
68+
foreach (key, value; other.get.getDisabledChecks)
69+
{
70+
assert(this.disabledChecks.get(key, 0) >= value);
71+
72+
this.disabledChecks[key] -= value;
73+
}
74+
}
75+
76+
static struct Poppable
77+
{
78+
~this()
79+
{
80+
if (onPop)
81+
onPop();
82+
onPop = null;
83+
}
84+
85+
private:
86+
void delegate() onPop;
87+
}
88+
89+
int[string] disabledChecks;
90+
}
91+
92+
struct NoLintFactory
93+
{
94+
static Nullable!NoLint fromModuleDeclaration(in ModuleDeclaration moduleDeclaration)
95+
{
96+
NoLint noLint;
97+
98+
foreach (atAttribute; moduleDeclaration.atAttributes)
99+
noLint.merge(NoLintFactory.fromAtAttribute(atAttribute));
100+
101+
if (!noLint.getDisabledChecks.length)
102+
return nullNoLint;
103+
104+
return noLint.nullable;
105+
}
106+
107+
static Nullable!NoLint fromDeclaration(in Declaration declaration)
108+
{
109+
NoLint noLint;
110+
foreach (attribute; declaration.attributes)
111+
noLint.merge(NoLintFactory.fromAttribute(attribute));
112+
113+
if (!noLint.getDisabledChecks.length)
114+
return nullNoLint;
115+
116+
return noLint.nullable;
117+
}
118+
119+
private:
120+
static Nullable!NoLint fromAttribute(const(Attribute) attribute)
121+
{
122+
if (attribute is null)
123+
return nullNoLint;
124+
125+
return NoLintFactory.fromAtAttribute(attribute.atAttribute);
126+
127+
}
128+
129+
static Nullable!NoLint fromAtAttribute(const(AtAttribute) atAttribute)
130+
{
131+
if (atAttribute is null)
132+
return nullNoLint;
133+
134+
auto ident = atAttribute.identifier;
135+
auto argumentList = atAttribute.argumentList;
136+
137+
if (argumentList !is null)
138+
{
139+
if (ident.text.length)
140+
return NoLintFactory.fromStructUda(ident, argumentList);
141+
else
142+
return NoLintFactory.fromStringUda(argumentList);
143+
144+
}
145+
else
146+
return nullNoLint;
147+
}
148+
149+
// @nolint("..")
150+
static Nullable!NoLint fromStructUda(in Token ident, in ArgumentList argumentList)
151+
in (ident.text.length && argumentList !is null)
152+
{
153+
if (ident.text != "nolint")
154+
return nullNoLint;
155+
156+
NoLint noLint;
157+
158+
foreach (nodeExpr; argumentList.items)
159+
{
160+
if (auto unaryExpr = cast(const UnaryExpression) nodeExpr)
161+
{
162+
auto primaryExpression = unaryExpr.primaryExpression;
163+
if (primaryExpression is null)
164+
continue;
165+
166+
if (primaryExpression.primary != tok!"stringLiteral")
167+
continue;
168+
169+
noLint.pushCheck(primaryExpression.primary.text.strip("\""));
170+
}
171+
}
172+
173+
if (!noLint.getDisabledChecks().length)
174+
return nullNoLint;
175+
176+
return noLint.nullable;
177+
}
178+
179+
// @("nolint(..)")
180+
static Nullable!NoLint fromStringUda(in ArgumentList argumentList)
181+
in (argumentList !is null)
182+
{
183+
NoLint noLint;
184+
185+
foreach (nodeExpr; argumentList.items)
186+
{
187+
if (auto unaryExpr = cast(const UnaryExpression) nodeExpr)
188+
{
189+
auto primaryExpression = unaryExpr.primaryExpression;
190+
if (primaryExpression is null)
191+
continue;
192+
193+
if (primaryExpression.primary != tok!"stringLiteral")
194+
continue;
195+
196+
auto str = primaryExpression.primary.text.strip("\"");
197+
Nullable!NoLint currNoLint = NoLintFactory.fromString(str);
198+
noLint.merge(currNoLint);
199+
}
200+
}
201+
202+
if (!noLint.getDisabledChecks().length)
203+
return nullNoLint;
204+
205+
return noLint.nullable;
206+
207+
}
208+
209+
// Transform a string with form "nolint(abc, efg)"
210+
// into a NoLint struct
211+
static Nullable!NoLint fromString(in string str)
212+
{
213+
static immutable re = regex(`[\w-_.]+`, "g");
214+
auto matches = matchAll(str, re);
215+
216+
if (!matches)
217+
return nullNoLint;
218+
219+
const udaName = matches.hit;
220+
if (udaName != "nolint")
221+
return nullNoLint;
222+
223+
matches.popFront;
224+
225+
NoLint noLint;
226+
227+
while (matches)
228+
{
229+
noLint.pushCheck(matches.hit);
230+
matches.popFront;
231+
}
232+
233+
if (!noLint.getDisabledChecks.length)
234+
return nullNoLint;
235+
236+
return noLint.nullable;
237+
}
238+
239+
static nullNoLint = Nullable!NoLint.init;
240+
}
241+
242+
unittest
243+
{
244+
const s1 = "nolint(abc)";
245+
const s2 = "nolint(abc, efg, hij)";
246+
const s3 = " nolint ( abc , efg ) ";
247+
const s4 = "nolint(dscanner.style.abc_efg-ijh)";
248+
const s5 = "OtherUda(abc)";
249+
const s6 = "nolint(dscanner)";
250+
251+
assert(NoLintFactory.fromString(s1).get.containsCheck("abc"));
252+
253+
assert(NoLintFactory.fromString(s2).get.containsCheck("abc"));
254+
assert(NoLintFactory.fromString(s2).get.containsCheck("efg"));
255+
assert(NoLintFactory.fromString(s2).get.containsCheck("hij"));
256+
257+
assert(NoLintFactory.fromString(s3).get.containsCheck("abc"));
258+
assert(NoLintFactory.fromString(s3).get.containsCheck("efg"));
259+
260+
assert(NoLintFactory.fromString(s4).get.containsCheck("dscanner.style.abc_efg-ijh"));
261+
262+
assert(NoLintFactory.fromString(s5).isNull);
263+
264+
assert(NoLintFactory.fromString(s6).get.containsCheck("dscanner"));
265+
assert(!NoLintFactory.fromString(s6).get.containsCheck("dscanner2"));
266+
assert(NoLintFactory.fromString(s6).get.containsCheck("dscanner.foo"));
267+
268+
import std.stdio : stderr, writeln;
269+
270+
(() @trusted => stderr.writeln("Unittest for NoLint passed."))();
271+
}

0 commit comments

Comments
 (0)