Skip to content

Commit d0295c3

Browse files
Implement sort requires and services code actions (#294)
* Setup code action protocol * Fixup protocol * Setup code actions * create sort services code action * Add basic requires sorting * Treat each require as a group * Update changelog * Ignore code action resolving for now * Update readme
1 parent 9cc7268 commit d0295c3

File tree

14 files changed

+719
-57
lines changed

14 files changed

+719
-57
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66

77
## [Unreleased]
88

9+
### Added
10+
11+
- Added two code actions: `Sort requires` and `Sort services` (services only enabled if `luau-lsp.types.roblox` == true).
12+
These actions will sort their respective groups alphabetically based on a variable name set.
13+
You can also set these actions to automatically run on save by configuring:
14+
15+
```json
16+
"editor.codeActionsOnSave": {
17+
"source.organizeImports": true
18+
}
19+
```
20+
921
### Changed
1022

1123
- Sync to upstream Luau 0.563

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ target_sources(Luau.LanguageServer PRIVATE
3333
src/operations/Rename.cpp
3434
src/operations/InlayHints.cpp
3535
src/operations/SemanticTokens.cpp
36+
src/operations/CodeAction.cpp
3637
)
3738

3839
target_sources(Luau.LanguageServer.CLI PRIVATE

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ If you use Luau in a different environment and are interested in using the langu
8080
- [x] Semantic Tokens
8181
- [x] Inlay Hints
8282
- [x] Documentation Comments ([Moonwave Style](https://github.com/evaera/moonwave) - supporting both `--- comment` and `--[=[ comment ]=]`, but must be next to statement)
83+
- [x] Code Actions
8384
- [ ] Call Hierarchy
8485
- [ ] Workspace Symbols
8586

@@ -88,7 +89,6 @@ They can be investigated at a later time:
8889

8990
- [ ] Go To Declaration (do not apply)
9091
- [ ] Go To Implementation (do not apply)
91-
- [ ] Code Actions (not necessary - could potentially add "fixers" for lints)
9292
- [ ] Code Lens (not necessary)
9393
- [ ] Document Highlight (not necessary - editor highlighting is sufficient)
9494
- [ ] Folding Range (not necessary - editor folding is sufficient)

src/LanguageServer.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ lsp::ServerCapabilities LanguageServer::getServerCapabilities()
8787
capabilities.colorProvider = true;
8888
// Document Link Provider
8989
capabilities.documentLinkProvider = {false};
90+
// Code Action Provider
91+
capabilities.codeActionProvider = {std::vector<lsp::CodeActionKind>{lsp::CodeActionKind::SourceOrganizeImports}, /* resolveProvider: */ false};
9092
// Rename Provider
9193
capabilities.renameProvider = true;
9294
// Inlay Hint Provider
@@ -164,6 +166,14 @@ void LanguageServer::onRequest(const id_type& id, const std::string& method, std
164166
{
165167
response = documentSymbol(REQUIRED_PARAMS(params, "textDocument/documentSymbol"));
166168
}
169+
else if (method == "textDocument/codeAction")
170+
{
171+
response = codeAction(REQUIRED_PARAMS(params, "textDocument/codeAction"));
172+
}
173+
// else if (method == "codeAction/resolve")
174+
// {
175+
// response = codeActionResolve(REQUIRED_PARAMS(params, "codeAction/resolve"));
176+
// }
167177
else if (method == "textDocument/semanticTokens/full")
168178
{
169179
response = semanticTokens(REQUIRED_PARAMS(params, "textDocument/semanticTokns/full"));

src/LuauExt.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,4 +1051,31 @@ std::optional<Luau::Location> getLocation(Luau::TypeId type)
10511051
}
10521052

10531053
return std::nullopt;
1054+
}
1055+
1056+
bool isGetService(const Luau::AstExpr* expr)
1057+
{
1058+
if (auto call = expr->as<Luau::AstExprCall>())
1059+
if (auto index = call->func->as<Luau::AstExprIndexName>())
1060+
if (index->index == "GetService")
1061+
if (auto name = index->expr->as<Luau::AstExprGlobal>())
1062+
if (name->name == "game")
1063+
return true;
1064+
1065+
return false;
1066+
}
1067+
1068+
bool isRequire(const Luau::AstExpr* expr)
1069+
{
1070+
if (auto call = expr->as<Luau::AstExprCall>())
1071+
{
1072+
if (auto funcAsGlobal = call->func->as<Luau::AstExprGlobal>(); funcAsGlobal && funcAsGlobal->name == "require")
1073+
return true;
1074+
}
1075+
else if (auto assertion = expr->as<Luau::AstExprTypeAssertion>())
1076+
{
1077+
return isRequire(assertion->expr);
1078+
}
1079+
1080+
return false;
10541081
}

src/include/LSP/LanguageServer.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ class LanguageServer
7070
std::vector<lsp::DocumentLink> documentLink(const lsp::DocumentLinkParams& params);
7171
lsp::DocumentColorResult documentColor(const lsp::DocumentColorParams& params);
7272
lsp::ColorPresentationResult colorPresentation(const lsp::ColorPresentationParams& params);
73+
lsp::CodeActionResult codeAction(const lsp::CodeActionParams& params);
7374

7475
std::optional<lsp::Hover> hover(const lsp::HoverParams& params);
7576
std::optional<lsp::SignatureHelp> signatureHelp(const lsp::SignatureHelpParams& params);

src/include/LSP/LuauExt.hpp

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,4 +113,46 @@ lsp::Position toUTF16(const TextDocument* textDocument, const Luau::Position& po
113113

114114
lsp::Diagnostic createTypeErrorDiagnostic(const Luau::TypeError& error, Luau::FileResolver* fileResolver, const TextDocument* textDocument = nullptr);
115115
lsp::Diagnostic createLintDiagnostic(const Luau::LintWarning& lint, const TextDocument* textDocument = nullptr);
116-
lsp::Diagnostic createParseErrorDiagnostic(const Luau::ParseError& error, const TextDocument* textDocument = nullptr);
116+
lsp::Diagnostic createParseErrorDiagnostic(const Luau::ParseError& error, const TextDocument* textDocument = nullptr);
117+
118+
bool isGetService(const Luau::AstExpr* expr);
119+
bool isRequire(const Luau::AstExpr* expr);
120+
121+
struct FindServicesVisitor : public Luau::AstVisitor
122+
{
123+
std::optional<size_t> firstServiceDefinitionLine = std::nullopt;
124+
std::map<std::string, Luau::AstStatLocal*> serviceLineMap{};
125+
126+
bool visit(Luau::AstStatLocal* local) override
127+
{
128+
if (local->vars.size != 1 || local->values.size != 1)
129+
return false;
130+
131+
auto localName = local->vars.data[0];
132+
auto expr = local->values.data[0];
133+
134+
if (!localName || !expr)
135+
return false;
136+
137+
auto line = localName->location.begin.line;
138+
139+
if (isGetService(expr))
140+
{
141+
firstServiceDefinitionLine =
142+
!firstServiceDefinitionLine.has_value() || firstServiceDefinitionLine.value() >= line ? line : firstServiceDefinitionLine.value();
143+
serviceLineMap.emplace(std::string(localName->name.value), local);
144+
}
145+
146+
return false;
147+
}
148+
149+
bool visit(Luau::AstStatBlock* block) override
150+
{
151+
for (Luau::AstStat* stat : block->body)
152+
{
153+
stat->visit(this);
154+
}
155+
156+
return false;
157+
}
158+
};

src/include/LSP/Utils.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
#include <filesystem>
55
#include <fstream>
66
#include <sstream>
7+
#include <vector>
78
#include <unordered_map>
9+
#include <algorithm>
810

911
std::optional<std::string> getParentPath(const std::string& path);
1012
std::optional<std::string> getAncestorPath(const std::string& path, const std::string& ancestorName);
@@ -18,6 +20,12 @@ bool endsWith(const std::string_view& str, const std::string_view& suffix);
1820
bool replace(std::string& str, const std::string& from, const std::string& to);
1921
void replaceAll(std::string& str, const std::string& from, const std::string& to);
2022

23+
template<typename V>
24+
inline bool contains(const std::vector<V>& vec, const V& value)
25+
{
26+
return std::find(std::begin(vec), std::end(vec), value) != std::end(vec);
27+
}
28+
2129
template<class K, class V>
2230
inline bool contains(const std::unordered_map<K, V>& map, const K& value)
2331
{

src/include/LSP/Workspace.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ class WorkspaceFolder
5858
void endAutocompletion(const lsp::CompletionParams& params);
5959
void suggestImports(const Luau::ModuleName& moduleName, const Luau::Position& position, const ClientConfiguration& config,
6060
std::vector<lsp::CompletionItem>& result);
61+
lsp::WorkspaceEdit computeOrganiseRequiresEdit(const lsp::DocumentUri& uri);
62+
lsp::WorkspaceEdit computeOrganiseServicesEdit(const lsp::DocumentUri& uri);
6163

6264
public:
6365
std::vector<std::string> getComments(const Luau::ModuleName& moduleName, const Luau::Location& node);
@@ -68,6 +70,7 @@ class WorkspaceFolder
6870
std::vector<lsp::DocumentLink> documentLink(const lsp::DocumentLinkParams& params);
6971
lsp::DocumentColorResult documentColor(const lsp::DocumentColorParams& params);
7072
lsp::ColorPresentationResult colorPresentation(const lsp::ColorPresentationParams& params);
73+
lsp::CodeActionResult codeAction(const lsp::CodeActionParams& params);
7174

7275
std::optional<lsp::Hover> hover(const lsp::HoverParams& params);
7376

src/include/Protocol/ClientCapabilities.hpp

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#include "nlohmann/json.hpp"
88
#include "Protocol/Completion.hpp"
9+
#include "Protocol/CodeAction.hpp"
910

1011
namespace lsp
1112
{
@@ -208,6 +209,99 @@ struct CompletionClientCapabilities
208209
NLOHMANN_DEFINE_OPTIONAL(
209210
CompletionClientCapabilities, dynamicRegistration, completionItem, completionItemKind, contextSupport, insertTextMode, completionList);
210211

212+
struct CodeActionClientCapabilities
213+
{
214+
/**
215+
* Whether code action supports dynamic registration.
216+
*/
217+
bool dynamicRegistration = false;
218+
219+
struct CodeActionLiteralSupport
220+
{
221+
struct CodeActionKindLiteralSupport
222+
{
223+
/**
224+
* The code action kind values the client supports. When this
225+
* property exists the client also guarantees that it will
226+
* handle values outside its set gracefully and falls back
227+
* to a default value when unknown.
228+
*/
229+
std::vector<CodeActionKind> valueSet;
230+
};
231+
232+
/**
233+
* The code action kind is supported with the following value
234+
* set.
235+
*/
236+
CodeActionKindLiteralSupport codeActionKind;
237+
};
238+
239+
/**
240+
* The client supports code action literals as a valid
241+
* response of the `textDocument/codeAction` request.
242+
*
243+
* @since 3.8.0
244+
*/
245+
std::optional<CodeActionLiteralSupport> codeActionLiteralSupport;
246+
247+
248+
/**
249+
* Whether code action supports the `isPreferred` property.
250+
*
251+
* @since 3.15.0
252+
*/
253+
bool isPreferredSupport = false;
254+
255+
/**
256+
* Whether code action supports the `disabled` property.
257+
*
258+
* @since 3.16.0
259+
*/
260+
bool disabledSupport = false;
261+
262+
/**
263+
* Whether code action supports the `data` property which is
264+
* preserved between a `textDocument/codeAction` and a
265+
* `codeAction/resolve` request.
266+
*
267+
* @since 3.16.0
268+
*/
269+
bool dataSupport = false;
270+
271+
272+
struct CodeActionResolveSupport
273+
{
274+
/**
275+
* The properties that a client can resolve lazily.
276+
*/
277+
std::vector<std::string> properties;
278+
};
279+
280+
/**
281+
* Whether the client supports resolving additional code action
282+
* properties via a separate `codeAction/resolve` request.
283+
*
284+
* @since 3.16.0
285+
*/
286+
std::optional<CodeActionResolveSupport> resolveSupport = std::nullopt;
287+
288+
/**
289+
* Whether the client honors the change annotations in
290+
* text edits and resource operations returned via the
291+
* `CodeAction#edit` property by for example presenting
292+
* the workspace edit in the user interface and asking
293+
* for confirmation.
294+
*
295+
* @since 3.16.0
296+
*/
297+
bool honorsChangeAnnotations = false;
298+
};
299+
NLOHMANN_DEFINE_OPTIONAL(CodeActionClientCapabilities::CodeActionLiteralSupport::CodeActionKindLiteralSupport, valueSet);
300+
NLOHMANN_DEFINE_OPTIONAL(CodeActionClientCapabilities::CodeActionLiteralSupport, codeActionKind);
301+
NLOHMANN_DEFINE_OPTIONAL(CodeActionClientCapabilities::CodeActionResolveSupport, properties);
302+
NLOHMANN_DEFINE_OPTIONAL(CodeActionClientCapabilities, dynamicRegistration, codeActionLiteralSupport, isPreferredSupport, disabledSupport,
303+
dataSupport, resolveSupport, honorsChangeAnnotations);
304+
211305
struct TextDocumentClientCapabilities
212306
{
213307
/**
@@ -216,8 +310,13 @@ struct TextDocumentClientCapabilities
216310
std::optional<CompletionClientCapabilities> completion = std::nullopt;
217311

218312
std::optional<DiagnosticClientCapabilities> diagnostic = std::nullopt;
313+
314+
/**
315+
* Capabilities specific to the `textDocument/codeAction` request.
316+
*/
317+
std::optional<CodeActionClientCapabilities> codeAction = std::nullopt;
219318
};
220-
NLOHMANN_DEFINE_OPTIONAL(TextDocumentClientCapabilities, completion, diagnostic);
319+
NLOHMANN_DEFINE_OPTIONAL(TextDocumentClientCapabilities, completion, diagnostic, codeAction);
221320

222321
struct DidChangeConfigurationClientCapabilities
223322
{

0 commit comments

Comments
 (0)