@herb-tools/language-server 0.3.1 → 0.4.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/README.md +74 -7
- package/bin/herb-language-server +0 -16
- package/dist/cli.js +27 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.js.map +1 -1
- package/dist/diagnostics.js +17 -50
- package/dist/diagnostics.js.map +1 -1
- package/dist/formatting_service.js +124 -0
- package/dist/formatting_service.js.map +1 -0
- package/dist/herb-language-server.js +16118 -6762
- package/dist/herb-language-server.js.map +1 -1
- package/dist/index.cjs +29900 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/linter_service.js +44 -0
- package/dist/linter_service.js.map +1 -0
- package/dist/parser_service.js +47 -0
- package/dist/parser_service.js.map +1 -0
- package/dist/project.js +2 -1
- package/dist/project.js.map +1 -1
- package/dist/server.js +72 -61
- package/dist/server.js.map +1 -1
- package/dist/service.js +16 -4
- package/dist/service.js.map +1 -1
- package/dist/settings.js +11 -1
- package/dist/settings.js.map +1 -1
- package/dist/types/cli.d.ts +4 -0
- package/dist/types/config.d.ts +9 -1
- package/dist/types/diagnostics.d.ts +9 -7
- package/dist/types/formatting_service.d.ts +19 -0
- package/dist/types/herb-language-server.d.ts +2 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/linter_service.d.ts +14 -0
- package/dist/types/parser_service.d.ts +10 -0
- package/dist/types/project.d.ts +2 -0
- package/dist/types/server.d.ts +7 -1
- package/dist/types/service.d.ts +6 -0
- package/dist/types/settings.d.ts +11 -0
- package/package.json +22 -24
- package/src/cli.ts +23 -0
- package/src/config.ts +9 -1
- package/src/diagnostics.ts +22 -79
- package/src/formatting_service.ts +150 -0
- package/src/herb-language-server.ts +6 -0
- package/src/index.ts +10 -0
- package/src/linter_service.ts +63 -0
- package/src/parser_service.ts +59 -0
- package/src/project.ts +4 -2
- package/src/server.ts +83 -65
- package/src/service.ts +21 -6
- package/src/settings.ts +24 -2
package/package.json
CHANGED
|
@@ -1,41 +1,42 @@
|
|
|
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.4.1",
|
|
5
5
|
"author": "Marco Roth",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"engines": {
|
|
8
8
|
"node": "*"
|
|
9
9
|
},
|
|
10
|
+
"homepage": "https://herb-tools.dev",
|
|
10
11
|
"bugs": "https://github.com/marcoroth/herb/issues/new?title=Package%20%60@herb-tools/language-server%60:%20",
|
|
11
12
|
"repository": {
|
|
12
13
|
"type": "git",
|
|
13
14
|
"url": "https://github.com/marcoroth/herb.git",
|
|
14
15
|
"directory": "javascript/packages/language-server"
|
|
15
16
|
},
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
"bin": {
|
|
18
|
+
"herb-language-server": "./bin/herb-language-server"
|
|
19
|
+
},
|
|
20
|
+
"main": "./dist/index.cjs",
|
|
21
|
+
"module": "./dist/index.js",
|
|
22
|
+
"require": "./dist/index.cjs",
|
|
23
|
+
"types": "./dist/types/index.d.ts",
|
|
19
24
|
"exports": {
|
|
20
25
|
"./package.json": "./package.json",
|
|
21
26
|
".": {
|
|
22
|
-
"types": "./dist/types/
|
|
23
|
-
"import": "./dist/
|
|
24
|
-
"require": "./dist/
|
|
25
|
-
"default": "./dist/
|
|
27
|
+
"types": "./dist/types/index.d.ts",
|
|
28
|
+
"import": "./dist/index.js",
|
|
29
|
+
"require": "./dist/index.cjs",
|
|
30
|
+
"default": "./dist/index.js"
|
|
26
31
|
}
|
|
27
32
|
},
|
|
28
|
-
"homepage": "https://herb-tools.dev",
|
|
29
|
-
"bin": {
|
|
30
|
-
"herb-language-server": "./bin/herb-language-server"
|
|
31
|
-
},
|
|
32
33
|
"scripts": {
|
|
33
34
|
"clean": "rimraf dist",
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"test": "
|
|
38
|
-
"prepublishOnly": "yarn clean && yarn build && yarn test"
|
|
35
|
+
"build": "yarn clean && tsc -b && rollup -c",
|
|
36
|
+
"dev": "tsc -b -w",
|
|
37
|
+
"test": "vitest",
|
|
38
|
+
"test:run": "vitest run",
|
|
39
|
+
"prepublishOnly": "yarn clean && yarn build && yarn test:run"
|
|
39
40
|
},
|
|
40
41
|
"files": [
|
|
41
42
|
"package.json",
|
|
@@ -45,17 +46,14 @@
|
|
|
45
46
|
"dist/"
|
|
46
47
|
],
|
|
47
48
|
"dependencies": {
|
|
48
|
-
"@herb-tools/
|
|
49
|
+
"@herb-tools/formatter": "0.4.1",
|
|
50
|
+
"@herb-tools/linter": "0.4.1",
|
|
51
|
+
"@herb-tools/node-wasm": "0.4.1",
|
|
49
52
|
"dedent": "^1.6.0",
|
|
50
|
-
"typescript": "^5.8.3",
|
|
51
53
|
"vscode-languageserver": "^9.0.1",
|
|
52
54
|
"vscode-languageserver-textdocument": "^1.0.12"
|
|
53
55
|
},
|
|
54
56
|
"devDependencies": {
|
|
55
|
-
"
|
|
56
|
-
"@rollup/plugin-typescript": "^12.1.3",
|
|
57
|
-
"@rollup/plugin-node-resolve": "^16.0.1",
|
|
58
|
-
"@rollup/plugin-json": "^6.1.0",
|
|
59
|
-
"rimraf": "^6.0.1"
|
|
57
|
+
"vitest": "^1.0.0"
|
|
60
58
|
}
|
|
61
59
|
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Server } from "./server"
|
|
2
|
+
|
|
3
|
+
export class CLI {
|
|
4
|
+
private usage = `
|
|
5
|
+
Usage: herb-language-server [options]
|
|
6
|
+
|
|
7
|
+
Options:
|
|
8
|
+
--stdio use stdio
|
|
9
|
+
--node-ipc use node-ipc
|
|
10
|
+
--socket=<port> use socket
|
|
11
|
+
`
|
|
12
|
+
|
|
13
|
+
run() {
|
|
14
|
+
if (process.argv.length <= 2) {
|
|
15
|
+
console.error(`Error: Connection input stream is not set. Set command line parameters: '--node-ipc', '--stdio' or '--socket=<port>'`)
|
|
16
|
+
console.error(this.usage)
|
|
17
|
+
process.exit(1)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const server = new Server()
|
|
21
|
+
server.listen()
|
|
22
|
+
}
|
|
23
|
+
}
|
package/src/config.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
export type HerbConfigOptions = {
|
|
1
|
+
export type HerbConfigOptions = {
|
|
2
|
+
formatter?: {
|
|
3
|
+
enabled?: boolean
|
|
4
|
+
include?: string[]
|
|
5
|
+
exclude?: string[]
|
|
6
|
+
indentWidth?: number
|
|
7
|
+
maxLineLength?: number
|
|
8
|
+
}
|
|
9
|
+
}
|
|
2
10
|
|
|
3
11
|
export type HerbLSPConfig = {
|
|
4
12
|
version: string
|
package/src/diagnostics.ts
CHANGED
|
@@ -1,106 +1,49 @@
|
|
|
1
|
-
import { Connection, Diagnostic, DiagnosticSeverity, Range, Position } from "vscode-languageserver/node"
|
|
2
1
|
import { TextDocument } from "vscode-languageserver-textdocument"
|
|
3
|
-
import {
|
|
2
|
+
import { Connection, Diagnostic } from "vscode-languageserver/node"
|
|
4
3
|
|
|
4
|
+
import { ParserService } from "./parser_service"
|
|
5
|
+
import { LinterService } from "./linter_service"
|
|
5
6
|
import { DocumentService } from "./document_service"
|
|
6
7
|
|
|
7
|
-
import type { Node, HerbError } from "@herb-tools/node-wasm"
|
|
8
|
-
|
|
9
|
-
class ErrorVisitor extends Visitor {
|
|
10
|
-
private diagnostics: Diagnostics
|
|
11
|
-
private textDocument: TextDocument
|
|
12
|
-
|
|
13
|
-
constructor(diagnostics: Diagnostics, textDocument: TextDocument) {
|
|
14
|
-
super()
|
|
15
|
-
this.diagnostics = diagnostics
|
|
16
|
-
this.textDocument = textDocument
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
visitChildNodes(node: Node) {
|
|
20
|
-
super.visitChildNodes(node)
|
|
21
|
-
|
|
22
|
-
node.errors.forEach(error => this.publishDiagnosticForError(error, node))
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
private publishDiagnosticForError(error: HerbError, node: Node): void {
|
|
26
|
-
this.diagnostics.pushDiagnostic(
|
|
27
|
-
error.message,
|
|
28
|
-
error.type,
|
|
29
|
-
this.rangeFromHerbError(error),
|
|
30
|
-
this.textDocument,
|
|
31
|
-
{
|
|
32
|
-
error: error.toJSON(),
|
|
33
|
-
node: node.toJSON()
|
|
34
|
-
},
|
|
35
|
-
DiagnosticSeverity.Error
|
|
36
|
-
)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
private rangeFromHerbError(error: HerbError): Range {
|
|
40
|
-
return Range.create(
|
|
41
|
-
Position.create(error.location.start.line - 1, error.location.start.column),
|
|
42
|
-
Position.create(error.location.end.line - 1, error.location.end.column),
|
|
43
|
-
)
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
8
|
export class Diagnostics {
|
|
48
9
|
private readonly connection: Connection
|
|
49
10
|
private readonly documentService: DocumentService
|
|
50
|
-
private readonly
|
|
11
|
+
private readonly parserService: ParserService
|
|
12
|
+
private readonly linterService: LinterService
|
|
51
13
|
private diagnostics: Map<TextDocument, Diagnostic[]> = new Map()
|
|
52
14
|
|
|
53
15
|
constructor(
|
|
54
16
|
connection: Connection,
|
|
55
17
|
documentService: DocumentService,
|
|
18
|
+
parserService: ParserService,
|
|
19
|
+
linterService: LinterService,
|
|
56
20
|
) {
|
|
57
21
|
this.connection = connection
|
|
58
22
|
this.documentService = documentService
|
|
23
|
+
this.parserService = parserService
|
|
24
|
+
this.linterService = linterService
|
|
59
25
|
}
|
|
60
26
|
|
|
61
|
-
validate(textDocument: TextDocument) {
|
|
62
|
-
const
|
|
63
|
-
const
|
|
64
|
-
const visitor = new ErrorVisitor(this, textDocument)
|
|
27
|
+
async validate(textDocument: TextDocument) {
|
|
28
|
+
const parseResult = this.parserService.parseDocument(textDocument)
|
|
29
|
+
const lintResult = await this.linterService.lintDocument(parseResult.document, textDocument)
|
|
65
30
|
|
|
66
|
-
|
|
31
|
+
const allDiagnostics = [
|
|
32
|
+
...parseResult.diagnostics,
|
|
33
|
+
...lintResult.diagnostics,
|
|
34
|
+
]
|
|
67
35
|
|
|
36
|
+
this.diagnostics.set(textDocument, allDiagnostics)
|
|
68
37
|
this.sendDiagnosticsFor(textDocument)
|
|
69
38
|
}
|
|
70
39
|
|
|
71
|
-
refreshDocument(document: TextDocument) {
|
|
72
|
-
this.validate(document)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
refreshAllDocuments() {
|
|
76
|
-
this.documentService.getAll().forEach((document) => {
|
|
77
|
-
this.refreshDocument(document)
|
|
78
|
-
})
|
|
40
|
+
async refreshDocument(document: TextDocument) {
|
|
41
|
+
await this.validate(document)
|
|
79
42
|
}
|
|
80
43
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
range: Range,
|
|
85
|
-
textDocument: TextDocument,
|
|
86
|
-
data = {},
|
|
87
|
-
severity: DiagnosticSeverity = DiagnosticSeverity.Error,
|
|
88
|
-
) {
|
|
89
|
-
const diagnostic: Diagnostic = {
|
|
90
|
-
source: this.diagnosticsSource,
|
|
91
|
-
severity,
|
|
92
|
-
range,
|
|
93
|
-
message,
|
|
94
|
-
code,
|
|
95
|
-
data,
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const diagnostics = this.diagnostics.get(textDocument) || []
|
|
99
|
-
diagnostics.push(diagnostic)
|
|
100
|
-
|
|
101
|
-
this.diagnostics.set(textDocument, diagnostics)
|
|
102
|
-
|
|
103
|
-
return diagnostic
|
|
44
|
+
async refreshAllDocuments() {
|
|
45
|
+
const documents = this.documentService.getAll()
|
|
46
|
+
await Promise.all(documents.map(document => this.refreshDocument(document)))
|
|
104
47
|
}
|
|
105
48
|
|
|
106
49
|
private sendDiagnosticsFor(textDocument: TextDocument) {
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { Connection, TextDocuments, DocumentFormattingParams, TextEdit, Range, Position } from "vscode-languageserver/node"
|
|
2
|
+
import { TextDocument } from "vscode-languageserver-textdocument"
|
|
3
|
+
import { Formatter, defaultFormatOptions } from "@herb-tools/formatter"
|
|
4
|
+
import { Project } from "./project"
|
|
5
|
+
import { Settings } from "./settings"
|
|
6
|
+
import { Config } from "./config"
|
|
7
|
+
import { glob } from "glob"
|
|
8
|
+
|
|
9
|
+
export class FormattingService {
|
|
10
|
+
private connection: Connection
|
|
11
|
+
private documents: TextDocuments<TextDocument>
|
|
12
|
+
private project: Project
|
|
13
|
+
private settings: Settings
|
|
14
|
+
private config?: Config
|
|
15
|
+
|
|
16
|
+
constructor(connection: Connection, documents: TextDocuments<TextDocument>, project: Project, settings: Settings) {
|
|
17
|
+
this.connection = connection
|
|
18
|
+
this.documents = documents
|
|
19
|
+
this.project = project
|
|
20
|
+
this.settings = settings
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async initialize() {
|
|
24
|
+
try {
|
|
25
|
+
this.config = await Config.fromPathOrNew(this.project.projectPath)
|
|
26
|
+
this.connection.console.log("Herb formatter initialized successfully")
|
|
27
|
+
} catch (error) {
|
|
28
|
+
this.connection.console.error(`Failed to initialize Herb formatter: ${error}`)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async refreshConfig() {
|
|
33
|
+
this.config = await Config.fromPathOrNew(this.project.projectPath)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private async shouldFormatFile(filePath: string): Promise<boolean> {
|
|
37
|
+
if (!this.config?.options.formatter) {
|
|
38
|
+
return true
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const formatter = this.config.options.formatter
|
|
42
|
+
|
|
43
|
+
// Check if formatting is disabled in project config
|
|
44
|
+
if (formatter.enabled === false) {
|
|
45
|
+
return false
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Check exclude patterns first
|
|
49
|
+
if (formatter.exclude) {
|
|
50
|
+
for (const pattern of formatter.exclude) {
|
|
51
|
+
try {
|
|
52
|
+
const matches = await new Promise<string[]>((resolve, reject) => {
|
|
53
|
+
glob(pattern, { cwd: this.project.projectPath }).then(resolve).catch(reject)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
if (Array.isArray(matches) && matches.some((match: string) => filePath.includes(match) || filePath.endsWith(match))) {
|
|
57
|
+
return false
|
|
58
|
+
}
|
|
59
|
+
} catch (error) {
|
|
60
|
+
continue
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (formatter.include && formatter.include.length > 0) {
|
|
66
|
+
for (const pattern of formatter.include) {
|
|
67
|
+
try {
|
|
68
|
+
const matches = await new Promise<string[]>((resolve, reject) => {
|
|
69
|
+
glob(pattern, { cwd: this.project.projectPath }).then(resolve).catch(reject)
|
|
70
|
+
})
|
|
71
|
+
if (Array.isArray(matches) && matches.some((match: string) => filePath.includes(match) || filePath.endsWith(match))) {
|
|
72
|
+
return true
|
|
73
|
+
}
|
|
74
|
+
} catch (error) {
|
|
75
|
+
continue
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return false
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return true
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private async getFormatterOptions(uri: string) {
|
|
86
|
+
const settings = await this.settings.getDocumentSettings(uri)
|
|
87
|
+
|
|
88
|
+
const projectFormatter = this.config?.options.formatter || {}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
indentWidth: projectFormatter.indentWidth ?? settings.formatter?.indentWidth ?? defaultFormatOptions.indentWidth,
|
|
92
|
+
maxLineLength: projectFormatter.maxLineLength ?? settings.formatter?.maxLineLength ?? defaultFormatOptions.maxLineLength
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private async performFormatting(params: DocumentFormattingParams): Promise<TextEdit[]> {
|
|
97
|
+
const document = this.documents.get(params.textDocument.uri)
|
|
98
|
+
|
|
99
|
+
if (!document) {
|
|
100
|
+
return []
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const options = await this.getFormatterOptions(params.textDocument.uri)
|
|
105
|
+
const formatter = new Formatter(this.project.herbBackend, options)
|
|
106
|
+
|
|
107
|
+
const text = document.getText()
|
|
108
|
+
let newText = formatter.format(text)
|
|
109
|
+
|
|
110
|
+
if (!newText.endsWith('\n')) {
|
|
111
|
+
newText = newText + '\n'
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (newText === text) {
|
|
115
|
+
return []
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const range: Range = {
|
|
119
|
+
start: Position.create(0, 0),
|
|
120
|
+
end: Position.create(document.lineCount, 0)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return [{ range, newText }]
|
|
124
|
+
} catch (error) {
|
|
125
|
+
this.connection.console.error(`Formatting failed: ${error}`)
|
|
126
|
+
|
|
127
|
+
return []
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async formatDocument(params: DocumentFormattingParams): Promise<TextEdit[]> {
|
|
132
|
+
const settings = await this.settings.getDocumentSettings(params.textDocument.uri)
|
|
133
|
+
|
|
134
|
+
if (settings.formatter?.enabled === false) {
|
|
135
|
+
return []
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const filePath = params.textDocument.uri.replace(/^file:\/\//, '')
|
|
139
|
+
|
|
140
|
+
if (!(await this.shouldFormatFile(filePath))) {
|
|
141
|
+
return []
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return this.performFormatting(params)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async formatDocumentIgnoreConfig(params: DocumentFormattingParams): Promise<TextEdit[]> {
|
|
148
|
+
return this.performFormatting(params)
|
|
149
|
+
}
|
|
150
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from "./server"
|
|
2
|
+
export * from "./service"
|
|
3
|
+
export * from "./config"
|
|
4
|
+
export * from "./diagnostics"
|
|
5
|
+
export * from "./document_service"
|
|
6
|
+
export * from "./formatting_service"
|
|
7
|
+
export * from "./project"
|
|
8
|
+
export * from "./settings"
|
|
9
|
+
export * from "./utils"
|
|
10
|
+
export * from "./cli"
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Diagnostic, DiagnosticSeverity, Range, Position, CodeDescription } from "vscode-languageserver/node"
|
|
2
|
+
import { TextDocument } from "vscode-languageserver-textdocument"
|
|
3
|
+
import { Linter } from "@herb-tools/linter"
|
|
4
|
+
|
|
5
|
+
import { Settings } from "./settings"
|
|
6
|
+
|
|
7
|
+
import type { DocumentNode } from "@herb-tools/node-wasm"
|
|
8
|
+
|
|
9
|
+
export interface LintServiceResult {
|
|
10
|
+
diagnostics: Diagnostic[]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class LinterService {
|
|
14
|
+
private readonly settings: Settings
|
|
15
|
+
private readonly source = "Herb Linter "
|
|
16
|
+
private linter: Linter
|
|
17
|
+
|
|
18
|
+
constructor(settings: Settings) {
|
|
19
|
+
this.settings = settings
|
|
20
|
+
this.linter = new Linter()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async lintDocument(document: DocumentNode, textDocument: TextDocument): Promise<LintServiceResult> {
|
|
24
|
+
const settings = await this.settings.getDocumentSettings(textDocument.uri)
|
|
25
|
+
const linterEnabled = settings?.linter?.enabled ?? true
|
|
26
|
+
|
|
27
|
+
if (!linterEnabled) {
|
|
28
|
+
return { diagnostics: [] }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const lintResult = this.linter.lint(document)
|
|
32
|
+
const diagnostics: Diagnostic[] = []
|
|
33
|
+
|
|
34
|
+
lintResult.offenses.forEach(offense => {
|
|
35
|
+
const severity = offense.severity === "error"
|
|
36
|
+
? DiagnosticSeverity.Error
|
|
37
|
+
: DiagnosticSeverity.Warning
|
|
38
|
+
|
|
39
|
+
const range = Range.create(
|
|
40
|
+
Position.create(offense.location.start.line - 1, offense.location.start.column),
|
|
41
|
+
Position.create(offense.location.end.line - 1, offense.location.end.column),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
const codeDescription: CodeDescription = {
|
|
45
|
+
href: `https://herb-tools.dev/linter/rules/${offense.rule}`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const diagnostic: Diagnostic = {
|
|
49
|
+
source: this.source,
|
|
50
|
+
severity,
|
|
51
|
+
range,
|
|
52
|
+
message: offense.message,
|
|
53
|
+
code: offense.rule,
|
|
54
|
+
data: { rule: offense.rule },
|
|
55
|
+
codeDescription
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
diagnostics.push(diagnostic)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
return { diagnostics }
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Diagnostic, DiagnosticSeverity, Range, Position } from "vscode-languageserver/node"
|
|
2
|
+
import { TextDocument } from "vscode-languageserver-textdocument"
|
|
3
|
+
import { Herb, Visitor } from "@herb-tools/node-wasm"
|
|
4
|
+
|
|
5
|
+
import type { Node, HerbError, DocumentNode } from "@herb-tools/node-wasm"
|
|
6
|
+
|
|
7
|
+
class ErrorVisitor extends Visitor {
|
|
8
|
+
private readonly source = "Herb Parser "
|
|
9
|
+
public diagnostics: Diagnostic[] = []
|
|
10
|
+
|
|
11
|
+
visitChildNodes(node: Node) {
|
|
12
|
+
super.visitChildNodes(node)
|
|
13
|
+
|
|
14
|
+
node.errors.forEach(error => this.addDiagnosticForError(error, node))
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
private addDiagnosticForError(error: HerbError, node: Node): void {
|
|
18
|
+
const diagnostic: Diagnostic = {
|
|
19
|
+
source: this.source,
|
|
20
|
+
severity: DiagnosticSeverity.Error,
|
|
21
|
+
range: this.rangeFromHerbError(error),
|
|
22
|
+
message: error.message,
|
|
23
|
+
code: error.type,
|
|
24
|
+
data: {
|
|
25
|
+
error: error.toJSON(),
|
|
26
|
+
node: node.toJSON()
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
this.diagnostics.push(diagnostic)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private rangeFromHerbError(error: HerbError): Range {
|
|
34
|
+
return Range.create(
|
|
35
|
+
Position.create(error.location.start.line - 1, error.location.start.column),
|
|
36
|
+
Position.create(error.location.end.line - 1, error.location.end.column),
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ParseServiceResult {
|
|
42
|
+
document: DocumentNode
|
|
43
|
+
diagnostics: Diagnostic[]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class ParserService {
|
|
47
|
+
parseDocument(textDocument: TextDocument): ParseServiceResult {
|
|
48
|
+
const content = textDocument.getText()
|
|
49
|
+
const result = Herb.parse(content)
|
|
50
|
+
|
|
51
|
+
const errorVisitor = new ErrorVisitor()
|
|
52
|
+
result.visit(errorVisitor)
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
document: result.value,
|
|
56
|
+
diagnostics: errorVisitor.diagnostics
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
package/src/project.ts
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
import { Herb } from "@herb-tools/node-wasm"
|
|
1
|
+
import { Herb, HerbBackend } from "@herb-tools/node-wasm"
|
|
2
2
|
import { Connection } from "vscode-languageserver/node"
|
|
3
3
|
|
|
4
4
|
export class Project {
|
|
5
5
|
connection: Connection
|
|
6
6
|
projectPath: string
|
|
7
|
+
herbBackend: HerbBackend
|
|
7
8
|
|
|
8
9
|
constructor(connection: Connection, projectPath: string) {
|
|
9
10
|
this.projectPath = projectPath
|
|
10
11
|
this.connection = connection
|
|
12
|
+
this.herbBackend = Herb
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
async initialize() {
|
|
14
|
-
await
|
|
16
|
+
await this.herbBackend.load()
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
async refresh() {
|