1 module workspaced.dub.diagnostics;
2 
3 import workspaced.api;
4 
5 import std.algorithm;
6 import std.string;
7 
8 import dparse.ast;
9 import dparse.lexer;
10 import dparse.parser;
11 import dparse.rollback_allocator;
12 
13 int[2] resolveDubDiagnosticRange(scope const(char)[] code,
14 	scope const(Token)[] tokens, Module parsed, int position,
15 	scope const(char)[] diagnostic)
16 {
17 	if (diagnostic.startsWith("use `is` instead of `==`",
18 		"use `!is` instead of `!=`"))
19 	{
20 		auto expr = new EqualComparisionFinder(position);
21 		expr.visit(parsed);
22 		if (expr.result !is null)
23 		{
24 			const left = &expr.result.left.tokens[$ - 1];
25 			const right = &expr.result.right.tokens[0];
26 			auto between = left[1 .. right - left];
27 			const tok = between[0];
28 			if (tok.type == expr.result.operator)
29 			{
30 				auto index = cast(int) tok.index;
31 				return [index, index + 2];
32 			}
33 		}
34 	}
35 	return [position, position];
36 }
37 
38 /// Finds the equals comparision at the given index.
39 /// Used to resolve issue locations for diagnostics of type
40 /// - use `is` instead of `==`
41 /// - use `!is` instead of `!=`
42 class EqualComparisionFinder : ASTVisitor
43 {
44 	this(size_t index)
45 	{
46 		this.index = index;
47 	}
48 
49 	override void visit(const(CmpExpression) expr)
50 	{
51 		if (expr.equalExpression !is null)
52 		{
53 			const start = expr.tokens[0].index;
54 			const last = expr.tokens[$ - 1];
55 			const end = last.index + last.text.length;
56 			if (index >= start && index < end)
57 			{
58 				result = cast(EqualExpression) expr.equalExpression;
59 			}
60 		}
61 		super.visit(expr);
62 	}
63 
64 	alias visit = ASTVisitor.visit;
65 	size_t index;
66 	EqualExpression result;
67 }
68 
69 unittest
70 {
71 	string code = q{void main() {
72 	if (foo(a == 4) == null)
73 	{
74 	}
75 }}.replace("\r\n", "\n");
76 
77 	LexerConfig config;
78 	RollbackAllocator rba;
79 	StringCache cache = StringCache(64);
80 	auto tokens = getTokensForParser(cast(ubyte[]) code, config, &cache);
81 	auto parsed = parseModule(tokens, "equal_finder.d", &rba);
82 
83 	auto range = resolveDubDiagnosticRange(code, tokens, parsed, 19,
84 		"use `is` instead of `==` when comparing with `null`");
85 
86 	assert(range == [31, 33]);
87 }