From dcc1d582a2c67124c688c2fac2b7eaff0e2c0dcf Mon Sep 17 00:00:00 2001 From: liushitong Date: Thu, 14 Aug 2025 19:04:27 +0800 Subject: [PATCH] [LSP]: fix getOrganizeImports Issue: https://gitee.com/openharmony/arkcompiler_ets_frontend/issues/ICSM2M Signed-off-by: liushitong Change-Id: Idd2c04aaeb0142d915bb265dc624b1e4e1568a85 --- ets2panda/bindings/test/cases.ts | 6 + .../test/expected/getOrganizeImports.json | 50 +++++ .../ExtractDefaultImport1_export.ets | 19 ++ .../ExtractDefaultImport1_import.ets | 17 ++ .../ExtractDefaultImport2_export.ets | 18 ++ .../ExtractDefaultImport2_import.ets | 16 ++ .../getOrganizeImports1.ets | 30 +++ .../getOrganizeImports2.ets | 17 ++ .../getOrganizeImports3.ets | 15 ++ .../getOrganizeImports4.ets | 18 ++ ets2panda/lsp/src/organize_imports.cpp | 140 ++++++++++---- .../test/unit/lsp/organize_imports_test.cpp | 177 ++++++++++++++++++ 12 files changed, 487 insertions(+), 36 deletions(-) create mode 100644 ets2panda/bindings/test/expected/getOrganizeImports.json create mode 100644 ets2panda/bindings/test/testcases/getOrganizeImports/ExtractDefaultImport1_export.ets create mode 100644 ets2panda/bindings/test/testcases/getOrganizeImports/ExtractDefaultImport1_import.ets create mode 100644 ets2panda/bindings/test/testcases/getOrganizeImports/ExtractDefaultImport2_export.ets create mode 100644 ets2panda/bindings/test/testcases/getOrganizeImports/ExtractDefaultImport2_import.ets create mode 100644 ets2panda/bindings/test/testcases/getOrganizeImports/getOrganizeImports1.ets create mode 100644 ets2panda/bindings/test/testcases/getOrganizeImports/getOrganizeImports2.ets create mode 100644 ets2panda/bindings/test/testcases/getOrganizeImports/getOrganizeImports3.ets create mode 100644 ets2panda/bindings/test/testcases/getOrganizeImports/getOrganizeImports4.ets diff --git a/ets2panda/bindings/test/cases.ts b/ets2panda/bindings/test/cases.ts index b742dad5a4..67da1ec007 100644 --- a/ets2panda/bindings/test/cases.ts +++ b/ets2panda/bindings/test/cases.ts @@ -104,6 +104,12 @@ export const basicCases: TestCases = { expectedFilePath: resolveTestPath('test/expected/getSuggestionDiagnostics.json'), '1': [resolveTestPath('test/testcases/getSuggestionDiagnostics/getSuggestionDiagnostics1.ets')] }, + getOrganizeImports: { + expectedFilePath: resolveTestPath('test/expected/getOrganizeImports.json'), + '1': [resolveTestPath('test/testcases/getOrganizeImports/getOrganizeImports1.ets')], + '2': [resolveTestPath('test/testcases/getOrganizeImports/ExtractDefaultImport1_import.ets')], + '3': [resolveTestPath('test/testcases/getOrganizeImports/ExtractDefaultImport2_import.ets')] + }, getQuickInfoAtPosition: { expectedFilePath: resolveTestPath('test/expected/getQuickInfoAtPosition.json'), '1': [resolveTestPath('test/testcases/getQuickInfoAtPosition/getQuickInfoAtPosition1.ets'), 626], diff --git a/ets2panda/bindings/test/expected/getOrganizeImports.json b/ets2panda/bindings/test/expected/getOrganizeImports.json new file mode 100644 index 0000000000..1de7f3ac0a --- /dev/null +++ b/ets2panda/bindings/test/expected/getOrganizeImports.json @@ -0,0 +1,50 @@ +{ + "1": { + "fileTextChanges": [ + { + "fileName": "getOrganizeImports1.ets", + "textChanges": [ + { + "span": { + "start": 608, + "length": 306 + }, + "newText": "import { Entry, Component } from '@ohos.arkui.component';\nimport { State } from '@ohos.arkui.stateManagement';\nimport { B, C } from './getOrganizeImports2';" + } + ] + } + ] + }, + "2": { + "fileTextChanges": [ + { + "fileName": "ExtractDefaultImport1_import.ets", + "textChanges": [ + { + "span": { + "start": 608, + "length": 56 + }, + "newText": "import Foo, { one } from './ExtractDefaultImport1_export';" + } + ] + } + ] + }, + "3": { + "fileTextChanges": [ + { + "fileName": "ExtractDefaultImport2_import.ets", + "textChanges": [ + { + "span": { + "start": 608, + "length": 51 + }, + "newText": "import Foo from './ExtractDefaultImport2_export';" + } + ] + } + ] + } +} diff --git a/ets2panda/bindings/test/testcases/getOrganizeImports/ExtractDefaultImport1_export.ets b/ets2panda/bindings/test/testcases/getOrganizeImports/ExtractDefaultImport1_export.ets new file mode 100644 index 0000000000..74b30142ab --- /dev/null +++ b/ets2panda/bindings/test/testcases/getOrganizeImports/ExtractDefaultImport1_export.ets @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export function one(): number { return 1; } +class Foo { + name: string = "john"; +} +export default Foo; diff --git a/ets2panda/bindings/test/testcases/getOrganizeImports/ExtractDefaultImport1_import.ets b/ets2panda/bindings/test/testcases/getOrganizeImports/ExtractDefaultImport1_import.ets new file mode 100644 index 0000000000..c46b16e019 --- /dev/null +++ b/ets2panda/bindings/test/testcases/getOrganizeImports/ExtractDefaultImport1_import.ets @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {Foo, one} from './ExtractDefaultImport1_export'; +let foo: Foo = new Foo(); +let a: number = one(); diff --git a/ets2panda/bindings/test/testcases/getOrganizeImports/ExtractDefaultImport2_export.ets b/ets2panda/bindings/test/testcases/getOrganizeImports/ExtractDefaultImport2_export.ets new file mode 100644 index 0000000000..df813019e4 --- /dev/null +++ b/ets2panda/bindings/test/testcases/getOrganizeImports/ExtractDefaultImport2_export.ets @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +class Foo { + name: string = "john"; +} +export default Foo; diff --git a/ets2panda/bindings/test/testcases/getOrganizeImports/ExtractDefaultImport2_import.ets b/ets2panda/bindings/test/testcases/getOrganizeImports/ExtractDefaultImport2_import.ets new file mode 100644 index 0000000000..fbff1565b4 --- /dev/null +++ b/ets2panda/bindings/test/testcases/getOrganizeImports/ExtractDefaultImport2_import.ets @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {Foo} from './ExtractDefaultImport2_export'; +let foo: Foo = new Foo(); diff --git a/ets2panda/bindings/test/testcases/getOrganizeImports/getOrganizeImports1.ets b/ets2panda/bindings/test/testcases/getOrganizeImports/getOrganizeImports1.ets new file mode 100644 index 0000000000..6e12d394d3 --- /dev/null +++ b/ets2panda/bindings/test/testcases/getOrganizeImports/getOrganizeImports1.ets @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Entry, Text, Column, Component, Button, ClickEvent } from '@ohos.arkui.component' +import { State } from '@ohos.arkui.stateManagement' +import hilog from '@ohos.hilog'; +import {B, C, A} from "./getOrganizeImports2"; +import { X } from "./getOrganizeImports3"; +import Foo from "./getOrganizeImports4"; +const a = B; +const b = C; + +@Entry +@Component +struct My { + @State stateVar: string = '' + build() { + } +} \ No newline at end of file diff --git a/ets2panda/bindings/test/testcases/getOrganizeImports/getOrganizeImports2.ets b/ets2panda/bindings/test/testcases/getOrganizeImports/getOrganizeImports2.ets new file mode 100644 index 0000000000..ebf6069434 --- /dev/null +++ b/ets2panda/bindings/test/testcases/getOrganizeImports/getOrganizeImports2.ets @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const A = 1; +export const B = 2; +export const C = 3; \ No newline at end of file diff --git a/ets2panda/bindings/test/testcases/getOrganizeImports/getOrganizeImports3.ets b/ets2panda/bindings/test/testcases/getOrganizeImports/getOrganizeImports3.ets new file mode 100644 index 0000000000..67d5744275 --- /dev/null +++ b/ets2panda/bindings/test/testcases/getOrganizeImports/getOrganizeImports3.ets @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const X = 1; diff --git a/ets2panda/bindings/test/testcases/getOrganizeImports/getOrganizeImports4.ets b/ets2panda/bindings/test/testcases/getOrganizeImports/getOrganizeImports4.ets new file mode 100644 index 0000000000..df813019e4 --- /dev/null +++ b/ets2panda/bindings/test/testcases/getOrganizeImports/getOrganizeImports4.ets @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +class Foo { + name: string = "john"; +} +export default Foo; diff --git a/ets2panda/lsp/src/organize_imports.cpp b/ets2panda/lsp/src/organize_imports.cpp index c91ebd45bd..e0a74cee5c 100644 --- a/ets2panda/lsp/src/organize_imports.cpp +++ b/ets2panda/lsp/src/organize_imports.cpp @@ -13,13 +13,12 @@ * limitations under the License. */ -#include -#include #include #include +#include "compiler/lowering/util.h" +#include "internal_api.h" #include "public/public.h" -#include "lsp/include/api.h" #include "lsp/include/organize_imports.h" namespace ark::es2panda::lsp { @@ -30,11 +29,7 @@ bool IsImportUsed(es2panda_Context *ctx, const ImportSpecifier &spec) auto *ast = context->parserProgram->Ast(); bool found = false; - ast->FindChild([&](ir::AstNode *node) { - if (node->IsETSImportDeclaration() || node->IsImportSpecifier()) { - return false; - } - + auto checkFunction = [&ctx, &spec, &found](ir::AstNode *node) { if (spec.type == ImportType::NAMESPACE) { if (node->IsTSQualifiedName()) { auto *qname = node->AsTSQualifiedName(); @@ -56,16 +51,29 @@ bool IsImportUsed(es2panda_Context *ctx, const ImportSpecifier &spec) return false; } - auto *parent = node->Parent(); - bool isProperty = - parent != nullptr && parent->IsMemberExpression() && (parent->AsMemberExpression()->Property() == node); - bool isImportSpecifier = parent != nullptr && parent->IsImportSpecifier(); - if (!isProperty && !isImportSpecifier) { + auto touchingToken = GetTouchingToken(ctx, spec.start, false); + if (touchingToken == nullptr || !touchingToken->IsIdentifier()) { + return false; + } + auto decl = compiler::DeclarationFromIdentifier(node->AsIdentifier()); + auto specDecl = compiler::DeclarationFromIdentifier(touchingToken->AsIdentifier()); + if (decl == nullptr || specDecl == nullptr) { + return false; + } + if (decl == specDecl) { found = true; return true; } + return false; - }); + }; + + for (auto &statement : ast->Statements()) { + if (statement == nullptr || statement->IsETSImportDeclaration() || statement->IsETSReExportDeclaration()) { + continue; + } + statement->FindChild(checkFunction); + } return found; } @@ -82,8 +90,13 @@ void ProcessImportSpecifier(ir::AstNode *spec, bool isTypeOnly, ImportInfo &info auto *importSpec = spec->AsImportSpecifier(); local = importSpec->Local(); auto *imported = importSpec->Imported(); + bool isDefault = false; + auto decl = compiler::DeclarationFromIdentifier(imported); + if (decl != nullptr) { + isDefault = decl->IsDefaultExported(); + } specInfo.importedName = imported != nullptr ? std::string(imported->Name()) : std::string(local->Name()); - specInfo.type = isTypeOnly ? ImportType::TYPE_ONLY : ImportType::NORMAL; + specInfo.type = isTypeOnly ? ImportType::TYPE_ONLY : isDefault ? ImportType::DEFAULT : ImportType::NORMAL; } else if (spec->IsImportDefaultSpecifier()) { auto *defaultSpec = spec->AsImportDefaultSpecifier(); local = defaultSpec->Local(); @@ -125,10 +138,16 @@ void CollectImports(es2panda_Context *context, std::vector &imports) [](ir::AstNode *a, ir::AstNode *b) { return a->Start().index < b->Start().index; }); for (auto *importNode : importsNode) { + if (importNode == nullptr || !importNode->IsETSImportDeclaration()) { + continue; + } auto *importDecl = importNode->AsETSImportDeclaration(); - if (importDecl == nullptr || importDecl->Source() == nullptr) { + if (importDecl->Source() == nullptr) { continue; } + if (importDecl->Start().index == importDecl->End().index) { + continue; // Skip empty import declarations + } auto *declInfo = static_cast(importNode); if (declInfo == nullptr) { @@ -166,42 +185,85 @@ void RemoveUnusedImports(std::vector &imports, es2panda_Context *ctx } } -std::vector GenerateTextChanges(const std::vector &imports) +std::tuple HasDefaultSpecifier(const std::vector &namedImports) { - if (imports.empty()) { - return {}; - } - - size_t start = imports.front().startIndex; - size_t end = imports.back().endIndex; - std::ostringstream oss; + auto it = std::find_if(namedImports.begin(), namedImports.end(), + [](const ImportSpecifier &spec) { return spec.type == ImportType::DEFAULT; }); + return std::make_tuple(it != namedImports.end(), + it != namedImports.end() ? std::distance(namedImports.begin(), it) : -1); +} - auto generateImportBlock = [](const ImportInfo &imp, std::ostringstream &osst, const std::string &prefix) { - osst << prefix; - size_t index = 0; - for (auto &namedImport : imp.namedImports) { - const auto &spec = namedImport; +void ExtractDefaultImport(const ImportInfo &imp, std::ostringstream &osst, const std::string &prefix, + const std::tuple &hasDefault) +{ + auto [_, defaultIndex] = hasDefault; + osst << prefix; + osst << imp.namedImports[defaultIndex].localName; + if (imp.namedImports.size() > 1) { + osst << ", { "; + } + for (size_t i = 0; i < imp.namedImports.size(); ++i) { + if (i != defaultIndex) { + const auto &spec = imp.namedImports[i]; if (spec.importedName != spec.localName) { osst << spec.importedName << " as " << spec.localName; } else { osst << spec.localName; } - if (index + 1 < imp.namedImports.size()) { + if (i + 1 == defaultIndex && defaultIndex == imp.namedImports.size() - 1) { + continue; // Skip if the default import is the last one + } + if (i + 1 < imp.namedImports.size()) { osst << ", "; } - index++; } - osst << " } from \'" << imp.moduleName << "\';\n"; - }; + } + if (imp.namedImports.size() > 1) { + osst << " }"; + } + osst << " from \'" << imp.moduleName << "\';\n"; +} + +void GenerateImportBlock(const ImportInfo &imp, std::ostringstream &osst, const std::string &prefix) +{ + osst << prefix; + size_t index = 0; + for (auto &namedImport : imp.namedImports) { + const auto &spec = namedImport; + if (spec.importedName != spec.localName) { + osst << spec.importedName << " as " << spec.localName; + } else { + osst << spec.localName; + } + if (index + 1 < imp.namedImports.size()) { + osst << ", "; + } + index++; + } + osst << " } from \'" << imp.moduleName << "\';\n"; +} + +std::vector GenerateTextChanges(const std::vector &imports) +{ + if (imports.empty()) { + return {}; + } + + std::ostringstream oss; for (const auto &imp : imports) { if (imp.namedImports.empty()) { continue; } + auto hasDefault = HasDefaultSpecifier(imp.namedImports); + if (std::get<0>(hasDefault) && imp.namedImports.size() > 1) { + ExtractDefaultImport(imp, oss, "import ", hasDefault); + continue; + } switch (imp.namedImports[0].type) { case ImportType::NORMAL: - generateImportBlock(imp, oss, "import { "); + GenerateImportBlock(imp, oss, "import { "); break; case ImportType::DEFAULT: oss << "import " << imp.namedImports[0].localName << " from \'" << imp.moduleName << "\';\n"; @@ -210,12 +272,18 @@ std::vector GenerateTextChanges(const std::vector &impor oss << "import * as " << imp.namedImports[0].localName << " from \'" << imp.moduleName << "\';\n"; break; case ImportType::TYPE_ONLY: - generateImportBlock(imp, oss, "import type { "); + GenerateImportBlock(imp, oss, "import type { "); break; } } - return {TextChange(TextSpan(start, end - start), oss.str())}; + std::string result = oss.str(); + if (!result.empty() && result.back() == '\n') { + result.pop_back(); // Remove trailing newline to avoid adding newlines repeatedly. + } + + return { + TextChange(TextSpan(imports.front().startIndex, imports.back().endIndex - imports.front().startIndex), result)}; } std::vector OrganizeImports::Organize(es2panda_Context *context, const std::string &fileName) diff --git a/ets2panda/test/unit/lsp/organize_imports_test.cpp b/ets2panda/test/unit/lsp/organize_imports_test.cpp index 137fea4d9d..291c1f3df1 100644 --- a/ets2panda/test/unit/lsp/organize_imports_test.cpp +++ b/ets2panda/test/unit/lsp/organize_imports_test.cpp @@ -193,3 +193,180 @@ TEST_F(OrganizeImportsTest, SystemDefaultImports) initializer.DestroyContext(ctx); } + +TEST_F(OrganizeImportsTest, UnusedImports) +{ + std::vector files = {"UnusedImports_import.ets", "UnusedImports_export.ets"}; + std::vector texts = { + R"( + import { Foo, Bar } from './UnusedImports_export'; + export { Bar } from './UnusedImports_export'; + )", + R"( + export class Foo { + name: string = "john"; + } + export class Bar { + name: string = "john"; + } + )"}; + + auto filePaths = CreateTempFile(files, texts); + Initializer initializer; + es2panda_Context *ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED); + + std::vector changes = OrganizeImports::Organize(ctx, filePaths[0]); + + ASSERT_EQ(changes.size(), 1); + + auto result = changes[0].textChanges[0]; + + const size_t expectedStart = 9; + const size_t expectedLength = 50; + ASSERT_EQ(result.span.start, expectedStart); + ASSERT_EQ(result.span.length, expectedLength); + EXPECT_EQ(result.newText, ""); + + initializer.DestroyContext(ctx); +} + +TEST_F(OrganizeImportsTest, UnusedDefaultImports) +{ + std::vector files = {"UnusedDefaultImports_import.ets", "UnusedDefaultImports_export.ets"}; + std::vector texts = { + R"( + import Foo from "./UnusedDefaultImports_export"; + )", + R"( + class Foo { + name: string = "john"; + } + export default Foo; + )"}; + + auto filePaths = CreateTempFile(files, texts); + Initializer initializer; + es2panda_Context *ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED); + + std::vector changes = OrganizeImports::Organize(ctx, filePaths[0]); + + ASSERT_EQ(changes.size(), 1); + + auto result = changes[0].textChanges[0]; + + const size_t expectedStart = 9; + const size_t expectedLength = 48; + ASSERT_EQ(result.span.start, expectedStart); + ASSERT_EQ(result.span.length, expectedLength); + ASSERT_EQ(result.newText, ""); + + initializer.DestroyContext(ctx); +} + +TEST_F(OrganizeImportsTest, ExtractDefaultImport1) +{ + std::vector files = {"ExtractDefaultImport1_import.ets", "ExtractDefaultImport1_export.ets"}; + std::vector texts = { + R"( + import {Foo, one} from './ExtractDefaultImport1_export'; + let foo: Foo = new Foo(); + let a: number = one(); + )", + R"( + export function one(): number { return 1; } + class Foo { + name: string = "john"; + } + export default Foo; + )"}; + + auto filePaths = CreateTempFile(files, texts); + Initializer initializer; + es2panda_Context *ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED); + + std::vector changes = OrganizeImports::Organize(ctx, filePaths[0]); + + ASSERT_EQ(changes.size(), 1); + + auto result = changes[0].textChanges[0]; + + const size_t expectedStart = 9; + const size_t expectedLength = 56; + ASSERT_EQ(result.span.start, expectedStart); + ASSERT_EQ(result.span.length, expectedLength); + ASSERT_EQ(result.newText, "import Foo, { one } from './ExtractDefaultImport1_export';"); + + initializer.DestroyContext(ctx); +} + +TEST_F(OrganizeImportsTest, ExtractDefaultImport2) +{ + std::vector files = {"ExtractDefaultImport2_import.ets", "ExtractDefaultImport2_export.ets"}; + std::vector texts = { + R"( + import {Foo} from './ExtractDefaultImport2_export'; + let foo: Foo = new Foo(); + )", + R"( + class Foo { + name: string = "john"; + } + export default Foo; + )"}; + + auto filePaths = CreateTempFile(files, texts); + Initializer initializer; + es2panda_Context *ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED); + + std::vector changes = OrganizeImports::Organize(ctx, filePaths[0]); + + ASSERT_EQ(changes.size(), 1); + + auto result = changes[0].textChanges[0]; + + const size_t expectedStart = 9; + const size_t expectedLength = 51; + ASSERT_EQ(result.span.start, expectedStart); + ASSERT_EQ(result.span.length, expectedLength); + ASSERT_EQ(result.newText, "import Foo from './ExtractDefaultImport2_export';"); + + initializer.DestroyContext(ctx); +} + +TEST_F(OrganizeImportsTest, ExtractDefaultImport3) +{ + std::vector files = {"ExtractDefaultImport3_import.ets", "ExtractDefaultImport3_export.ets"}; + std::vector texts = { + R"( + import { one, two, Foo } from './ExtractDefaultImport3_export'; + let foo: Foo = new Foo(); + let a: number = one(); + let b: number = two(); + )", + R"( + export function one(): number { return 1; } + export function two(): number { return 2; } + class Foo { + name: string = "john"; + } + export default Foo; + )"}; + + auto filePaths = CreateTempFile(files, texts); + Initializer initializer; + es2panda_Context *ctx = initializer.CreateContext(filePaths[0].c_str(), ES2PANDA_STATE_CHECKED); + + std::vector changes = OrganizeImports::Organize(ctx, filePaths[0]); + + ASSERT_EQ(changes.size(), 1); + + auto result = changes[0].textChanges[0]; + + const size_t expectedStart = 9; + const size_t expectedLength = 63; + ASSERT_EQ(result.span.start, expectedStart); + ASSERT_EQ(result.span.length, expectedLength); + ASSERT_EQ(result.newText, "import Foo, { one, two } from './ExtractDefaultImport3_export';"); + + initializer.DestroyContext(ctx); +} -- Gitee