Skip to content

Commit

Permalink
Implement sort requires and services code actions (#294)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
JohnnyMorganz authored Feb 12, 2023
1 parent 9cc7268 commit d0295c3
Show file tree
Hide file tree
Showing 14 changed files with 719 additions and 57 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [Unreleased]

### Added

- Added two code actions: `Sort requires` and `Sort services` (services only enabled if `luau-lsp.types.roblox` == true).
These actions will sort their respective groups alphabetically based on a variable name set.
You can also set these actions to automatically run on save by configuring:

```json
"editor.codeActionsOnSave": {
"source.organizeImports": true
}
```

### Changed

- Sync to upstream Luau 0.563
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ target_sources(Luau.LanguageServer PRIVATE
src/operations/Rename.cpp
src/operations/InlayHints.cpp
src/operations/SemanticTokens.cpp
src/operations/CodeAction.cpp
)

target_sources(Luau.LanguageServer.CLI PRIVATE
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ If you use Luau in a different environment and are interested in using the langu
- [x] Semantic Tokens
- [x] Inlay Hints
- [x] Documentation Comments ([Moonwave Style](https://github.com/evaera/moonwave) - supporting both `--- comment` and `--[=[ comment ]=]`, but must be next to statement)
- [x] Code Actions
- [ ] Call Hierarchy
- [ ] Workspace Symbols

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

- [ ] Go To Declaration (do not apply)
- [ ] Go To Implementation (do not apply)
- [ ] Code Actions (not necessary - could potentially add "fixers" for lints)
- [ ] Code Lens (not necessary)
- [ ] Document Highlight (not necessary - editor highlighting is sufficient)
- [ ] Folding Range (not necessary - editor folding is sufficient)
Expand Down
10 changes: 10 additions & 0 deletions src/LanguageServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ lsp::ServerCapabilities LanguageServer::getServerCapabilities()
capabilities.colorProvider = true;
// Document Link Provider
capabilities.documentLinkProvider = {false};
// Code Action Provider
capabilities.codeActionProvider = {std::vector<lsp::CodeActionKind>{lsp::CodeActionKind::SourceOrganizeImports}, /* resolveProvider: */ false};
// Rename Provider
capabilities.renameProvider = true;
// Inlay Hint Provider
Expand Down Expand Up @@ -164,6 +166,14 @@ void LanguageServer::onRequest(const id_type& id, const std::string& method, std
{
response = documentSymbol(REQUIRED_PARAMS(params, "textDocument/documentSymbol"));
}
else if (method == "textDocument/codeAction")
{
response = codeAction(REQUIRED_PARAMS(params, "textDocument/codeAction"));
}
// else if (method == "codeAction/resolve")
// {
// response = codeActionResolve(REQUIRED_PARAMS(params, "codeAction/resolve"));
// }
else if (method == "textDocument/semanticTokens/full")
{
response = semanticTokens(REQUIRED_PARAMS(params, "textDocument/semanticTokns/full"));
Expand Down
27 changes: 27 additions & 0 deletions src/LuauExt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1051,4 +1051,31 @@ std::optional<Luau::Location> getLocation(Luau::TypeId type)
}

return std::nullopt;
}

bool isGetService(const Luau::AstExpr* expr)
{
if (auto call = expr->as<Luau::AstExprCall>())
if (auto index = call->func->as<Luau::AstExprIndexName>())
if (index->index == "GetService")
if (auto name = index->expr->as<Luau::AstExprGlobal>())
if (name->name == "game")
return true;

return false;
}

bool isRequire(const Luau::AstExpr* expr)
{
if (auto call = expr->as<Luau::AstExprCall>())
{
if (auto funcAsGlobal = call->func->as<Luau::AstExprGlobal>(); funcAsGlobal && funcAsGlobal->name == "require")
return true;
}
else if (auto assertion = expr->as<Luau::AstExprTypeAssertion>())
{
return isRequire(assertion->expr);
}

return false;
}
1 change: 1 addition & 0 deletions src/include/LSP/LanguageServer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ class LanguageServer
std::vector<lsp::DocumentLink> documentLink(const lsp::DocumentLinkParams& params);
lsp::DocumentColorResult documentColor(const lsp::DocumentColorParams& params);
lsp::ColorPresentationResult colorPresentation(const lsp::ColorPresentationParams& params);
lsp::CodeActionResult codeAction(const lsp::CodeActionParams& params);

std::optional<lsp::Hover> hover(const lsp::HoverParams& params);
std::optional<lsp::SignatureHelp> signatureHelp(const lsp::SignatureHelpParams& params);
Expand Down
44 changes: 43 additions & 1 deletion src/include/LSP/LuauExt.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,46 @@ lsp::Position toUTF16(const TextDocument* textDocument, const Luau::Position& po

lsp::Diagnostic createTypeErrorDiagnostic(const Luau::TypeError& error, Luau::FileResolver* fileResolver, const TextDocument* textDocument = nullptr);
lsp::Diagnostic createLintDiagnostic(const Luau::LintWarning& lint, const TextDocument* textDocument = nullptr);
lsp::Diagnostic createParseErrorDiagnostic(const Luau::ParseError& error, const TextDocument* textDocument = nullptr);
lsp::Diagnostic createParseErrorDiagnostic(const Luau::ParseError& error, const TextDocument* textDocument = nullptr);

bool isGetService(const Luau::AstExpr* expr);
bool isRequire(const Luau::AstExpr* expr);

struct FindServicesVisitor : public Luau::AstVisitor
{
std::optional<size_t> firstServiceDefinitionLine = std::nullopt;
std::map<std::string, Luau::AstStatLocal*> serviceLineMap{};

bool visit(Luau::AstStatLocal* local) override
{
if (local->vars.size != 1 || local->values.size != 1)
return false;

auto localName = local->vars.data[0];
auto expr = local->values.data[0];

if (!localName || !expr)
return false;

auto line = localName->location.begin.line;

if (isGetService(expr))
{
firstServiceDefinitionLine =
!firstServiceDefinitionLine.has_value() || firstServiceDefinitionLine.value() >= line ? line : firstServiceDefinitionLine.value();
serviceLineMap.emplace(std::string(localName->name.value), local);
}

return false;
}

bool visit(Luau::AstStatBlock* block) override
{
for (Luau::AstStat* stat : block->body)
{
stat->visit(this);
}

return false;
}
};
8 changes: 8 additions & 0 deletions src/include/LSP/Utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
#include <filesystem>
#include <fstream>
#include <sstream>
#include <vector>
#include <unordered_map>
#include <algorithm>

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

template<typename V>
inline bool contains(const std::vector<V>& vec, const V& value)
{
return std::find(std::begin(vec), std::end(vec), value) != std::end(vec);
}

template<class K, class V>
inline bool contains(const std::unordered_map<K, V>& map, const K& value)
{
Expand Down
3 changes: 3 additions & 0 deletions src/include/LSP/Workspace.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ class WorkspaceFolder
void endAutocompletion(const lsp::CompletionParams& params);
void suggestImports(const Luau::ModuleName& moduleName, const Luau::Position& position, const ClientConfiguration& config,
std::vector<lsp::CompletionItem>& result);
lsp::WorkspaceEdit computeOrganiseRequiresEdit(const lsp::DocumentUri& uri);
lsp::WorkspaceEdit computeOrganiseServicesEdit(const lsp::DocumentUri& uri);

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

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

Expand Down
101 changes: 100 additions & 1 deletion src/include/Protocol/ClientCapabilities.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include "nlohmann/json.hpp"
#include "Protocol/Completion.hpp"
#include "Protocol/CodeAction.hpp"

namespace lsp
{
Expand Down Expand Up @@ -208,6 +209,99 @@ struct CompletionClientCapabilities
NLOHMANN_DEFINE_OPTIONAL(
CompletionClientCapabilities, dynamicRegistration, completionItem, completionItemKind, contextSupport, insertTextMode, completionList);

struct CodeActionClientCapabilities
{
/**
* Whether code action supports dynamic registration.
*/
bool dynamicRegistration = false;

struct CodeActionLiteralSupport
{
struct CodeActionKindLiteralSupport
{
/**
* The code action kind values the client supports. When this
* property exists the client also guarantees that it will
* handle values outside its set gracefully and falls back
* to a default value when unknown.
*/
std::vector<CodeActionKind> valueSet;
};

/**
* The code action kind is supported with the following value
* set.
*/
CodeActionKindLiteralSupport codeActionKind;
};

/**
* The client supports code action literals as a valid
* response of the `textDocument/codeAction` request.
*
* @since 3.8.0
*/
std::optional<CodeActionLiteralSupport> codeActionLiteralSupport;


/**
* Whether code action supports the `isPreferred` property.
*
* @since 3.15.0
*/
bool isPreferredSupport = false;

/**
* Whether code action supports the `disabled` property.
*
* @since 3.16.0
*/
bool disabledSupport = false;

/**
* Whether code action supports the `data` property which is
* preserved between a `textDocument/codeAction` and a
* `codeAction/resolve` request.
*
* @since 3.16.0
*/
bool dataSupport = false;


struct CodeActionResolveSupport
{
/**
* The properties that a client can resolve lazily.
*/
std::vector<std::string> properties;
};

/**
* Whether the client supports resolving additional code action
* properties via a separate `codeAction/resolve` request.
*
* @since 3.16.0
*/
std::optional<CodeActionResolveSupport> resolveSupport = std::nullopt;

/**
* Whether the client honors the change annotations in
* text edits and resource operations returned via the
* `CodeAction#edit` property by for example presenting
* the workspace edit in the user interface and asking
* for confirmation.
*
* @since 3.16.0
*/
bool honorsChangeAnnotations = false;
};
NLOHMANN_DEFINE_OPTIONAL(CodeActionClientCapabilities::CodeActionLiteralSupport::CodeActionKindLiteralSupport, valueSet);
NLOHMANN_DEFINE_OPTIONAL(CodeActionClientCapabilities::CodeActionLiteralSupport, codeActionKind);
NLOHMANN_DEFINE_OPTIONAL(CodeActionClientCapabilities::CodeActionResolveSupport, properties);
NLOHMANN_DEFINE_OPTIONAL(CodeActionClientCapabilities, dynamicRegistration, codeActionLiteralSupport, isPreferredSupport, disabledSupport,
dataSupport, resolveSupport, honorsChangeAnnotations);

struct TextDocumentClientCapabilities
{
/**
Expand All @@ -216,8 +310,13 @@ struct TextDocumentClientCapabilities
std::optional<CompletionClientCapabilities> completion = std::nullopt;

std::optional<DiagnosticClientCapabilities> diagnostic = std::nullopt;

/**
* Capabilities specific to the `textDocument/codeAction` request.
*/
std::optional<CodeActionClientCapabilities> codeAction = std::nullopt;
};
NLOHMANN_DEFINE_OPTIONAL(TextDocumentClientCapabilities, completion, diagnostic);
NLOHMANN_DEFINE_OPTIONAL(TextDocumentClientCapabilities, completion, diagnostic, codeAction);

struct DidChangeConfigurationClientCapabilities
{
Expand Down
Loading

0 comments on commit d0295c3

Please sign in to comment.