@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
package/dist/index.cjs
CHANGED
|
@@ -4,15 +4,15 @@ var node = require('vscode-languageserver/node');
|
|
|
4
4
|
var config = require('@herb-tools/config');
|
|
5
5
|
var formatter = require('@herb-tools/formatter');
|
|
6
6
|
var vscodeLanguageserverTextdocument = require('vscode-languageserver-textdocument');
|
|
7
|
-
var core = require('@herb-tools/core');
|
|
8
7
|
var nodeWasm = require('@herb-tools/node-wasm');
|
|
9
8
|
var linter = require('@herb-tools/linter');
|
|
10
9
|
var loader = require('@herb-tools/linter/loader');
|
|
11
10
|
var loader$1 = require('@herb-tools/rewriter/loader');
|
|
11
|
+
var core = require('@herb-tools/core');
|
|
12
12
|
var printer = require('@herb-tools/printer');
|
|
13
13
|
var rewriter = require('@herb-tools/rewriter');
|
|
14
14
|
|
|
15
|
-
var version = "0.9.
|
|
15
|
+
var version = "0.9.1";
|
|
16
16
|
|
|
17
17
|
class Settings {
|
|
18
18
|
constructor(params, connection) {
|
|
@@ -139,6 +139,48 @@ class DocumentService {
|
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
class Diagnostics {
|
|
143
|
+
constructor(connection, documentService, parserService, linterService, configService) {
|
|
144
|
+
this.diagnostics = new Map();
|
|
145
|
+
this.connection = connection;
|
|
146
|
+
this.documentService = documentService;
|
|
147
|
+
this.parserService = parserService;
|
|
148
|
+
this.linterService = linterService;
|
|
149
|
+
this.configService = configService;
|
|
150
|
+
}
|
|
151
|
+
async validate(textDocument) {
|
|
152
|
+
let allDiagnostics = [];
|
|
153
|
+
if (textDocument.uri.endsWith('.herb.yml')) {
|
|
154
|
+
allDiagnostics = await this.configService.validateDocument(textDocument);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
const parseResult = this.parserService.parseDocument(textDocument);
|
|
158
|
+
const lintResult = await this.linterService.lintDocument(textDocument);
|
|
159
|
+
allDiagnostics = [
|
|
160
|
+
...parseResult.diagnostics,
|
|
161
|
+
...lintResult.diagnostics,
|
|
162
|
+
];
|
|
163
|
+
}
|
|
164
|
+
this.diagnostics.set(textDocument, allDiagnostics);
|
|
165
|
+
this.sendDiagnosticsFor(textDocument);
|
|
166
|
+
}
|
|
167
|
+
async refreshDocument(document) {
|
|
168
|
+
await this.validate(document);
|
|
169
|
+
}
|
|
170
|
+
async refreshAllDocuments() {
|
|
171
|
+
const documents = this.documentService.getAll();
|
|
172
|
+
await Promise.all(documents.map(document => this.refreshDocument(document)));
|
|
173
|
+
}
|
|
174
|
+
sendDiagnosticsFor(textDocument) {
|
|
175
|
+
const diagnostics = this.diagnostics.get(textDocument) || [];
|
|
176
|
+
this.connection.sendDiagnostics({
|
|
177
|
+
uri: textDocument.uri,
|
|
178
|
+
diagnostics,
|
|
179
|
+
});
|
|
180
|
+
this.diagnostics.delete(textDocument);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
142
184
|
function lspPosition(herbPosition) {
|
|
143
185
|
return node.Position.create(herbPosition.line - 1, herbPosition.column);
|
|
144
186
|
}
|
|
@@ -203,265 +245,6 @@ function getFullDocumentRange(document) {
|
|
|
203
245
|
};
|
|
204
246
|
}
|
|
205
247
|
|
|
206
|
-
class Diagnostics {
|
|
207
|
-
constructor(connection, documentService, parserService, linterService, configService) {
|
|
208
|
-
this.diagnostics = new Map();
|
|
209
|
-
this.connection = connection;
|
|
210
|
-
this.documentService = documentService;
|
|
211
|
-
this.parserService = parserService;
|
|
212
|
-
this.linterService = linterService;
|
|
213
|
-
this.configService = configService;
|
|
214
|
-
}
|
|
215
|
-
async validate(textDocument) {
|
|
216
|
-
let allDiagnostics = [];
|
|
217
|
-
if (textDocument.uri.endsWith('.herb.yml')) {
|
|
218
|
-
allDiagnostics = await this.configService.validateDocument(textDocument);
|
|
219
|
-
}
|
|
220
|
-
else {
|
|
221
|
-
const parseResult = this.parserService.parseDocument(textDocument);
|
|
222
|
-
const lintResult = await this.linterService.lintDocument(textDocument);
|
|
223
|
-
const unreachableCodeDiagnostics = this.getUnreachableCodeDiagnostics(parseResult.document);
|
|
224
|
-
allDiagnostics = [
|
|
225
|
-
...parseResult.diagnostics,
|
|
226
|
-
...lintResult.diagnostics,
|
|
227
|
-
...unreachableCodeDiagnostics,
|
|
228
|
-
];
|
|
229
|
-
}
|
|
230
|
-
this.diagnostics.set(textDocument, allDiagnostics);
|
|
231
|
-
this.sendDiagnosticsFor(textDocument);
|
|
232
|
-
}
|
|
233
|
-
getUnreachableCodeDiagnostics(document) {
|
|
234
|
-
const collector = new UnreachableCodeCollector();
|
|
235
|
-
collector.visit(document);
|
|
236
|
-
return collector.diagnostics;
|
|
237
|
-
}
|
|
238
|
-
async refreshDocument(document) {
|
|
239
|
-
await this.validate(document);
|
|
240
|
-
}
|
|
241
|
-
async refreshAllDocuments() {
|
|
242
|
-
const documents = this.documentService.getAll();
|
|
243
|
-
await Promise.all(documents.map(document => this.refreshDocument(document)));
|
|
244
|
-
}
|
|
245
|
-
sendDiagnosticsFor(textDocument) {
|
|
246
|
-
const diagnostics = this.diagnostics.get(textDocument) || [];
|
|
247
|
-
this.connection.sendDiagnostics({
|
|
248
|
-
uri: textDocument.uri,
|
|
249
|
-
diagnostics,
|
|
250
|
-
});
|
|
251
|
-
this.diagnostics.delete(textDocument);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
class UnreachableCodeCollector extends core.Visitor {
|
|
255
|
-
constructor() {
|
|
256
|
-
super(...arguments);
|
|
257
|
-
this.diagnostics = [];
|
|
258
|
-
this.processedIfNodes = new Set();
|
|
259
|
-
this.processedElseNodes = new Set();
|
|
260
|
-
}
|
|
261
|
-
visitERBCaseNode(node) {
|
|
262
|
-
this.checkUnreachableChildren(node.children);
|
|
263
|
-
this.checkAndMarkElseClause(node.else_clause);
|
|
264
|
-
this.visitChildNodes(node);
|
|
265
|
-
}
|
|
266
|
-
visitERBCaseMatchNode(node) {
|
|
267
|
-
this.checkUnreachableChildren(node.children);
|
|
268
|
-
this.checkAndMarkElseClause(node.else_clause);
|
|
269
|
-
this.visitChildNodes(node);
|
|
270
|
-
}
|
|
271
|
-
visitERBIfNode(node) {
|
|
272
|
-
if (this.processedIfNodes.has(node)) {
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
this.markIfChainAsProcessed(node);
|
|
276
|
-
this.markElseNodesInIfChain(node);
|
|
277
|
-
const entireChainEmpty = this.isEntireIfChainEmpty(node);
|
|
278
|
-
if (entireChainEmpty) {
|
|
279
|
-
this.checkEmptyStatements(node, node.statements, "if");
|
|
280
|
-
}
|
|
281
|
-
else {
|
|
282
|
-
this.checkIfChainParts(node);
|
|
283
|
-
}
|
|
284
|
-
this.visitChildNodes(node);
|
|
285
|
-
}
|
|
286
|
-
visitERBElseNode(node) {
|
|
287
|
-
if (this.processedElseNodes.has(node)) {
|
|
288
|
-
this.visitChildNodes(node);
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
this.checkEmptyStatements(node, node.statements, "else");
|
|
292
|
-
this.visitChildNodes(node);
|
|
293
|
-
}
|
|
294
|
-
visitERBUnlessNode(node) {
|
|
295
|
-
const unlessHasContent = this.statementsHaveContent(node.statements);
|
|
296
|
-
const elseHasContent = node.else_clause && this.statementsHaveContent(node.else_clause.statements);
|
|
297
|
-
if (node.else_clause) {
|
|
298
|
-
this.processedElseNodes.add(node.else_clause);
|
|
299
|
-
}
|
|
300
|
-
const entireBlockEmpty = !unlessHasContent && !elseHasContent;
|
|
301
|
-
if (entireBlockEmpty) {
|
|
302
|
-
this.checkEmptyStatements(node, node.statements, "unless");
|
|
303
|
-
}
|
|
304
|
-
else {
|
|
305
|
-
if (!unlessHasContent) {
|
|
306
|
-
this.checkEmptyStatementsWithEndLocation(node, node.statements, "unless", node.else_clause);
|
|
307
|
-
}
|
|
308
|
-
if (node.else_clause && !elseHasContent) {
|
|
309
|
-
this.checkEmptyStatements(node.else_clause, node.else_clause.statements, "else");
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
this.visitChildNodes(node);
|
|
313
|
-
}
|
|
314
|
-
visitERBForNode(node) {
|
|
315
|
-
this.checkEmptyStatements(node, node.statements, "for");
|
|
316
|
-
this.visitChildNodes(node);
|
|
317
|
-
}
|
|
318
|
-
visitERBWhileNode(node) {
|
|
319
|
-
this.checkEmptyStatements(node, node.statements, "while");
|
|
320
|
-
this.visitChildNodes(node);
|
|
321
|
-
}
|
|
322
|
-
visitERBUntilNode(node) {
|
|
323
|
-
this.checkEmptyStatements(node, node.statements, "until");
|
|
324
|
-
this.visitChildNodes(node);
|
|
325
|
-
}
|
|
326
|
-
visitERBWhenNode(node) {
|
|
327
|
-
if (!node.then_keyword) {
|
|
328
|
-
this.checkEmptyStatements(node, node.statements, "when");
|
|
329
|
-
}
|
|
330
|
-
this.visitChildNodes(node);
|
|
331
|
-
}
|
|
332
|
-
visitERBBeginNode(node) {
|
|
333
|
-
this.checkEmptyStatements(node, node.statements, "begin");
|
|
334
|
-
this.visitChildNodes(node);
|
|
335
|
-
}
|
|
336
|
-
visitERBRescueNode(node) {
|
|
337
|
-
this.checkEmptyStatements(node, node.statements, "rescue");
|
|
338
|
-
this.visitChildNodes(node);
|
|
339
|
-
}
|
|
340
|
-
visitERBEnsureNode(node) {
|
|
341
|
-
this.checkEmptyStatements(node, node.statements, "ensure");
|
|
342
|
-
this.visitChildNodes(node);
|
|
343
|
-
}
|
|
344
|
-
visitERBBlockNode(node) {
|
|
345
|
-
this.checkEmptyStatements(node, node.body, "block");
|
|
346
|
-
this.visitChildNodes(node);
|
|
347
|
-
}
|
|
348
|
-
visitERBInNode(node) {
|
|
349
|
-
if (!node.then_keyword) {
|
|
350
|
-
this.checkEmptyStatements(node, node.statements, "in");
|
|
351
|
-
}
|
|
352
|
-
this.visitChildNodes(node);
|
|
353
|
-
}
|
|
354
|
-
checkUnreachableChildren(children) {
|
|
355
|
-
for (const child of children) {
|
|
356
|
-
if (core.isHTMLTextNode(child) && child.content.trim() === "") {
|
|
357
|
-
continue;
|
|
358
|
-
}
|
|
359
|
-
this.addDiagnostic(child.location, "Unreachable code: content between case and when/in is never executed");
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
checkEmptyStatements(node, statements, blockType) {
|
|
363
|
-
this.checkEmptyStatementsWithEndLocation(node, statements, blockType, null);
|
|
364
|
-
}
|
|
365
|
-
checkEmptyStatementsWithEndLocation(node, statements, blockType, subsequentNode) {
|
|
366
|
-
if (this.statementsHaveContent(statements)) {
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
369
|
-
const startLocation = node.location.start;
|
|
370
|
-
const endLocation = subsequentNode
|
|
371
|
-
? subsequentNode.location.start
|
|
372
|
-
: node.location.end;
|
|
373
|
-
this.addDiagnostic({ start: startLocation, end: endLocation }, `Empty ${blockType} block: this control flow statement has no content`);
|
|
374
|
-
}
|
|
375
|
-
addDiagnostic(location, message) {
|
|
376
|
-
const diagnostic = {
|
|
377
|
-
range: lspRangeFromLocation(location),
|
|
378
|
-
message,
|
|
379
|
-
severity: node.DiagnosticSeverity.Hint,
|
|
380
|
-
tags: [node.DiagnosticTag.Unnecessary],
|
|
381
|
-
source: "Herb Language Server"
|
|
382
|
-
};
|
|
383
|
-
this.diagnostics.push(diagnostic);
|
|
384
|
-
}
|
|
385
|
-
statementsHaveContent(statements) {
|
|
386
|
-
return statements.some(statement => {
|
|
387
|
-
if (core.isHTMLTextNode(statement)) {
|
|
388
|
-
return statement.content.trim() !== "";
|
|
389
|
-
}
|
|
390
|
-
return true;
|
|
391
|
-
});
|
|
392
|
-
}
|
|
393
|
-
checkAndMarkElseClause(elseClause) {
|
|
394
|
-
if (!elseClause) {
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
this.processedElseNodes.add(elseClause);
|
|
398
|
-
if (!this.statementsHaveContent(elseClause.statements)) {
|
|
399
|
-
this.checkEmptyStatements(elseClause, elseClause.statements, "else");
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
markIfChainAsProcessed(node) {
|
|
403
|
-
this.processedIfNodes.add(node);
|
|
404
|
-
this.traverseSubsequentNodes(node.subsequent, (current) => {
|
|
405
|
-
if (core.isERBIfNode(current)) {
|
|
406
|
-
this.processedIfNodes.add(current);
|
|
407
|
-
}
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
markElseNodesInIfChain(node) {
|
|
411
|
-
this.traverseSubsequentNodes(node.subsequent, (current) => {
|
|
412
|
-
if (core.isERBElseNode(current)) {
|
|
413
|
-
this.processedElseNodes.add(current);
|
|
414
|
-
}
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
|
-
traverseSubsequentNodes(startNode, callback) {
|
|
418
|
-
let current = startNode;
|
|
419
|
-
while (current) {
|
|
420
|
-
if (core.isERBIfNode(current)) {
|
|
421
|
-
callback(current);
|
|
422
|
-
current = current.subsequent;
|
|
423
|
-
}
|
|
424
|
-
else if (core.isERBElseNode(current)) {
|
|
425
|
-
callback(current);
|
|
426
|
-
break;
|
|
427
|
-
}
|
|
428
|
-
else {
|
|
429
|
-
break;
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
checkIfChainParts(node) {
|
|
434
|
-
if (!this.statementsHaveContent(node.statements)) {
|
|
435
|
-
this.checkEmptyStatementsWithEndLocation(node, node.statements, "if", node.subsequent);
|
|
436
|
-
}
|
|
437
|
-
this.traverseSubsequentNodes(node.subsequent, (current) => {
|
|
438
|
-
if (this.statementsHaveContent(current.statements)) {
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
441
|
-
const blockType = core.isERBIfNode(current) ? 'elsif' : 'else';
|
|
442
|
-
const nextSubsequent = core.isERBIfNode(current) ? current.subsequent : null;
|
|
443
|
-
if (nextSubsequent) {
|
|
444
|
-
this.checkEmptyStatementsWithEndLocation(current, current.statements, blockType, nextSubsequent);
|
|
445
|
-
}
|
|
446
|
-
else {
|
|
447
|
-
this.checkEmptyStatements(current, current.statements, blockType);
|
|
448
|
-
}
|
|
449
|
-
});
|
|
450
|
-
}
|
|
451
|
-
isEntireIfChainEmpty(node) {
|
|
452
|
-
if (this.statementsHaveContent(node.statements)) {
|
|
453
|
-
return false;
|
|
454
|
-
}
|
|
455
|
-
let hasContent = false;
|
|
456
|
-
this.traverseSubsequentNodes(node.subsequent, (current) => {
|
|
457
|
-
if (this.statementsHaveContent(current.statements)) {
|
|
458
|
-
hasContent = true;
|
|
459
|
-
}
|
|
460
|
-
});
|
|
461
|
-
return !hasContent;
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
|
|
465
248
|
class ErrorVisitor extends nodeWasm.Visitor {
|
|
466
249
|
constructor() {
|
|
467
250
|
super(...arguments);
|
|
@@ -520,6 +303,17 @@ function lintToDignosticSeverity(severity) {
|
|
|
520
303
|
case "hint": return node.DiagnosticSeverity.Hint;
|
|
521
304
|
}
|
|
522
305
|
}
|
|
306
|
+
function lintToDignosticTags(tags) {
|
|
307
|
+
if (!tags)
|
|
308
|
+
return [];
|
|
309
|
+
return tags.flatMap(tag => {
|
|
310
|
+
switch (tag) {
|
|
311
|
+
case "unnecessary": return [node.DiagnosticTag.Unnecessary];
|
|
312
|
+
case "deprecated": return [node.DiagnosticTag.Deprecated];
|
|
313
|
+
default: return [];
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
}
|
|
523
317
|
|
|
524
318
|
const OPEN_CONFIG_ACTION$1 = 'Open .herb.yml';
|
|
525
319
|
class LinterService {
|
|
@@ -651,7 +445,7 @@ class LinterService {
|
|
|
651
445
|
? `file://${customRulePath}`
|
|
652
446
|
: linter.ruleDocumentationUrl(offense.rule)
|
|
653
447
|
};
|
|
654
|
-
|
|
448
|
+
const diagnostic = {
|
|
655
449
|
source: this.source,
|
|
656
450
|
severity: lintToDignosticSeverity(offense.severity),
|
|
657
451
|
range,
|
|
@@ -660,6 +454,11 @@ class LinterService {
|
|
|
660
454
|
data: { rule: offense.rule },
|
|
661
455
|
codeDescription
|
|
662
456
|
};
|
|
457
|
+
const tags = lintToDignosticTags(offense.tags);
|
|
458
|
+
if (tags.length > 0) {
|
|
459
|
+
diagnostic.tags = tags;
|
|
460
|
+
}
|
|
461
|
+
return diagnostic;
|
|
663
462
|
});
|
|
664
463
|
return { diagnostics };
|
|
665
464
|
}
|
|
@@ -2678,6 +2477,9 @@ class Server {
|
|
|
2678
2477
|
const document = this.service.documentService.get(params.textDocument.uri);
|
|
2679
2478
|
if (!document)
|
|
2680
2479
|
return [];
|
|
2480
|
+
const parseResult = this.service.parserService.parseDocument(document);
|
|
2481
|
+
if (parseResult.diagnostics.length > 0)
|
|
2482
|
+
return [];
|
|
2681
2483
|
const diagnostics = params.context.diagnostics;
|
|
2682
2484
|
const documentText = document.getText();
|
|
2683
2485
|
const linterDisableCodeActions = this.service.codeActionService.createCodeActions(params.textDocument.uri, diagnostics, documentText);
|
|
@@ -2744,7 +2546,6 @@ exports.Project = Project;
|
|
|
2744
2546
|
exports.Server = Server;
|
|
2745
2547
|
exports.Service = Service;
|
|
2746
2548
|
exports.Settings = Settings;
|
|
2747
|
-
exports.UnreachableCodeCollector = UnreachableCodeCollector;
|
|
2748
2549
|
exports.camelize = camelize;
|
|
2749
2550
|
exports.capitalize = capitalize;
|
|
2750
2551
|
exports.dasherize = dasherize;
|
|
@@ -2752,6 +2553,7 @@ exports.erbTagToRange = erbTagToRange;
|
|
|
2752
2553
|
exports.getFullDocumentRange = getFullDocumentRange;
|
|
2753
2554
|
exports.isPositionInRange = isPositionInRange;
|
|
2754
2555
|
exports.lintToDignosticSeverity = lintToDignosticSeverity;
|
|
2556
|
+
exports.lintToDignosticTags = lintToDignosticTags;
|
|
2755
2557
|
exports.lspLine = lspLine;
|
|
2756
2558
|
exports.lspPosition = lspPosition;
|
|
2757
2559
|
exports.lspRangeFromLocation = lspRangeFromLocation;
|