diff --git a/README.md b/README.md index 32fbf494..53d741d9 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,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/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` ? 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'; 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..8362a7bc 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 590dce5a..f0470b73 100644 --- a/src/types.ts +++ b/src/types.ts @@ -11,6 +11,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__/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-by-import-order.spec.ts b/src/utils/__tests__/get-sorted-nodes-by-import-order.spec.ts new file mode 100644 index 00000000..d488c92a --- /dev/null +++ b/src/utils/__tests__/get-sorted-nodes-by-import-order.spec.ts @@ -0,0 +1,331 @@ +import { ImportDeclaration } from '@babel/types'; + +import { getImportNodes } from '../get-import-nodes'; +import { getSortedNodes } from '../get-sorted-nodes'; +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 * as x from 'x'; +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 = getSortedNodes(result, { + importOrder: [], + importOrderCaseInsensitive: false, + importOrderSeparation: false, + importOrderGroupNamespaceSpecifiers: false, + importOrderSortSpecifiers: false, + }) as ImportDeclaration[]; + + expect(getSortedNodesNames(sorted)).toEqual([ + 'BY', + 'Ba', + 'XY', + 'Xa', + 'a', + 'c', + 'g', + 'k', + 't', + 'x', + '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'], + ['x'], + ['z'], + ]); +}); + +test('it returns all sorted nodes case-insensitive', () => { + const result = getImportNodes(code); + const sorted = getSortedNodes(result, { + importOrder: [], + importOrderCaseInsensitive: true, + importOrderSeparation: false, + importOrderGroupNamespaceSpecifiers: false, + importOrderSortSpecifiers: false, + }) as ImportDeclaration[]; + + expect(getSortedNodesNames(sorted)).toEqual([ + 'a', + 'Ba', + 'BY', + 'c', + 'g', + 'k', + 't', + 'x', + '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'], + ['x'], + ['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, + importOrderGroupNamespaceSpecifiers: false, + importOrderSortSpecifiers: false, + }) as ImportDeclaration[]; + + expect(getSortedNodesNames(sorted)).toEqual([ + 'XY', + 'Xa', + 'c', + 'g', + 'x', + 'z', + 'a', + 't', + 'k', + 'BY', + 'Ba', + ]); + expect( + sorted + .filter((node) => node.type === 'ImportDeclaration') + .map((importDeclaration) => + getSortedNodesModulesNames(importDeclaration.specifiers), + ), + ).toEqual([ + ['XY'], + ['Xa'], + ['c', 'cD'], + ['g'], + ['x'], + ['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, + importOrderGroupNamespaceSpecifiers: false, + importOrderSortSpecifiers: false, + }) as ImportDeclaration[]; + expect(getSortedNodesNames(sorted)).toEqual([ + 'c', + 'g', + 'x', + 'Xa', + 'XY', + 'z', + 'a', + 't', + 'k', + 'Ba', + 'BY', + ]); + expect( + sorted + .filter((node) => node.type === 'ImportDeclaration') + .map((importDeclaration) => + getSortedNodesModulesNames(importDeclaration.specifiers), + ), + ).toEqual([ + ['c', 'cD'], + ['g'], + ['x'], + ['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, + importOrderGroupNamespaceSpecifiers: false, + importOrderSortSpecifiers: true, + }) as ImportDeclaration[]; + expect(getSortedNodesNames(sorted)).toEqual([ + 'XY', + 'Xa', + 'c', + 'g', + 'x', + 'z', + 'a', + 't', + 'k', + 'BY', + 'Ba', + ]); + expect( + sorted + .filter((node) => node.type === 'ImportDeclaration') + .map((importDeclaration) => + getSortedNodesModulesNames(importDeclaration.specifiers), + ), + ).toEqual([ + ['XY'], + ['Xa'], + ['c', 'cD'], + ['g'], + ['x'], + ['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, + importOrderGroupNamespaceSpecifiers: false, + importOrderSortSpecifiers: true, + }) as ImportDeclaration[]; + expect(getSortedNodesNames(sorted)).toEqual([ + 'c', + 'g', + 'x', + 'Xa', + 'XY', + 'z', + 'a', + 't', + 'k', + 'Ba', + 'BY', + ]); + expect( + sorted + .filter((node) => node.type === 'ImportDeclaration') + .map((importDeclaration) => + getSortedNodesModulesNames(importDeclaration.specifiers), + ), + ).toEqual([ + ['c', 'cD'], + ['g'], + ['x'], + ['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 = getSortedNodes(result, { + importOrder: ['^a$', '', '^t$', '^k$'], + importOrderSeparation: false, + importOrderCaseInsensitive: true, + importOrderGroupNamespaceSpecifiers: false, + importOrderSortSpecifiers: false, + }) as ImportDeclaration[]; + expect(getSortedNodesNames(sorted)).toEqual([ + 'a', + 'Ba', + 'BY', + 'c', + 'g', + 'x', + 'Xa', + 'XY', + 'z', + 't', + 'k', + ]); +}); + +test('it returns all sorted nodes with namespace specifiers at the top', () => { + const result = getImportNodes(code); + const sorted = getSortedNodes(result, { + importOrder: [], + importOrderCaseInsensitive: false, + importOrderSeparation: false, + importOrderGroupNamespaceSpecifiers: true, + importOrderSortSpecifiers: false, + }) as ImportDeclaration[]; + + expect(getSortedNodesNames(sorted)).toEqual([ + 'a', + 'x', + 'BY', + 'Ba', + 'XY', + 'Xa', + 'c', + 'g', + 'k', + 't', + 'z', + ]); +}); diff --git a/src/utils/__tests__/get-sorted-nodes.spec.ts b/src/utils/__tests__/get-sorted-nodes.spec.ts index d488c92a..e8cc8c6b 100644 --- a/src/utils/__tests__/get-sorted-nodes.spec.ts +++ b/src/utils/__tests__/get-sorted-nodes.spec.ts @@ -7,20 +7,24 @@ 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 * as x from 'x'; 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: [], @@ -31,148 +35,21 @@ test('it returns all sorted nodes', () => { }) as ImportDeclaration[]; expect(getSortedNodesNames(sorted)).toEqual([ - 'BY', - 'Ba', - 'XY', - 'Xa', - 'a', + 'se3', 'c', 'g', 'k', 't', - 'x', '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'], - ['x'], - ['z'], - ]); -}); - -test('it returns all sorted nodes case-insensitive', () => { - const result = getImportNodes(code); - const sorted = getSortedNodes(result, { - importOrder: [], - importOrderCaseInsensitive: true, - importOrderSeparation: false, - importOrderGroupNamespaceSpecifiers: false, - importOrderSortSpecifiers: false, - }) as ImportDeclaration[]; - - expect(getSortedNodesNames(sorted)).toEqual([ - 'a', - 'Ba', + 'se4', + 'se1', 'BY', - 'c', - 'g', - 'k', - 't', - 'x', - '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'], - ['x'], - ['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, - importOrderGroupNamespaceSpecifiers: false, - importOrderSortSpecifiers: false, - }) as ImportDeclaration[]; - - expect(getSortedNodesNames(sorted)).toEqual([ + 'Ba', 'XY', 'Xa', - 'c', - 'g', - 'x', - 'z', 'a', - 't', - 'k', - 'BY', - 'Ba', - ]); - expect( - sorted - .filter((node) => node.type === 'ImportDeclaration') - .map((importDeclaration) => - getSortedNodesModulesNames(importDeclaration.specifiers), - ), - ).toEqual([ - ['XY'], - ['Xa'], - ['c', 'cD'], - ['g'], - ['x'], - ['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, - importOrderGroupNamespaceSpecifiers: false, - importOrderSortSpecifiers: false, - }) as ImportDeclaration[]; - expect(getSortedNodesNames(sorted)).toEqual([ - 'c', - 'g', 'x', - 'Xa', - 'XY', - 'z', - 'a', - 't', - 'k', - 'Ba', - 'BY', + 'se2', ]); expect( sorted @@ -181,151 +58,20 @@ test('it returns all sorted nodes with sort order case-insensitive', () => { getSortedNodesModulesNames(importDeclaration.specifiers), ), ).toEqual([ + [], ['c', 'cD'], ['g'], - ['x'], - ['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, - importOrderGroupNamespaceSpecifiers: false, - importOrderSortSpecifiers: true, - }) as ImportDeclaration[]; - expect(getSortedNodesNames(sorted)).toEqual([ - 'XY', - 'Xa', - 'c', - 'g', - 'x', - 'z', - 'a', - 't', - 'k', - 'BY', - 'Ba', - ]); - expect( - sorted - .filter((node) => node.type === 'ImportDeclaration') - .map((importDeclaration) => - getSortedNodesModulesNames(importDeclaration.specifiers), - ), - ).toEqual([ - ['XY'], - ['Xa'], - ['c', 'cD'], - ['g'], - ['x'], + ['tC', 'tA', 'tB'], ['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, - importOrderGroupNamespaceSpecifiers: false, - importOrderSortSpecifiers: true, - }) as ImportDeclaration[]; - expect(getSortedNodesNames(sorted)).toEqual([ - 'c', - 'g', - 'x', - 'Xa', - 'XY', - 'z', - 'a', - 't', - 'k', - 'Ba', - 'BY', - ]); - expect( - sorted - .filter((node) => node.type === 'ImportDeclaration') - .map((importDeclaration) => - getSortedNodesModulesNames(importDeclaration.specifiers), - ), - ).toEqual([ - ['c', 'cD'], - ['g'], - ['x'], - ['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, - importOrderGroupNamespaceSpecifiers: false, - importOrderSortSpecifiers: false, - }) as ImportDeclaration[]; - expect(getSortedNodesNames(sorted)).toEqual([ - 'a', - 'Ba', - 'BY', - 'c', - 'g', - 'x', - 'Xa', - 'XY', - 'z', - 't', - 'k', - ]); -}); - -test('it returns all sorted nodes with namespace specifiers at the top', () => { - const result = getImportNodes(code); - const sorted = getSortedNodes(result, { - importOrder: [], - importOrderCaseInsensitive: false, - importOrderSeparation: false, - importOrderGroupNamespaceSpecifiers: true, - importOrderSortSpecifiers: false, - }) as ImportDeclaration[]; - - expect(getSortedNodesNames(sorted)).toEqual([ - 'a', - 'x', - 'BY', - 'Ba', - 'XY', - 'Xa', - 'c', - 'g', - 'k', - 't', - 'z', + ['x'], + [], ]); }); 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..a02fc3dc --- /dev/null +++ b/src/utils/adjust-comments-on-sorted-nodes.ts @@ -0,0 +1,39 @@ +import { ImportDeclaration, addComments, 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); + } +}; 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..d8ddd623 --- /dev/null +++ b/src/utils/get-sorted-nodes-by-import-order.ts @@ -0,0 +1,78 @@ +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'; +import { getSortedNodesGroup } from './get-sorted-nodes-group'; + +/** + * 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, + importOrderGroupNamespaceSpecifiers, + } = 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]; + + if (groupNodes.length === 0) continue; + + const sortedInsideGroup = getSortedNodesGroup(groupNodes, { + importOrderGroupNamespaceSpecifiers, + }); + + // Sort the import specifiers + if (importOrderSortSpecifiers) { + sortedInsideGroup.forEach((node) => + getSortedImportSpecifiers(node), + ); + } + + finalNodes.push(...sortedInsideGroup); + + if (importOrderSeparation) { + finalNodes.push(newLineNode); + } + } + + return finalNodes; +}; diff --git a/src/utils/get-sorted-nodes-group.ts b/src/utils/get-sorted-nodes-group.ts index 9c9ae6bd..89951336 100644 --- a/src/utils/get-sorted-nodes-group.ts +++ b/src/utils/get-sorted-nodes-group.ts @@ -1,4 +1,5 @@ import { Import, ImportDeclaration } from '@babel/types'; + import { naturalSort } from '../natural-sort'; import { PrettierOptions } from '../types'; diff --git a/src/utils/get-sorted-nodes.ts b/src/utils/get-sorted-nodes.ts index d2c81278..c4ed8045 100644 --- a/src/utils/get-sorted-nodes.ts +++ b/src/utils/get-sorted-nodes.ts @@ -1,106 +1,71 @@ -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 { getSortedNodesGroup } from './get-sorted-nodes-group'; +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, - importOrderGroupNamespaceSpecifiers, - } = 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, + 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; + }, + [], ); - 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 = getSortedNodesGroup(groupNodes, { - importOrderGroupNamespaceSpecifiers, - }); + const finalNodes: ImportOrLine[] = []; - // 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; }; diff --git a/tests/Angular/__snapshots__/ppsi.spec.js.snap b/tests/Angular/__snapshots__/ppsi.spec.js.snap index d2a3cb5b..35bc6136 100644 --- a/tests/Angular/__snapshots__/ppsi.spec.js.snap +++ b/tests/Angular/__snapshots__/ppsi.spec.js.snap @@ -80,3 +80,150 @@ 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-y3"; +import "side-effect-y1"; +import "side-effect-y2"; + +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-y3"; +import "side-effect-y1"; +import "side-effect-y2"; + +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..c5f86542 --- /dev/null +++ b/tests/Angular/imports-with-side-effect-imports.js @@ -0,0 +1,67 @@ +// 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-y3"; +import "side-effect-y1"; +import "side-effect-y2"; + +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..ac1bdf1c 100644 --- a/tests/Babel/__snapshots__/ppsi.spec.js.snap +++ b/tests/Babel/__snapshots__/ppsi.spec.js.snap @@ -42,3 +42,105 @@ 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-y3"; +import "side-effect-y1"; +import "side-effect-y2"; + +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-y3"; +import "side-effect-y1"; +import "side-effect-y2"; + +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..fcc19e9b --- /dev/null +++ b/tests/Babel/imports-with-side-effect-imports.js @@ -0,0 +1,45 @@ +// 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-y3"; +import "side-effect-y1"; +import "side-effect-y2"; + +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..153a68e3 100644 --- a/tests/Flow/__snapshots__/ppsi.spec.js.snap +++ b/tests/Flow/__snapshots__/ppsi.spec.js.snap @@ -65,3 +65,137 @@ 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-y3"; +import "side-effect-y1"; +import "side-effect-y2"; + +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-y3"; +import "side-effect-y1"; +import "side-effect-y2"; + +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..7ca6707a --- /dev/null +++ b/tests/Flow/imports-with-side-effect-imports.js @@ -0,0 +1,60 @@ +/** + * @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-y3"; +import "side-effect-y1"; +import "side-effect-y2"; + +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..d3da0a1d 100644 --- a/tests/ImportsSeparated/__snapshots__/ppsi.spec.js.snap +++ b/tests/ImportsSeparated/__snapshots__/ppsi.spec.js.snap @@ -51,6 +51,60 @@ function add(a: number, b: number) { `; +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"; +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 "side-effect"; + +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-only.ts - typescript-verify: import-export-only.ts 1`] = ` import React from 'react'; export const a = 1; @@ -100,10 +154,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-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/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..da52bb04 100644 --- a/tests/Typescript/__snapshots__/ppsi.spec.js.snap +++ b/tests/Typescript/__snapshots__/ppsi.spec.js.snap @@ -1,5 +1,123 @@ // 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-y3"; +import "side-effect-y1"; +import "side-effect-y2"; + +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-y3"; +import "side-effect-y1"; +import "side-effect-y2"; + +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..8e953a0f --- /dev/null +++ b/tests/Typescript/import-with-side-effect-imports.ts @@ -0,0 +1,53 @@ +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-y3"; +import "side-effect-y1"; +import "side-effect-y2"; + +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; + } +}