Skip to content

Commit 0d095a6

Browse files
authored
Move initialize handler to Rego router (#1977)
It's leaner, it's faster, it's Rego! I have wanted to do this for a long time, but it wasn't prioritized. But what are weekends for anyway. Also: - Remove types that were only used in Go initialize - Remove Go functions no longer used - Handle *json.RawMessage in rast.AnyToValue Signed-off-by: Anders Eknert <anders.eknert@apple.com>
1 parent 6e7feeb commit 0d095a6

22 files changed

Lines changed: 566 additions & 564 deletions

File tree

.regal/config.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,13 @@ rules:
136136
line-length:
137137
level: error
138138
non-breakable-word-threshold: 100
139+
prefer-snake-case:
140+
level: error
141+
ignore:
142+
files:
143+
# This package is used to build the LSP initialize
144+
# response, and names must conform to the specification
145+
- "bundle/regal/lsp/initialize/initialize.rego"
139146
imports:
140147
unresolved-reference:
141148
level: error
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
# METADATA
2+
# description: |
3+
# handler for the LSP "initialize" request, which is the first request sent by the client to the server in order
4+
# to initialize the connection and establish the capabilities of both client and server
5+
# related_resources:
6+
# - https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize
7+
# schemas:
8+
# - input: schema.regal.lsp.common
9+
# - input.params: schema.regal.lsp.initialize
10+
# scope: subpackages
11+
package regal.lsp.initialize
12+
13+
# METADATA
14+
# entrypoint: true
15+
result.response.serverInfo := _serverInfo
16+
17+
_serverInfo.name := "Regal"
18+
19+
default _serverInfo.version := "unknown"
20+
21+
_serverInfo.version := input.regal.server.version
22+
23+
# METADATA
24+
# description: The capabilities of Regal's language server, as defined in the LSP specification
25+
result.response.capabilities := _capabilities
26+
27+
_capabilities.textDocumentSync := {
28+
"openClose": true,
29+
# For now, send full document on change, but this is something we should improve.
30+
# See https://github.com/open-policy-agent/regal/issues/1651
31+
"change": 1,
32+
"save": {"includeText": true},
33+
}
34+
35+
_capabilities.diagnosticProvider := {
36+
"identifier": "rego",
37+
"interFileDependencies": true,
38+
"workspaceDiagnostics": true,
39+
}
40+
41+
_capabilities.workspace.fileOperations[operation].filters := filters if {
42+
filters := [{
43+
"scheme": "file",
44+
"pattern": {"glob": "**/*.rego"},
45+
}]
46+
some operation in ["didCreate", "didRename", "didDelete"]
47+
}
48+
49+
## NOTE(anders): The language server protocol doesn't go into detail about what this is meant to
50+
## entail, and there's nothing else in the request/response payloads that carry workspace folder
51+
## information. The best source I've found on the this topic is this example repo from VS Code,
52+
## where they have the client start one instance of the server per workspace folder:
53+
## https://github.com/microsoft/vscode-extension-samples/tree/main/lsp-multi-server-sample
54+
## That seems like a reasonable approach to take, and means we won't have to deal with workspace
55+
## folders throughout the rest of the codebase. But the question then is — what is the point of
56+
## this capability, and what does it mean to say we support it? Clearly we don't in the server as
57+
## *there is no way* to support it here.
58+
_capabilities.workspace.workspaceFolders.supported := true
59+
60+
_capabilities.inlayHintProvider := {
61+
# inlayHint/resolve request supported
62+
"resolveProvider": true,
63+
}
64+
65+
_capabilities.hoverProvider := true
66+
67+
_capabilities.signatureHelpProvider := {
68+
# In additional to the client's default trigger characters for signature help
69+
"triggerCharacters": ["(", ","]
70+
}
71+
72+
_capabilities.codeActionProvider := {
73+
# Currently supported code action kinds
74+
"codeActionKinds": [
75+
"quickfix",
76+
"source"
77+
],
78+
}
79+
80+
_capabilities.executeCommandProvider.commands := _commands
81+
82+
_commands contains "regal.eval"
83+
_commands contains "regal.fix.opa-fmt"
84+
_commands contains "regal.fix.use-rego-v1"
85+
_commands contains "regal.fix.use-assignment-operator"
86+
_commands contains "regal.fix.no-whitespace-comment"
87+
_commands contains "regal.fix.directory-package-mismatch"
88+
_commands contains "regal.fix.non-raw-regex-pattern"
89+
_commands contains "regal.fix.prefer-equals-comparison"
90+
_commands contains "regal.fix.constant-condition"
91+
_commands contains "regal.fix.redundant-existence-check"
92+
_commands contains "regal.config.disable-rule"
93+
_commands contains "regal.explorer" if input.regal.server.feature_flags.explorer_provider
94+
_commands contains "regal.debug" if input.regal.server.feature_flags.debug_provider
95+
96+
_capabilities.documentFormattingProvider := true
97+
98+
_capabilities.foldingRangeProvider := true
99+
100+
_capabilities.definitionProvider := true
101+
102+
_capabilities.documentSymbolProvider := true
103+
104+
_capabilities.workspaceSymbolProvider := true
105+
106+
_capabilities.completionProvider := {
107+
"triggerCharacters": [
108+
":", # to suggest :=
109+
".", # for refs
110+
],
111+
"resolveProvider": true,
112+
"completionItem": {"labelDetailsSupport": true},
113+
}
114+
115+
_capabilities.codeLensProvider := {
116+
# codeLens/resolve to be implemented
117+
"resolveProvider": false,
118+
}
119+
120+
_capabilities.documentLinkProvider := {
121+
# documentLink/resolve to be implemented
122+
"resolveProvider": false,
123+
}
124+
125+
_capabilities.documentHighlightProvider := true
126+
127+
_capabilities.selectionRangeProvider := true
128+
129+
_capabilities.linkedEditingRangeProvider := true
130+
131+
_capabilities.semanticTokensProvider := {
132+
"legend": {
133+
"tokenTypes": [
134+
"namespace",
135+
"variable",
136+
"import",
137+
"keyword",
138+
],
139+
"tokenModifiers": [
140+
"declaration",
141+
"definition",
142+
"reference",
143+
],
144+
},
145+
"full": true,
146+
}
147+
148+
# Note: 'experimental' is LSP terminology. We are using these to mean
149+
# custom additions that are ready for use, but not in the base spec.
150+
_capabilities.experimental.explorerProvider := input.regal.server.feature_flags.explorer_provider
151+
_capabilities.experimental.inlineEvalProvider := input.regal.server.feature_flags.inline_evaluation_provider
152+
_capabilities.experimental.debugProvider := input.regal.server.feature_flags.debug_provider
153+
_capabilities.experimental.opaTestProvider := input.regal.server.feature_flags.opa_test_provider
154+
155+
# METADATA
156+
# description: The server's identifier for the client, based on the clientInfo sent in the request
157+
# scope: document
158+
default result.regal.client.identifier := 0
159+
160+
result.regal.client.identifier := _client_identifier(input.params.clientInfo.name)
161+
162+
_client_identifier("Visual Studio Code") := 1
163+
_client_identifier("go test") := 2
164+
_client_identifier("Zed") := 3
165+
_client_identifier("Neovim") := 4
166+
_client_identifier(name) := 5 if contains(name, "IntelliJ")
167+
168+
# METADATA
169+
# description: The initialization options sent by the client, or an empty object if not provided
170+
# scope: document
171+
default result.regal.client.initializationOptions := {}
172+
173+
result.regal.client.initializationOptions := input.params.initializationOptions
174+
175+
# METADATA
176+
# description: The capabilities of the client, as sent in the initialize request
177+
# scope: document
178+
result.regal.client.capabilities := input.params.capabilities
179+
180+
# METADATA
181+
# description: The root URI of the workspace, as provided by the client
182+
result.regal.workspace.uri := input.params.rootUri if {
183+
input.params.rootUri != ""
184+
} else := input.params.workspaceFolders[0].uri
185+
186+
# METADATA
187+
# description: Any warnings to log from initialization
188+
# scope: document
189+
result.regal.warnings contains $"multiple workspace folders provided, only the first one will be used: {uri}" if {
190+
count(input.params.workspaceFolders) > 1
191+
192+
uri := input.params.workspaceFolders[0].uri
193+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package regal.lsp.initialize_test
2+
3+
import data.regal.lsp.initialize
4+
5+
test_initialize_experimental_capabilities if {
6+
response := initialize.result.response
7+
with input.params as {
8+
"capabilities": {},
9+
"initializationOptions": {},
10+
"clientInfo": {"name": "go test"},
11+
}
12+
with input.regal.server.feature_flags as {
13+
"explorer_provider": true,
14+
"inline_evaluation_provider": true,
15+
"debug_provider": true,
16+
"opa_test_provider": true,
17+
}
18+
19+
response.capabilities.experimental.explorerProvider == true
20+
response.capabilities.experimental.inlineEvalProvider == true
21+
response.capabilities.experimental.debugProvider == true
22+
response.capabilities.experimental.opaTestProvider == true
23+
}
24+
25+
test_initialize_client_identifier[client_name] if {
26+
some identifier, client_name in [
27+
"Space Editor!!1!",
28+
"Visual Studio Code",
29+
"go test",
30+
"Zed",
31+
"Neovim",
32+
"IntelliJ IDEA 2027.1",
33+
]
34+
35+
result := initialize.result
36+
with input.params.clientInfo.name as client_name
37+
38+
result.regal.client.identifier == identifier
39+
}
40+
41+
test_initialize_client_capabilities if {
42+
result := initialize.result
43+
with input.params.capabilities.textDocument.inlayHint.resolveSupport.properties as ["tooltip"]
44+
45+
result.regal.client.capabilities.textDocument.inlayHint.resolveSupport.properties == ["tooltip"]
46+
}

bundle/regal/lsp/main/main.rego

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ eval := response if {
2525
}
2626
}
2727

28+
_handler_for("initialize") := "initialize"
29+
2830
_handler_for(method) := lower(name) if ["textDocument", name] = split(method, "/")
2931

3032
_handler_for("completionItem/resolve") := "completion"

internal/embeds/schemas/regal/lsp/common.json

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@
4949
"type": "object",
5050
"description": "LSP server config and state",
5151
"properties": {
52+
"version": {
53+
"type": "string",
54+
"description": "Version of the Regal server"
55+
},
5256
"feature_flags": {
5357
"type": "object",
5458
"description": "Feature flags state",
@@ -61,9 +65,13 @@
6165
"type": "boolean",
6266
"description": "If the server is configured to operate as an explorer provider"
6367
},
64-
"inline_eval_provider": {
68+
"inline_evaluation_provider": {
69+
"type": "boolean",
70+
"description": "If the server is configured to operate as an inline evaluation provider"
71+
},
72+
"opa_test_provider": {
6573
"type": "boolean",
66-
"description": "If the server is configured to operate as an inline eval provider"
74+
"description": "If the server is configured to operate as an OPA test API provider"
6775
}
6876
},
6977
"required": [
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "regal.lsp.initialize",
4+
"$ref": "#/$defs/params",
5+
"$defs": {
6+
"params": {
7+
"type": "object",
8+
"properties": {
9+
"processId": {
10+
"type": "integer"
11+
},
12+
"clientInfo": {
13+
"type": "object",
14+
"description": "Information about the client",
15+
"properties": {
16+
"name": {
17+
"type": "string"
18+
},
19+
"version": {
20+
"type": "string"
21+
}
22+
},
23+
"required": [
24+
"name"
25+
]
26+
},
27+
"locale": {
28+
"type": "string"
29+
},
30+
"initializationOptions": {
31+
"type": "object"
32+
},
33+
"capabilities": {
34+
"type": "object",
35+
"description": "The capabilities supported by the client"
36+
},
37+
"rootUri": {
38+
"type": "string"
39+
},
40+
"workspaceFolders": {
41+
"type": "array",
42+
"items": {
43+
"type": "object",
44+
"properties": {
45+
"uri": {
46+
"type": "string"
47+
},
48+
"name": {
49+
"type": "string"
50+
}
51+
}
52+
}
53+
}
54+
},
55+
"required": [
56+
"processId",
57+
"capabilities"
58+
]
59+
}
60+
}
61+
}

internal/io/files/walker.go

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,6 @@ func (fw *Walker) WithSkipFunc(skipFunc filter.Func) *Walker {
7070
return fw
7171
}
7272

73-
// WithStatBeforeWalk sets whether to stat the root directory before walking.
74-
// The underlying filepath.WalkDir implementation panics on non-existent paths,
75-
// so this is useful when the input isn't guaranteed to exist.
76-
func (fw *Walker) WithStatBeforeWalk(statRoot bool) *Walker {
77-
fw.statRoot = statRoot
78-
79-
return fw
80-
}
81-
8273
// Walk walks the file system rooted at the root, calling f for each file
8374
// not filtered out by the filters.
8475
func (fw *Walker) Walk(f func(string) error) error {

0 commit comments

Comments
 (0)