llvm-project/clang/unittests/Format/SortImportsTestJS.cpp
Martin Probst 03c59765b3 clang-format: [JS] sort import aliases. Users can define aliases for long symbols using import aliases:
import X = A.B.C;

Previously, these were unhandled and would terminate import sorting.
With this change, aliases sort as their own group, coming last after all
other imports.

Aliases are not sorted within their group, as they may reference each
other, so order is significant.

This reverts commit f750c3d95a0c8bf1d21380ae753fce12010a7561. It fixes
the msan issue by not parsing past the end of the line when handling
import aliases.

Differential Revision: https://reviews.llvm.org/D118446
2022-01-28 11:51:28 +01:00

471 lines
15 KiB
C++

//===- unittest/Format/SortImportsTestJS.cpp - JS import sort unit tests --===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "FormatTestUtils.h"
#include "clang/Format/Format.h"
#include "llvm/Support/Debug.h"
#include "gtest/gtest.h"
#define DEBUG_TYPE "format-test"
namespace clang {
namespace format {
namespace {
class SortImportsTestJS : public ::testing::Test {
protected:
std::string sort(StringRef Code, unsigned Offset = 0, unsigned Length = 0) {
StringRef FileName = "input.js";
if (Length == 0U)
Length = Code.size() - Offset;
std::vector<tooling::Range> Ranges(1, tooling::Range(Offset, Length));
auto Sorted =
applyAllReplacements(Code, sortIncludes(Style, Code, Ranges, FileName));
EXPECT_TRUE(static_cast<bool>(Sorted));
auto Formatted = applyAllReplacements(
*Sorted, reformat(Style, *Sorted, Ranges, FileName));
EXPECT_TRUE(static_cast<bool>(Formatted));
return *Formatted;
}
void _verifySort(const char *File, int Line, llvm::StringRef Expected,
llvm::StringRef Code, unsigned Offset = 0,
unsigned Length = 0) {
::testing::ScopedTrace t(File, Line, ::testing::Message() << Code.str());
std::string Result = sort(Code, Offset, Length);
EXPECT_EQ(Expected.str(), Result) << "Expected:\n"
<< Expected << "\nActual:\n"
<< Result;
}
FormatStyle Style = getGoogleStyle(FormatStyle::LK_JavaScript);
};
#define verifySort(...) _verifySort(__FILE__, __LINE__, __VA_ARGS__)
TEST_F(SortImportsTestJS, AlreadySorted) {
verifySort("import {sym} from 'a';\n"
"import {sym} from 'b';\n"
"import {sym} from 'c';\n"
"\n"
"let x = 1;",
"import {sym} from 'a';\n"
"import {sym} from 'b';\n"
"import {sym} from 'c';\n"
"\n"
"let x = 1;");
}
TEST_F(SortImportsTestJS, BasicSorting) {
verifySort("import {sym} from 'a';\n"
"import {sym} from 'b';\n"
"import {sym} from 'c';\n"
"\n"
"let x = 1;",
"import {sym} from 'a';\n"
"import {sym} from 'c';\n"
"import {sym} from 'b';\n"
"let x = 1;");
}
TEST_F(SortImportsTestJS, DefaultBinding) {
verifySort("import A from 'a';\n"
"import B from 'b';\n"
"\n"
"let x = 1;",
"import B from 'b';\n"
"import A from 'a';\n"
"let x = 1;");
}
TEST_F(SortImportsTestJS, DefaultAndNamedBinding) {
verifySort("import A, {a} from 'a';\n"
"import B, {b} from 'b';\n"
"\n"
"let x = 1;",
"import B, {b} from 'b';\n"
"import A, {a} from 'a';\n"
"let x = 1;");
}
TEST_F(SortImportsTestJS, WrappedImportStatements) {
verifySort("import {sym1, sym2} from 'a';\n"
"import {sym} from 'b';\n"
"\n"
"1;",
"import\n"
" {sym}\n"
" from 'b';\n"
"import {\n"
" sym1,\n"
" sym2\n"
"} from 'a';\n"
"1;");
}
TEST_F(SortImportsTestJS, SeparateMainCodeBody) {
verifySort("import {sym} from 'a';"
"\n"
"let x = 1;\n",
"import {sym} from 'a'; let x = 1;\n");
}
TEST_F(SortImportsTestJS, Comments) {
verifySort("/** @fileoverview This is a great file. */\n"
"// A very important import follows.\n"
"import {sym} from 'a'; /* more comments */\n"
"import {sym} from 'b'; // from //foo:bar\n",
"/** @fileoverview This is a great file. */\n"
"import {sym} from 'b'; // from //foo:bar\n"
"// A very important import follows.\n"
"import {sym} from 'a'; /* more comments */\n");
verifySort("import {sym} from 'a';\n"
"import {sym} from 'b';\n"
"\n"
"/** Comment on variable. */\n"
"const x = 1;\n",
"import {sym} from 'b';\n"
"import {sym} from 'a';\n"
"\n"
"/** Comment on variable. */\n"
"const x = 1;\n");
}
TEST_F(SortImportsTestJS, SortStar) {
verifySort("import * as foo from 'a';\n"
"import {sym} from 'a';\n"
"import * as bar from 'b';\n",
"import {sym} from 'a';\n"
"import * as foo from 'a';\n"
"import * as bar from 'b';\n");
}
TEST_F(SortImportsTestJS, AliasesSymbols) {
verifySort("import {sym1 as alias1} from 'b';\n"
"import {sym2 as alias2, sym3 as alias3} from 'c';\n",
"import {sym2 as alias2, sym3 as alias3} from 'c';\n"
"import {sym1 as alias1} from 'b';\n");
}
TEST_F(SortImportsTestJS, SortSymbols) {
verifySort("import {sym1, sym2 as a, sym3} from 'b';\n",
"import {sym2 as a, sym1, sym3} from 'b';\n");
verifySort("import {sym1 /* important! */, /*!*/ sym2 as a} from 'b';\n",
"import {/*!*/ sym2 as a, sym1 /* important! */} from 'b';\n");
verifySort("import {sym1, sym2} from 'b';\n", "import {\n"
" sym2 \n"
",\n"
" sym1 \n"
"} from 'b';\n");
}
TEST_F(SortImportsTestJS, GroupImports) {
verifySort("import {a} from 'absolute';\n"
"\n"
"import {b} from '../parent';\n"
"import {b} from '../parent/nested';\n"
"\n"
"import {b} from './relative/path';\n"
"import {b} from './relative/path/nested';\n"
"\n"
"let x = 1;\n",
"import {b} from './relative/path/nested';\n"
"import {b} from './relative/path';\n"
"import {b} from '../parent/nested';\n"
"import {b} from '../parent';\n"
"import {a} from 'absolute';\n"
"let x = 1;\n");
}
TEST_F(SortImportsTestJS, Exports) {
verifySort("import {S} from 'bpath';\n"
"\n"
"import {T} from './cpath';\n"
"\n"
"export {A, B} from 'apath';\n"
"export {P} from '../parent';\n"
"export {R} from './relative';\n"
"export {S};\n"
"\n"
"let x = 1;\n"
"export y = 1;\n",
"export {R} from './relative';\n"
"import {T} from './cpath';\n"
"export {S};\n"
"export {A, B} from 'apath';\n"
"import {S} from 'bpath';\n"
"export {P} from '../parent';\n"
"let x = 1;\n"
"export y = 1;\n");
verifySort("import {S} from 'bpath';\n"
"\n"
"export {T} from 'epath';\n",
"export {T} from 'epath';\n"
"import {S} from 'bpath';\n");
}
TEST_F(SortImportsTestJS, SideEffectImports) {
verifySort("import 'ZZside-effect';\n"
"import 'AAside-effect';\n"
"\n"
"import {A} from 'absolute';\n"
"\n"
"import {R} from './relative';\n",
"import {R} from './relative';\n"
"import 'ZZside-effect';\n"
"import {A} from 'absolute';\n"
"import 'AAside-effect';\n");
}
TEST_F(SortImportsTestJS, AffectedRange) {
// Affected range inside of import statements.
verifySort("import {sym} from 'a';\n"
"import {sym} from 'b';\n"
"import {sym} from 'c';\n"
"\n"
"let x = 1;",
"import {sym} from 'c';\n"
"import {sym} from 'b';\n"
"import {sym} from 'a';\n"
"let x = 1;",
0, 30);
// Affected range outside of import statements.
verifySort("import {sym} from 'c';\n"
"import {sym} from 'b';\n"
"import {sym} from 'a';\n"
"\n"
"let x = 1;",
"import {sym} from 'c';\n"
"import {sym} from 'b';\n"
"import {sym} from 'a';\n"
"\n"
"let x = 1;",
70, 1);
}
TEST_F(SortImportsTestJS, SortingCanShrink) {
// Sort excluding a suffix.
verifySort("import {B} from 'a';\n"
"import {A} from 'b';\n"
"\n"
"1;",
"import {A} from 'b';\n"
"\n"
"import {B} from 'a';\n"
"\n"
"1;");
}
TEST_F(SortImportsTestJS, TrailingComma) {
verifySort("import {A, B,} from 'aa';\n", "import {B, A,} from 'aa';\n");
}
TEST_F(SortImportsTestJS, SortCaseInsensitive) {
verifySort("import {A} from 'aa';\n"
"import {A} from 'Ab';\n"
"import {A} from 'b';\n"
"import {A} from 'Bc';\n"
"\n"
"1;",
"import {A} from 'b';\n"
"import {A} from 'Bc';\n"
"import {A} from 'Ab';\n"
"import {A} from 'aa';\n"
"\n"
"1;");
verifySort("import {aa, Ab, b, Bc} from 'x';\n"
"\n"
"1;",
"import {b, Bc, Ab, aa} from 'x';\n"
"\n"
"1;");
}
TEST_F(SortImportsTestJS, SortMultiLine) {
// Reproduces issue where multi-line import was not parsed correctly.
verifySort("import {A} from 'a';\n"
"import {A} from 'b';\n"
"\n"
"1;",
"import\n"
"{\n"
"A\n"
"}\n"
"from\n"
"'b';\n"
"import {A} from 'a';\n"
"\n"
"1;");
}
TEST_F(SortImportsTestJS, SortDefaultImports) {
// Reproduces issue where multi-line import was not parsed correctly.
verifySort("import {A} from 'a';\n"
"import {default as B} from 'b';\n",
"import {default as B} from 'b';\n"
"import {A} from 'a';\n");
}
TEST_F(SortImportsTestJS, MergeImports) {
// basic operation
verifySort("import {X, Y} from 'a';\n"
"import {Z} from 'z';\n"
"\n"
"X + Y + Z;\n",
"import {X} from 'a';\n"
"import {Z} from 'z';\n"
"import {Y} from 'a';\n"
"\n"
"X + Y + Z;\n");
// merge only, no resorting.
verifySort("import {A, B} from 'foo';\n", "import {A} from 'foo';\n"
"import {B} from 'foo';");
// empty imports
verifySort("import {A} from 'foo';\n", "import {} from 'foo';\n"
"import {A} from 'foo';");
// ignores import *
verifySort("import * as foo from 'foo';\n"
"import {A} from 'foo';\n",
"import * as foo from 'foo';\n"
"import {A} from 'foo';\n");
// ignores default import
verifySort("import X from 'foo';\n"
"import {A} from 'foo';\n",
"import X from 'foo';\n"
"import {A} from 'foo';\n");
// keeps comments
// known issue: loses the 'also a' comment.
verifySort("// a\n"
"import {/* x */ X, /* y */ Y} from 'a';\n"
"// z\n"
"import {Z} from 'z';\n"
"\n"
"X + Y + Z;\n",
"// a\n"
"import {/* y */ Y} from 'a';\n"
"// z\n"
"import {Z} from 'z';\n"
"// also a\n"
"import {/* x */ X} from 'a';\n"
"\n"
"X + Y + Z;\n");
// do not merge imports and exports
verifySort("import {A} from 'foo';\n"
"\n"
"export {B} from 'foo';\n",
"import {A} from 'foo';\n"
"export {B} from 'foo';");
// do merge exports
verifySort("export {A, B} from 'foo';\n", "export {A} from 'foo';\n"
"export {B} from 'foo';");
// do not merge side effect imports with named ones
verifySort("import './a';\n"
"\n"
"import {bar} from './a';\n",
"import {bar} from './a';\n"
"import './a';\n");
}
TEST_F(SortImportsTestJS, RespectsClangFormatOff) {
verifySort("// clang-format off\n"
"import {B} from './b';\n"
"import {A} from './a';\n"
"// clang-format on\n",
"// clang-format off\n"
"import {B} from './b';\n"
"import {A} from './a';\n"
"// clang-format on\n");
verifySort("import {A} from './sorted1_a';\n"
"import {B} from './sorted1_b';\n"
"// clang-format off\n"
"import {B} from './unsorted_b';\n"
"import {A} from './unsorted_a';\n"
"// clang-format on\n"
"import {A} from './sorted2_a';\n"
"import {B} from './sorted2_b';\n",
"import {B} from './sorted1_b';\n"
"import {A} from './sorted1_a';\n"
"// clang-format off\n"
"import {B} from './unsorted_b';\n"
"import {A} from './unsorted_a';\n"
"// clang-format on\n"
"import {B} from './sorted2_b';\n"
"import {A} from './sorted2_a';\n");
// Boundary cases
verifySort("// clang-format on\n", "// clang-format on\n");
verifySort("// clang-format off\n", "// clang-format off\n");
verifySort("// clang-format on\n"
"// clang-format off\n",
"// clang-format on\n"
"// clang-format off\n");
verifySort("// clang-format off\n"
"// clang-format on\n"
"import {A} from './a';\n"
"import {B} from './b';\n",
"// clang-format off\n"
"// clang-format on\n"
"import {B} from './b';\n"
"import {A} from './a';\n");
// section ends with comment
verifySort("// clang-format on\n"
"import {A} from './a';\n"
"import {B} from './b';\n"
"import {C} from './c';\n"
"\n" // inserted empty line is working as intended: splits imports
// section from main code body
"// clang-format off\n",
"// clang-format on\n"
"import {C} from './c';\n"
"import {B} from './b';\n"
"import {A} from './a';\n"
"// clang-format off\n");
}
TEST_F(SortImportsTestJS, RespectsClangFormatOffInNamedImports) {
verifySort("// clang-format off\n"
"import {B, A} from './b';\n"
"// clang-format on\n"
"const x = 1;",
"// clang-format off\n"
"import {B, A} from './b';\n"
"// clang-format on\n"
"const x = 1;");
}
TEST_F(SortImportsTestJS, ImportEqAliases) {
verifySort("import {B} from 'bar';\n"
"import {A} from 'foo';\n"
"\n"
"import Z = A.C;\n"
"import Y = B.C.Z;\n"
"\n"
"export {Z};\n"
"\n"
"console.log(Z);\n",
"import {A} from 'foo';\n"
"import Z = A.C;\n"
"export {Z};\n"
"import {B} from 'bar';\n"
"import Y = B.C.Z;\n"
"\n"
"console.log(Z);\n");
}
} // end namespace
} // end namespace format
} // end namespace clang