@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.
Files changed (82) hide show
  1. package/dist/action_view_helpers.js +19 -0
  2. package/dist/action_view_helpers.js.map +1 -0
  3. package/dist/autofix_service.js +1 -1
  4. package/dist/autofix_service.js.map +1 -1
  5. package/dist/code_action_service.js +3 -6
  6. package/dist/code_action_service.js.map +1 -1
  7. package/dist/comment_ast_utils.js +206 -0
  8. package/dist/comment_ast_utils.js.map +1 -0
  9. package/dist/comment_service.js +175 -0
  10. package/dist/comment_service.js.map +1 -0
  11. package/dist/diagnostics.js +0 -233
  12. package/dist/diagnostics.js.map +1 -1
  13. package/dist/document_highlight_service.js +196 -0
  14. package/dist/document_highlight_service.js.map +1 -0
  15. package/dist/document_save_service.js +16 -6
  16. package/dist/document_save_service.js.map +1 -1
  17. package/dist/folding_range_service.js +209 -0
  18. package/dist/folding_range_service.js.map +1 -0
  19. package/dist/formatting_service.js +4 -4
  20. package/dist/formatting_service.js.map +1 -1
  21. package/dist/herb-language-server.js +152936 -41156
  22. package/dist/herb-language-server.js.map +1 -1
  23. package/dist/hover_service.js +70 -0
  24. package/dist/hover_service.js.map +1 -0
  25. package/dist/index.cjs +1299 -333
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.js +4 -0
  28. package/dist/index.js.map +1 -1
  29. package/dist/line_context_collector.js +73 -0
  30. package/dist/line_context_collector.js.map +1 -0
  31. package/dist/linter_service.js +27 -6
  32. package/dist/linter_service.js.map +1 -1
  33. package/dist/parser_service.js +6 -5
  34. package/dist/parser_service.js.map +1 -1
  35. package/dist/range_utils.js +65 -0
  36. package/dist/range_utils.js.map +1 -0
  37. package/dist/rewrite_code_action_service.js +135 -0
  38. package/dist/rewrite_code_action_service.js.map +1 -0
  39. package/dist/server.js +39 -2
  40. package/dist/server.js.map +1 -1
  41. package/dist/service.js +10 -0
  42. package/dist/service.js.map +1 -1
  43. package/dist/types/action_view_helpers.d.ts +5 -0
  44. package/dist/types/comment_ast_utils.d.ts +20 -0
  45. package/dist/types/comment_service.d.ts +14 -0
  46. package/dist/types/diagnostics.d.ts +1 -35
  47. package/dist/types/document_highlight_service.d.ts +28 -0
  48. package/dist/types/document_save_service.d.ts +8 -0
  49. package/dist/types/folding_range_service.d.ts +35 -0
  50. package/dist/types/formatting_service.d.ts +1 -1
  51. package/dist/types/hover_service.d.ts +8 -0
  52. package/dist/types/index.d.ts +4 -0
  53. package/dist/types/line_context_collector.d.ts +19 -0
  54. package/dist/types/linter_service.d.ts +1 -0
  55. package/dist/types/parser_service.d.ts +2 -1
  56. package/dist/types/range_utils.d.ts +16 -0
  57. package/dist/types/rewrite_code_action_service.d.ts +11 -0
  58. package/dist/types/service.d.ts +10 -0
  59. package/dist/types/utils.d.ts +4 -8
  60. package/dist/utils.js +10 -15
  61. package/dist/utils.js.map +1 -1
  62. package/package.json +10 -5
  63. package/src/action_view_helpers.ts +23 -0
  64. package/src/autofix_service.ts +1 -1
  65. package/src/code_action_service.ts +3 -6
  66. package/src/comment_ast_utils.ts +282 -0
  67. package/src/comment_service.ts +228 -0
  68. package/src/diagnostics.ts +1 -305
  69. package/src/document_highlight_service.ts +267 -0
  70. package/src/document_save_service.ts +19 -7
  71. package/src/folding_range_service.ts +287 -0
  72. package/src/formatting_service.ts +4 -4
  73. package/src/hover_service.ts +90 -0
  74. package/src/index.ts +4 -0
  75. package/src/line_context_collector.ts +97 -0
  76. package/src/linter_service.ts +35 -9
  77. package/src/parser_service.ts +9 -10
  78. package/src/range_utils.ts +90 -0
  79. package/src/rewrite_code_action_service.ts +165 -0
  80. package/src/server.ts +54 -2
  81. package/src/service.ts +15 -0
  82. package/src/utils.ts +12 -21
