@herb-tools/language-server 0.8.10 → 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 -4
- package/dist/formatting_service.js.map +1 -1
- package/dist/herb-language-server.js +150189 -41260
- 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 -63
- 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 -4
- 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) {
|
|
@@ -1287,10 +1341,7 @@ class CodeActionService {
|
|
|
1287
1341
|
};
|
|
1288
1342
|
}
|
|
1289
1343
|
offenseToRange(offense) {
|
|
1290
|
-
return
|
|
1291
|
-
start: node.Position.create(offense.location.start.line - 1, offense.location.start.column),
|
|
1292
|
-
end: node.Position.create(offense.location.end.line - 1, offense.location.end.column)
|
|
1293
|
-
};
|
|
1344
|
+
return lspRangeFromLocation(offense.location);
|
|
1294
1345
|
}
|
|
1295
1346
|
rangesEqual(r1, r2) {
|
|
1296
1347
|
return (r1.start.line === r2.start.line &&
|
|
@@ -1309,6 +1360,14 @@ class CodeActionService {
|
|
|
1309
1360
|
|
|
1310
1361
|
class DocumentSaveService {
|
|
1311
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();
|
|
1312
1371
|
this.connection = connection;
|
|
1313
1372
|
this.settings = settings;
|
|
1314
1373
|
this.autofixService = autofixService;
|
|
@@ -1325,6 +1384,10 @@ class DocumentSaveService {
|
|
|
1325
1384
|
this.connection.console.log(`[DocumentSave] applyFixes fixOnSave=${fixOnSave}`);
|
|
1326
1385
|
if (!fixOnSave)
|
|
1327
1386
|
return [];
|
|
1387
|
+
if (this.recentlyAutofixedViaFormatting.delete(document.uri)) {
|
|
1388
|
+
this.connection.console.log(`[DocumentSave] applyFixes skipping: already autofixed via formatting`);
|
|
1389
|
+
return [];
|
|
1390
|
+
}
|
|
1328
1391
|
return this.autofixService.autofix(document);
|
|
1329
1392
|
}
|
|
1330
1393
|
/**
|
|
@@ -1340,18 +1403,1066 @@ class DocumentSaveService {
|
|
|
1340
1403
|
let autofixEdits = [];
|
|
1341
1404
|
if (fixOnSave) {
|
|
1342
1405
|
autofixEdits = await this.autofixService.autofix(document);
|
|
1406
|
+
if (autofixEdits.length > 0) {
|
|
1407
|
+
this.recentlyAutofixedViaFormatting.add(document.uri);
|
|
1408
|
+
}
|
|
1343
1409
|
}
|
|
1344
1410
|
if (!formatterEnabled)
|
|
1345
1411
|
return autofixEdits;
|
|
1346
1412
|
if (autofixEdits.length === 0) {
|
|
1347
1413
|
return this.formattingService.formatOnSave(document, reason);
|
|
1348
1414
|
}
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
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,
|
|
1353
1977
|
};
|
|
1354
|
-
|
|
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] : "";
|
|
1355
2466
|
}
|
|
1356
2467
|
}
|
|
1357
2468
|
|
|
@@ -1369,6 +2480,11 @@ class Service {
|
|
|
1369
2480
|
this.codeActionService = new CodeActionService(this.project, this.config);
|
|
1370
2481
|
this.diagnostics = new Diagnostics(this.connection, this.documentService, this.parserService, this.linterService, this.configService);
|
|
1371
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);
|
|
1372
2488
|
if (params.initializationOptions) {
|
|
1373
2489
|
this.settings.globalSettings = params.initializationOptions;
|
|
1374
2490
|
}
|
|
@@ -1460,8 +2576,11 @@ class Server {
|
|
|
1460
2576
|
documentFormattingProvider: true,
|
|
1461
2577
|
documentRangeFormattingProvider: true,
|
|
1462
2578
|
codeActionProvider: {
|
|
1463
|
-
codeActionKinds: [node.CodeActionKind.QuickFix, node.CodeActionKind.SourceFixAll]
|
|
2579
|
+
codeActionKinds: [node.CodeActionKind.QuickFix, node.CodeActionKind.SourceFixAll, node.CodeActionKind.RefactorRewrite]
|
|
1464
2580
|
},
|
|
2581
|
+
foldingRangeProvider: true,
|
|
2582
|
+
documentHighlightProvider: true,
|
|
2583
|
+
hoverProvider: true,
|
|
1465
2584
|
},
|
|
1466
2585
|
};
|
|
1467
2586
|
if (this.service.settings.hasWorkspaceFolderCapability) {
|
|
@@ -1543,6 +2662,18 @@ class Server {
|
|
|
1543
2662
|
this.connection.onDocumentRangeFormatting((params) => {
|
|
1544
2663
|
return this.service.formattingService.formatRange(params);
|
|
1545
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
|
+
});
|
|
1546
2677
|
this.connection.onCodeAction((params) => {
|
|
1547
2678
|
const document = this.service.documentService.get(params.textDocument.uri);
|
|
1548
2679
|
if (!document)
|
|
@@ -1551,7 +2682,26 @@ class Server {
|
|
|
1551
2682
|
const documentText = document.getText();
|
|
1552
2683
|
const linterDisableCodeActions = this.service.codeActionService.createCodeActions(params.textDocument.uri, diagnostics, documentText);
|
|
1553
2684
|
const autofixCodeActions = this.service.codeActionService.autofixCodeActions(params, document);
|
|
1554
|
-
|
|
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);
|
|
1555
2705
|
});
|
|
1556
2706
|
}
|
|
1557
2707
|
listen() {
|
|
@@ -1582,8 +2732,13 @@ class CLI {
|
|
|
1582
2732
|
}
|
|
1583
2733
|
|
|
1584
2734
|
exports.CLI = CLI;
|
|
2735
|
+
exports.CommentService = CommentService;
|
|
1585
2736
|
exports.Diagnostics = Diagnostics;
|
|
2737
|
+
exports.DocumentHighlightCollector = DocumentHighlightCollector;
|
|
2738
|
+
exports.DocumentHighlightService = DocumentHighlightService;
|
|
1586
2739
|
exports.DocumentService = DocumentService;
|
|
2740
|
+
exports.FoldingRangeCollector = FoldingRangeCollector;
|
|
2741
|
+
exports.FoldingRangeService = FoldingRangeService;
|
|
1587
2742
|
exports.FormattingService = FormattingService;
|
|
1588
2743
|
exports.Project = Project;
|
|
1589
2744
|
exports.Server = Server;
|
|
@@ -1593,6 +2748,15 @@ exports.UnreachableCodeCollector = UnreachableCodeCollector;
|
|
|
1593
2748
|
exports.camelize = camelize;
|
|
1594
2749
|
exports.capitalize = capitalize;
|
|
1595
2750
|
exports.dasherize = dasherize;
|
|
2751
|
+
exports.erbTagToRange = erbTagToRange;
|
|
1596
2752
|
exports.getFullDocumentRange = getFullDocumentRange;
|
|
2753
|
+
exports.isPositionInRange = isPositionInRange;
|
|
1597
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;
|
|
1598
2762
|
//# sourceMappingURL=index.cjs.map
|