@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
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Diagnostic } from "vscode-languageserver/node";
|
|
2
2
|
import { TextDocument } from "vscode-languageserver-textdocument";
|
|
3
|
-
import type { DocumentNode } from "@herb-tools/node-wasm";
|
|
3
|
+
import type { DocumentNode, ParseResult, ParseOptions } from "@herb-tools/node-wasm";
|
|
4
4
|
export interface ParseServiceResult {
|
|
5
5
|
document: DocumentNode;
|
|
6
6
|
diagnostics: Diagnostic[];
|
|
7
7
|
}
|
|
8
8
|
export declare class ParserService {
|
|
9
9
|
parseDocument(textDocument: TextDocument): ParseServiceResult;
|
|
10
|
+
parseContent(content: string, options?: ParseOptions): ParseResult;
|
|
10
11
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Position, Range } from "vscode-languageserver/node";
|
|
2
|
+
import type { TextDocument } from "vscode-languageserver-textdocument";
|
|
3
|
+
import type { SerializedPosition, SerializedLocation, Node, Token, ERBNode, HTMLOpenTagNode } from "@herb-tools/core";
|
|
4
|
+
export declare function lspPosition(herbPosition: SerializedPosition): Position;
|
|
5
|
+
export declare function lspLine(herbPosition: SerializedPosition): number;
|
|
6
|
+
export declare function lspRangeFromLocation(herbLocation: SerializedLocation): Range;
|
|
7
|
+
export declare function erbTagToRange(node: ERBNode): Range | null;
|
|
8
|
+
export declare function tokenToRange(token: Token | null): Range | null;
|
|
9
|
+
export declare function nodeToRange(node: Node): Range;
|
|
10
|
+
export declare function openTagRanges(tag: HTMLOpenTagNode): (Range | null)[];
|
|
11
|
+
export declare function isPositionInRange(position: Position, range: Range): boolean;
|
|
12
|
+
export declare function rangeSize(range: Range): number;
|
|
13
|
+
/**
|
|
14
|
+
* Returns a Range that spans the entire document
|
|
15
|
+
*/
|
|
16
|
+
export declare function getFullDocumentRange(document: TextDocument): Range;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { CodeAction, Range } from "vscode-languageserver/node";
|
|
2
|
+
import { TextDocument } from "vscode-languageserver-textdocument";
|
|
3
|
+
import { ParserService } from "./parser_service";
|
|
4
|
+
export declare class RewriteCodeActionService {
|
|
5
|
+
private parserService;
|
|
6
|
+
constructor(parserService: ParserService);
|
|
7
|
+
getCodeActions(document: TextDocument, requestedRange: Range): CodeAction[];
|
|
8
|
+
private createActionViewToHTMLAction;
|
|
9
|
+
private createHTMLToActionViewAction;
|
|
10
|
+
private rangesOverlap;
|
|
11
|
+
}
|
package/dist/types/service.d.ts
CHANGED
|
@@ -11,6 +11,11 @@ import { ConfigService } from "./config_service";
|
|
|
11
11
|
import { AutofixService } from "./autofix_service";
|
|
12
12
|
import { CodeActionService } from "./code_action_service";
|
|
13
13
|
import { DocumentSaveService } from "./document_save_service";
|
|
14
|
+
import { FoldingRangeService } from "./folding_range_service";
|
|
15
|
+
import { DocumentHighlightService } from "./document_highlight_service";
|
|
16
|
+
import { HoverService } from "./hover_service";
|
|
17
|
+
import { RewriteCodeActionService } from "./rewrite_code_action_service";
|
|
18
|
+
import { CommentService } from "./comment_service";
|
|
14
19
|
export declare class Service {
|
|
15
20
|
connection: Connection;
|
|
16
21
|
settings: Settings;
|
|
@@ -25,6 +30,11 @@ export declare class Service {
|
|
|
25
30
|
configService: ConfigService;
|
|
26
31
|
codeActionService: CodeActionService;
|
|
27
32
|
documentSaveService: DocumentSaveService;
|
|
33
|
+
foldingRangeService: FoldingRangeService;
|
|
34
|
+
documentHighlightService: DocumentHighlightService;
|
|
35
|
+
hoverService: HoverService;
|
|
36
|
+
rewriteCodeActionService: RewriteCodeActionService;
|
|
37
|
+
commentService: CommentService;
|
|
28
38
|
constructor(connection: Connection, params: InitializeParams);
|
|
29
39
|
init(): Promise<void>;
|
|
30
40
|
refresh(): Promise<void>;
|
package/dist/types/utils.d.ts
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
import { DiagnosticSeverity } from "vscode-languageserver/node";
|
|
1
|
+
import { DiagnosticSeverity, DiagnosticTag } from "vscode-languageserver/node";
|
|
2
2
|
import type { LintSeverity } from "@herb-tools/linter";
|
|
3
|
-
import type {
|
|
4
|
-
import type { TextDocument } from "vscode-languageserver-textdocument";
|
|
3
|
+
import type { DiagnosticSeverity as HerbDiagnosticSeverity, DiagnosticTag as HerbDiagnosticTag } from "@herb-tools/core";
|
|
5
4
|
export declare function camelize(value: string): string;
|
|
6
5
|
export declare function dasherize(value: string): string;
|
|
7
6
|
export declare function capitalize(value: string): string;
|
|
8
|
-
export declare function lintToDignosticSeverity(severity: LintSeverity): DiagnosticSeverity;
|
|
9
|
-
|
|
10
|
-
* Returns a Range that spans the entire document
|
|
11
|
-
*/
|
|
12
|
-
export declare function getFullDocumentRange(document: TextDocument): Range;
|
|
7
|
+
export declare function lintToDignosticSeverity(severity: LintSeverity | HerbDiagnosticSeverity): DiagnosticSeverity;
|
|
8
|
+
export declare function lintToDignosticTags(tags?: HerbDiagnosticTag[]): DiagnosticTag[];
|
package/dist/utils.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { DiagnosticSeverity } from "vscode-languageserver/node";
|
|
2
|
-
import { Position } from "vscode-languageserver/node";
|
|
1
|
+
import { DiagnosticSeverity, DiagnosticTag } from "vscode-languageserver/node";
|
|
3
2
|
export function camelize(value) {
|
|
4
3
|
return value.replace(/(?:[_-])([a-z0-9])/g, (_, char) => char.toUpperCase());
|
|
5
4
|
}
|
|
@@ -17,19 +16,15 @@ export function lintToDignosticSeverity(severity) {
|
|
|
17
16
|
case "hint": return DiagnosticSeverity.Hint;
|
|
18
17
|
}
|
|
19
18
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
19
|
+
export function lintToDignosticTags(tags) {
|
|
20
|
+
if (!tags)
|
|
21
|
+
return [];
|
|
22
|
+
return tags.flatMap(tag => {
|
|
23
|
+
switch (tag) {
|
|
24
|
+
case "unnecessary": return [DiagnosticTag.Unnecessary];
|
|
25
|
+
case "deprecated": return [DiagnosticTag.Deprecated];
|
|
26
|
+
default: return [];
|
|
27
|
+
}
|
|
28
28
|
});
|
|
29
|
-
const lastLineLength = lastLineText.length;
|
|
30
|
-
return {
|
|
31
|
-
start: Position.create(0, 0),
|
|
32
|
-
end: Position.create(lastLine, lastLineLength)
|
|
33
|
-
};
|
|
34
29
|
}
|
|
35
30
|
//# sourceMappingURL=utils.js.map
|
package/dist/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAI9E,MAAM,UAAU,QAAQ,CAAC,KAAa;IACpC,OAAO,KAAK,CAAC,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAA;AAC9E,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAa;IACrC,OAAO,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC,CAAA;AACzE,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AACvD,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,QAA+C;IACrF,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,OAAO,CAAC,CAAC,OAAO,kBAAkB,CAAC,KAAK,CAAA;QAC7C,KAAK,SAAS,CAAC,CAAC,OAAO,kBAAkB,CAAC,OAAO,CAAA;QACjD,KAAK,MAAM,CAAC,CAAC,OAAO,kBAAkB,CAAC,WAAW,CAAA;QAClD,KAAK,MAAM,CAAC,CAAC,OAAO,kBAAkB,CAAC,IAAI,CAAA;IAC7C,CAAC;AACH,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,IAA0B;IAC5D,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAA;IAEpB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE;QACxB,QAAQ,GAAG,EAAE,CAAC;YACZ,KAAK,aAAa,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,WAAW,CAAC,CAAA;YACtD,KAAK,YAAY,CAAC,CAAC,OAAO,CAAC,aAAa,CAAC,UAAU,CAAC,CAAA;YACpD,OAAO,CAAC,CAAC,OAAO,EAAE,CAAA;QACpB,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@herb-tools/language-server",
|
|
3
3
|
"description": "Herb HTML+ERB Language Tools and Language Server Protocol integration.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.9.1",
|
|
5
5
|
"author": "Marco Roth",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"engines": {
|
|
@@ -45,12 +45,17 @@
|
|
|
45
45
|
"dist/"
|
|
46
46
|
],
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@herb-tools/config": "0.
|
|
49
|
-
"@herb-tools/formatter": "0.
|
|
50
|
-
"@herb-tools/linter": "0.
|
|
51
|
-
"@herb-tools/node-wasm": "0.
|
|
48
|
+
"@herb-tools/config": "0.9.1",
|
|
49
|
+
"@herb-tools/formatter": "0.9.1",
|
|
50
|
+
"@herb-tools/linter": "0.9.1",
|
|
51
|
+
"@herb-tools/node-wasm": "0.9.1",
|
|
52
|
+
"@herb-tools/printer": "0.9.1",
|
|
53
|
+
"@herb-tools/rewriter": "0.9.1",
|
|
52
54
|
"dedent": "^1.7.0",
|
|
53
55
|
"vscode-languageserver": "^9.0.1",
|
|
54
56
|
"vscode-languageserver-textdocument": "^1.0.12"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@types/node": "^25.3.3"
|
|
55
60
|
}
|
|
56
61
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface ActionViewHelperInfo {
|
|
2
|
+
signature: string
|
|
3
|
+
documentationURL: string
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export const ACTION_VIEW_HELPERS: Record<string, ActionViewHelperInfo> = {
|
|
7
|
+
"ActionView::Helpers::TagHelper#tag": {
|
|
8
|
+
signature: "tag.<tag name>(optional content, options)",
|
|
9
|
+
documentationURL: "https://api.rubyonrails.org/classes/ActionView/Helpers/TagHelper.html#method-i-tag",
|
|
10
|
+
},
|
|
11
|
+
"ActionView::Helpers::TagHelper#content_tag": {
|
|
12
|
+
signature: "content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block)",
|
|
13
|
+
documentationURL: "https://api.rubyonrails.org/classes/ActionView/Helpers/TagHelper.html#method-i-content_tag",
|
|
14
|
+
},
|
|
15
|
+
"ActionView::Helpers::UrlHelper#link_to": {
|
|
16
|
+
signature: "link_to(name = nil, options = nil, html_options = nil, &block)",
|
|
17
|
+
documentationURL: "https://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to",
|
|
18
|
+
},
|
|
19
|
+
"Turbo::FramesHelper#turbo_frame_tag": {
|
|
20
|
+
signature: "turbo_frame_tag(*ids, src: nil, target: nil, **attributes, &block)",
|
|
21
|
+
documentationURL: "https://www.rubydoc.info/github/hotwired/turbo-rails/Turbo/FramesHelper:turbo_frame_tag",
|
|
22
|
+
},
|
|
23
|
+
}
|
package/src/autofix_service.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { Herb } from "@herb-tools/node-wasm"
|
|
|
5
5
|
import { Linter } from "@herb-tools/linter"
|
|
6
6
|
import { Config } from "@herb-tools/config"
|
|
7
7
|
|
|
8
|
-
import { getFullDocumentRange } from "./
|
|
8
|
+
import { getFullDocumentRange } from "./range_utils"
|
|
9
9
|
|
|
10
10
|
export class AutofixService {
|
|
11
11
|
private connection: Connection
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CodeAction, CodeActionKind, CodeActionParams, Diagnostic, Range,
|
|
1
|
+
import { CodeAction, CodeActionKind, CodeActionParams, Diagnostic, Range, TextEdit, WorkspaceEdit, CreateFile, TextDocumentEdit, OptionalVersionedTextDocumentIdentifier } from "vscode-languageserver/node"
|
|
2
2
|
import { TextDocument } from "vscode-languageserver-textdocument"
|
|
3
3
|
|
|
4
4
|
import { Config } from "@herb-tools/config"
|
|
@@ -6,7 +6,7 @@ import { Project } from "./project"
|
|
|
6
6
|
import { Herb } from "@herb-tools/node-wasm"
|
|
7
7
|
import { Linter } from "@herb-tools/linter"
|
|
8
8
|
|
|
9
|
-
import { getFullDocumentRange } from "./
|
|
9
|
+
import { getFullDocumentRange, lspRangeFromLocation } from "./range_utils"
|
|
10
10
|
|
|
11
11
|
import type { LintOffense } from "@herb-tools/linter"
|
|
12
12
|
|
|
@@ -362,10 +362,7 @@ export class CodeActionService {
|
|
|
362
362
|
}
|
|
363
363
|
|
|
364
364
|
private offenseToRange(offense: LintOffense): Range {
|
|
365
|
-
return
|
|
366
|
-
start: Position.create(offense.location.start.line - 1, offense.location.start.column),
|
|
367
|
-
end: Position.create(offense.location.end.line - 1, offense.location.end.column)
|
|
368
|
-
}
|
|
365
|
+
return lspRangeFromLocation(offense.location)
|
|
369
366
|
}
|
|
370
367
|
|
|
371
368
|
private rangesEqual(r1: Range, r2: Range): boolean {
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { ParserService } from "./parser_service"
|
|
2
|
+
import { LineContextCollector } from "./line_context_collector"
|
|
3
|
+
|
|
4
|
+
import { HTMLCommentNode, Location, createSyntheticToken } from "@herb-tools/core"
|
|
5
|
+
import { IdentityPrinter } from "@herb-tools/printer"
|
|
6
|
+
|
|
7
|
+
import { isERBCommentNode, isLiteralNode, createLiteral } from "@herb-tools/core"
|
|
8
|
+
import { asMutable } from "@herb-tools/rewriter"
|
|
9
|
+
|
|
10
|
+
import type { Node, ERBContentNode } from "@herb-tools/core"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Commenting strategy for a line:
|
|
14
|
+
* - "single-erb": sole ERB tag on the line → insert # at column offset
|
|
15
|
+
* - "all-erb": multiple ERB tags with no significant HTML → # into each
|
|
16
|
+
* - "per-segment": control-flow ERB wrapping HTML → # each ERB, <!-- --> each HTML segment
|
|
17
|
+
* - "whole-line": output ERB mixed with HTML → wrap entire line in <!-- --> with ERB #
|
|
18
|
+
* - "html-only": pure HTML content → wrap in <!-- -->
|
|
19
|
+
*/
|
|
20
|
+
export type CommentStrategy = "single-erb" | "all-erb" | "per-segment" | "whole-line" | "html-only"
|
|
21
|
+
|
|
22
|
+
interface LineSegment {
|
|
23
|
+
text: string
|
|
24
|
+
isERB: boolean
|
|
25
|
+
node?: ERBContentNode
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function commentERBNode(node: ERBContentNode): void {
|
|
29
|
+
const mutable = asMutable(node)
|
|
30
|
+
|
|
31
|
+
if (mutable.tag_opening) {
|
|
32
|
+
const currentValue = mutable.tag_opening.value
|
|
33
|
+
|
|
34
|
+
mutable.tag_opening = createSyntheticToken(
|
|
35
|
+
currentValue.substring(0, 2) + "#" + currentValue.substring(2),
|
|
36
|
+
mutable.tag_opening.type
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function uncommentERBNode(node: ERBContentNode): void {
|
|
42
|
+
const mutable = asMutable(node)
|
|
43
|
+
|
|
44
|
+
if (mutable.tag_opening && mutable.tag_opening.value === "<%#") {
|
|
45
|
+
const contentValue = mutable.content?.value || ""
|
|
46
|
+
|
|
47
|
+
if (
|
|
48
|
+
contentValue.startsWith(" graphql ") ||
|
|
49
|
+
contentValue.startsWith(" %= ") ||
|
|
50
|
+
contentValue.startsWith(" == ") ||
|
|
51
|
+
contentValue.startsWith(" % ") ||
|
|
52
|
+
contentValue.startsWith(" = ") ||
|
|
53
|
+
contentValue.startsWith(" - ")
|
|
54
|
+
) {
|
|
55
|
+
mutable.tag_opening = createSyntheticToken("<%", mutable.tag_opening.type)
|
|
56
|
+
mutable.content = createSyntheticToken(contentValue.substring(1), mutable.content!.type)
|
|
57
|
+
} else {
|
|
58
|
+
mutable.tag_opening = createSyntheticToken("<%", mutable.tag_opening.type)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function determineStrategy(erbNodes: ERBContentNode[], lineText: string): CommentStrategy {
|
|
64
|
+
if (erbNodes.length === 0) {
|
|
65
|
+
return "html-only"
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (erbNodes.length === 1) {
|
|
69
|
+
const node = erbNodes[0]
|
|
70
|
+
if (!node.tag_opening || !node.tag_closing) return "html-only"
|
|
71
|
+
|
|
72
|
+
const nodeStart = node.tag_opening.location.start.column
|
|
73
|
+
const nodeEnd = node.tag_closing.location.end.column
|
|
74
|
+
const isSoleContent = lineText.substring(0, nodeStart).trim() === "" && lineText.substring(nodeEnd).trim() === ""
|
|
75
|
+
|
|
76
|
+
if (isSoleContent) {
|
|
77
|
+
return "single-erb"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return "whole-line"
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const segments = getLineSegments(lineText, erbNodes)
|
|
84
|
+
const hasHTML = segments.some(segment => !segment.isERB && segment.text.trim() !== "")
|
|
85
|
+
|
|
86
|
+
if (!hasHTML) {
|
|
87
|
+
return "all-erb"
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const allControlTags = erbNodes.every(node => node.tag_opening?.value === "<%")
|
|
91
|
+
|
|
92
|
+
if (allControlTags) {
|
|
93
|
+
return "per-segment"
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return "whole-line"
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function getLineSegments(lineText: string, erbNodes: ERBContentNode[]): LineSegment[] {
|
|
100
|
+
const segments: LineSegment[] = []
|
|
101
|
+
const sorted = [...erbNodes].sort((a, b) => a.tag_opening!.location.start.column - b.tag_opening!.location.start.column)
|
|
102
|
+
|
|
103
|
+
let position = 0
|
|
104
|
+
|
|
105
|
+
for (const node of sorted) {
|
|
106
|
+
const nodeStart = node.tag_opening!.location.start.column
|
|
107
|
+
const nodeEnd = node.tag_closing!.location.end.column
|
|
108
|
+
|
|
109
|
+
if (nodeStart > position) {
|
|
110
|
+
segments.push({ text: lineText.substring(position, nodeStart), isERB: false })
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
segments.push({ text: lineText.substring(nodeStart, nodeEnd), isERB: true, node })
|
|
114
|
+
position = nodeEnd
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (position < lineText.length) {
|
|
118
|
+
segments.push({ text: lineText.substring(position), isERB: false })
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return segments
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Comment a line using AST mutation for strategies where the parser produces flat children,
|
|
126
|
+
* and text-segment manipulation for per-segment (where the parser nests nodes).
|
|
127
|
+
*/
|
|
128
|
+
export function commentLineContent(content: string, erbNodes: ERBContentNode[], strategy: CommentStrategy, parserService: ParserService): string {
|
|
129
|
+
if (strategy === "per-segment") {
|
|
130
|
+
return commentPerSegment(content, erbNodes)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const parseResult = parserService.parseContent(content, { track_whitespace: true })
|
|
134
|
+
const lineCollector = new LineContextCollector()
|
|
135
|
+
parseResult.visit(lineCollector)
|
|
136
|
+
|
|
137
|
+
const lineERBNodes = lineCollector.erbNodesPerLine.get(0) || []
|
|
138
|
+
const document = parseResult.value
|
|
139
|
+
const children = asMutable(document).children
|
|
140
|
+
|
|
141
|
+
switch (strategy) {
|
|
142
|
+
case "all-erb":
|
|
143
|
+
for (const node of lineERBNodes) {
|
|
144
|
+
commentERBNode(node)
|
|
145
|
+
}
|
|
146
|
+
break
|
|
147
|
+
|
|
148
|
+
case "whole-line": {
|
|
149
|
+
for (const node of lineERBNodes) {
|
|
150
|
+
commentERBNode(node)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const commentNode = new HTMLCommentNode({
|
|
154
|
+
type: "AST_HTML_COMMENT_NODE",
|
|
155
|
+
location: Location.zero,
|
|
156
|
+
errors: [],
|
|
157
|
+
comment_start: createSyntheticToken("<!--", "TOKEN_HTML_COMMENT_START"),
|
|
158
|
+
children: [createLiteral(" "), ...(children.slice() as Node[]), createLiteral(" ")],
|
|
159
|
+
comment_end: createSyntheticToken("-->", "TOKEN_HTML_COMMENT_END"),
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
children.length = 0
|
|
163
|
+
children.push(commentNode)
|
|
164
|
+
break
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
case "html-only": {
|
|
168
|
+
const commentNode = new HTMLCommentNode({
|
|
169
|
+
type: "AST_HTML_COMMENT_NODE",
|
|
170
|
+
location: Location.zero,
|
|
171
|
+
errors: [],
|
|
172
|
+
comment_start: createSyntheticToken("<!--", "TOKEN_HTML_COMMENT_START"),
|
|
173
|
+
children: [createLiteral(" "), ...(children.slice() as Node[]), createLiteral(" ")],
|
|
174
|
+
comment_end: createSyntheticToken("-->", "TOKEN_HTML_COMMENT_END"),
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
children.length = 0
|
|
178
|
+
children.push(commentNode)
|
|
179
|
+
break
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return IdentityPrinter.print(document, { ignoreErrors: true })
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Per-segment commenting uses text segments because the parser creates nested
|
|
188
|
+
* structures (e.g., ERBIfNode) that don't allow flat child iteration.
|
|
189
|
+
*/
|
|
190
|
+
function commentPerSegment(content: string, erbNodes: ERBContentNode[]): string {
|
|
191
|
+
const segments = getLineSegments(content, erbNodes)
|
|
192
|
+
|
|
193
|
+
return segments.map(segment => {
|
|
194
|
+
if (segment.isERB) {
|
|
195
|
+
return segment.text.substring(0, 2) + "#" + segment.text.substring(2)
|
|
196
|
+
} else if (segment.text.trim() !== "") {
|
|
197
|
+
return `<!-- ${segment.text} -->`
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return segment.text
|
|
201
|
+
}).join("")
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function uncommentLineContent(content: string, parserService: ParserService): string {
|
|
205
|
+
const parseResult = parserService.parseContent(content, { track_whitespace: true })
|
|
206
|
+
const lineCollector = new LineContextCollector()
|
|
207
|
+
|
|
208
|
+
parseResult.visit(lineCollector)
|
|
209
|
+
|
|
210
|
+
const lineERBNodes = lineCollector.erbNodesPerLine.get(0) || []
|
|
211
|
+
const document = parseResult.value
|
|
212
|
+
const children = asMutable(document).children
|
|
213
|
+
|
|
214
|
+
for (const node of lineERBNodes) {
|
|
215
|
+
if (isERBCommentNode(node)) {
|
|
216
|
+
uncommentERBNode(node)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
let index = 0
|
|
221
|
+
|
|
222
|
+
while (index < children.length) {
|
|
223
|
+
const child = children[index]
|
|
224
|
+
|
|
225
|
+
if (child.type === "AST_HTML_COMMENT_NODE") {
|
|
226
|
+
const commentNode = child as HTMLCommentNode
|
|
227
|
+
const innerChildren = [...commentNode.children]
|
|
228
|
+
|
|
229
|
+
if (innerChildren.length > 0) {
|
|
230
|
+
const first = innerChildren[0]
|
|
231
|
+
|
|
232
|
+
if (isLiteralNode(first) && first.content.startsWith(" ")) {
|
|
233
|
+
const trimmed = first.content.substring(1)
|
|
234
|
+
|
|
235
|
+
if (trimmed === "") {
|
|
236
|
+
innerChildren.shift()
|
|
237
|
+
} else {
|
|
238
|
+
innerChildren[0] = createLiteral(trimmed)
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (innerChildren.length > 0) {
|
|
244
|
+
const last = innerChildren[innerChildren.length - 1]
|
|
245
|
+
|
|
246
|
+
if (isLiteralNode(last) && last.content.endsWith(" ")) {
|
|
247
|
+
const trimmed = last.content.substring(0, last.content.length - 1)
|
|
248
|
+
|
|
249
|
+
if (trimmed === "") {
|
|
250
|
+
innerChildren.pop()
|
|
251
|
+
} else {
|
|
252
|
+
innerChildren[innerChildren.length - 1] = createLiteral(trimmed)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const innerERBNodes: ERBContentNode[] = []
|
|
258
|
+
const innerCollector = new LineContextCollector()
|
|
259
|
+
|
|
260
|
+
for (const innerChild of innerChildren) {
|
|
261
|
+
innerCollector.visit(innerChild)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
innerERBNodes.push(...(innerCollector.erbNodesPerLine.get(0) || []))
|
|
265
|
+
|
|
266
|
+
for (const erbNode of innerERBNodes) {
|
|
267
|
+
if (isERBCommentNode(erbNode)) {
|
|
268
|
+
uncommentERBNode(erbNode)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
children.splice(index, 1, ...innerChildren)
|
|
273
|
+
index += innerChildren.length
|
|
274
|
+
|
|
275
|
+
continue
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
index++
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return IdentityPrinter.print(document, { ignoreErrors: true })
|
|
282
|
+
}
|