@herb-tools/language-server 0.9.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/diagnostics.js +0 -221
- package/dist/diagnostics.js.map +1 -1
- package/dist/herb-language-server.js +13186 -10335
- package/dist/herb-language-server.js.map +1 -1
- package/dist/index.cjs +65 -263
- package/dist/index.cjs.map +1 -1
- package/dist/linter_service.js +7 -2
- package/dist/linter_service.js.map +1 -1
- package/dist/server.js +3 -0
- package/dist/server.js.map +1 -1
- package/dist/types/diagnostics.d.ts +1 -34
- package/dist/types/utils.d.ts +4 -2
- package/dist/utils.js +12 -1
- package/dist/utils.js.map +1 -1
- package/package.json +7 -7
- package/src/diagnostics.ts +1 -291
- package/src/linter_service.ts +10 -2
- package/src/server.ts +3 -0
- package/src/utils.ts +15 -2
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { TextDocument } from "vscode-languageserver-textdocument";
|
|
2
|
-
import { Connection
|
|
3
|
-
import { Visitor } from "@herb-tools/core";
|
|
4
|
-
import type { ERBCaseNode, ERBCaseMatchNode, ERBIfNode, ERBElseNode, ERBUnlessNode, ERBForNode, ERBWhileNode, ERBUntilNode, ERBWhenNode, ERBBeginNode, ERBRescueNode, ERBEnsureNode, ERBBlockNode, ERBInNode } from "@herb-tools/core";
|
|
2
|
+
import { Connection } from "vscode-languageserver/node";
|
|
5
3
|
import { ParserService } from "./parser_service";
|
|
6
4
|
import { LinterService } from "./linter_service";
|
|
7
5
|
import { DocumentService } from "./document_service";
|
|
@@ -15,38 +13,7 @@ export declare class Diagnostics {
|
|
|
15
13
|
private diagnostics;
|
|
16
14
|
constructor(connection: Connection, documentService: DocumentService, parserService: ParserService, linterService: LinterService, configService: ConfigService);
|
|
17
15
|
validate(textDocument: TextDocument): Promise<void>;
|
|
18
|
-
private getUnreachableCodeDiagnostics;
|
|
19
16
|
refreshDocument(document: TextDocument): Promise<void>;
|
|
20
17
|
refreshAllDocuments(): Promise<void>;
|
|
21
18
|
private sendDiagnosticsFor;
|
|
22
19
|
}
|
|
23
|
-
export declare class UnreachableCodeCollector extends Visitor {
|
|
24
|
-
diagnostics: Diagnostic[];
|
|
25
|
-
private processedIfNodes;
|
|
26
|
-
private processedElseNodes;
|
|
27
|
-
visitERBCaseNode(node: ERBCaseNode): void;
|
|
28
|
-
visitERBCaseMatchNode(node: ERBCaseMatchNode): void;
|
|
29
|
-
visitERBIfNode(node: ERBIfNode): void;
|
|
30
|
-
visitERBElseNode(node: ERBElseNode): void;
|
|
31
|
-
visitERBUnlessNode(node: ERBUnlessNode): void;
|
|
32
|
-
visitERBForNode(node: ERBForNode): void;
|
|
33
|
-
visitERBWhileNode(node: ERBWhileNode): void;
|
|
34
|
-
visitERBUntilNode(node: ERBUntilNode): void;
|
|
35
|
-
visitERBWhenNode(node: ERBWhenNode): void;
|
|
36
|
-
visitERBBeginNode(node: ERBBeginNode): void;
|
|
37
|
-
visitERBRescueNode(node: ERBRescueNode): void;
|
|
38
|
-
visitERBEnsureNode(node: ERBEnsureNode): void;
|
|
39
|
-
visitERBBlockNode(node: ERBBlockNode): void;
|
|
40
|
-
visitERBInNode(node: ERBInNode): void;
|
|
41
|
-
private checkUnreachableChildren;
|
|
42
|
-
private checkEmptyStatements;
|
|
43
|
-
private checkEmptyStatementsWithEndLocation;
|
|
44
|
-
private addDiagnostic;
|
|
45
|
-
private statementsHaveContent;
|
|
46
|
-
private checkAndMarkElseClause;
|
|
47
|
-
private markIfChainAsProcessed;
|
|
48
|
-
private markElseNodesInIfChain;
|
|
49
|
-
private traverseSubsequentNodes;
|
|
50
|
-
private checkIfChainParts;
|
|
51
|
-
private isEntireIfChainEmpty;
|
|
52
|
-
}
|
package/dist/types/utils.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { DiagnosticSeverity } from "vscode-languageserver/node";
|
|
1
|
+
import { DiagnosticSeverity, DiagnosticTag } from "vscode-languageserver/node";
|
|
2
2
|
import type { LintSeverity } from "@herb-tools/linter";
|
|
3
|
+
import type { DiagnosticSeverity as HerbDiagnosticSeverity, DiagnosticTag as HerbDiagnosticTag } from "@herb-tools/core";
|
|
3
4
|
export declare function camelize(value: string): string;
|
|
4
5
|
export declare function dasherize(value: string): string;
|
|
5
6
|
export declare function capitalize(value: string): string;
|
|
6
|
-
export declare function lintToDignosticSeverity(severity: LintSeverity): DiagnosticSeverity;
|
|
7
|
+
export declare function lintToDignosticSeverity(severity: LintSeverity | HerbDiagnosticSeverity): DiagnosticSeverity;
|
|
8
|
+
export declare function lintToDignosticTags(tags?: HerbDiagnosticTag[]): DiagnosticTag[];
|
package/dist/utils.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DiagnosticSeverity } from "vscode-languageserver/node";
|
|
1
|
+
import { DiagnosticSeverity, DiagnosticTag } from "vscode-languageserver/node";
|
|
2
2
|
export function camelize(value) {
|
|
3
3
|
return value.replace(/(?:[_-])([a-z0-9])/g, (_, char) => char.toUpperCase());
|
|
4
4
|
}
|
|
@@ -16,4 +16,15 @@ export function lintToDignosticSeverity(severity) {
|
|
|
16
16
|
case "hint": return DiagnosticSeverity.Hint;
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
+
export function lintToDignosticTags(tags) {
|
|
20
|
+
if (!tags)
|
|
21
|
+
return [];
|
|
22
|
+
return tags.flatMap(tag => {
|
|
23
|
+
switch (tag) {
|
|
24
|
+
case "unnecessary": return [DiagnosticTag.Unnecessary];
|
|
25
|
+
case "deprecated": return [DiagnosticTag.Deprecated];
|
|
26
|
+
default: return [];
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
19
30
|
//# sourceMappingURL=utils.js.map
|
package/dist/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAA;
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAI9E,MAAM,UAAU,QAAQ,CAAC,KAAa;IACpC,OAAO,KAAK,CAAC,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAA;AAC9E,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,OAAO,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC,CAAA;AACzE,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AACvD,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,QAA+C;IACrF,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,OAAO,CAAC,CAAC,OAAO,kBAAkB,CAAC,KAAK,CAAA;QAC7C,KAAK,SAAS,CAAC,CAAC,OAAO,kBAAkB,CAAC,OAAO,CAAA;QACjD,KAAK,MAAM,CAAC,CAAC,OAAO,kBAAkB,CAAC,WAAW,CAAA;QAClD,KAAK,MAAM,CAAC,CAAC,OAAO,kBAAkB,CAAC,IAAI,CAAA;IAC7C,CAAC;AACH,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,IAA0B;IAC5D,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAA;IAEpB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE;QACxB,QAAQ,GAAG,EAAE,CAAC;YACZ,KAAK,aAAa,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,WAAW,CAAC,CAAA;YACtD,KAAK,YAAY,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,UAAU,CAAC,CAAA;YACpD,OAAO,CAAC,CAAC,OAAO,EAAE,CAAA;QACpB,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@herb-tools/language-server",
|
|
3
3
|
"description": "Herb HTML+ERB Language Tools and Language Server Protocol integration.",
|
|
4
|
-
"version": "0.9.
|
|
4
|
+
"version": "0.9.1",
|
|
5
5
|
"author": "Marco Roth",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"engines": {
|
|
@@ -45,12 +45,12 @@
|
|
|
45
45
|
"dist/"
|
|
46
46
|
],
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@herb-tools/config": "0.9.
|
|
49
|
-
"@herb-tools/formatter": "0.9.
|
|
50
|
-
"@herb-tools/linter": "0.9.
|
|
51
|
-
"@herb-tools/node-wasm": "0.9.
|
|
52
|
-
"@herb-tools/printer": "0.9.
|
|
53
|
-
"@herb-tools/rewriter": "0.9.
|
|
48
|
+
"@herb-tools/config": "0.9.1",
|
|
49
|
+
"@herb-tools/formatter": "0.9.1",
|
|
50
|
+
"@herb-tools/linter": "0.9.1",
|
|
51
|
+
"@herb-tools/node-wasm": "0.9.1",
|
|
52
|
+
"@herb-tools/printer": "0.9.1",
|
|
53
|
+
"@herb-tools/rewriter": "0.9.1",
|
|
54
54
|
"dedent": "^1.7.0",
|
|
55
55
|
"vscode-languageserver": "^9.0.1",
|
|
56
56
|
"vscode-languageserver-textdocument": "^1.0.12"
|
package/src/diagnostics.ts
CHANGED
|
@@ -1,33 +1,10 @@
|
|
|
1
1
|
import { TextDocument } from "vscode-languageserver-textdocument"
|
|
2
|
-
import { Connection, Diagnostic
|
|
3
|
-
import { Visitor } from "@herb-tools/core"
|
|
4
|
-
|
|
5
|
-
import type {
|
|
6
|
-
Node,
|
|
7
|
-
SerializedLocation,
|
|
8
|
-
ERBCaseNode,
|
|
9
|
-
ERBCaseMatchNode,
|
|
10
|
-
ERBIfNode,
|
|
11
|
-
ERBElseNode,
|
|
12
|
-
ERBUnlessNode,
|
|
13
|
-
ERBForNode,
|
|
14
|
-
ERBWhileNode,
|
|
15
|
-
ERBUntilNode,
|
|
16
|
-
ERBWhenNode,
|
|
17
|
-
ERBBeginNode,
|
|
18
|
-
ERBRescueNode,
|
|
19
|
-
ERBEnsureNode,
|
|
20
|
-
ERBBlockNode,
|
|
21
|
-
ERBInNode,
|
|
22
|
-
} from "@herb-tools/core"
|
|
23
|
-
|
|
24
|
-
import { isHTMLTextNode, isERBIfNode, isERBElseNode } from "@herb-tools/core"
|
|
2
|
+
import { Connection, Diagnostic } from "vscode-languageserver/node"
|
|
25
3
|
|
|
26
4
|
import { ParserService } from "./parser_service"
|
|
27
5
|
import { LinterService } from "./linter_service"
|
|
28
6
|
import { DocumentService } from "./document_service"
|
|
29
7
|
import { ConfigService } from "./config_service"
|
|
30
|
-
import { lspRangeFromLocation } from "./range_utils"
|
|
31
8
|
|
|
32
9
|
export class Diagnostics {
|
|
33
10
|
private readonly connection: Connection
|
|
@@ -59,12 +36,10 @@ export class Diagnostics {
|
|
|
59
36
|
} else {
|
|
60
37
|
const parseResult = this.parserService.parseDocument(textDocument)
|
|
61
38
|
const lintResult = await this.linterService.lintDocument(textDocument)
|
|
62
|
-
const unreachableCodeDiagnostics = this.getUnreachableCodeDiagnostics(parseResult.document)
|
|
63
39
|
|
|
64
40
|
allDiagnostics = [
|
|
65
41
|
...parseResult.diagnostics,
|
|
66
42
|
...lintResult.diagnostics,
|
|
67
|
-
...unreachableCodeDiagnostics,
|
|
68
43
|
]
|
|
69
44
|
}
|
|
70
45
|
|
|
@@ -72,12 +47,6 @@ export class Diagnostics {
|
|
|
72
47
|
this.sendDiagnosticsFor(textDocument)
|
|
73
48
|
}
|
|
74
49
|
|
|
75
|
-
private getUnreachableCodeDiagnostics(document: Node): Diagnostic[] {
|
|
76
|
-
const collector = new UnreachableCodeCollector()
|
|
77
|
-
collector.visit(document)
|
|
78
|
-
return collector.diagnostics
|
|
79
|
-
}
|
|
80
|
-
|
|
81
50
|
async refreshDocument(document: TextDocument) {
|
|
82
51
|
await this.validate(document)
|
|
83
52
|
}
|
|
@@ -98,262 +67,3 @@ export class Diagnostics {
|
|
|
98
67
|
this.diagnostics.delete(textDocument)
|
|
99
68
|
}
|
|
100
69
|
}
|
|
101
|
-
|
|
102
|
-
export class UnreachableCodeCollector extends Visitor {
|
|
103
|
-
diagnostics: Diagnostic[] = []
|
|
104
|
-
private processedIfNodes: Set<ERBIfNode> = new Set()
|
|
105
|
-
private processedElseNodes: Set<ERBElseNode> = new Set()
|
|
106
|
-
|
|
107
|
-
visitERBCaseNode(node: ERBCaseNode): void {
|
|
108
|
-
this.checkUnreachableChildren(node.children)
|
|
109
|
-
this.checkAndMarkElseClause(node.else_clause)
|
|
110
|
-
this.visitChildNodes(node)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
visitERBCaseMatchNode(node: ERBCaseMatchNode): void {
|
|
114
|
-
this.checkUnreachableChildren(node.children)
|
|
115
|
-
this.checkAndMarkElseClause(node.else_clause)
|
|
116
|
-
this.visitChildNodes(node)
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
visitERBIfNode(node: ERBIfNode): void {
|
|
120
|
-
if (this.processedIfNodes.has(node)) {
|
|
121
|
-
return
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
this.markIfChainAsProcessed(node)
|
|
125
|
-
|
|
126
|
-
this.markElseNodesInIfChain(node)
|
|
127
|
-
|
|
128
|
-
const entireChainEmpty = this.isEntireIfChainEmpty(node)
|
|
129
|
-
|
|
130
|
-
if (entireChainEmpty) {
|
|
131
|
-
this.checkEmptyStatements(node, node.statements, "if")
|
|
132
|
-
} else {
|
|
133
|
-
this.checkIfChainParts(node)
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
this.visitChildNodes(node)
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
visitERBElseNode(node: ERBElseNode): void {
|
|
140
|
-
if (this.processedElseNodes.has(node)) {
|
|
141
|
-
this.visitChildNodes(node)
|
|
142
|
-
return
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
this.checkEmptyStatements(node, node.statements, "else")
|
|
146
|
-
this.visitChildNodes(node)
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
visitERBUnlessNode(node: ERBUnlessNode): void {
|
|
150
|
-
const unlessHasContent = this.statementsHaveContent(node.statements)
|
|
151
|
-
const elseHasContent = node.else_clause && this.statementsHaveContent(node.else_clause.statements)
|
|
152
|
-
|
|
153
|
-
if (node.else_clause) {
|
|
154
|
-
this.processedElseNodes.add(node.else_clause)
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const entireBlockEmpty = !unlessHasContent && !elseHasContent
|
|
158
|
-
|
|
159
|
-
if (entireBlockEmpty) {
|
|
160
|
-
this.checkEmptyStatements(node, node.statements, "unless")
|
|
161
|
-
} else {
|
|
162
|
-
if (!unlessHasContent) {
|
|
163
|
-
this.checkEmptyStatementsWithEndLocation(node, node.statements, "unless", node.else_clause)
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (node.else_clause && !elseHasContent) {
|
|
167
|
-
this.checkEmptyStatements(node.else_clause, node.else_clause.statements, "else")
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
this.visitChildNodes(node)
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
visitERBForNode(node: ERBForNode): void {
|
|
175
|
-
this.checkEmptyStatements(node, node.statements, "for")
|
|
176
|
-
this.visitChildNodes(node)
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
visitERBWhileNode(node: ERBWhileNode): void {
|
|
180
|
-
this.checkEmptyStatements(node, node.statements, "while")
|
|
181
|
-
this.visitChildNodes(node)
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
visitERBUntilNode(node: ERBUntilNode): void {
|
|
185
|
-
this.checkEmptyStatements(node, node.statements, "until")
|
|
186
|
-
this.visitChildNodes(node)
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
visitERBWhenNode(node: ERBWhenNode): void {
|
|
190
|
-
if (!node.then_keyword) {
|
|
191
|
-
this.checkEmptyStatements(node, node.statements, "when")
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
this.visitChildNodes(node)
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
visitERBBeginNode(node: ERBBeginNode): void {
|
|
198
|
-
this.checkEmptyStatements(node, node.statements, "begin")
|
|
199
|
-
this.visitChildNodes(node)
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
visitERBRescueNode(node: ERBRescueNode): void {
|
|
203
|
-
this.checkEmptyStatements(node, node.statements, "rescue")
|
|
204
|
-
this.visitChildNodes(node)
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
visitERBEnsureNode(node: ERBEnsureNode): void {
|
|
208
|
-
this.checkEmptyStatements(node, node.statements, "ensure")
|
|
209
|
-
this.visitChildNodes(node)
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
visitERBBlockNode(node: ERBBlockNode): void {
|
|
213
|
-
this.checkEmptyStatements(node, node.body, "block")
|
|
214
|
-
this.visitChildNodes(node)
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
visitERBInNode(node: ERBInNode): void {
|
|
218
|
-
if (!node.then_keyword) {
|
|
219
|
-
this.checkEmptyStatements(node, node.statements, "in")
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
this.visitChildNodes(node)
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
private checkUnreachableChildren(children: Node[]): void {
|
|
226
|
-
for (const child of children) {
|
|
227
|
-
if (isHTMLTextNode(child) && child.content.trim() === "") {
|
|
228
|
-
continue
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
this.addDiagnostic(
|
|
232
|
-
child.location,
|
|
233
|
-
"Unreachable code: content between case and when/in is never executed"
|
|
234
|
-
)
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
private checkEmptyStatements(node: Node, statements: Node[], blockType: string): void {
|
|
239
|
-
this.checkEmptyStatementsWithEndLocation(node, statements, blockType, null)
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
private checkEmptyStatementsWithEndLocation(node: Node, statements: Node[], blockType: string, subsequentNode: Node | null): void {
|
|
243
|
-
if (this.statementsHaveContent(statements)) {
|
|
244
|
-
return
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
const startLocation = node.location.start
|
|
248
|
-
const endLocation = subsequentNode
|
|
249
|
-
? subsequentNode.location.start
|
|
250
|
-
: node.location.end
|
|
251
|
-
|
|
252
|
-
this.addDiagnostic(
|
|
253
|
-
{ start: startLocation, end: endLocation },
|
|
254
|
-
`Empty ${blockType} block: this control flow statement has no content`
|
|
255
|
-
)
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
private addDiagnostic(location: SerializedLocation, message: string): void {
|
|
259
|
-
const diagnostic: Diagnostic = {
|
|
260
|
-
range: lspRangeFromLocation(location),
|
|
261
|
-
message,
|
|
262
|
-
severity: DiagnosticSeverity.Hint,
|
|
263
|
-
tags: [DiagnosticTag.Unnecessary],
|
|
264
|
-
source: "Herb Language Server"
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
this.diagnostics.push(diagnostic)
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
private statementsHaveContent(statements: Node[]): boolean {
|
|
271
|
-
return statements.some(statement => {
|
|
272
|
-
if (isHTMLTextNode(statement)) {
|
|
273
|
-
return statement.content.trim() !== ""
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
return true
|
|
277
|
-
})
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
private checkAndMarkElseClause(elseClause: ERBElseNode | null): void {
|
|
281
|
-
if (!elseClause) {
|
|
282
|
-
return
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
this.processedElseNodes.add(elseClause)
|
|
286
|
-
if (!this.statementsHaveContent(elseClause.statements)) {
|
|
287
|
-
this.checkEmptyStatements(elseClause, elseClause.statements, "else")
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
private markIfChainAsProcessed(node: ERBIfNode): void {
|
|
292
|
-
this.processedIfNodes.add(node)
|
|
293
|
-
this.traverseSubsequentNodes(node.subsequent, (current) => {
|
|
294
|
-
if (isERBIfNode(current)) {
|
|
295
|
-
this.processedIfNodes.add(current)
|
|
296
|
-
}
|
|
297
|
-
})
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
private markElseNodesInIfChain(node: ERBIfNode): void {
|
|
301
|
-
this.traverseSubsequentNodes(node.subsequent, (current) => {
|
|
302
|
-
if (isERBElseNode(current)) {
|
|
303
|
-
this.processedElseNodes.add(current)
|
|
304
|
-
}
|
|
305
|
-
})
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
private traverseSubsequentNodes(startNode: Node | null, callback: (node: ERBIfNode | ERBElseNode) => void): void {
|
|
309
|
-
let current: Node | null = startNode
|
|
310
|
-
while (current) {
|
|
311
|
-
if (isERBIfNode(current)) {
|
|
312
|
-
callback(current)
|
|
313
|
-
current = current.subsequent
|
|
314
|
-
} else if (isERBElseNode(current)) {
|
|
315
|
-
callback(current)
|
|
316
|
-
break
|
|
317
|
-
} else {
|
|
318
|
-
break
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
private checkIfChainParts(node: ERBIfNode): void {
|
|
324
|
-
if (!this.statementsHaveContent(node.statements)) {
|
|
325
|
-
this.checkEmptyStatementsWithEndLocation(node, node.statements, "if", node.subsequent)
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
this.traverseSubsequentNodes(node.subsequent, (current) => {
|
|
329
|
-
if (this.statementsHaveContent(current.statements)) {
|
|
330
|
-
return
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
const blockType = isERBIfNode(current) ? 'elsif' : 'else'
|
|
334
|
-
const nextSubsequent = isERBIfNode(current) ? current.subsequent : null
|
|
335
|
-
|
|
336
|
-
if (nextSubsequent) {
|
|
337
|
-
this.checkEmptyStatementsWithEndLocation(current, current.statements, blockType, nextSubsequent)
|
|
338
|
-
} else {
|
|
339
|
-
this.checkEmptyStatements(current, current.statements, blockType)
|
|
340
|
-
}
|
|
341
|
-
})
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
private isEntireIfChainEmpty(node: ERBIfNode): boolean {
|
|
345
|
-
if (this.statementsHaveContent(node.statements)) {
|
|
346
|
-
return false
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
let hasContent = false
|
|
350
|
-
this.traverseSubsequentNodes(node.subsequent, (current) => {
|
|
351
|
-
if (this.statementsHaveContent(current.statements)) {
|
|
352
|
-
hasContent = true
|
|
353
|
-
}
|
|
354
|
-
})
|
|
355
|
-
|
|
356
|
-
return !hasContent
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
}
|
package/src/linter_service.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { Config } from "@herb-tools/config"
|
|
|
8
8
|
|
|
9
9
|
import { Settings } from "./settings"
|
|
10
10
|
import { Project } from "./project"
|
|
11
|
-
import { lintToDignosticSeverity } from "./utils"
|
|
11
|
+
import { lintToDignosticSeverity, lintToDignosticTags } from "./utils"
|
|
12
12
|
import { lspRangeFromLocation } from "./range_utils"
|
|
13
13
|
|
|
14
14
|
const OPEN_CONFIG_ACTION = 'Open .herb.yml'
|
|
@@ -179,7 +179,7 @@ export class LinterService {
|
|
|
179
179
|
: ruleDocumentationUrl(offense.rule)
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
|
|
182
|
+
const diagnostic: Diagnostic = {
|
|
183
183
|
source: this.source,
|
|
184
184
|
severity: lintToDignosticSeverity(offense.severity),
|
|
185
185
|
range,
|
|
@@ -188,6 +188,14 @@ export class LinterService {
|
|
|
188
188
|
data: { rule: offense.rule },
|
|
189
189
|
codeDescription
|
|
190
190
|
}
|
|
191
|
+
|
|
192
|
+
const tags = lintToDignosticTags(offense.tags)
|
|
193
|
+
|
|
194
|
+
if (tags.length > 0) {
|
|
195
|
+
diagnostic.tags = tags
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return diagnostic
|
|
191
199
|
})
|
|
192
200
|
|
|
193
201
|
return { diagnostics }
|
package/src/server.ts
CHANGED
|
@@ -186,6 +186,9 @@ export class Server {
|
|
|
186
186
|
|
|
187
187
|
if (!document) return []
|
|
188
188
|
|
|
189
|
+
const parseResult = this.service.parserService.parseDocument(document)
|
|
190
|
+
if (parseResult.diagnostics.length > 0) return []
|
|
191
|
+
|
|
189
192
|
const diagnostics = params.context.diagnostics
|
|
190
193
|
const documentText = document.getText()
|
|
191
194
|
|
package/src/utils.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { DiagnosticSeverity } from "vscode-languageserver/node"
|
|
1
|
+
import { DiagnosticSeverity, DiagnosticTag } from "vscode-languageserver/node"
|
|
2
2
|
import type { LintSeverity } from "@herb-tools/linter"
|
|
3
|
+
import type { DiagnosticSeverity as HerbDiagnosticSeverity, DiagnosticTag as HerbDiagnosticTag } from "@herb-tools/core"
|
|
3
4
|
|
|
4
5
|
export function camelize(value: string) {
|
|
5
6
|
return value.replace(/(?:[_-])([a-z0-9])/g, (_, char) => char.toUpperCase())
|
|
@@ -13,7 +14,7 @@ export function capitalize(value: string) {
|
|
|
13
14
|
return value.charAt(0).toUpperCase() + value.slice(1)
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
export function lintToDignosticSeverity(severity: LintSeverity): DiagnosticSeverity {
|
|
17
|
+
export function lintToDignosticSeverity(severity: LintSeverity | HerbDiagnosticSeverity): DiagnosticSeverity {
|
|
17
18
|
switch (severity) {
|
|
18
19
|
case "error": return DiagnosticSeverity.Error
|
|
19
20
|
case "warning": return DiagnosticSeverity.Warning
|
|
@@ -21,3 +22,15 @@ export function lintToDignosticSeverity(severity: LintSeverity): DiagnosticSever
|
|
|
21
22
|
case "hint": return DiagnosticSeverity.Hint
|
|
22
23
|
}
|
|
23
24
|
}
|
|
25
|
+
|
|
26
|
+
export function lintToDignosticTags(tags?: HerbDiagnosticTag[]): DiagnosticTag[] {
|
|
27
|
+
if (!tags) return []
|
|
28
|
+
|
|
29
|
+
return tags.flatMap(tag => {
|
|
30
|
+
switch (tag) {
|
|
31
|
+
case "unnecessary": return [DiagnosticTag.Unnecessary]
|
|
32
|
+
case "deprecated": return [DiagnosticTag.Deprecated]
|
|
33
|
+
default: return []
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
}
|