@herb-tools/language-server 0.8.9 → 0.9.0

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.
Files changed (82) hide show
  1. package/dist/action_view_helpers.js +19 -0
  2. package/dist/action_view_helpers.js.map +1 -0
  3. package/dist/autofix_service.js +1 -1
  4. package/dist/autofix_service.js.map +1 -1
  5. package/dist/code_action_service.js +3 -6
  6. package/dist/code_action_service.js.map +1 -1
  7. package/dist/comment_ast_utils.js +206 -0
  8. package/dist/comment_ast_utils.js.map +1 -0
  9. package/dist/comment_service.js +175 -0
  10. package/dist/comment_service.js.map +1 -0
  11. package/dist/diagnostics.js +15 -27
  12. package/dist/diagnostics.js.map +1 -1
  13. package/dist/document_highlight_service.js +196 -0
  14. package/dist/document_highlight_service.js.map +1 -0
  15. package/dist/document_save_service.js +16 -6
  16. package/dist/document_save_service.js.map +1 -1
  17. package/dist/folding_range_service.js +209 -0
  18. package/dist/folding_range_service.js.map +1 -0
  19. package/dist/formatting_service.js +4 -7
  20. package/dist/formatting_service.js.map +1 -1
  21. package/dist/herb-language-server.js +150276 -41207
  22. package/dist/herb-language-server.js.map +1 -1
  23. package/dist/hover_service.js +70 -0
  24. package/dist/hover_service.js.map +1 -0
  25. package/dist/index.cjs +1227 -66
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.js +4 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/line_context_collector.js +73 -0
  30. package/dist/line_context_collector.js.map +1 -0
  31. package/dist/linter_service.js +20 -4
  32. package/dist/linter_service.js.map +1 -1
  33. package/dist/parser_service.js +6 -5
  34. package/dist/parser_service.js.map +1 -1
  35. package/dist/range_utils.js +65 -0
  36. package/dist/range_utils.js.map +1 -0
  37. package/dist/rewrite_code_action_service.js +135 -0
  38. package/dist/rewrite_code_action_service.js.map +1 -0
  39. package/dist/server.js +36 -2
  40. package/dist/server.js.map +1 -1
  41. package/dist/service.js +10 -0
  42. package/dist/service.js.map +1 -1
  43. package/dist/types/action_view_helpers.d.ts +5 -0
  44. package/dist/types/comment_ast_utils.d.ts +20 -0
  45. package/dist/types/comment_service.d.ts +14 -0
  46. package/dist/types/diagnostics.d.ts +0 -1
  47. package/dist/types/document_highlight_service.d.ts +28 -0
  48. package/dist/types/document_save_service.d.ts +8 -0
  49. package/dist/types/folding_range_service.d.ts +35 -0
  50. package/dist/types/formatting_service.d.ts +1 -1
  51. package/dist/types/hover_service.d.ts +8 -0
  52. package/dist/types/index.d.ts +4 -0
  53. package/dist/types/line_context_collector.d.ts +19 -0
  54. package/dist/types/linter_service.d.ts +1 -0
  55. package/dist/types/parser_service.d.ts +2 -1
  56. package/dist/types/range_utils.d.ts +16 -0
  57. package/dist/types/rewrite_code_action_service.d.ts +11 -0
  58. package/dist/types/service.d.ts +10 -0
  59. package/dist/types/utils.d.ts +0 -6
  60. package/dist/utils.js +0 -16
  61. package/dist/utils.js.map +1 -1
  62. package/package.json +10 -5
  63. package/src/action_view_helpers.ts +23 -0
  64. package/src/autofix_service.ts +1 -1
  65. package/src/code_action_service.ts +3 -6
  66. package/src/comment_ast_utils.ts +282 -0
  67. package/src/comment_service.ts +228 -0
  68. package/src/diagnostics.ts +24 -38
  69. package/src/document_highlight_service.ts +267 -0
  70. package/src/document_save_service.ts +19 -7
  71. package/src/folding_range_service.ts +287 -0
  72. package/src/formatting_service.ts +4 -8
  73. package/src/hover_service.ts +90 -0
  74. package/src/index.ts +4 -0
  75. package/src/line_context_collector.ts +97 -0
  76. package/src/linter_service.ts +25 -7
  77. package/src/parser_service.ts +9 -10
  78. package/src/range_utils.ts +90 -0
  79. package/src/rewrite_code_action_service.ts +165 -0
  80. package/src/server.ts +51 -2
  81. package/src/service.ts +15 -0
  82. package/src/utils.ts +0 -22
package/dist/index.cjs CHANGED
@@ -9,8 +9,10 @@ var nodeWasm = require('@herb-tools/node-wasm');
9
9
  var linter = require('@herb-tools/linter');
10
10
  var loader = require('@herb-tools/linter/loader');
11
11
  var loader$1 = require('@herb-tools/rewriter/loader');
12
+ var printer = require('@herb-tools/printer');
13
+ var rewriter = require('@herb-tools/rewriter');
12
14
 
13
- var version = "0.8.9";
15
+ var version = "0.9.0";
14
16
 
