From 7b8989f79316d71b7bcec7686aee0f89af0fe786 Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Tue, 16 Nov 2021 21:23:24 +0100 Subject: [PATCH 1/8] implements #110 * The relative order of side effect and non side effect imports is now preseverd by default. See the readme and #110 for more info. * I renamed the `getSortedNodes` method to `getSortedNodesByImportOrder`; and added a new implementation for `getSortedNodes` that first splits the imports at each side effect node, then sorts each chunk via the original `getSortedNodesByImportOrder`. I also moved the logic for adjusting comments and inserting new lines to the new `getSortedNodes` method. * With the exception of one test case (`imports-with-comments-and-third-party` has a side effect import `import "./commands"`), all existing tests still pass without any modifications. This leads me to believe you haven't considered these kind of import statements yet and how they should be treated. So this PR could be thought of as defining the behavior for that scenario. * I also added a few additional tests for the new behavior with side effect imports. --- README.md | 30 +- package.json | 2 +- src/constants.ts | 3 + src/types.ts | 5 + .../get-sorted-nodes-by-import-order.spec.ts | 285 ++++++++++++++++++ src/utils/__tests__/get-sorted-nodes.spec.ts | 241 +-------------- src/utils/adjust-comments-on-sorted-nodes.ts | 39 +++ src/utils/get-sorted-nodes-by-import-order.ts | 74 +++++ src/utils/get-sorted-nodes.ts | 129 +++----- tests/Angular/__snapshots__/ppsi.spec.js.snap | 143 +++++++++ .../imports-with-side-effect-imports.js | 65 ++++ tests/Babel/__snapshots__/ppsi.spec.js.snap | 98 ++++++ .../Babel/imports-with-side-effect-imports.js | 43 +++ tests/Flow/__snapshots__/ppsi.spec.js.snap | 130 ++++++++ .../Flow/imports-with-side-effect-imports.js | 58 ++++ .../__snapshots__/ppsi.spec.js.snap | 2 +- .../__snapshots__/ppsi.spec.js.snap | 2 +- .../__snapshots__/ppsi.spec.js.snap | 7 +- .../import-export-in-between.ts | 1 + .../__snapshots__/ppsi.spec.js.snap | 4 +- .../__snapshots__/ppsi.spec.js.snap | 114 +++++++ .../import-with-side-effect-imports.ts | 51 ++++ 22 files changed, 1210 insertions(+), 316 deletions(-) create mode 100644 src/utils/__tests__/get-sorted-nodes-by-import-order.spec.ts create mode 100644 src/utils/adjust-comments-on-sorted-nodes.ts create mode 100644 src/utils/get-sorted-nodes-by-import-order.ts create mode 100644 tests/Angular/imports-with-side-effect-imports.js create mode 100644 tests/Babel/imports-with-side-effect-imports.js create mode 100644 tests/Flow/imports-with-side-effect-imports.js create mode 100644 tests/Typescript/import-with-side-effect-imports.ts diff --git a/README.md b/README.md index 9c8ca5f0..3b72e944 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,35 @@ importOrderParserPlugins: [] The plugin extracts the imports which are defined in `importOrder`. These imports are considered as _local imports_. The imports which are not part of the `importOrder` is considered as _third party imports_. -After, the plugin sorts the _local imports_ and _third party imports_ using [natural sort algorithm](https://en.wikipedia.org/wiki/Natural_sort_order). +First, the plugin checks for +[side effect imports](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#import_a_module_for_its_side_effects_only), +such as `import 'mock-fs'`. These imports often modify the global scope or apply some patches to the current +environment, which may affect other imports. To preserve potential side effects, these kind of side effect imports are +not sorted. They also behave as a barrier that other imports may not cross during the sort. So for example, let's say +you've got these imports: + +```javascript +import E from 'e'; +import F from 'f'; +import D from 'd'; +import 'c'; +import B from 'b'; +import A from 'a'; +``` + +Then the first three imports are sorted and the last two imports are sorted, but all imports above `c` stay above `c` +and all imports below `c` stay below `c`, resulting in: + +```javascript +import D from 'd'; +import E from 'e'; +import F from 'f'; +import 'c'; +import A from 'a'; +import B from 'b'; +``` + +Next, the plugin sorts the _local imports_ and _third party imports_ using [natural sort algorithm](https://en.wikipedia.org/wiki/Natural_sort_order). In the end, the plugin returns final imports with _third party imports_ on top and _local imports_ at the end. diff --git a/package.json b/package.json index 73a591cb..adfad031 100644 --- a/package.json +++ b/package.json @@ -43,8 +43,8 @@ "devDependencies": { "@types/chai": "4.2.15", "@types/jest": "26.0.20", - "@types/node": "14.14.34", "@types/lodash": "4.14.168", + "@types/node": "14.14.34", "jest": "26.6.3", "prettier": "2.3.1", "ts-jest": "26.5.3", diff --git a/src/constants.ts b/src/constants.ts index e04f402c..f5d901c8 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -7,6 +7,9 @@ export const jsx: ParserPlugin = 'jsx'; export const newLineCharacters = '\n\n'; +export const ChunkSideEffectNode = 'side-effect-node'; +export const ChunkSideOtherNode = 'other-node'; + /* * Used to mark the position between RegExps, * where the not matched imports should be placed diff --git a/src/types.ts b/src/types.ts index 095918d0..72dac645 100644 --- a/src/types.ts +++ b/src/types.ts @@ -10,6 +10,11 @@ export interface PrettierOptions extends RequiredOptions { importOrderSortSpecifiers: boolean; } +export interface ImportChunk { + nodes: ImportDeclaration[]; + type: string; +} + export type ImportGroups = Record; export type ImportOrLine = ImportDeclaration | ExpressionStatement; diff --git a/src/utils/__tests__/get-sorted-nodes-by-import-order.spec.ts b/src/utils/__tests__/get-sorted-nodes-by-import-order.spec.ts new file mode 100644 index 00000000..c655a298 --- /dev/null +++ b/src/utils/__tests__/get-sorted-nodes-by-import-order.spec.ts @@ -0,0 +1,285 @@ +import { ImportDeclaration } from '@babel/types'; + +import { getImportNodes } from '../get-import-nodes'; +import { getSortedNodesByImportOrder } from '../get-sorted-nodes-by-import-order'; +import { getSortedNodesModulesNames } from '../get-sorted-nodes-modules-names'; +import { getSortedNodesNames } from '../get-sorted-nodes-names'; + +const code = `// first comment +// second comment +import z from 'z'; +import c, { cD } from 'c'; +import g from 'g'; +import { tC, tA, tB } from 't'; +import k, { kE, kB } from 'k'; +import * as a from 'a'; +import BY from 'BY'; +import Ba from 'Ba'; +import XY from 'XY'; +import Xa from 'Xa'; +`; + +test('it returns all sorted nodes', () => { + const result = getImportNodes(code); + const sorted = getSortedNodesByImportOrder(result, { + importOrder: [], + importOrderCaseInsensitive: false, + importOrderSeparation: false, + importOrderSortSpecifiers: false, + }) as ImportDeclaration[]; + + expect(getSortedNodesNames(sorted)).toEqual([ + 'BY', + 'Ba', + 'XY', + 'Xa', + 'a', + 'c', + 'g', + 'k', + 't', + 'z', + ]); + expect( + sorted + .filter((node) => node.type === 'ImportDeclaration') + .map((importDeclaration) => + getSortedNodesModulesNames(importDeclaration.specifiers), + ), + ).toEqual([ + ['BY'], + ['Ba'], + ['XY'], + ['Xa'], + ['a'], + ['c', 'cD'], + ['g'], + ['k', 'kE', 'kB'], + ['tC', 'tA', 'tB'], + ['z'], + ]); +}); + +test('it returns all sorted nodes case-insensitive', () => { + const result = getImportNodes(code); + const sorted = getSortedNodesByImportOrder(result, { + importOrder: [], + importOrderCaseInsensitive: true, + importOrderSeparation: false, + importOrderSortSpecifiers: false, + }) as ImportDeclaration[]; + + expect(getSortedNodesNames(sorted)).toEqual([ + 'a', + 'Ba', + 'BY', + 'c', + 'g', + 'k', + 't', + 'Xa', + 'XY', + 'z', + ]); + expect( + sorted + .filter((node) => node.type === 'ImportDeclaration') + .map((importDeclaration) => + getSortedNodesModulesNames(importDeclaration.specifiers), + ), + ).toEqual([ + ['a'], + ['Ba'], + ['BY'], + ['c', 'cD'], + ['g'], + ['k', 'kE', 'kB'], + ['tC', 'tA', 'tB'], + ['Xa'], + ['XY'], + ['z'], + ]); +}); + +test('it returns all sorted nodes with sort order', () => { + const result = getImportNodes(code); + const sorted = getSortedNodesByImportOrder(result, { + importOrder: ['^a$', '^t$', '^k$', '^B'], + importOrderCaseInsensitive: false, + importOrderSeparation: false, + importOrderSortSpecifiers: false, + }) as ImportDeclaration[]; + + expect(getSortedNodesNames(sorted)).toEqual([ + 'XY', + 'Xa', + 'c', + 'g', + 'z', + 'a', + 't', + 'k', + 'BY', + 'Ba', + ]); + expect( + sorted + .filter((node) => node.type === 'ImportDeclaration') + .map((importDeclaration) => + getSortedNodesModulesNames(importDeclaration.specifiers), + ), + ).toEqual([ + ['XY'], + ['Xa'], + ['c', 'cD'], + ['g'], + ['z'], + ['a'], + ['tC', 'tA', 'tB'], + ['k', 'kE', 'kB'], + ['BY'], + ['Ba'], + ]); +}); + +test('it returns all sorted nodes with sort order case-insensitive', () => { + const result = getImportNodes(code); + const sorted = getSortedNodesByImportOrder(result, { + importOrder: ['^a$', '^t$', '^k$', '^B'], + importOrderCaseInsensitive: true, + importOrderSeparation: false, + importOrderSortSpecifiers: false, + }) as ImportDeclaration[]; + expect(getSortedNodesNames(sorted)).toEqual([ + 'c', + 'g', + 'Xa', + 'XY', + 'z', + 'a', + 't', + 'k', + 'Ba', + 'BY', + ]); + expect( + sorted + .filter((node) => node.type === 'ImportDeclaration') + .map((importDeclaration) => + getSortedNodesModulesNames(importDeclaration.specifiers), + ), + ).toEqual([ + ['c', 'cD'], + ['g'], + ['Xa'], + ['XY'], + ['z'], + ['a'], + ['tC', 'tA', 'tB'], + ['k', 'kE', 'kB'], + ['Ba'], + ['BY'], + ]); +}); + +test('it returns all sorted import nodes with sorted import specifiers', () => { + const result = getImportNodes(code); + const sorted = getSortedNodesByImportOrder(result, { + importOrder: ['^a$', '^t$', '^k$', '^B'], + importOrderCaseInsensitive: false, + importOrderSeparation: false, + importOrderSortSpecifiers: true, + }) as ImportDeclaration[]; + expect(getSortedNodesNames(sorted)).toEqual([ + 'XY', + 'Xa', + 'c', + 'g', + 'z', + 'a', + 't', + 'k', + 'BY', + 'Ba', + ]); + expect( + sorted + .filter((node) => node.type === 'ImportDeclaration') + .map((importDeclaration) => + getSortedNodesModulesNames(importDeclaration.specifiers), + ), + ).toEqual([ + ['XY'], + ['Xa'], + ['c', 'cD'], + ['g'], + ['z'], + ['a'], + ['tA', 'tB', 'tC'], + ['k', 'kB', 'kE'], + ['BY'], + ['Ba'], + ]); +}); + +test('it returns all sorted import nodes with sorted import specifiers with case-insensitive ', () => { + const result = getImportNodes(code); + const sorted = getSortedNodesByImportOrder(result, { + importOrder: ['^a$', '^t$', '^k$', '^B'], + importOrderCaseInsensitive: true, + importOrderSeparation: false, + importOrderSortSpecifiers: true, + }) as ImportDeclaration[]; + expect(getSortedNodesNames(sorted)).toEqual([ + 'c', + 'g', + 'Xa', + 'XY', + 'z', + 'a', + 't', + 'k', + 'Ba', + 'BY', + ]); + expect( + sorted + .filter((node) => node.type === 'ImportDeclaration') + .map((importDeclaration) => + getSortedNodesModulesNames(importDeclaration.specifiers), + ), + ).toEqual([ + ['c', 'cD'], + ['g'], + ['Xa'], + ['XY'], + ['z'], + ['a'], + ['tA', 'tB', 'tC'], + ['k', 'kB', 'kE'], + ['Ba'], + ['BY'], + ]); +}); + +test('it returns all sorted nodes with custom third party modules', () => { + const result = getImportNodes(code); + const sorted = getSortedNodesByImportOrder(result, { + importOrder: ['^a$', '', '^t$', '^k$'], + importOrderSeparation: false, + importOrderCaseInsensitive: true, + importOrderSortSpecifiers: false, + }) as ImportDeclaration[]; + expect(getSortedNodesNames(sorted)).toEqual([ + 'a', + 'Ba', + 'BY', + 'c', + 'g', + 'Xa', + 'XY', + 'z', + 't', + 'k', + ]); +}); diff --git a/src/utils/__tests__/get-sorted-nodes.spec.ts b/src/utils/__tests__/get-sorted-nodes.spec.ts index 746ed6e0..0538f65a 100644 --- a/src/utils/__tests__/get-sorted-nodes.spec.ts +++ b/src/utils/__tests__/get-sorted-nodes.spec.ts @@ -7,19 +7,23 @@ import { getSortedNodesNames } from '../get-sorted-nodes-names'; const code = `// first comment // second comment +import "se3"; import z from 'z'; import c, { cD } from 'c'; import g from 'g'; import { tC, tA, tB } from 't'; import k, { kE, kB } from 'k'; +import "se4"; +import "se1"; import * as a from 'a'; import BY from 'BY'; import Ba from 'Ba'; import XY from 'XY'; import Xa from 'Xa'; +import "se2"; `; -test('it returns all sorted nodes', () => { +test('it returns all sorted nodes, preserving the order side effect nodes', () => { const result = getImportNodes(code); const sorted = getSortedNodes(result, { importOrder: [], @@ -29,98 +33,20 @@ test('it returns all sorted nodes', () => { }) as ImportDeclaration[]; expect(getSortedNodesNames(sorted)).toEqual([ - 'BY', - 'Ba', - 'XY', - 'Xa', - 'a', + "se3", 'c', 'g', 'k', 't', 'z', - ]); - expect( - sorted - .filter((node) => node.type === 'ImportDeclaration') - .map((importDeclaration) => - getSortedNodesModulesNames(importDeclaration.specifiers), - ), - ).toEqual([ - ['BY'], - ['Ba'], - ['XY'], - ['Xa'], - ['a'], - ['c', 'cD'], - ['g'], - ['k', 'kE', 'kB'], - ['tC', 'tA', 'tB'], - ['z'], - ]); -}); - -test('it returns all sorted nodes case-insensitive', () => { - const result = getImportNodes(code); - const sorted = getSortedNodes(result, { - importOrder: [], - importOrderCaseInsensitive: true, - importOrderSeparation: false, - importOrderSortSpecifiers: false, - }) as ImportDeclaration[]; - - expect(getSortedNodesNames(sorted)).toEqual([ - 'a', - 'Ba', + "se4", + "se1", 'BY', - 'c', - 'g', - 'k', - 't', - 'Xa', - 'XY', - 'z', - ]); - expect( - sorted - .filter((node) => node.type === 'ImportDeclaration') - .map((importDeclaration) => - getSortedNodesModulesNames(importDeclaration.specifiers), - ), - ).toEqual([ - ['a'], - ['Ba'], - ['BY'], - ['c', 'cD'], - ['g'], - ['k', 'kE', 'kB'], - ['tC', 'tA', 'tB'], - ['Xa'], - ['XY'], - ['z'], - ]); -}); - -test('it returns all sorted nodes with sort order', () => { - const result = getImportNodes(code); - const sorted = getSortedNodes(result, { - importOrder: ['^a$', '^t$', '^k$', '^B'], - importOrderCaseInsensitive: false, - importOrderSeparation: false, - importOrderSortSpecifiers: false, - }) as ImportDeclaration[]; - - expect(getSortedNodesNames(sorted)).toEqual([ + 'Ba', 'XY', 'Xa', - 'c', - 'g', - 'z', 'a', - 't', - 'k', - 'BY', - 'Ba', + "se2", ]); expect( sorted @@ -129,157 +55,20 @@ test('it returns all sorted nodes with sort order', () => { getSortedNodesModulesNames(importDeclaration.specifiers), ), ).toEqual([ - ['XY'], - ['Xa'], + [], ['c', 'cD'], ['g'], - ['z'], - ['a'], - ['tC', 'tA', 'tB'], ['k', 'kE', 'kB'], - ['BY'], - ['Ba'], - ]); -}); - -test('it returns all sorted nodes with sort order case-insensitive', () => { - const result = getImportNodes(code); - const sorted = getSortedNodes(result, { - importOrder: ['^a$', '^t$', '^k$', '^B'], - importOrderCaseInsensitive: true, - importOrderSeparation: false, - importOrderSortSpecifiers: false, - }) as ImportDeclaration[]; - expect(getSortedNodesNames(sorted)).toEqual([ - 'c', - 'g', - 'Xa', - 'XY', - 'z', - 'a', - 't', - 'k', - 'Ba', - 'BY', - ]); - expect( - sorted - .filter((node) => node.type === 'ImportDeclaration') - .map((importDeclaration) => - getSortedNodesModulesNames(importDeclaration.specifiers), - ), - ).toEqual([ - ['c', 'cD'], - ['g'], - ['Xa'], - ['XY'], - ['z'], - ['a'], ['tC', 'tA', 'tB'], - ['k', 'kE', 'kB'], - ['Ba'], - ['BY'], - ]); -}); - -test('it returns all sorted import nodes with sorted import specifiers', () => { - const result = getImportNodes(code); - const sorted = getSortedNodes(result, { - importOrder: ['^a$', '^t$', '^k$', '^B'], - importOrderCaseInsensitive: false, - importOrderSeparation: false, - importOrderSortSpecifiers: true, - }) as ImportDeclaration[]; - expect(getSortedNodesNames(sorted)).toEqual([ - 'XY', - 'Xa', - 'c', - 'g', - 'z', - 'a', - 't', - 'k', - 'BY', - 'Ba', - ]); - expect( - sorted - .filter((node) => node.type === 'ImportDeclaration') - .map((importDeclaration) => - getSortedNodesModulesNames(importDeclaration.specifiers), - ), - ).toEqual([ - ['XY'], - ['Xa'], - ['c', 'cD'], - ['g'], ['z'], - ['a'], - ['tA', 'tB', 'tC'], - ['k', 'kB', 'kE'], + [], + [], ['BY'], ['Ba'], - ]); -}); - -test('it returns all sorted import nodes with sorted import specifiers with case-insensitive ', () => { - const result = getImportNodes(code); - const sorted = getSortedNodes(result, { - importOrder: ['^a$', '^t$', '^k$', '^B'], - importOrderCaseInsensitive: true, - importOrderSeparation: false, - importOrderSortSpecifiers: true, - }) as ImportDeclaration[]; - expect(getSortedNodesNames(sorted)).toEqual([ - 'c', - 'g', - 'Xa', - 'XY', - 'z', - 'a', - 't', - 'k', - 'Ba', - 'BY', - ]); - expect( - sorted - .filter((node) => node.type === 'ImportDeclaration') - .map((importDeclaration) => - getSortedNodesModulesNames(importDeclaration.specifiers), - ), - ).toEqual([ - ['c', 'cD'], - ['g'], - ['Xa'], ['XY'], - ['z'], + ['Xa'], ['a'], - ['tA', 'tB', 'tC'], - ['k', 'kB', 'kE'], - ['Ba'], - ['BY'], + [], ]); }); -test('it returns all sorted nodes with custom third party modules', () => { - const result = getImportNodes(code); - const sorted = getSortedNodes(result, { - importOrder: ['^a$', '', '^t$', '^k$'], - importOrderSeparation: false, - importOrderCaseInsensitive: true, - importOrderSortSpecifiers: false, - }) as ImportDeclaration[]; - expect(getSortedNodesNames(sorted)).toEqual([ - 'a', - 'Ba', - 'BY', - 'c', - 'g', - 'Xa', - 'XY', - 'z', - 't', - 'k', - ]); -}); diff --git a/src/utils/adjust-comments-on-sorted-nodes.ts b/src/utils/adjust-comments-on-sorted-nodes.ts new file mode 100644 index 00000000..39a9c6d9 --- /dev/null +++ b/src/utils/adjust-comments-on-sorted-nodes.ts @@ -0,0 +1,39 @@ +import { addComments, ImportDeclaration, removeComments } from '@babel/types'; +import { clone, isEqual } from 'lodash'; + +import { ImportOrLine } from "../types"; + +/** + * Takes the original nodes before sorting and the final nodes after sorting. + * Adjusts the comments on the final nodes so that they match the comments as + * they were in the original nodes. + * @param nodes A list of nodes in the order as they were originally. + * @param finalNodes The same set of nodes, but in the final sorting order. + */ +export const adjustCommentsOnSortedNodes = ( + nodes: ImportDeclaration[], + finalNodes: ImportOrLine[] +) => { + // maintain a copy of the nodes to extract comments from + const finalNodesClone = finalNodes.map(clone); + + const firstNodesComments = nodes[0].leadingComments; + + // Remove all comments from sorted nodes + finalNodes.forEach(removeComments); + + // insert comments other than the first comments + finalNodes.forEach((node, index) => { + if (isEqual(nodes[0].loc, node.loc)) return; + + addComments( + node, + 'leading', + finalNodesClone[index].leadingComments || [], + ); + }); + + if (firstNodesComments) { + addComments(finalNodes[0], 'leading', firstNodesComments); + } +} \ No newline at end of file diff --git a/src/utils/get-sorted-nodes-by-import-order.ts b/src/utils/get-sorted-nodes-by-import-order.ts new file mode 100644 index 00000000..85ac9d49 --- /dev/null +++ b/src/utils/get-sorted-nodes-by-import-order.ts @@ -0,0 +1,74 @@ +import { clone } from 'lodash'; + +import { THIRD_PARTY_MODULES_SPECIAL_WORD, newLineNode } from '../constants'; +import { naturalSort } from '../natural-sort'; +import { GetSortedNodes, ImportGroups, ImportOrLine } from '../types'; +import { getImportNodesMatchedGroup } from './get-import-nodes-matched-group'; +import { getSortedImportSpecifiers } from './get-sorted-import-specifiers'; + +/** + * This function returns the given nodes, sorted in the order as indicated by + * the importOrder array from the given options. + * The plugin considers these import nodes as local import declarations. + * @param nodes A subset of all import nodes that should be sorted. + * @param options Options to influence the behavior of the sorting algorithm. + */ +export const getSortedNodesByImportOrder: GetSortedNodes = (nodes, options) => { + naturalSort.insensitive = options.importOrderCaseInsensitive; + + let { importOrder } = options; + const { importOrderSeparation, importOrderSortSpecifiers } = options; + + const originalNodes = nodes.map(clone); + const finalNodes: ImportOrLine[] = []; + + if (!importOrder.includes(THIRD_PARTY_MODULES_SPECIAL_WORD)) { + importOrder = [THIRD_PARTY_MODULES_SPECIAL_WORD, ...importOrder]; + } + + const importOrderGroups = importOrder.reduce( + (groups, regexp) => ({ + ...groups, + [regexp]: [], + }), + {}, + ); + + const importOrderWithOutThirdPartyPlaceholder = importOrder.filter( + (group) => group !== THIRD_PARTY_MODULES_SPECIAL_WORD, + ); + + for (const node of originalNodes) { + const matchedGroup = getImportNodesMatchedGroup( + node, + importOrderWithOutThirdPartyPlaceholder, + ); + importOrderGroups[matchedGroup].push(node); + } + + for (const group of importOrder) { + const groupNodes = importOrderGroups[group]; + const last = group === importOrder[importOrder.length - 1]; + + if (groupNodes.length === 0) continue; + + const sortedInsideGroup = groupNodes.sort((a, b) => + naturalSort(a.source.value, b.source.value), + ); + + // Sort the import specifiers + if (importOrderSortSpecifiers) { + sortedInsideGroup.forEach((node) => + getSortedImportSpecifiers(node), + ); + } + + finalNodes.push(...sortedInsideGroup); + + if (importOrderSeparation && !last) { + finalNodes.push(newLineNode); + } + } + + return finalNodes; +}; diff --git a/src/utils/get-sorted-nodes.ts b/src/utils/get-sorted-nodes.ts index 61b5ff80..2db2af7d 100644 --- a/src/utils/get-sorted-nodes.ts +++ b/src/utils/get-sorted-nodes.ts @@ -1,101 +1,66 @@ -import { addComments, removeComments } from '@babel/types'; -import { clone, isEqual } from 'lodash'; - -import { THIRD_PARTY_MODULES_SPECIAL_WORD, newLineNode } from '../constants'; -import { naturalSort } from '../natural-sort'; -import { GetSortedNodes, ImportGroups, ImportOrLine } from '../types'; -import { getImportNodesMatchedGroup } from './get-import-nodes-matched-group'; -import { getSortedImportSpecifiers } from './get-sorted-import-specifiers'; +import { ChunkSideEffectNode, ChunkSideOtherNode, newLineNode } from "../constants"; +import { GetSortedNodes, ImportChunk, ImportOrLine } from "../types"; +import { adjustCommentsOnSortedNodes } from "./adjust-comments-on-sorted-nodes"; +import { getSortedNodesByImportOrder } from "./get-sorted-nodes-by-import-order"; /** - * This function returns all the nodes which are in the importOrder array. - * The plugin considered these import nodes as local import declarations. - * @param nodes all import nodes - * @param options + * This function returns the given nodes, sorted in the order as indicated by + * the importOrder array. The plugin considers these import nodes as local + * import declarations + * + * In addition, this method preserves the relative order of side effect imports + * and non side effect imports. A side effect import is an ImportDeclaration + * without any import specifiers. It does this by splitting the import nodes at + * each side effect node, then sorting only the non side effect import nodes + * between the side effect nodes according to the given options. + * @param nodes All import nodes that should be sorted. + * @param options Options to influence the behavior of the sorting algorithm. */ export const getSortedNodes: GetSortedNodes = (nodes, options) => { - naturalSort.insensitive = options.importOrderCaseInsensitive; - - let { importOrder } = options; - const { importOrderSeparation, importOrderSortSpecifiers } = options; + const { importOrderSeparation } = options; + + // Split nodes at each boundary between a side-effect node and a + // non-side-effect node, keeping both types of nodes together. + const splitBySideEffectNodes = + nodes.reduce((chunks, node) => { + const type = node.specifiers.length === 0 + ? ChunkSideEffectNode + : ChunkSideOtherNode; + const last = chunks[chunks.length - 1]; + if (last === undefined || last.type !== type) { + chunks.push({ type, nodes: [node] }); + } + else { + last.nodes.push(node); + } + return chunks; + }, []); - const originalNodes = nodes.map(clone); const finalNodes: ImportOrLine[] = []; - if (!importOrder.includes(THIRD_PARTY_MODULES_SPECIAL_WORD)) { - importOrder = [THIRD_PARTY_MODULES_SPECIAL_WORD, ...importOrder]; - } - - const importOrderGroups = importOrder.reduce( - (groups, regexp) => ({ - ...groups, - [regexp]: [], - }), - {}, - ); - - const importOrderWithOutThirdPartyPlaceholder = importOrder.filter( - (group) => group !== THIRD_PARTY_MODULES_SPECIAL_WORD, - ); - - for (const node of originalNodes) { - const matchedGroup = getImportNodesMatchedGroup( - node, - importOrderWithOutThirdPartyPlaceholder, - ); - importOrderGroups[matchedGroup].push(node); - } - - for (const group of importOrder) { - const groupNodes = importOrderGroups[group]; - - if (groupNodes.length === 0) continue; - - const sortedInsideGroup = groupNodes.sort((a, b) => - naturalSort(a.source.value, b.source.value), - ); - - // Sort the import specifiers - if (importOrderSortSpecifiers) { - sortedInsideGroup.forEach((node) => - getSortedImportSpecifiers(node), - ); + // Sort each chunk of side-effect and non-side-effect nodes, and insert new + // lines according the importOrderSeparation option. + for (const chunk of splitBySideEffectNodes) { + if (chunk.type === ChunkSideEffectNode) { + // do not sort side effect nodes + finalNodes.push(...chunk.nodes); + } + else { + // sort non-side effect nodes + const sorted = getSortedNodesByImportOrder(chunk.nodes, options); + finalNodes.push(...sorted); } - - finalNodes.push(...sortedInsideGroup); - if (importOrderSeparation) { finalNodes.push(newLineNode); } } if (finalNodes.length > 0 && !importOrderSeparation) { - // a newline after all imports finalNodes.push(newLineNode); } - // maintain a copy of the nodes to extract comments from - const finalNodesClone = finalNodes.map(clone); - - const firstNodesComments = nodes[0].leadingComments; - - // Remove all comments from sorted nodes - finalNodes.forEach(removeComments); - - // insert comments other than the first comments - finalNodes.forEach((node, index) => { - if (isEqual(nodes[0].loc, node.loc)) return; - - addComments( - node, - 'leading', - finalNodesClone[index].leadingComments || [], - ); - }); - - if (firstNodesComments) { - addComments(finalNodes[0], 'leading', firstNodesComments); - } + // Adjust the comments on the sorted nodes to match the original comments + adjustCommentsOnSortedNodes(nodes, finalNodes); return finalNodes; -}; +} \ No newline at end of file diff --git a/tests/Angular/__snapshots__/ppsi.spec.js.snap b/tests/Angular/__snapshots__/ppsi.spec.js.snap index d2a3cb5b..c20f99b5 100644 --- a/tests/Angular/__snapshots__/ppsi.spec.js.snap +++ b/tests/Angular/__snapshots__/ppsi.spec.js.snap @@ -80,3 +80,146 @@ export class TemplateController { } `; + +exports[`imports-with-side-effect-imports.js - typescript-verify: imports-with-side-effect-imports.js 1`] = ` +// I am top level comment in this file. +import thirdParty0 from "third-party0"; +import something3 from "@core/something3"; +import thirdDisco0 from "third-disco0"; +import otherthing3 from "@core/otherthing3"; + +import "side-effect-z"; + +import anotherSameLevelRelativePath3 from "./anotherSameLevelRelativePath3"; +import something0 from "@core/something0"; +import thirdDisco1 from "third-disco1"; +import otherthing0 from "@core/otherthing0"; +import sameLevelRelativePath3 from "./sameLevelRelativePath3"; +import thirdParty1 from "third-party1"; +import oneLevelRelativePath1 from "../oneLevelRelativePath1"; +import anotherOneLevelRelativePath1 from "../anotherOneLevelRelativePath1"; + +import "side-effect-y"; + +import oneLevelRelativePath2 from "../oneLevelRelativePath2"; +import anotherOneLevelRelativePath2 from "../anotherOneLevelRelativePath2"; +import something2 from "@core/something2"; +import thirdParty3 from "third-party3"; +import anotherSameLevelRelativePath1 from "./anotherSameLevelRelativePath1"; +import sameLevelRelativePath1 from "./sameLevelRelativePath1"; +import otherthing2 from "@core/otherthing2"; +import thirdDisco3 from "third-disco3"; + +import "side-effect-x"; +import anotherSameLevelRelativePath2 from "./anotherSameLevelRelativePath2"; +import sameLevelRelativePath2 from "./sameLevelRelativePath2"; +import something1 from "@core/something1"; +import oneLevelRelativePath3 from "../oneLevelRelativePath3"; +import anotherOneLevelRelativePath3 from "../anotherOneLevelRelativePath3"; +import otherthing1 from "@core/otherthing1"; +import thirdDisco2 from "third-disco2"; +import thirdParty2 from "third-party2"; + +import { Component } from "@angular/core"; + +@Controller("/design/template") +export class TemplateController { + requestFile: (url) => Promise; + CanvasKit: CanvasKit; + + constructor(private templateService: TemplateService, private _httpService: HttpService, + private _canvaskitService: CanvasKitService) { + + + this.CanvasKit = this._canvaskitService.canvasKit; + this.requestFile = (url) => { + const req = this._httpService + .get(\`http:\${url}\`, { responseType: "arraybuffer" }) + .pipe(retry(3)) + .toPromise(); + return new Promise((resolve, reject) => { + req + .then((resp) => { + resolve(resp.data); + }) + .catch(reject); + }); + }; + } +} +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// I am top level comment in this file. +import thirdDisco0 from "third-disco0"; +import thirdParty0 from "third-party0"; + +import otherthing3 from "@core/otherthing3"; +import something3 from "@core/something3"; + +import "side-effect-z"; + +import thirdDisco1 from "third-disco1"; +import thirdParty1 from "third-party1"; + +import otherthing0 from "@core/otherthing0"; +import something0 from "@core/something0"; + +import anotherOneLevelRelativePath1 from "../anotherOneLevelRelativePath1"; +import oneLevelRelativePath1 from "../oneLevelRelativePath1"; +import anotherSameLevelRelativePath3 from "./anotherSameLevelRelativePath3"; +import sameLevelRelativePath3 from "./sameLevelRelativePath3"; + +import "side-effect-y"; + +import thirdDisco3 from "third-disco3"; +import thirdParty3 from "third-party3"; + +import otherthing2 from "@core/otherthing2"; +import something2 from "@core/something2"; + +import anotherOneLevelRelativePath2 from "../anotherOneLevelRelativePath2"; +import oneLevelRelativePath2 from "../oneLevelRelativePath2"; +import anotherSameLevelRelativePath1 from "./anotherSameLevelRelativePath1"; +import sameLevelRelativePath1 from "./sameLevelRelativePath1"; + +import "side-effect-x"; + +import { Component } from "@angular/core"; +import thirdDisco2 from "third-disco2"; +import thirdParty2 from "third-party2"; + +import otherthing1 from "@core/otherthing1"; +import something1 from "@core/something1"; + +import anotherOneLevelRelativePath3 from "../anotherOneLevelRelativePath3"; +import oneLevelRelativePath3 from "../oneLevelRelativePath3"; +import anotherSameLevelRelativePath2 from "./anotherSameLevelRelativePath2"; +import sameLevelRelativePath2 from "./sameLevelRelativePath2"; + +@Controller("/design/template") +export class TemplateController { + requestFile: (url) => Promise; + CanvasKit: CanvasKit; + + constructor( + private templateService: TemplateService, + private _httpService: HttpService, + private _canvaskitService: CanvasKitService + ) { + this.CanvasKit = this._canvaskitService.canvasKit; + this.requestFile = (url) => { + const req = this._httpService + .get(\`http:\${url}\`, { + responseType: "arraybuffer", + }) + .pipe(retry(3)) + .toPromise(); + return new Promise((resolve, reject) => { + req.then((resp) => { + resolve(resp.data); + }).catch(reject); + }); + }; + } +} + +`; diff --git a/tests/Angular/imports-with-side-effect-imports.js b/tests/Angular/imports-with-side-effect-imports.js new file mode 100644 index 00000000..11561f03 --- /dev/null +++ b/tests/Angular/imports-with-side-effect-imports.js @@ -0,0 +1,65 @@ +// I am top level comment in this file. +import thirdParty0 from "third-party0"; +import something3 from "@core/something3"; +import thirdDisco0 from "third-disco0"; +import otherthing3 from "@core/otherthing3"; + +import "side-effect-z"; + +import anotherSameLevelRelativePath3 from "./anotherSameLevelRelativePath3"; +import something0 from "@core/something0"; +import thirdDisco1 from "third-disco1"; +import otherthing0 from "@core/otherthing0"; +import sameLevelRelativePath3 from "./sameLevelRelativePath3"; +import thirdParty1 from "third-party1"; +import oneLevelRelativePath1 from "../oneLevelRelativePath1"; +import anotherOneLevelRelativePath1 from "../anotherOneLevelRelativePath1"; + +import "side-effect-y"; + +import oneLevelRelativePath2 from "../oneLevelRelativePath2"; +import anotherOneLevelRelativePath2 from "../anotherOneLevelRelativePath2"; +import something2 from "@core/something2"; +import thirdParty3 from "third-party3"; +import anotherSameLevelRelativePath1 from "./anotherSameLevelRelativePath1"; +import sameLevelRelativePath1 from "./sameLevelRelativePath1"; +import otherthing2 from "@core/otherthing2"; +import thirdDisco3 from "third-disco3"; + +import "side-effect-x"; +import anotherSameLevelRelativePath2 from "./anotherSameLevelRelativePath2"; +import sameLevelRelativePath2 from "./sameLevelRelativePath2"; +import something1 from "@core/something1"; +import oneLevelRelativePath3 from "../oneLevelRelativePath3"; +import anotherOneLevelRelativePath3 from "../anotherOneLevelRelativePath3"; +import otherthing1 from "@core/otherthing1"; +import thirdDisco2 from "third-disco2"; +import thirdParty2 from "third-party2"; + +import { Component } from "@angular/core"; + +@Controller("/design/template") +export class TemplateController { + requestFile: (url) => Promise; + CanvasKit: CanvasKit; + + constructor(private templateService: TemplateService, private _httpService: HttpService, + private _canvaskitService: CanvasKitService) { + + + this.CanvasKit = this._canvaskitService.canvasKit; + this.requestFile = (url) => { + const req = this._httpService + .get(`http:${url}`, { responseType: "arraybuffer" }) + .pipe(retry(3)) + .toPromise(); + return new Promise((resolve, reject) => { + req + .then((resp) => { + resolve(resp.data); + }) + .catch(reject); + }); + }; + } +} diff --git a/tests/Babel/__snapshots__/ppsi.spec.js.snap b/tests/Babel/__snapshots__/ppsi.spec.js.snap index 84c62858..fd4f06ae 100644 --- a/tests/Babel/__snapshots__/ppsi.spec.js.snap +++ b/tests/Babel/__snapshots__/ppsi.spec.js.snap @@ -42,3 +42,101 @@ function add(a, b) { } `; + +exports[`imports-with-side-effect-imports.js - babel-verify: imports-with-side-effect-imports.js 1`] = ` +// I am top level comment in this file. +import thirdParty0 from "third-party0"; +import something3 from "@core/something3"; +import thirdDisco0 from "third-disco0"; +import otherthing3 from "@core/otherthing3"; + +import "side-effect-z"; + +import anotherSameLevelRelativePath3 from "./anotherSameLevelRelativePath3"; +import something0 from "@core/something0"; +import thirdDisco1 from "third-disco1"; +import otherthing0 from "@core/otherthing0"; +import sameLevelRelativePath3 from "./sameLevelRelativePath3"; +import thirdParty1 from "third-party1"; +import oneLevelRelativePath1 from "../oneLevelRelativePath1"; +import anotherOneLevelRelativePath1 from "../anotherOneLevelRelativePath1"; + +import "side-effect-y"; + +import oneLevelRelativePath2 from "../oneLevelRelativePath2"; +import anotherOneLevelRelativePath2 from "../anotherOneLevelRelativePath2"; +import something2 from "@core/something2"; +import thirdParty3 from "third-party3"; +import anotherSameLevelRelativePath1 from "./anotherSameLevelRelativePath1"; +import sameLevelRelativePath1 from "./sameLevelRelativePath1"; +import otherthing2 from "@core/otherthing2"; +import thirdDisco3 from "third-disco3"; + +import "side-effect-x"; +import anotherSameLevelRelativePath2 from "./anotherSameLevelRelativePath2"; +import sameLevelRelativePath2 from "./sameLevelRelativePath2"; +import something1 from "@core/something1"; +import oneLevelRelativePath3 from "../oneLevelRelativePath3"; +import anotherOneLevelRelativePath3 from "../anotherOneLevelRelativePath3"; +import otherthing1 from "@core/otherthing1"; +import thirdDisco2 from "third-disco2"; +import thirdParty2 from "third-party2"; + +import { Component } from "@angular/core"; + +function add(a,b) { + return a + b; +} +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// I am top level comment in this file. +import thirdDisco0 from "third-disco0"; +import thirdParty0 from "third-party0"; + +import otherthing3 from "@core/otherthing3"; +import something3 from "@core/something3"; + +import "side-effect-z"; + +import thirdDisco1 from "third-disco1"; +import thirdParty1 from "third-party1"; + +import otherthing0 from "@core/otherthing0"; +import something0 from "@core/something0"; + +import anotherOneLevelRelativePath1 from "../anotherOneLevelRelativePath1"; +import oneLevelRelativePath1 from "../oneLevelRelativePath1"; +import anotherSameLevelRelativePath3 from "./anotherSameLevelRelativePath3"; +import sameLevelRelativePath3 from "./sameLevelRelativePath3"; + +import "side-effect-y"; + +import thirdDisco3 from "third-disco3"; +import thirdParty3 from "third-party3"; + +import otherthing2 from "@core/otherthing2"; +import something2 from "@core/something2"; + +import anotherOneLevelRelativePath2 from "../anotherOneLevelRelativePath2"; +import oneLevelRelativePath2 from "../oneLevelRelativePath2"; +import anotherSameLevelRelativePath1 from "./anotherSameLevelRelativePath1"; +import sameLevelRelativePath1 from "./sameLevelRelativePath1"; + +import "side-effect-x"; + +import { Component } from "@angular/core"; +import thirdDisco2 from "third-disco2"; +import thirdParty2 from "third-party2"; + +import otherthing1 from "@core/otherthing1"; +import something1 from "@core/something1"; + +import anotherOneLevelRelativePath3 from "../anotherOneLevelRelativePath3"; +import oneLevelRelativePath3 from "../oneLevelRelativePath3"; +import anotherSameLevelRelativePath2 from "./anotherSameLevelRelativePath2"; +import sameLevelRelativePath2 from "./sameLevelRelativePath2"; + +function add(a, b) { + return a + b; +} + +`; diff --git a/tests/Babel/imports-with-side-effect-imports.js b/tests/Babel/imports-with-side-effect-imports.js new file mode 100644 index 00000000..22bf65e1 --- /dev/null +++ b/tests/Babel/imports-with-side-effect-imports.js @@ -0,0 +1,43 @@ +// I am top level comment in this file. +import thirdParty0 from "third-party0"; +import something3 from "@core/something3"; +import thirdDisco0 from "third-disco0"; +import otherthing3 from "@core/otherthing3"; + +import "side-effect-z"; + +import anotherSameLevelRelativePath3 from "./anotherSameLevelRelativePath3"; +import something0 from "@core/something0"; +import thirdDisco1 from "third-disco1"; +import otherthing0 from "@core/otherthing0"; +import sameLevelRelativePath3 from "./sameLevelRelativePath3"; +import thirdParty1 from "third-party1"; +import oneLevelRelativePath1 from "../oneLevelRelativePath1"; +import anotherOneLevelRelativePath1 from "../anotherOneLevelRelativePath1"; + +import "side-effect-y"; + +import oneLevelRelativePath2 from "../oneLevelRelativePath2"; +import anotherOneLevelRelativePath2 from "../anotherOneLevelRelativePath2"; +import something2 from "@core/something2"; +import thirdParty3 from "third-party3"; +import anotherSameLevelRelativePath1 from "./anotherSameLevelRelativePath1"; +import sameLevelRelativePath1 from "./sameLevelRelativePath1"; +import otherthing2 from "@core/otherthing2"; +import thirdDisco3 from "third-disco3"; + +import "side-effect-x"; +import anotherSameLevelRelativePath2 from "./anotherSameLevelRelativePath2"; +import sameLevelRelativePath2 from "./sameLevelRelativePath2"; +import something1 from "@core/something1"; +import oneLevelRelativePath3 from "../oneLevelRelativePath3"; +import anotherOneLevelRelativePath3 from "../anotherOneLevelRelativePath3"; +import otherthing1 from "@core/otherthing1"; +import thirdDisco2 from "third-disco2"; +import thirdParty2 from "third-party2"; + +import { Component } from "@angular/core"; + +function add(a,b) { + return a + b; +} diff --git a/tests/Flow/__snapshots__/ppsi.spec.js.snap b/tests/Flow/__snapshots__/ppsi.spec.js.snap index 0703b7dc..7b5b5ef7 100644 --- a/tests/Flow/__snapshots__/ppsi.spec.js.snap +++ b/tests/Flow/__snapshots__/ppsi.spec.js.snap @@ -65,3 +65,133 @@ export function givesAFoo3Obj(): AliasFoo3 { } `; + +exports[`imports-with-side-effect-imports.js - flow-verify: imports-with-side-effect-imports.js 1`] = ` +/** + * @flow + */ + +// I am top level comment in this file. +import thirdParty0 from "third-party0"; +import something3 from "@core/something3"; +import thirdDisco0 from "third-disco0"; +import otherthing3 from "@core/otherthing3"; +import { type Something2 } from './__generated2__/'; + +import "side-effect-z"; + +import anotherSameLevelRelativePath3 from "./anotherSameLevelRelativePath3"; +import something0 from "@core/something0"; +import thirdDisco1 from "third-disco1"; +import { type Something3 } from './__generated3__/'; +import otherthing0 from "@core/otherthing0"; +import sameLevelRelativePath3 from "./sameLevelRelativePath3"; +import thirdParty1 from "third-party1"; +import oneLevelRelativePath1 from "../oneLevelRelativePath1"; +import anotherOneLevelRelativePath1 from "../anotherOneLevelRelativePath1"; + +import "side-effect-y"; + +import oneLevelRelativePath2 from "../oneLevelRelativePath2"; +import anotherOneLevelRelativePath2 from "../anotherOneLevelRelativePath2"; +import something2 from "@core/something2"; +import thirdParty3 from "third-party3"; +import anotherSameLevelRelativePath1 from "./anotherSameLevelRelativePath1"; +import sameLevelRelativePath1 from "./sameLevelRelativePath1"; +import otherthing2 from "@core/otherthing2"; +import { type Something1 } from './__generated1__/'; +import thirdDisco3 from "third-disco3"; + +import "side-effect-x"; +import anotherSameLevelRelativePath2 from "./anotherSameLevelRelativePath2"; +import sameLevelRelativePath2 from "./sameLevelRelativePath2"; +import something1 from "@core/something1"; +import { type Something0 } from './__generated0__/'; +import oneLevelRelativePath3 from "../oneLevelRelativePath3"; +import anotherOneLevelRelativePath3 from "../anotherOneLevelRelativePath3"; +import otherthing1 from "@core/otherthing1"; +import thirdDisco2 from "third-disco2"; +import thirdParty2 from "third-party2"; + +import { Component } from "@angular/core"; + +const handleChange = (value: ?string) => {} + +export type AliasFoo3 = { + givesANum(): number +}; +export function givesAFoo3Obj(): AliasFoo3 { + return { + givesANum(): number { return 42; } + }; +}; +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/** + * @flow + */ +// I am top level comment in this file. +import thirdDisco0 from "third-disco0"; +import thirdParty0 from "third-party0"; + +import otherthing3 from "@core/otherthing3"; +import something3 from "@core/something3"; + +import { type Something2 } from "./__generated2__/"; + +import "side-effect-z"; + +import thirdDisco1 from "third-disco1"; +import thirdParty1 from "third-party1"; + +import otherthing0 from "@core/otherthing0"; +import something0 from "@core/something0"; + +import anotherOneLevelRelativePath1 from "../anotherOneLevelRelativePath1"; +import oneLevelRelativePath1 from "../oneLevelRelativePath1"; +import { type Something3 } from "./__generated3__/"; +import anotherSameLevelRelativePath3 from "./anotherSameLevelRelativePath3"; +import sameLevelRelativePath3 from "./sameLevelRelativePath3"; + +import "side-effect-y"; + +import thirdDisco3 from "third-disco3"; +import thirdParty3 from "third-party3"; + +import otherthing2 from "@core/otherthing2"; +import something2 from "@core/something2"; + +import anotherOneLevelRelativePath2 from "../anotherOneLevelRelativePath2"; +import oneLevelRelativePath2 from "../oneLevelRelativePath2"; +import { type Something1 } from "./__generated1__/"; +import anotherSameLevelRelativePath1 from "./anotherSameLevelRelativePath1"; +import sameLevelRelativePath1 from "./sameLevelRelativePath1"; + +import "side-effect-x"; + +import { Component } from "@angular/core"; +import thirdDisco2 from "third-disco2"; +import thirdParty2 from "third-party2"; + +import otherthing1 from "@core/otherthing1"; +import something1 from "@core/something1"; + +import anotherOneLevelRelativePath3 from "../anotherOneLevelRelativePath3"; +import oneLevelRelativePath3 from "../oneLevelRelativePath3"; +import { type Something0 } from "./__generated0__/"; +import anotherSameLevelRelativePath2 from "./anotherSameLevelRelativePath2"; +import sameLevelRelativePath2 from "./sameLevelRelativePath2"; + +const handleChange = (value: ?string) => {}; + +export type AliasFoo3 = { + givesANum(): number, +}; +export function givesAFoo3Obj(): AliasFoo3 { + return { + givesANum(): number { + return 42; + }, + }; +} + +`; diff --git a/tests/Flow/imports-with-side-effect-imports.js b/tests/Flow/imports-with-side-effect-imports.js new file mode 100644 index 00000000..d6e3b593 --- /dev/null +++ b/tests/Flow/imports-with-side-effect-imports.js @@ -0,0 +1,58 @@ +/** + * @flow + */ + +// I am top level comment in this file. +import thirdParty0 from "third-party0"; +import something3 from "@core/something3"; +import thirdDisco0 from "third-disco0"; +import otherthing3 from "@core/otherthing3"; +import { type Something2 } from './__generated2__/'; + +import "side-effect-z"; + +import anotherSameLevelRelativePath3 from "./anotherSameLevelRelativePath3"; +import something0 from "@core/something0"; +import thirdDisco1 from "third-disco1"; +import { type Something3 } from './__generated3__/'; +import otherthing0 from "@core/otherthing0"; +import sameLevelRelativePath3 from "./sameLevelRelativePath3"; +import thirdParty1 from "third-party1"; +import oneLevelRelativePath1 from "../oneLevelRelativePath1"; +import anotherOneLevelRelativePath1 from "../anotherOneLevelRelativePath1"; + +import "side-effect-y"; + +import oneLevelRelativePath2 from "../oneLevelRelativePath2"; +import anotherOneLevelRelativePath2 from "../anotherOneLevelRelativePath2"; +import something2 from "@core/something2"; +import thirdParty3 from "third-party3"; +import anotherSameLevelRelativePath1 from "./anotherSameLevelRelativePath1"; +import sameLevelRelativePath1 from "./sameLevelRelativePath1"; +import otherthing2 from "@core/otherthing2"; +import { type Something1 } from './__generated1__/'; +import thirdDisco3 from "third-disco3"; + +import "side-effect-x"; +import anotherSameLevelRelativePath2 from "./anotherSameLevelRelativePath2"; +import sameLevelRelativePath2 from "./sameLevelRelativePath2"; +import something1 from "@core/something1"; +import { type Something0 } from './__generated0__/'; +import oneLevelRelativePath3 from "../oneLevelRelativePath3"; +import anotherOneLevelRelativePath3 from "../anotherOneLevelRelativePath3"; +import otherthing1 from "@core/otherthing1"; +import thirdDisco2 from "third-disco2"; +import thirdParty2 from "third-party2"; + +import { Component } from "@angular/core"; + +const handleChange = (value: ?string) => {} + +export type AliasFoo3 = { + givesANum(): number +}; +export function givesAFoo3Obj(): AliasFoo3 { + return { + givesANum(): number { return 42; } + }; +}; diff --git a/tests/ImportsNotSeparated/__snapshots__/ppsi.spec.js.snap b/tests/ImportsNotSeparated/__snapshots__/ppsi.spec.js.snap index be225c5a..7e72e906 100644 --- a/tests/ImportsNotSeparated/__snapshots__/ppsi.spec.js.snap +++ b/tests/ImportsNotSeparated/__snapshots__/ppsi.spec.js.snap @@ -96,8 +96,8 @@ function add(a:number,b:number) { ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // I am top level comment in this file. // I am second line of top level comment in this file. -import React from "react"; import "./commands"; +import React from "react"; // Comment // Comment diff --git a/tests/ImportsNotSeparatedRest/__snapshots__/ppsi.spec.js.snap b/tests/ImportsNotSeparatedRest/__snapshots__/ppsi.spec.js.snap index b30addd1..9992da3c 100644 --- a/tests/ImportsNotSeparatedRest/__snapshots__/ppsi.spec.js.snap +++ b/tests/ImportsNotSeparatedRest/__snapshots__/ppsi.spec.js.snap @@ -96,8 +96,8 @@ function add(a:number,b:number) { ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // I am top level comment in this file. // I am second line of top level comment in this file. -import React from "react"; import "./commands"; +import React from "react"; // Comment // Comment diff --git a/tests/ImportsSeparated/__snapshots__/ppsi.spec.js.snap b/tests/ImportsSeparated/__snapshots__/ppsi.spec.js.snap index fbd4c8c4..53343caa 100644 --- a/tests/ImportsSeparated/__snapshots__/ppsi.spec.js.snap +++ b/tests/ImportsSeparated/__snapshots__/ppsi.spec.js.snap @@ -1,6 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`import-export-in-between.ts - typescript-verify: import-export-in-between.ts 1`] = ` +import "side-effect"; import threeLevelRelativePath from "../../../threeLevelRelativePath"; import sameLevelRelativePath from "./sameLevelRelativePath"; import thirdParty from "third-party"; @@ -22,6 +23,8 @@ function add(a:number,b:number) { return a + b; } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +import "side-effect"; + import a from "a"; import c from "c"; import thirdParty from "third-party"; @@ -100,10 +103,10 @@ function add(a:number,b:number) { ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // I am top level comment in this file. // I am second line of top level comment in this file. -import React from "react"; - import "./commands"; +import React from "react"; + // Comment // Comment diff --git a/tests/ImportsSeparated/import-export-in-between.ts b/tests/ImportsSeparated/import-export-in-between.ts index 16bc1d77..aef667be 100644 --- a/tests/ImportsSeparated/import-export-in-between.ts +++ b/tests/ImportsSeparated/import-export-in-between.ts @@ -1,3 +1,4 @@ +import "side-effect"; import threeLevelRelativePath from "../../../threeLevelRelativePath"; import sameLevelRelativePath from "./sameLevelRelativePath"; import thirdParty from "third-party"; diff --git a/tests/ImportsSeparatedRest/__snapshots__/ppsi.spec.js.snap b/tests/ImportsSeparatedRest/__snapshots__/ppsi.spec.js.snap index 57f43871..675c7181 100644 --- a/tests/ImportsSeparatedRest/__snapshots__/ppsi.spec.js.snap +++ b/tests/ImportsSeparatedRest/__snapshots__/ppsi.spec.js.snap @@ -100,10 +100,10 @@ function add(a:number,b:number) { ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // I am top level comment in this file. // I am second line of top level comment in this file. -import React from "react"; - import "./commands"; +import React from "react"; + // Comment // Comment diff --git a/tests/Typescript/__snapshots__/ppsi.spec.js.snap b/tests/Typescript/__snapshots__/ppsi.spec.js.snap index 8899a200..acb880be 100644 --- a/tests/Typescript/__snapshots__/ppsi.spec.js.snap +++ b/tests/Typescript/__snapshots__/ppsi.spec.js.snap @@ -1,5 +1,119 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`import-with-side-effect-imports.ts - typescript-verify: import-with-side-effect-imports.ts 1`] = ` +import thirdParty0 from "third-party0"; +import something3 from "@core/something3"; +import thirdDisco0 from "third-disco0"; +import otherthing3 from "@core/otherthing3"; + +import "side-effect-z"; + +import anotherSameLevelRelativePath3 from "./anotherSameLevelRelativePath3"; +import something0 from "@core/something0"; +import thirdDisco1 from "third-disco1"; +import otherthing0 from "@core/otherthing0"; +import sameLevelRelativePath3 from "./sameLevelRelativePath3"; +import thirdParty1 from "third-party1"; +import oneLevelRelativePath1 from "../oneLevelRelativePath1"; +import anotherOneLevelRelativePath1 from "../anotherOneLevelRelativePath1"; + +import "side-effect-y"; + +import oneLevelRelativePath2 from "../oneLevelRelativePath2"; +import anotherOneLevelRelativePath2 from "../anotherOneLevelRelativePath2"; +import something2 from "@core/something2"; +import thirdParty3 from "third-party3"; +import anotherSameLevelRelativePath1 from "./anotherSameLevelRelativePath1"; +import sameLevelRelativePath1 from "./sameLevelRelativePath1"; +import otherthing2 from "@core/otherthing2"; +import thirdDisco3 from "third-disco3"; + +import "side-effect-x"; +import anotherSameLevelRelativePath2 from "./anotherSameLevelRelativePath2"; +import sameLevelRelativePath2 from "./sameLevelRelativePath2"; +import something1 from "@core/something1"; +import oneLevelRelativePath3 from "../oneLevelRelativePath3"; +import anotherOneLevelRelativePath3 from "../anotherOneLevelRelativePath3"; +import otherthing1 from "@core/otherthing1"; +import thirdDisco2 from "third-disco2"; +import thirdParty2 from "third-party2"; + +import { Component } from "@angular/core"; + +@Component({ + selector: "app-root", + templateUrl: "./app.component.html", + styleUrls: ["./app.component.css"] +}) +export class AppComponent extends BaseComponent { + title = "ng-prettier"; + + override get text(): string { + return isEmpty(this.title) ? "" : this.title; + } +} +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +import thirdDisco0 from "third-disco0"; +import thirdParty0 from "third-party0"; + +import otherthing3 from "@core/otherthing3"; +import something3 from "@core/something3"; + +import "side-effect-z"; + +import thirdDisco1 from "third-disco1"; +import thirdParty1 from "third-party1"; + +import otherthing0 from "@core/otherthing0"; +import something0 from "@core/something0"; + +import anotherOneLevelRelativePath1 from "../anotherOneLevelRelativePath1"; +import oneLevelRelativePath1 from "../oneLevelRelativePath1"; +import anotherSameLevelRelativePath3 from "./anotherSameLevelRelativePath3"; +import sameLevelRelativePath3 from "./sameLevelRelativePath3"; + +import "side-effect-y"; + +import thirdDisco3 from "third-disco3"; +import thirdParty3 from "third-party3"; + +import otherthing2 from "@core/otherthing2"; +import something2 from "@core/something2"; + +import anotherOneLevelRelativePath2 from "../anotherOneLevelRelativePath2"; +import oneLevelRelativePath2 from "../oneLevelRelativePath2"; +import anotherSameLevelRelativePath1 from "./anotherSameLevelRelativePath1"; +import sameLevelRelativePath1 from "./sameLevelRelativePath1"; + +import "side-effect-x"; + +import { Component } from "@angular/core"; +import thirdDisco2 from "third-disco2"; +import thirdParty2 from "third-party2"; + +import otherthing1 from "@core/otherthing1"; +import something1 from "@core/something1"; + +import anotherOneLevelRelativePath3 from "../anotherOneLevelRelativePath3"; +import oneLevelRelativePath3 from "../oneLevelRelativePath3"; +import anotherSameLevelRelativePath2 from "./anotherSameLevelRelativePath2"; +import sameLevelRelativePath2 from "./sameLevelRelativePath2"; + +@Component({ + selector: "app-root", + templateUrl: "./app.component.html", + styleUrls: ["./app.component.css"], +}) +export class AppComponent extends BaseComponent { + title = "ng-prettier"; + + override get text(): string { + return isEmpty(this.title) ? "" : this.title; + } +} + +`; + exports[`imports-with-comments.ts - typescript-verify: imports-with-comments.ts 1`] = ` import z from 'z'; import { isEmpty } from "lodash-es"; diff --git a/tests/Typescript/import-with-side-effect-imports.ts b/tests/Typescript/import-with-side-effect-imports.ts new file mode 100644 index 00000000..2b1a3b5a --- /dev/null +++ b/tests/Typescript/import-with-side-effect-imports.ts @@ -0,0 +1,51 @@ +import thirdParty0 from "third-party0"; +import something3 from "@core/something3"; +import thirdDisco0 from "third-disco0"; +import otherthing3 from "@core/otherthing3"; + +import "side-effect-z"; + +import anotherSameLevelRelativePath3 from "./anotherSameLevelRelativePath3"; +import something0 from "@core/something0"; +import thirdDisco1 from "third-disco1"; +import otherthing0 from "@core/otherthing0"; +import sameLevelRelativePath3 from "./sameLevelRelativePath3"; +import thirdParty1 from "third-party1"; +import oneLevelRelativePath1 from "../oneLevelRelativePath1"; +import anotherOneLevelRelativePath1 from "../anotherOneLevelRelativePath1"; + +import "side-effect-y"; + +import oneLevelRelativePath2 from "../oneLevelRelativePath2"; +import anotherOneLevelRelativePath2 from "../anotherOneLevelRelativePath2"; +import something2 from "@core/something2"; +import thirdParty3 from "third-party3"; +import anotherSameLevelRelativePath1 from "./anotherSameLevelRelativePath1"; +import sameLevelRelativePath1 from "./sameLevelRelativePath1"; +import otherthing2 from "@core/otherthing2"; +import thirdDisco3 from "third-disco3"; + +import "side-effect-x"; +import anotherSameLevelRelativePath2 from "./anotherSameLevelRelativePath2"; +import sameLevelRelativePath2 from "./sameLevelRelativePath2"; +import something1 from "@core/something1"; +import oneLevelRelativePath3 from "../oneLevelRelativePath3"; +import anotherOneLevelRelativePath3 from "../anotherOneLevelRelativePath3"; +import otherthing1 from "@core/otherthing1"; +import thirdDisco2 from "third-disco2"; +import thirdParty2 from "third-party2"; + +import { Component } from "@angular/core"; + +@Component({ + selector: "app-root", + templateUrl: "./app.component.html", + styleUrls: ["./app.component.css"] +}) +export class AppComponent extends BaseComponent { + title = "ng-prettier"; + + override get text(): string { + return isEmpty(this.title) ? "" : this.title; + } +} From d05dc43609d934c5d81f9c91d1d805ffc4a7bab4 Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Wed, 17 Nov 2021 18:35:48 +0100 Subject: [PATCH 2/8] expand tests with multiple successive side effect imports (which should keep their relative order) #110 --- tests/Angular/__snapshots__/ppsi.spec.js.snap | 8 ++++++-- tests/Angular/imports-with-side-effect-imports.js | 4 +++- tests/Babel/__snapshots__/ppsi.spec.js.snap | 8 ++++++-- tests/Babel/imports-with-side-effect-imports.js | 4 +++- tests/Flow/__snapshots__/ppsi.spec.js.snap | 8 ++++++-- tests/Flow/imports-with-side-effect-imports.js | 4 +++- tests/Typescript/__snapshots__/ppsi.spec.js.snap | 8 ++++++-- tests/Typescript/import-with-side-effect-imports.ts | 4 +++- 8 files changed, 36 insertions(+), 12 deletions(-) diff --git a/tests/Angular/__snapshots__/ppsi.spec.js.snap b/tests/Angular/__snapshots__/ppsi.spec.js.snap index c20f99b5..35bc6136 100644 --- a/tests/Angular/__snapshots__/ppsi.spec.js.snap +++ b/tests/Angular/__snapshots__/ppsi.spec.js.snap @@ -99,7 +99,9 @@ import thirdParty1 from "third-party1"; import oneLevelRelativePath1 from "../oneLevelRelativePath1"; import anotherOneLevelRelativePath1 from "../anotherOneLevelRelativePath1"; -import "side-effect-y"; +import "side-effect-y3"; +import "side-effect-y1"; +import "side-effect-y2"; import oneLevelRelativePath2 from "../oneLevelRelativePath2"; import anotherOneLevelRelativePath2 from "../anotherOneLevelRelativePath2"; @@ -168,7 +170,9 @@ import oneLevelRelativePath1 from "../oneLevelRelativePath1"; import anotherSameLevelRelativePath3 from "./anotherSameLevelRelativePath3"; import sameLevelRelativePath3 from "./sameLevelRelativePath3"; -import "side-effect-y"; +import "side-effect-y3"; +import "side-effect-y1"; +import "side-effect-y2"; import thirdDisco3 from "third-disco3"; import thirdParty3 from "third-party3"; diff --git a/tests/Angular/imports-with-side-effect-imports.js b/tests/Angular/imports-with-side-effect-imports.js index 11561f03..c5f86542 100644 --- a/tests/Angular/imports-with-side-effect-imports.js +++ b/tests/Angular/imports-with-side-effect-imports.js @@ -15,7 +15,9 @@ import thirdParty1 from "third-party1"; import oneLevelRelativePath1 from "../oneLevelRelativePath1"; import anotherOneLevelRelativePath1 from "../anotherOneLevelRelativePath1"; -import "side-effect-y"; +import "side-effect-y3"; +import "side-effect-y1"; +import "side-effect-y2"; import oneLevelRelativePath2 from "../oneLevelRelativePath2"; import anotherOneLevelRelativePath2 from "../anotherOneLevelRelativePath2"; diff --git a/tests/Babel/__snapshots__/ppsi.spec.js.snap b/tests/Babel/__snapshots__/ppsi.spec.js.snap index fd4f06ae..ac1bdf1c 100644 --- a/tests/Babel/__snapshots__/ppsi.spec.js.snap +++ b/tests/Babel/__snapshots__/ppsi.spec.js.snap @@ -61,7 +61,9 @@ import thirdParty1 from "third-party1"; import oneLevelRelativePath1 from "../oneLevelRelativePath1"; import anotherOneLevelRelativePath1 from "../anotherOneLevelRelativePath1"; -import "side-effect-y"; +import "side-effect-y3"; +import "side-effect-y1"; +import "side-effect-y2"; import oneLevelRelativePath2 from "../oneLevelRelativePath2"; import anotherOneLevelRelativePath2 from "../anotherOneLevelRelativePath2"; @@ -108,7 +110,9 @@ import oneLevelRelativePath1 from "../oneLevelRelativePath1"; import anotherSameLevelRelativePath3 from "./anotherSameLevelRelativePath3"; import sameLevelRelativePath3 from "./sameLevelRelativePath3"; -import "side-effect-y"; +import "side-effect-y3"; +import "side-effect-y1"; +import "side-effect-y2"; import thirdDisco3 from "third-disco3"; import thirdParty3 from "third-party3"; diff --git a/tests/Babel/imports-with-side-effect-imports.js b/tests/Babel/imports-with-side-effect-imports.js index 22bf65e1..fcc19e9b 100644 --- a/tests/Babel/imports-with-side-effect-imports.js +++ b/tests/Babel/imports-with-side-effect-imports.js @@ -15,7 +15,9 @@ import thirdParty1 from "third-party1"; import oneLevelRelativePath1 from "../oneLevelRelativePath1"; import anotherOneLevelRelativePath1 from "../anotherOneLevelRelativePath1"; -import "side-effect-y"; +import "side-effect-y3"; +import "side-effect-y1"; +import "side-effect-y2"; import oneLevelRelativePath2 from "../oneLevelRelativePath2"; import anotherOneLevelRelativePath2 from "../anotherOneLevelRelativePath2"; diff --git a/tests/Flow/__snapshots__/ppsi.spec.js.snap b/tests/Flow/__snapshots__/ppsi.spec.js.snap index 7b5b5ef7..153a68e3 100644 --- a/tests/Flow/__snapshots__/ppsi.spec.js.snap +++ b/tests/Flow/__snapshots__/ppsi.spec.js.snap @@ -90,7 +90,9 @@ import thirdParty1 from "third-party1"; import oneLevelRelativePath1 from "../oneLevelRelativePath1"; import anotherOneLevelRelativePath1 from "../anotherOneLevelRelativePath1"; -import "side-effect-y"; +import "side-effect-y3"; +import "side-effect-y1"; +import "side-effect-y2"; import oneLevelRelativePath2 from "../oneLevelRelativePath2"; import anotherOneLevelRelativePath2 from "../anotherOneLevelRelativePath2"; @@ -152,7 +154,9 @@ import { type Something3 } from "./__generated3__/"; import anotherSameLevelRelativePath3 from "./anotherSameLevelRelativePath3"; import sameLevelRelativePath3 from "./sameLevelRelativePath3"; -import "side-effect-y"; +import "side-effect-y3"; +import "side-effect-y1"; +import "side-effect-y2"; import thirdDisco3 from "third-disco3"; import thirdParty3 from "third-party3"; diff --git a/tests/Flow/imports-with-side-effect-imports.js b/tests/Flow/imports-with-side-effect-imports.js index d6e3b593..7ca6707a 100644 --- a/tests/Flow/imports-with-side-effect-imports.js +++ b/tests/Flow/imports-with-side-effect-imports.js @@ -21,7 +21,9 @@ import thirdParty1 from "third-party1"; import oneLevelRelativePath1 from "../oneLevelRelativePath1"; import anotherOneLevelRelativePath1 from "../anotherOneLevelRelativePath1"; -import "side-effect-y"; +import "side-effect-y3"; +import "side-effect-y1"; +import "side-effect-y2"; import oneLevelRelativePath2 from "../oneLevelRelativePath2"; import anotherOneLevelRelativePath2 from "../anotherOneLevelRelativePath2"; diff --git a/tests/Typescript/__snapshots__/ppsi.spec.js.snap b/tests/Typescript/__snapshots__/ppsi.spec.js.snap index acb880be..da52bb04 100644 --- a/tests/Typescript/__snapshots__/ppsi.spec.js.snap +++ b/tests/Typescript/__snapshots__/ppsi.spec.js.snap @@ -17,7 +17,9 @@ import thirdParty1 from "third-party1"; import oneLevelRelativePath1 from "../oneLevelRelativePath1"; import anotherOneLevelRelativePath1 from "../anotherOneLevelRelativePath1"; -import "side-effect-y"; +import "side-effect-y3"; +import "side-effect-y1"; +import "side-effect-y2"; import oneLevelRelativePath2 from "../oneLevelRelativePath2"; import anotherOneLevelRelativePath2 from "../anotherOneLevelRelativePath2"; @@ -72,7 +74,9 @@ import oneLevelRelativePath1 from "../oneLevelRelativePath1"; import anotherSameLevelRelativePath3 from "./anotherSameLevelRelativePath3"; import sameLevelRelativePath3 from "./sameLevelRelativePath3"; -import "side-effect-y"; +import "side-effect-y3"; +import "side-effect-y1"; +import "side-effect-y2"; import thirdDisco3 from "third-disco3"; import thirdParty3 from "third-party3"; diff --git a/tests/Typescript/import-with-side-effect-imports.ts b/tests/Typescript/import-with-side-effect-imports.ts index 2b1a3b5a..8e953a0f 100644 --- a/tests/Typescript/import-with-side-effect-imports.ts +++ b/tests/Typescript/import-with-side-effect-imports.ts @@ -14,7 +14,9 @@ import thirdParty1 from "third-party1"; import oneLevelRelativePath1 from "../oneLevelRelativePath1"; import anotherOneLevelRelativePath1 from "../anotherOneLevelRelativePath1"; -import "side-effect-y"; +import "side-effect-y3"; +import "side-effect-y1"; +import "side-effect-y2"; import oneLevelRelativePath2 from "../oneLevelRelativePath2"; import anotherOneLevelRelativePath2 from "../anotherOneLevelRelativePath2"; From 5619b226ed1fc1f289786c07e4b4fc3f02c9398d Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Tue, 14 Dec 2021 22:03:59 +0100 Subject: [PATCH 3/8] Use iterative loop instead of for-each loop #110 / #111 --- src/utils/get-sorted-nodes-by-import-order.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/utils/get-sorted-nodes-by-import-order.ts b/src/utils/get-sorted-nodes-by-import-order.ts index 85ac9d49..75262199 100644 --- a/src/utils/get-sorted-nodes-by-import-order.ts +++ b/src/utils/get-sorted-nodes-by-import-order.ts @@ -46,7 +46,8 @@ export const getSortedNodesByImportOrder: GetSortedNodes = (nodes, options) => { importOrderGroups[matchedGroup].push(node); } - for (const group of importOrder) { + for (let i = 0; i < importOrder.length; i += 1) { + const group = importOrder[i]; const groupNodes = importOrderGroups[group]; const last = group === importOrder[importOrder.length - 1]; From 170b06a71a2cc414e539916c5af28e4fb9c22b4b Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Wed, 15 Dec 2021 09:06:59 +0100 Subject: [PATCH 4/8] Check for last item in iteration via index #110 / #111 --- src/utils/get-sorted-nodes-by-import-order.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/get-sorted-nodes-by-import-order.ts b/src/utils/get-sorted-nodes-by-import-order.ts index 75262199..fbd93f7e 100644 --- a/src/utils/get-sorted-nodes-by-import-order.ts +++ b/src/utils/get-sorted-nodes-by-import-order.ts @@ -49,7 +49,6 @@ export const getSortedNodesByImportOrder: GetSortedNodes = (nodes, options) => { for (let i = 0; i < importOrder.length; i += 1) { const group = importOrder[i]; const groupNodes = importOrderGroups[group]; - const last = group === importOrder[importOrder.length - 1]; if (groupNodes.length === 0) continue; @@ -66,7 +65,8 @@ export const getSortedNodesByImportOrder: GetSortedNodes = (nodes, options) => { finalNodes.push(...sortedInsideGroup); - if (importOrderSeparation && !last) { + // Do not add a new line after the last group of nodes + if (importOrderSeparation && i < importOrder.length - 1) { finalNodes.push(newLineNode); } } From 61b487b359c92215b09b11d8c61114836190618e Mon Sep 17 00:00:00 2001 From: Miles Au Date: Fri, 17 Dec 2021 15:34:37 +0100 Subject: [PATCH 5/8] Fix typo in example file --- examples/example.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/example.ts b/examples/example.ts index 5062e3cf..f9a69c4c 100644 --- a/examples/example.ts +++ b/examples/example.ts @@ -7,7 +7,7 @@ import React, { } from 'react'; import { logger } from '@core/logger'; import { reduce, debounce } from 'lodash'; -import { Message } from '../Mesage'; +import { Message } from '../Message'; import { createServer } from '@server/node'; import { Alert } from '@ui/Alert'; import { repeat, filter, add } from './utils'; From 9929ea14b2e6968cfd05c0bb45c1df21254dd8e2 Mon Sep 17 00:00:00 2001 From: Miles Au Date: Fri, 17 Dec 2021 15:35:12 +0100 Subject: [PATCH 6/8] Fix .prettierrc path in DEBUG doc --- docs/DEBUG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/DEBUG.md b/docs/DEBUG.md index c1fd0106..1ce661f6 100644 --- a/docs/DEBUG.md +++ b/docs/DEBUG.md @@ -4,13 +4,13 @@ ### How to run example in the repository ? ```shell -yarn run example examples/example.ts --config .prettierrc +yarn run example examples/example.ts --config examples/.prettierrc ``` ### How to debug the plugin using node `debugger` ? You can set a `debugger` anywhere in the code and then use following command: ```shell -yarn run compile && node --inspect-brk ./node_modules/.bin/prettier --config .prettierrc --plugin lib/src/index.js examples/example.ts +yarn run compile && node --inspect-brk ./node_modules/.bin/prettier --config examples/.prettierrc --plugin lib/src/index.js examples/example.ts ``` ### How to debug the unit test using `debugger` ? From 5e2a3ebcf5628dbbae6bed9c9451ed4955cd93eb Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Tue, 11 Jan 2022 20:14:05 +0100 Subject: [PATCH 7/8] Refactor test, add test for adjust-comments, run prettier #110 #111 --- .../adjust-comments-on-sorted-nodes.spec.ts | 107 ++++++++++++++++++ src/utils/__tests__/get-sorted-nodes.spec.ts | 9 +- src/utils/adjust-comments-on-sorted-nodes.ts | 10 +- src/utils/get-sorted-nodes.ts | 37 +++--- .../__snapshots__/ppsi.spec.js.snap | 51 +++++++++ .../import-export-in-between-side-effect.ts | 21 ++++ .../import-export-in-between.ts | 1 - 7 files changed, 209 insertions(+), 27 deletions(-) create mode 100644 src/utils/__tests__/adjust-comments-on-sorted-nodes.spec.ts create mode 100644 tests/ImportsSeparated/import-export-in-between-side-effect.ts diff --git a/src/utils/__tests__/adjust-comments-on-sorted-nodes.spec.ts b/src/utils/__tests__/adjust-comments-on-sorted-nodes.spec.ts new file mode 100644 index 00000000..acf76c09 --- /dev/null +++ b/src/utils/__tests__/adjust-comments-on-sorted-nodes.spec.ts @@ -0,0 +1,107 @@ +import { ImportDeclaration } from '@babel/types'; + +import { adjustCommentsOnSortedNodes } from '../adjust-comments-on-sorted-nodes'; +import { getImportNodes } from '../get-import-nodes'; + +function leadingComments(node: ImportDeclaration): string[] { + return node.leadingComments?.map((c) => c.value) ?? []; +} + +function trailingComments(node: ImportDeclaration): string[] { + return node.trailingComments?.map((c) => c.value) ?? []; +} + +test('it preserves the single leading comment for each import declaration', () => { + const importNodes = getImportNodes(` + import {x} from "c"; + // comment b + import {y} from "b"; + // comment a + import {z} from "a"; + `); + expect(importNodes).toHaveLength(3); + const finalNodes = [importNodes[2], importNodes[1], importNodes[0]]; + adjustCommentsOnSortedNodes(importNodes, finalNodes); + expect(finalNodes).toHaveLength(3); + expect(leadingComments(finalNodes[0])).toEqual([' comment a']); + expect(trailingComments(finalNodes[0])).toEqual([]); + expect(leadingComments(finalNodes[1])).toEqual([' comment b']); + expect(trailingComments(finalNodes[1])).toEqual([]); + expect(leadingComments(finalNodes[2])).toEqual([]); + expect(trailingComments(finalNodes[2])).toEqual([]); +}); + +test('it preserves multiple leading comments for each import declaration', () => { + const importNodes = getImportNodes(` + import {x} from "c"; + // comment b1 + // comment b2 + // comment b3 + import {y} from "b"; + // comment a1 + // comment a2 + // comment a3 + import {z} from "a"; + `); + expect(importNodes).toHaveLength(3); + const finalNodes = [importNodes[2], importNodes[1], importNodes[0]]; + adjustCommentsOnSortedNodes(importNodes, finalNodes); + expect(finalNodes).toHaveLength(3); + expect(leadingComments(finalNodes[0])).toEqual([ + ' comment a1', + ' comment a2', + ' comment a3', + ]); + expect(trailingComments(finalNodes[0])).toEqual([]); + expect(leadingComments(finalNodes[1])).toEqual([ + ' comment b1', + ' comment b2', + ' comment b3', + ]); + expect(trailingComments(finalNodes[1])).toEqual([]); + expect(leadingComments(finalNodes[2])).toEqual([]); + expect(trailingComments(finalNodes[2])).toEqual([]); +}); + +test('it does not move comments at before all import declarations', () => { + const importNodes = getImportNodes(` + // comment c1 + // comment c2 + import {x} from "c"; + import {y} from "b"; + import {z} from "a"; + `); + expect(importNodes).toHaveLength(3); + const finalNodes = [importNodes[2], importNodes[1], importNodes[0]]; + adjustCommentsOnSortedNodes(importNodes, finalNodes); + expect(finalNodes).toHaveLength(3); + expect(leadingComments(finalNodes[0])).toEqual([ + ' comment c1', + ' comment c2', + ]); + expect(trailingComments(finalNodes[0])).toEqual([]); + expect(leadingComments(finalNodes[1])).toEqual([]); + expect(trailingComments(finalNodes[1])).toEqual([]); + expect(leadingComments(finalNodes[2])).toEqual([]); + expect(trailingComments(finalNodes[2])).toEqual([]); +}); + +test('it does not affect comments after all import declarations', () => { + const importNodes = getImportNodes(` + import {x} from "c"; + import {y} from "b"; + import {z} from "a"; + // comment final 1 + // comment final 2 + `); + expect(importNodes).toHaveLength(3); + const finalNodes = [importNodes[2], importNodes[1], importNodes[0]]; + adjustCommentsOnSortedNodes(importNodes, finalNodes); + expect(finalNodes).toHaveLength(3); + expect(leadingComments(finalNodes[0])).toEqual([]); + expect(trailingComments(finalNodes[0])).toEqual([]); + expect(leadingComments(finalNodes[1])).toEqual([]); + expect(trailingComments(finalNodes[1])).toEqual([]); + expect(leadingComments(finalNodes[2])).toEqual([]); + expect(trailingComments(finalNodes[2])).toEqual([]); +}); diff --git a/src/utils/__tests__/get-sorted-nodes.spec.ts b/src/utils/__tests__/get-sorted-nodes.spec.ts index 0538f65a..f532b898 100644 --- a/src/utils/__tests__/get-sorted-nodes.spec.ts +++ b/src/utils/__tests__/get-sorted-nodes.spec.ts @@ -33,20 +33,20 @@ test('it returns all sorted nodes, preserving the order side effect nodes', () = }) as ImportDeclaration[]; expect(getSortedNodesNames(sorted)).toEqual([ - "se3", + 'se3', 'c', 'g', 'k', 't', 'z', - "se4", - "se1", + 'se4', + 'se1', 'BY', 'Ba', 'XY', 'Xa', 'a', - "se2", + 'se2', ]); expect( sorted @@ -71,4 +71,3 @@ test('it returns all sorted nodes, preserving the order side effect nodes', () = [], ]); }); - diff --git a/src/utils/adjust-comments-on-sorted-nodes.ts b/src/utils/adjust-comments-on-sorted-nodes.ts index 39a9c6d9..a02fc3dc 100644 --- a/src/utils/adjust-comments-on-sorted-nodes.ts +++ b/src/utils/adjust-comments-on-sorted-nodes.ts @@ -1,18 +1,18 @@ -import { addComments, ImportDeclaration, removeComments } from '@babel/types'; +import { ImportDeclaration, addComments, removeComments } from '@babel/types'; import { clone, isEqual } from 'lodash'; -import { ImportOrLine } from "../types"; +import { ImportOrLine } from '../types'; /** * Takes the original nodes before sorting and the final nodes after sorting. * Adjusts the comments on the final nodes so that they match the comments as - * they were in the original nodes. + * they were in the original nodes. * @param nodes A list of nodes in the order as they were originally. * @param finalNodes The same set of nodes, but in the final sorting order. */ export const adjustCommentsOnSortedNodes = ( nodes: ImportDeclaration[], - finalNodes: ImportOrLine[] + finalNodes: ImportOrLine[], ) => { // maintain a copy of the nodes to extract comments from const finalNodesClone = finalNodes.map(clone); @@ -36,4 +36,4 @@ export const adjustCommentsOnSortedNodes = ( if (firstNodesComments) { addComments(finalNodes[0], 'leading', firstNodesComments); } -} \ No newline at end of file +}; diff --git a/src/utils/get-sorted-nodes.ts b/src/utils/get-sorted-nodes.ts index 2db2af7d..bb69cec6 100644 --- a/src/utils/get-sorted-nodes.ts +++ b/src/utils/get-sorted-nodes.ts @@ -1,13 +1,17 @@ -import { ChunkSideEffectNode, ChunkSideOtherNode, newLineNode } from "../constants"; -import { GetSortedNodes, ImportChunk, ImportOrLine } from "../types"; -import { adjustCommentsOnSortedNodes } from "./adjust-comments-on-sorted-nodes"; -import { getSortedNodesByImportOrder } from "./get-sorted-nodes-by-import-order"; +import { + ChunkSideEffectNode, + ChunkSideOtherNode, + newLineNode, +} from '../constants'; +import { GetSortedNodes, ImportChunk, ImportOrLine } from '../types'; +import { adjustCommentsOnSortedNodes } from './adjust-comments-on-sorted-nodes'; +import { getSortedNodesByImportOrder } from './get-sorted-nodes-by-import-order'; /** * This function returns the given nodes, sorted in the order as indicated by * the importOrder array. The plugin considers these import nodes as local * import declarations - * + * * In addition, this method preserves the relative order of side effect imports * and non side effect imports. A side effect import is an ImportDeclaration * without any import specifiers. It does this by splitting the import nodes at @@ -21,20 +25,22 @@ export const getSortedNodes: GetSortedNodes = (nodes, options) => { // Split nodes at each boundary between a side-effect node and a // non-side-effect node, keeping both types of nodes together. - const splitBySideEffectNodes = - nodes.reduce((chunks, node) => { - const type = node.specifiers.length === 0 - ? ChunkSideEffectNode - : ChunkSideOtherNode; + const splitBySideEffectNodes = nodes.reduce( + (chunks, node) => { + const type = + node.specifiers.length === 0 + ? ChunkSideEffectNode + : ChunkSideOtherNode; const last = chunks[chunks.length - 1]; if (last === undefined || last.type !== type) { chunks.push({ type, nodes: [node] }); - } - else { + } else { last.nodes.push(node); } return chunks; - }, []); + }, + [], + ); const finalNodes: ImportOrLine[] = []; @@ -44,8 +50,7 @@ export const getSortedNodes: GetSortedNodes = (nodes, options) => { if (chunk.type === ChunkSideEffectNode) { // do not sort side effect nodes finalNodes.push(...chunk.nodes); - } - else { + } else { // sort non-side effect nodes const sorted = getSortedNodesByImportOrder(chunk.nodes, options); finalNodes.push(...sorted); @@ -63,4 +68,4 @@ export const getSortedNodes: GetSortedNodes = (nodes, options) => { adjustCommentsOnSortedNodes(nodes, finalNodes); return finalNodes; -} \ No newline at end of file +}; diff --git a/tests/ImportsSeparated/__snapshots__/ppsi.spec.js.snap b/tests/ImportsSeparated/__snapshots__/ppsi.spec.js.snap index 53343caa..d3da0a1d 100644 --- a/tests/ImportsSeparated/__snapshots__/ppsi.spec.js.snap +++ b/tests/ImportsSeparated/__snapshots__/ppsi.spec.js.snap @@ -1,6 +1,57 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`import-export-in-between.ts - typescript-verify: import-export-in-between.ts 1`] = ` +import threeLevelRelativePath from "../../../threeLevelRelativePath"; +import sameLevelRelativePath from "./sameLevelRelativePath"; +import thirdParty from "third-party"; +export { random } from './random'; +import c from 'c'; +import oneLevelRelativePath from "../oneLevelRelativePath"; +import otherthing from "@core/otherthing"; +import a from 'a'; +import twoLevelRelativePath from "../../twoLevelRelativePath"; +import component from "@ui/hello"; +export default { + title: 'hello', +}; +import fourLevelRelativePath from "../../../../fourLevelRelativePath"; +import something from "@server/something"; +import x from 'x'; + +function add(a:number,b:number) { + return a + b; +} +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +import a from "a"; +import c from "c"; +import thirdParty from "third-party"; +import x from "x"; + +import otherthing from "@core/otherthing"; + +import something from "@server/something"; + +import component from "@ui/hello"; + +import fourLevelRelativePath from "../../../../fourLevelRelativePath"; +import threeLevelRelativePath from "../../../threeLevelRelativePath"; +import twoLevelRelativePath from "../../twoLevelRelativePath"; +import oneLevelRelativePath from "../oneLevelRelativePath"; +import sameLevelRelativePath from "./sameLevelRelativePath"; + +export { random } from "./random"; + +export default { + title: "hello", +}; + +function add(a: number, b: number) { + return a + b; +} + +`; + +exports[`import-export-in-between-side-effect.ts - typescript-verify: import-export-in-between-side-effect.ts 1`] = ` import "side-effect"; import threeLevelRelativePath from "../../../threeLevelRelativePath"; import sameLevelRelativePath from "./sameLevelRelativePath"; diff --git a/tests/ImportsSeparated/import-export-in-between-side-effect.ts b/tests/ImportsSeparated/import-export-in-between-side-effect.ts new file mode 100644 index 00000000..aef667be --- /dev/null +++ b/tests/ImportsSeparated/import-export-in-between-side-effect.ts @@ -0,0 +1,21 @@ +import "side-effect"; +import threeLevelRelativePath from "../../../threeLevelRelativePath"; +import sameLevelRelativePath from "./sameLevelRelativePath"; +import thirdParty from "third-party"; +export { random } from './random'; +import c from 'c'; +import oneLevelRelativePath from "../oneLevelRelativePath"; +import otherthing from "@core/otherthing"; +import a from 'a'; +import twoLevelRelativePath from "../../twoLevelRelativePath"; +import component from "@ui/hello"; +export default { + title: 'hello', +}; +import fourLevelRelativePath from "../../../../fourLevelRelativePath"; +import something from "@server/something"; +import x from 'x'; + +function add(a:number,b:number) { + return a + b; +} diff --git a/tests/ImportsSeparated/import-export-in-between.ts b/tests/ImportsSeparated/import-export-in-between.ts index aef667be..16bc1d77 100644 --- a/tests/ImportsSeparated/import-export-in-between.ts +++ b/tests/ImportsSeparated/import-export-in-between.ts @@ -1,4 +1,3 @@ -import "side-effect"; import threeLevelRelativePath from "../../../threeLevelRelativePath"; import sameLevelRelativePath from "./sameLevelRelativePath"; import thirdParty from "third-party"; From 9a43ff2b7db07063a90f62f34495b197eb3b3832 Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Tue, 18 Jan 2022 23:49:53 +0100 Subject: [PATCH 8/8] Lower case constant name to follow the project's style guide #110 #111 --- src/constants.ts | 4 ++-- src/utils/get-sorted-nodes.ts | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/constants.ts b/src/constants.ts index f5d901c8..8362a7bc 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -7,8 +7,8 @@ export const jsx: ParserPlugin = 'jsx'; export const newLineCharacters = '\n\n'; -export const ChunkSideEffectNode = 'side-effect-node'; -export const ChunkSideOtherNode = 'other-node'; +export const chunkSideEffectNode = 'side-effect-node'; +export const chunkSideOtherNode = 'other-node'; /* * Used to mark the position between RegExps, diff --git a/src/utils/get-sorted-nodes.ts b/src/utils/get-sorted-nodes.ts index bb69cec6..c4ed8045 100644 --- a/src/utils/get-sorted-nodes.ts +++ b/src/utils/get-sorted-nodes.ts @@ -1,6 +1,6 @@ import { - ChunkSideEffectNode, - ChunkSideOtherNode, + chunkSideEffectNode, + chunkSideOtherNode, newLineNode, } from '../constants'; import { GetSortedNodes, ImportChunk, ImportOrLine } from '../types'; @@ -29,8 +29,8 @@ export const getSortedNodes: GetSortedNodes = (nodes, options) => { (chunks, node) => { const type = node.specifiers.length === 0 - ? ChunkSideEffectNode - : ChunkSideOtherNode; + ? chunkSideEffectNode + : chunkSideOtherNode; const last = chunks[chunks.length - 1]; if (last === undefined || last.type !== type) { chunks.push({ type, nodes: [node] }); @@ -47,7 +47,7 @@ export const getSortedNodes: GetSortedNodes = (nodes, options) => { // Sort each chunk of side-effect and non-side-effect nodes, and insert new // lines according the importOrderSeparation option. for (const chunk of splitBySideEffectNodes) { - if (chunk.type === ChunkSideEffectNode) { + if (chunk.type === chunkSideEffectNode) { // do not sort side effect nodes finalNodes.push(...chunk.nodes); } else {