Skip to content

Commit bdb3150

Browse files
Support inline documentation in definition files (#518)
Support inline documentation in definitions files
1 parent 35620ec commit bdb3150

File tree

7 files changed

+260
-23
lines changed

7 files changed

+260
-23
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
1010

1111
- The server now accepts DataModel information from the Studio Plugin even when there is no sourcemap present ([#1216](https://github.com/JohnnyMorganz/luau-lsp/pull/1216))
1212
- Added `luau-lsp.completion.imports.includedServices` and `luau-lsp.completion.imports.excludedServices` to configure what services show up during auto-importing. If non-empty, only the services listed in `includedServices` will show up. None of the services listed in `excludedServices` will ever show up.
13+
- Added support for inline documentation comments in global type definition files (relies on name being provided in settings, see below) ([#271](https://github.com/JohnnyMorganz/luau-lsp/issues/271))
1314

1415
### Changed
1516

src/DocumentationParser.cpp

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,26 @@ struct AttachCommentsVisitor : public Luau::AstVisitor
306306
return false;
307307
}
308308

309+
bool visit(Luau::AstStatDeclareExternType* klass) override
310+
{
311+
if (klass->location.begin >= pos)
312+
return false;
313+
if (klass->location.begin > closestPreviousNode)
314+
closestPreviousNode = klass->location.begin;
315+
316+
for (const auto& item : klass->props)
317+
{
318+
if (item.ty->location.begin >= pos)
319+
continue;
320+
closestPreviousNode = std::max(closestPreviousNode, item.ty->location.begin);
321+
item.ty->visit(this);
322+
if (item.ty->location.end <= pos)
323+
closestPreviousNode = std::max(closestPreviousNode, item.ty->location.end);
324+
}
325+
326+
return false;
327+
}
328+
309329
bool visit(Luau::AstStatBlock* block) override
310330
{
311331
// If the position is after the block, then it can be ignored
@@ -353,19 +373,29 @@ std::vector<Luau::Comment> getCommentLocations(const Luau::SourceModule* module,
353373
/// Performs transformations so that the comments are normalised to lines inside of it (i.e., trimming whitespace, removing comment start/end)
354374
std::vector<std::string> WorkspaceFolder::getComments(const Luau::ModuleName& moduleName, const Luau::Location& node)
355375
{
356-
auto sourceModule = frontend.getSourceModule(moduleName);
357-
if (!sourceModule)
358-
return {};
376+
Luau::SourceModule* sourceModule;
377+
TextDocumentPtr textDocument{nullptr};
378+
379+
if (auto it = definitionsSourceModules.find(moduleName); it != definitionsSourceModules.end())
380+
{
381+
sourceModule = &it->second.second;
382+
textDocument = TextDocumentPtr(&it->second.first);
383+
}
384+
else
385+
{
386+
sourceModule = frontend.getSourceModule(moduleName);
387+
if (!sourceModule)
388+
return {};
389+
390+
textDocument = fileResolver.getOrCreateTextDocumentFromModuleName(moduleName);
391+
if (!textDocument)
392+
return {};
393+
}
359394

360395
auto commentLocations = getCommentLocations(sourceModule, node);
361396
if (commentLocations.empty())
362397
return {};
363398

364-
// Get relevant text document
365-
auto textDocument = fileResolver.getOrCreateTextDocumentFromModuleName(moduleName);
366-
if (!textDocument)
367-
return {};
368-
369399
std::vector<std::string> comments{};
370400
for (auto& comment : commentLocations)
371401
{
@@ -447,6 +477,10 @@ std::optional<std::string> WorkspaceFolder::getDocumentationForType(const Luau::
447477
{
448478
return printMoonwaveDocumentation(getComments(ttv->definitionModuleName, ttv->definitionLocation));
449479
}
480+
else if (auto etv = Luau::get<Luau::ExternType>(followedTy); etv && !etv->definitionModuleName.empty() && etv->definitionLocation)
481+
{
482+
return printMoonwaveDocumentation(getComments(etv->definitionModuleName, *etv->definitionLocation));
483+
}
450484
return std::nullopt;
451485
}
452486

src/LuauExt.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ std::optional<nlohmann::json> parseDefinitionsFileMetadata(const std::string& de
5656
Luau::LoadDefinitionFileResult registerDefinitions(
5757
Luau::Frontend& frontend, Luau::GlobalTypes& globals, const std::string& packageName, const std::string& definitions)
5858
{
59-
return frontend.loadDefinitionFile(globals, globals.globalScope, definitions, packageName, /* captureComments = */ false);
59+
return frontend.loadDefinitionFile(globals, globals.globalScope, definitions, packageName, /* captureComments = */ true);
6060
}
6161

6262
using NameOrExpr = std::variant<std::string, Luau::AstExpr*>;

src/Workspace.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,8 @@ void WorkspaceFolder::registerTypes(const std::vector<std::string>& disabledGlob
521521
{
522522
// Clear any set diagnostics
523523
client->publishDiagnostics({uri, std::nullopt, {}});
524+
TextDocument textDocument(Uri::file(resolvedFilePath), "luau", 0, *definitionsContents);
525+
definitionsSourceModules.emplace(packageName, std::make_pair(textDocument, result.sourceModule));
524526
}
525527
else
526528
{

src/include/LSP/Workspace.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ class WorkspaceFolder
5050
/// When a new request comes in and the workspace is not ready, we will prepare it then.
5151
bool isReady = false;
5252

53+
private:
54+
/// Mapping between a definitions package name to the TextDocument / SourceModule that contains this definitions.
55+
/// Used for documentation comment lookup within definition files.
56+
std::unordered_map<std::string, std::pair<TextDocument, Luau::SourceModule>> definitionsSourceModules{};
57+
5358
public:
5459
WorkspaceFolder(Client* client, std::string name, const lsp::DocumentUri& uri, std::optional<Luau::Config> defaultConfig)
5560
: client(client)

tests/Hover.test.cpp

Lines changed: 189 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -323,13 +323,9 @@ TEST_CASE_FIXTURE(Fixture, "includes_documentation_for_type_alias_declarations")
323323

324324
auto result = workspace.hover(params, nullptr);
325325
REQUIRE(result);
326-
CHECK_EQ(
327-
result->contents.value,
328-
codeBlock("luau", "type Meters = number") +
329-
kDocumentationBreaker +
330-
"The metre (or meter in [US spelling]; symbol: m) is the [base unit] of [length]\n" +
331-
"in the [International System of Units] (SI)\n"
332-
);
326+
CHECK_EQ(result->contents.value, codeBlock("luau", "type Meters = number") + kDocumentationBreaker +
327+
"The metre (or meter in [US spelling]; symbol: m) is the [base unit] of [length]\n" +
328+
"in the [International System of Units] (SI)\n");
333329
}
334330

335331
TEST_CASE_FIXTURE(Fixture, "includes_documentation_for_type_alias_declarations_of_intersected_tables")
@@ -356,13 +352,9 @@ TEST_CASE_FIXTURE(Fixture, "includes_documentation_for_type_alias_declarations_o
356352

357353
auto result = workspace.hover(params, nullptr);
358354
REQUIRE(result);
359-
CHECK_EQ(
360-
result->contents.value,
361-
codeBlock("luau", "type Foobar = {\n bar: \"Bar\"\n} & {\n foo: \"Foo\"\n}") +
362-
kDocumentationBreaker +
363-
"The terms foobar (/ˈfuːbɑːr/), foo, bar, baz, qux, quux, and others are used as\n" +
364-
"metasyntactic variables and placeholder names in computer programming or computer-related documentation\n"
365-
);
355+
CHECK_EQ(result->contents.value, codeBlock("luau", "type Foobar = {\n bar: \"Bar\"\n} & {\n foo: \"Foo\"\n}") + kDocumentationBreaker +
356+
"The terms foobar (/ˈfuːbɑːr/), foo, bar, baz, qux, quux, and others are used as\n" +
357+
"metasyntactic variables and placeholder names in computer programming or computer-related documentation\n");
366358
}
367359

368360
TEST_CASE_FIXTURE(Fixture, "includes_documentation_for_type_references")
@@ -417,6 +409,189 @@ TEST_CASE_FIXTURE(Fixture, "includes_documentation_for_external_type_references"
417409
CHECK_EQ(result->contents.value, codeBlock("luau", "type Types.Value = string") + kDocumentationBreaker + "This is a type\n");
418410
}
419411

412+
TEST_CASE_FIXTURE(Fixture, "show_type_of_global_variable")
413+
{
414+
auto source = R"(
415+
print(DocumentedGlobalVariable)
416+
)";
417+
418+
auto uri = newDocument("foo.luau", source);
419+
420+
lsp::HoverParams params;
421+
params.textDocument = lsp::TextDocumentIdentifier{uri};
422+
params.position = lsp::Position{1, 23};
423+
424+
auto result = workspace.hover(params, nullptr);
425+
REQUIRE(result);
426+
CHECK_EQ(result->contents.value, codeBlock("luau", "type DocumentedGlobalVariable = number"));
427+
}
428+
429+
TEST_CASE_FIXTURE(Fixture, "includes_documentation_for_a_global_type_table_from_definitions_file")
430+
{
431+
auto source = R"(
432+
local x: DocumentedTable = nil
433+
)";
434+
435+
auto uri = newDocument("foo.luau", source);
436+
437+
lsp::HoverParams params;
438+
params.textDocument = lsp::TextDocumentIdentifier{uri};
439+
params.position = lsp::Position{1, 24};
440+
441+
auto result = workspace.hover(params, nullptr);
442+
REQUIRE(result);
443+
CHECK_EQ(result->contents.value, codeBlock("luau", "type DocumentedTable = {\n"
444+
" member1: string\n"
445+
"}") +
446+
kDocumentationBreaker + "This is a documented table\n");
447+
}
448+
449+
TEST_CASE_FIXTURE(Fixture, "includes_documentation_for_a_global_type_table_from_definitions_file_when_hovering_over_variable_with_type")
450+
{
451+
auto source = R"(
452+
local x: DocumentedTable = nil
453+
)";
454+
455+
auto uri = newDocument("foo.luau", source);
456+
457+
lsp::HoverParams params;
458+
params.textDocument = lsp::TextDocumentIdentifier{uri};
459+
params.position = lsp::Position{1, 14};
460+
461+
auto result = workspace.hover(params, nullptr);
462+
REQUIRE(result);
463+
CHECK_EQ(result->contents.value, codeBlock("luau", "local x: {\n"
464+
" member1: string\n"
465+
"}") +
466+
kDocumentationBreaker + "This is a documented table\n");
467+
}
468+
469+
TEST_CASE_FIXTURE(Fixture, "includes_documentation_for_a_global_type_table_from_definitions_file_when_hovering_over_property")
470+
{
471+
auto source = R"(
472+
local x: DocumentedTable = nil
473+
local y = x.member1
474+
)";
475+
476+
auto uri = newDocument("foo.luau", source);
477+
478+
lsp::HoverParams params;
479+
params.textDocument = lsp::TextDocumentIdentifier{uri};
480+
params.position = lsp::Position{2, 23};
481+
482+
auto result = workspace.hover(params, nullptr);
483+
REQUIRE(result);
484+
CHECK_EQ(result->contents.value, codeBlock("luau", "string") + kDocumentationBreaker + "This is documented member1 of the table\n");
485+
}
486+
487+
TEST_CASE_FIXTURE(Fixture, "includes_documentation_for_a_global_function_call_from_definitions_file")
488+
{
489+
auto source = R"(
490+
DocumentedGlobalFunction()
491+
)";
492+
493+
auto uri = newDocument("foo.luau", source);
494+
495+
lsp::HoverParams params;
496+
params.textDocument = lsp::TextDocumentIdentifier{uri};
497+
params.position = lsp::Position{1, 20};
498+
499+
auto result = workspace.hover(params, nullptr);
500+
REQUIRE(result);
501+
CHECK_EQ(result->contents.value,
502+
codeBlock("luau", "function DocumentedGlobalFunction(): number") + kDocumentationBreaker + "This is a documented global function\n");
503+
}
504+
505+
TEST_CASE_FIXTURE(Fixture, "includes_documentation_when_hovering_over_class_type_from_definitions_file")
506+
{
507+
auto source = R"(
508+
local x: DocumentedClass
509+
)";
510+
511+
auto uri = newDocument("foo.luau", source);
512+
513+
lsp::HoverParams params;
514+
params.textDocument = lsp::TextDocumentIdentifier{uri};
515+
params.position = lsp::Position{1, 23};
516+
517+
auto result = workspace.hover(params, nullptr);
518+
REQUIRE(result);
519+
CHECK_EQ(
520+
result->contents.value, codeBlock("luau", "type DocumentedClass = DocumentedClass") + kDocumentationBreaker + "This is a documented class\n");
521+
}
522+
523+
TEST_CASE_FIXTURE(Fixture, "includes_documentation_when_hovering_over_variable_with_class_type")
524+
{
525+
auto source = R"(
526+
local x: DocumentedClass
527+
)";
528+
529+
auto uri = newDocument("foo.luau", source);
530+
531+
lsp::HoverParams params;
532+
params.textDocument = lsp::TextDocumentIdentifier{uri};
533+
params.position = lsp::Position{1, 14};
534+
535+
auto result = workspace.hover(params, nullptr);
536+
REQUIRE(result);
537+
CHECK_EQ(result->contents.value, codeBlock("luau", "local x: DocumentedClass") + kDocumentationBreaker + "This is a documented class\n");
538+
}
539+
540+
TEST_CASE_FIXTURE(Fixture, "includes_documentation_when_hovering_over_class_type_property")
541+
{
542+
auto source = R"(
543+
local x: DocumentedClass
544+
local y = x.member1
545+
)";
546+
547+
auto uri = newDocument("foo.luau", source);
548+
549+
lsp::HoverParams params;
550+
params.textDocument = lsp::TextDocumentIdentifier{uri};
551+
params.position = lsp::Position{2, 23};
552+
553+
auto result = workspace.hover(params, nullptr);
554+
REQUIRE(result);
555+
CHECK_EQ(result->contents.value, codeBlock("luau", "string") + kDocumentationBreaker + "This is a documented member1 of the class\n");
556+
}
557+
558+
TEST_CASE_FIXTURE(Fixture, "includes_documentation_when_hovering_over_class_type_method_call")
559+
{
560+
auto source = R"(
561+
local x: DocumentedClass
562+
local y = x:function1()
563+
)";
564+
565+
auto uri = newDocument("foo.luau", source);
566+
567+
lsp::HoverParams params;
568+
params.textDocument = lsp::TextDocumentIdentifier{uri};
569+
params.position = lsp::Position{2, 23};
570+
571+
auto result = workspace.hover(params, nullptr);
572+
REQUIRE(result);
573+
CHECK_EQ(result->contents.value,
574+
codeBlock("luau", "function DocumentedClass:function1(): number") + kDocumentationBreaker + "This is a documented function1 of the class\n");
575+
}
576+
577+
// TEST_CASE_FIXTURE(Fixture, "includes_documentation_when_hovering_over_global_variable_from_definitions_file")
578+
//{
579+
// auto source = R"(
580+
// print(DocumentedGlobalVariable)
581+
// )";
582+
//
583+
// auto uri = newDocument("foo.luau", source);
584+
//
585+
// lsp::HoverParams params;
586+
// params.textDocument = lsp::TextDocumentIdentifier{uri};
587+
// params.position = lsp::Position{1, 23};
588+
//
589+
// auto result = workspace.hover(params, nullptr);
590+
// REQUIRE(result);
591+
// CHECK_EQ(result->contents.value,
592+
// codeBlock("luau", "type DocumentedGlobalVariable = number") + kDocumentationBreaker + "This is a documented global variable\n");
593+
// }
594+
420595
TEST_CASE_FIXTURE(Fixture, "handles_type_references_without_types_graph")
421596
{
422597
auto source = newDocument("types.luau", R"(

tests/testdata/standard_definitions.d.luau

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,23 @@ declare Instance: {
101101
}
102102

103103
declare game: DataModel
104+
105+
--- This is a documented table
106+
export type DocumentedTable = {
107+
--- This is documented member1 of the table
108+
member1: string,
109+
}
110+
111+
--- This is a documented class
112+
declare class DocumentedClass
113+
--- This is a documented member1 of the class
114+
member1: string
115+
--- This is a documented function1 of the class
116+
function function1(self): number
117+
end
118+
119+
--- This is a documented global function
120+
declare function DocumentedGlobalFunction(): number
121+
122+
--- This is a documented global variable
123+
declare DocumentedGlobalVariable: number

0 commit comments

Comments
 (0)