@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/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.0";
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
- return {
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;