已合并
Fix LSP import definition navigation #11174
wdc创建于 12 天前
Fix LSP import definition navigation #11174
已合并
共 4 个文件变更+226-6
| @@ -24,6 +24,10 @@ | |||
| 24 | 24 | ||
| 25 | 25 | ||
| 26 | 26 | ||
| 27 | +namespace ark::es2panda::util { | ||
| 28 | +class ImportInfo; | ||
| 29 | +} // namespace ark::es2panda::util | ||
| 30 | + | ||
| 27 | namespace ark::es2panda::lsp { | 31 | namespace ark::es2panda::lsp { |
| 28 | 32 | ||
| 29 | class Initializer { | 33 | class Initializer { |
| @@ -169,6 +173,7 @@ varbinder::Decl *FindDeclInFunctionScope(varbinder::Scope *scope, const util::St | |||
| 169 | varbinder::Decl *FindDeclInGlobalScope(varbinder::Scope *scope, const util::StringView &name); | 173 | varbinder::Decl *FindDeclInGlobalScope(varbinder::Scope *scope, const util::StringView &name); |
| 170 | varbinder::Decl *FindDeclInScopeWithFallback(varbinder::Scope *scope, const util::StringView &name); | 174 | varbinder::Decl *FindDeclInScopeWithFallback(varbinder::Scope *scope, const util::StringView &name); |
| 171 | std::string GetImportFilePath(es2panda_Context *context, size_t pos); | 175 | std::string GetImportFilePath(es2panda_Context *context, size_t pos); |
| 176 | +std::string GetImportFilePath(const util::ImportInfo &importInfo); | ||
| 172 | void MakeDiagnosticReferences(es2panda_Context *context, const util::DiagnosticStorage &diagnostics, | 177 | void MakeDiagnosticReferences(es2panda_Context *context, const util::DiagnosticStorage &diagnostics, |
| 173 | DiagnosticReferences &result); | 178 | DiagnosticReferences &result); |
| 174 | std::string GetTokenTypes(ir::ModifierFlags flags); | 179 | std::string GetTokenTypes(ir::ModifierFlags flags); |
| @@ -26,6 +26,7 @@ | |||
| 26 | 26 | ||
| 27 | 27 | ||
| 28 | 28 | ||
| 29 | + | ||
| 29 | 30 | ||
| 30 | 31 | ||
| 31 | 32 | ||
| @@ -56,6 +57,30 @@ using ark::es2panda::lsp::details::GetCompletionEntryDetailsImpl; | |||
| 56 | extern "C" { | 57 | extern "C" { |
| 57 | namespace ark::es2panda::lsp { | 58 | namespace ark::es2panda::lsp { |
| 58 | 59 | ||
| 60 | +static std::string GetDefinitionFilePathFromImportNamespaceAlias(const ir::AstNode *declNode) | ||
| 61 | +{ | ||
| 62 | + if (declNode == nullptr || !declNode->IsImportNamespaceSpecifier()) { | ||
| 63 | + return {}; | ||
| 64 | + } | ||
| 65 | + | ||
| 66 | + const auto *parent = declNode->Parent(); | ||
| 67 | + if (parent == nullptr) { | ||
| 68 | + return {}; | ||
| 69 | + } | ||
| 70 | + | ||
| 71 | + const ir::ETSImportDeclaration *importDecl = nullptr; | ||
| 72 | + if (parent->IsETSImportDeclaration()) { | ||
| 73 | + importDecl = parent->AsETSImportDeclaration(); | ||
| 74 | + } else if (parent->IsETSReExportDeclaration()) { | ||
| 75 | + importDecl = parent->AsETSReExportDeclaration()->GetETSImportDeclarations(); | ||
| 76 | + } | ||
| 77 | + if (importDecl == nullptr) { | ||
| 78 | + return {}; | ||
| 79 | + } | ||
| 80 | + | ||
| 81 | + return GetImportFilePath(importDecl->ImportInfo()); | ||
| 82 | +} | ||
| 83 | + | ||
| 59 | DefinitionInfo GetDefinitionAtPosition(es2panda_Context *context, size_t position) | 84 | DefinitionInfo GetDefinitionAtPosition(es2panda_Context *context, size_t position) |
| 60 | { | 85 | { |
| 61 | auto ctx = reinterpret_cast<public_lib::Context *>(context); | 86 | auto ctx = reinterpret_cast<public_lib::Context *>(context); |
| @@ -74,6 +99,12 @@ DefinitionInfo GetDefinitionAtPosition(es2panda_Context *context, size_t positio | |||
| 74 | if (declInfo.first == nullptr) { | 99 | if (declInfo.first == nullptr) { |
| 75 | return result; | 100 | return result; |
| 76 | } | 101 | } |
| 102 | + | ||
| 103 | + auto importNamespaceAliasFilePath = GetDefinitionFilePathFromImportNamespaceAlias(declInfo.first); | ||
| 104 | + if (!importNamespaceAliasFilePath.empty()) { | ||
| 105 | + return {importNamespaceAliasFilePath, 0, 0}; | ||
| 106 | + } | ||
| 107 | + | ||
| 77 | auto node = declInfo.first; | 108 | auto node = declInfo.first; |
| 78 | auto targetNode = declInfo.first->FindChild([&declInfo](ir::AstNode *childNode) { | 109 | auto targetNode = declInfo.first->FindChild([&declInfo](ir::AstNode *childNode) { |
| 79 | return childNode->IsIdentifier() && childNode->AsIdentifier()->Name() == declInfo.second; | 110 | return childNode->IsIdentifier() && childNode->AsIdentifier()->Name() == declInfo.second; |
| @@ -1452,16 +1452,19 @@ std::string GetImportFilePath(es2panda_Context *context, size_t pos) | |||
| 1452 | } | 1452 | } |
| 1453 | auto parent = node->Parent(); | 1453 | auto parent = node->Parent(); |
| 1454 | if (parent != nullptr && parent->IsETSImportDeclaration() && parent->AsETSImportDeclaration()->Source() == node) { | 1454 | if (parent != nullptr && parent->IsETSImportDeclaration() && parent->AsETSImportDeclaration()->Source() == node) { |
| 1455 | - const auto &importInfo = parent->AsETSImportDeclaration()->ImportInfo(); | 1455 | + res = GetImportFilePath(parent->AsETSImportDeclaration()->ImportInfo()); |
| 1456 | - if (node->IsStringLiteral() && node->AsStringLiteral()->Str().StartsWith("dynamic@")) { | ||
| 1457 | - res = std::string(importInfo.TextSource()); | ||
| 1458 | - } else { | ||
| 1459 | - res = std::string(importInfo.ResolvedSource()); | ||
| 1460 | - } | ||
| 1461 | } | 1456 | } |
| 1462 | return res; | 1457 | return res; |
| 1463 | } | 1458 | } |
| 1464 | 1459 | ||
| 1460 | +std::string GetImportFilePath(const util::ImportInfo &importInfo) | ||
| 1461 | +{ | ||
| 1462 | + if (importInfo.HasSpecifiedDeclPath() && !importInfo.ReferencesABC()) { | ||
| 1463 | + return std::string(importInfo.DeclPath()); | ||
| 1464 | + } | ||
| 1465 | + return std::string(importInfo.ResolvedSource()); | ||
| 1466 | +} | ||
| 1467 | + | ||
| 1465 | std::vector<ir::AstNode *> RemoveRefDuplicates(const std::vector<ir::AstNode *> &nodes) | 1468 | std::vector<ir::AstNode *> RemoveRefDuplicates(const std::vector<ir::AstNode *> &nodes) |
| 1466 | { | 1469 | { |
| 1467 | auto hashFunc = [](const ir::AstNode *node) { | 1470 | auto hashFunc = [](const ir::AstNode *node) { |
| @@ -14,13 +14,38 @@ | |||
| 14 | */ | 14 | */ |
| 15 | 15 | ||
| 16 | 16 | ||
| 17 | + | ||
| 17 | 18 | ||
| 19 | + | ||
| 18 | 20 | ||
| 21 | + | ||
| 19 | 22 | ||
| 20 | 23 | ||
| 21 | 24 | ||
| 22 | using ark::es2panda::lsp::Initializer; | 25 | using ark::es2panda::lsp::Initializer; |
| 23 | 26 | ||
| 27 | +namespace { | ||
| 28 | + | ||
| 29 | +constexpr size_t DYNAMIC_DEPENDENCY_INDEX = 0U; | ||
| 30 | +constexpr size_t STATIC_IMPLEMENTATION_INDEX = 1U; | ||
| 31 | +constexpr size_t STATIC_DECLARATION_INDEX = 2U; | ||
| 32 | +constexpr size_t DEPENDENCY_USE_INDEX = 3U; | ||
| 33 | +constexpr size_t DEPENDENCY_CONFIG_INDEX = 4U; | ||
| 34 | +constexpr size_t DEPENDENCY_FILE_COUNT = 5U; | ||
| 35 | + | ||
| 36 | +std::string MakeDependencyConfig(const std::filesystem::path &tempDir, const std::vector<std::string> &files) | ||
| 37 | +{ | ||
| 38 | + const auto dynamicDependencyPath = (tempDir / files[DYNAMIC_DEPENDENCY_INDEX]).string(); | ||
| 39 | + const auto staticImplementationPath = (tempDir / files[STATIC_IMPLEMENTATION_INDEX]).string(); | ||
| 40 | + const auto staticDeclarationPath = (tempDir / files[STATIC_DECLARATION_INDEX]).string(); | ||
| 41 | + return R"({"compilerOptions": {"baseUrl": ")" + tempDir.string() + | ||
| 42 | + R"(", "dependencies": {"js": {"language": "js", "path": ")" + dynamicDependencyPath + | ||
| 43 | + R"(", "ohmUrl": "js"}, "staticLib": {"language": "ets", "path": ")" + staticDeclarationPath + | ||
| 44 | + R"(", "sourceFilePath": ")" + staticImplementationPath + R"("}}}})"; | ||
| 45 | +} | ||
| 46 | + | ||
| 47 | +} // namespace | ||
| 48 | + | ||
| 24 | class LspGetDefTests : public LSPAPITests {}; | 49 | class LspGetDefTests : public LSPAPITests {}; |
| 25 | 50 | ||
| 26 | TEST_F(LspGetDefTests, GetDefinitionAtPosition1) | 51 | TEST_F(LspGetDefTests, GetDefinitionAtPosition1) |
| @@ -496,6 +521,162 @@ TEST_F(LspGetDefTests, DisableLoweringTest4) | |||
| 496 | ASSERT_EQ(result.length, expectedLength); | 521 | ASSERT_EQ(result.length, expectedLength); |
| 497 | } | 522 | } |
| 498 | 523 | ||
| 524 | +TEST_F(LspGetDefTests, GetDefinitionAtPosition_ImportNamespaceAliasDeclaration) | ||
| 525 | +{ | ||
| 526 | + std::vector<std::string> files = {"getDefinitionAtPositionNamespaceAliasDeclarationSource.ets", | ||
| 527 | + "getDefinitionAtPositionNamespaceAliasDeclarationUse.ets"}; | ||
| 528 | + std::vector<std::string> texts = { | ||
| 529 | + R"(export class A {})", R"(import * as aaaa from './getDefinitionAtPositionNamespaceAliasDeclarationSource';)"}; | ||
| 530 | + auto filePaths = CreateTempFile(files, texts); | ||
| 531 | + size_t const expectedFileCount = 2; | ||
| 532 | + ASSERT_EQ(filePaths.size(), expectedFileCount); | ||
| 533 | + | ||
| 534 | + const auto offset = texts[1].find("aaaa"); | ||
| 535 | + ASSERT_NE(offset, std::string::npos); | ||
| 536 | + | ||
| 537 | + LSPAPI const *lspApi = GetImpl(); | ||
| 538 | + Initializer initializer = Initializer(); | ||
| 539 | + auto ctx = initializer.CreateContext(filePaths[1].c_str(), ES2PANDA_STATE_CHECKED); | ||
| 540 | + auto result = lspApi->getDefinitionAtPosition(ctx, offset); | ||
| 541 | + initializer.DestroyContext(ctx); | ||
| 542 | + | ||
| 543 | + ASSERT_EQ(result.fileName, filePaths[0]); | ||
| 544 | + ASSERT_EQ(result.start, 0U); | ||
| 545 | + ASSERT_EQ(result.length, 0U); | ||
| 546 | +} | ||
| 547 | + | ||
| 548 | +TEST_F(LspGetDefTests, GetDefinitionAtPosition_ImportNamespaceModulePath) | ||
| 549 | +{ | ||
| 550 | + std::vector<std::string> files = {"getDefinitionAtPositionNamespacePathSource.ets", | ||
| 551 | + "getDefinitionAtPositionNamespacePathUse.ets"}; | ||
| 552 | + const std::string importPath = "./getDefinitionAtPositionNamespacePathSource"; | ||
| 553 | + std::vector<std::string> texts = {R"(export class A {})", | ||
| 554 | + R"(import * as aaaa from './getDefinitionAtPositionNamespacePathSource';)"}; | ||
| 555 | + auto filePaths = CreateTempFile(files, texts); | ||
| 556 | + size_t const expectedFileCount = 2; | ||
| 557 | + ASSERT_EQ(filePaths.size(), expectedFileCount); | ||
| 558 | + | ||
| 559 | + const auto offset = texts[1].find(importPath); | ||
| 560 | + ASSERT_NE(offset, std::string::npos); | ||
| 561 | + | ||
| 562 | + LSPAPI const *lspApi = GetImpl(); | ||
| 563 | + Initializer initializer = Initializer(); | ||
| 564 | + auto ctx = initializer.CreateContext(filePaths[1].c_str(), ES2PANDA_STATE_CHECKED); | ||
| 565 | + auto result = lspApi->getDefinitionAtPosition(ctx, offset); | ||
| 566 | + initializer.DestroyContext(ctx); | ||
| 567 | + | ||
| 568 | + ASSERT_EQ(result.fileName, filePaths[0]); | ||
| 569 | + ASSERT_EQ(result.start, 0U); | ||
| 570 | + ASSERT_EQ(result.length, 0U); | ||
| 571 | +} | ||
| 572 | + | ||
| 573 | +TEST_F(LspGetDefTests, GetDefinitionAtPosition_ArktsconfigDependency) | ||
| 574 | +{ | ||
| 575 | + std::vector<std::string> files = { | ||
| 576 | + "getDefinitionAtPositionDynamicDependency.d.ets", "getDefinitionAtPositionStaticDependency.ets", | ||
| 577 | + "getDefinitionAtPositionStaticDependency.d.ets", "getDefinitionAtPositionDependencyUse.ets", | ||
| 578 | + "getDefinitionAtPositionDependencyArktsconfig.json"}; | ||
| 579 | + const auto tempDir = std::filesystem::path(testing::TempDir()).append(GetExecutableName()); | ||
| 580 | + const auto configText = MakeDependencyConfig(tempDir, files); | ||
| 581 | + std::vector<std::string> texts = {R"(export declare class DynamicA {})", R"(export class StaticA {})", | ||
| 582 | + R"(export declare class StaticA {})", | ||
| 583 | + R"('use static' | ||
| 584 | + | ||
| 585 | +import * as jsModule from 'js'; | ||
| 586 | +import * as staticModule from 'staticLib';)", | ||
| 587 | + configText}; | ||
| 588 | + auto filePaths = CreateTempFile(files, texts); | ||
| 589 | + ASSERT_EQ(filePaths.size(), DEPENDENCY_FILE_COUNT); | ||
| 590 | + | ||
| 591 | + const auto configOption = "--arktsconfig=" + filePaths[DEPENDENCY_CONFIG_INDEX]; | ||
| 592 | + std::array<const char *, 2U> args = {filePaths[DEPENDENCY_USE_INDEX].c_str(), configOption.c_str()}; | ||
| 593 | + auto *compilerImpl = es2panda_GetImpl(ES2PANDA_LIB_VERSION); | ||
| 594 | + ASSERT_NE(compilerImpl, nullptr); | ||
| 595 | + | ||
| 596 | + auto configDeleter = [compilerImpl](es2panda_Config *config) { compilerImpl->DestroyConfigWithoutLog(config); }; | ||
| 597 | + std::unique_ptr<es2panda_Config, decltype(configDeleter)> config( | ||
| 598 | + compilerImpl->CreateConfig(args.size(), args.data()), configDeleter); | ||
| 599 | + ASSERT_NE(config, nullptr); | ||
| 600 | + | ||
| 601 | + auto contextDeleter = [compilerImpl](es2panda_Context *context) { compilerImpl->DestroyContext(context); }; | ||
| 602 | + std::unique_ptr<es2panda_Context, decltype(contextDeleter)> context( | ||
| 603 | + compilerImpl->CreateContextFromFile(config.get(), filePaths[DEPENDENCY_USE_INDEX].c_str()), contextDeleter); | ||
| 604 | + ASSERT_NE(context, nullptr); | ||
| 605 | + compilerImpl->ProceedToState(context.get(), ES2PANDA_STATE_BOUND); | ||
| 606 | + | ||
| 607 | + const auto dynamicAliasOffset = texts[DEPENDENCY_USE_INDEX].find("jsModule"); | ||
| 608 | + const auto dynamicSourceOffset = texts[DEPENDENCY_USE_INDEX].rfind("js"); | ||
| 609 | + const auto staticAliasOffset = texts[DEPENDENCY_USE_INDEX].find("staticModule"); | ||
| 610 | + const auto staticSourceOffset = texts[DEPENDENCY_USE_INDEX].rfind("staticLib"); | ||
| 611 | + ASSERT_NE(dynamicAliasOffset, std::string::npos); | ||
| 612 | + ASSERT_NE(dynamicSourceOffset, std::string::npos); | ||
| 613 | + ASSERT_NE(staticAliasOffset, std::string::npos); | ||
| 614 | + ASSERT_NE(staticSourceOffset, std::string::npos); | ||
| 615 | + | ||
| 616 | + LSPAPI const *lspApi = GetImpl(); | ||
| 617 | + const std::array<std::pair<size_t, std::string>, 4U> expectedDefinitions = { | ||
| 618 | + {{dynamicAliasOffset, filePaths[DYNAMIC_DEPENDENCY_INDEX]}, | ||
| 619 | + {dynamicSourceOffset, filePaths[DYNAMIC_DEPENDENCY_INDEX]}, | ||
| 620 | + {staticAliasOffset, filePaths[STATIC_DECLARATION_INDEX]}, | ||
| 621 | + {staticSourceOffset, filePaths[STATIC_DECLARATION_INDEX]}}}; | ||
| 622 | + for (const auto &[offset, expectedFile] : expectedDefinitions) { | ||
| 623 | + const auto result = lspApi->getDefinitionAtPosition(context.get(), offset); | ||
| 624 | + EXPECT_EQ(result.fileName, expectedFile); | ||
| 625 | + EXPECT_EQ(result.start, 0U); | ||
| 626 | + EXPECT_EQ(result.length, 0U); | ||
| 627 | + } | ||
| 628 | +} | ||
| 629 | + | ||
| 630 | +TEST_F(LspGetDefTests, GetDefinitionAtPosition_ImportNamespaceAliasUsage) | ||
| 631 | +{ | ||
| 632 | + std::vector<std::string> files = {"getDefinitionAtPositionNamespaceAliasUsageSource.ets", | ||
| 633 | + "getDefinitionAtPositionNamespaceAliasUsageUse.ets"}; | ||
| 634 | + std::vector<std::string> texts = {R"(export class A {})", | ||
| 635 | + R"(import * as aaaa from './getDefinitionAtPositionNamespaceAliasUsageSource'; | ||
| 636 | +let instance = new aaaa.A();)"}; | ||
| 637 | + auto filePaths = CreateTempFile(files, texts); | ||
| 638 | + size_t const expectedFileCount = 2; | ||
| 639 | + ASSERT_EQ(filePaths.size(), expectedFileCount); | ||
| 640 | + | ||
| 641 | + const auto offset = texts[1].rfind("aaaa"); | ||
| 642 | + ASSERT_NE(offset, std::string::npos); | ||
| 643 | + | ||
| 644 | + LSPAPI const *lspApi = GetImpl(); | ||
| 645 | + Initializer initializer = Initializer(); | ||
| 646 | + auto ctx = initializer.CreateContext(filePaths[1].c_str(), ES2PANDA_STATE_CHECKED); | ||
| 647 | + auto result = lspApi->getDefinitionAtPosition(ctx, offset); | ||
| 648 | + initializer.DestroyContext(ctx); | ||
| 649 | + | ||
| 650 | + ASSERT_EQ(result.fileName, filePaths[0]); | ||
| 651 | + ASSERT_EQ(result.start, 0U); | ||
| 652 | + ASSERT_EQ(result.length, 0U); | ||
| 653 | +} | ||
| 654 | + | ||
| 655 | +TEST_F(LspGetDefTests, GetDefinitionAtPosition_ReExportNamespaceAliasInDeclaration) | ||
| 656 | +{ | ||
| 657 | + std::vector<std::string> files = {"getDefinitionAtPositionReExportNamespaceAliasSource.ets", | ||
| 658 | + "getDefinitionAtPositionReExportNamespaceAliasUse.ets"}; | ||
| 659 | + std::vector<std::string> texts = { | ||
| 660 | + R"(export class A {})", | ||
| 661 | + R"(export * as components from './getDefinitionAtPositionReExportNamespaceAliasSource';)"}; | ||
| 662 | + auto filePaths = CreateTempFile(files, texts); | ||
| 663 | + size_t const expectedFileCount = 2; | ||
| 664 | + ASSERT_EQ(filePaths.size(), expectedFileCount); | ||
| 665 | + | ||
| 666 | + const auto offset = texts[1].find("components"); | ||
| 667 | + ASSERT_NE(offset, std::string::npos); | ||
| 668 | + | ||
| 669 | + LSPAPI const *lspApi = GetImpl(); | ||
| 670 | + Initializer initializer = Initializer(); | ||
| 671 | + auto ctx = initializer.CreateContext(filePaths[1].c_str(), ES2PANDA_STATE_CHECKED); | ||
| 672 | + auto result = lspApi->getDefinitionAtPosition(ctx, offset); | ||
| 673 | + initializer.DestroyContext(ctx); | ||
| 674 | + | ||
| 675 | + ASSERT_EQ(result.fileName, filePaths[0]); | ||
| 676 | + ASSERT_EQ(result.start, 0U); | ||
| 677 | + ASSERT_EQ(result.length, 0U); | ||
| 678 | +} | ||
| 679 | + | ||
| 499 | TEST_F(LspGetDefTests, negativeTest1) | 680 | TEST_F(LspGetDefTests, negativeTest1) |
| 500 | { | 681 | { |
| 501 | std::vector<std::string> files = {"SetJumpTarget.ets"}; | 682 | std::vector<std::string> files = {"SetJumpTarget.ets"}; |
export * as components from "./source";Can we jump to./sourcewhen click./source?