15
17
  class Settings {
16
18
  constructor(params, connection) {
@@ -137,6 +139,70 @@ class DocumentService {
137
139
  }
138
140
  }
139
141
 
142
+ function lspPosition(herbPosition) {
143
+ return node.Position.create(herbPosition.line - 1, herbPosition.column);
144
+ }
145
+ function lspLine(herbPosition) {
146
+ return herbPosition.line - 1;
147
+ }
148
+ function lspRangeFromLocation(herbLocation) {
149
+ return node.Range.create(lspPosition(herbLocation.start), lspPosition(herbLocation.end));
150
+ }
151
+ function erbTagToRange(node$1) {
152
+ if (!node$1.tag_opening || !node$1.tag_closing)
153
+ return null;
154
+ return node.Range.create(lspPosition(node$1.tag_opening.location.start), lspPosition(node$1.tag_closing.location.end));
155
+ }
156
+ function tokenToRange(token) {
157
+ if (!token)
158
+ return null;
159
+ return lspRangeFromLocation(token.location);
160
+ }
161
+ function nodeToRange(node) {
162
+ return lspRangeFromLocation(node.location);
163
+ }
164
+ function openTagRanges(tag) {
165
+ const ranges = [];
166
+ if (tag.tag_opening && tag.tag_name) {
167
+ ranges.push(node.Range.create(lspPosition(tag.tag_opening.location.start), lspPosition(tag.tag_name.location.end)));
168
+ }
169
+ ranges.push(tokenToRange(tag.tag_closing));
170
+ return ranges;
171
+ }
172
+ function isPositionInRange(position, range) {
173
+ if (position.line < range.start.line || position.line > range.end.line) {
174
+ return false;
175
+ }
176
+ if (position.line === range.start.line && position.character < range.start.character) {
177
+ return false;
178
+ }
179
+ if (position.line === range.end.line && position.character > range.end.character) {
180
+ return false;
181
+ }
182
+ return true;
183
+ }
184
+ function rangeSize(range) {
185
+ if (range.start.line === range.end.line) {
186
+ return range.end.character - range.start.character;
187
+ }
188
+ return (range.end.line - range.start.line) * 10000 + range.end.character;
189
+ }
190
+ /**
191
+ * Returns a Range that spans the entire document
192
+ */
193
+ function getFullDocumentRange(document) {
194
+ const lastLine = document.lineCount - 1;
195
+ const lastLineText = document.getText({
196
+ start: node.Position.create(lastLine, 0),
197
+ end: node.Position.create(lastLine + 1, 0)
198
+ });
199
+ const lastLineLength = lastLineText.length;
200
+ return {
201
+ start: node.Position.create(0, 0),
202
+ end: node.Position.create(lastLine, lastLineLength)
203
+ };
204
+ }
205
+
140
206
  class Diagnostics {
141
207
  constructor(connection, documentService, parserService, linterService, configService) {
142
208
  this.diagnostics = new Map();
@@ -308,16 +374,7 @@ class UnreachableCodeCollector extends core.Visitor {
308
374
  }
309
375
  addDiagnostic(location, message) {
310
376
  const diagnostic = {
311
- range: {
312
- start: {
313
- line: this.toZeroBased(location.start.line),
314
- character: location.start.column
315
- },
316
- end: {
317
- line: this.toZeroBased(location.end.line),
318
- character: location.end.column
319
- }
320
- },
377
+ range: lspRangeFromLocation(location),
321
378
  message,
322
379
  severity: node.DiagnosticSeverity.Hint,
323
380
  tags: [node.DiagnosticTag.Unnecessary],
@@ -345,14 +402,14 @@ class UnreachableCodeCollector extends core.Visitor {
345
402
  markIfChainAsProcessed(node) {
346
403
  this.processedIfNodes.add(node);
347
404
  this.traverseSubsequentNodes(node.subsequent, (current) => {
348
- if (current.type === 'AST_ERB_IF_NODE') {
405
+ if (core.isERBIfNode(current)) {
349
406
  this.processedIfNodes.add(current);
350
407
  }
351
408
  });
352
409
  }
353
410
  markElseNodesInIfChain(node) {
354
411
  this.traverseSubsequentNodes(node.subsequent, (current) => {
355
- if (current.type === 'AST_ERB_ELSE_NODE') {
412
+ if (core.isERBElseNode(current)) {
356
413
  this.processedElseNodes.add(current);
357
414
  }
358
415
  });
@@ -360,10 +417,14 @@ class UnreachableCodeCollector extends core.Visitor {
360
417
  traverseSubsequentNodes(startNode, callback) {
361
418
  let current = startNode;
362
419
  while (current) {
363
- callback(current);
364
- if ('subsequent' in current) {
420
+ if (core.isERBIfNode(current)) {
421
+ callback(current);
365
422
  current = current.subsequent;
366
423
  }
424
+ else if (core.isERBElseNode(current)) {
425
+ callback(current);
426
+ break;
427
+ }
367
428
  else {
368
429
  break;
369
430
  }
@@ -374,14 +435,11 @@ class UnreachableCodeCollector extends core.Visitor {
374
435
  this.checkEmptyStatementsWithEndLocation(node, node.statements, "if", node.subsequent);
375
436
  }
376
437
  this.traverseSubsequentNodes(node.subsequent, (current) => {
377
- if (!('statements' in current) || !Array.isArray(current.statements)) {
378
- return;
379
- }
380
438
  if (this.statementsHaveContent(current.statements)) {
381
439
  return;
382
440
  }
383
- const blockType = current.type === 'AST_ERB_IF_NODE' ? 'elsif' : 'else';
384
- const nextSubsequent = 'subsequent' in current ? current.subsequent : null;
441
+ const blockType = core.isERBIfNode(current) ? 'elsif' : 'else';
442
+ const nextSubsequent = core.isERBIfNode(current) ? current.subsequent : null;
385
443
  if (nextSubsequent) {
386
444
  this.checkEmptyStatementsWithEndLocation(current, current.statements, blockType, nextSubsequent);
387
445
  }
@@ -396,17 +454,12 @@ class UnreachableCodeCollector extends core.Visitor {
396
454
  }
397
455
  let hasContent = false;
398
456
  this.traverseSubsequentNodes(node.subsequent, (current) => {
399
- if ('statements' in current && Array.isArray(current.statements)) {
400
- if (this.statementsHaveContent(current.statements)) {
401
- hasContent = true;
402
- }
457
+ if (this.statementsHaveContent(current.statements)) {
458
+ hasContent = true;
403
459
  }
404
460
  });
405
461
  return !hasContent;
406
462
  }
407
- toZeroBased(line) {
408
- return line - 1;
409
- }
410
463
  }
411
464
 
412
465
  class ErrorVisitor extends nodeWasm.Visitor {
@@ -423,7 +476,7 @@ class ErrorVisitor extends nodeWasm.Visitor {
423
476
  const diagnostic = {
424
477
  source: this.source,
425
478
  severity: node.DiagnosticSeverity.Error,
426
- range: this.rangeFromHerbError(error),
479
+ range: lspRangeFromLocation(error.location),
427
480
  message: error.message,
428
481
  code: error.type,
429
482
  data: {
@@ -433,9 +486,6 @@ class ErrorVisitor extends nodeWasm.Visitor {
433
486
  };
434
487
  this.diagnostics.push(diagnostic);
435
488
  }
436
- rangeFromHerbError(error) {
437
- return node.Range.create(node.Position.create(error.location.start.line - 1, error.location.start.column), node.Position.create(error.location.end.line - 1, error.location.end.column));
438
- }
439
489
  }
440
490
  class ParserService {
441
491
  parseDocument(textDocument) {
@@ -448,6 +498,9 @@ class ParserService {
448
498
  diagnostics: errorVisitor.diagnostics
449
499
  };
450
500
  }
501
+ parseContent(content, options) {
502
+ return nodeWasm.Herb.parse(content, options);
503
+ }
451
504
  }
452
505
 
453
506
  function camelize(value) {
@@ -467,21 +520,6 @@ function lintToDignosticSeverity(severity) {
467
520
  case "hint": return node.DiagnosticSeverity.Hint;
468
521
  }
469
522
  }
470
- /**
471
- * Returns a Range that spans the entire document
472
- */
473
- function getFullDocumentRange(document) {
474
- const lastLine = document.lineCount - 1;
475
- const lastLineText = document.getText({
476
- start: node.Position.create(lastLine, 0),
477
- end: node.Position.create(lastLine + 1, 0)
478
- });
479
- const lastLineLength = lastLineText.length;
480
- return {
481
- start: node.Position.create(0, 0),
482
- end: node.Position.create(lastLine, lastLineLength)
483
- };
484
- }
485
523
 
486
524
  const OPEN_CONFIG_ACTION$1 = 'Open .herb.yml';
487
525
  class LinterService {
@@ -563,8 +601,24 @@ class LinterService {
563
601
  this.connection.window.showWarningMessage(message);
564
602
  }
565
603
  }
604
+ shouldLintFile(uri) {
605
+ const filePath = uri.replace(/^file:\/\//, '');
606
+ if (filePath.endsWith('.herb.yml'))
607
+ return false;
608
+ const config$1 = this.settings.projectConfig;
609
+ if (!config$1)
610
+ return true;
611
+ const hasConfigFile = config.Config.exists(config$1.projectPath);
612
+ if (!hasConfigFile)
613
+ return true;
614
+ const relativePath = filePath.replace(this.project.projectPath + '/', '');
615
+ return config$1.isLinterEnabledForPath(relativePath);
616
+ }
566
617
  async lintDocument(textDocument) {
567
618
  var _a, _b, _c, _d;
619
+ if (!this.shouldLintFile(textDocument.uri)) {
620
+ return { diagnostics: [] };
621
+ }
568
622
  const settings = await this.settings.getDocumentSettings(textDocument.uri);
569
623
  const linterEnabled = (_b = (_a = settings === null || settings === void 0 ? void 0 : settings.linter) === null || _a === void 0 ? void 0 : _a.enabled) !== null && _b !== void 0 ? _b : true;
570
624
  if (!linterEnabled) {
@@ -590,12 +644,12 @@ class LinterService {
590
644
  const content = textDocument.getText();
591
645
  const lintResult = this.linter.lint(content, { fileName: textDocument.uri });
592
646
  const diagnostics = lintResult.offenses.map(offense => {
593
- const range = node.Range.create(node.Position.create(offense.location.start.line - 1, offense.location.start.column), node.Position.create(offense.location.end.line - 1, offense.location.end.column));
647
+ const range = lspRangeFromLocation(offense.location);
594
648
  const customRulePath = this.customRulePaths.get(offense.rule);
595
649
  const codeDescription = {
596
650
  href: customRulePath
597
651
  ? `file://${customRulePath}`
598
- : `https://herb-tools.dev/linter/rules/${offense.rule}`
652
+ : linter.ruleDocumentationUrl(offense.rule)
599
653
  };
600
654
  return {
601
655
  source: this.source,
@@ -770,7 +824,7 @@ class FormattingService {
770
824
  this.postRewriters = [];
771
825
  }
772
826
  }
773
- async formatOnSave(document, reason) {
827
+ async formatOnSave(document, reason, textOverride) {
774
828
  this.connection.console.log(`[Formatting] formatOnSave called for ${document.uri}`);
775
829
  if (reason !== node.TextDocumentSaveReason.Manual) {
776
830
  this.connection.console.log(`[Formatting] Skipping: reason=${reason} (not manual)`);
@@ -781,7 +835,7 @@ class FormattingService {
781
835
  this.connection.console.log(`[Formatting] Skipping: file not in formatter config`);
782
836
  return [];
783
837
  }
784
- return this.performFormatting({ textDocument: { uri: document.uri }, options: { tabSize: 2, insertSpaces: true } });
838
+ return this.performFormatting({ textDocument: { uri: document.uri }, options: { tabSize: 2, insertSpaces: true } }, textOverride);
785
839
  }
786
840
  shouldFormatFile(filePath) {
787
841
  if (filePath.endsWith('.herb.yml'))
@@ -809,13 +863,13 @@ class FormattingService {
809
863
  }
810
864
  };
811
865
  }
812
- async performFormatting(params) {
866
+ async performFormatting(params, textOverride) {
813
867
  const document = this.documents.get(params.textDocument.uri);
814
868
  if (!document) {
815
869
  return [];
816
870
  }
817
871
  try {
818
- const text = document.getText();
872
+ const text = textOverride !== null && textOverride !== void 0 ? textOverride : document.getText();
819
873
  const config = await this.getConfigWithSettings(params.textDocument.uri);
820
874
  this.connection.console.log(`[Formatting] Creating formatter with ${this.preRewriters.length} pre-rewriters, ${this.postRewriters.length} post-rewriters`);
821
875
  if (this.failedRewriters.size > 0) {
@@ -923,9 +977,6 @@ class FormattingService {
923
977
  return indentString + line;
924
978
  }).join('\n');
925
979
  }
926
- if (!formattedText.endsWith('\n')) {
927
- formattedText += '\n';
928
- }
929
980
  if (formattedText === rangeText) {
930
981
  return [];
931
982
  }
@@ -1290,10 +1341,7 @@ class CodeActionService {
1290
1341
  };
1291
1342
  }
1292
1343
  offenseToRange(offense) {
1293
- return {
1294
- start: node.Position.create(offense.location.start.line - 1, offense.location.start.column),
1295
- end: node.Position.create(offense.location.end.line - 1, offense.location.end.column)
1296
- };
1344
+ return lspRangeFromLocation(offense.location);
1297
1345
  }
1298
1346
  rangesEqual(r1, r2) {
1299
1347
  return (r1.start.line === r2.start.line &&
@@ -1312,6 +1360,14 @@ class CodeActionService {
1312
1360
 
1313
1361
  class DocumentSaveService {
1314
1362
  constructor(connection, settings, autofixService, formattingService) {
1363
+ /**
1364
+ * Tracks documents that were recently autofixed via applyFixesAndFormatting
1365
+ * (triggered by onDocumentFormatting). When editor.formatOnSave is enabled,
1366
+ * onDocumentFormatting fires BEFORE willSaveWaitUntil. If applyFixesAndFormatting
1367
+ * already applied autofix, applyFixes must skip to avoid conflicting edits
1368
+ * (since this.documents hasn't been updated between the two events).
1369
+ */
1370
+ this.recentlyAutofixedViaFormatting = new Set();
1315
1371
  this.connection = connection;
1316
1372
  this.settings = settings;
1317
1373
  this.autofixService = autofixService;
@@ -1328,6 +1384,10 @@ class DocumentSaveService {
1328
1384
  this.connection.console.log(`[DocumentSave] applyFixes fixOnSave=${fixOnSave}`);
1329
1385
  if (!fixOnSave)
1330
1386
  return [];
1387
+ if (this.recentlyAutofixedViaFormatting.delete(document.uri)) {
1388
+ this.connection.console.log(`[DocumentSave] applyFixes skipping: already autofixed via formatting`);
1389
+ return [];
1390
+ }
1331
1391
  return this.autofixService.autofix(document);
1332
1392
  }
1333
1393
  /**
@@ -1343,18 +1403,1066 @@ class DocumentSaveService {
1343
1403
  let autofixEdits = [];
1344
1404
  if (fixOnSave) {
1345
1405
  autofixEdits = await this.autofixService.autofix(document);
1406
+ if (autofixEdits.length > 0) {
1407
+ this.recentlyAutofixedViaFormatting.add(document.uri);
1408
+ }
1346
1409
  }
1347
1410
  if (!formatterEnabled)
1348
1411
  return autofixEdits;
1349
1412
  if (autofixEdits.length === 0) {
1350
1413
  return this.formattingService.formatOnSave(document, reason);
1351
1414
  }
1352
- const autofixedDocument = {
1353
- ...document,
1354
- uri: document.uri,
1355
- getText: () => autofixEdits[0].newText,
1415
+ return this.formattingService.formatOnSave(document, reason, autofixEdits[0].newText);
1416
+ }
1417
+ }
1418
+
1419
+ class FoldingRangeService {
1420
+ constructor(parserService) {
1421
+ this.parserService = parserService;
1422
+ }
1423
+ getFoldingRanges(textDocument) {
1424
+ const parseResult = this.parserService.parseDocument(textDocument);
1425
+ const collector = new FoldingRangeCollector();
1426
+ collector.visit(parseResult.document);
1427
+ return collector.ranges;
1428
+ }
1429
+ }
1430
+ class FoldingRangeCollector extends core.Visitor {
1431
+ constructor() {
1432
+ super(...arguments);
1433
+ this.ranges = [];
1434
+ this.processedIfNodes = new Set();
1435
+ }
1436
+ visitHTMLElementNode(node) {
1437
+ if (node.body.length > 0 && node.open_tag && node.close_tag) {
1438
+ this.addRange(node.open_tag.location.end, node.close_tag.location.start);
1439
+ }
1440
+ this.visitChildNodes(node);
1441
+ }
1442
+ visitHTMLOpenTagNode(node) {
1443
+ if (node.children.length > 0 && node.tag_opening && node.tag_closing) {
1444
+ this.addRange(node.tag_opening.location.end, node.tag_closing.location.start);
1445
+ }
1446
+ this.visitChildNodes(node);
1447
+ }
1448
+ visitHTMLCommentNode(node$1) {
1449
+ if (node$1.comment_start && node$1.comment_end) {
1450
+ this.addRange(node$1.comment_start.location.end, node$1.comment_end.location.start, node.FoldingRangeKind.Comment);
1451
+ }
1452
+ this.visitChildNodes(node$1);
1453
+ }
1454
+ visitHTMLAttributeValueNode(node) {
1455
+ if (node.children.length > 0) {
1456
+ const first = node.children[0];
1457
+ const last = node.children[node.children.length - 1];
1458
+ this.addRange(first.location.start, last.location.end);
1459
+ }
1460
+ this.visitChildNodes(node);
1461
+ }
1462
+ visitCDATANode(node) {
1463
+ this.addRange(node.location.start, node.location.end);
1464
+ this.visitChildNodes(node);
1465
+ }
1466
+ visitHTMLConditionalElementNode(node) {
1467
+ this.addRange(node.location.start, node.location.end);
1468
+ this.visitChildNodes(node);
1469
+ }
1470
+ visitERBNode(node) {
1471
+ var _a;
1472
+ if (node.tag_closing && 'end_node' in node && ((_a = node.end_node) === null || _a === void 0 ? void 0 : _a.tag_opening)) {
1473
+ this.addRange(node.tag_closing.location.end, node.end_node.tag_opening.location.start);
1474
+ }
1475
+ else {
1476
+ this.addRange(node.location.start, node.location.end);
1477
+ }
1478
+ }
1479
+ visitERBContentNode(node) {
1480
+ if (node.tag_opening && node.tag_closing) {
1481
+ this.addRange(node.tag_opening.location.end, node.tag_closing.location.start);
1482
+ }
1483
+ this.visitChildNodes(node);
1484
+ }
1485
+ visitERBIfNode(node) {
1486
+ var _a, _b;
1487
+ if (this.processedIfNodes.has(node)) {
1488
+ this.visitChildNodes(node);
1489
+ return;
1490
+ }
1491
+ this.markIfChainAsProcessed(node);
1492
+ const nextAfterIf = (_a = node.subsequent) !== null && _a !== void 0 ? _a : node.end_node;
1493
+ if (node.tag_closing && (nextAfterIf === null || nextAfterIf === void 0 ? void 0 : nextAfterIf.tag_opening)) {
1494
+ this.addRange(node.tag_closing.location.end, nextAfterIf.tag_opening.location.start);
1495
+ }
1496
+ let current = node.subsequent;
1497
+ while (current) {
1498
+ if (core.isERBIfNode(current)) {
1499
+ const nextAfterElsif = (_b = current.subsequent) !== null && _b !== void 0 ? _b : node.end_node;
1500
+ if (current.tag_closing && (nextAfterElsif === null || nextAfterElsif === void 0 ? void 0 : nextAfterElsif.tag_opening)) {
1501
+ this.addRange(current.tag_closing.location.end, nextAfterElsif.tag_opening.location.start);
1502
+ }
1503
+ current = current.subsequent;
1504
+ }
1505
+ else {
1506
+ break;
1507
+ }
1508
+ }
1509
+ this.visitChildNodes(node);
1510
+ }
1511
+ visitERBUnlessNode(node) {
1512
+ var _a, _b;
1513
+ const nextAfterUnless = (_a = node.else_clause) !== null && _a !== void 0 ? _a : node.end_node;
1514
+ if (node.tag_closing && (nextAfterUnless === null || nextAfterUnless === void 0 ? void 0 : nextAfterUnless.tag_opening)) {
1515
+ this.addRange(node.tag_closing.location.end, nextAfterUnless.tag_opening.location.start);
1516
+ }
1517
+ if (node.else_clause) {
1518
+ if (node.else_clause.tag_closing && ((_b = node.end_node) === null || _b === void 0 ? void 0 : _b.tag_opening)) {
1519
+ this.addRange(node.else_clause.tag_closing.location.end, node.end_node.tag_opening.location.start);
1520
+ }
1521
+ }
1522
+ this.visitChildNodes(node);
1523
+ }
1524
+ visitERBCaseNode(node) {
1525
+ this.addCaseFoldingRanges(node);
1526
+ this.visitChildNodes(node);
1527
+ }
1528
+ visitERBCaseMatchNode(node) {
1529
+ this.addCaseFoldingRanges(node);
1530
+ this.visitChildNodes(node);
1531
+ }
1532
+ visitERBWhenNode(node) {
1533
+ this.visitChildNodes(node);
1534
+ }
1535
+ visitERBInNode(node) {
1536
+ this.visitChildNodes(node);
1537
+ }
1538
+ visitERBBeginNode(node) {
1539
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1540
+ const nextAfterBegin = (_c = (_b = (_a = node.rescue_clause) !== null && _a !== void 0 ? _a : node.else_clause) !== null && _b !== void 0 ? _b : node.ensure_clause) !== null && _c !== void 0 ? _c : node.end_node;
1541
+ if (node.tag_closing && (nextAfterBegin === null || nextAfterBegin === void 0 ? void 0 : nextAfterBegin.tag_opening)) {
1542
+ this.addRange(node.tag_closing.location.end, nextAfterBegin.tag_opening.location.start);
1543
+ }
1544
+ let rescue = node.rescue_clause;
1545
+ while (rescue) {
1546
+ const nextAfterRescue = (_f = (_e = (_d = rescue.subsequent) !== null && _d !== void 0 ? _d : node.else_clause) !== null && _e !== void 0 ? _e : node.ensure_clause) !== null && _f !== void 0 ? _f : node.end_node;
1547
+ if (rescue.tag_closing && (nextAfterRescue === null || nextAfterRescue === void 0 ? void 0 : nextAfterRescue.tag_opening)) {
1548
+ this.addRange(rescue.tag_closing.location.end, nextAfterRescue.tag_opening.location.start);
1549
+ }
1550
+ rescue = rescue.subsequent;
1551
+ }
1552
+ if (node.else_clause) {
1553
+ const nextAfterElse = (_g = node.ensure_clause) !== null && _g !== void 0 ? _g : node.end_node;
1554
+ if (node.else_clause.tag_closing && (nextAfterElse === null || nextAfterElse === void 0 ? void 0 : nextAfterElse.tag_opening)) {
1555
+ this.addRange(node.else_clause.tag_closing.location.end, nextAfterElse.tag_opening.location.start);
1556
+ }
1557
+ }
1558
+ if (node.ensure_clause) {
1559
+ if (node.ensure_clause.tag_closing && ((_h = node.end_node) === null || _h === void 0 ? void 0 : _h.tag_opening)) {
1560
+ this.addRange(node.ensure_clause.tag_closing.location.end, node.end_node.tag_opening.location.start);
1561
+ }
1562
+ }
1563
+ this.visitChildNodes(node);
1564
+ }
1565
+ visitERBRescueNode(node) {
1566
+ this.visitChildNodes(node);
1567
+ }
1568
+ visitERBElseNode(node) {
1569
+ this.addRange(node.location.start, node.location.end);
1570
+ this.visitChildNodes(node);
1571
+ }
1572
+ visitERBEnsureNode(node) {
1573
+ this.visitChildNodes(node);
1574
+ }
1575
+ markIfChainAsProcessed(node) {
1576
+ this.processedIfNodes.add(node);
1577
+ let current = node.subsequent;
1578
+ while (current) {
1579
+ if (core.isERBIfNode(current)) {
1580
+ this.processedIfNodes.add(current);
1581
+ current = current.subsequent;
1582
+ }
1583
+ else {
1584
+ break;
1585
+ }
1586
+ }
1587
+ }
1588
+ addCaseFoldingRanges(node) {
1589
+ var _a, _b, _c, _d;
1590
+ const conditions = node.conditions;
1591
+ const firstCondition = conditions[0];
1592
+ const nextAfterCase = (_a = firstCondition !== null && firstCondition !== void 0 ? firstCondition : node.else_clause) !== null && _a !== void 0 ? _a : node.end_node;
1593
+ if (node.tag_closing && (nextAfterCase === null || nextAfterCase === void 0 ? void 0 : nextAfterCase.tag_opening)) {
1594
+ this.addRange(node.tag_closing.location.end, nextAfterCase.tag_opening.location.start);
1595
+ }
1596
+ for (let i = 0; i < conditions.length; i++) {
1597
+ const condition = conditions[i];
1598
+ const nextCondition = (_c = (_b = conditions[i + 1]) !== null && _b !== void 0 ? _b : node.else_clause) !== null && _c !== void 0 ? _c : node.end_node;
1599
+ if (condition.tag_closing && (nextCondition === null || nextCondition === void 0 ? void 0 : nextCondition.tag_opening)) {
1600
+ this.addRange(condition.tag_closing.location.end, nextCondition.tag_opening.location.start);
1601
+ }
1602
+ }
1603
+ if (node.else_clause) {
1604
+ if (node.else_clause.tag_closing && ((_d = node.end_node) === null || _d === void 0 ? void 0 : _d.tag_opening)) {
1605
+ this.addRange(node.else_clause.tag_closing.location.end, node.end_node.tag_opening.location.start);
1606
+ }
1607
+ }
1608
+ }
1609
+ addRange(start, end, kind) {
1610
+ const startLine = lspLine(start);
1611
+ const endLine = lspLine(end) - 1;
1612
+ if (endLine > startLine) {
1613
+ this.ranges.push({
1614
+ startLine,
1615
+ startCharacter: start.column,
1616
+ endLine,
1617
+ endCharacter: end.column,
1618
+ kind,
1619
+ });
1620
+ }
1621
+ }
1622
+ }
1623
+
1624
+ class DocumentHighlightCollector extends core.Visitor {
1625
+ constructor() {
1626
+ super(...arguments);
1627
+ this.groups = [];
1628
+ this.processedIfNodes = new Set();
1629
+ }
1630
+ visitERBNode(node) {
1631
+ if ('end_node' in node && node.end_node) {
1632
+ this.addGroup([erbTagToRange(node), erbTagToRange(node.end_node)]);
1633
+ }
1634
+ }
1635
+ visitERBContentNode(node) {
1636
+ this.addGroup([tokenToRange(node.tag_opening), tokenToRange(node.tag_closing)]);
1637
+ this.visitChildNodes(node);
1638
+ }
1639
+ visitHTMLCommentNode(node) {
1640
+ this.addGroup([tokenToRange(node.comment_start), tokenToRange(node.comment_end)]);
1641
+ this.visitChildNodes(node);
1642
+ }
1643
+ visitHTMLElementNode(node) {
1644
+ const ranges = [];
1645
+ if (node.open_tag && core.isHTMLOpenTagNode(node.open_tag)) {
1646
+ ranges.push(...openTagRanges(node.open_tag));
1647
+ }
1648
+ else if (node.open_tag) {
1649
+ ranges.push(nodeToRange(node.open_tag));
1650
+ }
1651
+ if (node.close_tag) {
1652
+ ranges.push(nodeToRange(node.close_tag));
1653
+ }
1654
+ this.addGroup(ranges);
1655
+ this.visitChildNodes(node);
1656
+ }
1657
+ visitHTMLAttributeNode(node) {
1658
+ const ranges = [];
1659
+ if (node.name) {
1660
+ ranges.push(nodeToRange(node.name));
1661
+ }
1662
+ if (node.equals) {
1663
+ ranges.push(tokenToRange(node.equals));
1664
+ }
1665
+ if (node.value) {
1666
+ ranges.push(nodeToRange(node.value));
1667
+ }
1668
+ this.addGroup(ranges);
1669
+ this.visitChildNodes(node);
1670
+ }
1671
+ visitHTMLConditionalElementNode(node) {
1672
+ const ranges = [];
1673
+ if (node.open_conditional) {
1674
+ ranges.push(erbTagToRange(node.open_conditional));
1675
+ }
1676
+ if (node.open_tag) {
1677
+ ranges.push(...openTagRanges(node.open_tag));
1678
+ }
1679
+ if (node.close_tag) {
1680
+ ranges.push(nodeToRange(node.close_tag));
1681
+ }
1682
+ if (node.close_conditional) {
1683
+ ranges.push(erbTagToRange(node.close_conditional));
1684
+ }
1685
+ this.addGroup(ranges);
1686
+ this.visitChildNodes(node);
1687
+ }
1688
+ visitERBIfNode(node) {
1689
+ if (this.processedIfNodes.has(node)) {
1690
+ this.visitChildNodes(node);
1691
+ return;
1692
+ }
1693
+ this.markIfChainAsProcessed(node);
1694
+ const ranges = [];
1695
+ ranges.push(erbTagToRange(node));
1696
+ let current = node.subsequent;
1697
+ while (current) {
1698
+ if (core.isERBIfNode(current)) {
1699
+ ranges.push(erbTagToRange(current));
1700
+ current = current.subsequent;
1701
+ }
1702
+ else if (core.isERBElseNode(current)) {
1703
+ ranges.push(erbTagToRange(current));
1704
+ break;
1705
+ }
1706
+ else {
1707
+ break;
1708
+ }
1709
+ }
1710
+ if (node.end_node) {
1711
+ ranges.push(erbTagToRange(node.end_node));
1712
+ }
1713
+ this.addGroup(ranges);
1714
+ this.visitChildNodes(node);
1715
+ }
1716
+ visitERBUnlessNode(node) {
1717
+ const ranges = [];
1718
+ ranges.push(erbTagToRange(node));
1719
+ if (node.else_clause) {
1720
+ ranges.push(erbTagToRange(node.else_clause));
1721
+ }
1722
+ if (node.end_node) {
1723
+ ranges.push(erbTagToRange(node.end_node));
1724
+ }
1725
+ this.addGroup(ranges);
1726
+ this.visitChildNodes(node);
1727
+ }
1728
+ visitERBCaseNode(node) {
1729
+ this.visitERBAnyCaseNode(node);
1730
+ }
1731
+ visitERBCaseMatchNode(node) {
1732
+ this.visitERBAnyCaseNode(node);
1733
+ }
1734
+ visitERBAnyCaseNode(node) {
1735
+ const ranges = [];
1736
+ ranges.push(erbTagToRange(node));
1737
+ for (const condition of node.conditions) {
1738
+ ranges.push(erbTagToRange(condition));
1739
+ }
1740
+ if (node.else_clause) {
1741
+ ranges.push(erbTagToRange(node.else_clause));
1742
+ }
1743
+ if (node.end_node) {
1744
+ ranges.push(erbTagToRange(node.end_node));
1745
+ }
1746
+ this.addGroup(ranges);
1747
+ this.visitChildNodes(node);
1748
+ }
1749
+ visitERBBeginNode(node) {
1750
+ const ranges = [];
1751
+ ranges.push(erbTagToRange(node));
1752
+ let rescue = node.rescue_clause;
1753
+ while (rescue) {
1754
+ ranges.push(erbTagToRange(rescue));
1755
+ rescue = rescue.subsequent;
1756
+ }
1757
+ if (node.else_clause) {
1758
+ ranges.push(erbTagToRange(node.else_clause));
1759
+ }
1760
+ if (node.ensure_clause) {
1761
+ ranges.push(erbTagToRange(node.ensure_clause));
1762
+ }
1763
+ if (node.end_node) {
1764
+ ranges.push(erbTagToRange(node.end_node));
1765
+ }
1766
+ this.addGroup(ranges);
1767
+ this.visitChildNodes(node);
1768
+ }
1769
+ markIfChainAsProcessed(node) {
1770
+ this.processedIfNodes.add(node);
1771
+ let current = node.subsequent;
1772
+ while (current) {
1773
+ if (core.isERBIfNode(current)) {
1774
+ this.processedIfNodes.add(current);
1775
+ current = current.subsequent;
1776
+ }
1777
+ else {
1778
+ break;
1779
+ }
1780
+ }
1781
+ }
1782
+ addGroup(ranges) {
1783
+ const filtered = ranges.filter((r) => r !== null);
1784
+ if (filtered.length >= 2) {
1785
+ this.groups.push(filtered);
1786
+ }
1787
+ }
1788
+ }
1789
+ class DocumentHighlightService {
1790
+ constructor(parserService) {
1791
+ this.parserService = parserService;
1792
+ }
1793
+ getDocumentHighlights(textDocument, position) {
1794
+ const parseResult = this.parserService.parseDocument(textDocument);
1795
+ const collector = new DocumentHighlightCollector();
1796
+ collector.visit(parseResult.document);
1797
+ let bestGroup = null;
1798
+ let bestSize = Infinity;
1799
+ for (const group of collector.groups) {
1800
+ const matchingRange = group.find(range => isPositionInRange(position, range));
1801
+ if (matchingRange) {
1802
+ const size = rangeSize(matchingRange);
1803
+ if (size < bestSize) {
1804
+ bestSize = size;
1805
+ bestGroup = group;
1806
+ }
1807
+ }
1808
+ }
1809
+ if (bestGroup) {
1810
+ return bestGroup.map(range => node.DocumentHighlight.create(range, node.DocumentHighlightKind.Text));
1811
+ }
1812
+ return [];
1813
+ }
1814
+ }
1815
+
1816
+ const ACTION_VIEW_HELPERS = {
1817
+ "ActionView::Helpers::TagHelper#tag": {
1818
+ signature: "tag.<tag name>(optional content, options)",
1819
+ documentationURL: "https://api.rubyonrails.org/classes/ActionView/Helpers/TagHelper.html#method-i-tag",
1820
+ },
1821
+ "ActionView::Helpers::TagHelper#content_tag": {
1822
+ signature: "content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block)",
1823
+ documentationURL: "https://api.rubyonrails.org/classes/ActionView/Helpers/TagHelper.html#method-i-content_tag",
1824
+ },
1825
+ "ActionView::Helpers::UrlHelper#link_to": {
1826
+ signature: "link_to(name = nil, options = nil, html_options = nil, &block)",
1827
+ documentationURL: "https://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to",
1828
+ },
1829
+ "Turbo::FramesHelper#turbo_frame_tag": {
1830
+ signature: "turbo_frame_tag(*ids, src: nil, target: nil, **attributes, &block)",
1831
+ documentationURL: "https://www.rubydoc.info/github/hotwired/turbo-rails/Turbo/FramesHelper:turbo_frame_tag",
1832
+ },
1833
+ };
1834
+
1835
+ class ActionViewElementCollector extends nodeWasm.Visitor {
1836
+ constructor() {
1837
+ super(...arguments);
1838
+ this.elements = [];
1839
+ }
1840
+ visitHTMLElementNode(node) {
1841
+ if (node.element_source && node.element_source !== "HTML" && core.isERBOpenTagNode(node.open_tag)) {
1842
+ this.elements.push({
1843
+ node,
1844
+ range: nodeToRange(node),
1845
+ });
1846
+ }
1847
+ this.visitChildNodes(node);
1848
+ }
1849
+ }
1850
+ class HoverService {
1851
+ constructor(parserService) {
1852
+ this.parserService = parserService;
1853
+ }
1854
+ getHover(textDocument, position) {
1855
+ const parseResult = this.parserService.parseContent(textDocument.getText(), {
1856
+ action_view_helpers: true,
1857
+ track_whitespace: true,
1858
+ });
1859
+ const collector = new ActionViewElementCollector();
1860
+ collector.visit(parseResult.value);
1861
+ let bestElement = null;
1862
+ let bestSize = Infinity;
1863
+ for (const element of collector.elements) {
1864
+ if (isPositionInRange(position, element.range)) {
1865
+ const size = rangeSize(element.range);
1866
+ if (size < bestSize) {
1867
+ bestSize = size;
1868
+ bestElement = element;
1869
+ }
1870
+ }
1871
+ }
1872
+ if (!bestElement) {
1873
+ return null;
1874
+ }
1875
+ const elementSource = bestElement.node.element_source;
1876
+ const rewriter$1 = new rewriter.ActionViewTagHelperToHTMLRewriter();
1877
+ const rewrittenNode = rewriter$1.rewrite(bestElement.node, { baseDir: process.cwd() });
1878
+ const htmlOutput = printer.IdentityPrinter.print(rewrittenNode);
1879
+ const helper = ACTION_VIEW_HELPERS[elementSource];
1880
+ const parts = [];
1881
+ if (helper) {
1882
+ parts.push(`\`\`\`ruby\n${helper.signature}\n\`\`\``);
1883
+ }
1884
+ parts.push(`**HTML equivalent**\n\`\`\`erb\n${htmlOutput.trim()}\n\`\`\``);
1885
+ if (helper) {
1886
+ parts.push(`[${elementSource}](${helper.documentationURL})`);
1887
+ }
1888
+ return {
1889
+ contents: {
1890
+ kind: node.MarkupKind.Markdown,
1891
+ value: parts.join("\n\n"),
1892
+ },
1893
+ range: bestElement.range,
1894
+ };
1895
+ }
1896
+ }
1897
+
1898
+ class ElementCollector extends nodeWasm.Visitor {
1899
+ constructor() {
1900
+ super(...arguments);
1901
+ this.actionViewElements = [];
1902
+ this.htmlElements = [];
1903
+ }
1904
+ visitHTMLElementNode(node) {
1905
+ if (node.element_source && node.element_source !== "HTML" && core.isERBOpenTagNode(node.open_tag)) {
1906
+ this.actionViewElements.push({
1907
+ node,
1908
+ range: nodeToRange(node),
1909
+ });
1910
+ }
1911
+ else if (core.isHTMLOpenTagNode(node.open_tag) && node.open_tag.tag_name) {
1912
+ this.htmlElements.push({
1913
+ node,
1914
+ range: nodeToRange(node),
1915
+ });
1916
+ }
1917
+ this.visitChildNodes(node);
1918
+ }
1919
+ }
1920
+ class RewriteCodeActionService {
1921
+ constructor(parserService) {
1922
+ this.parserService = parserService;
1923
+ }
1924
+ getCodeActions(document, requestedRange) {
1925
+ const parseResult = this.parserService.parseContent(document.getText(), {
1926
+ action_view_helpers: true,
1927
+ track_whitespace: true,
1928
+ });
1929
+ const collector = new ElementCollector();
1930
+ collector.visit(parseResult.value);
1931
+ const actions = [];
1932
+ for (const element of collector.actionViewElements) {
1933
+ if (!this.rangesOverlap(element.range, requestedRange))
1934
+ continue;
1935
+ const action = this.createActionViewToHTMLAction(document, element);
1936
+ if (action) {
1937
+ actions.push(action);
1938
+ }
1939
+ }
1940
+ for (const element of collector.htmlElements) {
1941
+ if (!this.rangesOverlap(element.range, requestedRange))
1942
+ continue;
1943
+ const action = this.createHTMLToActionViewAction(document, element);
1944
+ if (action) {
1945
+ actions.push(action);
1946
+ }
1947
+ }
1948
+ return actions;
1949
+ }
1950
+ createActionViewToHTMLAction(document, element) {
1951
+ var _a;
1952
+ const originalText = document.getText(element.range);
1953
+ const parseResult = this.parserService.parseContent(originalText, {
1954
+ action_view_helpers: true,
1955
+ track_whitespace: true,
1956
+ });
1957
+ if (parseResult.failed)
1958
+ return null;
1959
+ const rewriter$1 = new rewriter.ActionViewTagHelperToHTMLRewriter();
1960
+ rewriter$1.rewrite(parseResult.value, { baseDir: process.cwd() });
1961
+ const rewrittenText = printer.IdentityPrinter.print(parseResult.value);
1962
+ if (rewrittenText === originalText)
1963
+ return null;
1964
+ const edit = {
1965
+ changes: {
1966
+ [document.uri]: [node.TextEdit.replace(element.range, rewrittenText)]
1967
+ }
1968
+ };
1969
+ const tagName = (_a = element.node.tag_name) === null || _a === void 0 ? void 0 : _a.value;
1970
+ const title = tagName
1971
+ ? `Herb: Convert to \`<${tagName}>\``
1972
+ : "Herb: Convert to HTML";
1973
+ return {
1974
+ title,
1975
+ kind: node.CodeActionKind.RefactorRewrite,
1976
+ edit,
1356
1977
  };
1357
- return this.formattingService.formatOnSave(autofixedDocument, reason);
1978
+ }
1979
+ createHTMLToActionViewAction(document, element) {
1980
+ var _a;
1981
+ const originalText = document.getText(element.range);
1982
+ const parseResult = this.parserService.parseContent(originalText, {
1983
+ track_whitespace: true,
1984
+ });
1985
+ if (parseResult.failed)
1986
+ return null;
1987
+ const rewriter$1 = new rewriter.HTMLToActionViewTagHelperRewriter();
1988
+ rewriter$1.rewrite(parseResult.value, { baseDir: process.cwd() });
1989
+ const rewrittenText = printer.IdentityPrinter.print(parseResult.value);
1990
+ if (rewrittenText === originalText)
1991
+ return null;
1992
+ const edit = {
1993
+ changes: {
1994
+ [document.uri]: [node.TextEdit.replace(element.range, rewrittenText)]
1995
+ }
1996
+ };
1997
+ const tagName = (_a = element.node.tag_name) === null || _a === void 0 ? void 0 : _a.value;
1998
+ const isAnchor = tagName === "a";
1999
+ const isTurboFrame = tagName === "turbo-frame";
2000
+ const methodName = tagName === null || tagName === void 0 ? void 0 : tagName.replace(/-/g, "_");
2001
+ const title = isAnchor
2002
+ ? "Herb: Convert to `link_to`"
2003
+ : isTurboFrame
2004
+ ? "Herb: Convert to `turbo_frame_tag`"
2005
+ : methodName
2006
+ ? `Herb: Convert to \`tag.${methodName}\``
2007
+ : "Herb: Convert to tag helper";
2008
+ return {
2009
+ title,
2010
+ kind: node.CodeActionKind.RefactorRewrite,
2011
+ edit,
2012
+ };
2013
+ }
2014
+ rangesOverlap(r1, r2) {
2015
+ if (r1.end.line < r2.start.line)
2016
+ return false;
2017
+ if (r1.start.line > r2.end.line)
2018
+ return false;
2019
+ if (r1.end.line === r2.start.line && r1.end.character < r2.start.character)
2020
+ return false;
2021
+ if (r1.start.line === r2.end.line && r1.start.character > r2.end.character)
2022
+ return false;
2023
+ return true;
2024
+ }
2025
+ }
2026
+
2027
+ class LineContextCollector extends core.Visitor {
2028
+ constructor() {
2029
+ super(...arguments);
2030
+ this.lineMap = new Map();
2031
+ this.erbNodesPerLine = new Map();
2032
+ this.htmlCommentNodesPerLine = new Map();
2033
+ }
2034
+ visitERBNode(node) {
2035
+ if (!node.tag_opening || !node.tag_closing)
2036
+ return;
2037
+ const startLine = lspLine(node.tag_opening.location.start);
2038
+ const nodes = this.erbNodesPerLine.get(startLine) || [];
2039
+ nodes.push(node);
2040
+ this.erbNodesPerLine.set(startLine, nodes);
2041
+ if (core.isERBCommentNode(node)) {
2042
+ this.setLine(startLine, "erb-comment", node);
2043
+ }
2044
+ else {
2045
+ this.setLine(startLine, "erb-tag", node);
2046
+ }
2047
+ }
2048
+ visitERBContentNode(node) {
2049
+ this.visitERBNode(node);
2050
+ this.visitChildNodes(node);
2051
+ }
2052
+ visitHTMLCommentNode(node) {
2053
+ const startLine = lspLine(node.location.start);
2054
+ const endLine = lspLine(node.location.end);
2055
+ for (let line = startLine; line <= endLine; line++) {
2056
+ this.htmlCommentNodesPerLine.set(line, node);
2057
+ this.setLine(line, "html-comment", node);
2058
+ }
2059
+ this.visitChildNodes(node);
2060
+ }
2061
+ visitHTMLElementNode(node) {
2062
+ const startLine = lspLine(node.location.start);
2063
+ const endLine = lspLine(node.location.end);
2064
+ for (let line = startLine; line <= endLine; line++) {
2065
+ if (!this.lineMap.has(line)) {
2066
+ this.setLine(line, "html-content", node);
2067
+ }
2068
+ }
2069
+ this.visitChildNodes(node);
2070
+ }
2071
+ visitHTMLTextNode(node) {
2072
+ const startLine = lspLine(node.location.start);
2073
+ const endLine = lspLine(node.location.end);
2074
+ for (let line = startLine; line <= endLine; line++) {
2075
+ if (!this.lineMap.has(line)) {
2076
+ this.setLine(line, "html-content", node);
2077
+ }
2078
+ }
2079
+ this.visitChildNodes(node);
2080
+ }
2081
+ setLine(line, context, node) {
2082
+ const existing = this.lineMap.get(line);
2083
+ if (existing) {
2084
+ if (existing.context === "erb-comment" || existing.context === "erb-tag")
2085
+ return;
2086
+ if (context === "erb-comment" || context === "erb-tag") {
2087
+ this.lineMap.set(line, { line, context, node });
2088
+ return;
2089
+ }
2090
+ if (existing.context === "html-comment")
2091
+ return;
2092
+ }
2093
+ this.lineMap.set(line, { line, context, node });
2094
+ }
2095
+ }
2096
+
2097
+ function commentERBNode(node) {
2098
+ const mutable = rewriter.asMutable(node);
2099
+ if (mutable.tag_opening) {
2100
+ const currentValue = mutable.tag_opening.value;
2101
+ mutable.tag_opening = core.createSyntheticToken(currentValue.substring(0, 2) + "#" + currentValue.substring(2), mutable.tag_opening.type);
2102
+ }
2103
+ }
2104
+ function uncommentERBNode(node) {
2105
+ var _a;
2106
+ const mutable = rewriter.asMutable(node);
2107
+ if (mutable.tag_opening && mutable.tag_opening.value === "<%#") {
2108
+ const contentValue = ((_a = mutable.content) === null || _a === void 0 ? void 0 : _a.value) || "";
2109
+ if (contentValue.startsWith(" graphql ") ||
2110
+ contentValue.startsWith(" %= ") ||
2111
+ contentValue.startsWith(" == ") ||
2112
+ contentValue.startsWith(" % ") ||
2113
+ contentValue.startsWith(" = ") ||
2114
+ contentValue.startsWith(" - ")) {
2115
+ mutable.tag_opening = core.createSyntheticToken("<%", mutable.tag_opening.type);
2116
+ mutable.content = core.createSyntheticToken(contentValue.substring(1), mutable.content.type);
2117
+ }
2118
+ else {
2119
+ mutable.tag_opening = core.createSyntheticToken("<%", mutable.tag_opening.type);
2120
+ }
2121
+ }
2122
+ }
2123
+ function determineStrategy(erbNodes, lineText) {
2124
+ if (erbNodes.length === 0) {
2125
+ return "html-only";
2126
+ }
2127
+ if (erbNodes.length === 1) {
2128
+ const node = erbNodes[0];
2129
+ if (!node.tag_opening || !node.tag_closing)
2130
+ return "html-only";
2131
+ const nodeStart = node.tag_opening.location.start.column;
2132
+ const nodeEnd = node.tag_closing.location.end.column;
2133
+ const isSoleContent = lineText.substring(0, nodeStart).trim() === "" && lineText.substring(nodeEnd).trim() === "";
2134
+ if (isSoleContent) {
2135
+ return "single-erb";
2136
+ }
2137
+ return "whole-line";
2138
+ }
2139
+ const segments = getLineSegments(lineText, erbNodes);
2140
+ const hasHTML = segments.some(segment => !segment.isERB && segment.text.trim() !== "");
2141
+ if (!hasHTML) {
2142
+ return "all-erb";
2143
+ }
2144
+ const allControlTags = erbNodes.every(node => { var _a; return ((_a = node.tag_opening) === null || _a === void 0 ? void 0 : _a.value) === "<%"; });
2145
+ if (allControlTags) {
2146
+ return "per-segment";
2147
+ }
2148
+ return "whole-line";
2149
+ }
2150
+ function getLineSegments(lineText, erbNodes) {
2151
+ const segments = [];
2152
+ const sorted = [...erbNodes].sort((a, b) => a.tag_opening.location.start.column - b.tag_opening.location.start.column);
2153
+ let position = 0;
2154
+ for (const node of sorted) {
2155
+ const nodeStart = node.tag_opening.location.start.column;
2156
+ const nodeEnd = node.tag_closing.location.end.column;
2157
+ if (nodeStart > position) {
2158
+ segments.push({ text: lineText.substring(position, nodeStart), isERB: false });
2159
+ }
2160
+ segments.push({ text: lineText.substring(nodeStart, nodeEnd), isERB: true, node });
2161
+ position = nodeEnd;
2162
+ }
2163
+ if (position < lineText.length) {
2164
+ segments.push({ text: lineText.substring(position), isERB: false });
2165
+ }
2166
+ return segments;
2167
+ }
2168
+ /**
2169
+ * Comment a line using AST mutation for strategies where the parser produces flat children,
2170
+ * and text-segment manipulation for per-segment (where the parser nests nodes).
2171
+ */
2172
+ function commentLineContent(content, erbNodes, strategy, parserService) {
2173
+ if (strategy === "per-segment") {
2174
+ return commentPerSegment(content, erbNodes);
2175
+ }
2176
+ const parseResult = parserService.parseContent(content, { track_whitespace: true });
2177
+ const lineCollector = new LineContextCollector();
2178
+ parseResult.visit(lineCollector);
2179
+ const lineERBNodes = lineCollector.erbNodesPerLine.get(0) || [];
2180
+ const document = parseResult.value;
2181
+ const children = rewriter.asMutable(document).children;
2182
+ switch (strategy) {
2183
+ case "all-erb":
2184
+ for (const node of lineERBNodes) {
2185
+ commentERBNode(node);
2186
+ }
2187
+ break;
2188
+ case "whole-line": {
2189
+ for (const node of lineERBNodes) {
2190
+ commentERBNode(node);
2191
+ }
2192
+ const commentNode = new core.HTMLCommentNode({
2193
+ type: "AST_HTML_COMMENT_NODE",
2194
+ location: core.Location.zero,
2195
+ errors: [],
2196
+ comment_start: core.createSyntheticToken("<!--", "TOKEN_HTML_COMMENT_START"),
2197
+ children: [core.createLiteral(" "), ...children.slice(), core.createLiteral(" ")],
2198
+ comment_end: core.createSyntheticToken("-->", "TOKEN_HTML_COMMENT_END"),
2199
+ });
2200
+ children.length = 0;
2201
+ children.push(commentNode);
2202
+ break;
2203
+ }
2204
+ case "html-only": {
2205
+ const commentNode = new core.HTMLCommentNode({
2206
+ type: "AST_HTML_COMMENT_NODE",
2207
+ location: core.Location.zero,
2208
+ errors: [],
2209
+ comment_start: core.createSyntheticToken("<!--", "TOKEN_HTML_COMMENT_START"),
2210
+ children: [core.createLiteral(" "), ...children.slice(), core.createLiteral(" ")],
2211
+ comment_end: core.createSyntheticToken("-->", "TOKEN_HTML_COMMENT_END"),
2212
+ });
2213
+ children.length = 0;
2214
+ children.push(commentNode);
2215
+ break;
2216
+ }
2217
+ }
2218
+ return printer.IdentityPrinter.print(document, { ignoreErrors: true });
2219
+ }
2220
+ /**
2221
+ * Per-segment commenting uses text segments because the parser creates nested
2222
+ * structures (e.g., ERBIfNode) that don't allow flat child iteration.
2223
+ */
2224
+ function commentPerSegment(content, erbNodes) {
2225
+ const segments = getLineSegments(content, erbNodes);
2226
+ return segments.map(segment => {
2227
+ if (segment.isERB) {
2228
+ return segment.text.substring(0, 2) + "#" + segment.text.substring(2);
2229
+ }
2230
+ else if (segment.text.trim() !== "") {
2231
+ return `<!-- ${segment.text} -->`;
2232
+ }
2233
+ return segment.text;
2234
+ }).join("");
2235
+ }
2236
+ function uncommentLineContent(content, parserService) {
2237
+ const parseResult = parserService.parseContent(content, { track_whitespace: true });
2238
+ const lineCollector = new LineContextCollector();
2239
+ parseResult.visit(lineCollector);
2240
+ const lineERBNodes = lineCollector.erbNodesPerLine.get(0) || [];
2241
+ const document = parseResult.value;
2242
+ const children = rewriter.asMutable(document).children;
2243
+ for (const node of lineERBNodes) {
2244
+ if (core.isERBCommentNode(node)) {
2245
+ uncommentERBNode(node);
2246
+ }
2247
+ }
2248
+ let index = 0;
2249
+ while (index < children.length) {
2250
+ const child = children[index];
2251
+ if (child.type === "AST_HTML_COMMENT_NODE") {
2252
+ const commentNode = child;
2253
+ const innerChildren = [...commentNode.children];
2254
+ if (innerChildren.length > 0) {
2255
+ const first = innerChildren[0];
2256
+ if (core.isLiteralNode(first) && first.content.startsWith(" ")) {
2257
+ const trimmed = first.content.substring(1);
2258
+ if (trimmed === "") {
2259
+ innerChildren.shift();
2260
+ }
2261
+ else {
2262
+ innerChildren[0] = core.createLiteral(trimmed);
2263
+ }
2264
+ }
2265
+ }
2266
+ if (innerChildren.length > 0) {
2267
+ const last = innerChildren[innerChildren.length - 1];
2268
+ if (core.isLiteralNode(last) && last.content.endsWith(" ")) {
2269
+ const trimmed = last.content.substring(0, last.content.length - 1);
2270
+ if (trimmed === "") {
2271
+ innerChildren.pop();
2272
+ }
2273
+ else {
2274
+ innerChildren[innerChildren.length - 1] = core.createLiteral(trimmed);
2275
+ }
2276
+ }
2277
+ }
2278
+ const innerERBNodes = [];
2279
+ const innerCollector = new LineContextCollector();
2280
+ for (const innerChild of innerChildren) {
2281
+ innerCollector.visit(innerChild);
2282
+ }
2283
+ innerERBNodes.push(...(innerCollector.erbNodesPerLine.get(0) || []));
2284
+ for (const erbNode of innerERBNodes) {
2285
+ if (core.isERBCommentNode(erbNode)) {
2286
+ uncommentERBNode(erbNode);
2287
+ }
2288
+ }
2289
+ children.splice(index, 1, ...innerChildren);
2290
+ index += innerChildren.length;
2291
+ continue;
2292
+ }
2293
+ index++;
2294
+ }
2295
+ return printer.IdentityPrinter.print(document, { ignoreErrors: true });
2296
+ }
2297
+
2298
+ class CommentService {
2299
+ constructor(parserService) {
2300
+ this.parserService = parserService;
2301
+ }
2302
+ toggleLineComment(document, range) {
2303
+ const parseResult = this.parserService.parseDocument(document);
2304
+ const collector = new LineContextCollector();
2305
+ collector.visit(parseResult.document);
2306
+ const startLine = range.start.line;
2307
+ const endLine = range.end.line;
2308
+ const lineInfos = [];
2309
+ for (let line = startLine; line <= endLine; line++) {
2310
+ const lineText = document.getText(node.Range.create(line, 0, line + 1, 0)).replace(/\n$/, "");
2311
+ if (lineText.trim() === "") {
2312
+ continue;
2313
+ }
2314
+ if (this.lineIsIfFalseWrapped(lineText) !== null) {
2315
+ lineInfos.push({ line, context: "erb-comment", node: null });
2316
+ continue;
2317
+ }
2318
+ const htmlCommentNode = collector.htmlCommentNodesPerLine.get(line);
2319
+ const info = collector.lineMap.get(line);
2320
+ if (htmlCommentNode && this.htmlCommentSpansLine(htmlCommentNode, lineText)) {
2321
+ lineInfos.push({ line, context: "html-comment", node: htmlCommentNode });
2322
+ }
2323
+ else if (info) {
2324
+ if (info.context === "html-comment") {
2325
+ lineInfos.push({ line, context: "html-content", node: null });
2326
+ }
2327
+ else {
2328
+ lineInfos.push(info);
2329
+ }
2330
+ }
2331
+ else {
2332
+ lineInfos.push({ line, context: "html-content", node: null });
2333
+ }
2334
+ }
2335
+ if (lineInfos.length === 0)
2336
+ return [];
2337
+ const allCommented = lineInfos.every(info => info.context === "erb-comment" || info.context === "html-comment");
2338
+ const edits = [];
2339
+ if (allCommented) {
2340
+ for (const info of lineInfos) {
2341
+ const lineText = document.getText(node.Range.create(info.line, 0, info.line + 1, 0)).replace(/\n$/, "");
2342
+ const edit = this.uncommentLine(info, lineText, collector);
2343
+ if (edit)
2344
+ edits.push(edit);
2345
+ }
2346
+ }
2347
+ else {
2348
+ for (const info of lineInfos) {
2349
+ if (info.context === "erb-comment" || info.context === "html-comment")
2350
+ continue;
2351
+ const lineText = document.getText(node.Range.create(info.line, 0, info.line + 1, 0)).replace(/\n$/, "");
2352
+ const erbNodes = collector.erbNodesPerLine.get(info.line) || [];
2353
+ const edit = this.commentLine(info, lineText, erbNodes, collector);
2354
+ if (edit)
2355
+ edits.push(edit);
2356
+ }
2357
+ }
2358
+ return edits;
2359
+ }
2360
+ toggleBlockComment(document, range) {
2361
+ const startLine = range.start.line;
2362
+ const endLine = range.end.line;
2363
+ const firstLineText = document.getText(node.Range.create(startLine, 0, startLine + 1, 0)).replace(/\n$/, "");
2364
+ const lastLineText = document.getText(node.Range.create(endLine, 0, endLine + 1, 0)).replace(/\n$/, "");
2365
+ const isWrapped = firstLineText.trim() === "<% if false %>" && lastLineText.trim() === "<% end %>";
2366
+ if (isWrapped) {
2367
+ return [
2368
+ node.TextEdit.del(node.Range.create(endLine, 0, endLine + 1, 0)),
2369
+ node.TextEdit.del(node.Range.create(startLine, 0, startLine + 1, 0)),
2370
+ ];
2371
+ }
2372
+ else {
2373
+ const firstLineIndent = this.getIndentation(firstLineText);
2374
+ return [
2375
+ node.TextEdit.insert(node.Position.create(endLine + 1, 0), `${firstLineIndent}<% end %>\n`),
2376
+ node.TextEdit.insert(node.Position.create(startLine, 0), `${firstLineIndent}<% if false %>\n`),
2377
+ ];
2378
+ }
2379
+ }
2380
+ commentLine(info, lineText, erbNodes, collector) {
2381
+ const lineRange = node.Range.create(info.line, 0, info.line, lineText.length);
2382
+ const indent = this.getIndentation(lineText);
2383
+ const content = lineText.trimStart();
2384
+ const htmlCommentNode = collector.htmlCommentNodesPerLine.get(info.line);
2385
+ if (htmlCommentNode) {
2386
+ return node.TextEdit.replace(lineRange, `${indent}<% if false %>${content}<% end %>`);
2387
+ }
2388
+ const strategy = determineStrategy(erbNodes, lineText);
2389
+ if (strategy === "single-erb") {
2390
+ const node$1 = erbNodes[0];
2391
+ const insertColumn = node$1.tag_opening.location.start.column + 2;
2392
+ return node.TextEdit.insert(node.Position.create(info.line, insertColumn), "#");
2393
+ }
2394
+ const result = commentLineContent(content, erbNodes, strategy, this.parserService);
2395
+ return node.TextEdit.replace(lineRange, indent + result);
2396
+ }
2397
+ lineIsIfFalseWrapped(lineText) {
2398
+ const trimmed = lineText.trimStart();
2399
+ const indent = this.getIndentation(lineText);
2400
+ if (trimmed.startsWith("<% if false %>") && trimmed.endsWith("<% end %>")) {
2401
+ const inner = trimmed.slice("<% if false %>".length, -"<% end %>".length);
2402
+ return indent + inner;
2403
+ }
2404
+ return null;
2405
+ }
2406
+ uncommentLine(info, lineText, collector) {
2407
+ var _a;
2408
+ const lineRange = node.Range.create(info.line, 0, info.line, lineText.length);
2409
+ const indent = this.getIndentation(lineText);
2410
+ const ifFalseContent = this.lineIsIfFalseWrapped(lineText);
2411
+ if (ifFalseContent !== null) {
2412
+ return node.TextEdit.replace(lineRange, ifFalseContent);
2413
+ }
2414
+ if (info.context === "erb-comment") {
2415
+ const node$1 = info.node;
2416
+ if (!(node$1 === null || node$1 === void 0 ? void 0 : node$1.tag_opening) || !(node$1 === null || node$1 === void 0 ? void 0 : node$1.tag_closing))
2417
+ return null;
2418
+ const contentValue = (_a = node$1.content) === null || _a === void 0 ? void 0 : _a.value;
2419
+ const trimmedContent = (contentValue === null || contentValue === void 0 ? void 0 : contentValue.trim()) || "";
2420
+ if (trimmedContent.startsWith("<") && !trimmedContent.startsWith("<%")) {
2421
+ return node.TextEdit.replace(lineRange, `${indent}${trimmedContent}`);
2422
+ }
2423
+ if (lspLine(node$1.tag_opening.location.start) !== info.line)
2424
+ return null;
2425
+ const erbNodes = collector.erbNodesPerLine.get(info.line) || [];
2426
+ if (erbNodes.length > 1) {
2427
+ const content = lineText.trimStart();
2428
+ const result = uncommentLineContent(content, this.parserService);
2429
+ return node.TextEdit.replace(lineRange, indent + result);
2430
+ }
2431
+ const hashColumn = node$1.tag_opening.location.start.column + 2;
2432
+ if ((contentValue === null || contentValue === void 0 ? void 0 : contentValue.startsWith(" graphql ")) ||
2433
+ (contentValue === null || contentValue === void 0 ? void 0 : contentValue.startsWith(" %= ")) ||
2434
+ (contentValue === null || contentValue === void 0 ? void 0 : contentValue.startsWith(" == ")) ||
2435
+ (contentValue === null || contentValue === void 0 ? void 0 : contentValue.startsWith(" % ")) ||
2436
+ (contentValue === null || contentValue === void 0 ? void 0 : contentValue.startsWith(" = ")) ||
2437
+ (contentValue === null || contentValue === void 0 ? void 0 : contentValue.startsWith(" - "))) {
2438
+ return node.TextEdit.del(node.Range.create(info.line, hashColumn, info.line, hashColumn + 2));
2439
+ }
2440
+ return node.TextEdit.del(node.Range.create(info.line, hashColumn, info.line, hashColumn + 1));
2441
+ }
2442
+ if (info.context === "html-comment") {
2443
+ const commentNode = info.node;
2444
+ if ((commentNode === null || commentNode === void 0 ? void 0 : commentNode.comment_start) && (commentNode === null || commentNode === void 0 ? void 0 : commentNode.comment_end)) {
2445
+ const contentStart = commentNode.comment_start.location.end.column;
2446
+ const contentEnd = commentNode.comment_end.location.start.column;
2447
+ const innerContent = lineText.substring(contentStart, contentEnd).trim();
2448
+ const result = uncommentLineContent(innerContent, this.parserService);
2449
+ return node.TextEdit.replace(lineRange, `${indent}${result}`);
2450
+ }
2451
+ }
2452
+ return null;
2453
+ }
2454
+ htmlCommentSpansLine(node, lineText) {
2455
+ if (!node.comment_start || !node.comment_end)
2456
+ return false;
2457
+ const commentStart = node.comment_start.location.start.column;
2458
+ const commentEnd = node.comment_end.location.end.column;
2459
+ const contentBefore = lineText.substring(0, commentStart).trim();
2460
+ const contentAfter = lineText.substring(commentEnd).trim();
2461
+ return contentBefore === "" && contentAfter === "";
2462
+ }
2463
+ getIndentation(lineText) {
2464
+ const match = lineText.match(/^(\s*)/);
2465
+ return match ? match[1] : "";
1358
2466
  }
1359
2467
  }
1360
2468
 
@@ -1372,6 +2480,11 @@ class Service {
1372
2480
  this.codeActionService = new CodeActionService(this.project, this.config);
1373
2481
  this.diagnostics = new Diagnostics(this.connection, this.documentService, this.parserService, this.linterService, this.configService);
1374
2482
  this.documentSaveService = new DocumentSaveService(this.connection, this.settings, this.autofixService, this.formattingService);
2483
+ this.foldingRangeService = new FoldingRangeService(this.parserService);
2484
+ this.documentHighlightService = new DocumentHighlightService(this.parserService);
2485
+ this.hoverService = new HoverService(this.parserService);
2486
+ this.rewriteCodeActionService = new RewriteCodeActionService(this.parserService);
2487
+ this.commentService = new CommentService(this.parserService);
1375
2488
  if (params.initializationOptions) {
1376
2489
  this.settings.globalSettings = params.initializationOptions;
1377
2490
  }
@@ -1463,8 +2576,11 @@ class Server {
1463
2576
  documentFormattingProvider: true,
1464
2577
  documentRangeFormattingProvider: true,
1465
2578
  codeActionProvider: {
1466
- codeActionKinds: [node.CodeActionKind.QuickFix, node.CodeActionKind.SourceFixAll]
2579
+ codeActionKinds: [node.CodeActionKind.QuickFix, node.CodeActionKind.SourceFixAll, node.CodeActionKind.RefactorRewrite]
1467
2580
  },
2581
+ foldingRangeProvider: true,
2582
+ documentHighlightProvider: true,
2583
+ hoverProvider: true,
1468
2584
  },
1469
2585
  };
1470
2586
  if (this.service.settings.hasWorkspaceFolderCapability) {
@@ -1546,6 +2662,18 @@ class Server {
1546
2662
  this.connection.onDocumentRangeFormatting((params) => {
1547
2663
  return this.service.formattingService.formatRange(params);
1548
2664
  });
2665
+ this.connection.onDocumentHighlight((params) => {
2666
+ const document = this.service.documentService.get(params.textDocument.uri);
2667
+ if (!document)
2668
+ return [];
2669
+ return this.service.documentHighlightService.getDocumentHighlights(document, params.position);
2670
+ });
2671
+ this.connection.onHover((params) => {
2672
+ const document = this.service.documentService.get(params.textDocument.uri);
2673
+ if (!document)
2674
+ return null;
2675
+ return this.service.hoverService.getHover(document, params.position);
2676
+ });
1549
2677
  this.connection.onCodeAction((params) => {
1550
2678
  const document = this.service.documentService.get(params.textDocument.uri);
1551
2679
  if (!document)
@@ -1554,7 +2682,26 @@ class Server {
1554
2682
  const documentText = document.getText();
1555
2683
  const linterDisableCodeActions = this.service.codeActionService.createCodeActions(params.textDocument.uri, diagnostics, documentText);
1556
2684
  const autofixCodeActions = this.service.codeActionService.autofixCodeActions(params, document);
1557
- return autofixCodeActions.concat(linterDisableCodeActions);
2685
+ const rewriteCodeActions = this.service.rewriteCodeActionService.getCodeActions(document, params.range);
2686
+ return autofixCodeActions.concat(linterDisableCodeActions).concat(rewriteCodeActions);
2687
+ });
2688
+ this.connection.onFoldingRanges((params) => {
2689
+ const document = this.service.documentService.get(params.textDocument.uri);
2690
+ if (!document)
2691
+ return [];
2692
+ return this.service.foldingRangeService.getFoldingRanges(document);
2693
+ });
2694
+ this.connection.onRequest('herb/toggleLineComment', (params) => {
2695
+ const document = this.service.documentService.get(params.textDocument.uri);
2696
+ if (!document)
2697
+ return [];
2698
+ return this.service.commentService.toggleLineComment(document, params.range);
2699
+ });
2700
+ this.connection.onRequest('herb/toggleBlockComment', (params) => {
2701
+ const document = this.service.documentService.get(params.textDocument.uri);
2702
+ if (!document)
2703
+ return [];
2704
+ return this.service.commentService.toggleBlockComment(document, params.range);
1558
2705
  });
1559
2706
  }
1560
2707
  listen() {
@@ -1585,8 +2732,13 @@ class CLI {
1585
2732
  }
1586
2733
 
1587
2734
  exports.CLI = CLI;
2735
+ exports.CommentService = CommentService;
1588
2736
  exports.Diagnostics = Diagnostics;
2737
+ exports.DocumentHighlightCollector = DocumentHighlightCollector;
2738
+ exports.DocumentHighlightService = DocumentHighlightService;
1589
2739
  exports.DocumentService = DocumentService;
2740
+ exports.FoldingRangeCollector = FoldingRangeCollector;
2741
+ exports.FoldingRangeService = FoldingRangeService;
1590
2742
  exports.FormattingService = FormattingService;
1591
2743
  exports.Project = Project;
1592
2744
  exports.Server = Server;
@@ -1596,6 +2748,15 @@ exports.UnreachableCodeCollector = UnreachableCodeCollector;
1596
2748
  exports.camelize = camelize;
1597
2749
  exports.capitalize = capitalize;
1598
2750
  exports.dasherize = dasherize;
2751
+ exports.erbTagToRange = erbTagToRange;
1599
2752
  exports.getFullDocumentRange = getFullDocumentRange;
2753
+ exports.isPositionInRange = isPositionInRange;
1600
2754
  exports.lintToDignosticSeverity = lintToDignosticSeverity;
2755
+ exports.lspLine = lspLine;
2756
+ exports.lspPosition = lspPosition;
2757
+ exports.lspRangeFromLocation = lspRangeFromLocation;
2758
+ exports.nodeToRange = nodeToRange;
2759
+ exports.openTagRanges = openTagRanges;
2760
+ exports.rangeSize = rangeSize;
2761
+ exports.tokenToRange = tokenToRange;
1601
2762
  //# sourceMappingURL=index.cjs.map