@@ -0,0 +1,228 @@
1
+ import { TextEdit, Range, Position } from "vscode-languageserver/node"
2
+ import { TextDocument } from "vscode-languageserver-textdocument"
3
+
4
+ import { ParserService } from "./parser_service"
5
+ import { LineContextCollector } from "./line_context_collector"
6
+
7
+ import { lspLine } from "./range_utils"
8
+ import { determineStrategy, commentLineContent, uncommentLineContent } from "./comment_ast_utils"
9
+
10
+ import type { LineInfo } from "./line_context_collector"
11
+ import type { ERBContentNode, HTMLCommentNode } from "@herb-tools/core"
12
+
13
+ export class CommentService {
14
+ private parserService: ParserService
15
+
16
+ constructor(parserService: ParserService) {
17
+ this.parserService = parserService
18
+ }
19
+
20
+ toggleLineComment(document: TextDocument, range: Range): TextEdit[] {
21
+ const parseResult = this.parserService.parseDocument(document)
22
+ const collector = new LineContextCollector()
23
+
24
+ collector.visit(parseResult.document)
25
+
26
+ const startLine = range.start.line
27
+ const endLine = range.end.line
28
+ const lineInfos: LineInfo[] = []
29
+
30
+ for (let line = startLine; line <= endLine; line++) {
31
+ const lineText = document.getText(Range.create(line, 0, line + 1, 0)).replace(/\n$/, "")
32
+
33
+ if (lineText.trim() === "") {
34
+ continue
35
+ }
36
+
37
+ if (this.lineIsIfFalseWrapped(lineText) !== null) {
38
+ lineInfos.push({ line, context: "erb-comment", node: null })
39
+ continue
40
+ }
41
+
42
+ const htmlCommentNode = collector.htmlCommentNodesPerLine.get(line)
43
+ const info = collector.lineMap.get(line)
44
+
45
+ if (htmlCommentNode && this.htmlCommentSpansLine(htmlCommentNode, lineText)) {
46
+ lineInfos.push({ line, context: "html-comment", node: htmlCommentNode })
47
+ } else if (info) {
48
+ if (info.context === "html-comment") {
49
+ lineInfos.push({ line, context: "html-content", node: null })
50
+ } else {
51
+ lineInfos.push(info)
52
+ }
53
+ } else {
54
+ lineInfos.push({ line, context: "html-content", node: null })
55
+ }
56
+ }
57
+
58
+ if (lineInfos.length === 0) return []
59
+
60
+ const allCommented = lineInfos.every(
61
+ info => info.context === "erb-comment" || info.context === "html-comment"
62
+ )
63
+
64
+ const edits: TextEdit[] = []
65
+
66
+ if (allCommented) {
67
+ for (const info of lineInfos) {
68
+ const lineText = document.getText(Range.create(info.line, 0, info.line + 1, 0)).replace(/\n$/, "")
69
+ const edit = this.uncommentLine(info, lineText, collector)
70
+
71
+ if (edit) edits.push(edit)
72
+ }
73
+ } else {
74
+ for (const info of lineInfos) {
75
+ if (info.context === "erb-comment" || info.context === "html-comment") continue
76
+
77
+ const lineText = document.getText(Range.create(info.line, 0, info.line + 1, 0)).replace(/\n$/, "")
78
+ const erbNodes = collector.erbNodesPerLine.get(info.line) || []
79
+ const edit = this.commentLine(info, lineText, erbNodes, collector)
80
+
81
+ if (edit) edits.push(edit)
82
+ }
83
+ }
84
+
85
+ return edits
86
+ }
87
+
88
+ toggleBlockComment(document: TextDocument, range: Range): TextEdit[] {
89
+ const startLine = range.start.line
90
+ const endLine = range.end.line
91
+
92
+ const firstLineText = document.getText(Range.create(startLine, 0, startLine + 1, 0)).replace(/\n$/, "")
93
+ const lastLineText = document.getText(Range.create(endLine, 0, endLine + 1, 0)).replace(/\n$/, "")
94
+ const isWrapped = firstLineText.trim() === "<% if false %>" && lastLineText.trim() === "<% end %>"
95
+
96
+ if (isWrapped) {
97
+ return [
98
+ TextEdit.del(Range.create(endLine, 0, endLine + 1, 0)),
99
+ TextEdit.del(Range.create(startLine, 0, startLine + 1, 0)),
100
+ ]
101
+ } else {
102
+ const firstLineIndent = this.getIndentation(firstLineText)
103
+
104
+ return [
105
+ TextEdit.insert(Position.create(endLine + 1, 0), `${firstLineIndent}<% end %>\n`),
106
+ TextEdit.insert(Position.create(startLine, 0), `${firstLineIndent}<% if false %>\n`),
107
+ ]
108
+ }
109
+ }
110
+
111
+ private commentLine(info: LineInfo, lineText: string, erbNodes: ERBContentNode[], collector: LineContextCollector): TextEdit | null {
112
+ const lineRange = Range.create(info.line, 0, info.line, lineText.length)
113
+ const indent = this.getIndentation(lineText)
114
+ const content = lineText.trimStart()
115
+ const htmlCommentNode = collector.htmlCommentNodesPerLine.get(info.line)
116
+
117
+ if (htmlCommentNode) {
118
+ return TextEdit.replace(lineRange, `${indent}<% if false %>${content}<% end %>`)
119
+ }
120
+
121
+ const strategy = determineStrategy(erbNodes, lineText)
122
+
123
+ if (strategy === "single-erb") {
124
+ const node = erbNodes[0]
125
+ const insertColumn = node.tag_opening!.location.start.column + 2
126
+
127
+ return TextEdit.insert(Position.create(info.line, insertColumn), "#")
128
+ }
129
+
130
+ const result = commentLineContent(content, erbNodes, strategy, this.parserService)
131
+
132
+ return TextEdit.replace(lineRange, indent + result)
133
+ }
134
+
135
+ private lineIsIfFalseWrapped(lineText: string): string | null {
136
+ const trimmed = lineText.trimStart()
137
+ const indent = this.getIndentation(lineText)
138
+
139
+ if (trimmed.startsWith("<% if false %>") && trimmed.endsWith("<% end %>")) {
140
+ const inner = trimmed.slice("<% if false %>".length, -"<% end %>".length)
141
+
142
+ return indent + inner
143
+ }
144
+
145
+ return null
146
+ }
147
+
148
+ private uncommentLine(info: LineInfo, lineText: string, collector: LineContextCollector): TextEdit | null {
149
+ const lineRange = Range.create(info.line, 0, info.line, lineText.length)
150
+ const indent = this.getIndentation(lineText)
151
+ const ifFalseContent = this.lineIsIfFalseWrapped(lineText)
152
+
153
+ if (ifFalseContent !== null) {
154
+ return TextEdit.replace(lineRange, ifFalseContent)
155
+ }
156
+
157
+ if (info.context === "erb-comment") {
158
+ const node = info.node as ERBContentNode
159
+ if (!node?.tag_opening || !node?.tag_closing) return null
160
+
161
+ const contentValue = (node as any).content?.value as string | null
162
+ const trimmedContent = contentValue?.trim() || ""
163
+
164
+ if (trimmedContent.startsWith("<") && !trimmedContent.startsWith("<%")) {
165
+ return TextEdit.replace(lineRange, `${indent}${trimmedContent}`)
166
+ }
167
+
168
+ if (lspLine(node.tag_opening.location.start) !== info.line) return null
169
+
170
+ const erbNodes = collector.erbNodesPerLine.get(info.line) || []
171
+
172
+ if (erbNodes.length > 1) {
173
+ const content = lineText.trimStart()
174
+ const result = uncommentLineContent(content, this.parserService)
175
+
176
+ return TextEdit.replace(lineRange, indent + result)
177
+ }
178
+
179
+ const hashColumn = node.tag_opening.location.start.column + 2
180
+
181
+ if (
182
+ contentValue?.startsWith(" graphql ") ||
183
+ contentValue?.startsWith(" %= ") ||
184
+ contentValue?.startsWith(" == ") ||
185
+ contentValue?.startsWith(" % ") ||
186
+ contentValue?.startsWith(" = ") ||
187
+ contentValue?.startsWith(" - ")
188
+ ) {
189
+ return TextEdit.del(Range.create(info.line, hashColumn, info.line, hashColumn + 2))
190
+ }
191
+
192
+ return TextEdit.del(Range.create(info.line, hashColumn, info.line, hashColumn + 1))
193
+ }
194
+
195
+ if (info.context === "html-comment") {
196
+ const commentNode = info.node as HTMLCommentNode | null
197
+
198
+ if (commentNode?.comment_start && commentNode?.comment_end) {
199
+ const contentStart = commentNode.comment_start.location.end.column
200
+ const contentEnd = commentNode.comment_end.location.start.column
201
+ const innerContent = lineText.substring(contentStart, contentEnd).trim()
202
+
203
+ const result = uncommentLineContent(innerContent, this.parserService)
204
+
205
+ return TextEdit.replace(lineRange, `${indent}${result}`)
206
+ }
207
+ }
208
+
209
+ return null
210
+ }
211
+
212
+ private htmlCommentSpansLine(node: HTMLCommentNode, lineText: string): boolean {
213
+ if (!node.comment_start || !node.comment_end) return false
214
+
215
+ const commentStart = node.comment_start.location.start.column
216
+ const commentEnd = node.comment_end.location.end.column
217
+ const contentBefore = lineText.substring(0, commentStart).trim()
218
+ const contentAfter = lineText.substring(commentEnd).trim()
219
+
220
+ return contentBefore === "" && contentAfter === ""
221
+ }
222
+
223
+ private getIndentation(lineText: string): string {
224
+ const match = lineText.match(/^(\s*)/)
225
+
226
+ return match ? match[1] : ""
227
+ }
228
+ }
@@ -1,26 +1,5 @@
1
1
  import { TextDocument } from "vscode-languageserver-textdocument"
