Pass string content to autocomplete callback (#800)

Closes #718 

We pass an extra `contents` parameter to the string callback function to
provide contextual information for autocomplete.

Because we support both string literals and simple interpolated strings,
we pass it through as a `std::string` value.

This is a breaking change
This commit is contained in:
JohnnyMorganz 2023-01-11 16:28:11 +00:00 committed by GitHub
parent be52bd91e4
commit 86494918f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 54 additions and 6 deletions

View File

@ -89,7 +89,8 @@ struct AutocompleteResult
}; };
using ModuleName = std::string; using ModuleName = std::string;
using StringCompletionCallback = std::function<std::optional<AutocompleteEntryMap>(std::string tag, std::optional<const ClassType*> ctx)>; using StringCompletionCallback =
std::function<std::optional<AutocompleteEntryMap>(std::string tag, std::optional<const ClassType*> ctx, std::optional<std::string> contents)>;
AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName, Position position, StringCompletionCallback callback); AutocompleteResult autocomplete(Frontend& frontend, const ModuleName& moduleName, Position position, StringCompletionCallback callback);

View File

@ -1262,6 +1262,23 @@ static bool isSimpleInterpolatedString(const AstNode* node)
return interpString != nullptr && interpString->expressions.size == 0; return interpString != nullptr && interpString->expressions.size == 0;
} }
static std::optional<std::string> getStringContents(const AstNode* node)
{
if (const AstExprConstantString* string = node->as<AstExprConstantString>())
{
return std::string(string->value.data, string->value.size);
}
else if (const AstExprInterpString* interpString = node->as<AstExprInterpString>(); interpString && interpString->expressions.size == 0)
{
LUAU_ASSERT(interpString->strings.size == 1);
return std::string(interpString->strings.data->data, interpString->strings.data->size);
}
else
{
return std::nullopt;
}
}
static std::optional<AutocompleteEntryMap> autocompleteStringParams(const SourceModule& sourceModule, const ModulePtr& module, static std::optional<AutocompleteEntryMap> autocompleteStringParams(const SourceModule& sourceModule, const ModulePtr& module,
const std::vector<AstNode*>& nodes, Position position, StringCompletionCallback callback) const std::vector<AstNode*>& nodes, Position position, StringCompletionCallback callback)
{ {
@ -1294,10 +1311,13 @@ static std::optional<AutocompleteEntryMap> autocompleteStringParams(const Source
return std::nullopt; return std::nullopt;
} }
auto performCallback = [&](const FunctionType* funcType) -> std::optional<AutocompleteEntryMap> { std::optional<std::string> candidateString = getStringContents(nodes.back());
auto performCallback = [&](const FunctionType* funcType) -> std::optional<AutocompleteEntryMap>
{
for (const std::string& tag : funcType->tags) for (const std::string& tag : funcType->tags)
{ {
if (std::optional<AutocompleteEntryMap> ret = callback(tag, getMethodContainingClass(module, candidate->func))) if (std::optional<AutocompleteEntryMap> ret = callback(tag, getMethodContainingClass(module, candidate->func), candidateString))
{ {
return ret; return ret;
} }

View File

@ -18,7 +18,7 @@ LUAU_FASTFLAG(LuauSetMetatableDoesNotTimeTravel)
using namespace Luau; using namespace Luau;
static std::optional<AutocompleteEntryMap> nullCallback(std::string tag, std::optional<const ClassType*> ptr) static std::optional<AutocompleteEntryMap> nullCallback(std::string tag, std::optional<const ClassType*> ptr, std::optional<std::string> contents)
{ {
return std::nullopt; return std::nullopt;
} }
@ -36,9 +36,9 @@ struct ACFixtureImpl : BaseType
return Luau::autocomplete(this->frontend, "MainModule", Position{row, column}, nullCallback); return Luau::autocomplete(this->frontend, "MainModule", Position{row, column}, nullCallback);
} }
AutocompleteResult autocomplete(char marker) AutocompleteResult autocomplete(char marker, StringCompletionCallback callback = nullCallback)
{ {
return Luau::autocomplete(this->frontend, "MainModule", getPosition(marker), nullCallback); return Luau::autocomplete(this->frontend, "MainModule", getPosition(marker), callback);
} }
CheckResult check(const std::string& source) CheckResult check(const std::string& source)
@ -3363,4 +3363,31 @@ TEST_CASE_FIXTURE(ACFixture, "type_reduction_is_hooked_up_to_autocomplete")
// CHECK("{| x: nil |}" == toString(*ty2, opts)); // CHECK("{| x: nil |}" == toString(*ty2, opts));
} }
TEST_CASE_FIXTURE(ACFixture, "string_contents_is_available_to_callback")
{
loadDefinition(R"(
declare function require(path: string): any
)");
std::optional<Binding> require = frontend.typeCheckerForAutocomplete.globalScope->linearSearchForBinding("require");
REQUIRE(require);
Luau::unfreeze(frontend.typeCheckerForAutocomplete.globalTypes);
attachTag(require->typeId, "RequireCall");
Luau::freeze(frontend.typeCheckerForAutocomplete.globalTypes);
check(R"(
local x = require("testing/@1")
)");
bool isCorrect = false;
auto ac1 = autocomplete('1',
[&isCorrect](std::string, std::optional<const ClassType*>, std::optional<std::string> contents) -> std::optional<AutocompleteEntryMap>
{
isCorrect = contents.has_value() && contents.value() == "testing/";
return std::nullopt;
});
CHECK(isCorrect);
}
TEST_SUITE_END(); TEST_SUITE_END();