1 module served.linters.dfmt;
2 
3 import std.algorithm;
4 import std.array;
5 import std.conv;
6 import std.file;
7 import std.json;
8 import std.path;
9 import std.string;
10 
11 import served.extension;
12 import served.linters.diagnosticmanager;
13 import served.types;
14 
15 import workspaced.api;
16 import workspaced.coms;
17 
18 import workspaced.com.dfmt;
19 
20 static immutable string DfmtDiagnosticSource = "dfmt";
21 
22 enum DiagnosticSlot = 2;
23 
24 void lint(Document document)
25 {
26 	auto fileConfig = config(document.uri);
27 	if (!fileConfig.d.enableFormatting)
28 		return;
29 
30 	if (!backend.has!DfmtComponent)
31 		return;
32 	auto dfmt = backend.get!DfmtComponent;
33 
34 	createDiagnosticsFor!DiagnosticSlot(document.uri) = lintDfmt(dfmt, document);
35 	updateDiagnostics(document.uri);
36 }
37 
38 private Diagnostic[] lintDfmt(DfmtComponent dfmt, ref Document document)
39 {
40 	auto instructions = dfmt.findDfmtInstructions(document.rawText);
41 
42 	auto diagnostics = appender!(Diagnostic[]);
43 	bool fmtOn = true;
44 
45 	Position positionCache;
46 	size_t byteCache;
47 
48 	void setFmt(DfmtInstruction instruction, bool on)
49 	{
50 		if (fmtOn == on)
51 		{
52 		}
53 		fmtOn = on;
54 	}
55 
56 	foreach (DfmtInstruction instruction; instructions)
57 	{
58 		Diagnostic d;
59 		d.source = DfmtDiagnosticSource;
60 		auto start = document.movePositionBytes(positionCache, byteCache, instruction.index);
61 		auto end = document.movePositionBytes(start, instruction.index, instruction.index + instruction.length);
62 		positionCache = end;
63 		byteCache = instruction.index + instruction.length;
64 		d.range = TextRange(start, end);
65 
66 		final switch (instruction.type)
67 		{
68 			case DfmtInstruction.Type.dfmtOn:
69 			case DfmtInstruction.Type.dfmtOff:
70 				bool on = instruction.type == DfmtInstruction.Type.dfmtOn;
71 				if (on == fmtOn)
72 				{
73 					d.message = on ? "Redundant `dfmt on`" : "Redundant `dfmt off`";
74 					d.code = JsonValue(on ? "redundant-on" : "redundant-off");
75 					d.severity = DiagnosticSeverity.hint;
76 					d.tags = [DiagnosticTag.unnecessary];
77 					diagnostics ~= d;
78 				}
79 				fmtOn = on;
80 				break;
81 			case DfmtInstruction.Type.unknown:
82 				d.message = "Not a valid dfmt command (try `//dfmt off` or `//dfmt on` instead)";
83 				d.code = JsonValue("unknown-comment");
84 				d.severity = DiagnosticSeverity.warning;
85 				diagnostics ~= d;
86 				break;
87 		}
88 	}
89 
90 	return diagnostics.data;
91 }
92 
93 void clear()
94 {
95 	diagnostics[DiagnosticSlot] = null;
96 	updateDiagnostics();
97 }
98 
99 @("misspelling on/off")
100 unittest
101 {
102 	auto dfmt = new DfmtComponent();
103 	dfmt.workspaced = new WorkspaceD();
104 	Document d = Document.nullDocument(`void foo() {
105 	//dfmt offs
106 	int i = 5;
107 	//dfmt onf
108 }`);
109 	auto linted = lintDfmt(dfmt, d);
110 	assert(linted.length == 2);
111 	assert(linted[0].severity.deref == DiagnosticSeverity.warning);
112 	assert(linted[1].severity.deref == DiagnosticSeverity.warning);
113 }
114 
115 @("redundant on/off")
116 unittest
117 {
118 	auto dfmt = new DfmtComponent();
119 	dfmt.workspaced = new WorkspaceD();
120 	Document d = Document.nullDocument(`void foo() {
121 	//dfmt on
122 	//dfmt off
123 	int i = 5;
124 	//dfmt off
125 	//dfmt ons
126 }`);
127 	auto linted = lintDfmt(dfmt, d);
128 	import std.stdio; stderr.writeln("diagnostics:\n", linted);
129 	assert(linted.length == 3);
130 	assert(linted[0].severity.deref == DiagnosticSeverity.hint);
131 	assert(linted[1].severity.deref == DiagnosticSeverity.hint);
132 	assert(linted[2].severity.deref == DiagnosticSeverity.warning);
133 }