2
- import { Connection, Diagnostic, DiagnosticSeverity, DiagnosticTag } from "vscode-languageserver/node"
3
- import { Visitor } from "@herb-tools/core"
4
-
5
- import type {
6
- Node,
7
- ERBCaseNode,
8
- ERBCaseMatchNode,
9
- ERBIfNode,
10
- ERBElseNode,
11
- ERBUnlessNode,
12
- ERBForNode,
13
- ERBWhileNode,
14
- ERBUntilNode,
15
- ERBWhenNode,
16
- ERBBeginNode,
17
- ERBRescueNode,
18
- ERBEnsureNode,
19
- ERBBlockNode,
20
- ERBInNode,
21
- } from "@herb-tools/core"
22
-
23
- import { isHTMLTextNode } from "@herb-tools/core"
2
+ import { Connection, Diagnostic } from "vscode-languageserver/node"
24
3
 
25
4
  import { ParserService } from "./parser_service"
26
5
  import { LinterService } from "./linter_service"
@@ -57,12 +36,10 @@ export class Diagnostics {
57
36
  } else {
58
37
  const parseResult = this.parserService.parseDocument(textDocument)
59
38
  const lintResult = await this.linterService.lintDocument(textDocument)
60
- const unreachableCodeDiagnostics = this.getUnreachableCodeDiagnostics(parseResult.document)
61
39
 
62
40
  allDiagnostics = [
63
41
  ...parseResult.diagnostics,
64
42
  ...lintResult.diagnostics,
65
- ...unreachableCodeDiagnostics,
66
43
  ]
67
44
  }
