clang-format: [JS] Sort imported symbols.
Summary: E.g. sort `import {b, a} from 'x';` into `import {a, b} from 'x';`.
Reviewers: djasper
Subscribers: cfe-commits, klimek
Differential Revision: http://reviews.llvm.org/D20798
llvm-svn: 271400
			
			
This commit is contained in:
		
							parent
							
								
									256845036d
								
							
						
					
					
						commit
						081f176a62
					
				| 
						 | 
				
			
			@ -34,6 +34,7 @@
 | 
			
		|||
#include "llvm/Support/Path.h"
 | 
			
		||||
#include "llvm/Support/Regex.h"
 | 
			
		||||
#include "llvm/Support/YAMLTraits.h"
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <queue>
 | 
			
		||||
#include <string>
 | 
			
		||||
| 
						 | 
				
			
			@ -1225,14 +1226,7 @@ static void sortCppIncludes(const FormatStyle &Style,
 | 
			
		|||
 | 
			
		||||
  // If the #includes are out of order, we generate a single replacement fixing
 | 
			
		||||
  // the entire block. Otherwise, no replacement is generated.
 | 
			
		||||
  bool OutOfOrder = false;
 | 
			
		||||
  for (unsigned i = 1, e = Indices.size(); i != e; ++i) {
 | 
			
		||||
    if (Indices[i] != i) {
 | 
			
		||||
      OutOfOrder = true;
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (!OutOfOrder)
 | 
			
		||||
  if (std::is_sorted(Indices.begin(), Indices.end()))
 | 
			
		||||
    return;
 | 
			
		||||
 | 
			
		||||
  std::string result;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,6 +25,7 @@
 | 
			
		|||
#include "llvm/ADT/STLExtras.h"
 | 
			
		||||
#include "llvm/ADT/SmallVector.h"
 | 
			
		||||
#include "llvm/Support/Debug.h"
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
#define DEBUG_TYPE "format-formatter"
 | 
			
		||||
| 
						 | 
				
			
			@ -40,6 +41,13 @@ using clang::format::FormatStyle;
 | 
			
		|||
struct JsImportedSymbol {
 | 
			
		||||
  StringRef Symbol;
 | 
			
		||||
  StringRef Alias;
 | 
			
		||||
  SourceRange Range;
 | 
			
		||||
 | 
			
		||||
  bool operator==(const JsImportedSymbol &RHS) const {
 | 
			
		||||
    // Ignore Range for comparison, it is only used to stitch code together,
 | 
			
		||||
    // but imports at different code locations are still conceptually the same.
 | 
			
		||||
    return Symbol == RHS.Symbol && Alias == RHS.Alias;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// An ES6 module reference.
 | 
			
		||||
| 
						 | 
				
			
			@ -139,23 +147,14 @@ public:
 | 
			
		|||
                     [&](unsigned LHSI, unsigned RHSI) {
 | 
			
		||||
                       return References[LHSI] < References[RHSI];
 | 
			
		||||
                     });
 | 
			
		||||
    // FIXME: Pull this into a common function.
 | 
			
		||||
    bool OutOfOrder = false;
 | 
			
		||||
    for (unsigned i = 0, e = Indices.size(); i != e; ++i) {
 | 
			
		||||
      if (i != Indices[i]) {
 | 
			
		||||
        OutOfOrder = true;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (!OutOfOrder)
 | 
			
		||||
      return Result;
 | 
			
		||||
    bool ReferencesInOrder = std::is_sorted(Indices.begin(), Indices.end());
 | 
			
		||||
 | 
			
		||||
    // Replace all existing import/export statements.
 | 
			
		||||
    std::string ReferencesText;
 | 
			
		||||
    bool SymbolsInOrder = true;
 | 
			
		||||
    for (unsigned i = 0, e = Indices.size(); i != e; ++i) {
 | 
			
		||||
      JsModuleReference Reference = References[Indices[i]];
 | 
			
		||||
      StringRef ReferenceStmt = getSourceText(Reference.Range);
 | 
			
		||||
      ReferencesText += ReferenceStmt;
 | 
			
		||||
      if (appendReference(ReferencesText, Reference))
 | 
			
		||||
        SymbolsInOrder = false;
 | 
			
		||||
      if (i + 1 < e) {
 | 
			
		||||
        // Insert breaks between imports and exports.
 | 
			
		||||
        ReferencesText += "\n";
 | 
			
		||||
| 
						 | 
				
			
			@ -167,6 +166,10 @@ public:
 | 
			
		|||
          ReferencesText += "\n";
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (ReferencesInOrder && SymbolsInOrder)
 | 
			
		||||
      return Result;
 | 
			
		||||
 | 
			
		||||
    // Separate references from the main code body of the file.
 | 
			
		||||
    if (FirstNonImportLine && FirstNonImportLine->First->NewlinesBefore < 2)
 | 
			
		||||
      ReferencesText += "\n";
 | 
			
		||||
| 
						 | 
				
			
			@ -211,10 +214,45 @@ private:
 | 
			
		|||
  }
 | 
			
		||||
 | 
			
		||||
  StringRef getSourceText(SourceRange Range) {
 | 
			
		||||
    return getSourceText(Range.getBegin(), Range.getEnd());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  StringRef getSourceText(SourceLocation Begin, SourceLocation End) {
 | 
			
		||||
    const SourceManager &SM = Env.getSourceManager();
 | 
			
		||||
    return FileContents.substr(SM.getFileOffset(Range.getBegin()),
 | 
			
		||||
                               SM.getFileOffset(Range.getEnd()) -
 | 
			
		||||
                                   SM.getFileOffset(Range.getBegin()));
 | 
			
		||||
    return FileContents.substr(SM.getFileOffset(Begin),
 | 
			
		||||
                               SM.getFileOffset(End) - SM.getFileOffset(Begin));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Appends ``Reference`` to ``Buffer``, returning true if text within the
 | 
			
		||||
  // ``Reference`` changed (e.g. symbol order).
 | 
			
		||||
  bool appendReference(std::string &Buffer, JsModuleReference &Reference) {
 | 
			
		||||
    // Sort the individual symbols within the import.
 | 
			
		||||
    // E.g. `import {b, a} from 'x';` -> `import {a, b} from 'x';`
 | 
			
		||||
    SmallVector<JsImportedSymbol, 1> Symbols = Reference.Symbols;
 | 
			
		||||
    std::stable_sort(
 | 
			
		||||
        Symbols.begin(), Symbols.end(),
 | 
			
		||||
        [&](const JsImportedSymbol &LHS, const JsImportedSymbol &RHS) {
 | 
			
		||||
          return LHS.Symbol < RHS.Symbol;
 | 
			
		||||
        });
 | 
			
		||||
    if (Symbols == Reference.Symbols) {
 | 
			
		||||
      // No change in symbol order.
 | 
			
		||||
      StringRef ReferenceStmt = getSourceText(Reference.Range);
 | 
			
		||||
      Buffer += ReferenceStmt;
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
    // Stitch together the module reference start...
 | 
			
		||||
    SourceLocation SymbolsStart = Reference.Symbols.front().Range.getBegin();
 | 
			
		||||
    SourceLocation SymbolsEnd = Reference.Symbols.back().Range.getEnd();
 | 
			
		||||
    Buffer += getSourceText(Reference.Range.getBegin(), SymbolsStart);
 | 
			
		||||
    // ... then the references in order ...
 | 
			
		||||
    for (auto *I = Symbols.begin(), *E = Symbols.end(); I != E; ++I) {
 | 
			
		||||
      if (I != Symbols.begin())
 | 
			
		||||
        Buffer += ",";
 | 
			
		||||
      Buffer += getSourceText(I->Range);
 | 
			
		||||
    }
 | 
			
		||||
    // ... followed by the module reference end.
 | 
			
		||||
    Buffer += getSourceText(SymbolsEnd, Reference.Range.getEnd());
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Parses module references in the given lines. Returns the module references,
 | 
			
		||||
| 
						 | 
				
			
			@ -350,6 +388,9 @@ private:
 | 
			
		|||
 | 
			
		||||
      JsImportedSymbol Symbol;
 | 
			
		||||
      Symbol.Symbol = Current->TokenText;
 | 
			
		||||
      // Make sure to include any preceding comments.
 | 
			
		||||
      Symbol.Range.setBegin(
 | 
			
		||||
          Current->getPreviousNonComment()->Next->WhitespaceRange.getBegin());
 | 
			
		||||
      nextToken();
 | 
			
		||||
 | 
			
		||||
      if (Current->is(Keywords.kw_as)) {
 | 
			
		||||
| 
						 | 
				
			
			@ -359,6 +400,7 @@ private:
 | 
			
		|||
        Symbol.Alias = Current->TokenText;
 | 
			
		||||
        nextToken();
 | 
			
		||||
      }
 | 
			
		||||
      Symbol.Range.setEnd(Current->Tok.getLocation());
 | 
			
		||||
      Reference.Symbols.push_back(Symbol);
 | 
			
		||||
 | 
			
		||||
      if (Current->is(tok::r_brace))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -67,6 +67,21 @@ TEST_F(SortImportsTestJS, BasicSorting) {
 | 
			
		|||
             "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"
 | 
			
		||||
| 
						 | 
				
			
			@ -101,6 +116,18 @@ TEST_F(SortImportsTestJS, AliasesSymbols) {
 | 
			
		|||
             "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"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue