Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

- Sync to upstream Luau 0.698
- Auto-imports are now enabled by default. Configure `luau-lsp.completion.imports.enabled` if you wish to disable it ([#619](https://github.com/JohnnyMorganz/luau-lsp/issues/619))
- Definitions files must now provide a name for the file in settings and command line: i.e.
`--definitions:@roblox=path/to/globalTypes.d.luau`. This is to support mapping global types back to their original
definitions file for documentation features. Please update your LSP settings (`luau-lsp.types.definitionFiles`) and command line arguments.
Backwards compatibility has been temporarily preserved, with random names generated.
- Table properties are now prioritised above other autocomplete entries (again, fixing a dormant bug)
- Contextual keywords (`else` / `elseif` / `end`) are prioritised over other autocomplete entries when inside of the relevant statement

Expand Down
5 changes: 3 additions & 2 deletions editors/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ $ luau-lsp --help

## Configuring Definitions and Documentation

You can add in built-in definitions by passing the `--definitions=PATH` argument.
You can add in built-in definitions by passing the `--definitions:@name=PATH` argument.
The `name` should be a unique reference to the definitions file.
This can be done multiple times:

```sh
$ luau-lsp lsp --definitions=/path/to/globalTypes.d.luau
$ luau-lsp lsp --definition:@roblox=/path/to/globalTypes.d.luau
```

> NOTE: Definitions file syntax is unstable and undocumented. It may change at any time
Expand Down
6 changes: 3 additions & 3 deletions editors/code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -328,10 +328,10 @@
"scope": "resource"
},
"luau-lsp.types.definitionFiles": {
"markdownDescription": "A list of paths to definition files to load in to the type checker. Note that definition file syntax is currently unstable and may change at any time",
"type": "array",
"markdownDescription": "A mapping of package names to paths of definition files to load in to the type checker. Note that definition file syntax is currently unstable and may change at any time",
"type": "object",
"default": [],
"items": {
"additionalProperties": {
"type": "string"
},
"scope": "window"
Expand Down
17 changes: 13 additions & 4 deletions editors/code/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,9 +176,18 @@ const startLanguageServer = async (context: vscode.ExtensionContext) => {
const typesConfig = vscode.workspace.getConfiguration("luau-lsp.types");

// Load extra type definitions
const definitionFiles = typesConfig.get<string[]>("definitionFiles");
// TODO: deprecate and remove support of array-based definitionFiles configuration
let definitionFiles = typesConfig.get<
{ [packageName: string]: string } | string[]
>("definitionFiles");
if (definitionFiles) {
for (let definitionPath of definitionFiles) {
if (Array.isArray(definitionFiles)) {
definitionFiles = Object.fromEntries(
definitionFiles.map((path, index) => ["roblox" + index, path]),
);
}

for (let [packageName, definitionPath] of Object.entries(definitionFiles)) {
definitionPath = utils.resolvePath(definitionPath);
let uri;
if (vscode.workspace.workspaceFolders) {
Expand All @@ -190,10 +199,10 @@ const startLanguageServer = async (context: vscode.ExtensionContext) => {
uri = vscode.Uri.file(definitionPath);
}
if (await utils.exists(uri)) {
addArg(`--definitions=${uri.fsPath}`);
addArg(`--definitions:${packageName}=${uri.fsPath}`);
} else {
vscode.window.showWarningMessage(
`Definitions file at ${definitionPath} does not exist, types will not be provided from this file`,
`Definitions file '${packageName}' at ${definitionPath} does not exist, types will not be provided from this file`,
);
}
}
Expand Down
4 changes: 2 additions & 2 deletions editors/code/src/roblox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -603,11 +603,11 @@ export const preLanguageServerStart = async (
typesConfig.get<string>("robloxSecurityLevel") ?? "PluginSecurity";
await updateApiInfo(context);
addArg(
`--definitions=${globalTypesUri(context, securityLevel, "Prod").fsPath}`,
`--definitions:@roblox=${globalTypesUri(context, securityLevel, "Prod").fsPath}`,
"Prod",
);
addArg(
`--definitions=${globalTypesUri(context, securityLevel, "Debug").fsPath}`,
`--definitions:@roblox=${globalTypesUri(context, securityLevel, "Debug").fsPath}`,
"Debug",
);
addArg(`--docs=${apiDocsUri(context).fsPath}`);
Expand Down
45 changes: 39 additions & 6 deletions src/AnalyzeCli.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,8 @@ std::vector<std::string> getFilesToAnalyze(const std::vector<std::string>& paths
return files;
}

void applySettings(
const std::string& settingsContents, CliClient& client, std::vector<std::string>& ignoreGlobPatterns, std::vector<std::string>& definitionsPaths)
void applySettings(const std::string& settingsContents, CliClient& client, std::vector<std::string>& ignoreGlobPatterns,
std::unordered_map<std::string, std::string>& definitionsPaths)
{
client.configuration = dottedToClientConfiguration(settingsContents);

Expand All @@ -194,7 +194,7 @@ void applySettings(
ignoreGlobPatterns.reserve(ignoreGlobPatterns.size() + ignoreGlobsConfiguration.size());
ignoreGlobPatterns.insert(ignoreGlobPatterns.end(), ignoreGlobsConfiguration.cbegin(), ignoreGlobsConfiguration.cend());
definitionsPaths.reserve(definitionsPaths.size() + definitionsFilesConfiguration.size());
definitionsPaths.insert(definitionsPaths.end(), definitionsFilesConfiguration.cbegin(), definitionsFilesConfiguration.cend());
definitionsPaths.insert(definitionsFilesConfiguration.begin(), definitionsFilesConfiguration.end());

// Process any fflags
registerFastFlagsCLI(client.configuration.fflags.override);
Expand All @@ -206,12 +206,45 @@ void applySettings(
"Please manually configure necessary FFlags\n";
}

std::unordered_map<std::string, std::string> processDefinitionsFilePaths(const argparse::ArgumentParser& program)
{
std::unordered_map<std::string, std::string> definitionsFiles{};
size_t backwardsCompatibilityNameSuffix = 0;
for (const auto& definition : program.get<std::vector<std::string>>("--definitions"))
{
std::string packageName = definition;
std::string filePath = definition;

size_t eqIndex = definition.find('=');
if (eqIndex == std::string::npos)
{
// TODO: Remove Me - backwards compatibility
packageName = "@roblox";
if (backwardsCompatibilityNameSuffix > 0)
packageName += std::to_string(backwardsCompatibilityNameSuffix);
backwardsCompatibilityNameSuffix += 1;
}
else
{
packageName = definition.substr(0, eqIndex);
filePath = definition.substr(eqIndex + 1, definition.length());
}

if (!Luau::startsWith(packageName, "@"))
packageName = "@" + packageName;

definitionsFiles.emplace(packageName, filePath);
}

return definitionsFiles;
}

int startAnalyze(const argparse::ArgumentParser& program)
{
ReportFormat format = ReportFormat::Default;
bool annotate = program.is_used("--annotate");
auto sourcemapPath = program.present<std::string>("--sourcemap");
auto definitionsPaths = program.get<std::vector<std::string>>("--definitions");
auto definitionsPaths = processDefinitionsFilePaths(program);
auto ignoreGlobPatterns = program.get<std::vector<std::string>>("--ignore");
auto baseLuaurc = program.present<std::string>("--base-luaurc");
auto settingsPath = program.present<std::string>("--settings");
Expand Down Expand Up @@ -325,7 +358,7 @@ int startAnalyze(const argparse::ArgumentParser& program)
"definitions files; use `--platform=standard` to silence\n");
}

for (auto& definitionsPath : definitionsPaths)
for (const auto& [packageName, definitionsPath] : definitionsPaths)
{
auto uri = fileResolver.rootUri.resolvePath(definitionsPath);
if (!uri.exists())
Expand All @@ -341,7 +374,7 @@ int startAnalyze(const argparse::ArgumentParser& program)
return 1;
}

auto loadResult = types::registerDefinitions(frontend, frontend.globals, *definitionsContents);
auto loadResult = types::registerDefinitions(frontend, frontend.globals, packageName, *definitionsContents);
if (!loadResult.success)
{
fprintf(stderr, "Failed to load definitions\n");
Expand Down
6 changes: 3 additions & 3 deletions src/LuauExt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ std::optional<nlohmann::json> parseDefinitionsFileMetadata(const std::string& de
return std::nullopt;
}

Luau::LoadDefinitionFileResult registerDefinitions(Luau::Frontend& frontend, Luau::GlobalTypes& globals, const std::string& definitions)
Luau::LoadDefinitionFileResult registerDefinitions(
Luau::Frontend& frontend, Luau::GlobalTypes& globals, const std::string& packageName, const std::string& definitions)
{
// TODO: packageName shouldn't just be "@roblox"
return frontend.loadDefinitionFile(globals, globals.globalScope, definitions, "@roblox", /* captureComments = */ false);
return frontend.loadDefinitionFile(globals, globals.globalScope, definitions, packageName, /* captureComments = */ false);
}

using NameOrExpr = std::variant<std::string, Luau::AstExpr*>;
Expand Down
23 changes: 17 additions & 6 deletions src/Workspace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ void WorkspaceFolder::updateTextDocument(const lsp::DocumentUri& uri, const lsp:
!isIgnoredFile(dirtyUri, config))
{
auto dependencyDiags = documentDiagnostics(
lsp::DocumentDiagnosticParams{{dirtyUri}}, /* cancellationToken=*/ nullptr, /* allowUnmanagedFiles= */ true);
lsp::DocumentDiagnosticParams{{dirtyUri}}, /* cancellationToken=*/nullptr, /* allowUnmanagedFiles= */ true);
client->publishDiagnostics(lsp::PublishDiagnosticsParams{dirtyUri, std::nullopt, dependencyDiags.items});
}
}
Expand Down Expand Up @@ -283,7 +283,7 @@ bool WorkspaceFolder::isDefinitionFile(const Uri& path, const std::optional<Clie
{
auto config = givenConfig ? *givenConfig : client->getConfiguration(rootUri);

for (auto& file : config.types.definitionFiles)
for (auto& [_, file] : config.types.definitionFiles)
{
if (rootUri.resolvePath(resolvePath(file)) == path)
{
Expand Down Expand Up @@ -474,10 +474,21 @@ void WorkspaceFolder::registerTypes(const std::vector<std::string>& disabledGlob
if (client->definitionsFiles.empty())
client->sendLogMessage(lsp::MessageType::Warning, "No definitions file provided by client");

for (const auto& definitionsFile : client->definitionsFiles)
// For backwards compatibility, we need to keep an ordering where a definitions file for '@roblox' is always processed first
std::vector<std::pair<std::string, std::string>> definitionsFilesToProcess{};
definitionsFilesToProcess.reserve(client->definitionsFiles.size());
if (auto it = client->definitionsFiles.find("@roblox"); it != client->definitionsFiles.end())
definitionsFilesToProcess.emplace_back(*it);
for (const auto& pair : client->definitionsFiles)
{
if (pair.first != "@roblox")
definitionsFilesToProcess.emplace_back(pair);
}

for (const auto& [packageName, definitionsFile] : definitionsFilesToProcess)
{
auto resolvedFilePath = resolvePath(definitionsFile);
client->sendLogMessage(lsp::MessageType::Info, "Loading definitions file: " + resolvedFilePath);
client->sendLogMessage(lsp::MessageType::Info, "Loading definitions file: " + packageName + " - " + resolvedFilePath);

auto definitionsContents = Luau::FileUtils::readFile(resolvedFilePath);
if (!definitionsContents)
Expand All @@ -495,9 +506,9 @@ void WorkspaceFolder::registerTypes(const std::vector<std::string>& disabledGlob
client->sendTrace("workspace initialization: parsing definitions file metadata COMPLETED", json(definitionsFileMetadata).dump());

client->sendTrace("workspace initialization: registering types definition");
auto result = types::registerDefinitions(frontend, frontend.globals, *definitionsContents);
auto result = types::registerDefinitions(frontend, frontend.globals, packageName, *definitionsContents);
if (!FFlag::LuauSolverV2)
types::registerDefinitions(frontend, frontend.globalsForAutocomplete, *definitionsContents);
types::registerDefinitions(frontend, frontend.globalsForAutocomplete, packageName, *definitionsContents);
client->sendTrace("workspace initialization: registering types definition COMPLETED");

client->sendTrace("workspace: applying platform mutations on definitions");
Expand Down
6 changes: 4 additions & 2 deletions src/include/Analyze/AnalyzeCli.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

struct WorkspaceFileResolver;

std::unordered_map<std::string, std::string> processDefinitionsFilePaths(const argparse::ArgumentParser& program);

struct FilePathInformation
{
Uri uri;
Expand All @@ -18,6 +20,6 @@ struct FilePathInformation
FilePathInformation getFilePath(const WorkspaceFileResolver* fileResolver, const std::string& moduleName);

std::vector<std::string> getFilesToAnalyze(const std::vector<std::string>& paths, const std::vector<std::string>& ignoreGlobPatterns);
void applySettings(
const std::string& settingsContents, CliClient& client, std::vector<std::string>& ignoreGlobPatterns, std::vector<std::string>& definitionsPaths);
void applySettings(const std::string& settingsContents, CliClient& client, std::vector<std::string>& ignoreGlobPatterns,
std::unordered_map<std::string, std::string>& definitionsPaths);
int startAnalyze(const argparse::ArgumentParser& program);
2 changes: 1 addition & 1 deletion src/include/LSP/Client.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class Client : public BaseClient
lsp::ClientCapabilities capabilities;
lsp::TraceValue traceMode = lsp::TraceValue::Off;
/// A registered definitions file passed by the client
std::vector<std::string> definitionsFiles{};
std::unordered_map<std::string, std::string> definitionsFiles{};
/// A registered documentation file passed by the client
std::vector<std::string> documentationFiles{};
/// Parsed documentation database
Expand Down
32 changes: 30 additions & 2 deletions src/include/LSP/ClientConfiguration.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,39 @@ struct ClientTypesConfiguration
/// DEPRECATED: USE `platform.type` INSTEAD
bool roblox = true;
/// Any definition files to load globally
std::vector<std::string> definitionFiles{};
std::unordered_map<std::string, std::string> definitionFiles{};
/// A list of globals to remove from the global scope. Accepts full libraries or particular functions (e.g., `table` or `table.clone`)
std::vector<std::string> disabledGlobals{};
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(ClientTypesConfiguration, roblox, definitionFiles, disabledGlobals);

// TODO: ser/de defined manually to retain backwards compatibility of 'definitionsFiles' being an array
inline void to_json(nlohmann::json& nlohmann_json_j, const ClientTypesConfiguration& nlohmann_json_t)
{
NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, roblox, definitionFiles, disabledGlobals))
}
inline void from_json(const nlohmann::json& json, ClientTypesConfiguration& object)
{
if (json.contains("roblox"))
json["roblox"].get_to(object.roblox);
if (json.contains("disabledGlobals"))
json["disabledGlobals"].get_to(object.disabledGlobals);
if (json.contains("definitionFiles"))
{
if (json["definitionFiles"].is_object())
json["definitionFiles"].get_to(object.definitionFiles);
else
{
// Backwards compatibility, randomise the packageName of the definitionFiles
size_t backwardsCompatibilityNameSuffix = 1;
for (const auto& definition : json["definitionFiles"].get<std::vector<std::string>>())
{
std::string packageName = "@roblox" + std::to_string(backwardsCompatibilityNameSuffix);
object.definitionFiles.emplace(packageName, definition);
backwardsCompatibilityNameSuffix += 1;
}
}
}
}

enum struct InlayHintsParameterNamesConfig
{
Expand Down
3 changes: 2 additions & 1 deletion src/include/LSP/LuauExt.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ bool isMetamethod(const Luau::Name& name);

std::optional<nlohmann::json> parseDefinitionsFileMetadata(const std::string& definitions);

Luau::LoadDefinitionFileResult registerDefinitions(Luau::Frontend& frontend, Luau::GlobalTypes& globals, const std::string& definitions);
Luau::LoadDefinitionFileResult registerDefinitions(
Luau::Frontend& frontend, Luau::GlobalTypes& globals, const std::string& packageName, const std::string& definitions);

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

Expand Down
6 changes: 3 additions & 3 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ int startLanguageServer(const argparse::ArgumentParser& program)
_setmode(_fileno(stdout), _O_BINARY);
#endif

auto definitionsFiles = program.get<std::vector<std::string>>("--definitions");
auto definitionsFiles = processDefinitionsFilePaths(program);
auto documentationFiles = program.get<std::vector<std::string>>("--docs");
auto baseLuaurc = program.present<std::string>("--base-luaurc");
auto transportPipeFile = program.present<std::string>("--pipe");
Expand Down Expand Up @@ -242,7 +242,7 @@ int main(int argc, char** argv)
.help("A path to a Luau definitions file to load into the global namespace")
.default_value<std::vector<std::string>>({})
.append()
.metavar("PATH");
.metavar("@NAME=PATH");
analyze_command.add_argument("--ignore")
.help("file glob pattern for ignoring error outputs")
.default_value<std::vector<std::string>>({})
Expand All @@ -263,7 +263,7 @@ int main(int argc, char** argv)
.help("path to a Luau definitions file to load into the global namespace")
.default_value<std::vector<std::string>>({})
.append()
.metavar("PATH");
.metavar("@NAME=PATH");
lsp_command.add_argument("--docs", "--documentation")
.help("path to a Luau documentation database for loaded definitions")
.default_value<std::vector<std::string>>({})
Expand Down
Loading
Loading