@herb-tools/language-server 0.8.10 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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 +0 -233
- 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 +152936 -41156
- 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 +1299 -333
- 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 +27 -6
- 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 +39 -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 +1 -35
- 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 +4 -8
- package/dist/utils.js +10 -15
- 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 +1 -305
- 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 +35 -9
- 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 +54 -2
- package/src/service.ts +15 -0
- package/src/utils.ts +12 -21
package/dist/index.cjs
CHANGED
|
@@ -4,13 +4,15 @@ var node = require('vscode-languageserver/node');
|
|
|
4
4
|
var config = require('@herb-tools/config');
|
|
5
5
|
var formatter = require('@herb-tools/formatter');
|
|
6
6
|
var vscodeLanguageserverTextdocument = require('vscode-languageserver-textdocument');
|
|
7
|
-
var core = require('@herb-tools/core');
|
|
8
7
|
var nodeWasm = require('@herb-tools/node-wasm');
|
|
9
8
|
var linter = require('@herb-tools/linter');
|
|
10
9
|
var loader = require('@herb-tools/linter/loader');
|
|
11
10
|
var loader$1 = require('@herb-tools/rewriter/loader');
|
|
11
|
+
var core = require('@herb-tools/core');
|
|
12
|
+
var printer = require('@herb-tools/printer');
|
|
13
|
+
var rewriter = require('@herb-tools/rewriter');
|
|
12
14
|
|
|
13
|
-
var version = "0.
|
|
15
|
+
var version = "0.9.1";
|
|
14
16
|
|
|
15
17
|
class Settings {
|
|
16
18
|
constructor(params, connection) {
|
|
@@ -154,21 +156,14 @@ class Diagnostics {
|
|
|
154
156
|
else {
|
|
155
157
|
const parseResult = this.parserService.parseDocument(textDocument);
|
|
156
158
|
const lintResult = await this.linterService.lintDocument(textDocument);
|
|
157
|
-
const unreachableCodeDiagnostics = this.getUnreachableCodeDiagnostics(parseResult.document);
|
|
158
159
|
allDiagnostics = [
|
|
159
160
|
...parseResult.diagnostics,
|
|
160
161
|
...lintResult.diagnostics,
|
|
161
|
-
...unreachableCodeDiagnostics,
|
|
162
162
|
];
|
|
163
163
|
}
|
|
164
164
|
this.diagnostics.set(textDocument, allDiagnostics);
|
|
165
165
|
this.sendDiagnosticsFor(textDocument);
|
|
166
166
|
}
|
|
167
|
-
getUnreachableCodeDiagnostics(document) {
|
|
168
|
-
const collector = new UnreachableCodeCollector();
|
|
169
|
-
collector.visit(document);
|
|
170
|
-
return collector.diagnostics;
|
|
171
|
-
}
|
|
172
167
|
async refreshDocument(document) {
|
|
173
168
|
await this.validate(document);
|
|
174
169
|
}
|
|
@@ -185,228 +180,69 @@ class Diagnostics {
|
|
|
185
180
|
this.diagnostics.delete(textDocument);
|
|
186
181
|
}
|
|
187
182
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
else {
|
|
216
|
-
this.checkIfChainParts(node);
|
|
217
|
-
}
|
|
218
|
-
this.visitChildNodes(node);
|
|
219
|
-
}
|
|
220
|
-
visitERBElseNode(node) {
|
|
221
|
-
if (this.processedElseNodes.has(node)) {
|
|
222
|
-
this.visitChildNodes(node);
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
this.checkEmptyStatements(node, node.statements, "else");
|
|
226
|
-
this.visitChildNodes(node);
|
|
227
|
-
}
|
|
228
|
-
visitERBUnlessNode(node) {
|
|
229
|
-
const unlessHasContent = this.statementsHaveContent(node.statements);
|
|
230
|
-
const elseHasContent = node.else_clause && this.statementsHaveContent(node.else_clause.statements);
|
|
231
|
-
if (node.else_clause) {
|
|
232
|
-
this.processedElseNodes.add(node.else_clause);
|
|
233
|
-
}
|
|
234
|
-
const entireBlockEmpty = !unlessHasContent && !elseHasContent;
|
|
235
|
-
if (entireBlockEmpty) {
|
|
236
|
-
this.checkEmptyStatements(node, node.statements, "unless");
|
|
237
|
-
}
|
|
238
|
-
else {
|
|
239
|
-
if (!unlessHasContent) {
|
|
240
|
-
this.checkEmptyStatementsWithEndLocation(node, node.statements, "unless", node.else_clause);
|
|
241
|
-
}
|
|
242
|
-
if (node.else_clause && !elseHasContent) {
|
|
243
|
-
this.checkEmptyStatements(node.else_clause, node.else_clause.statements, "else");
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
this.visitChildNodes(node);
|
|
247
|
-
}
|
|
248
|
-
visitERBForNode(node) {
|
|
249
|
-
this.checkEmptyStatements(node, node.statements, "for");
|
|
250
|
-
this.visitChildNodes(node);
|
|
251
|
-
}
|
|
252
|
-
visitERBWhileNode(node) {
|
|
253
|
-
this.checkEmptyStatements(node, node.statements, "while");
|
|
254
|
-
this.visitChildNodes(node);
|
|
255
|
-
}
|
|
256
|
-
visitERBUntilNode(node) {
|
|
257
|
-
this.checkEmptyStatements(node, node.statements, "until");
|
|
258
|
-
this.visitChildNodes(node);
|
|
259
|
-
}
|
|
260
|
-
visitERBWhenNode(node) {
|
|
261
|
-
if (!node.then_keyword) {
|
|
262
|
-
this.checkEmptyStatements(node, node.statements, "when");
|
|
263
|
-
}
|
|
264
|
-
this.visitChildNodes(node);
|
|
265
|
-
}
|
|
266
|
-
visitERBBeginNode(node) {
|
|
267
|
-
this.checkEmptyStatements(node, node.statements, "begin");
|
|
268
|
-
this.visitChildNodes(node);
|
|
269
|
-
}
|
|
270
|
-
visitERBRescueNode(node) {
|
|
271
|
-
this.checkEmptyStatements(node, node.statements, "rescue");
|
|
272
|
-
this.visitChildNodes(node);
|
|
273
|
-
}
|
|
274
|
-
visitERBEnsureNode(node) {
|
|
275
|
-
this.checkEmptyStatements(node, node.statements, "ensure");
|
|
276
|
-
this.visitChildNodes(node);
|
|
277
|
-
}
|
|
278
|
-
visitERBBlockNode(node) {
|
|
279
|
-
this.checkEmptyStatements(node, node.body, "block");
|
|
280
|
-
this.visitChildNodes(node);
|
|
281
|
-
}
|
|
282
|
-
visitERBInNode(node) {
|
|
283
|
-
if (!node.then_keyword) {
|
|
284
|
-
this.checkEmptyStatements(node, node.statements, "in");
|
|
285
|
-
}
|
|
286
|
-
this.visitChildNodes(node);
|
|
287
|
-
}
|
|
288
|
-
checkUnreachableChildren(children) {
|
|
289
|
-
for (const child of children) {
|
|
290
|
-
if (core.isHTMLTextNode(child) && child.content.trim() === "") {
|
|
291
|
-
continue;
|
|
292
|
-
}
|
|
293
|
-
this.addDiagnostic(child.location, "Unreachable code: content between case and when/in is never executed");
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
checkEmptyStatements(node, statements, blockType) {
|
|
297
|
-
this.checkEmptyStatementsWithEndLocation(node, statements, blockType, null);
|
|
298
|
-
}
|
|
299
|
-
checkEmptyStatementsWithEndLocation(node, statements, blockType, subsequentNode) {
|
|
300
|
-
if (this.statementsHaveContent(statements)) {
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
const startLocation = node.location.start;
|
|
304
|
-
const endLocation = subsequentNode
|
|
305
|
-
? subsequentNode.location.start
|
|
306
|
-
: node.location.end;
|
|
307
|
-
this.addDiagnostic({ start: startLocation, end: endLocation }, `Empty ${blockType} block: this control flow statement has no content`);
|
|
308
|
-
}
|
|
309
|
-
addDiagnostic(location, message) {
|
|
310
|
-
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
|
-
},
|
|
321
|
-
message,
|
|
322
|
-
severity: node.DiagnosticSeverity.Hint,
|
|
323
|
-
tags: [node.DiagnosticTag.Unnecessary],
|
|
324
|
-
source: "Herb Language Server"
|
|
325
|
-
};
|
|
326
|
-
this.diagnostics.push(diagnostic);
|
|
327
|
-
}
|
|
328
|
-
statementsHaveContent(statements) {
|
|
329
|
-
return statements.some(statement => {
|
|
330
|
-
if (core.isHTMLTextNode(statement)) {
|
|
331
|
-
return statement.content.trim() !== "";
|
|
332
|
-
}
|
|
333
|
-
return true;
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
|
-
checkAndMarkElseClause(elseClause) {
|
|
337
|
-
if (!elseClause) {
|
|
338
|
-
return;
|
|
339
|
-
}
|
|
340
|
-
this.processedElseNodes.add(elseClause);
|
|
341
|
-
if (!this.statementsHaveContent(elseClause.statements)) {
|
|
342
|
-
this.checkEmptyStatements(elseClause, elseClause.statements, "else");
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
markIfChainAsProcessed(node) {
|
|
346
|
-
this.processedIfNodes.add(node);
|
|
347
|
-
this.traverseSubsequentNodes(node.subsequent, (current) => {
|
|
348
|
-
if (current.type === 'AST_ERB_IF_NODE') {
|
|
349
|
-
this.processedIfNodes.add(current);
|
|
350
|
-
}
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
markElseNodesInIfChain(node) {
|
|
354
|
-
this.traverseSubsequentNodes(node.subsequent, (current) => {
|
|
355
|
-
if (current.type === 'AST_ERB_ELSE_NODE') {
|
|
356
|
-
this.processedElseNodes.add(current);
|
|
357
|
-
}
|
|
358
|
-
});
|
|
183
|
+
|
|
184
|
+
function lspPosition(herbPosition) {
|
|
185
|
+
return node.Position.create(herbPosition.line - 1, herbPosition.column);
|
|
186
|
+
}
|
|
187
|
+
function lspLine(herbPosition) {
|
|
188
|
+
return herbPosition.line - 1;
|
|
189
|
+
}
|
|
190
|
+
function lspRangeFromLocation(herbLocation) {
|
|
191
|
+
return node.Range.create(lspPosition(herbLocation.start), lspPosition(herbLocation.end));
|
|
192
|
+
}
|
|
193
|
+
function erbTagToRange(node$1) {
|
|
194
|
+
if (!node$1.tag_opening || !node$1.tag_closing)
|
|
195
|
+
return null;
|
|
196
|
+
return node.Range.create(lspPosition(node$1.tag_opening.location.start), lspPosition(node$1.tag_closing.location.end));
|
|
197
|
+
}
|
|
198
|
+
function tokenToRange(token) {
|
|
199
|
+
if (!token)
|
|
200
|
+
return null;
|
|
201
|
+
return lspRangeFromLocation(token.location);
|
|
202
|
+
}
|
|
203
|
+
function nodeToRange(node) {
|
|
204
|
+
return lspRangeFromLocation(node.location);
|
|
205
|
+
}
|
|
206
|
+
function openTagRanges(tag) {
|
|
207
|
+
const ranges = [];
|
|
208
|
+
if (tag.tag_opening && tag.tag_name) {
|
|
209
|
+
ranges.push(node.Range.create(lspPosition(tag.tag_opening.location.start), lspPosition(tag.tag_name.location.end)));
|
|
359
210
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
}
|
|
367
|
-
else {
|
|
368
|
-
break;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
211
|
+
ranges.push(tokenToRange(tag.tag_closing));
|
|
212
|
+
return ranges;
|
|
213
|
+
}
|
|
214
|
+
function isPositionInRange(position, range) {
|
|
215
|
+
if (position.line < range.start.line || position.line > range.end.line) {
|
|
216
|
+
return false;
|
|
371
217
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
this.checkEmptyStatementsWithEndLocation(node, node.statements, "if", node.subsequent);
|
|
375
|
-
}
|
|
376
|
-
this.traverseSubsequentNodes(node.subsequent, (current) => {
|
|
377
|
-
if (!('statements' in current) || !Array.isArray(current.statements)) {
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
380
|
-
if (this.statementsHaveContent(current.statements)) {
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
383
|
-
const blockType = current.type === 'AST_ERB_IF_NODE' ? 'elsif' : 'else';
|
|
384
|
-
const nextSubsequent = 'subsequent' in current ? current.subsequent : null;
|
|
385
|
-
if (nextSubsequent) {
|
|
386
|
-
this.checkEmptyStatementsWithEndLocation(current, current.statements, blockType, nextSubsequent);
|
|
387
|
-
}
|
|
388
|
-
else {
|
|
389
|
-
this.checkEmptyStatements(current, current.statements, blockType);
|
|
390
|
-
}
|
|
391
|
-
});
|
|
218
|
+
if (position.line === range.start.line && position.character < range.start.character) {
|
|
219
|
+
return false;
|
|
392
220
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
return false;
|
|
396
|
-
}
|
|
397
|
-
let hasContent = false;
|
|
398
|
-
this.traverseSubsequentNodes(node.subsequent, (current) => {
|
|
399
|
-
if ('statements' in current && Array.isArray(current.statements)) {
|
|
400
|
-
if (this.statementsHaveContent(current.statements)) {
|
|
401
|
-
hasContent = true;
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
});
|
|
405
|
-
return !hasContent;
|
|
221
|
+
if (position.line === range.end.line && position.character > range.end.character) {
|
|
222
|
+
return false;
|
|
406
223
|
}
|
|
407
|
-
|
|
408
|
-
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
function rangeSize(range) {
|
|
227
|
+
if (range.start.line === range.end.line) {
|
|
228
|
+
return range.end.character - range.start.character;
|
|
409
229
|
}
|
|
230
|
+
return (range.end.line - range.start.line) * 10000 + range.end.character;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Returns a Range that spans the entire document
|
|
234
|
+
*/
|
|
235
|
+
function getFullDocumentRange(document) {
|
|
236
|
+
const lastLine = document.lineCount - 1;
|
|
237
|
+
const lastLineText = document.getText({
|
|
238
|
+
start: node.Position.create(lastLine, 0),
|
|
239
|
+
end: node.Position.create(lastLine + 1, 0)
|
|
240
|
+
});
|
|
241
|
+
const lastLineLength = lastLineText.length;
|
|
242
|
+
return {
|
|
243
|
+
start: node.Position.create(0, 0),
|
|
244
|
+
end: node.Position.create(lastLine, lastLineLength)
|
|
245
|
+
};
|
|
410
246
|
}
|
|
411
247
|
|
|
412
248
|
class ErrorVisitor extends nodeWasm.Visitor {
|
|
@@ -423,7 +259,7 @@ class ErrorVisitor extends nodeWasm.Visitor {
|
|
|
423
259
|
const diagnostic = {
|
|
424
260
|
source: this.source,
|
|
425
261
|
severity: node.DiagnosticSeverity.Error,
|
|
426
|
-
range:
|
|
262
|
+
range: lspRangeFromLocation(error.location),
|
|
427
263
|
message: error.message,
|
|
428
264
|
code: error.type,
|
|
429
265
|
data: {
|
|
@@ -433,9 +269,6 @@ class ErrorVisitor extends nodeWasm.Visitor {
|
|
|
433
269
|
};
|
|
434
270
|
this.diagnostics.push(diagnostic);
|
|
435
271
|
}
|
|
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
272
|
}
|
|
440
273
|
class ParserService {
|
|
441
274
|
parseDocument(textDocument) {
|
|
@@ -448,6 +281,9 @@ class ParserService {
|
|
|
448
281
|
diagnostics: errorVisitor.diagnostics
|
|
449
282
|
};
|
|
450
283
|
}
|
|
284
|
+
parseContent(content, options) {
|
|
285
|
+
return nodeWasm.Herb.parse(content, options);
|
|
286
|
+
}
|
|
451
287
|
}
|
|
452
288
|
|
|
453
289
|
function camelize(value) {
|
|
@@ -467,20 +303,16 @@ function lintToDignosticSeverity(severity) {
|
|
|
467
303
|
case "hint": return node.DiagnosticSeverity.Hint;
|
|
468
304
|
}
|
|
469
305
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
306
|
+
function lintToDignosticTags(tags) {
|
|
307
|
+
if (!tags)
|
|
308
|
+
return [];
|
|
309
|
+
return tags.flatMap(tag => {
|
|
310
|
+
switch (tag) {
|
|
311
|
+
case "unnecessary": return [node.DiagnosticTag.Unnecessary];
|
|
312
|
+
case "deprecated": return [node.DiagnosticTag.Deprecated];
|
|
313
|
+
default: return [];
|
|
314
|
+
}
|
|
478
315
|
});
|
|
479
|
-
const lastLineLength = lastLineText.length;
|
|
480
|
-
return {
|
|
481
|
-
start: node.Position.create(0, 0),
|
|
482
|
-
end: node.Position.create(lastLine, lastLineLength)
|
|
483
|
-
};
|
|
484
316
|
}
|
|
485
317
|
|
|
486
318
|
const OPEN_CONFIG_ACTION$1 = 'Open .herb.yml';
|
|
@@ -563,8 +395,24 @@ class LinterService {
|
|
|
563
395
|
this.connection.window.showWarningMessage(message);
|
|
564
396
|
}
|
|
565
397
|
}
|
|
398
|
+
shouldLintFile(uri) {
|
|
399
|
+
const filePath = uri.replace(/^file:\/\//, '');
|
|
400
|
+
if (filePath.endsWith('.herb.yml'))
|
|
401
|
+
return false;
|
|
402
|
+
const config$1 = this.settings.projectConfig;
|
|
403
|
+
if (!config$1)
|
|
404
|
+
return true;
|
|
405
|
+
const hasConfigFile = config.Config.exists(config$1.projectPath);
|
|
406
|
+
if (!hasConfigFile)
|
|
407
|
+
return true;
|
|
408
|
+
const relativePath = filePath.replace(this.project.projectPath + '/', '');
|
|
409
|
+
return config$1.isLinterEnabledForPath(relativePath);
|
|
410
|
+
}
|
|
566
411
|
async lintDocument(textDocument) {
|
|
567
412
|
var _a, _b, _c, _d;
|
|
413
|
+
if (!this.shouldLintFile(textDocument.uri)) {
|
|
414
|
+
return { diagnostics: [] };
|
|
415
|
+
}
|
|
568
416
|
const settings = await this.settings.getDocumentSettings(textDocument.uri);
|
|
569
417
|
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
418
|
if (!linterEnabled) {
|
|
@@ -590,14 +438,14 @@ class LinterService {
|
|
|
590
438
|
const content = textDocument.getText();
|
|
591
439
|
const lintResult = this.linter.lint(content, { fileName: textDocument.uri });
|
|
592
440
|
const diagnostics = lintResult.offenses.map(offense => {
|
|
593
|
-
const range =
|
|
441
|
+
const range = lspRangeFromLocation(offense.location);
|
|
594
442
|
const customRulePath = this.customRulePaths.get(offense.rule);
|
|
595
443
|
const codeDescription = {
|
|
596
444
|
href: customRulePath
|
|
597
445
|
? `file://${customRulePath}`
|
|
598
|
-
:
|
|
446
|
+
: linter.ruleDocumentationUrl(offense.rule)
|
|
599
447
|
};
|
|
600
|
-
|
|
448
|
+
const diagnostic = {
|
|
601
449
|
source: this.source,
|
|
602
450
|
severity: lintToDignosticSeverity(offense.severity),
|
|
603
451
|
range,
|
|
@@ -606,6 +454,11 @@ class LinterService {
|
|
|
606
454
|
data: { rule: offense.rule },
|
|
607
455
|
codeDescription
|
|
608
456
|
};
|
|
457
|
+
const tags = lintToDignosticTags(offense.tags);
|
|
458
|
+
if (tags.length > 0) {
|
|
459
|
+
diagnostic.tags = tags;
|
|
460
|
+
}
|
|
461
|
+
return diagnostic;
|
|
609
462
|
});
|
|
610
463
|
return { diagnostics };
|
|
611
464
|
}
|
|
@@ -770,7 +623,7 @@ class FormattingService {
|
|
|
770
623
|
this.postRewriters = [];
|
|
771
624
|
}
|
|
772
625
|
}
|
|
773
|
-
async formatOnSave(document, reason) {
|
|
626
|
+
async formatOnSave(document, reason, textOverride) {
|
|
774
627
|
this.connection.console.log(`[Formatting] formatOnSave called for ${document.uri}`);
|
|
775
628
|
if (reason !== node.TextDocumentSaveReason.Manual) {
|
|
776
629
|
this.connection.console.log(`[Formatting] Skipping: reason=${reason} (not manual)`);
|
|
@@ -781,7 +634,7 @@ class FormattingService {
|
|
|
781
634
|
this.connection.console.log(`[Formatting] Skipping: file not in formatter config`);
|
|
782
635
|
return [];
|
|
783
636
|
}
|
|
784
|
-
return this.performFormatting({ textDocument: { uri: document.uri }, options: { tabSize: 2, insertSpaces: true } });
|
|
637
|
+
return this.performFormatting({ textDocument: { uri: document.uri }, options: { tabSize: 2, insertSpaces: true } }, textOverride);
|
|
785
638
|
}
|
|
786
639
|
shouldFormatFile(filePath) {
|
|
787
640
|
if (filePath.endsWith('.herb.yml'))
|
|
@@ -809,13 +662,13 @@ class FormattingService {
|
|
|
809
662
|
}
|
|
810
663
|
};
|
|
811
664
|
}
|
|
812
|
-
async performFormatting(params) {
|
|
665
|
+
async performFormatting(params, textOverride) {
|
|
813
666
|
const document = this.documents.get(params.textDocument.uri);
|
|
814
667
|
if (!document) {
|
|
815
668
|
return [];
|
|
816
669
|
}
|
|
817
670
|
try {
|
|
818
|
-
const text = document.getText();
|
|
671
|
+
const text = textOverride !== null && textOverride !== void 0 ? textOverride : document.getText();
|
|
819
672
|
const config = await this.getConfigWithSettings(params.textDocument.uri);
|
|
820
673
|
this.connection.console.log(`[Formatting] Creating formatter with ${this.preRewriters.length} pre-rewriters, ${this.postRewriters.length} post-rewriters`);
|
|
821
674
|
if (this.failedRewriters.size > 0) {
|
|
@@ -1287,10 +1140,7 @@ class CodeActionService {
|
|
|
1287
1140
|
};
|
|
1288
1141
|
}
|
|
1289
1142
|
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
|
-
};
|
|
1143
|
+
return lspRangeFromLocation(offense.location);
|
|
1294
1144
|
}
|
|
1295
1145
|
rangesEqual(r1, r2) {
|
|
1296
1146
|
return (r1.start.line === r2.start.line &&
|
|
@@ -1309,6 +1159,14 @@ class CodeActionService {
|
|
|
1309
1159
|
|
|
1310
1160
|
class DocumentSaveService {
|
|
1311
1161
|
constructor(connection, settings, autofixService, formattingService) {
|
|
1162
|
+
/**
|
|
1163
|
+
* Tracks documents that were recently autofixed via applyFixesAndFormatting
|
|
1164
|
+
* (triggered by onDocumentFormatting). When editor.formatOnSave is enabled,
|
|
1165
|
+
* onDocumentFormatting fires BEFORE willSaveWaitUntil. If applyFixesAndFormatting
|
|
1166
|
+
* already applied autofix, applyFixes must skip to avoid conflicting edits
|
|
1167
|
+
* (since this.documents hasn't been updated between the two events).
|
|
1168
|
+
*/
|
|
1169
|
+
this.recentlyAutofixedViaFormatting = new Set();
|
|
1312
1170
|
this.connection = connection;
|
|
1313
1171
|
this.settings = settings;
|
|
1314
1172
|
this.autofixService = autofixService;
|
|
@@ -1325,6 +1183,10 @@ class DocumentSaveService {
|
|
|
1325
1183
|
this.connection.console.log(`[DocumentSave] applyFixes fixOnSave=${fixOnSave}`);
|
|
1326
1184
|
if (!fixOnSave)
|
|
1327
1185
|
return [];
|
|
1186
|
+
if (this.recentlyAutofixedViaFormatting.delete(document.uri)) {
|
|
1187
|
+
this.connection.console.log(`[DocumentSave] applyFixes skipping: already autofixed via formatting`);
|
|
1188
|
+
return [];
|
|
1189
|
+
}
|
|
1328
1190
|
return this.autofixService.autofix(document);
|
|
1329
1191
|
}
|
|
1330
1192
|
/**
|
|
@@ -1340,97 +1202,1150 @@ class DocumentSaveService {
|
|
|
1340
1202
|
let autofixEdits = [];
|
|
1341
1203
|
if (fixOnSave) {
|
|
1342
1204
|
autofixEdits = await this.autofixService.autofix(document);
|
|
1205
|
+
if (autofixEdits.length > 0) {
|
|
1206
|
+
this.recentlyAutofixedViaFormatting.add(document.uri);
|
|
1207
|
+
}
|
|
1343
1208
|
}
|
|
1344
1209
|
if (!formatterEnabled)
|
|
1345
1210
|
return autofixEdits;
|
|
1346
1211
|
if (autofixEdits.length === 0) {
|
|
1347
1212
|
return this.formattingService.formatOnSave(document, reason);
|
|
1348
1213
|
}
|
|
1349
|
-
|
|
1350
|
-
...document,
|
|
1351
|
-
uri: document.uri,
|
|
1352
|
-
getText: () => autofixEdits[0].newText,
|
|
1353
|
-
};
|
|
1354
|
-
return this.formattingService.formatOnSave(autofixedDocument, reason);
|
|
1214
|
+
return this.formattingService.formatOnSave(document, reason, autofixEdits[0].newText);
|
|
1355
1215
|
}
|
|
1356
1216
|
}
|
|
1357
1217
|
|
|
1358
|
-
class
|
|
1359
|
-
constructor(
|
|
1360
|
-
this.
|
|
1361
|
-
this.settings = new Settings(params, this.connection);
|
|
1362
|
-
this.documentService = new DocumentService(this.connection);
|
|
1363
|
-
this.project = new Project(connection, this.settings.projectPath.replace("file://", ""));
|
|
1364
|
-
this.parserService = new ParserService();
|
|
1365
|
-
this.linterService = new LinterService(this.connection, this.settings, this.project);
|
|
1366
|
-
this.formattingService = new FormattingService(this.connection, this.documentService.documents, this.project, this.settings);
|
|
1367
|
-
this.autofixService = new AutofixService(this.connection, this.config);
|
|
1368
|
-
this.configService = new ConfigService(this.project.projectPath);
|
|
1369
|
-
this.codeActionService = new CodeActionService(this.project, this.config);
|
|
1370
|
-
this.diagnostics = new Diagnostics(this.connection, this.documentService, this.parserService, this.linterService, this.configService);
|
|
1371
|
-
this.documentSaveService = new DocumentSaveService(this.connection, this.settings, this.autofixService, this.formattingService);
|
|
1372
|
-
if (params.initializationOptions) {
|
|
1373
|
-
this.settings.globalSettings = params.initializationOptions;
|
|
1374
|
-
}
|
|
1218
|
+
class FoldingRangeService {
|
|
1219
|
+
constructor(parserService) {
|
|
1220
|
+
this.parserService = parserService;
|
|
1375
1221
|
}
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
formatter: this.settings.globalSettings.formatter
|
|
1393
|
-
}, { projectPath: this.project.projectPath, version });
|
|
1394
|
-
this.codeActionService.setConfig(this.config);
|
|
1395
|
-
this.autofixService.setConfig(this.config);
|
|
1222
|
+
getFoldingRanges(textDocument) {
|
|
1223
|
+
const parseResult = this.parserService.parseDocument(textDocument);
|
|
1224
|
+
const collector = new FoldingRangeCollector();
|
|
1225
|
+
collector.visit(parseResult.document);
|
|
1226
|
+
return collector.ranges;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
class FoldingRangeCollector extends core.Visitor {
|
|
1230
|
+
constructor() {
|
|
1231
|
+
super(...arguments);
|
|
1232
|
+
this.ranges = [];
|
|
1233
|
+
this.processedIfNodes = new Set();
|
|
1234
|
+
}
|
|
1235
|
+
visitHTMLElementNode(node) {
|
|
1236
|
+
if (node.body.length > 0 && node.open_tag && node.close_tag) {
|
|
1237
|
+
this.addRange(node.open_tag.location.end, node.close_tag.location.start);
|
|
1396
1238
|
}
|
|
1397
|
-
|
|
1398
|
-
await this.formattingService.refreshConfig(this.config);
|
|
1399
|
-
this.linterService.rebuildLinter();
|
|
1400
|
-
this.documentService.onDidClose((change) => {
|
|
1401
|
-
this.settings.documentSettings.delete(change.document.uri);
|
|
1402
|
-
});
|
|
1403
|
-
this.documentService.onDidChangeContent(async (change) => {
|
|
1404
|
-
await this.diagnostics.refreshDocument(change.document);
|
|
1405
|
-
});
|
|
1239
|
+
this.visitChildNodes(node);
|
|
1406
1240
|
}
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1241
|
+
visitHTMLOpenTagNode(node) {
|
|
1242
|
+
if (node.children.length > 0 && node.tag_opening && node.tag_closing) {
|
|
1243
|
+
this.addRange(node.tag_opening.location.end, node.tag_closing.location.start);
|
|
1244
|
+
}
|
|
1245
|
+
this.visitChildNodes(node);
|
|
1411
1246
|
}
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
this.
|
|
1415
|
-
this.codeActionService.setConfig(this.config);
|
|
1416
|
-
this.autofixService.setConfig(this.config);
|
|
1417
|
-
if (this.config.version && this.config.version !== version) {
|
|
1418
|
-
this.connection.console.warn(`Config file version (${this.config.version}) does not match current version (${version}). ` +
|
|
1419
|
-
`Consider updating your .herb.yml file.`);
|
|
1420
|
-
}
|
|
1247
|
+
visitHTMLCommentNode(node$1) {
|
|
1248
|
+
if (node$1.comment_start && node$1.comment_end) {
|
|
1249
|
+
this.addRange(node$1.comment_start.location.end, node$1.comment_end.location.start, node.FoldingRangeKind.Comment);
|
|
1421
1250
|
}
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
this.
|
|
1429
|
-
|
|
1251
|
+
this.visitChildNodes(node$1);
|
|
1252
|
+
}
|
|
1253
|
+
visitHTMLAttributeValueNode(node) {
|
|
1254
|
+
if (node.children.length > 0) {
|
|
1255
|
+
const first = node.children[0];
|
|
1256
|
+
const last = node.children[node.children.length - 1];
|
|
1257
|
+
this.addRange(first.location.start, last.location.end);
|
|
1258
|
+
}
|
|
1259
|
+
this.visitChildNodes(node);
|
|
1260
|
+
}
|
|
1261
|
+
visitCDATANode(node) {
|
|
1262
|
+
this.addRange(node.location.start, node.location.end);
|
|
1263
|
+
this.visitChildNodes(node);
|
|
1264
|
+
}
|
|
1265
|
+
visitHTMLConditionalElementNode(node) {
|
|
1266
|
+
this.addRange(node.location.start, node.location.end);
|
|
1267
|
+
this.visitChildNodes(node);
|
|
1268
|
+
}
|
|
1269
|
+
visitERBNode(node) {
|
|
1270
|
+
var _a;
|
|
1271
|
+
if (node.tag_closing && 'end_node' in node && ((_a = node.end_node) === null || _a === void 0 ? void 0 : _a.tag_opening)) {
|
|
1272
|
+
this.addRange(node.tag_closing.location.end, node.end_node.tag_opening.location.start);
|
|
1273
|
+
}
|
|
1274
|
+
else {
|
|
1275
|
+
this.addRange(node.location.start, node.location.end);
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
visitERBContentNode(node) {
|
|
1279
|
+
if (node.tag_opening && node.tag_closing) {
|
|
1280
|
+
this.addRange(node.tag_opening.location.end, node.tag_closing.location.start);
|
|
1281
|
+
}
|
|
1282
|
+
this.visitChildNodes(node);
|
|
1283
|
+
}
|
|
1284
|
+
visitERBIfNode(node) {
|
|
1285
|
+
var _a, _b;
|
|
1286
|
+
if (this.processedIfNodes.has(node)) {
|
|
1287
|
+
this.visitChildNodes(node);
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1290
|
+
this.markIfChainAsProcessed(node);
|
|
1291
|
+
const nextAfterIf = (_a = node.subsequent) !== null && _a !== void 0 ? _a : node.end_node;
|
|
1292
|
+
if (node.tag_closing && (nextAfterIf === null || nextAfterIf === void 0 ? void 0 : nextAfterIf.tag_opening)) {
|
|
1293
|
+
this.addRange(node.tag_closing.location.end, nextAfterIf.tag_opening.location.start);
|
|
1294
|
+
}
|
|
1295
|
+
let current = node.subsequent;
|
|
1296
|
+
while (current) {
|
|
1297
|
+
if (core.isERBIfNode(current)) {
|
|
1298
|
+
const nextAfterElsif = (_b = current.subsequent) !== null && _b !== void 0 ? _b : node.end_node;
|
|
1299
|
+
if (current.tag_closing && (nextAfterElsif === null || nextAfterElsif === void 0 ? void 0 : nextAfterElsif.tag_opening)) {
|
|
1300
|
+
this.addRange(current.tag_closing.location.end, nextAfterElsif.tag_opening.location.start);
|
|
1301
|
+
}
|
|
1302
|
+
current = current.subsequent;
|
|
1303
|
+
}
|
|
1304
|
+
else {
|
|
1305
|
+
break;
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
this.visitChildNodes(node);
|
|
1309
|
+
}
|
|
1310
|
+
visitERBUnlessNode(node) {
|
|
1311
|
+
var _a, _b;
|
|
1312
|
+
const nextAfterUnless = (_a = node.else_clause) !== null && _a !== void 0 ? _a : node.end_node;
|
|
1313
|
+
if (node.tag_closing && (nextAfterUnless === null || nextAfterUnless === void 0 ? void 0 : nextAfterUnless.tag_opening)) {
|
|
1314
|
+
this.addRange(node.tag_closing.location.end, nextAfterUnless.tag_opening.location.start);
|
|
1315
|
+
}
|
|
1316
|
+
if (node.else_clause) {
|
|
1317
|
+
if (node.else_clause.tag_closing && ((_b = node.end_node) === null || _b === void 0 ? void 0 : _b.tag_opening)) {
|
|
1318
|
+
this.addRange(node.else_clause.tag_closing.location.end, node.end_node.tag_opening.location.start);
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
this.visitChildNodes(node);
|
|
1322
|
+
}
|
|
1323
|
+
visitERBCaseNode(node) {
|
|
1324
|
+
this.addCaseFoldingRanges(node);
|
|
1325
|
+
this.visitChildNodes(node);
|
|
1326
|
+
}
|
|
1327
|
+
visitERBCaseMatchNode(node) {
|
|
1328
|
+
this.addCaseFoldingRanges(node);
|
|
1329
|
+
this.visitChildNodes(node);
|
|
1330
|
+
}
|
|
1331
|
+
visitERBWhenNode(node) {
|
|
1332
|
+
this.visitChildNodes(node);
|
|
1333
|
+
}
|
|
1334
|
+
visitERBInNode(node) {
|
|
1335
|
+
this.visitChildNodes(node);
|
|
1336
|
+
}
|
|
1337
|
+
visitERBBeginNode(node) {
|
|
1338
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1339
|
+
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;
|
|
1340
|
+
if (node.tag_closing && (nextAfterBegin === null || nextAfterBegin === void 0 ? void 0 : nextAfterBegin.tag_opening)) {
|
|
1341
|
+
this.addRange(node.tag_closing.location.end, nextAfterBegin.tag_opening.location.start);
|
|
1342
|
+
}
|
|
1343
|
+
let rescue = node.rescue_clause;
|
|
1344
|
+
while (rescue) {
|
|
1345
|
+
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;
|
|
1346
|
+
if (rescue.tag_closing && (nextAfterRescue === null || nextAfterRescue === void 0 ? void 0 : nextAfterRescue.tag_opening)) {
|
|
1347
|
+
this.addRange(rescue.tag_closing.location.end, nextAfterRescue.tag_opening.location.start);
|
|
1348
|
+
}
|
|
1349
|
+
rescue = rescue.subsequent;
|
|
1350
|
+
}
|
|
1351
|
+
if (node.else_clause) {
|
|
1352
|
+
const nextAfterElse = (_g = node.ensure_clause) !== null && _g !== void 0 ? _g : node.end_node;
|
|
1353
|
+
if (node.else_clause.tag_closing && (nextAfterElse === null || nextAfterElse === void 0 ? void 0 : nextAfterElse.tag_opening)) {
|
|
1354
|
+
this.addRange(node.else_clause.tag_closing.location.end, nextAfterElse.tag_opening.location.start);
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
if (node.ensure_clause) {
|
|
1358
|
+
if (node.ensure_clause.tag_closing && ((_h = node.end_node) === null || _h === void 0 ? void 0 : _h.tag_opening)) {
|
|
1359
|
+
this.addRange(node.ensure_clause.tag_closing.location.end, node.end_node.tag_opening.location.start);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
this.visitChildNodes(node);
|
|
1363
|
+
}
|
|
1364
|
+
visitERBRescueNode(node) {
|
|
1365
|
+
this.visitChildNodes(node);
|
|
1366
|
+
}
|
|
1367
|
+
visitERBElseNode(node) {
|
|
1368
|
+
this.addRange(node.location.start, node.location.end);
|
|
1369
|
+
this.visitChildNodes(node);
|
|
1370
|
+
}
|
|
1371
|
+
visitERBEnsureNode(node) {
|
|
1372
|
+
this.visitChildNodes(node);
|
|
1373
|
+
}
|
|
1374
|
+
markIfChainAsProcessed(node) {
|
|
1375
|
+
this.processedIfNodes.add(node);
|
|
1376
|
+
let current = node.subsequent;
|
|
1377
|
+
while (current) {
|
|
1378
|
+
if (core.isERBIfNode(current)) {
|
|
1379
|
+
this.processedIfNodes.add(current);
|
|
1380
|
+
current = current.subsequent;
|
|
1381
|
+
}
|
|
1382
|
+
else {
|
|
1383
|
+
break;
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
addCaseFoldingRanges(node) {
|
|
1388
|
+
var _a, _b, _c, _d;
|
|
1389
|
+
const conditions = node.conditions;
|
|
1390
|
+
const firstCondition = conditions[0];
|
|
1391
|
+
const nextAfterCase = (_a = firstCondition !== null && firstCondition !== void 0 ? firstCondition : node.else_clause) !== null && _a !== void 0 ? _a : node.end_node;
|
|
1392
|
+
if (node.tag_closing && (nextAfterCase === null || nextAfterCase === void 0 ? void 0 : nextAfterCase.tag_opening)) {
|
|
1393
|
+
this.addRange(node.tag_closing.location.end, nextAfterCase.tag_opening.location.start);
|
|
1394
|
+
}
|
|
1395
|
+
for (let i = 0; i < conditions.length; i++) {
|
|
1396
|
+
const condition = conditions[i];
|
|
1397
|
+
const nextCondition = (_c = (_b = conditions[i + 1]) !== null && _b !== void 0 ? _b : node.else_clause) !== null && _c !== void 0 ? _c : node.end_node;
|
|
1398
|
+
if (condition.tag_closing && (nextCondition === null || nextCondition === void 0 ? void 0 : nextCondition.tag_opening)) {
|
|
1399
|
+
this.addRange(condition.tag_closing.location.end, nextCondition.tag_opening.location.start);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
if (node.else_clause) {
|
|
1403
|
+
if (node.else_clause.tag_closing && ((_d = node.end_node) === null || _d === void 0 ? void 0 : _d.tag_opening)) {
|
|
1404
|
+
this.addRange(node.else_clause.tag_closing.location.end, node.end_node.tag_opening.location.start);
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
addRange(start, end, kind) {
|
|
1409
|
+
const startLine = lspLine(start);
|
|
1410
|
+
const endLine = lspLine(end) - 1;
|
|
1411
|
+
if (endLine > startLine) {
|
|
1412
|
+
this.ranges.push({
|
|
1413
|
+
startLine,
|
|
1414
|
+
startCharacter: start.column,
|
|
1415
|
+
endLine,
|
|
1416
|
+
endCharacter: end.column,
|
|
1417
|
+
kind,
|
|
1418
|
+
});
|
|
1430
1419
|
}
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
class DocumentHighlightCollector extends core.Visitor {
|
|
1424
|
+
constructor() {
|
|
1425
|
+
super(...arguments);
|
|
1426
|
+
this.groups = [];
|
|
1427
|
+
this.processedIfNodes = new Set();
|
|
1428
|
+
}
|
|
1429
|
+
visitERBNode(node) {
|
|
1430
|
+
if ('end_node' in node && node.end_node) {
|
|
1431
|
+
this.addGroup([erbTagToRange(node), erbTagToRange(node.end_node)]);
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
visitERBContentNode(node) {
|
|
1435
|
+
this.addGroup([tokenToRange(node.tag_opening), tokenToRange(node.tag_closing)]);
|
|
1436
|
+
this.visitChildNodes(node);
|
|
1437
|
+
}
|
|
1438
|
+
visitHTMLCommentNode(node) {
|
|
1439
|
+
this.addGroup([tokenToRange(node.comment_start), tokenToRange(node.comment_end)]);
|
|
1440
|
+
this.visitChildNodes(node);
|
|
1441
|
+
}
|
|
1442
|
+
visitHTMLElementNode(node) {
|
|
1443
|
+
const ranges = [];
|
|
1444
|
+
if (node.open_tag && core.isHTMLOpenTagNode(node.open_tag)) {
|
|
1445
|
+
ranges.push(...openTagRanges(node.open_tag));
|
|
1446
|
+
}
|
|
1447
|
+
else if (node.open_tag) {
|
|
1448
|
+
ranges.push(nodeToRange(node.open_tag));
|
|
1449
|
+
}
|
|
1450
|
+
if (node.close_tag) {
|
|
1451
|
+
ranges.push(nodeToRange(node.close_tag));
|
|
1452
|
+
}
|
|
1453
|
+
this.addGroup(ranges);
|
|
1454
|
+
this.visitChildNodes(node);
|
|
1455
|
+
}
|
|
1456
|
+
visitHTMLAttributeNode(node) {
|
|
1457
|
+
const ranges = [];
|
|
1458
|
+
if (node.name) {
|
|
1459
|
+
ranges.push(nodeToRange(node.name));
|
|
1460
|
+
}
|
|
1461
|
+
if (node.equals) {
|
|
1462
|
+
ranges.push(tokenToRange(node.equals));
|
|
1463
|
+
}
|
|
1464
|
+
if (node.value) {
|
|
1465
|
+
ranges.push(nodeToRange(node.value));
|
|
1466
|
+
}
|
|
1467
|
+
this.addGroup(ranges);
|
|
1468
|
+
this.visitChildNodes(node);
|
|
1469
|
+
}
|
|
1470
|
+
visitHTMLConditionalElementNode(node) {
|
|
1471
|
+
const ranges = [];
|
|
1472
|
+
if (node.open_conditional) {
|
|
1473
|
+
ranges.push(erbTagToRange(node.open_conditional));
|
|
1474
|
+
}
|
|
1475
|
+
if (node.open_tag) {
|
|
1476
|
+
ranges.push(...openTagRanges(node.open_tag));
|
|
1477
|
+
}
|
|
1478
|
+
if (node.close_tag) {
|
|
1479
|
+
ranges.push(nodeToRange(node.close_tag));
|
|
1480
|
+
}
|
|
1481
|
+
if (node.close_conditional) {
|
|
1482
|
+
ranges.push(erbTagToRange(node.close_conditional));
|
|
1483
|
+
}
|
|
1484
|
+
this.addGroup(ranges);
|
|
1485
|
+
this.visitChildNodes(node);
|
|
1486
|
+
}
|
|
1487
|
+
visitERBIfNode(node) {
|
|
1488
|
+
if (this.processedIfNodes.has(node)) {
|
|
1489
|
+
this.visitChildNodes(node);
|
|
1490
|
+
return;
|
|
1491
|
+
}
|
|
1492
|
+
this.markIfChainAsProcessed(node);
|
|
1493
|
+
const ranges = [];
|
|
1494
|
+
ranges.push(erbTagToRange(node));
|
|
1495
|
+
let current = node.subsequent;
|
|
1496
|
+
while (current) {
|
|
1497
|
+
if (core.isERBIfNode(current)) {
|
|
1498
|
+
ranges.push(erbTagToRange(current));
|
|
1499
|
+
current = current.subsequent;
|
|
1500
|
+
}
|
|
1501
|
+
else if (core.isERBElseNode(current)) {
|
|
1502
|
+
ranges.push(erbTagToRange(current));
|
|
1503
|
+
break;
|
|
1504
|
+
}
|
|
1505
|
+
else {
|
|
1506
|
+
break;
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
if (node.end_node) {
|
|
1510
|
+
ranges.push(erbTagToRange(node.end_node));
|
|
1511
|
+
}
|
|
1512
|
+
this.addGroup(ranges);
|
|
1513
|
+
this.visitChildNodes(node);
|
|
1514
|
+
}
|
|
1515
|
+
visitERBUnlessNode(node) {
|
|
1516
|
+
const ranges = [];
|
|
1517
|
+
ranges.push(erbTagToRange(node));
|
|
1518
|
+
if (node.else_clause) {
|
|
1519
|
+
ranges.push(erbTagToRange(node.else_clause));
|
|
1520
|
+
}
|
|
1521
|
+
if (node.end_node) {
|
|
1522
|
+
ranges.push(erbTagToRange(node.end_node));
|
|
1523
|
+
}
|
|
1524
|
+
this.addGroup(ranges);
|
|
1525
|
+
this.visitChildNodes(node);
|
|
1526
|
+
}
|
|
1527
|
+
visitERBCaseNode(node) {
|
|
1528
|
+
this.visitERBAnyCaseNode(node);
|
|
1529
|
+
}
|
|
1530
|
+
visitERBCaseMatchNode(node) {
|
|
1531
|
+
this.visitERBAnyCaseNode(node);
|
|
1532
|
+
}
|
|
1533
|
+
visitERBAnyCaseNode(node) {
|
|
1534
|
+
const ranges = [];
|
|
1535
|
+
ranges.push(erbTagToRange(node));
|
|
1536
|
+
for (const condition of node.conditions) {
|
|
1537
|
+
ranges.push(erbTagToRange(condition));
|
|
1538
|
+
}
|
|
1539
|
+
if (node.else_clause) {
|
|
1540
|
+
ranges.push(erbTagToRange(node.else_clause));
|
|
1541
|
+
}
|
|
1542
|
+
if (node.end_node) {
|
|
1543
|
+
ranges.push(erbTagToRange(node.end_node));
|
|
1544
|
+
}
|
|
1545
|
+
this.addGroup(ranges);
|
|
1546
|
+
this.visitChildNodes(node);
|
|
1547
|
+
}
|
|
1548
|
+
visitERBBeginNode(node) {
|
|
1549
|
+
const ranges = [];
|
|
1550
|
+
ranges.push(erbTagToRange(node));
|
|
1551
|
+
let rescue = node.rescue_clause;
|
|
1552
|
+
while (rescue) {
|
|
1553
|
+
ranges.push(erbTagToRange(rescue));
|
|
1554
|
+
rescue = rescue.subsequent;
|
|
1555
|
+
}
|
|
1556
|
+
if (node.else_clause) {
|
|
1557
|
+
ranges.push(erbTagToRange(node.else_clause));
|
|
1558
|
+
}
|
|
1559
|
+
if (node.ensure_clause) {
|
|
1560
|
+
ranges.push(erbTagToRange(node.ensure_clause));
|
|
1561
|
+
}
|
|
1562
|
+
if (node.end_node) {
|
|
1563
|
+
ranges.push(erbTagToRange(node.end_node));
|
|
1564
|
+
}
|
|
1565
|
+
this.addGroup(ranges);
|
|
1566
|
+
this.visitChildNodes(node);
|
|
1567
|
+
}
|
|
1568
|
+
markIfChainAsProcessed(node) {
|
|
1569
|
+
this.processedIfNodes.add(node);
|
|
1570
|
+
let current = node.subsequent;
|
|
1571
|
+
while (current) {
|
|
1572
|
+
if (core.isERBIfNode(current)) {
|
|
1573
|
+
this.processedIfNodes.add(current);
|
|
1574
|
+
current = current.subsequent;
|
|
1575
|
+
}
|
|
1576
|
+
else {
|
|
1577
|
+
break;
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
addGroup(ranges) {
|
|
1582
|
+
const filtered = ranges.filter((r) => r !== null);
|
|
1583
|
+
if (filtered.length >= 2) {
|
|
1584
|
+
this.groups.push(filtered);
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
class DocumentHighlightService {
|
|
1589
|
+
constructor(parserService) {
|
|
1590
|
+
this.parserService = parserService;
|
|
1591
|
+
}
|
|
1592
|
+
getDocumentHighlights(textDocument, position) {
|
|
1593
|
+
const parseResult = this.parserService.parseDocument(textDocument);
|
|
1594
|
+
const collector = new DocumentHighlightCollector();
|
|
1595
|
+
collector.visit(parseResult.document);
|
|
1596
|
+
let bestGroup = null;
|
|
1597
|
+
let bestSize = Infinity;
|
|
1598
|
+
for (const group of collector.groups) {
|
|
1599
|
+
const matchingRange = group.find(range => isPositionInRange(position, range));
|
|
1600
|
+
if (matchingRange) {
|
|
1601
|
+
const size = rangeSize(matchingRange);
|
|
1602
|
+
if (size < bestSize) {
|
|
1603
|
+
bestSize = size;
|
|
1604
|
+
bestGroup = group;
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
if (bestGroup) {
|
|
1609
|
+
return bestGroup.map(range => node.DocumentHighlight.create(range, node.DocumentHighlightKind.Text));
|
|
1610
|
+
}
|
|
1611
|
+
return [];
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
const ACTION_VIEW_HELPERS = {
|
|
1616
|
+
"ActionView::Helpers::TagHelper#tag": {
|
|
1617
|
+
signature: "tag.<tag name>(optional content, options)",
|
|
1618
|
+
documentationURL: "https://api.rubyonrails.org/classes/ActionView/Helpers/TagHelper.html#method-i-tag",
|
|
1619
|
+
},
|
|
1620
|
+
"ActionView::Helpers::TagHelper#content_tag": {
|
|
1621
|
+
signature: "content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block)",
|
|
1622
|
+
documentationURL: "https://api.rubyonrails.org/classes/ActionView/Helpers/TagHelper.html#method-i-content_tag",
|
|
1623
|
+
},
|
|
1624
|
+
"ActionView::Helpers::UrlHelper#link_to": {
|
|
1625
|
+
signature: "link_to(name = nil, options = nil, html_options = nil, &block)",
|
|
1626
|
+
documentationURL: "https://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to",
|
|
1627
|
+
},
|
|
1628
|
+
"Turbo::FramesHelper#turbo_frame_tag": {
|
|
1629
|
+
signature: "turbo_frame_tag(*ids, src: nil, target: nil, **attributes, &block)",
|
|
1630
|
+
documentationURL: "https://www.rubydoc.info/github/hotwired/turbo-rails/Turbo/FramesHelper:turbo_frame_tag",
|
|
1631
|
+
},
|
|
1632
|
+
};
|
|
1633
|
+
|
|
1634
|
+
class ActionViewElementCollector extends nodeWasm.Visitor {
|
|
1635
|
+
constructor() {
|
|
1636
|
+
super(...arguments);
|
|
1637
|
+
this.elements = [];
|
|
1638
|
+
}
|
|
1639
|
+
visitHTMLElementNode(node) {
|
|
1640
|
+
if (node.element_source && node.element_source !== "HTML" && core.isERBOpenTagNode(node.open_tag)) {
|
|
1641
|
+
this.elements.push({
|
|
1642
|
+
node,
|
|
1643
|
+
range: nodeToRange(node),
|
|
1644
|
+
});
|
|
1645
|
+
}
|
|
1646
|
+
this.visitChildNodes(node);
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
class HoverService {
|
|
1650
|
+
constructor(parserService) {
|
|
1651
|
+
this.parserService = parserService;
|
|
1652
|
+
}
|
|
1653
|
+
getHover(textDocument, position) {
|
|
1654
|
+
const parseResult = this.parserService.parseContent(textDocument.getText(), {
|
|
1655
|
+
action_view_helpers: true,
|
|
1656
|
+
track_whitespace: true,
|
|
1657
|
+
});
|
|
1658
|
+
const collector = new ActionViewElementCollector();
|
|
1659
|
+
collector.visit(parseResult.value);
|
|
1660
|
+
let bestElement = null;
|
|
1661
|
+
let bestSize = Infinity;
|
|
1662
|
+
for (const element of collector.elements) {
|
|
1663
|
+
if (isPositionInRange(position, element.range)) {
|
|
1664
|
+
const size = rangeSize(element.range);
|
|
1665
|
+
if (size < bestSize) {
|
|
1666
|
+
bestSize = size;
|
|
1667
|
+
bestElement = element;
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
if (!bestElement) {
|
|
1672
|
+
return null;
|
|
1673
|
+
}
|
|
1674
|
+
const elementSource = bestElement.node.element_source;
|
|
1675
|
+
const rewriter$1 = new rewriter.ActionViewTagHelperToHTMLRewriter();
|
|
1676
|
+
const rewrittenNode = rewriter$1.rewrite(bestElement.node, { baseDir: process.cwd() });
|
|
1677
|
+
const htmlOutput = printer.IdentityPrinter.print(rewrittenNode);
|
|
1678
|
+
const helper = ACTION_VIEW_HELPERS[elementSource];
|
|
1679
|
+
const parts = [];
|
|
1680
|
+
if (helper) {
|
|
1681
|
+
parts.push(`\`\`\`ruby\n${helper.signature}\n\`\`\``);
|
|
1682
|
+
}
|
|
1683
|
+
parts.push(`**HTML equivalent**\n\`\`\`erb\n${htmlOutput.trim()}\n\`\`\``);
|
|
1684
|
+
if (helper) {
|
|
1685
|
+
parts.push(`[${elementSource}](${helper.documentationURL})`);
|
|
1686
|
+
}
|
|
1687
|
+
return {
|
|
1688
|
+
contents: {
|
|
1689
|
+
kind: node.MarkupKind.Markdown,
|
|
1690
|
+
value: parts.join("\n\n"),
|
|
1691
|
+
},
|
|
1692
|
+
range: bestElement.range,
|
|
1693
|
+
};
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
class ElementCollector extends nodeWasm.Visitor {
|
|
1698
|
+
constructor() {
|
|
1699
|
+
super(...arguments);
|
|
1700
|
+
this.actionViewElements = [];
|
|
1701
|
+
this.htmlElements = [];
|
|
1702
|
+
}
|
|
1703
|
+
visitHTMLElementNode(node) {
|
|
1704
|
+
if (node.element_source && node.element_source !== "HTML" && core.isERBOpenTagNode(node.open_tag)) {
|
|
1705
|
+
this.actionViewElements.push({
|
|
1706
|
+
node,
|
|
1707
|
+
range: nodeToRange(node),
|
|
1708
|
+
});
|
|
1709
|
+
}
|
|
1710
|
+
else if (core.isHTMLOpenTagNode(node.open_tag) && node.open_tag.tag_name) {
|
|
1711
|
+
this.htmlElements.push({
|
|
1712
|
+
node,
|
|
1713
|
+
range: nodeToRange(node),
|
|
1714
|
+
});
|
|
1715
|
+
}
|
|
1716
|
+
this.visitChildNodes(node);
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
class RewriteCodeActionService {
|
|
1720
|
+
constructor(parserService) {
|
|
1721
|
+
this.parserService = parserService;
|
|
1722
|
+
}
|
|
1723
|
+
getCodeActions(document, requestedRange) {
|
|
1724
|
+
const parseResult = this.parserService.parseContent(document.getText(), {
|
|
1725
|
+
action_view_helpers: true,
|
|
1726
|
+
track_whitespace: true,
|
|
1727
|
+
});
|
|
1728
|
+
const collector = new ElementCollector();
|
|
1729
|
+
collector.visit(parseResult.value);
|
|
1730
|
+
const actions = [];
|
|
1731
|
+
for (const element of collector.actionViewElements) {
|
|
1732
|
+
if (!this.rangesOverlap(element.range, requestedRange))
|
|
1733
|
+
continue;
|
|
1734
|
+
const action = this.createActionViewToHTMLAction(document, element);
|
|
1735
|
+
if (action) {
|
|
1736
|
+
actions.push(action);
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
for (const element of collector.htmlElements) {
|
|
1740
|
+
if (!this.rangesOverlap(element.range, requestedRange))
|
|
1741
|
+
continue;
|
|
1742
|
+
const action = this.createHTMLToActionViewAction(document, element);
|
|
1743
|
+
if (action) {
|
|
1744
|
+
actions.push(action);
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
return actions;
|
|
1748
|
+
}
|
|
1749
|
+
createActionViewToHTMLAction(document, element) {
|
|
1750
|
+
var _a;
|
|
1751
|
+
const originalText = document.getText(element.range);
|
|
1752
|
+
const parseResult = this.parserService.parseContent(originalText, {
|
|
1753
|
+
action_view_helpers: true,
|
|
1754
|
+
track_whitespace: true,
|
|
1755
|
+
});
|
|
1756
|
+
if (parseResult.failed)
|
|
1757
|
+
return null;
|
|
1758
|
+
const rewriter$1 = new rewriter.ActionViewTagHelperToHTMLRewriter();
|
|
1759
|
+
rewriter$1.rewrite(parseResult.value, { baseDir: process.cwd() });
|
|
1760
|
+
const rewrittenText = printer.IdentityPrinter.print(parseResult.value);
|
|
1761
|
+
if (rewrittenText === originalText)
|
|
1762
|
+
return null;
|
|
1763
|
+
const edit = {
|
|
1764
|
+
changes: {
|
|
1765
|
+
[document.uri]: [node.TextEdit.replace(element.range, rewrittenText)]
|
|
1766
|
+
}
|
|
1767
|
+
};
|
|
1768
|
+
const tagName = (_a = element.node.tag_name) === null || _a === void 0 ? void 0 : _a.value;
|
|
1769
|
+
const title = tagName
|
|
1770
|
+
? `Herb: Convert to \`<${tagName}>\``
|
|
1771
|
+
: "Herb: Convert to HTML";
|
|
1772
|
+
return {
|
|
1773
|
+
title,
|
|
1774
|
+
kind: node.CodeActionKind.RefactorRewrite,
|
|
1775
|
+
edit,
|
|
1776
|
+
};
|
|
1777
|
+
}
|
|
1778
|
+
createHTMLToActionViewAction(document, element) {
|
|
1779
|
+
var _a;
|
|
1780
|
+
const originalText = document.getText(element.range);
|
|
1781
|
+
const parseResult = this.parserService.parseContent(originalText, {
|
|
1782
|
+
track_whitespace: true,
|
|
1783
|
+
});
|
|
1784
|
+
if (parseResult.failed)
|
|
1785
|
+
return null;
|
|
1786
|
+
const rewriter$1 = new rewriter.HTMLToActionViewTagHelperRewriter();
|
|
1787
|
+
rewriter$1.rewrite(parseResult.value, { baseDir: process.cwd() });
|
|
1788
|
+
const rewrittenText = printer.IdentityPrinter.print(parseResult.value);
|
|
1789
|
+
if (rewrittenText === originalText)
|
|
1790
|
+
return null;
|
|
1791
|
+
const edit = {
|
|
1792
|
+
changes: {
|
|
1793
|
+
[document.uri]: [node.TextEdit.replace(element.range, rewrittenText)]
|
|
1794
|
+
}
|
|
1795
|
+
};
|
|
1796
|
+
const tagName = (_a = element.node.tag_name) === null || _a === void 0 ? void 0 : _a.value;
|
|
1797
|
+
const isAnchor = tagName === "a";
|
|
1798
|
+
const isTurboFrame = tagName === "turbo-frame";
|
|
1799
|
+
const methodName = tagName === null || tagName === void 0 ? void 0 : tagName.replace(/-/g, "_");
|
|
1800
|
+
const title = isAnchor
|
|
1801
|
+
? "Herb: Convert to `link_to`"
|
|
1802
|
+
: isTurboFrame
|
|
1803
|
+
? "Herb: Convert to `turbo_frame_tag`"
|
|
1804
|
+
: methodName
|
|
1805
|
+
? `Herb: Convert to \`tag.${methodName}\``
|
|
1806
|
+
: "Herb: Convert to tag helper";
|
|
1807
|
+
return {
|
|
1808
|
+
title,
|
|
1809
|
+
kind: node.CodeActionKind.RefactorRewrite,
|
|
1810
|
+
edit,
|
|
1811
|
+
};
|
|
1812
|
+
}
|
|
1813
|
+
rangesOverlap(r1, r2) {
|
|
1814
|
+
if (r1.end.line < r2.start.line)
|
|
1815
|
+
return false;
|
|
1816
|
+
if (r1.start.line > r2.end.line)
|
|
1817
|
+
return false;
|
|
1818
|
+
if (r1.end.line === r2.start.line && r1.end.character < r2.start.character)
|
|
1819
|
+
return false;
|
|
1820
|
+
if (r1.start.line === r2.end.line && r1.start.character > r2.end.character)
|
|
1821
|
+
return false;
|
|
1822
|
+
return true;
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
class LineContextCollector extends core.Visitor {
|
|
1827
|
+
constructor() {
|
|
1828
|
+
super(...arguments);
|
|
1829
|
+
this.lineMap = new Map();
|
|
1830
|
+
this.erbNodesPerLine = new Map();
|
|
1831
|
+
this.htmlCommentNodesPerLine = new Map();
|
|
1832
|
+
}
|
|
1833
|
+
visitERBNode(node) {
|
|
1834
|
+
if (!node.tag_opening || !node.tag_closing)
|
|
1835
|
+
return;
|
|
1836
|
+
const startLine = lspLine(node.tag_opening.location.start);
|
|
1837
|
+
const nodes = this.erbNodesPerLine.get(startLine) || [];
|
|
1838
|
+
nodes.push(node);
|
|
1839
|
+
this.erbNodesPerLine.set(startLine, nodes);
|
|
1840
|
+
if (core.isERBCommentNode(node)) {
|
|
1841
|
+
this.setLine(startLine, "erb-comment", node);
|
|
1842
|
+
}
|
|
1843
|
+
else {
|
|
1844
|
+
this.setLine(startLine, "erb-tag", node);
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
visitERBContentNode(node) {
|
|
1848
|
+
this.visitERBNode(node);
|
|
1849
|
+
this.visitChildNodes(node);
|
|
1850
|
+
}
|
|
1851
|
+
visitHTMLCommentNode(node) {
|
|
1852
|
+
const startLine = lspLine(node.location.start);
|
|
1853
|
+
const endLine = lspLine(node.location.end);
|
|
1854
|
+
for (let line = startLine; line <= endLine; line++) {
|
|
1855
|
+
this.htmlCommentNodesPerLine.set(line, node);
|
|
1856
|
+
this.setLine(line, "html-comment", node);
|
|
1857
|
+
}
|
|
1858
|
+
this.visitChildNodes(node);
|
|
1859
|
+
}
|
|
1860
|
+
visitHTMLElementNode(node) {
|
|
1861
|
+
const startLine = lspLine(node.location.start);
|
|
1862
|
+
const endLine = lspLine(node.location.end);
|
|
1863
|
+
for (let line = startLine; line <= endLine; line++) {
|
|
1864
|
+
if (!this.lineMap.has(line)) {
|
|
1865
|
+
this.setLine(line, "html-content", node);
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
this.visitChildNodes(node);
|
|
1869
|
+
}
|
|
1870
|
+
visitHTMLTextNode(node) {
|
|
1871
|
+
const startLine = lspLine(node.location.start);
|
|
1872
|
+
const endLine = lspLine(node.location.end);
|
|
1873
|
+
for (let line = startLine; line <= endLine; line++) {
|
|
1874
|
+
if (!this.lineMap.has(line)) {
|
|
1875
|
+
this.setLine(line, "html-content", node);
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
this.visitChildNodes(node);
|
|
1879
|
+
}
|
|
1880
|
+
setLine(line, context, node) {
|
|
1881
|
+
const existing = this.lineMap.get(line);
|
|
1882
|
+
if (existing) {
|
|
1883
|
+
if (existing.context === "erb-comment" || existing.context === "erb-tag")
|
|
1884
|
+
return;
|
|
1885
|
+
if (context === "erb-comment" || context === "erb-tag") {
|
|
1886
|
+
this.lineMap.set(line, { line, context, node });
|
|
1887
|
+
return;
|
|
1888
|
+
}
|
|
1889
|
+
if (existing.context === "html-comment")
|
|
1890
|
+
return;
|
|
1891
|
+
}
|
|
1892
|
+
this.lineMap.set(line, { line, context, node });
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
function commentERBNode(node) {
|
|
1897
|
+
const mutable = rewriter.asMutable(node);
|
|
1898
|
+
if (mutable.tag_opening) {
|
|
1899
|
+
const currentValue = mutable.tag_opening.value;
|
|
1900
|
+
mutable.tag_opening = core.createSyntheticToken(currentValue.substring(0, 2) + "#" + currentValue.substring(2), mutable.tag_opening.type);
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
function uncommentERBNode(node) {
|
|
1904
|
+
var _a;
|
|
1905
|
+
const mutable = rewriter.asMutable(node);
|
|
1906
|
+
if (mutable.tag_opening && mutable.tag_opening.value === "<%#") {
|
|
1907
|
+
const contentValue = ((_a = mutable.content) === null || _a === void 0 ? void 0 : _a.value) || "";
|
|
1908
|
+
if (contentValue.startsWith(" graphql ") ||
|
|
1909
|
+
contentValue.startsWith(" %= ") ||
|
|
1910
|
+
contentValue.startsWith(" == ") ||
|
|
1911
|
+
contentValue.startsWith(" % ") ||
|
|
1912
|
+
contentValue.startsWith(" = ") ||
|
|
1913
|
+
contentValue.startsWith(" - ")) {
|
|
1914
|
+
mutable.tag_opening = core.createSyntheticToken("<%", mutable.tag_opening.type);
|
|
1915
|
+
mutable.content = core.createSyntheticToken(contentValue.substring(1), mutable.content.type);
|
|
1916
|
+
}
|
|
1917
|
+
else {
|
|
1918
|
+
mutable.tag_opening = core.createSyntheticToken("<%", mutable.tag_opening.type);
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
function determineStrategy(erbNodes, lineText) {
|
|
1923
|
+
if (erbNodes.length === 0) {
|
|
1924
|
+
return "html-only";
|
|
1925
|
+
}
|
|
1926
|
+
if (erbNodes.length === 1) {
|
|
1927
|
+
const node = erbNodes[0];
|
|
1928
|
+
if (!node.tag_opening || !node.tag_closing)
|
|
1929
|
+
return "html-only";
|
|
1930
|
+
const nodeStart = node.tag_opening.location.start.column;
|
|
1931
|
+
const nodeEnd = node.tag_closing.location.end.column;
|
|
1932
|
+
const isSoleContent = lineText.substring(0, nodeStart).trim() === "" && lineText.substring(nodeEnd).trim() === "";
|
|
1933
|
+
if (isSoleContent) {
|
|
1934
|
+
return "single-erb";
|
|
1935
|
+
}
|
|
1936
|
+
return "whole-line";
|
|
1937
|
+
}
|
|
1938
|
+
const segments = getLineSegments(lineText, erbNodes);
|
|
1939
|
+
const hasHTML = segments.some(segment => !segment.isERB && segment.text.trim() !== "");
|
|
1940
|
+
if (!hasHTML) {
|
|
1941
|
+
return "all-erb";
|
|
1942
|
+
}
|
|
1943
|
+
const allControlTags = erbNodes.every(node => { var _a; return ((_a = node.tag_opening) === null || _a === void 0 ? void 0 : _a.value) === "<%"; });
|
|
1944
|
+
if (allControlTags) {
|
|
1945
|
+
return "per-segment";
|
|
1946
|
+
}
|
|
1947
|
+
return "whole-line";
|
|
1948
|
+
}
|
|
1949
|
+
function getLineSegments(lineText, erbNodes) {
|
|
1950
|
+
const segments = [];
|
|
1951
|
+
const sorted = [...erbNodes].sort((a, b) => a.tag_opening.location.start.column - b.tag_opening.location.start.column);
|
|
1952
|
+
let position = 0;
|
|
1953
|
+
for (const node of sorted) {
|
|
1954
|
+
const nodeStart = node.tag_opening.location.start.column;
|
|
1955
|
+
const nodeEnd = node.tag_closing.location.end.column;
|
|
1956
|
+
if (nodeStart > position) {
|
|
1957
|
+
segments.push({ text: lineText.substring(position, nodeStart), isERB: false });
|
|
1958
|
+
}
|
|
1959
|
+
segments.push({ text: lineText.substring(nodeStart, nodeEnd), isERB: true, node });
|
|
1960
|
+
position = nodeEnd;
|
|
1961
|
+
}
|
|
1962
|
+
if (position < lineText.length) {
|
|
1963
|
+
segments.push({ text: lineText.substring(position), isERB: false });
|
|
1964
|
+
}
|
|
1965
|
+
return segments;
|
|
1966
|
+
}
|
|
1967
|
+
/**
|
|
1968
|
+
* Comment a line using AST mutation for strategies where the parser produces flat children,
|
|
1969
|
+
* and text-segment manipulation for per-segment (where the parser nests nodes).
|
|
1970
|
+
*/
|
|
1971
|
+
function commentLineContent(content, erbNodes, strategy, parserService) {
|
|
1972
|
+
if (strategy === "per-segment") {
|
|
1973
|
+
return commentPerSegment(content, erbNodes);
|
|
1974
|
+
}
|
|
1975
|
+
const parseResult = parserService.parseContent(content, { track_whitespace: true });
|
|
1976
|
+
const lineCollector = new LineContextCollector();
|
|
1977
|
+
parseResult.visit(lineCollector);
|
|
1978
|
+
const lineERBNodes = lineCollector.erbNodesPerLine.get(0) || [];
|
|
1979
|
+
const document = parseResult.value;
|
|
1980
|
+
const children = rewriter.asMutable(document).children;
|
|
1981
|
+
switch (strategy) {
|
|
1982
|
+
case "all-erb":
|
|
1983
|
+
for (const node of lineERBNodes) {
|
|
1984
|
+
commentERBNode(node);
|
|
1985
|
+
}
|
|
1986
|
+
break;
|
|
1987
|
+
case "whole-line": {
|
|
1988
|
+
for (const node of lineERBNodes) {
|
|
1989
|
+
commentERBNode(node);
|
|
1990
|
+
}
|
|
1991
|
+
const commentNode = new core.HTMLCommentNode({
|
|
1992
|
+
type: "AST_HTML_COMMENT_NODE",
|
|
1993
|
+
location: core.Location.zero,
|
|
1994
|
+
errors: [],
|
|
1995
|
+
comment_start: core.createSyntheticToken("<!--", "TOKEN_HTML_COMMENT_START"),
|
|
1996
|
+
children: [core.createLiteral(" "), ...children.slice(), core.createLiteral(" ")],
|
|
1997
|
+
comment_end: core.createSyntheticToken("-->", "TOKEN_HTML_COMMENT_END"),
|
|
1998
|
+
});
|
|
1999
|
+
children.length = 0;
|
|
2000
|
+
children.push(commentNode);
|
|
2001
|
+
break;
|
|
2002
|
+
}
|
|
2003
|
+
case "html-only": {
|
|
2004
|
+
const commentNode = new core.HTMLCommentNode({
|
|
2005
|
+
type: "AST_HTML_COMMENT_NODE",
|
|
2006
|
+
location: core.Location.zero,
|
|
2007
|
+
errors: [],
|
|
2008
|
+
comment_start: core.createSyntheticToken("<!--", "TOKEN_HTML_COMMENT_START"),
|
|
2009
|
+
children: [core.createLiteral(" "), ...children.slice(), core.createLiteral(" ")],
|
|
2010
|
+
comment_end: core.createSyntheticToken("-->", "TOKEN_HTML_COMMENT_END"),
|
|
2011
|
+
});
|
|
2012
|
+
children.length = 0;
|
|
2013
|
+
children.push(commentNode);
|
|
2014
|
+
break;
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
return printer.IdentityPrinter.print(document, { ignoreErrors: true });
|
|
2018
|
+
}
|
|
2019
|
+
/**
|
|
2020
|
+
* Per-segment commenting uses text segments because the parser creates nested
|
|
2021
|
+
* structures (e.g., ERBIfNode) that don't allow flat child iteration.
|
|
2022
|
+
*/
|
|
2023
|
+
function commentPerSegment(content, erbNodes) {
|
|
2024
|
+
const segments = getLineSegments(content, erbNodes);
|
|
2025
|
+
return segments.map(segment => {
|
|
2026
|
+
if (segment.isERB) {
|
|
2027
|
+
return segment.text.substring(0, 2) + "#" + segment.text.substring(2);
|
|
2028
|
+
}
|
|
2029
|
+
else if (segment.text.trim() !== "") {
|
|
2030
|
+
return `<!-- ${segment.text} -->`;
|
|
2031
|
+
}
|
|
2032
|
+
return segment.text;
|
|
2033
|
+
}).join("");
|
|
2034
|
+
}
|
|
2035
|
+
function uncommentLineContent(content, parserService) {
|
|
2036
|
+
const parseResult = parserService.parseContent(content, { track_whitespace: true });
|
|
2037
|
+
const lineCollector = new LineContextCollector();
|
|
2038
|
+
parseResult.visit(lineCollector);
|
|
2039
|
+
const lineERBNodes = lineCollector.erbNodesPerLine.get(0) || [];
|
|
2040
|
+
const document = parseResult.value;
|
|
2041
|
+
const children = rewriter.asMutable(document).children;
|
|
2042
|
+
for (const node of lineERBNodes) {
|
|
2043
|
+
if (core.isERBCommentNode(node)) {
|
|
2044
|
+
uncommentERBNode(node);
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
let index = 0;
|
|
2048
|
+
while (index < children.length) {
|
|
2049
|
+
const child = children[index];
|
|
2050
|
+
if (child.type === "AST_HTML_COMMENT_NODE") {
|
|
2051
|
+
const commentNode = child;
|
|
2052
|
+
const innerChildren = [...commentNode.children];
|
|
2053
|
+
if (innerChildren.length > 0) {
|
|
2054
|
+
const first = innerChildren[0];
|
|
2055
|
+
if (core.isLiteralNode(first) && first.content.startsWith(" ")) {
|
|
2056
|
+
const trimmed = first.content.substring(1);
|
|
2057
|
+
if (trimmed === "") {
|
|
2058
|
+
innerChildren.shift();
|
|
2059
|
+
}
|
|
2060
|
+
else {
|
|
2061
|
+
innerChildren[0] = core.createLiteral(trimmed);
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
if (innerChildren.length > 0) {
|
|
2066
|
+
const last = innerChildren[innerChildren.length - 1];
|
|
2067
|
+
if (core.isLiteralNode(last) && last.content.endsWith(" ")) {
|
|
2068
|
+
const trimmed = last.content.substring(0, last.content.length - 1);
|
|
2069
|
+
if (trimmed === "") {
|
|
2070
|
+
innerChildren.pop();
|
|
2071
|
+
}
|
|
2072
|
+
else {
|
|
2073
|
+
innerChildren[innerChildren.length - 1] = core.createLiteral(trimmed);
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
const innerERBNodes = [];
|
|
2078
|
+
const innerCollector = new LineContextCollector();
|
|
2079
|
+
for (const innerChild of innerChildren) {
|
|
2080
|
+
innerCollector.visit(innerChild);
|
|
2081
|
+
}
|
|
2082
|
+
innerERBNodes.push(...(innerCollector.erbNodesPerLine.get(0) || []));
|
|
2083
|
+
for (const erbNode of innerERBNodes) {
|
|
2084
|
+
if (core.isERBCommentNode(erbNode)) {
|
|
2085
|
+
uncommentERBNode(erbNode);
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
children.splice(index, 1, ...innerChildren);
|
|
2089
|
+
index += innerChildren.length;
|
|
2090
|
+
continue;
|
|
2091
|
+
}
|
|
2092
|
+
index++;
|
|
2093
|
+
}
|
|
2094
|
+
return printer.IdentityPrinter.print(document, { ignoreErrors: true });
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
class CommentService {
|
|
2098
|
+
constructor(parserService) {
|
|
2099
|
+
this.parserService = parserService;
|
|
2100
|
+
}
|
|
2101
|
+
toggleLineComment(document, range) {
|
|
2102
|
+
const parseResult = this.parserService.parseDocument(document);
|
|
2103
|
+
const collector = new LineContextCollector();
|
|
2104
|
+
collector.visit(parseResult.document);
|
|
2105
|
+
const startLine = range.start.line;
|
|
2106
|
+
const endLine = range.end.line;
|
|
2107
|
+
const lineInfos = [];
|
|
2108
|
+
for (let line = startLine; line <= endLine; line++) {
|
|
2109
|
+
const lineText = document.getText(node.Range.create(line, 0, line + 1, 0)).replace(/\n$/, "");
|
|
2110
|
+
if (lineText.trim() === "") {
|
|
2111
|
+
continue;
|
|
2112
|
+
}
|
|
2113
|
+
if (this.lineIsIfFalseWrapped(lineText) !== null) {
|
|
2114
|
+
lineInfos.push({ line, context: "erb-comment", node: null });
|
|
2115
|
+
continue;
|
|
2116
|
+
}
|
|
2117
|
+
const htmlCommentNode = collector.htmlCommentNodesPerLine.get(line);
|
|
2118
|
+
const info = collector.lineMap.get(line);
|
|
2119
|
+
if (htmlCommentNode && this.htmlCommentSpansLine(htmlCommentNode, lineText)) {
|
|
2120
|
+
lineInfos.push({ line, context: "html-comment", node: htmlCommentNode });
|
|
2121
|
+
}
|
|
2122
|
+
else if (info) {
|
|
2123
|
+
if (info.context === "html-comment") {
|
|
2124
|
+
lineInfos.push({ line, context: "html-content", node: null });
|
|
2125
|
+
}
|
|
2126
|
+
else {
|
|
2127
|
+
lineInfos.push(info);
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
else {
|
|
2131
|
+
lineInfos.push({ line, context: "html-content", node: null });
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
if (lineInfos.length === 0)
|
|
2135
|
+
return [];
|
|
2136
|
+
const allCommented = lineInfos.every(info => info.context === "erb-comment" || info.context === "html-comment");
|
|
2137
|
+
const edits = [];
|
|
2138
|
+
if (allCommented) {
|
|
2139
|
+
for (const info of lineInfos) {
|
|
2140
|
+
const lineText = document.getText(node.Range.create(info.line, 0, info.line + 1, 0)).replace(/\n$/, "");
|
|
2141
|
+
const edit = this.uncommentLine(info, lineText, collector);
|
|
2142
|
+
if (edit)
|
|
2143
|
+
edits.push(edit);
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
else {
|
|
2147
|
+
for (const info of lineInfos) {
|
|
2148
|
+
if (info.context === "erb-comment" || info.context === "html-comment")
|
|
2149
|
+
continue;
|
|
2150
|
+
const lineText = document.getText(node.Range.create(info.line, 0, info.line + 1, 0)).replace(/\n$/, "");
|
|
2151
|
+
const erbNodes = collector.erbNodesPerLine.get(info.line) || [];
|
|
2152
|
+
const edit = this.commentLine(info, lineText, erbNodes, collector);
|
|
2153
|
+
if (edit)
|
|
2154
|
+
edits.push(edit);
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
return edits;
|
|
2158
|
+
}
|
|
2159
|
+
toggleBlockComment(document, range) {
|
|
2160
|
+
const startLine = range.start.line;
|
|
2161
|
+
const endLine = range.end.line;
|
|
2162
|
+
const firstLineText = document.getText(node.Range.create(startLine, 0, startLine + 1, 0)).replace(/\n$/, "");
|
|
2163
|
+
const lastLineText = document.getText(node.Range.create(endLine, 0, endLine + 1, 0)).replace(/\n$/, "");
|
|
2164
|
+
const isWrapped = firstLineText.trim() === "<% if false %>" && lastLineText.trim() === "<% end %>";
|
|
2165
|
+
if (isWrapped) {
|
|
2166
|
+
return [
|
|
2167
|
+
node.TextEdit.del(node.Range.create(endLine, 0, endLine + 1, 0)),
|
|
2168
|
+
node.TextEdit.del(node.Range.create(startLine, 0, startLine + 1, 0)),
|
|
2169
|
+
];
|
|
2170
|
+
}
|
|
2171
|
+
else {
|
|
2172
|
+
const firstLineIndent = this.getIndentation(firstLineText);
|
|
2173
|
+
return [
|
|
2174
|
+
node.TextEdit.insert(node.Position.create(endLine + 1, 0), `${firstLineIndent}<% end %>\n`),
|
|
2175
|
+
node.TextEdit.insert(node.Position.create(startLine, 0), `${firstLineIndent}<% if false %>\n`),
|
|
2176
|
+
];
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
commentLine(info, lineText, erbNodes, collector) {
|
|
2180
|
+
const lineRange = node.Range.create(info.line, 0, info.line, lineText.length);
|
|
2181
|
+
const indent = this.getIndentation(lineText);
|
|
2182
|
+
const content = lineText.trimStart();
|
|
2183
|
+
const htmlCommentNode = collector.htmlCommentNodesPerLine.get(info.line);
|
|
2184
|
+
if (htmlCommentNode) {
|
|
2185
|
+
return node.TextEdit.replace(lineRange, `${indent}<% if false %>${content}<% end %>`);
|
|
2186
|
+
}
|
|
2187
|
+
const strategy = determineStrategy(erbNodes, lineText);
|
|
2188
|
+
if (strategy === "single-erb") {
|
|
2189
|
+
const node$1 = erbNodes[0];
|
|
2190
|
+
const insertColumn = node$1.tag_opening.location.start.column + 2;
|
|
2191
|
+
return node.TextEdit.insert(node.Position.create(info.line, insertColumn), "#");
|
|
2192
|
+
}
|
|
2193
|
+
const result = commentLineContent(content, erbNodes, strategy, this.parserService);
|
|
2194
|
+
return node.TextEdit.replace(lineRange, indent + result);
|
|
2195
|
+
}
|
|
2196
|
+
lineIsIfFalseWrapped(lineText) {
|
|
2197
|
+
const trimmed = lineText.trimStart();
|
|
2198
|
+
const indent = this.getIndentation(lineText);
|
|
2199
|
+
if (trimmed.startsWith("<% if false %>") && trimmed.endsWith("<% end %>")) {
|
|
2200
|
+
const inner = trimmed.slice("<% if false %>".length, -"<% end %>".length);
|
|
2201
|
+
return indent + inner;
|
|
2202
|
+
}
|
|
2203
|
+
return null;
|
|
2204
|
+
}
|
|
2205
|
+
uncommentLine(info, lineText, collector) {
|
|
2206
|
+
var _a;
|
|
2207
|
+
const lineRange = node.Range.create(info.line, 0, info.line, lineText.length);
|
|
2208
|
+
const indent = this.getIndentation(lineText);
|
|
2209
|
+
const ifFalseContent = this.lineIsIfFalseWrapped(lineText);
|
|
2210
|
+
if (ifFalseContent !== null) {
|
|
2211
|
+
return node.TextEdit.replace(lineRange, ifFalseContent);
|
|
2212
|
+
}
|
|
2213
|
+
if (info.context === "erb-comment") {
|
|
2214
|
+
const node$1 = info.node;
|
|
2215
|
+
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))
|
|
2216
|
+
return null;
|
|
2217
|
+
const contentValue = (_a = node$1.content) === null || _a === void 0 ? void 0 : _a.value;
|
|
2218
|
+
const trimmedContent = (contentValue === null || contentValue === void 0 ? void 0 : contentValue.trim()) || "";
|
|
2219
|
+
if (trimmedContent.startsWith("<") && !trimmedContent.startsWith("<%")) {
|
|
2220
|
+
return node.TextEdit.replace(lineRange, `${indent}${trimmedContent}`);
|
|
2221
|
+
}
|
|
2222
|
+
if (lspLine(node$1.tag_opening.location.start) !== info.line)
|
|
2223
|
+
return null;
|
|
2224
|
+
const erbNodes = collector.erbNodesPerLine.get(info.line) || [];
|
|
2225
|
+
if (erbNodes.length > 1) {
|
|
2226
|
+
const content = lineText.trimStart();
|
|
2227
|
+
const result = uncommentLineContent(content, this.parserService);
|
|
2228
|
+
return node.TextEdit.replace(lineRange, indent + result);
|
|
2229
|
+
}
|
|
2230
|
+
const hashColumn = node$1.tag_opening.location.start.column + 2;
|
|
2231
|
+
if ((contentValue === null || contentValue === void 0 ? void 0 : contentValue.startsWith(" graphql ")) ||
|
|
2232
|
+
(contentValue === null || contentValue === void 0 ? void 0 : contentValue.startsWith(" %= ")) ||
|
|
2233
|
+
(contentValue === null || contentValue === void 0 ? void 0 : contentValue.startsWith(" == ")) ||
|
|
2234
|
+
(contentValue === null || contentValue === void 0 ? void 0 : contentValue.startsWith(" % ")) ||
|
|
2235
|
+
(contentValue === null || contentValue === void 0 ? void 0 : contentValue.startsWith(" = ")) ||
|
|
2236
|
+
(contentValue === null || contentValue === void 0 ? void 0 : contentValue.startsWith(" - "))) {
|
|
2237
|
+
return node.TextEdit.del(node.Range.create(info.line, hashColumn, info.line, hashColumn + 2));
|
|
2238
|
+
}
|
|
2239
|
+
return node.TextEdit.del(node.Range.create(info.line, hashColumn, info.line, hashColumn + 1));
|
|
2240
|
+
}
|
|
2241
|
+
if (info.context === "html-comment") {
|
|
2242
|
+
const commentNode = info.node;
|
|
2243
|
+
if ((commentNode === null || commentNode === void 0 ? void 0 : commentNode.comment_start) && (commentNode === null || commentNode === void 0 ? void 0 : commentNode.comment_end)) {
|
|
2244
|
+
const contentStart = commentNode.comment_start.location.end.column;
|
|
2245
|
+
const contentEnd = commentNode.comment_end.location.start.column;
|
|
2246
|
+
const innerContent = lineText.substring(contentStart, contentEnd).trim();
|
|
2247
|
+
const result = uncommentLineContent(innerContent, this.parserService);
|
|
2248
|
+
return node.TextEdit.replace(lineRange, `${indent}${result}`);
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
return null;
|
|
2252
|
+
}
|
|
2253
|
+
htmlCommentSpansLine(node, lineText) {
|
|
2254
|
+
if (!node.comment_start || !node.comment_end)
|
|
2255
|
+
return false;
|
|
2256
|
+
const commentStart = node.comment_start.location.start.column;
|
|
2257
|
+
const commentEnd = node.comment_end.location.end.column;
|
|
2258
|
+
const contentBefore = lineText.substring(0, commentStart).trim();
|
|
2259
|
+
const contentAfter = lineText.substring(commentEnd).trim();
|
|
2260
|
+
return contentBefore === "" && contentAfter === "";
|
|
2261
|
+
}
|
|
2262
|
+
getIndentation(lineText) {
|
|
2263
|
+
const match = lineText.match(/^(\s*)/);
|
|
2264
|
+
return match ? match[1] : "";
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
class Service {
|
|
2269
|
+
constructor(connection, params) {
|
|
2270
|
+
this.connection = connection;
|
|
2271
|
+
this.settings = new Settings(params, this.connection);
|
|
2272
|
+
this.documentService = new DocumentService(this.connection);
|
|
2273
|
+
this.project = new Project(connection, this.settings.projectPath.replace("file://", ""));
|
|
2274
|
+
this.parserService = new ParserService();
|
|
2275
|
+
this.linterService = new LinterService(this.connection, this.settings, this.project);
|
|
2276
|
+
this.formattingService = new FormattingService(this.connection, this.documentService.documents, this.project, this.settings);
|
|
2277
|
+
this.autofixService = new AutofixService(this.connection, this.config);
|
|
2278
|
+
this.configService = new ConfigService(this.project.projectPath);
|
|
2279
|
+
this.codeActionService = new CodeActionService(this.project, this.config);
|
|
2280
|
+
this.diagnostics = new Diagnostics(this.connection, this.documentService, this.parserService, this.linterService, this.configService);
|
|
2281
|
+
this.documentSaveService = new DocumentSaveService(this.connection, this.settings, this.autofixService, this.formattingService);
|
|
2282
|
+
this.foldingRangeService = new FoldingRangeService(this.parserService);
|
|
2283
|
+
this.documentHighlightService = new DocumentHighlightService(this.parserService);
|
|
2284
|
+
this.hoverService = new HoverService(this.parserService);
|
|
2285
|
+
this.rewriteCodeActionService = new RewriteCodeActionService(this.parserService);
|
|
2286
|
+
this.commentService = new CommentService(this.parserService);
|
|
2287
|
+
if (params.initializationOptions) {
|
|
2288
|
+
this.settings.globalSettings = params.initializationOptions;
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
async init() {
|
|
2292
|
+
await this.project.initialize();
|
|
2293
|
+
await this.formattingService.initialize();
|
|
2294
|
+
try {
|
|
2295
|
+
this.config = await config.Config.loadForEditor(this.project.projectPath, version);
|
|
2296
|
+
this.codeActionService.setConfig(this.config);
|
|
2297
|
+
this.autofixService.setConfig(this.config);
|
|
2298
|
+
if (this.config.version && this.config.version !== version) {
|
|
2299
|
+
this.connection.console.warn(`Config file version (${this.config.version}) does not match current version (${version}). ` +
|
|
2300
|
+
`Consider updating your .herb.yml file.`);
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
catch (error) {
|
|
2304
|
+
this.connection.console.warn(`Failed to load config: ${error instanceof Error ? error.message : String(error)}. Using personal settings with defaults.`);
|
|
2305
|
+
this.config = config.Config.fromObject({
|
|
2306
|
+
linter: this.settings.globalSettings.linter,
|
|
2307
|
+
formatter: this.settings.globalSettings.formatter
|
|
2308
|
+
}, { projectPath: this.project.projectPath, version });
|
|
2309
|
+
this.codeActionService.setConfig(this.config);
|
|
2310
|
+
this.autofixService.setConfig(this.config);
|
|
2311
|
+
}
|
|
2312
|
+
await this.settings.initializeProjectConfig(this.config);
|
|
2313
|
+
await this.formattingService.refreshConfig(this.config);
|
|
2314
|
+
this.linterService.rebuildLinter();
|
|
2315
|
+
this.documentService.onDidClose((change) => {
|
|
2316
|
+
this.settings.documentSettings.delete(change.document.uri);
|
|
2317
|
+
});
|
|
2318
|
+
this.documentService.onDidChangeContent(async (change) => {
|
|
2319
|
+
await this.diagnostics.refreshDocument(change.document);
|
|
2320
|
+
});
|
|
2321
|
+
}
|
|
2322
|
+
async refresh() {
|
|
2323
|
+
await this.project.refresh();
|
|
2324
|
+
await this.formattingService.refreshConfig(this.config);
|
|
2325
|
+
await this.diagnostics.refreshAllDocuments();
|
|
2326
|
+
}
|
|
2327
|
+
async refreshConfig() {
|
|
2328
|
+
try {
|
|
2329
|
+
this.config = await config.Config.loadForEditor(this.project.projectPath, version);
|
|
2330
|
+
this.codeActionService.setConfig(this.config);
|
|
2331
|
+
this.autofixService.setConfig(this.config);
|
|
2332
|
+
if (this.config.version && this.config.version !== version) {
|
|
2333
|
+
this.connection.console.warn(`Config file version (${this.config.version}) does not match current version (${version}). ` +
|
|
2334
|
+
`Consider updating your .herb.yml file.`);
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
catch (error) {
|
|
2338
|
+
this.connection.console.warn(`Failed to load config: ${error instanceof Error ? error.message : String(error)}. Using personal settings with defaults.`);
|
|
2339
|
+
this.config = config.Config.fromObject({
|
|
2340
|
+
linter: this.settings.globalSettings.linter,
|
|
2341
|
+
formatter: this.settings.globalSettings.formatter
|
|
2342
|
+
}, { projectPath: this.project.projectPath, version });
|
|
2343
|
+
this.codeActionService.setConfig(this.config);
|
|
2344
|
+
this.autofixService.setConfig(this.config);
|
|
2345
|
+
}
|
|
2346
|
+
await this.settings.refreshProjectConfig(this.config);
|
|
2347
|
+
await this.formattingService.refreshConfig(this.config);
|
|
2348
|
+
this.linterService.rebuildLinter();
|
|
1434
2349
|
}
|
|
1435
2350
|
}
|
|
1436
2351
|
|
|
@@ -1460,8 +2375,11 @@ class Server {
|
|
|
1460
2375
|
documentFormattingProvider: true,
|
|
1461
2376
|
documentRangeFormattingProvider: true,
|
|
1462
2377
|
codeActionProvider: {
|
|
1463
|
-
codeActionKinds: [node.CodeActionKind.QuickFix, node.CodeActionKind.SourceFixAll]
|
|
2378
|
+
codeActionKinds: [node.CodeActionKind.QuickFix, node.CodeActionKind.SourceFixAll, node.CodeActionKind.RefactorRewrite]
|
|
1464
2379
|
},
|
|
2380
|
+
foldingRangeProvider: true,
|
|
2381
|
+
documentHighlightProvider: true,
|
|
2382
|
+
hoverProvider: true,
|
|
1465
2383
|
},
|
|
1466
2384
|
};
|
|
1467
2385
|
if (this.service.settings.hasWorkspaceFolderCapability) {
|
|
@@ -1543,15 +2461,49 @@ class Server {
|
|
|
1543
2461
|
this.connection.onDocumentRangeFormatting((params) => {
|
|
1544
2462
|
return this.service.formattingService.formatRange(params);
|
|
1545
2463
|
});
|
|
2464
|
+
this.connection.onDocumentHighlight((params) => {
|
|
2465
|
+
const document = this.service.documentService.get(params.textDocument.uri);
|
|
2466
|
+
if (!document)
|
|
2467
|
+
return [];
|
|
2468
|
+
return this.service.documentHighlightService.getDocumentHighlights(document, params.position);
|
|
2469
|
+
});
|
|
2470
|
+
this.connection.onHover((params) => {
|
|
2471
|
+
const document = this.service.documentService.get(params.textDocument.uri);
|
|
2472
|
+
if (!document)
|
|
2473
|
+
return null;
|
|
2474
|
+
return this.service.hoverService.getHover(document, params.position);
|
|
2475
|
+
});
|
|
1546
2476
|
this.connection.onCodeAction((params) => {
|
|
1547
2477
|
const document = this.service.documentService.get(params.textDocument.uri);
|
|
1548
2478
|
if (!document)
|
|
1549
2479
|
return [];
|
|
2480
|
+
const parseResult = this.service.parserService.parseDocument(document);
|
|
2481
|
+
if (parseResult.diagnostics.length > 0)
|
|
2482
|
+
return [];
|
|
1550
2483
|
const diagnostics = params.context.diagnostics;
|
|
1551
2484
|
const documentText = document.getText();
|
|
1552
2485
|
const linterDisableCodeActions = this.service.codeActionService.createCodeActions(params.textDocument.uri, diagnostics, documentText);
|
|
1553
2486
|
const autofixCodeActions = this.service.codeActionService.autofixCodeActions(params, document);
|
|
1554
|
-
|
|
2487
|
+
const rewriteCodeActions = this.service.rewriteCodeActionService.getCodeActions(document, params.range);
|
|
2488
|
+
return autofixCodeActions.concat(linterDisableCodeActions).concat(rewriteCodeActions);
|
|
2489
|
+
});
|
|
2490
|
+
this.connection.onFoldingRanges((params) => {
|
|
2491
|
+
const document = this.service.documentService.get(params.textDocument.uri);
|
|
2492
|
+
if (!document)
|
|
2493
|
+
return [];
|
|
2494
|
+
return this.service.foldingRangeService.getFoldingRanges(document);
|
|
2495
|
+
});
|
|
2496
|
+
this.connection.onRequest('herb/toggleLineComment', (params) => {
|
|
2497
|
+
const document = this.service.documentService.get(params.textDocument.uri);
|
|
2498
|
+
if (!document)
|
|
2499
|
+
return [];
|
|
2500
|
+
return this.service.commentService.toggleLineComment(document, params.range);
|
|
2501
|
+
});
|
|
2502
|
+
this.connection.onRequest('herb/toggleBlockComment', (params) => {
|
|
2503
|
+
const document = this.service.documentService.get(params.textDocument.uri);
|
|
2504
|
+
if (!document)
|
|
2505
|
+
return [];
|
|
2506
|
+
return this.service.commentService.toggleBlockComment(document, params.range);
|
|
1555
2507
|
});
|
|
1556
2508
|
}
|
|
1557
2509
|
listen() {
|
|
@@ -1582,17 +2534,31 @@ class CLI {
|
|
|
1582
2534
|
}
|
|
1583
2535
|
|
|
1584
2536
|
exports.CLI = CLI;
|
|
2537
|
+
exports.CommentService = CommentService;
|
|
1585
2538
|
exports.Diagnostics = Diagnostics;
|
|
2539
|
+
exports.DocumentHighlightCollector = DocumentHighlightCollector;
|
|
2540
|
+
exports.DocumentHighlightService = DocumentHighlightService;
|
|
1586
2541
|
exports.DocumentService = DocumentService;
|
|
2542
|
+
exports.FoldingRangeCollector = FoldingRangeCollector;
|
|
2543
|
+
exports.FoldingRangeService = FoldingRangeService;
|
|
1587
2544
|
exports.FormattingService = FormattingService;
|
|
1588
2545
|
exports.Project = Project;
|
|
1589
2546
|
exports.Server = Server;
|
|
1590
2547
|
exports.Service = Service;
|
|
1591
2548
|
exports.Settings = Settings;
|
|
1592
|
-
exports.UnreachableCodeCollector = UnreachableCodeCollector;
|
|
1593
2549
|
exports.camelize = camelize;
|
|
1594
2550
|
exports.capitalize = capitalize;
|
|
1595
2551
|
exports.dasherize = dasherize;
|
|
2552
|
+
exports.erbTagToRange = erbTagToRange;
|
|
1596
2553
|
exports.getFullDocumentRange = getFullDocumentRange;
|
|
2554
|
+
exports.isPositionInRange = isPositionInRange;
|
|
1597
2555
|
exports.lintToDignosticSeverity = lintToDignosticSeverity;
|
|
2556
|
+
exports.lintToDignosticTags = lintToDignosticTags;
|
|
2557
|
+
exports.lspLine = lspLine;
|
|
2558
|
+
exports.lspPosition = lspPosition;
|
|
2559
|
+
exports.lspRangeFromLocation = lspRangeFromLocation;
|
|
2560
|
+
exports.nodeToRange = nodeToRange;
|
|
2561
|
+
exports.openTagRanges = openTagRanges;
|
|
2562
|
+
exports.rangeSize = rangeSize;
|
|
2563
|
+
exports.tokenToRange = tokenToRange;
|
|
1598
2564
|
//# sourceMappingURL=index.cjs.map
|