68
45
 
@@ -70,12 +47,6 @@ export class Diagnostics {
70
47
  this.sendDiagnosticsFor(textDocument)
71
48
  }
72
49
 
73
- private getUnreachableCodeDiagnostics(document: Node): Diagnostic[] {
74
- const collector = new UnreachableCodeCollector()
75
- collector.visit(document)
76
- return collector.diagnostics
77
- }
78
-
79
50
  async refreshDocument(document: TextDocument) {
80
51
  await this.validate(document)
81
52
  }
@@ -96,278 +67,3 @@ export class Diagnostics {
96
67
  this.diagnostics.delete(textDocument)
97
68
  }
98
69
  }
99
-
100
- export class UnreachableCodeCollector extends Visitor {
101
- diagnostics: Diagnostic[] = []
102
- private processedIfNodes: Set<ERBIfNode> = new Set()
103
- private processedElseNodes: Set<ERBElseNode> = new Set()
104
-
105
- visitERBCaseNode(node: ERBCaseNode): void {
106
- this.checkUnreachableChildren(node.children)
107
- this.checkAndMarkElseClause(node.else_clause)
108
- this.visitChildNodes(node)
109
- }
110
-
111
- visitERBCaseMatchNode(node: ERBCaseMatchNode): void {
112
- this.checkUnreachableChildren(node.children)
113
- this.checkAndMarkElseClause(node.else_clause)
114
- this.visitChildNodes(node)
115
- }
116
-
117
- visitERBIfNode(node: ERBIfNode): void {
118
- if (this.processedIfNodes.has(node)) {
119
- return
120
- }
121
-
122
- this.markIfChainAsProcessed(node)
123
-
124
- this.markElseNodesInIfChain(node)
125
-
126
- const entireChainEmpty = this.isEntireIfChainEmpty(node)
127
-
128
- if (entireChainEmpty) {
129
- this.checkEmptyStatements(node, node.statements, "if")
130
- } else {
131
- this.checkIfChainParts(node)
132
- }
133
-
134
- this.visitChildNodes(node)
135
- }
136
-
137
- visitERBElseNode(node: ERBElseNode): void {
138
- if (this.processedElseNodes.has(node)) {
139
- this.visitChildNodes(node)
140
- return
141
- }
142
-
143
- this.checkEmptyStatements(node, node.statements, "else")
144
- this.visitChildNodes(node)
145
- }
146
-
147
- visitERBUnlessNode(node: ERBUnlessNode): void {
148
- const unlessHasContent = this.statementsHaveContent(node.statements)
149
- const elseHasContent = node.else_clause && this.statementsHaveContent(node.else_clause.statements)
150
-
151
- if (node.else_clause) {
152
- this.processedElseNodes.add(node.else_clause)
153
- }
154
-
155
- const entireBlockEmpty = !unlessHasContent && !elseHasContent
156
-
157
- if (entireBlockEmpty) {
158
- this.checkEmptyStatements(node, node.statements, "unless")
159
- } else {
160
- if (!unlessHasContent) {
161
- this.checkEmptyStatementsWithEndLocation(node, node.statements, "unless", node.else_clause)
162
- }
163
-
164
- if (node.else_clause && !elseHasContent) {
165
- this.checkEmptyStatements(node.else_clause, node.else_clause.statements, "else")
166
- }
167
- }
168
-
169
- this.visitChildNodes(node)
170
- }
171
-
172
- visitERBForNode(node: ERBForNode): void {
173
- this.checkEmptyStatements(node, node.statements, "for")
174
- this.visitChildNodes(node)
175
- }
176
-
177
- visitERBWhileNode(node: ERBWhileNode): void {
178
- this.checkEmptyStatements(node, node.statements, "while")
179
- this.visitChildNodes(node)
180
- }
181
-
182
- visitERBUntilNode(node: ERBUntilNode): void {
183
- this.checkEmptyStatements(node, node.statements, "until")
184
- this.visitChildNodes(node)
185
- }
186
-
187
- visitERBWhenNode(node: ERBWhenNode): void {
188
- if (!node.then_keyword) {
189
- this.checkEmptyStatements(node, node.statements, "when")
190
- }
191
-
192
- this.visitChildNodes(node)
193
- }
194
-
195
- visitERBBeginNode(node: ERBBeginNode): void {
196
- this.checkEmptyStatements(node, node.statements, "begin")
197
- this.visitChildNodes(node)
198
- }
199
-
200
- visitERBRescueNode(node: ERBRescueNode): void {
201
- this.checkEmptyStatements(node, node.statements, "rescue")
202
- this.visitChildNodes(node)
203
- }
204
-
205
- visitERBEnsureNode(node: ERBEnsureNode): void {
206
- this.checkEmptyStatements(node, node.statements, "ensure")
207
- this.visitChildNodes(node)
208
- }
209
-
210
- visitERBBlockNode(node: ERBBlockNode): void {
211
- this.checkEmptyStatements(node, node.body, "block")
212
- this.visitChildNodes(node)
213
- }
214
-
215
- visitERBInNode(node: ERBInNode): void {
216
- if (!node.then_keyword) {
217
- this.checkEmptyStatements(node, node.statements, "in")
218
- }
219
-
220
- this.visitChildNodes(node)
221
- }
222
-
223
- private checkUnreachableChildren(children: Node[]): void {
224
- for (const child of children) {
225
- if (isHTMLTextNode(child) && child.content.trim() === "") {
226
- continue
227
- }
228
-
229
- this.addDiagnostic(
230
- child.location,
231
- "Unreachable code: content between case and when/in is never executed"
232
- )
233
- }
234
- }
235
-
236
- private checkEmptyStatements(node: Node, statements: Node[], blockType: string): void {
237
- this.checkEmptyStatementsWithEndLocation(node, statements, blockType, null)
238
- }
239
-
240
- private checkEmptyStatementsWithEndLocation(node: Node, statements: Node[], blockType: string, subsequentNode: Node | null): void {
241
- if (this.statementsHaveContent(statements)) {
242
- return
243
- }
244
-
245
- const startLocation = node.location.start
246
- const endLocation = subsequentNode
247
- ? subsequentNode.location.start
248
- : node.location.end
249
-
250
- this.addDiagnostic(
251
- { start: startLocation, end: endLocation },
252
- `Empty ${blockType} block: this control flow statement has no content`
253
- )
254
- }
255
-
256
- private addDiagnostic(location: { start: { line: number; column: number }, end: { line: number; column: number } }, message: string): void {
257
- const diagnostic: Diagnostic = {
258
- range: {
259
- start: {
260
- line: this.toZeroBased(location.start.line),
261
- character: location.start.column
262
- },
263
- end: {
264
- line: this.toZeroBased(location.end.line),
265
- character: location.end.column
266
- }
267
- },
268
- message,
269
- severity: DiagnosticSeverity.Hint,
270
- tags: [DiagnosticTag.Unnecessary],
271
- source: "Herb Language Server"
272
- }
273
-
274
- this.diagnostics.push(diagnostic)
275
- }
276
-
277
- private statementsHaveContent(statements: Node[]): boolean {
278
- return statements.some(statement => {
279
- if (isHTMLTextNode(statement)) {
280
- return statement.content.trim() !== ""
281
- }
282
-
283
- return true
284
- })
285
- }
286
-
287
- private checkAndMarkElseClause(elseClause: ERBElseNode | null): void {
288
- if (!elseClause) {
289
- return
290
- }
291
-
292
- this.processedElseNodes.add(elseClause)
293
- if (!this.statementsHaveContent(elseClause.statements)) {
294
- this.checkEmptyStatements(elseClause, elseClause.statements, "else")
295
- }
296
- }
297
-
298
- private markIfChainAsProcessed(node: ERBIfNode): void {
299
- this.processedIfNodes.add(node)
300
- this.traverseSubsequentNodes(node.subsequent, (current) => {
301
- if (current.type === 'AST_ERB_IF_NODE') {
302
- this.processedIfNodes.add(current as ERBIfNode)
303
- }
304
- })
305
- }
306
-
307
- private markElseNodesInIfChain(node: ERBIfNode): void {
308
- this.traverseSubsequentNodes(node.subsequent, (current) => {
309
- if (current.type === 'AST_ERB_ELSE_NODE') {
310
- this.processedElseNodes.add(current as ERBElseNode)
311
- }
312
- })
313
- }
314
-
315
- private traverseSubsequentNodes(startNode: Node | null, callback: (node: Node) => void): void {
316
- let current = startNode
317
- while (current) {
318
- callback(current)
319
-
320
- if ('subsequent' in current) {
321
- current = (current as any).subsequent
322
- } else {
323
- break
324
- }
325
- }
326
- }
327
-
328
- private checkIfChainParts(node: ERBIfNode): void {
329
- if (!this.statementsHaveContent(node.statements)) {
330
- this.checkEmptyStatementsWithEndLocation(node, node.statements, "if", node.subsequent)
331
- }
332
-
333
- this.traverseSubsequentNodes(node.subsequent, (current) => {
334
- if (!('statements' in current) || !Array.isArray((current as any).statements)) {
335
- return
336
- }
337
-
338
- if (this.statementsHaveContent((current as any).statements)) {
339
- return
340
- }
341
-
342
- const blockType = current.type === 'AST_ERB_IF_NODE' ? 'elsif' : 'else'
343
- const nextSubsequent = 'subsequent' in current ? (current as any).subsequent : null
344
-
345
- if (nextSubsequent) {
346
- this.checkEmptyStatementsWithEndLocation(current, (current as any).statements, blockType, nextSubsequent)
347
- } else {
348
- this.checkEmptyStatements(current, (current as any).statements, blockType)
349
- }
350
- })
351
- }
352
-
353
- private isEntireIfChainEmpty(node: ERBIfNode): boolean {
354
- if (this.statementsHaveContent(node.statements)) {
355
- return false
356
- }
357
-
358
- let hasContent = false
359
- this.traverseSubsequentNodes(node.subsequent, (current) => {
360
- if ('statements' in current && Array.isArray((current as any).statements)) {
361
- if (this.statementsHaveContent((current as any).statements)) {
362
- hasContent = true
363
- }
364
- }
365
- })
366
-
367
- return !hasContent
368
- }
369
-
370
- private toZeroBased(line: number): number {
371
- return line - 1
372
- }
373
- }