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 }