@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.
- package/dist/action_view_helpers.js +19 -0
- package/dist/action_view_helpers.js.map +1 -0
- package/dist/autofix_service.js +1 -1
- package/dist/autofix_service.js.map +1 -1
- package/dist/code_action_service.js +3 -6
- package/dist/code_action_service.js.map +1 -1
- package/dist/comment_ast_utils.js +206 -0
- package/dist/comment_ast_utils.js.map +1 -0
- package/dist/comment_service.js +175 -0
- package/dist/comment_service.js.map +1 -0
- package/dist/diagnostics.js +15 -27
- package/dist/diagnostics.js.map +1 -1
- package/dist/document_highlight_service.js +196 -0
- package/dist/document_highlight_service.js.map +1 -0
- package/dist/document_save_service.js +16 -6
- package/dist/document_save_service.js.map +1 -1
- package/dist/folding_range_service.js +209 -0
- package/dist/folding_range_service.js.map +1 -0
- package/dist/formatting_service.js +4 -7
- package/dist/formatting_service.js.map +1 -1
- package/dist/herb-language-server.js +150276 -41207
- package/dist/herb-language-server.js.map +1 -1
- package/dist/hover_service.js +70 -0
- package/dist/hover_service.js.map +1 -0
- package/dist/index.cjs +1227 -66
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/line_context_collector.js +73 -0
- package/dist/line_context_collector.js.map +1 -0
- package/dist/linter_service.js +20 -4
- package/dist/linter_service.js.map +1 -1
- package/dist/parser_service.js +6 -5
- package/dist/parser_service.js.map +1 -1
- package/dist/range_utils.js +65 -0
- package/dist/range_utils.js.map +1 -0
- package/dist/rewrite_code_action_service.js +135 -0
- package/dist/rewrite_code_action_service.js.map +1 -0
- package/dist/server.js +36 -2
- package/dist/server.js.map +1 -1
- package/dist/service.js +10 -0
- package/dist/service.js.map +1 -1
- package/dist/types/action_view_helpers.d.ts +5 -0
- package/dist/types/comment_ast_utils.d.ts +20 -0
- package/dist/types/comment_service.d.ts +14 -0
- package/dist/types/diagnostics.d.ts +0 -1
- package/dist/types/document_highlight_service.d.ts +28 -0
- package/dist/types/document_save_service.d.ts +8 -0
- package/dist/types/folding_range_service.d.ts +35 -0
- package/dist/types/formatting_service.d.ts +1 -1
- package/dist/types/hover_service.d.ts +8 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/line_context_collector.d.ts +19 -0
- package/dist/types/linter_service.d.ts +1 -0
- package/dist/types/parser_service.d.ts +2 -1
- package/dist/types/range_utils.d.ts +16 -0
- package/dist/types/rewrite_code_action_service.d.ts +11 -0
- package/dist/types/service.d.ts +10 -0
- package/dist/types/utils.d.ts +0 -6
- package/dist/utils.js +0 -16
- package/dist/utils.js.map +1 -1
- package/package.json +10 -5
- package/src/action_view_helpers.ts +23 -0
- package/src/autofix_service.ts +1 -1
- package/src/code_action_service.ts +3 -6
- package/src/comment_ast_utils.ts +282 -0
- package/src/comment_service.ts +228 -0
- package/src/diagnostics.ts +24 -38
- package/src/document_highlight_service.ts +267 -0
- package/src/document_save_service.ts +19 -7
- package/src/folding_range_service.ts +287 -0
- package/src/formatting_service.ts +4 -8
- package/src/hover_service.ts +90 -0
- package/src/index.ts +4 -0
- package/src/line_context_collector.ts +97 -0
- package/src/linter_service.ts +25 -7
- package/src/parser_service.ts +9 -10
- package/src/range_utils.ts +90 -0
- package/src/rewrite_code_action_service.ts +165 -0
- package/src/server.ts +51 -2
- package/src/service.ts +15 -0
- 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.
|
|
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
|
|
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
|
|
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
|
-
|
|
364
|
-
|
|
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
|
|
384
|
-
const nextSubsequent =
|
|
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 (
|
|
400
|
-
|
|
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:
|
|
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 =
|
|
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
|
-
:
|
|
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
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|