├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .github
└── main.workflow
├── .gitignore
├── .importsortrc
├── .prettierrc
├── LICENSE
├── README.md
├── lerna.json
├── package.json
├── packages
├── atom-import-sort
│ ├── .eslintrc
│ ├── .gitignore
│ ├── .npmignore
│ ├── README.md
│ ├── doc
│ │ └── atom-import-sort.gif
│ ├── keymaps
│ │ └── atom-import-sort.cson
│ ├── menus
│ │ └── atom-import-sort.cson
│ ├── package.json
│ ├── src
│ │ ├── index.d.ts
│ │ └── index.ts
│ ├── test
│ │ └── index.ts
│ └── tsconfig.json
├── import-sort-cli
│ ├── .eslintrc
│ ├── package.json
│ ├── src
│ │ └── index.ts
│ ├── test
│ │ └── index.ts
│ └── tsconfig.json
├── import-sort-config
│ ├── package.json
│ ├── src
│ │ ├── index.d.ts
│ │ └── index.ts
│ ├── test
│ │ ├── fixtures
│ │ │ ├── .importsortrc
│ │ │ ├── local-style.js
│ │ │ └── node_modules
│ │ │ │ ├── import-sort-style-test
│ │ │ │ ├── index.js
│ │ │ │ └── package.json
│ │ │ │ └── some-parser
│ │ │ │ ├── index.js
│ │ │ │ └── package.json
│ │ └── index.ts
│ └── tsconfig.json
├── import-sort-parser-babylon
│ ├── package.json
│ ├── src
│ │ ├── index.d.ts
│ │ └── index.ts
│ ├── test
│ │ ├── flow-babelrc
│ │ │ ├── .babelrc
│ │ │ └── flow.ts
│ │ ├── flow
│ │ │ └── flow.ts
│ │ ├── index.ts
│ │ ├── typescript-babelrc
│ │ │ ├── .babelrc
│ │ │ └── typescript.ts
│ │ └── typescript
│ │ │ └── typescript.ts
│ └── tsconfig.json
├── import-sort-parser-typescript
│ ├── package.json
│ ├── src
│ │ └── index.ts
│ ├── test
│ │ └── index.ts
│ └── tsconfig.json
├── import-sort-parser
│ ├── package.json
│ ├── src
│ │ └── index.ts
│ ├── test
│ │ └── index.ts
│ └── tsconfig.json
├── import-sort-playground
│ ├── .eslintrc
│ ├── .importsortrc
│ ├── babylon.js
│ ├── package.json
│ ├── tsconfig.json
│ └── typescript.ts
├── import-sort-style-eslint
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ └── index.ts
│ ├── test
│ │ ├── default
│ │ │ ├── .eslintrc
│ │ │ └── index.ts
│ │ └── index.ts
│ └── tsconfig.json
├── import-sort-style-module
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ └── index.ts
│ ├── test
│ │ └── index.ts
│ └── tsconfig.json
├── import-sort-style-renke
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ └── index.ts
│ ├── test
│ │ └── index.ts
│ └── tsconfig.json
├── import-sort-style
│ ├── package.json
│ ├── src
│ │ └── index.ts
│ ├── test
│ │ └── index.ts
│ └── tsconfig.json
├── import-sort
│ ├── README.md
│ ├── package.json
│ ├── src
│ │ ├── index.ts
│ │ └── style
│ │ │ └── StyleAPI.ts
│ ├── test
│ │ ├── babylon_eslint_default
│ │ │ ├── .eslintrc
│ │ │ ├── babylon_eslint.js.test
│ │ │ └── index.ts
│ │ ├── babylon_eslint_ignore_case
│ │ │ ├── .eslintrc
│ │ │ ├── babylon_eslint.js.test
│ │ │ └── index.ts
│ │ ├── index.ts
│ │ ├── issues.ts
│ │ ├── stubImport.ts
│ │ └── style
│ │ │ ├── StyleAPI.ts
│ │ │ └── issues.ts
│ └── tsconfig.json
├── tsconfig.json
└── tsconfig.settings.json
├── scripts
└── publish-atom-import-sort.js
├── tsconfig.json
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 |
6 | insert_final_newline = false
7 | end_of_line = lf
8 |
9 | indent_size = 2
10 | indent_style = space
11 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | packages/*/test
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "@typescript-eslint/parser",
3 | "plugins": ["@typescript-eslint"],
4 | "extends": ["airbnb", "plugin:@typescript-eslint/recommended", "prettier"],
5 | "rules": {
6 | "@typescript-eslint/indent": "off",
7 | "@typescript-eslint/explicit-function-return-type": "off",
8 | "import/no-extraneous-dependencies": ["error", {"devDependencies": true}],
9 | "no-restricted-syntax": "off",
10 | "@typescript-eslint/interface-name-prefix": "off",
11 | "import/no-unresolved": "off",
12 | "import/no-dynamic-require": "off",
13 | "global-require": "off",
14 | "no-use-before-define": ["error", "nofunc"],
15 | "@typescript-eslint/no-use-before-define": ["error", "nofunc"],
16 | "no-continue": "off",
17 | "prefer-template": "off"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/.github/main.workflow:
--------------------------------------------------------------------------------
1 | workflow "ci" {
2 | on = "push"
3 | resolves = ["test", "lint"]
4 | }
5 |
6 | action "install" {
7 | uses = "actions/npm@3c8332795d5443adc712d30fa147db61fd520b5a"
8 | runs = "yarn"
9 | args = "install"
10 | }
11 |
12 | action "build" {
13 | uses = "actions/npm@3c8332795d5443adc712d30fa147db61fd520b5a"
14 | needs = ["install"]
15 | runs = "yarn"
16 | args = "run build"
17 | }
18 |
19 | action "test" {
20 | uses = "actions/npm@3c8332795d5443adc712d30fa147db61fd520b5a"
21 | needs = ["build"]
22 | runs = "yarn"
23 | args = "test"
24 | }
25 |
26 | action "lint" {
27 | uses = "actions/npm@3c8332795d5443adc712d30fa147db61fd520b5a"
28 | needs = ["build"]
29 | runs = "yarn"
30 | args = "run lint"
31 | }
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .vscode/
3 | *.iml
4 | lerna-debug.log
5 | yarn-error.log
6 | node_modules
7 | npm-debug.log
8 | packages/*/lib/
9 |
10 | !**/fixtures/node_modules/
11 |
--------------------------------------------------------------------------------
/.importsortrc:
--------------------------------------------------------------------------------
1 | {
2 | ".ts": {
3 | "parser": "babylon",
4 | "style": "module"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "all",
3 | "bracketSpacing": false
4 | }
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016, Renke Grunwald
2 |
3 | Permission to use, copy, modify, and/or distribute this software for any
4 | purpose with or without fee is hereby granted, provided that the above
5 | copyright notice and this permission notice appear in all copies.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # import-sort
2 |
3 | `import-sort` is a set of packages that allow you to sort your ES2015 (aka ES6)
4 | imports. Both JavaScript and TypeScript files are supported.
5 |
6 | # Sorting imports
7 |
8 | There are multiple ways to actually sort your imports. Just pick the one that
9 | suits you most.
10 |
11 | - Visual Studio Code
12 | - Atom
13 | - Vim
14 | - JetBrains IDEs (IntelliJ IDEA, WebStorm etc.)
15 | - Command Line
16 |
17 | ## Visual Studio Code (vsc-sort-imports)
18 |
19 | Sort your imports directy from within
20 | [Visual Studio Code](https://code.visualstudio.com/).
21 |
22 | See
23 | [sort-imports](https://marketplace.visualstudio.com/items?itemName=amatiasq.sort-imports)
24 | in the Visual Studio Marketplace for more details.
25 |
26 | This extension was originally developed by
27 | [Peter Juras](https://github.com/peterjuras) and is currently maintained by
28 | [A. Matías Quezada](https://github.com/amatiasq). Thank you very much!
29 |
30 | ## Atom (atom-import-sort)
31 |
32 | Sort your imports directly from within [Atom](https://atom.io/). Go to
33 | [package](https://atom.io/packages/atom-import-sort) or install it directly with
34 | `apm install atom-import-sort`. The plugin can sort imports in both JavaScript
35 | and TypeScript.
36 |
37 | After you installed the package you can sort your imports using the
38 | Ctrl + Alt + o key binding or trigger it
39 | manually from the command palette with the `Import Sort: Sort` command.
40 |
41 | The package also offers a "sort on save" option to automatically sort your
42 | imports whenever you save a JavaScript or TypeScript file. It's disabled by
43 | default.
44 |
45 | ## Vim (vim-sort-imports)
46 |
47 | Sort your imports directy from within Vim. See
48 | [vim-sort-imports](https://github.com/ruanyl/vim-sort-imports) for more details
49 | about the configuration.
50 |
51 | # JetBrains IDEs
52 |
53 | To use import-sort in any of the JetBrains IDEs follow the instructions regarding File Watcher in the
54 | [prettier documentation](https://prettier.io/docs/en/webstorm.html) and replace `prettier` with `import-sort`.
55 |
56 | Alternatively, just install the [File Watcher plugin](https://plugins.jetbrains.com/plugin/7177-file-watchers) and try
57 | to import the following
58 | [File Watcher configuration](https://gist.githubusercontent.com/renke/f08c6022a01a1465b025f83b82b3b028/raw/3eb3fd5f7dd6fc67f145c6a27ff1db6eb64c27bb/watchers.xml).
59 |
60 | ## Command Line (import-sort-cli)
61 |
62 | Sort your imports from the command line. Useful to sort all your files in bulk
63 | or from a script in your `package.json`.
64 |
65 | Install it with `npm install --save-dev import-sort-cli` or use it directly with
66 | `npx import-sort-cli`.
67 |
68 | _ATTENTION_: Since version 4 `--write` modifies file in-place. The old
69 | `--overwrite` flag was removed. The CLI now behaves closer to
70 | [prettier's](https://github.com/prettier/prettier) CLI. Also, the exit code is
71 | now 0 even when unsorted were sorted (unless `--list-different` is used.)
72 |
73 | ```
74 | Usage: import-sort [OPTION]... [FILE/GLOB]...
75 |
76 | Options:
77 | --list-different, -l Print the names of files that are not sorted. [boolean]
78 | --write Edit files in-place. [boolean]
79 | --with-node-modules Process files inside 'node_modules' directory..[boolean]
80 | --version, -v Show version number [boolean]
81 | --help, -h Show help [boolean]
82 | ```
83 |
84 | ## Node.js (import-sort)
85 |
86 | Sort your imports via [Node.js](https://nodejs.org/). For more information take
87 | a look at the code of the `import-sort-cli` package.
88 |
89 | To use it you probably want to install `import-sort`, `import-sort-config`, a
90 | parser (say `import-sort-parser-babylon`) and a style (say
91 | `import-sort-style-eslint`).
92 |
93 | The `import-sort` library is basically the heart that powers `import-sort-cli`
94 | and `atom-import-sort` and should be used if you want to integrate it with other
95 | environments.
96 |
97 | # Ignoring files
98 |
99 | Sometimes the imports in a certain file should not be sorted. To prevent
100 | `import-sort` from sorting a particular file, just add `// import-sort-ignore`
101 | or `/* import-sort-ignore */` to your file. Anwhere in the file is fine.
102 |
103 | # Dealing with comments
104 |
105 | Prior versions of `import-sort` had problems with comments that were attached to
106 | imports. This is now mostly fixed and situations like the following should no
107 | longer cause problems.
108 |
109 | ```js
110 | import foo from "bar"; // This will move with the import
111 | ```
112 |
113 | ```js
114 | // This will also move with the import
115 | import foo from "bar";
116 | ```
117 |
118 | ```js
119 | // This won't move with the import
120 |
121 | // This will move with the import
122 | import foo from "bar";
123 | // This won't move with the import
124 | ```
125 |
126 | In general, every comment that is directly above the import (no blank line
127 | between them) or is on the same line is considered part of it.
128 |
129 | That means that things like `// eslint-disable line` and `// eslint-disable-next-line` are finally supported.
130 |
131 | For copyright headers and compiler pragmas (like `@flow`) a blank line should be
132 | added after the comment.
133 |
134 | ```js
135 | // @flow
136 |
137 | import foo from "bar";
138 | ```
139 |
140 | # Using a different style or parser
141 |
142 | Styles (and parsers) can be configured on a per-project basis including support
143 | for different types of files (currently JavaScript and TypeScript).
144 |
145 | Just add the following to your `package.json` and adapt it to your liking:
146 |
147 | ```json
148 | "importSort": {
149 | ".js, .jsx, .es6, .es, .mjs, .ts, .tsx": {
150 | "parser": "babylon",
151 | "style": "eslint"
152 | }
153 | }
154 | ```
155 |
156 | The keys are a list of file extensions that map to the parser and style that
157 | should be used for files that have any of the listed file extensions.
158 |
159 | Instead of putting your configuration into your `package.json` you can also use
160 | a `.importsortrc` file written in JSON. For more details see
161 | [cosmiconfig](https://github.com/davidtheclark/cosmiconfig) which is used
162 | internally by `import-sort`.
163 |
164 | By default, `import-sort` comes with these styles:
165 |
166 | - [`import-sort-style-eslint` (default)](packages/import-sort-style-eslint): A
167 | style that that is compatible with [ESLint's](http://eslint.org/)
168 | [sort-imports](http://eslint.org/docs/rules/sort-imports) rule.
169 |
170 | - [`import-sort-style-module`](packages/import-sort-style-module): A style that
171 | groups and sorts by module.
172 |
173 | - [`import-sort-style-renke`](packages/import-sort-style-renke): Renke's
174 | personal style.
175 |
176 | # Writing you own custom style
177 |
178 | Since styles can now be configured using your `package.json` it's way easier to
179 | write and use your own style.
180 |
181 | A style is module that should be called `import-sort-style-$name` where `$name`
182 | is the name of the style.
183 |
184 | An API is provided to specify how the imports are sorted (see
185 | [style API](packages/import-sort-style/src/index.ts#L3) for more details).
186 |
187 | The best way to write your own style is to look at existing styles like
188 | [`import-sort-style-renke`](packages/import-sort-style-renke/src/index.ts) and
189 | adapt it to your liking.
190 |
191 | # Feedback
192 |
193 | I appreciate any kind of feedback. Just create an issue or drop me a mail.
194 | Thanks!
195 |
196 | # License
197 |
198 | See [LICENSE](LICENSE).
199 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "lerna": "2.11.0",
3 | "version": "6.0.0",
4 | "useWorkspaces": true,
5 | "packages": ["packages/*"],
6 | "npmClient": "yarn"
7 | }
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "build": "tsc -b packages",
5 | "build:watch": "tsc -b packages -w",
6 | "clean:build": "lerna exec -- rimraf lib",
7 | "test": "mocha --require ts-node/register --recursive \"packages/*/test/**/*.ts\"",
8 | "test:watch": "mocha -w --require ts-node/register --recursive \"packages/*/test/**/*.ts\"",
9 | "lint": "eslint --ext ts packages/*/src"
10 | },
11 | "lint-staged": {
12 | "*.ts": [
13 | "npx import-sort-cli --write",
14 | "prettier --write",
15 | "eslint --fix",
16 | "git add"
17 | ],
18 | "*.{json,md}": [
19 | "prettier --write",
20 | "git add"
21 | ]
22 | },
23 | "workspaces": {
24 | "packages": [
25 | "packages/*"
26 | ]
27 | },
28 | "devDependencies": {
29 | "@types/chai": "^4.1.4",
30 | "@types/mocha": "^5.2.3",
31 | "@types/node": "^10.12.20",
32 | "chai": "^4.1.2",
33 | "husky": "^1.3.1",
34 | "import-sort": "^6",
35 | "import-sort-cli": "^6",
36 | "import-sort-parser-babylon": "^6",
37 | "import-sort-style-module": "^6",
38 | "lerna": "^3.10.7",
39 | "lint-staged": "^8.1.1",
40 | "mocha": "^5.2.0",
41 | "prettier": "^1.13.5",
42 | "ts-node": "^8.0.2",
43 | "typescript": "^3.2.4"
44 | },
45 | "husky": {
46 | "hooks": {
47 | "pre-commit": "lint-staged"
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/packages/atom-import-sort/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../.eslintrc",
3 | "globals": {
4 | "atom": false
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/packages/atom-import-sort/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 |
--------------------------------------------------------------------------------
/packages/atom-import-sort/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 | test
3 | tsconfig.json
4 | tslint.json
5 |
6 |
--------------------------------------------------------------------------------
/packages/atom-import-sort/README.md:
--------------------------------------------------------------------------------
1 | # atom-import-sort
2 |
3 | Sort ES2015 (aka ES6) imports directly from within Atom. Both JavaScript and TypeScript are supported.
4 |
5 | 
6 |
7 | After you installed the package you can sort your imports using the Ctrl + Alt + o key binding or trigger it manually from the command palette with the `Import Sort: Sort` command.
8 |
9 | The package also offers a "sort on save" option to automatically sort your imports whenever you save a JavaScript or TypeScript file. It's disabled by default.
10 |
11 | See [`import-sort`](https://github.com/renke/import-sort) for information about customizing the way imports are sorted.
12 |
--------------------------------------------------------------------------------
/packages/atom-import-sort/doc/atom-import-sort.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renke/import-sort/302fe2d494307f4fedff7ad2b8a4b67d4eaad142/packages/atom-import-sort/doc/atom-import-sort.gif
--------------------------------------------------------------------------------
/packages/atom-import-sort/keymaps/atom-import-sort.cson:
--------------------------------------------------------------------------------
1 | 'atom-text-editor[data-grammar~="source"][data-grammar~="js"],atom-text-editor[data-grammar~="source"][data-grammar~="ts"]':
2 | 'ctrl-alt-o': 'import-sort:sort'
3 |
--------------------------------------------------------------------------------
/packages/atom-import-sort/menus/atom-import-sort.cson:
--------------------------------------------------------------------------------
1 | 'context-menu':
2 | 'atom-text-editor[data-grammar~="source"][data-grammar~="js"], atom-text-editor[data-grammar~="source"][data-grammar~="ts"]': [
3 | {
4 | 'label': 'Sort Imports'
5 | 'command': 'import-sort:sort'
6 | }
7 | ]
8 | 'menu': [
9 | {
10 | 'label': 'Packages'
11 | 'submenu': [
12 | 'label': 'Import Sort'
13 | 'submenu': [
14 | {
15 | 'label': 'Sort Imports'
16 | 'command': 'import-sort:sort'
17 | }
18 | ]
19 | ]
20 | }
21 | ]
22 |
--------------------------------------------------------------------------------
/packages/atom-import-sort/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "atom-import-sort",
3 | "private": true,
4 | "version": "6.0.0",
5 | "description": "Sort ES2015 (aka ES6) imports. Manually – or automatically when you save your JavaSript or TypeScript files.",
6 | "keywords": [
7 | "es6",
8 | "eslint",
9 | "import-sort",
10 | "import",
11 | "sort",
12 | "typescript"
13 | ],
14 | "main": "lib/index.js",
15 | "typings": "lib/index.d.ts",
16 | "scripts": {
17 | "prepublishOnly": "tsc -b .",
18 | "build": "tsc -b .",
19 | "build:watch": "tsc -b . -w",
20 | "test": "mocha --require ts-node/register --recursive \"test/**/*.ts\"",
21 | "test:watch": "mocha -w --require ts-node/register --recursive \"test/**/*.ts\"",
22 | "lint": "eslint --ext ts src"
23 | },
24 | "author": "Renke Grunwald (https://github.com/renke)",
25 | "repository": "renke/import-sort",
26 | "license": "ISC",
27 | "engines": {
28 | "atom": ">=1.0.0 <2.0.0"
29 | },
30 | "devDependencies": {
31 | "@types/atom": "^1.31.0",
32 | "@types/chai": "^4.0.0",
33 | "@types/mocha": "^5.2.5",
34 | "@types/node": "^10.12.20",
35 | "@typescript-eslint/eslint-plugin": "^1.2.0",
36 | "chai": "^4.0.2",
37 | "eslint": "^5.13.0",
38 | "eslint-config-airbnb": "^17.1.0",
39 | "eslint-config-prettier": "^4.0.0",
40 | "eslint-plugin-import": "^2.16.0",
41 | "eslint-plugin-jsx-a11y": "^6.2.0",
42 | "eslint-plugin-react": "^7.12.4",
43 | "mocha": "^5.2.0",
44 | "ts-node": "^8.0.2",
45 | "typescript": "^3.2.4"
46 | },
47 | "dependencies": {
48 | "import-sort": "^6.0.0",
49 | "import-sort-config": "^6.0.0",
50 | "import-sort-parser-babylon": "^6.0.0",
51 | "import-sort-parser-typescript": "^6.0.0",
52 | "import-sort-style-eslint": "^6.0.0",
53 | "loophole": "^1.1.0"
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/packages/atom-import-sort/src/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module "loophole" {
2 | export function allowUnsafeEval(f: () => unknown);
3 | export function allowUnsafeNewFunction(f: () => unknown);
4 | }
5 |
--------------------------------------------------------------------------------
/packages/atom-import-sort/src/index.ts:
--------------------------------------------------------------------------------
1 | import {dirname, extname} from "path";
2 |
3 | import {CompositeDisposable, TextEditor} from "atom"; // eslint-disable-line
4 | import sortImports, {ICodeChange} from "import-sort";
5 | import {getConfig} from "import-sort-config";
6 | import {allowUnsafeEval, allowUnsafeNewFunction} from "loophole";
7 |
8 | // eslint-disable-next-line
9 | export class Plugin {
10 | public bufferWillSaveDisposables;
11 |
12 | public editorObserverDisposable;
13 |
14 | public config = {
15 | sortOnSave: {
16 | title: "Sort on save",
17 | description:
18 | "Automatically sort your Javascript files when you save them.",
19 | type: "boolean",
20 | default: false,
21 | },
22 | };
23 |
24 | public activate() {
25 | atom.config.observe(
26 | "atom-import-sort.sortOnSave",
27 | (sortOnSave: boolean) => {
28 | if (sortOnSave) {
29 | this.observeEditors();
30 | } else {
31 | this.unobserveEditors();
32 | }
33 | },
34 | );
35 |
36 | // tslint:disable-next-line
37 | atom.commands.add(
38 | 'atom-text-editor[data-grammar~="source"][data-grammar~="js"],atom-text-editor[data-grammar~="source"][data-grammar~="ts"]',
39 | "import-sort:sort",
40 | () => this.sortCurrentEditor(),
41 | );
42 | }
43 |
44 | public deactivate() {
45 | this.unobserveEditors();
46 | }
47 |
48 | private observeEditors() {
49 | if (!this.editorObserverDisposable) {
50 | this.bufferWillSaveDisposables = new CompositeDisposable();
51 |
52 | this.editorObserverDisposable = atom.workspace.observeTextEditors(
53 | editor => {
54 | this.bufferWillSaveDisposables.add(
55 | editor.getBuffer().onWillSave(() => {
56 | this.sortEditor(editor, true);
57 | }),
58 | );
59 | },
60 | );
61 | }
62 | }
63 |
64 | private unobserveEditors() {
65 | if (this.editorObserverDisposable) {
66 | this.bufferWillSaveDisposables.dispose();
67 |
68 | this.editorObserverDisposable.dispose();
69 | this.editorObserverDisposable = null;
70 | }
71 | }
72 |
73 | // eslint-disable-next-line
74 | private sortEditor(editor, notifyErrors = false) {
75 | const scopeDescriptor = editor.getRootScopeDescriptor();
76 |
77 | if (!scopeDescriptor) {
78 | return;
79 | }
80 |
81 | const scope = scopeDescriptor.scopes[0];
82 |
83 | let extension: string | undefined;
84 | let directory: string | undefined;
85 |
86 | const path = editor.getPath();
87 |
88 | if (path) {
89 | const rawExtension = extname(path);
90 |
91 | if (rawExtension.indexOf(".") !== -1) {
92 | extension = rawExtension;
93 | }
94 |
95 | directory = dirname(path);
96 | } else {
97 | // TODO: Refactor the following if statements
98 |
99 | if (scope.split(".").some(part => part === "js")) {
100 | extension = ".js";
101 | }
102 |
103 | if (scope.split(".").some(part => part === "ts")) {
104 | extension = ".ts";
105 | }
106 |
107 | [directory] = atom.project.getPaths();
108 | }
109 |
110 | if (!extension) {
111 | return;
112 | }
113 |
114 | try {
115 | const sortConfig = getConfig(extension, directory);
116 |
117 | if (!sortConfig) {
118 | if (!notifyErrors) {
119 | atom.notifications.addWarning(
120 | `No configuration found for this file type`,
121 | );
122 | }
123 |
124 | return;
125 | }
126 |
127 | const {parser, style, config: rawConfig} = sortConfig;
128 |
129 | if (!parser || !style) {
130 | if (!parser && !notifyErrors) {
131 | atom.notifications.addWarning(
132 | `Parser '${sortConfig.config.parser}' not found`,
133 | );
134 | }
135 |
136 | if (!style && !notifyErrors) {
137 | atom.notifications.addWarning(
138 | `Style '${sortConfig.config.style}' not found`,
139 | );
140 | }
141 |
142 | return;
143 | }
144 |
145 | const cursor = editor.getCursorBufferPosition();
146 | const unsorted = editor.getText();
147 |
148 | let changes: ICodeChange[];
149 |
150 | allowUnsafeNewFunction(() => {
151 | allowUnsafeEval(() => {
152 | ({changes} = sortImports(
153 | unsorted,
154 | parser,
155 | style,
156 | path,
157 | rawConfig.options,
158 | ));
159 | });
160 | });
161 |
162 | (editor as TextEditor).transact(() => {
163 | for (const change of changes) {
164 | const start = editor.buffer.positionForCharacterIndex(change.start);
165 | const end = editor.buffer.positionForCharacterIndex(change.end);
166 |
167 | editor.setTextInBufferRange([start, end], change.code);
168 | }
169 | });
170 |
171 | editor.setCursorBufferPosition(cursor);
172 | } catch (e) {
173 | if (!notifyErrors) {
174 | atom.notifications.addWarning(
175 | `Failed to sort imports:\n${e.toString()}`,
176 | );
177 | }
178 | }
179 | }
180 |
181 | private sortCurrentEditor() {
182 | const editor = atom.workspace.getActiveTextEditor();
183 |
184 | if (editor) {
185 | this.sortEditor(editor);
186 | }
187 | }
188 | }
189 |
190 | module.exports = new Plugin();
191 |
--------------------------------------------------------------------------------
/packages/atom-import-sort/test/index.ts:
--------------------------------------------------------------------------------
1 | // import "mocha";
2 | // import {assert} from "chai";
3 |
--------------------------------------------------------------------------------
/packages/atom-import-sort/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.settings.json",
3 |
4 | "compilerOptions": {
5 | "outDir": "lib",
6 | "rootDir": "src"
7 | },
8 |
9 | "include": ["src"],
10 |
11 | "references": [
12 | {"path": "../import-sort-parser"},
13 | {"path": "../import-sort-config"},
14 | {"path": "../import-sort-parser-babylon"},
15 | {"path": "../import-sort-parser-typescript"},
16 | {"path": "../import-sort-style-eslint"}
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/packages/import-sort-cli/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../.eslintrc",
3 | "rules": {
4 | "no-console": "off"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/packages/import-sort-cli/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "import-sort-cli",
3 | "version": "6.0.0",
4 | "description": "Sort ES2015 (aka ES6) imports from the command line.",
5 | "main": "lib/index.js",
6 | "bin": {
7 | "import-sort": "lib/index.js"
8 | },
9 | "typings": "lib/index.d.ts",
10 | "scripts": {
11 | "prepublishOnly": "tsc -b .",
12 | "build": "tsc -b .",
13 | "build:watch": "tsc -b . -w",
14 | "test": "mocha --require ts-node/register --recursive \"test/**/*.ts\"",
15 | "test:watch": "mocha -w --require ts-node/register --recursive \"test/**/*.ts\"",
16 | "lint": "eslint --ext ts src"
17 | },
18 | "author": "Renke Grunwald (https://github.com/renke)",
19 | "repository": "renke/import-sort",
20 | "license": "ISC",
21 | "files": [
22 | "lib"
23 | ],
24 | "devDependencies": {
25 | "@types/chai": "^4.1.4",
26 | "@types/diff": "^4.0.0",
27 | "@types/globby": "^8.0.0",
28 | "@types/mkdirp": "^0.5.2",
29 | "@types/mocha": "^5.2.3",
30 | "@types/node": "^10.3.5",
31 | "@types/yargs": "^12.0.8",
32 | "@typescript-eslint/eslint-plugin": "^1.2.0",
33 | "chai": "^4.0.2",
34 | "eslint": "^5.13.0",
35 | "eslint-config-airbnb": "^17.1.0",
36 | "eslint-config-prettier": "^4.0.0",
37 | "eslint-plugin-import": "^2.16.0",
38 | "eslint-plugin-jsx-a11y": "^6.2.0",
39 | "eslint-plugin-react": "^7.12.4",
40 | "mocha": "^5.2.0",
41 | "ts-node": "^8.0.2",
42 | "typescript": "^3.2.4"
43 | },
44 | "dependencies": {
45 | "diff": "^4.0.1",
46 | "file": "^0.2.2",
47 | "globby": "^9.0.0",
48 | "import-sort": "^6.0.0",
49 | "import-sort-config": "^6.0.0",
50 | "import-sort-parser": "^6.0.0",
51 | "import-sort-parser-babylon": "^6.0.0",
52 | "import-sort-style": "^6.0.0",
53 | "import-sort-style-eslint": "^6.0.0",
54 | "mkdirp": "^0.5.1",
55 | "yargs": "^12.0.5"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/packages/import-sort-cli/src/index.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import {readFileSync, writeFileSync} from "fs";
4 | import * as path from "path";
5 |
6 | import * as globby from "globby";
7 | import sortImports, {ISortResult} from "import-sort";
8 | import {IResolvedConfig, getConfig} from "import-sort-config";
9 | import * as yargs from "yargs";
10 |
11 | yargs
12 | .usage(
13 | `
14 | Usage: import-sort [OPTION]... [FILE/GLOB]...
15 |
16 | `.trim(),
17 | )
18 | .describe("list-different", "Print the names of files that are not sorted.")
19 | .boolean("list-different")
20 | .alias("list-different", "l")
21 |
22 | .describe("write", "Edit files in-place.")
23 | .boolean("write")
24 |
25 | .describe(
26 | "with-node-modules",
27 | "Process files inside 'node_modules' directory..",
28 | )
29 | .boolean("with-node-modules")
30 |
31 | // tslint:disable-next-line:no-var-requires
32 | .version(require("../package.json").version)
33 | .alias("version", "v")
34 |
35 | .help()
36 | .alias("help", "h");
37 |
38 | const {argv} = yargs;
39 |
40 | let filePatterns = argv._;
41 |
42 | const listDifferent = argv["list-different"];
43 | const writeFiles = argv.write;
44 | const ignoreNodeModules = !argv["with-node-modules"];
45 |
46 | if (filePatterns.length === 0) {
47 | yargs.showHelp();
48 | process.exit(1);
49 | }
50 |
51 | if (ignoreNodeModules) {
52 | filePatterns = filePatterns.concat([
53 | "!**/node_modules/**",
54 | "!./node_modules/**",
55 | ]);
56 | }
57 |
58 | let filePaths;
59 |
60 | try {
61 | filePaths = globby
62 | // @ts-ignore
63 | .sync(filePatterns, {dot: true, expandDirectories: false})
64 | .map(filePath => path.relative(process.cwd(), filePath));
65 | } catch (e) {
66 | console.error("Invalid file patterns");
67 | process.exit(2);
68 | }
69 |
70 | if (filePaths.length === 0) {
71 | console.error(
72 | `No files found for the given patterns: ${filePatterns.join(", ")}`,
73 | );
74 | process.exit(2);
75 | }
76 |
77 | for (const filePath of filePaths) {
78 | let config;
79 |
80 | try {
81 | config = getAndCheckConfig(path.extname(filePath), path.dirname(filePath));
82 | } catch (e) {
83 | handleFilePathError(filePath, e);
84 | continue;
85 | }
86 |
87 | const unsortedCode = readFileSync(filePath).toString("utf8");
88 |
89 | const {parser, style, config: rawConfig} = config;
90 | let sortResult: ISortResult | undefined;
91 |
92 | try {
93 | sortResult = sortImports(
94 | unsortedCode,
95 | parser,
96 | style,
97 | filePath,
98 | rawConfig.options,
99 | );
100 | } catch (e) {
101 | handleFilePathError(filePath, e);
102 | continue;
103 | }
104 |
105 | const {code: sortedCode, changes} = sortResult;
106 |
107 | const isDifferent = changes.length > 0;
108 |
109 | if (writeFiles && isDifferent) {
110 | writeFileSync(filePath, sortedCode, {encoding: "utf-8"});
111 | }
112 |
113 | if (listDifferent && isDifferent) {
114 | process.exitCode = 1;
115 | console.log(filePath);
116 | }
117 |
118 | if (!writeFiles && !listDifferent) {
119 | process.stdout.write(sortedCode);
120 | }
121 | }
122 |
123 | function getAndCheckConfig(
124 | extension: string,
125 | fileDirectory: string,
126 | ): IResolvedConfig {
127 | const resolvedConfig = getConfig(extension, fileDirectory);
128 |
129 | if (!resolvedConfig) {
130 | throw new Error(`No configuration found for file type ${extension}`);
131 | }
132 |
133 | const rawParser = resolvedConfig.config.parser;
134 | const rawStyle = resolvedConfig.config.style;
135 |
136 | throwIf(!rawParser, `No parser defined for file type ${extension}`);
137 | throwIf(!rawStyle, `No style defined for file type ${extension}`);
138 |
139 | const {parser, style} = resolvedConfig;
140 |
141 | throwIf(!parser, `Parser "${rawParser}" could not be resolved`);
142 | throwIf(!style, `Style "${rawStyle}" could not be resolved`);
143 |
144 | return resolvedConfig;
145 | }
146 |
147 | function handleFilePathError(filePath, e) {
148 | console.error(`${filePath}:`);
149 | console.error(e.toString());
150 | process.exitCode = 2;
151 | }
152 |
153 | function throwIf(condition: boolean, message: string) {
154 | if (condition) {
155 | throw new Error(message);
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/packages/import-sort-cli/test/index.ts:
--------------------------------------------------------------------------------
1 | // import "mocha";
2 | // import {assert} from "chai";
3 |
--------------------------------------------------------------------------------
/packages/import-sort-cli/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.settings.json",
3 |
4 | "compilerOptions": {
5 | "outDir": "lib",
6 | "rootDir": "src"
7 | },
8 |
9 | "include": ["src"],
10 |
11 | "references": [{"path": "../import-sort"}, {"path": "../import-sort-config"}]
12 | }
13 |
--------------------------------------------------------------------------------
/packages/import-sort-config/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "import-sort-config",
3 | "version": "6.0.0",
4 | "description": "Parser for the import-sort config key.",
5 | "main": "lib/index.js",
6 | "typings": "lib/index.d.ts",
7 | "scripts": {
8 | "prepublishOnly": "tsc -b .",
9 | "build": "tsc -b .",
10 | "build:watch": "tsc -b . -w",
11 | "test": "mocha --require ts-node/register --recursive \"test/**/*.ts\"",
12 | "test:watch": "mocha -w --require ts-node/register --recursive \"test/**/*.ts\"",
13 | "lint": "eslint --ext ts src"
14 | },
15 | "author": "Renke Grunwald (https://github.com/renke)",
16 | "repository": "renke/import-sort",
17 | "license": "ISC",
18 | "files": [
19 | "lib"
20 | ],
21 | "devDependencies": {
22 | "@types/chai": "^4.1.4",
23 | "@types/minimatch": "^3.0.3",
24 | "@types/mocha": "^5.2.3",
25 | "@types/node": "^10.3.5",
26 | "@types/resolve-from": "^4.0.0",
27 | "@typescript-eslint/eslint-plugin": "^1.2.0",
28 | "chai": "^4.0.2",
29 | "eslint": "^5.13.0",
30 | "eslint-config-airbnb": "^17.1.0",
31 | "eslint-config-prettier": "^4.0.0",
32 | "eslint-plugin-import": "^2.16.0",
33 | "eslint-plugin-jsx-a11y": "^6.2.0",
34 | "eslint-plugin-react": "^7.12.4",
35 | "import-sort-parser": "^6.0.0",
36 | "import-sort-parser-babylon": "^6.0.0",
37 | "import-sort-parser-typescript": "^6.0.0",
38 | "import-sort-style-eslint": "^6.0.0",
39 | "mocha": "^5.2.0",
40 | "ts-node": "^8.0.2",
41 | "typescript": "^3.2.4"
42 | },
43 | "dependencies": {
44 | "cosmiconfig": "^5.0.5",
45 | "find-root": "^1.0.0",
46 | "minimatch": "^3.0.4",
47 | "resolve-from": "^4.0.0"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/packages/import-sort-config/src/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module "find-root" {
2 | function findRoot(startingPath: string): string;
3 | export = findRoot;
4 | }
5 |
--------------------------------------------------------------------------------
/packages/import-sort-config/src/index.ts:
--------------------------------------------------------------------------------
1 | import * as cosmiconfig from "cosmiconfig";
2 | import * as minimatch from "minimatch";
3 | import {silent as resolve} from "resolve-from";
4 |
5 | export interface IConfigByGlobs {
6 | [globs: string]: IConfig;
7 | }
8 |
9 | export interface IConfig {
10 | parser?: string;
11 | style?: string;
12 | options?: object;
13 | }
14 |
15 | export interface IResolvedConfig {
16 | config: IConfig;
17 |
18 | parser?: string;
19 | style?: string;
20 | }
21 |
22 | export const DEFAULT_CONFIGS: IConfigByGlobs = {
23 | ".js, .jsx, .es6, .es, .mjs, .ts, .tsx": {
24 | parser: "babylon",
25 | style: "eslint",
26 | },
27 | };
28 |
29 | export function getConfig(
30 | extension: string,
31 | directory?: string,
32 | defaultConfigs = DEFAULT_CONFIGS,
33 | ): IResolvedConfig | undefined {
34 | const defaultConfig = getConfigForExtension(defaultConfigs, extension);
35 | let packageConfig: IConfig | undefined;
36 |
37 | if (directory) {
38 | packageConfig = getConfigFromDirectory(directory, extension);
39 | }
40 |
41 | const actualConfig = mergeConfigs([defaultConfig, packageConfig]);
42 |
43 | if (!actualConfig) {
44 | return undefined;
45 | }
46 |
47 | const resolvedConfig = resolveConfig(actualConfig, directory);
48 |
49 | return resolvedConfig;
50 | }
51 |
52 | function getConfigFromDirectory(
53 | directory: string,
54 | extension: string,
55 | ): IConfig | undefined {
56 | const packageConfigs = getAllConfigsFromDirectory(directory);
57 |
58 | if (!packageConfigs) {
59 | return undefined;
60 | }
61 |
62 | return getConfigForExtension(packageConfigs, extension);
63 | }
64 |
65 | function getConfigForExtension(
66 | configs: IConfigByGlobs,
67 | extension: string,
68 | ): IConfig | undefined {
69 | const foundConfigs: (IConfig | undefined)[] = Object.keys(configs).map(
70 | joinedGlobs => {
71 | const globs = joinedGlobs.split(",").map(rawGlob => rawGlob.trim());
72 | const config = configs[joinedGlobs];
73 |
74 | if (globs.some(glob => minimatch(extension, glob))) {
75 | return config;
76 | }
77 |
78 | return undefined;
79 | },
80 | );
81 |
82 | return mergeConfigs(foundConfigs);
83 | }
84 |
85 | function getAllConfigsFromDirectory(
86 | directory: string,
87 | ): IConfigByGlobs | undefined {
88 | const configsLoader = cosmiconfig("importsort", {
89 | sync: true,
90 | packageProp: "importSort",
91 | rcExtensions: true,
92 | });
93 |
94 | try {
95 | const configsResult = configsLoader.searchSync(directory);
96 |
97 | if (!configsResult) {
98 | return undefined;
99 | }
100 |
101 | return configsResult.config;
102 | } catch (e) {
103 | // Do nothing…
104 | }
105 |
106 | return undefined;
107 | }
108 |
109 | function mergeConfigs(
110 | rawConfigs: (IConfig | undefined)[],
111 | ): IConfig | undefined {
112 | const configs = rawConfigs.filter(rawConfig => !!rawConfig) as IConfig[];
113 |
114 | if (configs.length === 0) {
115 | return undefined;
116 | }
117 |
118 | return configs.reduce((previousConfig, currentConfig) => {
119 | if (!currentConfig) {
120 | return previousConfig;
121 | }
122 |
123 | const config = {...previousConfig};
124 |
125 | if (currentConfig.parser) {
126 | config.parser = currentConfig.parser;
127 | }
128 |
129 | if (currentConfig.style) {
130 | config.style = currentConfig.style;
131 | }
132 |
133 | if (currentConfig.options) {
134 | config.options = currentConfig.options;
135 | }
136 |
137 | return config;
138 | });
139 | }
140 |
141 | function resolveConfig(config: IConfig, directory?: string): IResolvedConfig {
142 | const resolvedConfig: IResolvedConfig = {
143 | config,
144 | };
145 |
146 | if (config.parser) {
147 | resolvedConfig.parser = resolveParser(config.parser, directory);
148 | }
149 |
150 | if (config.style) {
151 | resolvedConfig.style = resolveStyle(config.style, directory);
152 | }
153 |
154 | return resolvedConfig;
155 | }
156 |
157 | function resolveParser(module: string, directory?: string) {
158 | return (
159 | resolveModule(`import-sort-parser-${module}`, directory) ||
160 | resolveModule(module, directory)
161 | );
162 | }
163 |
164 | function resolveStyle(module: string, directory?: string) {
165 | return (
166 | resolveModule(`import-sort-style-${module}`, directory) ||
167 | resolveModule(module, directory)
168 | );
169 | }
170 |
171 | function resolveModule(module: string, directory?: string): string | undefined {
172 | if (directory) {
173 | const path = resolve(directory, module);
174 |
175 | if (path) {
176 | return path;
177 | }
178 | }
179 |
180 | const defaultPath = resolve(__dirname, module);
181 |
182 | if (defaultPath) {
183 | return defaultPath;
184 | }
185 |
186 | return undefined;
187 | }
188 |
--------------------------------------------------------------------------------
/packages/import-sort-config/test/fixtures/.importsortrc:
--------------------------------------------------------------------------------
1 | {
2 | "unprefixed": {
3 | "parser": "some-parser"
4 | },
5 | "shorthand": {
6 | "style": "test"
7 | },
8 | "relative": {
9 | "style": "./local-style"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/import-sort-config/test/fixtures/local-style.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renke/import-sort/302fe2d494307f4fedff7ad2b8a4b67d4eaad142/packages/import-sort-config/test/fixtures/local-style.js
--------------------------------------------------------------------------------
/packages/import-sort-config/test/fixtures/node_modules/import-sort-style-test/index.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renke/import-sort/302fe2d494307f4fedff7ad2b8a4b67d4eaad142/packages/import-sort-config/test/fixtures/node_modules/import-sort-style-test/index.js
--------------------------------------------------------------------------------
/packages/import-sort-config/test/fixtures/node_modules/import-sort-style-test/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "import-sort-style-test"
3 | }
4 |
--------------------------------------------------------------------------------
/packages/import-sort-config/test/fixtures/node_modules/some-parser/index.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renke/import-sort/302fe2d494307f4fedff7ad2b8a4b67d4eaad142/packages/import-sort-config/test/fixtures/node_modules/some-parser/index.js
--------------------------------------------------------------------------------
/packages/import-sort-config/test/fixtures/node_modules/some-parser/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "some-parser"
3 | }
4 |
--------------------------------------------------------------------------------
/packages/import-sort-config/test/index.ts:
--------------------------------------------------------------------------------
1 | import "mocha";
2 |
3 | import {assert} from "chai";
4 |
5 | import {getConfig} from "../src";
6 |
7 | import path = require("path");
8 |
9 | import resolve = require("resolve-from");
10 |
11 | describe("default config", () => {
12 | const fixtures = path.join(__dirname, "./fixtures");
13 |
14 | it("should resolve to default config", () => {
15 | const config = getConfig(".js");
16 |
17 | assert.equal(
18 | config!.parser,
19 | resolve(__dirname, "import-sort-parser-babylon"),
20 | );
21 | assert.equal(config!.style, resolve(__dirname, "import-sort-style-eslint"));
22 | });
23 |
24 | it("should resolve shorthand module names", () => {
25 | const config = getConfig("shorthand", fixtures);
26 |
27 | assert.equal(config!.style, resolve(fixtures, "import-sort-style-test"));
28 | });
29 |
30 | it("should resolve relative modules", () => {
31 | const config = getConfig("relative", fixtures);
32 |
33 | assert.equal(config!.style, resolve(fixtures, "./local-style.js"));
34 | });
35 |
36 | it("should resolve any module", () => {
37 | const config = getConfig("unprefixed", fixtures);
38 |
39 | assert.equal(config!.parser, resolve(fixtures, "some-parser"));
40 | });
41 | });
42 |
--------------------------------------------------------------------------------
/packages/import-sort-config/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.settings.json",
3 |
4 | "compilerOptions": {
5 | "outDir": "lib",
6 | "rootDir": "src"
7 | },
8 |
9 | "include": ["src"]
10 | }
11 |
--------------------------------------------------------------------------------
/packages/import-sort-parser-babylon/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "import-sort-parser-babylon",
3 | "version": "6.0.0",
4 | "description": "An import-sort parser based on the JavaScript parser Babylon.",
5 | "main": "lib/index.js",
6 | "typings": "lib/index.d.ts",
7 | "scripts": {
8 | "prepublishOnly": "tsc -b .",
9 | "build": "tsc -b .",
10 | "build:watch": "tsc -b . -w",
11 | "test": "mocha --require ts-node/register --recursive \"test/**/*.ts\"",
12 | "test:watch": "mocha -w --require ts-node/register --recursive \"test/**/*.ts\"",
13 | "lint": "eslint --ext ts src"
14 | },
15 | "author": "Renke Grunwald (https://github.com/renke)",
16 | "repository": "renke/import-sort",
17 | "license": "ISC",
18 | "files": [
19 | "lib"
20 | ],
21 | "devDependencies": {
22 | "@babel/preset-flow": "^7.0.0",
23 | "@babel/preset-typescript": "^7.1.0",
24 | "@types/babylon": "^6.16.3",
25 | "@types/chai": "^4.1.4",
26 | "@types/mocha": "^5.2.5",
27 | "@types/node": "^10.5.2",
28 | "@typescript-eslint/eslint-plugin": "^1.2.0",
29 | "chai": "^4.0.2",
30 | "eslint": "^5.13.0",
31 | "eslint-config-airbnb": "^17.1.0",
32 | "eslint-config-prettier": "^4.0.0",
33 | "eslint-plugin-import": "^2.16.0",
34 | "eslint-plugin-jsx-a11y": "^6.2.0",
35 | "eslint-plugin-react": "^7.12.4",
36 | "import-sort-parser": "^6.0.0",
37 | "mocha": "^5.2.0",
38 | "ts-node": "^8.0.2",
39 | "typescript": "^3.2.4"
40 | },
41 | "dependencies": {
42 | "@babel/core": "^7.2.2",
43 | "@babel/parser": "^7.0.0-beta.54",
44 | "@babel/traverse": "^7.0.0-beta.54",
45 | "@babel/types": "^7.0.0-beta.54",
46 | "find-line-column": "^0.5.2"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/packages/import-sort-parser-babylon/src/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module "find-line-column" {
2 | export default function findLineColumn(
3 | text: string,
4 | offset: number,
5 | ): {
6 | line: number;
7 | column: number;
8 | };
9 | }
10 |
--------------------------------------------------------------------------------
/packages/import-sort-parser-babylon/src/index.ts:
--------------------------------------------------------------------------------
1 | import {extname} from "path";
2 |
3 | import {
4 | loadOptions as babelLoadOptions,
5 | loadPartialConfig as babelLoadPartialOptions,
6 | parse as babelParse,
7 | } from "@babel/core";
8 | import {ParserOptions, parse as babelParserParse} from "@babel/parser";
9 | import traverse from "@babel/traverse";
10 | import {
11 | isImportDefaultSpecifier,
12 | isImportNamespaceSpecifier,
13 | isImportSpecifier,
14 | } from "@babel/types";
15 | // tslint:disable-next-line:no-implicit-dependencies
16 | import {IImport, IParserOptions, NamedMember} from "import-sort-parser";
17 |
18 | // TODO: Mocha currently doesn't pick up the declaration in index.d.ts
19 | // eslint-disable-next-line
20 | const findLineColumn = require("find-line-column");
21 |
22 | const TYPESCRIPT_EXTENSIONS = [".ts", ".tsx"];
23 |
24 | const COMMON_PARSER_PLUGINS = [
25 | "jsx",
26 | "doExpressions",
27 | "objectRestSpread",
28 | ["decorators", {decoratorsBeforeExport: true}],
29 | "classProperties",
30 | "classPrivateProperties",
31 | "classPrivateMethods",
32 | "exportDefaultFrom",
33 | "exportNamespaceFrom",
34 | "asyncGenerators",
35 | "functionBind",
36 | "functionSent",
37 | "dynamicImport",
38 | "numericSeparator",
39 | "optionalChaining",
40 | "importMeta",
41 | "bigInt",
42 | "optionalCatchBinding",
43 | "throwExpressions",
44 | ["pipelineOperator", {proposal: "minimal"}],
45 | "nullishCoalescingOperator",
46 | ];
47 |
48 | const FLOW_PARSER_PLUGINS = ["flow", "flowComments", ...COMMON_PARSER_PLUGINS];
49 |
50 | const FLOW_PARSER_OPTIONS = {
51 | allowImportExportEverywhere: true,
52 | allowAwaitOutsideFunction: true,
53 | allowReturnOutsideFunction: true,
54 | allowSuperOutsideMethod: true,
55 |
56 | sourceType: "module",
57 |
58 | plugins: FLOW_PARSER_PLUGINS,
59 | };
60 |
61 | const TYPESCRIPT_PARSER_PLUGINS = ["typescript", ...COMMON_PARSER_PLUGINS];
62 |
63 | const TYPESCRIPT_PARSER_OPTIONS = {
64 | allowImportExportEverywhere: true,
65 | allowAwaitOutsideFunction: true,
66 | allowReturnOutsideFunction: true,
67 | allowSuperOutsideMethod: true,
68 |
69 | sourceType: "module",
70 |
71 | plugins: TYPESCRIPT_PARSER_PLUGINS,
72 | };
73 |
74 | export function parseImports(
75 | code: string,
76 | options: IParserOptions = {},
77 | ): IImport[] {
78 | const babelPartialOptions = babelLoadPartialOptions({filename: options.file});
79 |
80 | let parsed;
81 |
82 | if (babelPartialOptions.hasFilesystemConfig()) {
83 | // We always prefer .babelrc (or similar) if one was found
84 | parsed = babelParse(code, babelLoadOptions({filename: options.file}));
85 | } else {
86 | const {file} = options;
87 |
88 | const isTypeScript = file && TYPESCRIPT_EXTENSIONS.includes(extname(file));
89 |
90 | const parserOptions = isTypeScript
91 | ? TYPESCRIPT_PARSER_OPTIONS
92 | : FLOW_PARSER_OPTIONS;
93 |
94 | parsed = babelParserParse(
95 | code,
96 | (parserOptions as unknown) as ParserOptions,
97 | );
98 | }
99 |
100 | const imports: IImport[] = [];
101 |
102 | const ignore = (parsed.comments || []).some(comment => {
103 | return comment.value.includes("import-sort-ignore");
104 | });
105 |
106 | if (ignore) {
107 | return imports;
108 | }
109 |
110 | traverse(parsed, {
111 | ImportDeclaration(path) {
112 | const {node} = path;
113 |
114 | const importStart = node.start;
115 | const importEnd = node.end;
116 |
117 | let start = importStart;
118 | let end = importEnd;
119 |
120 | if (node.leadingComments) {
121 | const comments = node.leadingComments;
122 |
123 | let current = node.leadingComments.length - 1;
124 | let previous: number | undefined;
125 |
126 | while (comments[current] && comments[current].end + 1 === start) {
127 | if (
128 | code
129 | .substring(comments[current].start, comments[current].end)
130 | .indexOf("#!") === 0
131 | ) {
132 | break;
133 | }
134 |
135 | // TODO: Improve this so that comments with leading whitespace are allowed
136 | if (findLineColumn(code, comments[current].start).col !== 0) {
137 | break;
138 | }
139 |
140 | previous = current;
141 | ({start} = comments[previous]);
142 | current -= 1;
143 | }
144 | }
145 |
146 | if (node.trailingComments) {
147 | const comments = node.trailingComments;
148 |
149 | let current = 0;
150 | let previous: number | undefined;
151 |
152 | while (comments[current] && comments[current].start - 1 === end) {
153 | if (comments[current].loc.start.line !== node.loc.start.line) {
154 | break;
155 | }
156 |
157 | previous = current;
158 | ({end} = comments[previous]);
159 | current += 1;
160 | }
161 | }
162 |
163 | const imported: IImport = {
164 | start,
165 | end,
166 |
167 | importStart,
168 | importEnd,
169 |
170 | moduleName: node.source.value,
171 |
172 | type: node.importKind === "type" ? "import-type" : "import",
173 | namedMembers: [],
174 | };
175 |
176 | if (node.specifiers) {
177 | node.specifiers.forEach(specifier => {
178 | if (isImportSpecifier(specifier)) {
179 | const type = specifier.importKind === "type" ? {type: true} : {};
180 |
181 | imported.namedMembers.push({
182 | name: specifier.imported.name,
183 | alias: specifier.local.name,
184 | ...type,
185 | });
186 | } else if (isImportDefaultSpecifier(specifier)) {
187 | imported.defaultMember = specifier.local.name;
188 | } else if (isImportNamespaceSpecifier) {
189 | imported.namespaceMember = specifier.local.name;
190 | }
191 | });
192 | }
193 |
194 | imports.push(imported);
195 | },
196 | });
197 |
198 | return imports;
199 | }
200 |
201 | export function formatImport(
202 | code: string,
203 | imported: IImport,
204 | eol = "\n",
205 | ): string {
206 | const importStart = imported.importStart || imported.start;
207 | const importEnd = imported.importEnd || imported.end;
208 |
209 | const importCode = code.substring(importStart, importEnd);
210 |
211 | const {namedMembers} = imported;
212 |
213 | if (namedMembers.length === 0) {
214 | return code.substring(imported.start, imported.end);
215 | }
216 |
217 | const newImportCode = importCode.replace(
218 | /\{[\s\S]*\}/g,
219 | namedMembersString => {
220 | const useMultipleLines = namedMembersString.indexOf(eol) !== -1;
221 |
222 | let prefix: string | undefined;
223 |
224 | if (useMultipleLines) {
225 | [prefix] = namedMembersString
226 | .split(eol)[1]
227 | .match(/^\s*/) as RegExpMatchArray;
228 | }
229 |
230 | const useSpaces = namedMembersString.charAt(1) === " ";
231 |
232 | const userTrailingComma = namedMembersString
233 | .replace("}", "")
234 | .trim()
235 | .endsWith(",");
236 |
237 | return formatNamedMembers(
238 | namedMembers,
239 | useMultipleLines,
240 | useSpaces,
241 | userTrailingComma,
242 | prefix,
243 | eol,
244 | );
245 | },
246 | );
247 |
248 | return (
249 | code.substring(imported.start, importStart) +
250 | newImportCode +
251 | code.substring(importEnd, importEnd + (imported.end - importEnd))
252 | );
253 | }
254 |
255 | function formatNamedMembers(
256 | namedMembers: NamedMember[],
257 | useMultipleLines: boolean,
258 | useSpaces: boolean,
259 | useTrailingComma: boolean,
260 | prefix: string | undefined,
261 | eol = "\n",
262 | ): string {
263 | if (useMultipleLines) {
264 | return (
265 | "{" +
266 | eol +
267 | namedMembers
268 | .map(({name, alias, type}, index) => {
269 | const lastImport = index === namedMembers.length - 1;
270 | const comma = !useTrailingComma && lastImport ? "" : ",";
271 | const typeModifier = type ? "type " : "";
272 |
273 | if (name === alias) {
274 | return `${prefix}${typeModifier}${name}${comma}` + eol;
275 | }
276 |
277 | return `${prefix}${typeModifier}${name} as ${alias}${comma}` + eol;
278 | })
279 | .join("") +
280 | "}"
281 | );
282 | }
283 |
284 | const space = useSpaces ? " " : "";
285 | const comma = useTrailingComma ? "," : "";
286 |
287 | return (
288 | "{" +
289 | space +
290 | namedMembers
291 | .map(({name, alias, type}) => {
292 | const typeModifier = type ? "type " : "";
293 |
294 | if (name === alias) {
295 | return `${typeModifier}${name}`;
296 | }
297 |
298 | return `${typeModifier}${name} as ${alias}`;
299 | })
300 | .join(", ") +
301 | comma +
302 | space +
303 | "}"
304 | );
305 | }
306 |
--------------------------------------------------------------------------------
/packages/import-sort-parser-babylon/test/flow-babelrc/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-flow"]
3 | }
4 |
--------------------------------------------------------------------------------
/packages/import-sort-parser-babylon/test/flow-babelrc/flow.ts:
--------------------------------------------------------------------------------
1 | import "mocha";
2 |
3 | import {assert} from "chai";
4 | import {IImport} from "import-sort-parser";
5 |
6 | import {formatImport, parseImports} from "../../lib";
7 |
8 | const parseFlowImports = code => {
9 | // Pass (fake) file name to the parser so it can read .babelrc
10 | return parseImports(code, {file: __dirname + "/flow.js"});
11 | };
12 |
13 | describe("parseImports (Flow, with @babel/preset-flow)", () => {
14 | it("should return default type import", () => {
15 | const imports = parseFlowImports(
16 | `
17 | import type p from 'q';
18 | `.trim(),
19 | );
20 |
21 | assert.equal(imports[0].type, "import-type");
22 | assert.equal(imports[0].start, 0);
23 | assert.equal(imports[0].end, imports[0].end);
24 | assert.equal(imports[0].moduleName, "q");
25 | assert.equal(imports[0].defaultMember, "p");
26 | });
27 |
28 | it("should include type information for named type imports", () => {
29 | const imports = parseFlowImports(
30 | `
31 | import {type a} from "x";
32 | `.trim(),
33 | );
34 |
35 | assert.equal(imports[0].namedMembers[0].type, true);
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/packages/import-sort-parser-babylon/test/flow/flow.ts:
--------------------------------------------------------------------------------
1 | import "mocha";
2 |
3 | import {assert} from "chai";
4 | import {IImport} from "import-sort-parser";
5 |
6 | import {formatImport, parseImports} from "../../lib";
7 |
8 | const parseFlowImports = code => {
9 | // No file name is passed to the parser here
10 | return parseImports(code);
11 | };
12 |
13 | describe("parseImports (Flow, without @babel/preset-flow)", () => {
14 | it("should return default type import", () => {
15 | const imports = parseFlowImports(
16 | `
17 | import type p from 'q';
18 | `.trim(),
19 | );
20 |
21 | assert.equal(imports[0].type, "import-type");
22 | assert.equal(imports[0].start, 0);
23 | assert.equal(imports[0].end, imports[0].end);
24 | assert.equal(imports[0].moduleName, "q");
25 | assert.equal(imports[0].defaultMember, "p");
26 | });
27 |
28 | it("should include type information for named type imports", () => {
29 | const imports = parseFlowImports(
30 | `
31 | import {type a} from "x";
32 | `.trim(),
33 | );
34 |
35 | assert.equal(imports[0].namedMembers[0].type, true);
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/packages/import-sort-parser-babylon/test/index.ts:
--------------------------------------------------------------------------------
1 | import "mocha";
2 |
3 | import {assert} from "chai";
4 | import {IImport} from "import-sort-parser";
5 |
6 | import {formatImport, parseImports} from "../src";
7 |
8 | describe("parseImports", () => {
9 | it(`should return no imports when using "import-sort-ignore" multi line comment`, () => {
10 | let imports: IImport[];
11 |
12 | imports = parseImports(
13 | `
14 | /* import-sort-ignore */
15 | import "a";
16 | import b from "b";
17 | import {c} from "c";
18 | import d, {e} from "f";
19 | import g, {h as hh} from "i";
20 | import * as j from "k";
21 | import l, * as m from "o";
22 | `.trim(),
23 | );
24 |
25 | assert.isEmpty(imports);
26 |
27 | imports = parseImports(
28 | `
29 | import "a";
30 | import b from "b";
31 | import {c} from "c";
32 | import d, {e} from "f";
33 | import g, {h as hh} from "i";
34 | import * as j from "k";
35 | import l, * as m from "o";
36 | /* import-sort-ignore */
37 | `.trim(),
38 | );
39 |
40 | assert.isEmpty(imports);
41 | });
42 |
43 | it(`should return no imports when using "import-sort-ignore" single line comment`, () => {
44 | let imports: IImport[];
45 |
46 | imports = parseImports(
47 | `
48 | // import-sort-ignore
49 | import "a";
50 | import b from "b";
51 | import {c} from "c";
52 | import d, {e} from "f";
53 | import g, {h as hh} from "i";
54 | import * as j from "k";
55 | import l, * as m from "o";
56 | `.trim(),
57 | );
58 |
59 | assert.isEmpty(imports);
60 |
61 | imports = parseImports(
62 | `
63 | import "a";
64 | import b from "b";
65 | import {c} from "c";
66 | import d, {e} from "f";
67 | import g, {h as hh} from "i";
68 | import * as j from "k";
69 | import l, * as m from "o";
70 | // import-sort-ignore
71 | `.trim(),
72 | );
73 |
74 | assert.isEmpty(imports);
75 | });
76 |
77 | it("should return imports", () => {
78 | const imports = parseImports(
79 | `
80 | import "a";
81 | import b from "b";
82 | import {c} from "c";
83 | import d, {e} from "f";
84 | import g, {h as hh} from "i";
85 | import * as j from "k";
86 | import l, * as m from "o";
87 | `.trim(),
88 | );
89 |
90 | assert.equal(imports.length, 7);
91 |
92 | imports.forEach(imported => {
93 | assert.equal(imported.type, "import");
94 | });
95 |
96 | // import "a";
97 | assert.equal(imports[0].start, 0);
98 | assert.equal(imports[0].end, 11);
99 | assert.equal(imports[0].moduleName, "a");
100 |
101 | // import b from "b";
102 | assert.equal(imports[1].start, imports[0].end + 1);
103 | assert.equal(imports[1].end, imports[0].end + 1 + 18);
104 | assert.equal(imports[1].moduleName, "b");
105 | assert.equal(imports[1].defaultMember, "b");
106 |
107 | // import {c} from "c";
108 | assert.equal(imports[2].start, imports[1].end + 1);
109 | assert.equal(imports[2].end, imports[1].end + 1 + 20);
110 | assert.equal(imports[2].moduleName, "c");
111 | assert.deepEqual(imports[2].namedMembers![0], {name: "c", alias: "c"});
112 |
113 | // import d, {e} from "f";
114 | assert.equal(imports[3].start, imports[2].end + 1);
115 | assert.equal(imports[3].end, imports[2].end + 1 + 23);
116 | assert.equal(imports[3].moduleName, "f");
117 | assert.equal(imports[3].defaultMember, "d");
118 | assert.deepEqual(imports[3].namedMembers![0], {name: "e", alias: "e"});
119 |
120 | // import g, {h as hh} from "i";
121 | assert.equal(imports[4].start, imports[3].end + 1);
122 | assert.equal(imports[4].end, imports[3].end + 1 + 29);
123 | assert.equal(imports[4].moduleName, "i");
124 | assert.equal(imports[4].defaultMember, "g");
125 | assert.deepEqual(imports[4].namedMembers![0], {name: "h", alias: "hh"});
126 |
127 | // import * as j from "k";
128 | assert.equal(imports[5].start, imports[4].end + 1);
129 | assert.equal(imports[5].end, imports[4].end + 1 + 23);
130 | assert.equal(imports[5].moduleName, "k");
131 | assert.equal(imports[5].namespaceMember, "j");
132 |
133 | // import l, * as m from "o";
134 | assert.equal(imports[6].start, imports[5].end + 1);
135 | assert.equal(imports[6].end, imports[5].end + 1 + 26);
136 | assert.equal(imports[6].moduleName, "o");
137 | assert.equal(imports[6].defaultMember, "l");
138 | assert.equal(imports[6].namespaceMember, "m");
139 | });
140 |
141 | it("should include nearby comments", () => {
142 | const imports = parseImports(
143 | `
144 | // Above
145 | import "a"; // Besides
146 | // Below
147 | `.trim(),
148 | );
149 |
150 | assert.equal(imports[0].start, 0);
151 | assert.equal(imports[0].end, 31);
152 | });
153 |
154 | it("should include all comments", () => {
155 | const imports = parseImports(
156 | `
157 | // Above
158 | // Above
159 | import "a"; // Besides
160 | // Below
161 | // Below
162 | `.trim(),
163 | );
164 |
165 | assert.equal(imports[0].start, 0);
166 | assert.equal(imports[0].end, 40);
167 | });
168 |
169 | it("should only include nearby comments", () => {
170 | const imports = parseImports(
171 | `
172 | // Above
173 |
174 | import "a"; // Besides
175 |
176 | // Below
177 | `.trim(),
178 | );
179 |
180 | assert.equal(imports[0].start, 10);
181 | assert.equal(imports[0].end, 10 + 22);
182 | });
183 |
184 | it("should not include shebang", () => {
185 | const imports = parseImports(
186 | `
187 | #!/bin/sh
188 | import "a";
189 | `.trim(),
190 | );
191 |
192 | assert.equal(imports[0].start, 10);
193 | assert.equal(imports[0].end, 10 + 11);
194 | });
195 |
196 | it("should include all nearby but exclude far away comments", () => {
197 | const imports = parseImports(
198 | `
199 | // Above
200 |
201 | // Above
202 | import "a"; // Besides
203 | // Below
204 |
205 | // Below
206 | `.trim(),
207 | );
208 |
209 | assert.equal(imports[0].start, 10);
210 | assert.equal(imports[0].end, 10 + 31);
211 | });
212 |
213 | it("should not treat trailing comment on previous import as leading comment", () => {
214 | const imports = parseImports(
215 | `
216 | import "a"; // Besides
217 | import "b";
218 | `.trim(),
219 | );
220 |
221 | assert.equal(imports[0].start, 0);
222 | assert.equal(imports[0].end, 22);
223 |
224 | assert.equal(imports[1].start, 23);
225 | assert.equal(imports[1].end, 1 + 22 + 11);
226 | });
227 |
228 | it("should include type information for named type imports", () => {
229 | const imports = parseImports(
230 | `
231 | import {type a} from "x";
232 | `.trim(),
233 | );
234 |
235 | assert.equal(imports[0].namedMembers[0].type, true);
236 | });
237 | });
238 |
239 | describe("formatImport", () => {
240 | it("should not change one-line imports", () => {
241 | const actual = `
242 | import {a, b, c} from "xyz"
243 | `.trim();
244 |
245 | const imported: IImport = {
246 | start: 0,
247 | end: 27,
248 | type: "import",
249 | moduleName: "xyz",
250 | namedMembers: [
251 | {name: "a", alias: "a"},
252 | {name: "b", alias: "b"},
253 | {name: "c", alias: "c"},
254 | ],
255 | };
256 |
257 | const expected = `
258 | import {a, b, c} from "xyz"
259 | `.trim();
260 |
261 | assert.equal(formatImport(actual, imported), expected);
262 | });
263 |
264 | it("should not change full multi-line imports with same indendation", () => {
265 | const actual = `
266 | import {
267 | a,
268 | b,
269 | c
270 | } from "xyz"
271 | `.trim();
272 |
273 | const imported: IImport = {
274 | start: 0,
275 | end: 36,
276 | type: "import",
277 | moduleName: "xyz",
278 | namedMembers: [
279 | {name: "a", alias: "a"},
280 | {name: "b", alias: "b"},
281 | {name: "c", alias: "c"},
282 | ],
283 | };
284 |
285 | const expected = `
286 | import {
287 | a,
288 | b,
289 | c
290 | } from "xyz"
291 | `.trim();
292 |
293 | assert.equal(formatImport(actual, imported), expected);
294 | });
295 |
296 | it("should change partial multi-line imports indented by 2 spaces", () => {
297 | const actual = `
298 | import {a,
299 | b,
300 | c
301 | } from "xyz"
302 | `.trim();
303 |
304 | const imported: IImport = {
305 | start: 0,
306 | end: 30,
307 | type: "import",
308 | moduleName: "xyz",
309 | namedMembers: [
310 | {name: "a", alias: "a"},
311 | {name: "b", alias: "b"},
312 | {name: "c", alias: "c"},
313 | ],
314 | };
315 |
316 | const expected = `
317 | import {
318 | a,
319 | b,
320 | c
321 | } from "xyz"
322 | `.trim();
323 |
324 | assert.equal(formatImport(actual, imported), expected);
325 | });
326 |
327 | it("should change partial multi-line imports indented by 4 spaces", () => {
328 | const actual = `
329 | import {a,
330 | b,
331 | c
332 | } from "xyz"
333 | `.trim();
334 |
335 | const imported: IImport = {
336 | start: 0,
337 | end: 32,
338 | type: "import",
339 | moduleName: "xyz",
340 | namedMembers: [
341 | {name: "a", alias: "a"},
342 | {name: "b", alias: "b"},
343 | {name: "c", alias: "c"},
344 | ],
345 | };
346 |
347 | const expected = `
348 | import {
349 | a,
350 | b,
351 | c
352 | } from "xyz"
353 | `.trim();
354 |
355 | assert.equal(formatImport(actual, imported), expected);
356 | });
357 |
358 | it("should preserve whitespace around braces in one-line imports", () => {
359 | const actual = `
360 | import { a, b, c } from "xyz"
361 | `.trim();
362 |
363 | const imported: IImport = {
364 | start: 0,
365 | end: 29,
366 | type: "import",
367 | moduleName: "xyz",
368 | namedMembers: [
369 | {name: "a", alias: "a"},
370 | {name: "b", alias: "b"},
371 | {name: "c", alias: "c"},
372 | ],
373 | };
374 |
375 | const expected = `
376 | import { a, b, c } from "xyz"
377 | `.trim();
378 |
379 | assert.equal(formatImport(actual, imported), expected);
380 | });
381 |
382 | it("should format named same-line type imports", () => {
383 | const actual = `
384 | import { type a, type b, c } from "xyz"
385 | `.trim();
386 |
387 | const imported: IImport = {
388 | start: 0,
389 | end: 40,
390 | type: "import",
391 | moduleName: "xyz",
392 | namedMembers: [
393 | {name: "a", alias: "a", type: true},
394 | {name: "b", alias: "b", type: true},
395 | {name: "c", alias: "c"},
396 | ],
397 | };
398 |
399 | const expected = `
400 | import { type a, type b, c } from "xyz"
401 | `.trim();
402 |
403 | assert.equal(formatImport(actual, imported), expected);
404 | });
405 |
406 | it("should format named multi-line type imports", () => {
407 | const actual = `
408 | import {
409 | type a,
410 | type b,
411 | c
412 | } from "xyz"
413 | `.trim();
414 |
415 | const imported: IImport = {
416 | start: 0,
417 | end: 46,
418 | type: "import",
419 | moduleName: "xyz",
420 | namedMembers: [
421 | {name: "a", alias: "a", type: true},
422 | {name: "b", alias: "b", type: true},
423 | {name: "c", alias: "c"},
424 | ],
425 | };
426 |
427 | const expected = `
428 | import {
429 | type a,
430 | type b,
431 | c
432 | } from "xyz"
433 | `.trim();
434 |
435 | assert.equal(formatImport(actual, imported), expected);
436 | });
437 |
438 | it("should deal with export between decorator and class (issue #68)", () => {
439 | const code = `
440 | import styles from './index.scss';
441 |
442 | @cssModule(styles)
443 | export class Sidebar extends React.Component {
444 | }
445 | `.trim();
446 |
447 | assert.doesNotThrow(() => parseImports(code));
448 | });
449 | });
450 |
--------------------------------------------------------------------------------
/packages/import-sort-parser-babylon/test/typescript-babelrc/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-typescript"]
3 | }
4 |
--------------------------------------------------------------------------------
/packages/import-sort-parser-babylon/test/typescript-babelrc/typescript.ts:
--------------------------------------------------------------------------------
1 | import "mocha";
2 |
3 | import {assert} from "chai";
4 |
5 | import {parseImports} from "../../lib";
6 |
7 | const parseTypeScriptImports = code => {
8 | // Pass (fake) file name to the parser so it can read .babelrc
9 | return parseImports(code, {file: __dirname + "/typescript.ts"});
10 | };
11 |
12 | describe("parseImports (TypeScript, with @babel/preset-typescript)", () => {
13 | it("should return imports", () => {
14 | const imports = parseTypeScriptImports(
15 | `
16 | import "a";
17 | import b from "b";
18 | import {c} from "c";
19 | import d, {e} from "f";
20 | import g, {h as hh} from "i";
21 | import * as j from "k";
22 | import l, * as m from "o";
23 |
24 | // Random TypeScript syntax (that is not Flow syntax)
25 | const a: number = "123" as any;
26 | `.trim(),
27 | );
28 |
29 | assert.equal(imports.length, 7);
30 |
31 | imports.forEach(imported => {
32 | assert.equal(imported.type, "import");
33 | });
34 |
35 | // import "a";
36 | assert.equal(imports[0].start, 0);
37 | assert.equal(imports[0].end, 11);
38 | assert.equal(imports[0].moduleName, "a");
39 |
40 | // import b from "b";
41 | assert.equal(imports[1].start, imports[0].end + 1);
42 | assert.equal(imports[1].end, imports[0].end + 1 + 18);
43 | assert.equal(imports[1].moduleName, "b");
44 | assert.equal(imports[1].defaultMember, "b");
45 |
46 | // import {c} from "c";
47 | assert.equal(imports[2].start, imports[1].end + 1);
48 | assert.equal(imports[2].end, imports[1].end + 1 + 20);
49 | assert.equal(imports[2].moduleName, "c");
50 | assert.deepEqual(imports[2].namedMembers![0], {name: "c", alias: "c"});
51 |
52 | // import d, {e} from "f";
53 | assert.equal(imports[3].start, imports[2].end + 1);
54 | assert.equal(imports[3].end, imports[2].end + 1 + 23);
55 | assert.equal(imports[3].moduleName, "f");
56 | assert.equal(imports[3].defaultMember, "d");
57 | assert.deepEqual(imports[3].namedMembers![0], {name: "e", alias: "e"});
58 |
59 | // import g, {h as hh} from "i";
60 | assert.equal(imports[4].start, imports[3].end + 1);
61 | assert.equal(imports[4].end, imports[3].end + 1 + 29);
62 | assert.equal(imports[4].moduleName, "i");
63 | assert.equal(imports[4].defaultMember, "g");
64 | assert.deepEqual(imports[4].namedMembers![0], {name: "h", alias: "hh"});
65 |
66 | // import * as j from "k";
67 | assert.equal(imports[5].start, imports[4].end + 1);
68 | assert.equal(imports[5].end, imports[4].end + 1 + 23);
69 | assert.equal(imports[5].moduleName, "k");
70 | assert.equal(imports[5].namespaceMember, "j");
71 |
72 | // import l, * as m from "o";
73 | assert.equal(imports[6].start, imports[5].end + 1);
74 | assert.equal(imports[6].end, imports[5].end + 1 + 26);
75 | assert.equal(imports[6].moduleName, "o");
76 | assert.equal(imports[6].defaultMember, "l");
77 | assert.equal(imports[6].namespaceMember, "m");
78 | });
79 | });
80 |
--------------------------------------------------------------------------------
/packages/import-sort-parser-babylon/test/typescript/typescript.ts:
--------------------------------------------------------------------------------
1 | import "mocha";
2 |
3 | import {assert} from "chai";
4 |
5 | import {parseImports} from "../../lib";
6 |
7 | const parseTypeScriptImports = code => {
8 | // No file name is passed to the parser here
9 | return parseImports(code, {file: __dirname + "/typescript.ts"});
10 | };
11 |
12 | describe("parseImports (TypeScript, without @babel/preset-typescript)", () => {
13 | it("should return imports", () => {
14 | const imports = parseTypeScriptImports(
15 | `
16 | import "a";
17 | import b from "b";
18 | import {c} from "c";
19 | import d, {e} from "f";
20 | import g, {h as hh} from "i";
21 | import * as j from "k";
22 | import l, * as m from "o";
23 |
24 | // Random TypeScript syntax (that is not Flow syntax)
25 | const a: number = "123" as any;
26 | `.trim(),
27 | );
28 |
29 | assert.equal(imports.length, 7);
30 |
31 | imports.forEach(imported => {
32 | assert.equal(imported.type, "import");
33 | });
34 |
35 | // import "a";
36 | assert.equal(imports[0].start, 0);
37 | assert.equal(imports[0].end, 11);
38 | assert.equal(imports[0].moduleName, "a");
39 |
40 | // import b from "b";
41 | assert.equal(imports[1].start, imports[0].end + 1);
42 | assert.equal(imports[1].end, imports[0].end + 1 + 18);
43 | assert.equal(imports[1].moduleName, "b");
44 | assert.equal(imports[1].defaultMember, "b");
45 |
46 | // import {c} from "c";
47 | assert.equal(imports[2].start, imports[1].end + 1);
48 | assert.equal(imports[2].end, imports[1].end + 1 + 20);
49 | assert.equal(imports[2].moduleName, "c");
50 | assert.deepEqual(imports[2].namedMembers![0], {name: "c", alias: "c"});
51 |
52 | // import d, {e} from "f";
53 | assert.equal(imports[3].start, imports[2].end + 1);
54 | assert.equal(imports[3].end, imports[2].end + 1 + 23);
55 | assert.equal(imports[3].moduleName, "f");
56 | assert.equal(imports[3].defaultMember, "d");
57 | assert.deepEqual(imports[3].namedMembers![0], {name: "e", alias: "e"});
58 |
59 | // import g, {h as hh} from "i";
60 | assert.equal(imports[4].start, imports[3].end + 1);
61 | assert.equal(imports[4].end, imports[3].end + 1 + 29);
62 | assert.equal(imports[4].moduleName, "i");
63 | assert.equal(imports[4].defaultMember, "g");
64 | assert.deepEqual(imports[4].namedMembers![0], {name: "h", alias: "hh"});
65 |
66 | // import * as j from "k";
67 | assert.equal(imports[5].start, imports[4].end + 1);
68 | assert.equal(imports[5].end, imports[4].end + 1 + 23);
69 | assert.equal(imports[5].moduleName, "k");
70 | assert.equal(imports[5].namespaceMember, "j");
71 |
72 | // import l, * as m from "o";
73 | assert.equal(imports[6].start, imports[5].end + 1);
74 | assert.equal(imports[6].end, imports[5].end + 1 + 26);
75 | assert.equal(imports[6].moduleName, "o");
76 | assert.equal(imports[6].defaultMember, "l");
77 | assert.equal(imports[6].namespaceMember, "m");
78 | });
79 | });
80 |
--------------------------------------------------------------------------------
/packages/import-sort-parser-babylon/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.settings.json",
3 |
4 | "compilerOptions": {
5 | "outDir": "lib",
6 | "rootDir": "src"
7 | },
8 |
9 | "include": ["src"],
10 |
11 | "references": [{"path": "../import-sort-parser"}]
12 | }
13 |
--------------------------------------------------------------------------------
/packages/import-sort-parser-typescript/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "import-sort-parser-typescript",
3 | "version": "6.0.0",
4 | "description": "An import-sort parser based on the TypeScript parser.",
5 | "main": "lib/index.js",
6 | "typings": "lib/index.d.ts",
7 | "scripts": {
8 | "prepublishOnly": "tsc -b .",
9 | "build": "tsc -b .",
10 | "build:watch": "tsc -b . -w",
11 | "test": "mocha --require ts-node/register --recursive \"test/**/*.ts\"",
12 | "test:watch": "mocha -w --require ts-node/register --recursive \"test/**/*.ts\"",
13 | "lint": "eslint --ext ts src"
14 | },
15 | "author": "Renke Grunwald (https://github.com/renke)",
16 | "repository": "renke/import-sort",
17 | "license": "ISC",
18 | "files": [
19 | "lib"
20 | ],
21 | "devDependencies": {
22 | "@types/chai": "^4.1.4",
23 | "@types/mocha": "^5.2.3",
24 | "@types/node": "^10.3.5",
25 | "@typescript-eslint/eslint-plugin": "^1.2.0",
26 | "chai": "^4.0.2",
27 | "eslint": "^5.13.0",
28 | "eslint-config-airbnb": "^17.1.0",
29 | "eslint-config-prettier": "^4.0.0",
30 | "eslint-plugin-import": "^2.16.0",
31 | "eslint-plugin-jsx-a11y": "^6.2.0",
32 | "eslint-plugin-react": "^7.12.4",
33 | "import-sort-parser": "^6.0.0",
34 | "mocha": "^5.2.0",
35 | "ts-node": "^8.0.2"
36 | },
37 | "dependencies": {
38 | "typescript": "^3.2.4"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/packages/import-sort-parser-typescript/src/index.ts:
--------------------------------------------------------------------------------
1 | import {IImport, ImportType, NamedMember} from "import-sort-parser"; // tslint:disable-line
2 | import * as typescript from "typescript";
3 |
4 | export function parseImports(code: string): IImport[] {
5 | const host: typescript.CompilerHost = {
6 | fileExists: () => true,
7 | readFile: () => "",
8 |
9 | getSourceFile: () => {
10 | return typescript.createSourceFile(
11 | "",
12 | code,
13 | typescript.ScriptTarget.Latest,
14 | true,
15 | );
16 | },
17 |
18 | getDefaultLibFileName: () => "lib.d.ts",
19 | writeFile: () => null,
20 | getCurrentDirectory: () => "",
21 | getDirectories: () => [],
22 | getCanonicalFileName: fileName => fileName,
23 | useCaseSensitiveFileNames: () => true,
24 | getNewLine: () => typescript.sys.newLine,
25 | };
26 |
27 | const program = typescript.createProgram(
28 | ["foo.ts"],
29 | {
30 | noResolve: true,
31 | target: typescript.ScriptTarget.Latest,
32 | experimentalDecorators: true,
33 | experimentalAsyncFunctions: true,
34 | },
35 | host,
36 | );
37 |
38 | const sourceFile = program.getSourceFile("foo.ts");
39 |
40 | if (!sourceFile) {
41 | throw new Error("Source file not found. This should not happen.");
42 | }
43 |
44 | const imports: IImport[] = [];
45 |
46 | typescript.forEachChild(sourceFile, node => {
47 | switch (node.kind) {
48 | case typescript.SyntaxKind.ImportDeclaration: {
49 | imports.push(
50 | parseImportDeclaration(
51 | code,
52 | sourceFile,
53 | node as typescript.ImportDeclaration,
54 | ),
55 | );
56 | break;
57 | }
58 | case typescript.SyntaxKind.ImportEqualsDeclaration: {
59 | break;
60 | }
61 | default: {
62 | break;
63 | }
64 | }
65 | });
66 |
67 | return imports;
68 | }
69 |
70 | function parseImportDeclaration(
71 | code: string,
72 | sourceFile: typescript.SourceFile,
73 | importDeclaration: typescript.ImportDeclaration,
74 | ): IImport {
75 | const importStart =
76 | importDeclaration.pos + importDeclaration.getLeadingTriviaWidth();
77 | const importEnd = importDeclaration.end;
78 |
79 | let start = importStart;
80 | let end = importEnd;
81 |
82 | const leadingComments = getComments(sourceFile, importDeclaration, false);
83 | const trailingComments = getComments(sourceFile, importDeclaration, true);
84 |
85 | if (leadingComments) {
86 | const comments = leadingComments;
87 |
88 | let current = leadingComments.length - 1;
89 | let previous: number | undefined;
90 |
91 | while (comments[current] && comments[current].end + 1 === start) {
92 | if (
93 | code
94 | .substring(comments[current].pos, comments[current].end)
95 | .startsWith("#!")
96 | ) {
97 | break;
98 | }
99 |
100 | previous = current;
101 | start = comments[previous].pos;
102 | current -= 1;
103 | }
104 | }
105 |
106 | if (trailingComments) {
107 | const comments = trailingComments;
108 |
109 | let current = 0;
110 | let previous: number | undefined;
111 |
112 | while (comments[current] && comments[current].pos - 1 === end) {
113 | // TODO: Why is this not needed?
114 | // if (comments[current].loc.start.line !== node.loc.start.line) {
115 | // break;
116 | // }
117 |
118 | previous = current;
119 | ({end} = comments[previous]);
120 | current += 1;
121 | }
122 | }
123 |
124 | const type: ImportType = "import";
125 |
126 | const moduleName = importDeclaration.moduleSpecifier
127 | .getText()
128 | .replace(/["']/g, "");
129 |
130 | const imported: IImport = {
131 | start,
132 | end,
133 | importStart,
134 | importEnd,
135 | type,
136 | moduleName,
137 | namedMembers: [],
138 | };
139 |
140 | const {importClause} = importDeclaration;
141 |
142 | if (importClause) {
143 | if (importClause.name) {
144 | imported.defaultMember = importClause.name.text;
145 | }
146 |
147 | const {namedBindings} = importClause;
148 |
149 | if (namedBindings) {
150 | if (namedBindings.kind === typescript.SyntaxKind.NamespaceImport) {
151 | const namespaceImport = namedBindings as typescript.NamespaceImport;
152 | imported.namespaceMember = namespaceImport.name.text;
153 | }
154 |
155 | if (namedBindings.kind === typescript.SyntaxKind.NamedImports) {
156 | const namedImports = namedBindings as typescript.NamedImports;
157 |
158 | for (const element of namedImports.elements) {
159 | const alias = element.name.text;
160 | let name = alias;
161 |
162 | if (element.propertyName) {
163 | name = element.propertyName.text;
164 | }
165 |
166 | imported.namedMembers.push({
167 | name: fixMultipleUnderscore(name),
168 | alias: fixMultipleUnderscore(alias),
169 | });
170 | }
171 | }
172 | }
173 | }
174 |
175 | return imported;
176 | }
177 |
178 | // This hack circumvents a bug (?) in the TypeScript parser where a named
179 | // binding's name or alias that consists only of underscores contains an
180 | // additional underscore. We just remove the superfluous underscore here.
181 | //
182 | // See https://github.com/renke/import-sort/issues/18 for more details.
183 | function fixMultipleUnderscore(name) {
184 | if (name.match(/^_{2,}$/)) {
185 | return name.substring(1);
186 | }
187 |
188 | return name;
189 | }
190 |
191 | // Taken from https://github.com/fkling/astexplorer/blob/master/src/parsers/js/typescript.js#L68
192 | function getComments(
193 | sourceFile: typescript.SourceFile,
194 | node: typescript.Node,
195 | isTrailing: boolean,
196 | ): typescript.CommentRange[] | undefined {
197 | if (node.parent) {
198 | const nodePos = isTrailing ? node.end : node.pos;
199 | const parentPos = isTrailing ? node.parent.end : node.parent.pos;
200 |
201 | if (
202 | node.parent.kind === typescript.SyntaxKind.SourceFile ||
203 | nodePos !== parentPos
204 | ) {
205 | let comments: typescript.CommentRange[] | undefined;
206 |
207 | if (isTrailing) {
208 | comments = typescript.getTrailingCommentRanges(
209 | sourceFile.text,
210 | nodePos,
211 | );
212 | } else {
213 | comments = typescript.getLeadingCommentRanges(sourceFile.text, nodePos);
214 | }
215 |
216 | if (Array.isArray(comments)) {
217 | return comments;
218 | }
219 | }
220 | }
221 |
222 | return undefined;
223 | }
224 |
225 | export function formatImport(
226 | code: string,
227 | imported: IImport,
228 | eol = "\n",
229 | ): string {
230 | const importStart = imported.importStart || imported.start;
231 | const importEnd = imported.importEnd || imported.end;
232 |
233 | const importCode = code.substring(importStart, importEnd);
234 |
235 | const {namedMembers} = imported;
236 |
237 | if (namedMembers.length === 0) {
238 | return code.substring(imported.start, imported.end);
239 | }
240 |
241 | const newImportCode = importCode.replace(
242 | /\{[\s\S]*\}/g,
243 | namedMembersString => {
244 | const useMultipleLines = namedMembersString.indexOf(eol) !== -1;
245 |
246 | let prefix: string | undefined;
247 |
248 | if (useMultipleLines) {
249 | [prefix] = namedMembersString
250 | .split(eol)[1]
251 | .match(/^\s*/) as RegExpMatchArray;
252 | }
253 |
254 | const useSpaces = namedMembersString.charAt(1) === " ";
255 |
256 | const userTrailingComma = namedMembersString
257 | .replace("}", "")
258 | .trim()
259 | .endsWith(",");
260 |
261 | return formatNamedMembers(
262 | namedMembers,
263 | useMultipleLines,
264 | useSpaces,
265 | userTrailingComma,
266 | prefix,
267 | eol,
268 | );
269 | },
270 | );
271 |
272 | return (
273 | code.substring(imported.start, importStart) +
274 | newImportCode +
275 | code.substring(importEnd, importEnd + (imported.end - importEnd))
276 | );
277 | }
278 |
279 | function formatNamedMembers(
280 | namedMembers: NamedMember[],
281 | useMultipleLines: boolean,
282 | useSpaces: boolean,
283 | useTrailingComma: boolean,
284 | prefix: string | undefined,
285 | eol = "\n",
286 | ): string {
287 | if (useMultipleLines) {
288 | return (
289 | "{" +
290 | eol +
291 | namedMembers
292 | .map(({name, alias}, index) => {
293 | const lastImport = index === namedMembers.length - 1;
294 | const comma = !useTrailingComma && lastImport ? "" : ",";
295 |
296 | if (name === alias) {
297 | return `${prefix}${name}${comma}` + eol;
298 | }
299 |
300 | return `${prefix}${name} as ${alias}${comma}` + eol;
301 | })
302 | .join("") +
303 | "}"
304 | );
305 | }
306 |
307 | const space = useSpaces ? " " : "";
308 | const comma = useTrailingComma ? "," : "";
309 |
310 | return (
311 | "{" +
312 | space +
313 | namedMembers
314 | .map(({name, alias}) => {
315 | if (name === alias) {
316 | return `${name}`;
317 | }
318 |
319 | return `${name} as ${alias}`;
320 | })
321 | .join(", ") +
322 | comma +
323 | space +
324 | "}"
325 | );
326 | }
327 |
--------------------------------------------------------------------------------
/packages/import-sort-parser-typescript/test/index.ts:
--------------------------------------------------------------------------------
1 | import "mocha";
2 | import {assert} from "chai";
3 | import {parseImports, formatImport} from "../src";
4 | import {IImport} from "import-sort-parser";
5 |
6 | describe("parse", () => {
7 | it("should return imports", () => {
8 | const imports = parseImports(
9 | `
10 | import "a";
11 | import b from "b";
12 | import {c} from "c";
13 | import d, {e} from "f";
14 | import g, {h as hh} from "i";
15 | import * as j from "k";
16 | import l, * as m from "o";
17 | `.trim());
18 |
19 | assert.equal(imports.length, 7);
20 |
21 | imports.forEach(imported => {
22 | assert.equal(imported.type, "import");
23 | });
24 |
25 | // import "a";
26 | assert.equal(imports[0].start, 0);
27 | assert.equal(imports[0].end, 11);
28 | assert.equal(imports[0].moduleName, "a");
29 |
30 | // import b from "b";
31 | assert.equal(imports[1].start, imports[0].end + 1);
32 | assert.equal(imports[1].end, imports[0].end + 1 + 18);
33 | assert.equal(imports[1].moduleName, "b");
34 | assert.equal(imports[1].defaultMember, "b");
35 |
36 | // import {c} from "c";
37 | assert.equal(imports[2].start, imports[1].end + 1);
38 | assert.equal(imports[2].end, imports[1].end + 1 + 20);
39 | assert.equal(imports[2].moduleName, "c");
40 | assert.deepEqual(imports[2].namedMembers![0], {name: "c", alias: "c"});
41 |
42 | // import d, {e} from "f";
43 | assert.equal(imports[3].start, imports[2].end + 1);
44 | assert.equal(imports[3].end, imports[2].end + 1 + 23);
45 | assert.equal(imports[3].moduleName, "f");
46 | assert.equal(imports[3].defaultMember, "d");
47 | assert.deepEqual(imports[3].namedMembers![0], {name: "e", alias: "e"});
48 |
49 | // import g, {h as hh} from "i";
50 | assert.equal(imports[4].start, imports[3].end + 1);
51 | assert.equal(imports[4].end, imports[3].end + 1 + 29);
52 | assert.equal(imports[4].moduleName, "i");
53 | assert.equal(imports[4].defaultMember, "g");
54 | assert.deepEqual(imports[4].namedMembers![0], {name: "h", alias: "hh"});
55 |
56 | // import * as j from "k";
57 | assert.equal(imports[5].start, imports[4].end + 1);
58 | assert.equal(imports[5].end, imports[4].end + 1 + 23);
59 | assert.equal(imports[5].moduleName, "k");
60 | assert.equal(imports[5].namespaceMember, "j");
61 |
62 | // import l, * as m from "o";
63 | assert.equal(imports[6].start, imports[5].end + 1);
64 | assert.equal(imports[6].end, imports[5].end + 1 + 26);
65 | assert.equal(imports[6].moduleName, "o");
66 | assert.equal(imports[6].defaultMember, "l");
67 | assert.equal(imports[6].namespaceMember, "m");
68 | });
69 |
70 | it("should deal with single quotes (issue #22 and #15)", () => {
71 | const imports = parseImports(`
72 | import 'a';
73 | `);
74 |
75 | assert.equal(imports[0].moduleName, "a");
76 | })
77 |
78 | it("should include nearby comments", () => {
79 | const imports = parseImports(
80 | `
81 | // Above
82 | import "a"; // Besides
83 | // Below
84 | `.trim());
85 |
86 | assert.equal(imports[0].start, 0);
87 | assert.equal(imports[0].end, 31);
88 | });
89 |
90 | it("should include all comments", () => {
91 | const imports = parseImports(
92 | `
93 | // Above
94 | // Above
95 | import "a"; // Besides
96 | // Below
97 | // Below
98 | `.trim());
99 |
100 | assert.equal(imports[0].start, 0);
101 | assert.equal(imports[0].end, 40);
102 | });
103 |
104 | it("should only include nearby comments", () => {
105 | const imports = parseImports(
106 | `
107 | // Above
108 |
109 | import "a"; // Besides
110 |
111 | // Below
112 | `.trim());
113 |
114 | assert.equal(imports[0].start, 10);
115 | assert.equal(imports[0].end, 10 + 22);
116 | });
117 |
118 | it("should not include shebang", () => {
119 | const imports = parseImports(
120 | `
121 | #!/bin/sh
122 | import "a";
123 | `.trim());
124 |
125 | assert.equal(imports[0].start, 10);
126 | assert.equal(imports[0].end, 10 + 11);
127 | });
128 |
129 | it("should include all nearby but exclude far away comments", () => {
130 | const imports = parseImports(
131 | `
132 | // Above
133 |
134 | // Above
135 | import "a"; // Besides
136 | // Below
137 |
138 | // Below
139 | `.trim());
140 |
141 | assert.equal(imports[0].start, 10);
142 | assert.equal(imports[0].end, 10 + 31);
143 | });
144 |
145 | it("should not treat trailing comment on previous import as leading comment", () => {
146 | const imports = parseImports(
147 | `
148 | import "a"; // Besides
149 | import "b";
150 | `.trim());
151 |
152 | assert.equal(imports[0].start, 0);
153 | assert.equal(imports[0].end, 22);
154 |
155 | assert.equal(imports[1].start, 23);
156 | assert.equal(imports[1].end, 1 + 22 + 11);
157 | });
158 | });
159 |
160 | describe("formatImport", () => {
161 | it("CR+LF, named members, typescriptshould not change one-line imports", () => {
162 | const actual =
163 | `
164 | import {a, b, c} from "xyz"
165 | `.trim();
166 |
167 | const imported: IImport = {
168 | start: 0,
169 | end: 27,
170 | type: "import",
171 | moduleName: "xyz",
172 | namedMembers: [
173 | {name: "a", alias: "a"},
174 | {name: "b", alias: "b"},
175 | {name: "c", alias: "c"},
176 | ],
177 | };
178 |
179 | const expected =
180 | `
181 | import {a, b, c} from "xyz"
182 | `.trim();
183 |
184 | assert.equal(formatImport(actual, imported), expected);
185 | });
186 |
187 | it("should not change full multi-line imports with same indendation", () => {
188 | const actual =
189 | `
190 | import {
191 | a,
192 | b,
193 | c
194 | } from "xyz"
195 | `.trim();
196 |
197 | const imported: IImport = {
198 | start: 0,
199 | end: 36,
200 | type: "import",
201 | moduleName: "xyz",
202 | namedMembers: [
203 | {name: "a", alias: "a"},
204 | {name: "b", alias: "b"},
205 | {name: "c", alias: "c"},
206 | ],
207 | };
208 |
209 | const expected =
210 | `
211 | import {
212 | a,
213 | b,
214 | c
215 | } from "xyz"
216 | `.trim();
217 |
218 | assert.equal(formatImport(actual, imported), expected);
219 | });
220 |
221 | it("should change partial multi-line imports indented by 2 spaces", () => {
222 | const actual =
223 | `
224 | import {a,
225 | b,
226 | c
227 | } from "xyz"
228 | `.trim();
229 |
230 | const imported: IImport = {
231 | start: 0,
232 | end: 30,
233 | type: "import",
234 | moduleName: "xyz",
235 | namedMembers: [
236 | {name: "a", alias: "a"},
237 | {name: "b", alias: "b"},
238 | {name: "c", alias: "c"},
239 | ],
240 | };
241 |
242 | const expected =
243 | `
244 | import {
245 | a,
246 | b,
247 | c
248 | } from "xyz"
249 | `.trim();
250 |
251 | assert.equal(formatImport(actual, imported), expected);
252 | });
253 |
254 | it("should change partial multi-line imports indented by 4 spaces", () => {
255 | const actual =
256 | `
257 | import {a,
258 | b,
259 | c
260 | } from "xyz"
261 | `.trim();
262 |
263 | const imported: IImport = {
264 | start: 0,
265 | end: 32,
266 | type: "import",
267 | moduleName: "xyz",
268 | namedMembers: [
269 | {name: "a", alias: "a"},
270 | {name: "b", alias: "b"},
271 | {name: "c", alias: "c"},
272 | ],
273 | };
274 |
275 | const expected =
276 | `
277 | import {
278 | a,
279 | b,
280 | c
281 | } from "xyz"
282 | `.trim();
283 |
284 | assert.equal(formatImport(actual, imported), expected);
285 | });
286 |
287 | it("should preserve whitespace around braces in one-line imports", () => {
288 | const actual =
289 | `
290 | import { a, b, c } from "xyz"
291 | `.trim();
292 |
293 | const imported: IImport = {
294 | start: 0,
295 | end: 29,
296 | type: "import",
297 | moduleName: "xyz",
298 | namedMembers: [
299 | {name: "a", alias: "a"},
300 | {name: "b", alias: "b"},
301 | {name: "c", alias: "c"},
302 | ],
303 | };
304 |
305 | const expected =
306 | `
307 | import { a, b, c } from "xyz"
308 | `.trim();
309 |
310 | assert.equal(formatImport(actual, imported), expected);
311 | });
312 | });
313 |
--------------------------------------------------------------------------------
/packages/import-sort-parser-typescript/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.settings.json",
3 |
4 | "compilerOptions": {
5 | "outDir": "lib",
6 | "rootDir": "src"
7 | },
8 |
9 | "include": ["src"],
10 |
11 | "references": [{"path": "../import-sort-parser"}]
12 | }
13 |
--------------------------------------------------------------------------------
/packages/import-sort-parser/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "import-sort-parser",
3 | "version": "6.0.0",
4 | "description": "Interfaces for import-sort parsers.",
5 | "main": "lib/index.js",
6 | "typings": "lib/index.d.ts",
7 | "scripts": {
8 | "prepublishOnly": "tsc -b .",
9 | "build": "tsc -b .",
10 | "build:watch": "tsc -b . -w",
11 | "test": "mocha --require ts-node/register --recursive \"test/**/*.ts\"",
12 | "test:watch": "mocha -w --require ts-node/register --recursive \"test/**/*.ts\"",
13 | "lint": "eslint --ext ts src"
14 | },
15 | "author": "Renke Grunwald (https://github.com/renke)",
16 | "repository": "renke/import-sort",
17 | "license": "ISC",
18 | "files": [
19 | "lib"
20 | ],
21 | "devDependencies": {
22 | "@types/chai": "^4.1.4",
23 | "@types/mocha": "^5.2.3",
24 | "@types/node": "^10.3.5",
25 | "@typescript-eslint/eslint-plugin": "^1.2.0",
26 | "chai": "^4.0.2",
27 | "eslint": "^5.13.0",
28 | "eslint-config-airbnb": "^17.1.0",
29 | "eslint-config-prettier": "^4.0.0",
30 | "eslint-plugin-import": "^2.16.0",
31 | "eslint-plugin-jsx-a11y": "^6.2.0",
32 | "eslint-plugin-react": "^7.12.4",
33 | "mocha": "^5.2.0",
34 | "ts-node": "^8.0.2",
35 | "typescript": "^3.2.4"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/packages/import-sort-parser/src/index.ts:
--------------------------------------------------------------------------------
1 | export interface IParserOptions {
2 | file?: string;
3 | }
4 |
5 | export interface IParser {
6 | parseImports(code: string, options?: IParserOptions): IImport[];
7 | formatImport(code: string, imported: IImport, eol?: string): string;
8 | }
9 |
10 | export interface IImport {
11 | start: number;
12 | end: number;
13 |
14 | importStart?: number;
15 | importEnd?: number;
16 |
17 | type: ImportType;
18 |
19 | moduleName: string;
20 |
21 | defaultMember?: string;
22 | namespaceMember?: string;
23 | namedMembers: NamedMember[];
24 | }
25 |
26 | export type ImportType = "import" | "require" | "import-equals" | "import-type";
27 |
28 | export interface NamedMember {
29 | name: string;
30 | alias: string;
31 | type?: boolean;
32 | }
33 |
--------------------------------------------------------------------------------
/packages/import-sort-parser/test/index.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renke/import-sort/302fe2d494307f4fedff7ad2b8a4b67d4eaad142/packages/import-sort-parser/test/index.ts
--------------------------------------------------------------------------------
/packages/import-sort-parser/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.settings.json",
3 |
4 | "compilerOptions": {
5 | "outDir": "lib",
6 | "rootDir": "src"
7 | },
8 |
9 | "include": ["src"]
10 | }
11 |
--------------------------------------------------------------------------------
/packages/import-sort-playground/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 |
4 | "parserOptions": {
5 | "sourceType": "module",
6 | },
7 |
8 | "rules": {
9 | "sort-imports": [2, {
10 | ignoreCase: true,
11 | "memberSyntaxSortOrder": ["single", "all", "none", "multiple"]
12 | }],
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/packages/import-sort-playground/.importsortrc:
--------------------------------------------------------------------------------
1 | {
2 | ".js": {
3 | "parser": "babylon",
4 | "style": "renke",
5 | "options": {
6 | alias: [
7 | "components", "reducers", "actions", "selectors"
8 | ]
9 | }
10 | },
11 |
12 | ".ts": {
13 | "parser": "typescript",
14 | "style": "module"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/import-sort-playground/babylon.js:
--------------------------------------------------------------------------------
1 | import "a";
2 |
3 | import * as b from "b";
4 | import React from 'react';
5 | import ReactDOM from 'react-dom';
6 | import Search from '@uber/react-inline-icons/search';
7 | import f from "f";
8 | import isEqual from 'lodash/isEqual';
9 | import {TextInput} from '@uber/react-inputs';
10 | import {a, c, i} from "e";
11 | import {connectToStyles} from '@uber/superfine-react';
12 |
13 | import LocationModal from './location-modal';
14 | import t from '../../../util/i18n';
15 | import type {AccountType} from '../../../types/thrift/yellow/yellow';
16 | import {LocationMap} from './map';
17 | import {NEW_LOCATION_UUID} from './constants';
18 | import type {SuperfineStylePropType} from '../../../util/types';
19 | import {matches} from './utils';
20 |
--------------------------------------------------------------------------------
/packages/import-sort-playground/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "import-sort-playground",
3 | "private": true,
4 | "version": "6.0.0",
5 | "description": "",
6 | "main": "index.js",
7 | "author": "Renke Grunwald (https://github.com/renke)",
8 | "repository": "renke/import-sort",
9 | "license": "ISC",
10 | "dependencies": {
11 | "babel-eslint": "^10.0.1",
12 | "eslint": "^5.0.0",
13 | "import-sort-parser-babylon": "^6.0.0",
14 | "import-sort-parser-typescript": "^6.0.0",
15 | "import-sort-style-eslint": "^6.0.0",
16 | "import-sort-style-module": "^6.0.0",
17 | "import-sort-style-renke": "^6.0.0",
18 | "typescript": "^3.2.4"
19 | },
20 | "devDependencies": {
21 | "@typescript-eslint/eslint-plugin": "^1.2.0",
22 | "eslint": "^5.13.0",
23 | "eslint-config-airbnb": "^17.1.0",
24 | "eslint-config-prettier": "^4.0.0",
25 | "eslint-plugin-import": "^2.16.0",
26 | "eslint-plugin-jsx-a11y": "^6.2.0",
27 | "eslint-plugin-react": "^7.12.4"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/import-sort-playground/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig"
3 | }
4 |
--------------------------------------------------------------------------------
/packages/import-sort-playground/typescript.ts:
--------------------------------------------------------------------------------
1 | import {eumel} from "a";
2 |
3 | // NativeModules.TTRNBridge = {log:()=>{}};NativeModules.TTRNDeviceInfo = { model: 'iPhone', appVersion: '6.3.0' };
4 | import {
5 | a,
6 | b,
7 | c,
8 | } from './src/utils';
9 |
--------------------------------------------------------------------------------
/packages/import-sort-style-eslint/README.md:
--------------------------------------------------------------------------------
1 | # import-sort-style-eslint
2 |
3 | A style for [import-sort](https://github.com/renke/import-sort) that conforms to
4 | the [ESLint](http://eslint.org/) rule
5 | [sort-imports](http://eslint.org/docs/rules/sort-imports).
6 |
7 | ```js
8 | // Modules with side effects (not sorted because order may matter)
9 | import "a";
10 | import "c";
11 | import "b";
12 |
13 | // Modules with only namespace member sorted by member
14 | import * as aa from "aa";
15 | import * as bb from "bb";
16 |
17 | // Modules with multiple members sorted by first member
18 | import aaa, {bbb} from "aaa";
19 | import {ccc, ddd} from "ccc";
20 | import eee, * as fff from "eee";
21 |
22 | // Modules with single member sorted by member
23 | import aaaa from "aaaa";
24 | import {bbbb} from "bbbb";
25 |
26 | ```
27 |
--------------------------------------------------------------------------------
/packages/import-sort-style-eslint/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "import-sort-style-eslint",
3 | "version": "6.0.0",
4 | "description": "An import-sort style that is compatible with ESLint's sort-imports rule",
5 | "main": "lib/index.js",
6 | "typings": "lib/index.d.ts",
7 | "scripts": {
8 | "prepublishOnly": "tsc -b .",
9 | "build": "tsc -b .",
10 | "build:watch": "tsc -b . -w",
11 | "test": "mocha --require ts-node/register --recursive \"test/**/*.ts\"",
12 | "test:watch": "mocha -w --require ts-node/register --recursive \"test/**/*.ts\"",
13 | "lint": "eslint --ext ts src"
14 | },
15 | "author": "Renke Grunwald (https://github.com/renke)",
16 | "repository": "renke/import-sort",
17 | "license": "ISC",
18 | "files": [
19 | "lib"
20 | ],
21 | "devDependencies": {
22 | "@types/chai": "^4.1.4",
23 | "@types/lodash": "^4.14.110",
24 | "@types/mocha": "^5.2.3",
25 | "@types/node": "^10.3.5",
26 | "@typescript-eslint/eslint-plugin": "^1.2.0",
27 | "chai": "^4.0.2",
28 | "eslint": "^5.13.0",
29 | "eslint-config-airbnb": "^17.1.0",
30 | "eslint-config-prettier": "^4.0.0",
31 | "eslint-plugin-import": "^2.16.0",
32 | "eslint-plugin-jsx-a11y": "^6.2.0",
33 | "eslint-plugin-react": "^7.12.4",
34 | "import-sort-style": "^6.0.0",
35 | "mocha": "^5.2.0",
36 | "ts-node": "^8.0.2",
37 | "typescript": "^3.2.4"
38 | },
39 | "dependencies": {
40 | "eslint": "^5.0.0",
41 | "lodash": "^4.17.10"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/packages/import-sort-style-eslint/src/index.ts:
--------------------------------------------------------------------------------
1 | import {resolve} from "path";
2 |
3 | import {CLIEngine} from "eslint";
4 | import {IStyleAPI, IStyleItem} from "import-sort-style";
5 | import * as _ from "lodash";
6 |
7 | export default function(styleApi: IStyleAPI, file?: string): IStyleItem[] {
8 | const {
9 | member,
10 | alias,
11 |
12 | hasNoMember,
13 | hasOnlyNamespaceMember,
14 | hasMultipleMembers,
15 | hasSingleMember,
16 |
17 | unicode,
18 | } = styleApi;
19 |
20 | let useLowerCase = false;
21 | let memberSortSyntaxOrder = ["none", "all", "multiple", "single"];
22 |
23 | if (file) {
24 | try {
25 | const eslintCLI = new CLIEngine({});
26 | const eslintConfig = eslintCLI.getConfigForFile(resolve(file));
27 |
28 | useLowerCase = _.get(
29 | eslintConfig,
30 | "rules.sort-imports[1].ignoreCase",
31 | false,
32 | );
33 |
34 | const newMemberSortSyntaxOrder: string[] = _.get(
35 | eslintConfig,
36 | "rules.sort-imports[1].memberSyntaxSortOrder",
37 | [],
38 | );
39 |
40 | if (
41 | _.difference(memberSortSyntaxOrder, newMemberSortSyntaxOrder).length ===
42 | 0
43 | ) {
44 | memberSortSyntaxOrder = newMemberSortSyntaxOrder;
45 | }
46 | } catch (e) {
47 | // Just use defaults in this case
48 | }
49 | }
50 |
51 | const eslintSort = (first, second) => {
52 | if (useLowerCase) {
53 | return unicode(first.toLowerCase(), second.toLowerCase());
54 | }
55 |
56 | return unicode(first, second);
57 | };
58 |
59 | const styleItemByType = {
60 | none: {match: hasNoMember},
61 | all: {match: hasOnlyNamespaceMember, sort: member(eslintSort)},
62 | multiple: {
63 | match: hasMultipleMembers,
64 | sort: member(eslintSort),
65 | sortNamedMembers: alias(eslintSort),
66 | },
67 | single: {match: hasSingleMember, sort: member(eslintSort)},
68 | };
69 |
70 | return [
71 | // none (don't sort them, because side-effects may need a particular ordering)
72 | styleItemByType[memberSortSyntaxOrder[0]],
73 | {separator: true},
74 |
75 | // all
76 | styleItemByType[memberSortSyntaxOrder[1]],
77 | {separator: true},
78 |
79 | // multiple
80 | styleItemByType[memberSortSyntaxOrder[2]],
81 | {separator: true},
82 |
83 | // single
84 | styleItemByType[memberSortSyntaxOrder[3]],
85 | ];
86 | }
87 |
--------------------------------------------------------------------------------
/packages/import-sort-style-eslint/test/default/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 |
4 | "parserOptions": {
5 | "sourceType": "module"
6 | },
7 |
8 | "rules": {
9 | "sort-imports": [
10 | 2,
11 | {
12 | "ignoreCase": false,
13 | "ignoreDeclarationSort": false,
14 | "ignoreMemberSort": false,
15 | "memberSyntaxSortOrder": ["none", "all", "multiple", "single"]
16 | }
17 | ]
18 | },
19 |
20 | "root": true
21 | }
22 |
--------------------------------------------------------------------------------
/packages/import-sort-style-eslint/test/default/index.ts:
--------------------------------------------------------------------------------
1 | // WIP
2 |
3 | // // import "mocha";
4 | // // import {assert} from "chai";
5 |
6 | // import {CLIEngine, Linter} from "eslint";
7 | // import {sortImports} from "import-sort";
8 | // import * as parser from "import-sort-parser-babylon";
9 |
10 | // import style from "../../src";
11 |
12 | // const eslintCLI = new CLIEngine({});
13 |
14 | // const eslintConfig = eslintCLI.getConfigForFile(`${__dirname}/.eslintrc`);
15 |
16 | // const linter = new Linter();
17 |
18 | // const code = `
19 | // import def from "m";
20 | // import x, {a} from "m";
21 | // import y, * as b from "m";
22 | // import * as all from "m";
23 | // import {named} from "m";
24 | // `.trim();
25 |
26 | // const {code: actual, changes} = sortImports(
27 | // code,
28 | // parser,
29 | // style,
30 | // `${__dirname}/index.ts`,
31 | // );
32 |
33 | // console.log(actual);
34 |
35 | // const errors = linter.verify(actual, eslintConfig);
36 |
37 | // console.log(errors);
38 |
--------------------------------------------------------------------------------
/packages/import-sort-style-eslint/test/index.ts:
--------------------------------------------------------------------------------
1 | // import "mocha";
2 | // import {assert} from "chai";
3 |
--------------------------------------------------------------------------------
/packages/import-sort-style-eslint/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.settings.json",
3 |
4 | "compilerOptions": {
5 | "outDir": "lib",
6 | "rootDir": "src"
7 | },
8 |
9 | "include": ["src"],
10 |
11 | "references": [{"path": "../import-sort-style"}]
12 | }
13 |
--------------------------------------------------------------------------------
/packages/import-sort-style-module/README.md:
--------------------------------------------------------------------------------
1 | # import-sort-style-module
2 |
3 | A style for [import-sort](https://github.com/renke/import-sort) that is focused
4 | on modules.
5 |
6 | ```js
7 | // Absolute modules with side effects (not sorted because order may matter)
8 | import "a";
9 | import "c";
10 | import "b";
11 |
12 | // Relative modules with side effects (not sorted because order may matter)
13 | import "./a";
14 | import "./c";
15 | import "./b";
16 |
17 | // Modules from the Node.js "standard" library sorted by name
18 | import {readFile, writeFile} from "fs";
19 | import * as path from "path";
20 |
21 | // Third-party modules sorted by name
22 | import aa from "aa";
23 | import bb from "bb";
24 | import cc from "cc";
25 |
26 | // First-party modules sorted by "relative depth" and then by name
27 | import aaa from "../../aaa";
28 | import bbb from "../../bbb";
29 | import aaaa from "../aaaa";
30 | import bbbb from "../bbbb";
31 | import aaaaa from "./aaaaa";
32 | import bbbbb from "./bbbbb";
33 | ```
34 |
--------------------------------------------------------------------------------
/packages/import-sort-style-module/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "import-sort-style-module",
3 | "version": "6.0.0",
4 | "description": "An import-sort style that groups and sorts by module",
5 | "main": "lib/index.js",
6 | "typings": "lib/index.d.ts",
7 | "scripts": {
8 | "prepublishOnly": "tsc -b .",
9 | "build": "tsc -b .",
10 | "build:watch": "tsc -b . -w",
11 | "test": "mocha --require ts-node/register --recursive \"test/**/*.ts\"",
12 | "test:watch": "mocha -w --require ts-node/register --recursive \"test/**/*.ts\"",
13 | "lint": "eslint --ext ts src"
14 | },
15 | "author": "Renke Grunwald (https://github.com/renke)",
16 | "repository": "renke/import-sort",
17 | "license": "ISC",
18 | "files": [
19 | "lib"
20 | ],
21 | "devDependencies": {
22 | "@types/chai": "^4.1.4",
23 | "@types/mocha": "^5.2.3",
24 | "@types/node": "^10.3.5",
25 | "@typescript-eslint/eslint-plugin": "^1.2.0",
26 | "chai": "^4.0.2",
27 | "eslint": "^5.13.0",
28 | "eslint-config-airbnb": "^17.1.0",
29 | "eslint-config-prettier": "^4.0.0",
30 | "eslint-plugin-import": "^2.16.0",
31 | "eslint-plugin-jsx-a11y": "^6.2.0",
32 | "eslint-plugin-react": "^7.12.4",
33 | "import-sort-style": "^6.0.0",
34 | "mocha": "^5.2.0",
35 | "ts-node": "^8.0.2",
36 | "typescript": "^3.2.4"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/packages/import-sort-style-module/src/index.ts:
--------------------------------------------------------------------------------
1 | import {IStyleAPI, IStyleItem} from "import-sort-style";
2 |
3 | export default function(styleApi: IStyleAPI): IStyleItem[] {
4 | const {
5 | alias,
6 | and,
7 | dotSegmentCount,
8 | hasNoMember,
9 | isAbsoluteModule,
10 | isNodeModule,
11 | isRelativeModule,
12 | moduleName,
13 | naturally,
14 | unicode,
15 | } = styleApi;
16 |
17 | return [
18 | // import "foo"
19 | {match: and(hasNoMember, isAbsoluteModule)},
20 | {separator: true},
21 |
22 | // import "./foo"
23 | {match: and(hasNoMember, isRelativeModule)},
24 | {separator: true},
25 |
26 | // import … from "fs";
27 | {
28 | match: isNodeModule,
29 | sort: moduleName(naturally),
30 | sortNamedMembers: alias(unicode),
31 | },
32 | {separator: true},
33 |
34 | // import … from "foo";
35 | {
36 | match: isAbsoluteModule,
37 | sort: moduleName(naturally),
38 | sortNamedMembers: alias(unicode),
39 | },
40 | {separator: true},
41 |
42 | // import … from "./foo";
43 | // import … from "../foo";
44 | {
45 | match: isRelativeModule,
46 | sort: [dotSegmentCount, moduleName(naturally)],
47 | sortNamedMembers: alias(unicode),
48 | },
49 | {separator: true},
50 | ];
51 | }
52 |
--------------------------------------------------------------------------------
/packages/import-sort-style-module/test/index.ts:
--------------------------------------------------------------------------------
1 | // import "mocha";
2 | // import {assert} from "chai";
3 |
--------------------------------------------------------------------------------
/packages/import-sort-style-module/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.settings.json",
3 |
4 | "compilerOptions": {
5 | "outDir": "lib",
6 | "rootDir": "src"
7 | },
8 |
9 | "include": ["src"],
10 |
11 | "references": [{"path": "../import-sort-style"}]
12 | }
13 |
--------------------------------------------------------------------------------
/packages/import-sort-style-renke/README.md:
--------------------------------------------------------------------------------
1 | # import-sort-style-eslint
2 |
3 | Renke's personal style for [import-sort](https://github.com/renke/import-sort).
4 |
--------------------------------------------------------------------------------
/packages/import-sort-style-renke/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "import-sort-style-renke",
3 | "version": "6.0.0",
4 | "description": "Renke's personal import-sort style",
5 | "main": "lib/index.js",
6 | "typings": "lib/index.d.ts",
7 | "scripts": {
8 | "prepublishOnly": "tsc -b .",
9 | "build": "tsc -b .",
10 | "build:watch": "tsc -b . -w",
11 | "test": "mocha --require ts-node/register --recursive \"test/**/*.ts\"",
12 | "test:watch": "mocha -w --require ts-node/register --recursive \"test/**/*.ts\"",
13 | "lint": "eslint --ext ts src"
14 | },
15 | "author": "Renke Grunwald (https://github.com/renke)",
16 | "repository": "renke/import-sort",
17 | "license": "ISC",
18 | "files": [
19 | "lib"
20 | ],
21 | "devDependencies": {
22 | "@types/chai": "^4.1.4",
23 | "@types/mocha": "^5.2.3",
24 | "@types/node": "^10.3.5",
25 | "@typescript-eslint/eslint-plugin": "^1.2.0",
26 | "chai": "^4.0.2",
27 | "eslint": "^5.13.0",
28 | "eslint-config-airbnb": "^17.1.0",
29 | "eslint-config-prettier": "^4.0.0",
30 | "eslint-plugin-import": "^2.16.0",
31 | "eslint-plugin-jsx-a11y": "^6.2.0",
32 | "eslint-plugin-react": "^7.12.4",
33 | "import-sort-style": "^6.0.0",
34 | "mocha": "^5.2.0",
35 | "ts-node": "^8.0.2",
36 | "typescript": "^3.2.4"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/packages/import-sort-style-renke/src/index.ts:
--------------------------------------------------------------------------------
1 | import {IStyleAPI, IStyleItem} from "import-sort-style";
2 |
3 | export default function(styleApi: IStyleAPI): IStyleItem[] {
4 | const {
5 | and,
6 | hasDefaultMember,
7 | hasNamedMembers,
8 | hasNamespaceMember,
9 | hasNoMember,
10 | hasOnlyDefaultMember,
11 | hasOnlyNamedMembers,
12 | hasOnlyNamespaceMember,
13 | isAbsoluteModule,
14 | isRelativeModule,
15 | member,
16 | name,
17 | not,
18 | startsWithAlphanumeric,
19 | startsWithLowerCase,
20 | startsWithUpperCase,
21 | unicode,
22 | } = styleApi;
23 |
24 | return [
25 | // import "foo"
26 | {match: and(hasNoMember, isAbsoluteModule)},
27 | {separator: true},
28 |
29 | // import "./foo"
30 | {match: and(hasNoMember, isRelativeModule)},
31 | {separator: true},
32 |
33 | // import * as _ from "bar";
34 | {
35 | match: and(
36 | hasOnlyNamespaceMember,
37 | isAbsoluteModule,
38 | not(member(startsWithAlphanumeric)),
39 | ),
40 | sort: member(unicode),
41 | },
42 | // import * as Foo from "bar";
43 | {
44 | match: and(
45 | hasOnlyNamespaceMember,
46 | isAbsoluteModule,
47 | member(startsWithUpperCase),
48 | ),
49 | sort: member(unicode),
50 | },
51 | // import * as foo from "bar";
52 | {
53 | match: and(
54 | hasOnlyNamespaceMember,
55 | isAbsoluteModule,
56 | member(startsWithLowerCase),
57 | ),
58 | sort: member(unicode),
59 | },
60 |
61 | // import _, * as bar from "baz";
62 | {
63 | match: and(
64 | hasDefaultMember,
65 | hasNamespaceMember,
66 | isAbsoluteModule,
67 | not(member(startsWithAlphanumeric)),
68 | ),
69 | sort: member(unicode),
70 | },
71 | // import Foo, * as bar from "baz";
72 | {
73 | match: and(
74 | hasDefaultMember,
75 | hasNamespaceMember,
76 | isAbsoluteModule,
77 | member(startsWithUpperCase),
78 | ),
79 | sort: member(unicode),
80 | },
81 | // import foo, * as bar from "baz";
82 | {
83 | match: and(
84 | hasDefaultMember,
85 | hasNamespaceMember,
86 | isAbsoluteModule,
87 | member(startsWithUpperCase),
88 | ),
89 | sort: member(unicode),
90 | },
91 |
92 | // import _ from "bar";
93 | {
94 | match: and(
95 | hasOnlyDefaultMember,
96 | isAbsoluteModule,
97 | not(member(startsWithAlphanumeric)),
98 | ),
99 | sort: member(unicode),
100 | },
101 | // import Foo from "bar";
102 | {
103 | match: and(
104 | hasOnlyDefaultMember,
105 | isAbsoluteModule,
106 | member(startsWithUpperCase),
107 | ),
108 | sort: member(unicode),
109 | },
110 | // import foo from "bar";
111 | {
112 | match: and(
113 | hasOnlyDefaultMember,
114 | isAbsoluteModule,
115 | member(startsWithLowerCase),
116 | ),
117 | sort: member(unicode),
118 | },
119 |
120 | // import _, {bar, …} from "baz";
121 | {
122 | match: and(
123 | hasDefaultMember,
124 | hasNamedMembers,
125 | isAbsoluteModule,
126 | not(member(startsWithAlphanumeric)),
127 | ),
128 | sort: member(unicode),
129 | sortNamedMembers: name(unicode),
130 | },
131 | // import Foo, {bar, …} from "baz";
132 | {
133 | match: and(
134 | hasDefaultMember,
135 | hasNamedMembers,
136 | isAbsoluteModule,
137 | member(startsWithUpperCase),
138 | ),
139 | sort: member(unicode),
140 | sortNamedMembers: name(unicode),
141 | },
142 | // import foo, {bar, …} from "baz";
143 | {
144 | match: and(
145 | hasDefaultMember,
146 | hasNamedMembers,
147 | isAbsoluteModule,
148 | member(startsWithLowerCase),
149 | ),
150 | sort: member(unicode),
151 | sortNamedMembers: name(unicode),
152 | },
153 |
154 | // import {_, bar, …} from "baz";
155 | {
156 | match: and(
157 | hasOnlyNamedMembers,
158 | isAbsoluteModule,
159 | not(member(startsWithAlphanumeric)),
160 | ),
161 | sort: member(unicode),
162 | sortNamedMembers: name(unicode),
163 | },
164 | // import {Foo, bar, …} from "baz";
165 | {
166 | match: and(
167 | hasOnlyNamedMembers,
168 | isAbsoluteModule,
169 | member(startsWithUpperCase),
170 | ),
171 | sort: member(unicode),
172 | sortNamedMembers: name(unicode),
173 | },
174 | // import {foo, bar, …} from "baz";
175 | {
176 | match: and(
177 | hasOnlyNamedMembers,
178 | isAbsoluteModule,
179 | member(startsWithLowerCase),
180 | ),
181 | sort: member(unicode),
182 | sortNamedMembers: name(unicode),
183 | },
184 |
185 | {separator: true},
186 |
187 | // import * as _ from "./bar";
188 | {
189 | match: and(
190 | hasOnlyNamespaceMember,
191 | isRelativeModule,
192 | not(member(startsWithAlphanumeric)),
193 | ),
194 | sort: member(unicode),
195 | },
196 | // import * as Foo from "./bar";
197 | {
198 | match: and(
199 | hasOnlyNamespaceMember,
200 | isRelativeModule,
201 | member(startsWithUpperCase),
202 | ),
203 | sort: member(unicode),
204 | },
205 | // import * as foo from "./bar";
206 | {
207 | match: and(
208 | hasOnlyNamespaceMember,
209 | isRelativeModule,
210 | member(startsWithLowerCase),
211 | ),
212 | sort: member(unicode),
213 | },
214 |
215 | // import _, * as bar from "./baz";
216 | {
217 | match: and(
218 | hasDefaultMember,
219 | hasNamespaceMember,
220 | isRelativeModule,
221 | not(member(startsWithAlphanumeric)),
222 | ),
223 | sort: member(unicode),
224 | },
225 | // import Foo, * as bar from "./baz";
226 | {
227 | match: and(
228 | hasDefaultMember,
229 | hasNamespaceMember,
230 | isRelativeModule,
231 | member(startsWithUpperCase),
232 | ),
233 | sort: member(unicode),
234 | },
235 | // import foo, * as bar from "./baz";
236 | {
237 | match: and(
238 | hasDefaultMember,
239 | hasNamespaceMember,
240 | isRelativeModule,
241 | member(startsWithUpperCase),
242 | ),
243 | sort: member(unicode),
244 | },
245 |
246 | // import _ from "./bar";
247 | {
248 | match: and(
249 | hasOnlyDefaultMember,
250 | isRelativeModule,
251 | not(member(startsWithAlphanumeric)),
252 | ),
253 | sort: member(unicode),
254 | },
255 | // import Foo from "./bar";
256 | {
257 | match: and(
258 | hasOnlyDefaultMember,
259 | isRelativeModule,
260 | member(startsWithUpperCase),
261 | ),
262 | sort: member(unicode),
263 | },
264 | // import foo from "./bar";
265 | {
266 | match: and(
267 | hasOnlyDefaultMember,
268 | isRelativeModule,
269 | member(startsWithLowerCase),
270 | ),
271 | sort: member(unicode),
272 | },
273 |
274 | // import _, {bar, …} from "./baz";
275 | {
276 | match: and(
277 | hasDefaultMember,
278 | hasNamedMembers,
279 | isRelativeModule,
280 | not(member(startsWithAlphanumeric)),
281 | ),
282 | sort: member(unicode),
283 | sortNamedMembers: name(unicode),
284 | },
285 | // import Foo, {bar, …} from "./baz";
286 | {
287 | match: and(
288 | hasDefaultMember,
289 | hasNamedMembers,
290 | isRelativeModule,
291 | member(startsWithUpperCase),
292 | ),
293 | sort: member(unicode),
294 | sortNamedMembers: name(unicode),
295 | },
296 | // import foo, {bar, …} from "./baz";
297 | {
298 | match: and(
299 | hasDefaultMember,
300 | hasNamedMembers,
301 | isRelativeModule,
302 | member(startsWithLowerCase),
303 | ),
304 | sort: member(unicode),
305 | sortNamedMembers: name(unicode),
306 | },
307 |
308 | // import {_, bar, …} from "./baz";
309 | {
310 | match: and(
311 | hasOnlyNamedMembers,
312 | isRelativeModule,
313 | not(member(startsWithAlphanumeric)),
314 | ),
315 | sort: member(unicode),
316 | sortNamedMembers: name(unicode),
317 | },
318 | // import {Foo, bar, …} from "./baz";
319 | {
320 | match: and(
321 | hasOnlyNamedMembers,
322 | isRelativeModule,
323 | member(startsWithUpperCase),
324 | ),
325 | sort: member(unicode),
326 | sortNamedMembers: name(unicode),
327 | },
328 | // import {foo, bar, …} from "./baz";
329 | {
330 | match: and(
331 | hasOnlyNamedMembers,
332 | isRelativeModule,
333 | member(startsWithLowerCase),
334 | ),
335 | sort: member(unicode),
336 | sortNamedMembers: name(unicode),
337 | },
338 |
339 | {separator: true},
340 | ];
341 | }
342 |
--------------------------------------------------------------------------------
/packages/import-sort-style-renke/test/index.ts:
--------------------------------------------------------------------------------
1 | // import "mocha";
2 | // import {assert} from "chai";
3 |
--------------------------------------------------------------------------------
/packages/import-sort-style-renke/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.settings.json",
3 |
4 | "compilerOptions": {
5 | "outDir": "lib",
6 | "rootDir": "src"
7 | },
8 |
9 | "include": ["src"],
10 |
11 | "references": [{"path": "../import-sort-style"}]
12 | }
13 |
--------------------------------------------------------------------------------
/packages/import-sort-style/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "import-sort-style",
3 | "version": "6.0.0",
4 | "description": "Interfaces for import-sort styles.",
5 | "main": "lib/index.js",
6 | "typings": "lib/index.d.ts",
7 | "scripts": {
8 | "prepublishOnly": "tsc -b .",
9 | "build": "tsc -b .",
10 | "build:watch": "tsc -b . -w",
11 | "test": "mocha --require ts-node/register --recursive \"test/**/*.ts\"",
12 | "test:watch": "mocha -w --require ts-node/register --recursive \"test/**/*.ts\"",
13 | "lint": "eslint --ext ts src"
14 | },
15 | "author": "Renke Grunwald (https://github.com/renke)",
16 | "repository": "renke/import-sort",
17 | "license": "ISC",
18 | "files": [
19 | "lib"
20 | ],
21 | "devDependencies": {
22 | "@types/chai": "^4.1.4",
23 | "@types/mocha": "^5.2.3",
24 | "@types/node": "^10.3.5",
25 | "@typescript-eslint/eslint-plugin": "^1.2.0",
26 | "chai": "^4.0.2",
27 | "eslint": "^5.13.0",
28 | "eslint-config-airbnb": "^17.1.0",
29 | "eslint-config-prettier": "^4.0.0",
30 | "eslint-plugin-import": "^2.16.0",
31 | "eslint-plugin-jsx-a11y": "^6.2.0",
32 | "eslint-plugin-react": "^7.12.4",
33 | "import-sort-parser": "^6.0.0",
34 | "mocha": "^5.2.0",
35 | "ts-node": "^8.0.2",
36 | "typescript": "^3.2.4"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/packages/import-sort-style/src/index.ts:
--------------------------------------------------------------------------------
1 | import {IImport, NamedMember} from "import-sort-parser";
2 |
3 | export interface IStyleAPI {
4 | member: ISelectorFunction;
5 |
6 | // firstNamedMember: ISelectorFunction;
7 | // defaultMember: ISelectorFunction;
8 | // namespaceMember: ISelectorFunction;
9 |
10 | moduleName: ISelectorFunction;
11 |
12 | name: INamedMemberSelectorFunction;
13 | alias: INamedMemberSelectorFunction;
14 |
15 | always: IMatcherFunction;
16 | not: (matcher: IMatcherFunction) => IMatcherFunction;
17 |
18 | and: (...matcher: IMatcherFunction[]) => IMatcherFunction;
19 | or: (...matcher: IMatcherFunction[]) => IMatcherFunction;
20 |
21 | hasNoMember: IMatcherFunction;
22 | hasMember: IMatcherFunction;
23 |
24 | hasDefaultMember: IMatcherFunction;
25 | hasNamespaceMember: IMatcherFunction;
26 | hasNamedMembers: IMatcherFunction;
27 |
28 | hasOnlyDefaultMember: IMatcherFunction;
29 | hasOnlyNamespaceMember: IMatcherFunction;
30 | hasOnlyNamedMembers: IMatcherFunction;
31 |
32 | hasMultipleMembers: IMatcherFunction;
33 | hasSingleMember: IMatcherFunction;
34 |
35 | isNodeModule: IMatcherFunction;
36 | isRelativeModule: IMatcherFunction;
37 | isAbsoluteModule: IMatcherFunction;
38 | isScopedModule: IMatcherFunction;
39 | isInstalledModule(baseFile: string): IMatcherFunction;
40 |
41 | startsWithUpperCase: IPredicateFunction;
42 | startsWithLowerCase: IPredicateFunction;
43 | startsWithAlphanumeric: IPredicateFunction;
44 |
45 | startsWith(...prefixes: string[]): IPredicateFunction;
46 |
47 | // reverse: (sorter: ISorterFunction) => ISorterFunction;
48 | naturally: IComparatorFunction;
49 | unicode: IComparatorFunction;
50 | dotSegmentCount: ISorterFunction;
51 | }
52 |
53 | export interface IMatcherFunction {
54 | (i: IImport): boolean;
55 | }
56 |
57 | export interface ISorterFunction {
58 | (i1: IImport, i2: IImport): number;
59 | }
60 |
61 | export interface INamedMemberSorterFunction {
62 | (n1: NamedMember, n2: NamedMember): number;
63 | }
64 |
65 | export interface ISelectorFunction {
66 | (f: IPredicateFunction): IMatcherFunction;
67 | (c: IComparatorFunction): ISorterFunction;
68 | }
69 |
70 | export interface INamedMemberSelectorFunction {
71 | (c: IComparatorFunction): INamedMemberSorterFunction;
72 | }
73 |
74 | export interface IPredicateFunction {
75 | (s: string): boolean;
76 | }
77 |
78 | export interface IComparatorFunction {
79 | (s1: string, s2: string): number;
80 | }
81 |
82 | export interface IStyleItem {
83 | match?: IMatcherFunction;
84 |
85 | sort?: ISorterFunction | ISorterFunction[];
86 | sortNamedMembers?: INamedMemberSorterFunction | INamedMemberSorterFunction[];
87 |
88 | separator?: boolean;
89 | }
90 |
91 | export interface IStyle {
92 | (styleApi: IStyleAPI, file?: string, options?: object): IStyleItem[];
93 | }
94 |
--------------------------------------------------------------------------------
/packages/import-sort-style/test/index.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renke/import-sort/302fe2d494307f4fedff7ad2b8a4b67d4eaad142/packages/import-sort-style/test/index.ts
--------------------------------------------------------------------------------
/packages/import-sort-style/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.settings.json",
3 |
4 | "compilerOptions": {
5 | "outDir": "lib",
6 | "rootDir": "src"
7 | },
8 |
9 | "include": ["src"],
10 |
11 | "references": [{"path": "../import-sort-parser"}]
12 | }
13 |
--------------------------------------------------------------------------------
/packages/import-sort/README.md:
--------------------------------------------------------------------------------
1 | # import-sort
2 |
3 | See [import-sort](https://github.com/renke/import-sort) for a README.
4 |
--------------------------------------------------------------------------------
/packages/import-sort/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "import-sort",
3 | "version": "6.0.0",
4 | "description": "Sort ES2015 (aka ES6) imports programmatically.",
5 | "main": "lib/index.js",
6 | "typings": "lib/index.d.ts",
7 | "scripts": {
8 | "prepublishOnly": "tsc -b .",
9 | "build": "tsc -b .",
10 | "build:watch": "tsc -b . -w",
11 | "test": "mocha --require ts-node/register --recursive \"test/**/*.ts\"",
12 | "test:watch": "mocha -w --require ts-node/register --recursive \"test/**/*.ts\"",
13 | "lint": "eslint --ext ts src"
14 | },
15 | "author": "Renke Grunwald (https://github.com/renke)",
16 | "repository": "renke/import-sort",
17 | "license": "ISC",
18 | "files": [
19 | "lib"
20 | ],
21 | "devDependencies": {
22 | "@types/chai": "^4.1.4",
23 | "@types/mocha": "^5.2.3",
24 | "@types/node": "^10.3.5",
25 | "@types/resolve": "^0.0.8",
26 | "@typescript-eslint/eslint-plugin": "^1.2.0",
27 | "chai": "^4.0.2",
28 | "eslint": "^5.13.0",
29 | "eslint-config-airbnb": "^17.1.0",
30 | "eslint-config-prettier": "^4.0.0",
31 | "eslint-plugin-import": "^2.16.0",
32 | "eslint-plugin-jsx-a11y": "^6.2.0",
33 | "eslint-plugin-react": "^7.12.4",
34 | "import-sort-parser-babylon": "^6.0.0",
35 | "import-sort-parser-typescript": "^6.0.0",
36 | "import-sort-style-eslint": "^6.0.0",
37 | "mocha": "^5.2.0",
38 | "rimraf": "^2.6.3",
39 | "ts-node": "^8.0.2",
40 | "typescript": "^3.2.4"
41 | },
42 | "dependencies": {
43 | "detect-newline": "^2.1.0",
44 | "import-sort-parser": "^6.0.0",
45 | "import-sort-style": "^6.0.0",
46 | "is-builtin-module": "^3.0.0",
47 | "resolve": "^1.8.1"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/packages/import-sort/src/index.ts:
--------------------------------------------------------------------------------
1 | import * as detectNewline from "detect-newline";
2 | import {IImport, IParser, NamedMember} from "import-sort-parser";
3 | import {
4 | INamedMemberSorterFunction,
5 | ISorterFunction,
6 | IStyle,
7 | } from "import-sort-style";
8 |
9 | import StyleAPI from "./style/StyleAPI";
10 |
11 | export interface ISortResult {
12 | code: string;
13 | changes: ICodeChange[];
14 | }
15 |
16 | export interface ICodeChange {
17 | start: number;
18 | end: number;
19 | code: string;
20 | note?: string;
21 | }
22 |
23 | export default function importSort(
24 | code: string,
25 | rawParser: string | IParser,
26 | rawStyle: string | IStyle,
27 | file?: string,
28 | options?: object,
29 | ): ISortResult {
30 | let style: IStyle;
31 |
32 | const parser: IParser =
33 | typeof rawParser === "string" ? require(rawParser) : (rawParser as IParser);
34 |
35 | if (typeof rawStyle === "string") {
36 | style = require(rawStyle);
37 |
38 | // eslint-disable-next-line
39 | if ((style as any).default) {
40 | // eslint-disable-next-line
41 | style = (style as any).default;
42 | }
43 | } else {
44 | style = rawStyle as IStyle;
45 | }
46 |
47 | return sortImports(code, parser, style, file, options);
48 | }
49 |
50 | export function sortImports(
51 | code: string,
52 | parser: IParser,
53 | style: IStyle,
54 | file?: string,
55 | options?: object,
56 | ): ISortResult {
57 | // eslint-disable-next-line
58 | const items = addFallback(style, file, options || {})(StyleAPI);
59 |
60 | const buckets: IImport[][] = items.map(() => []);
61 |
62 | const imports = parser.parseImports(code, {
63 | file,
64 | });
65 |
66 | if (imports.length === 0) {
67 | return {code, changes: []};
68 | }
69 |
70 | const eol = detectNewline.graceful(code);
71 |
72 | const changes: ICodeChange[] = [];
73 |
74 | // Fill buckets
75 | for (const imported of imports) {
76 | let sortedImport = imported;
77 |
78 | const index = items.findIndex(item => {
79 | // eslint-disable-next-line
80 | sortedImport = sortNamedMembers(imported, item.sortNamedMembers);
81 | return !!item.match && item.match(sortedImport);
82 | });
83 |
84 | if (index !== -1) {
85 | buckets[index].push(sortedImport);
86 | }
87 | }
88 |
89 | // Sort buckets
90 | buckets.forEach((bucket, index) => {
91 | const {sort} = items[index];
92 |
93 | if (!sort) {
94 | return;
95 | }
96 |
97 | if (!Array.isArray(sort)) {
98 | bucket.sort(sort as ISorterFunction);
99 | return;
100 | }
101 |
102 | const sorters = sort as ISorterFunction[];
103 |
104 | if (sorters.length === 0) {
105 | return;
106 | }
107 |
108 | const multiSort = (first: IImport, second: IImport): number => {
109 | let sorterIndex = 0;
110 | let comparison = 0;
111 |
112 | while (comparison === 0 && sorters[sorterIndex]) {
113 | comparison = sorters[sorterIndex](first, second);
114 | sorterIndex += 1;
115 | }
116 |
117 | return comparison;
118 | };
119 |
120 | bucket.sort(multiSort);
121 | });
122 |
123 | let importsCode = "";
124 |
125 | // Track if we need to insert a separator
126 | let separator = false;
127 |
128 | buckets.forEach((bucket, index) => {
129 | if (bucket.length > 0 && separator) {
130 | importsCode += eol;
131 | separator = false;
132 | }
133 |
134 | bucket.forEach(imported => {
135 | // const sortedImport = sortNamedMembers(imported, items[index].sortNamedMembers);
136 | const importString = parser.formatImport(code, imported, eol);
137 | importsCode += importString + eol;
138 | });
139 |
140 | // Add separator but only when at least one import was already added
141 | if (items[index].separator && importsCode !== "") {
142 | separator = true;
143 | }
144 | });
145 |
146 | let sortedCode = code;
147 |
148 | // Remove imports
149 | imports
150 | .slice()
151 | .reverse()
152 | .forEach(imported => {
153 | let importEnd = imported.end;
154 |
155 | if (sortedCode.charAt(imported.end).match(/\s/)) {
156 | importEnd += 1;
157 | }
158 |
159 | changes.push({
160 | start: imported.start,
161 | end: importEnd,
162 | code: "",
163 | note: "import-remove",
164 | });
165 |
166 | sortedCode =
167 | sortedCode.slice(0, imported.start) +
168 | sortedCode.slice(importEnd, code.length);
169 | });
170 |
171 | const {start} = imports[0];
172 |
173 | // Split code at first original import
174 | let before = code.substring(0, start);
175 | let after = sortedCode.substring(start, sortedCode.length);
176 |
177 | const oldBeforeLength = before.length;
178 | const oldAfterLength = after.length;
179 |
180 | let beforeChange: ICodeChange | undefined;
181 | let afterChange: ICodeChange | undefined;
182 |
183 | // Collapse all whitespace into a single blank line
184 | before = before.replace(/\s+$/, match => {
185 | beforeChange = {
186 | start: start - match.length,
187 | end: start,
188 | code: eol + eol,
189 | note: "before-collapse",
190 | };
191 |
192 | return eol + eol;
193 | });
194 |
195 | // Collapse all whitespace into a single new line
196 | after = after.replace(/^\s+/, match => {
197 | afterChange = {
198 | start,
199 | end: start + match.length,
200 | code: eol,
201 | note: "after-collapse",
202 | };
203 |
204 | return eol;
205 | });
206 |
207 | // Remove all whitespace at the beginning of the code
208 | if (before.match(/^\s+$/)) {
209 | beforeChange = {
210 | start: start - oldBeforeLength,
211 | end: start,
212 | code: "",
213 | note: "before-trim",
214 | };
215 |
216 | before = "";
217 | }
218 |
219 | // Remove all whitespace at the end of the code
220 | if (after.match(/^\s+$/)) {
221 | afterChange = {
222 | start,
223 | end: start + oldAfterLength,
224 | code: "",
225 | note: "after-trim",
226 | };
227 |
228 | after = "";
229 | }
230 |
231 | if (afterChange) {
232 | changes.push(afterChange);
233 | }
234 |
235 | if (beforeChange) {
236 | changes.push(beforeChange);
237 | }
238 |
239 | const change = {
240 | start: before.length,
241 | end: before.length,
242 | code: importsCode,
243 | note: "imports",
244 | };
245 |
246 | changes.push(change);
247 |
248 | if (code === before + importsCode + after) {
249 | return {code, changes: []};
250 | }
251 |
252 | return {
253 | code: before + importsCode + after,
254 | changes,
255 | };
256 | }
257 |
258 | function sortNamedMembers(
259 | imported: IImport,
260 | rawSort?: INamedMemberSorterFunction | INamedMemberSorterFunction[],
261 | ): IImport {
262 | const sort = rawSort;
263 |
264 | if (!sort) {
265 | return imported;
266 | }
267 |
268 | if (!Array.isArray(sort)) {
269 | const singleSortedImport = {...imported};
270 |
271 | singleSortedImport.namedMembers = [...imported.namedMembers].sort(
272 | sort as INamedMemberSorterFunction,
273 | );
274 |
275 | return singleSortedImport;
276 | }
277 |
278 | const sorters = sort as INamedMemberSorterFunction[];
279 |
280 | if (sorters.length === 0) {
281 | return imported;
282 | }
283 |
284 | const multiSort = (first: NamedMember, second: NamedMember): number => {
285 | let sorterIndex = 0;
286 | let comparison = 0;
287 |
288 | while (comparison === 0 && sorters[sorterIndex]) {
289 | comparison = sorters[sorterIndex](first, second);
290 | sorterIndex += 1;
291 | }
292 |
293 | return comparison;
294 | };
295 |
296 | const sortedImport = {...imported};
297 | sortedImport.namedMembers = [...imported.namedMembers].sort(multiSort);
298 |
299 | return sortedImport;
300 | }
301 |
302 | export function applyChanges(code: string, changes: ICodeChange[]): string {
303 | let changedCode = code;
304 |
305 | for (const change of changes) {
306 | changedCode =
307 | changedCode.slice(0, change.start) +
308 | change.code +
309 | changedCode.slice(change.end, changedCode.length);
310 | }
311 |
312 | return changedCode;
313 | }
314 |
315 | function addFallback(style: IStyle, file?: string, options?: object): IStyle {
316 | return styleApi => {
317 | const items = [{separator: true}, {match: styleApi.always}];
318 |
319 | return style(styleApi, file, options).concat(items);
320 | };
321 | }
322 |
--------------------------------------------------------------------------------
/packages/import-sort/src/style/StyleAPI.ts:
--------------------------------------------------------------------------------
1 | import {dirname} from "path";
2 |
3 | import {IImport} from "import-sort-parser";
4 | import {
5 | IComparatorFunction,
6 | IMatcherFunction,
7 | INamedMemberSorterFunction,
8 | IPredicateFunction,
9 | ISorterFunction,
10 | IStyleAPI,
11 | } from "import-sort-style";
12 | import * as resolve from "resolve";
13 |
14 | import isNodeModulePredicate = require("is-builtin-module");
15 |
16 | function member(predicate: IPredicateFunction): IMatcherFunction;
17 | function member(comparator: IComparatorFunction): ISorterFunction;
18 | function member(
19 | predicateOrComparator: IPredicateFunction | IComparatorFunction,
20 | ): IMatcherFunction | ISorterFunction {
21 | // tslint:disable-next-line
22 | if ((predicateOrComparator as Function).length === 1) {
23 | const predicate = predicateOrComparator as IPredicateFunction;
24 |
25 | return (imported: IImport): boolean => {
26 | const importMember =
27 | imported.defaultMember ||
28 | imported.namespaceMember ||
29 | imported.namedMembers[0].alias;
30 | return predicate(importMember);
31 | };
32 | }
33 | const comparator = predicateOrComparator as IComparatorFunction;
34 |
35 | return (firstImport: IImport, secondImport: IImport): number => {
36 | const first =
37 | firstImport.defaultMember ||
38 | firstImport.namespaceMember ||
39 | firstImport.namedMembers[0].alias;
40 | const second =
41 | secondImport.defaultMember ||
42 | secondImport.namespaceMember ||
43 | secondImport.namedMembers[0].alias;
44 |
45 | return comparator(first, second);
46 | };
47 | }
48 |
49 | function moduleName(predicate: IPredicateFunction): IMatcherFunction;
50 | function moduleName(comparator: IComparatorFunction): ISorterFunction;
51 | function moduleName(
52 | predicateOrComparator: IPredicateFunction | IComparatorFunction,
53 | ): IMatcherFunction | ISorterFunction {
54 | // tslint:disable-next-line
55 | if ((predicateOrComparator as Function).length === 1) {
56 | const predicate = predicateOrComparator as IPredicateFunction;
57 |
58 | return (imported: IImport): boolean => {
59 | const importMember = imported.moduleName;
60 | return predicate(importMember);
61 | };
62 | }
63 | const comparator = predicateOrComparator as IComparatorFunction;
64 |
65 | return (firstImport: IImport, secondImport: IImport): number => {
66 | const first = firstImport.moduleName;
67 | const second = secondImport.moduleName;
68 |
69 | return comparator(first, second);
70 | };
71 | }
72 |
73 | function name(comparator: IComparatorFunction): INamedMemberSorterFunction {
74 | return (firstNamedMember, secondNamedMember) => {
75 | return comparator(firstNamedMember.name, secondNamedMember.name);
76 | };
77 | }
78 |
79 | function alias(comparator: IComparatorFunction): INamedMemberSorterFunction {
80 | return (firstNamedMember, secondNamedMember) => {
81 | return comparator(firstNamedMember.alias, secondNamedMember.alias);
82 | };
83 | }
84 |
85 | function always() {
86 | return true;
87 | }
88 |
89 | function not(matcher: IMatcherFunction): IMatcherFunction {
90 | return imported => {
91 | return !matcher(imported);
92 | };
93 | }
94 |
95 | function and(...matchers: IMatcherFunction[]): IMatcherFunction {
96 | return imported => {
97 | return matchers.every(matcher => matcher(imported));
98 | };
99 | }
100 |
101 | function or(...matchers: IMatcherFunction[]): IMatcherFunction {
102 | return imported => {
103 | return matchers.some(matcher => matcher(imported));
104 | };
105 | }
106 |
107 | function hasDefaultMember(imported: IImport): boolean {
108 | return !!imported.defaultMember;
109 | }
110 |
111 | function hasNamespaceMember(imported: IImport): boolean {
112 | return !!imported.namespaceMember;
113 | }
114 |
115 | function hasNamedMembers(imported: IImport): boolean {
116 | return imported.namedMembers.length > 0;
117 | }
118 |
119 | function hasMember(imported: IImport): boolean {
120 | return (
121 | hasDefaultMember(imported) ||
122 | hasNamespaceMember(imported) ||
123 | hasNamedMembers(imported)
124 | );
125 | }
126 |
127 | function hasNoMember(imported: IImport): boolean {
128 | return !hasMember(imported);
129 | }
130 |
131 | function hasOnlyDefaultMember(imported: IImport): boolean {
132 | return (
133 | hasDefaultMember(imported) &&
134 | !hasNamespaceMember(imported) &&
135 | !hasNamedMembers(imported)
136 | );
137 | }
138 |
139 | function hasOnlyNamespaceMember(imported: IImport): boolean {
140 | return (
141 | !hasDefaultMember(imported) &&
142 | hasNamespaceMember(imported) &&
143 | !hasNamedMembers(imported)
144 | );
145 | }
146 |
147 | function hasOnlyNamedMembers(imported: IImport): boolean {
148 | return (
149 | !hasDefaultMember(imported) &&
150 | !hasNamespaceMember(imported) &&
151 | hasNamedMembers(imported)
152 | );
153 | }
154 |
155 | function hasMultipleMembers(imported): boolean {
156 | return (
157 | imported.namedMembers.length +
158 | (imported.defaultMember ? 1 : 0) +
159 | (imported.namespaceMember ? 1 : 0) >
160 | 1
161 | );
162 | }
163 |
164 | function hasSingleMember(imported): boolean {
165 | return (
166 | imported.namedMembers.length + (imported.defaultMember ? 1 : 0) === 1 &&
167 | !hasNamespaceMember(imported)
168 | );
169 | }
170 |
171 | function isNodeModule(imported: IImport): boolean {
172 | return isNodeModulePredicate(imported.moduleName);
173 | }
174 |
175 | function isRelativeModule(imported: IImport): boolean {
176 | return imported.moduleName.indexOf(".") === 0;
177 | }
178 |
179 | function isAbsoluteModule(imported: IImport): boolean {
180 | return !isRelativeModule(imported);
181 | }
182 |
183 | function isInstalledModule(baseFile: string): IMatcherFunction {
184 | return (imported: IImport) => {
185 | try {
186 | const resolvePath = resolve.sync(imported.moduleName, {
187 | basedir: dirname(baseFile),
188 | });
189 |
190 | return resolvePath.includes("node_modules");
191 | } catch (e) {
192 | return false;
193 | }
194 | };
195 | }
196 |
197 | function isScopedModule(imported: IImport): boolean {
198 | return imported.moduleName.startsWith("@");
199 | }
200 |
201 | function startsWithUpperCase(text: string): boolean {
202 | const start = text.charAt(0);
203 | return text.charAt(0) === start.toUpperCase();
204 | }
205 |
206 | function startsWithLowerCase(text: string): boolean {
207 | const start = text.charAt(0);
208 | return text.charAt(0) === start.toLowerCase();
209 | }
210 |
211 | function startsWithAlphanumeric(text: string): boolean {
212 | return !!text.match(/^[A-Za-z0-9]/);
213 | }
214 |
215 | function startsWith(...prefixes: string[]) {
216 | return text => {
217 | return prefixes.some(prefix => text.startsWith(prefix));
218 | };
219 | }
220 |
221 | function naturally(first: string, second: string): number {
222 | return first.localeCompare(second, "en");
223 | }
224 |
225 | function unicode(first: string, second: string): number {
226 | if (first < second) {
227 | return -1;
228 | }
229 |
230 | if (first > second) {
231 | return 1;
232 | }
233 |
234 | return 0;
235 | }
236 |
237 | function dotSegmentCount(firstImport: IImport, secondImport: IImport): number {
238 | const regex = /\.+(?=\/)/g;
239 |
240 | const firstCount = (firstImport.moduleName.match(regex) || []).join("")
241 | .length;
242 | const secondCount = (secondImport.moduleName.match(regex) || []).join("")
243 | .length;
244 |
245 | if (firstCount > secondCount) {
246 | return -1;
247 | }
248 |
249 | if (firstCount < secondCount) {
250 | return 1;
251 | }
252 |
253 | return 0;
254 | }
255 |
256 | const StyleAPI: IStyleAPI = {
257 | member,
258 |
259 | moduleName,
260 |
261 | name,
262 | alias,
263 |
264 | always,
265 | not,
266 | and,
267 | or,
268 |
269 | hasMember,
270 | hasNoMember,
271 |
272 | hasNamespaceMember,
273 | hasDefaultMember,
274 | hasNamedMembers,
275 |
276 | hasOnlyDefaultMember,
277 | hasOnlyNamespaceMember,
278 | hasOnlyNamedMembers,
279 |
280 | hasMultipleMembers,
281 | hasSingleMember,
282 |
283 | isNodeModule,
284 | isRelativeModule,
285 | isAbsoluteModule,
286 | isScopedModule,
287 | isInstalledModule,
288 |
289 | startsWithUpperCase,
290 | startsWithLowerCase,
291 | startsWithAlphanumeric,
292 |
293 | startsWith,
294 |
295 | naturally,
296 | unicode,
297 | dotSegmentCount,
298 | };
299 |
300 | export default StyleAPI;
301 |
--------------------------------------------------------------------------------
/packages/import-sort/test/babylon_eslint_default/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parserOptions": {
3 | "sourceType": "module",
4 | },
5 |
6 | "rules": {
7 | "sort-imports": [2, {"ignoreCase": false}],
8 | },
9 | }
--------------------------------------------------------------------------------
/packages/import-sort/test/babylon_eslint_default/babylon_eslint.js.test:
--------------------------------------------------------------------------------
1 | import {AccountType} from '../../../types/thrift/yellow/yellow';
2 | import {connectToStyles} from '@uber/superfine-react';
3 | import isEqual from 'lodash/isEqual';
4 | import {LocationMap} from './map';
5 | import LocationModal from './location-modal';
6 | import {matches} from './utils';
7 | import {NEW_LOCATION_UUID} from './constants';
8 | import React from 'react';
9 | import ReactDOM from 'react-dom';
10 | import Search from '@uber/react-inline-icons/search';
11 | import {SuperfineStylePropType} from '../../../util/types';
12 | import t from '../../../util/i18n';
13 | import {TextInput} from '@uber/react-inputs';
--------------------------------------------------------------------------------
/packages/import-sort/test/babylon_eslint_default/index.ts:
--------------------------------------------------------------------------------
1 | import "mocha";
2 | import {assert} from "chai";
3 |
4 | import * as parser from "import-sort-parser-babylon";
5 | import style from "import-sort-style-eslint";
6 | import {sortImports} from "../../src";
7 |
8 | import {CLIEngine} from "eslint";
9 | import {readFileSync} from "fs";
10 | import {join} from "path";
11 |
12 | describe("sortImports (babylon, eslint)", () => {
13 | it("should have no errors", () => {
14 | const file = join(__dirname, "babylon_eslint.js.test");
15 | const code = readFileSync(file, "utf-8");
16 |
17 | const result = sortImports(code, parser, style, file);
18 |
19 | const cli = new CLIEngine({
20 | pwd: __dirname,
21 | });
22 |
23 | const report = cli.executeOnText(result.code, file);
24 |
25 | assert.equal(report.errorCount, 0);
26 | });
27 | });
--------------------------------------------------------------------------------
/packages/import-sort/test/babylon_eslint_ignore_case/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parserOptions": {
3 | "sourceType": "module",
4 | },
5 |
6 | "rules": {
7 | "sort-imports": [2, {"ignoreCase": true}],
8 | },
9 | }
--------------------------------------------------------------------------------
/packages/import-sort/test/babylon_eslint_ignore_case/babylon_eslint.js.test:
--------------------------------------------------------------------------------
1 | import {AccountType} from '../../../types/thrift/yellow/yellow';
2 | import {connectToStyles} from '@uber/superfine-react';
3 | import isEqual from 'lodash/isEqual';
4 | import {LocationMap} from './map';
5 | import LocationModal from './location-modal';
6 | import {matches} from './utils';
7 | import {NEW_LOCATION_UUID} from './constants';
8 | import React from 'react';
9 | import ReactDOM from 'react-dom';
10 | import Search from '@uber/react-inline-icons/search';
11 | import {SuperfineStylePropType} from '../../../util/types';
12 | import t from '../../../util/i18n';
13 | import {TextInput} from '@uber/react-inputs';
--------------------------------------------------------------------------------
/packages/import-sort/test/babylon_eslint_ignore_case/index.ts:
--------------------------------------------------------------------------------
1 | import "mocha";
2 | import {assert} from "chai";
3 |
4 | import * as parser from "import-sort-parser-babylon";
5 | import style from "import-sort-style-eslint";
6 | import {sortImports} from "../../src";
7 |
8 | import {CLIEngine} from "eslint";
9 | import {readFileSync} from "fs";
10 | import {join} from "path";
11 |
12 | describe("sortImports (babylon, eslint with ignore case) (issue #20)", () => {
13 | it("should have no errors", () => {
14 | const file = join(__dirname, "babylon_eslint.js.test");
15 | const code = readFileSync(file, "utf-8");
16 |
17 | const result = sortImports(code, parser, style, file);
18 |
19 | const cli = new CLIEngine({
20 | pwd: __dirname,
21 | });
22 |
23 | const report = cli.executeOnText(result.code, file);
24 |
25 | assert.equal(report.errorCount, 0);
26 | });
27 | });
--------------------------------------------------------------------------------
/packages/import-sort/test/index.ts:
--------------------------------------------------------------------------------
1 | import "mocha";
2 |
3 | import {assert} from "chai";
4 | import * as parser from "import-sort-parser-babylon";
5 | import {IStyle, IStyleAPI, IStyleItem} from "import-sort-style";
6 |
7 | import {applyChanges, sortImports} from "../src";
8 |
9 | const NO_BUCKET_STYLE: IStyle = (styleApi: IStyleAPI): Array => {
10 | return [];
11 | };
12 |
13 | describe("sortImports (babylon, NO_BUCKET_STYLE)", () => {
14 | it("should not sort imports but keep them", () => {
15 | const code =
16 | `
17 | import b from "b";
18 | import a from "a";
19 | `.trim() + "\n";
20 |
21 | const expected =
22 | `
23 | import b from "b";
24 | import a from "a";
25 | `.trim() + "\n";
26 |
27 | const result = sortImports(code, parser, NO_BUCKET_STYLE);
28 |
29 | const actual = result.code;
30 | const changes = result.changes;
31 |
32 | assert.equal(actual, expected);
33 | assert.equal(applyChanges(code, changes), expected);
34 | });
35 | });
36 |
37 | const ONE_BUCKET_NATURALLY_STYLE: IStyle = (
38 | styleApi: IStyleAPI,
39 | ): Array => {
40 | const items: Array = [
41 | {
42 | match: styleApi.always,
43 | sort: styleApi.member(styleApi.naturally),
44 | },
45 | ];
46 |
47 | return items;
48 | };
49 |
50 | describe("sortImports (babylon, ONE_BUCKET_NATURALLY_STYLE)", () => {
51 | it("should sort code containing only imports", () => {
52 | const code =
53 | `
54 | import b from "b";
55 | import a from "a";
56 | `.trim() + "\n";
57 |
58 | const expected =
59 | `
60 | import a from "a";
61 | import b from "b";
62 | `.trim() + "\n";
63 |
64 | const result = sortImports(code, parser, ONE_BUCKET_NATURALLY_STYLE);
65 |
66 | const actual = result.code;
67 | const changes = result.changes;
68 |
69 | assert.equal(actual, expected);
70 | assert.equal(applyChanges(code, changes), expected);
71 | });
72 |
73 | it("should sort code containing imports and other things", () => {
74 | const code =
75 | `
76 | import b from "b";
77 | import a from "a";
78 |
79 | console.log("Hello World");
80 | `.trim() + "\n";
81 |
82 | const expected =
83 | `
84 | import a from "a";
85 | import b from "b";
86 |
87 | console.log("Hello World");
88 | `.trim() + "\n";
89 |
90 | const result = sortImports(code, parser, ONE_BUCKET_NATURALLY_STYLE);
91 |
92 | const actual = result.code;
93 | const changes = result.changes;
94 |
95 | assert.equal(actual, expected);
96 | assert.equal(applyChanges(code, changes), expected);
97 | });
98 |
99 | it("should sort code containing imports and other things", () => {
100 | const code =
101 | `
102 | import b from "b";
103 | import a from "a";
104 |
105 | console.log("Hello World");
106 | `.trim() + "\n";
107 |
108 | const expected =
109 | `
110 | import a from "a";
111 | import b from "b";
112 |
113 | console.log("Hello World");
114 | `.trim() + "\n";
115 |
116 | const result = sortImports(code, parser, ONE_BUCKET_NATURALLY_STYLE);
117 |
118 | const actual = result.code;
119 | const changes = result.changes;
120 |
121 | assert.equal(actual, expected);
122 | assert.equal(applyChanges(code, changes), expected);
123 | });
124 |
125 | it("should sort code containing imports, a comment and trailing new lines", () => {
126 | const code =
127 | `
128 | // Above
129 |
130 | import b from "b";
131 | import a from "a";
132 | `.trim() + "\n\n";
133 |
134 | const expected =
135 | `
136 | // Above
137 |
138 | import a from "a";
139 | import b from "b";
140 | `.trim() + "\n";
141 |
142 | const result = sortImports(code, parser, ONE_BUCKET_NATURALLY_STYLE);
143 |
144 | const actual = result.code;
145 | const changes = result.changes;
146 |
147 | assert.equal(actual, expected);
148 | assert.equal(applyChanges(code, changes), expected);
149 | });
150 |
151 | it("should sort code containing imports followed by a comment", () => {
152 | const code =
153 | `
154 | import b from "b";
155 | import a from "a" // a;
156 |
157 | console.log("Hello World");
158 | `.trim() + "\n";
159 |
160 | const expected =
161 | `
162 | import a from "a" // a;
163 | import b from "b";
164 |
165 | console.log("Hello World");
166 | `.trim() + "\n";
167 |
168 | const result = sortImports(code, parser, ONE_BUCKET_NATURALLY_STYLE);
169 |
170 | const actual = result.code;
171 | const changes = result.changes;
172 |
173 | assert.equal(actual, expected);
174 | assert.equal(applyChanges(code, changes), expected);
175 | });
176 |
177 | it("should sort code containing imports followed by a comment", () => {
178 | const code =
179 | `
180 | import b from "b";
181 | // a
182 | import a from "a";
183 |
184 | console.log("Hello World");
185 | `.trim() + "\n";
186 |
187 | const expected =
188 | `
189 | // a
190 | import a from "a";
191 | import b from "b";
192 |
193 | console.log("Hello World");
194 | `.trim() + "\n";
195 |
196 | const result = sortImports(code, parser, ONE_BUCKET_NATURALLY_STYLE);
197 |
198 | const actual = result.code;
199 | const changes = result.changes;
200 |
201 | assert.equal(actual, expected);
202 | assert.equal(applyChanges(code, changes), expected);
203 | });
204 |
205 | it("should sort code containing imports anywhere at top-level", () => {
206 | const code =
207 | `
208 | import b from "b";
209 | import a from "a";
210 |
211 | console.log("Hello");
212 | import c from "c";
213 | console.log("World");
214 | `.trim() + "\n";
215 |
216 | const expected =
217 | `
218 | import a from "a";
219 | import b from "b";
220 | import c from "c";
221 |
222 | console.log("Hello");
223 | console.log("World");
224 | `.trim() + "\n";
225 |
226 | const result = sortImports(code, parser, ONE_BUCKET_NATURALLY_STYLE);
227 |
228 | const actual = result.code;
229 | const changes = result.changes;
230 |
231 | assert.equal(actual, expected);
232 | assert.equal(applyChanges(code, changes), expected);
233 | });
234 |
235 | it("should format import such that all named members are on its own line", () => {
236 | const code =
237 | `
238 | import {a,
239 | b, c} from "a";
240 | `.trim() + "\n";
241 |
242 | const expected =
243 | `
244 | import {
245 | a,
246 | b,
247 | c
248 | } from "a";
249 | `.trim() + "\n";
250 |
251 | const result = sortImports(code, parser, ONE_BUCKET_NATURALLY_STYLE);
252 |
253 | const actual = result.code;
254 | const changes = result.changes;
255 |
256 | assert.equal(actual, expected);
257 | assert.equal(applyChanges(code, changes), expected);
258 | });
259 | });
260 |
261 | const TWO_BUCKETS_NATURALLY_STYLE: IStyle = (
262 | styleApi: IStyleAPI,
263 | ): Array => {
264 | const items: Array = [
265 | {
266 | match: styleApi.isAbsoluteModule,
267 | sort: styleApi.member(styleApi.naturally),
268 | },
269 | {
270 | match: styleApi.isRelativeModule,
271 | sort: styleApi.member(styleApi.naturally),
272 | },
273 | ];
274 |
275 | return items;
276 | };
277 |
278 | describe("sortImports (babylon, TWO_BUCKETS_NATURALLY_STYLE)", () => {
279 | it("should sort code containing only imports", () => {
280 | const code =
281 | `
282 | import b from "b";
283 | import d from "./d";
284 | import a from "a";
285 | import c from "./c";
286 | `.trim() + "\n";
287 |
288 | const expected =
289 | `
290 | import a from "a";
291 | import b from "b";
292 | import c from "./c";
293 | import d from "./d";
294 | `.trim() + "\n";
295 |
296 | const result = sortImports(code, parser, TWO_BUCKETS_NATURALLY_STYLE);
297 |
298 | const actual = result.code;
299 | const changes = result.changes;
300 |
301 | assert.equal(actual, expected);
302 | assert.equal(applyChanges(code, changes), expected);
303 | });
304 |
305 | it("should sort code containing imports and other code", () => {
306 | const code =
307 | `
308 | import b from "b";
309 | import d from "./d";
310 | import a from "a";
311 | import c from "./c";
312 |
313 | console.log("Hello World");
314 | `.trim() + "\n";
315 |
316 | const expected =
317 | `
318 | import a from "a";
319 | import b from "b";
320 | import c from "./c";
321 | import d from "./d";
322 |
323 | console.log("Hello World");
324 | `.trim() + "\n";
325 |
326 | const result = sortImports(code, parser, TWO_BUCKETS_NATURALLY_STYLE);
327 |
328 | const actual = result.code;
329 | const changes = result.changes;
330 |
331 | assert.equal(actual, expected);
332 | assert.equal(applyChanges(code, changes), expected);
333 | });
334 | });
335 |
336 | const TWO_BUCKETS_WITH_SEPARATOR_NATURALLY_STYLE: IStyle = (
337 | styleApi: IStyleAPI,
338 | ): Array => {
339 | const items: Array = [
340 | {
341 | match: styleApi.isAbsoluteModule,
342 | sort: styleApi.member(styleApi.naturally),
343 | },
344 | {
345 | separator: true,
346 | },
347 | {
348 | match: styleApi.isRelativeModule,
349 | sort: styleApi.member(styleApi.naturally),
350 | },
351 | ];
352 |
353 | return items;
354 | };
355 |
356 | describe("sortImports (babylon, TWO_BUCKETS_WITH_SEPARATOR_NATURALLY_STYLE)", () => {
357 | it("should sort code containing only imports", () => {
358 | const code =
359 | `
360 | import b from "b";
361 | import d from "./d";
362 | import a from "a";
363 | import c from "./c";
364 | `.trim() + "\n";
365 |
366 | const expected =
367 | `
368 | import a from "a";
369 | import b from "b";
370 |
371 | import c from "./c";
372 | import d from "./d";
373 | `.trim() + "\n";
374 |
375 | const result = sortImports(
376 | code,
377 | parser,
378 | TWO_BUCKETS_WITH_SEPARATOR_NATURALLY_STYLE,
379 | );
380 |
381 | const actual = result.code;
382 | const changes = result.changes;
383 |
384 | assert.equal(actual, expected);
385 | assert.equal(applyChanges(code, changes), expected);
386 | });
387 |
388 | it("should sort code containing imports and other code", () => {
389 | const code =
390 | `
391 | import b from "b";
392 | import d from "./d";
393 |
394 | import a from "a";
395 | import c from "./c";
396 |
397 | console.log("Hello World");
398 | `.trim() + "\n";
399 |
400 | const expected =
401 | `
402 | import a from "a";
403 | import b from "b";
404 |
405 | import c from "./c";
406 | import d from "./d";
407 |
408 | console.log("Hello World");
409 | `.trim() + "\n";
410 |
411 | const result = sortImports(
412 | code,
413 | parser,
414 | TWO_BUCKETS_WITH_SEPARATOR_NATURALLY_STYLE,
415 | );
416 |
417 | const actual = result.code;
418 | const changes = result.changes;
419 |
420 | assert.equal(actual, expected);
421 | assert.equal(applyChanges(code, changes), expected);
422 | });
423 | });
424 |
425 | const FOUR_BUCKETS_WITH_SEPARATORS_NATURALLY_STYLE: IStyle = (
426 | styleApi: IStyleAPI,
427 | ): Array => {
428 | const items: Array = [
429 | {
430 | match: styleApi.not(styleApi.hasMember),
431 | sort: styleApi.member(styleApi.naturally),
432 | },
433 | {
434 | separator: true,
435 | },
436 | {
437 | match: styleApi.hasOnlyDefaultMember,
438 | sort: styleApi.member(styleApi.naturally),
439 | },
440 | {
441 | separator: true,
442 | },
443 | {
444 | match: styleApi.hasOnlyNamespaceMember,
445 | sort: styleApi.member(styleApi.naturally),
446 | },
447 | {
448 | separator: true,
449 | },
450 | {
451 | match: styleApi.hasOnlyNamedMembers,
452 | sort: styleApi.member(styleApi.naturally),
453 | },
454 | ];
455 |
456 | return items;
457 | };
458 |
459 | describe("sortImports (babylon, FOUR_BUCKETS_WITH_SEPARATORS_NATURALLY_STYLE)", () => {
460 | it("should sort code containing only imports", () => {
461 | const code =
462 | `
463 | import {d} from "./d";
464 | import * as c from "c";
465 | import b from "b";
466 | import "a";
467 | `.trim() + "\n";
468 |
469 | const expected =
470 | `
471 | import "a";
472 |
473 | import b from "b";
474 |
475 | import * as c from "c";
476 |
477 | import {d} from "./d";
478 | `.trim() + "\n";
479 |
480 | const result = sortImports(
481 | code,
482 | parser,
483 | FOUR_BUCKETS_WITH_SEPARATORS_NATURALLY_STYLE,
484 | );
485 |
486 | const actual = result.code;
487 | const changes = result.changes;
488 |
489 | assert.equal(actual, expected);
490 | assert.equal(applyChanges(code, changes), expected);
491 | });
492 |
493 | it("should not add unnecessary separators", () => {
494 | const code =
495 | `
496 | import {d} from "./d";
497 | import "a";
498 | `.trim() + "\n";
499 |
500 | const expected =
501 | `
502 | import "a";
503 |
504 | import {d} from "./d";
505 | `.trim() + "\n";
506 |
507 | const result = sortImports(
508 | code,
509 | parser,
510 | FOUR_BUCKETS_WITH_SEPARATORS_NATURALLY_STYLE,
511 | );
512 |
513 | const actual = result.code;
514 | const changes = result.changes;
515 |
516 | assert.equal(actual, expected);
517 | assert.equal(applyChanges(code, changes), expected);
518 | });
519 |
520 | it("should not change code containing no imports", () => {
521 | const code =
522 | `
523 | `.trim() + "\n";
524 |
525 | const expected =
526 | `
527 | `.trim() + "\n";
528 |
529 | const result = sortImports(
530 | code,
531 | parser,
532 | FOUR_BUCKETS_WITH_SEPARATORS_NATURALLY_STYLE,
533 | );
534 |
535 | const actual = result.code;
536 | const changes = result.changes;
537 |
538 | assert.equal(actual, expected);
539 | assert.equal(applyChanges(code, changes), expected);
540 | });
541 |
542 | it("should sort imports even placed in a single line", () => {
543 | const code =
544 | `
545 | import a from "a"; import b from "b";
546 | `.trim() + "\n";
547 |
548 | const expected =
549 | `
550 | import a from "a";
551 | import b from "b";
552 | `.trim() + "\n";
553 |
554 | const result = sortImports(
555 | code,
556 | parser,
557 | FOUR_BUCKETS_WITH_SEPARATORS_NATURALLY_STYLE,
558 | );
559 |
560 | const actual = result.code;
561 | const changes = result.changes;
562 |
563 | assert.equal(actual, expected);
564 | assert.equal(applyChanges(code, changes), expected);
565 | });
566 |
567 | it("should sort imports the span multiple lines", () => {
568 | const code =
569 | `
570 | import {e} from "e";
571 | import {
572 | b,
573 | c,
574 | d,
575 | } from "bcd";
576 | import {a} from "a";
577 | `.trim() + "\n";
578 |
579 | const expected =
580 | `
581 | import {a} from "a";
582 | import {
583 | b,
584 | c,
585 | d,
586 | } from "bcd";
587 | import {e} from "e";
588 | `.trim() + "\n";
589 |
590 | const result = sortImports(
591 | code,
592 | parser,
593 | FOUR_BUCKETS_WITH_SEPARATORS_NATURALLY_STYLE,
594 | );
595 |
596 | const actual = result.code;
597 | const changes = result.changes;
598 |
599 | assert.equal(actual, expected);
600 | assert.equal(applyChanges(code, changes), expected);
601 | });
602 |
603 | it("should leave a blank line after headers (such as copyright texts)", () => {
604 | const code =
605 | `
606 | // Copyright
607 |
608 | import b from "b";
609 | import a from "a";
610 | `.trim() + "\n";
611 |
612 | const expected =
613 | `
614 | // Copyright
615 |
616 | import a from "a";
617 | import b from "b";
618 | `.trim() + "\n";
619 |
620 | const result = sortImports(
621 | code,
622 | parser,
623 | FOUR_BUCKETS_WITH_SEPARATORS_NATURALLY_STYLE,
624 | );
625 |
626 | const actual = result.code;
627 | const changes = result.changes;
628 |
629 | assert.equal(actual, expected);
630 | assert.equal(applyChanges(code, changes), expected);
631 | });
632 |
633 | it("should add a blank line after the shebang", () => {
634 | const code =
635 | `
636 | #!/usr/bin/env node
637 | import b from "b";
638 | import a from "a";
639 | `.trim() + "\n";
640 |
641 | const expected =
642 | `
643 | #!/usr/bin/env node
644 |
645 | import a from "a";
646 | import b from "b";
647 | `.trim() + "\n";
648 |
649 | const result = sortImports(
650 | code,
651 | parser,
652 | FOUR_BUCKETS_WITH_SEPARATORS_NATURALLY_STYLE,
653 | );
654 |
655 | const actual = result.code;
656 | const changes = result.changes;
657 |
658 | assert.equal(actual, expected);
659 | assert.equal(applyChanges(code, changes), expected);
660 | });
661 |
662 | it("should leave no blank files if imports come first", () => {
663 | const code =
664 | "\n\n" +
665 | `
666 | import b from "b";
667 | import a from "a";
668 | `.trim() +
669 | "\n";
670 |
671 | const expected =
672 | `
673 | import a from "a";
674 | import b from "b";
675 | `.trim() + "\n";
676 |
677 | const result = sortImports(
678 | code,
679 | parser,
680 | FOUR_BUCKETS_WITH_SEPARATORS_NATURALLY_STYLE,
681 | );
682 |
683 | const actual = result.code;
684 | const changes = result.changes;
685 |
686 | assert.equal(actual, expected);
687 | assert.equal(applyChanges(code, changes), expected);
688 | });
689 | });
690 |
691 | const ONE_BUCKET_NATURALLY_BY_MODULE_AND_MEMBER_STYLE: IStyle = (
692 | styleApi: IStyleAPI,
693 | ): Array => {
694 | const items: Array = [
695 | {
696 | match: styleApi.always,
697 | sort: [
698 | styleApi.moduleName(styleApi.naturally),
699 | styleApi.member(styleApi.naturally),
700 | ],
701 | },
702 | ];
703 |
704 | return items;
705 | };
706 |
707 | describe("sortImports (babylon, ONE_BUCKET_NATURALLY_BY_MODULE_AND_MEMBER_STYLE)", () => {
708 | it("should sort code containing only imports", () => {
709 | const code =
710 | `
711 | import b from "y";
712 | import a from "x";
713 | import d from "y";
714 | import c from "x";
715 | `.trim() + "\n";
716 |
717 | const expected =
718 | `
719 | import a from "x";
720 | import c from "x";
721 | import b from "y";
722 | import d from "y";
723 | `.trim() + "\n";
724 |
725 | const result = sortImports(
726 | code,
727 | parser,
728 | ONE_BUCKET_NATURALLY_BY_MODULE_AND_MEMBER_STYLE,
729 | );
730 |
731 | const actual = result.code;
732 | const changes = result.changes;
733 |
734 | assert.equal(actual, expected);
735 | assert.equal(applyChanges(code, changes), expected);
736 | });
737 | });
738 |
739 | const ONE_BUCKET_NATURAL_NAMED_MEMBERS_STYLE: IStyle = (
740 | styleApi: IStyleAPI,
741 | ): Array => {
742 | const items: Array = [
743 | {
744 | match: styleApi.always,
745 | sort: [
746 | styleApi.moduleName(styleApi.naturally),
747 | styleApi.member(styleApi.naturally),
748 | ],
749 | sortNamedMembers: styleApi.name(styleApi.naturally),
750 | },
751 | ];
752 |
753 | return items;
754 | };
755 |
756 | describe("sortImports (babylon, ONE_BUCKET_NATURAL_NAMED_MEMBERS_STYLE)", () => {
757 | it("should sort named members", () => {
758 | const code =
759 | `
760 | import {c, b, a} from "x";
761 | `.trim() + "\n";
762 |
763 | const expected =
764 | `
765 | import {a, b, c} from "x";
766 | `.trim() + "\n";
767 |
768 | const result = sortImports(
769 | code,
770 | parser,
771 | ONE_BUCKET_NATURAL_NAMED_MEMBERS_STYLE,
772 | );
773 |
774 | const actual = result.code;
775 | const changes = result.changes;
776 |
777 | assert.equal(actual, expected);
778 | assert.equal(applyChanges(code, changes), expected);
779 | });
780 |
781 | it("should sort named members", () => {
782 | const code =
783 | `
784 | import {
785 | c,
786 | b,
787 | a
788 | } from "x";
789 | `.trim() + "\n";
790 |
791 | const expected =
792 | `
793 | import {
794 | a,
795 | b,
796 | c
797 | } from "x";
798 | `.trim() + "\n";
799 |
800 | const result = sortImports(
801 | code,
802 | parser,
803 | ONE_BUCKET_NATURAL_NAMED_MEMBERS_STYLE,
804 | );
805 |
806 | const actual = result.code;
807 | const changes = result.changes;
808 |
809 | assert.equal(actual, expected);
810 | assert.equal(applyChanges(code, changes), expected);
811 | });
812 | });
813 |
--------------------------------------------------------------------------------
/packages/import-sort/test/issues.ts:
--------------------------------------------------------------------------------
1 | import {assert} from "chai";
2 | import * as parserBabylon from "import-sort-parser-babylon";
3 | import * as parserTypescript from "import-sort-parser-typescript";
4 | import {IStyle, IStyleAPI, IStyleItem} from "import-sort-style";
5 |
6 | import {applyChanges, sortImports} from "../src";
7 |
8 | const ONE_BUCKET_NATURAL_NAMED_MEMBERS_STYLE: IStyle = (
9 | styleApi: IStyleAPI,
10 | ): Array => {
11 | const items: Array = [
12 | {
13 | match: styleApi.always,
14 | sort: [
15 | styleApi.moduleName(styleApi.naturally),
16 | styleApi.member(styleApi.naturally),
17 | ],
18 | sortNamedMembers: styleApi.name(styleApi.naturally),
19 | },
20 | ];
21 |
22 | return items;
23 | };
24 |
25 | describe("Support dynamic imports / code splitting (issue #46)", () => {
26 | it("should recognize dynamic import syntax", () => {
27 | const code =
28 | `
29 | const LoadableDesignPatterns = Loadable({
30 | loader: () => import('./design_patterns'),
31 | loading: Loading,
32 | });
33 | `.trim() + "\n";
34 |
35 | const result = sortImports(
36 | code,
37 | parserBabylon,
38 | ONE_BUCKET_NATURAL_NAMED_MEMBERS_STYLE,
39 | );
40 |
41 | assert.equal(code, result.code);
42 | });
43 | });
44 |
45 | describe("Error with comments (issue #35)", () => {
46 | it("should not swallow parts of the leading comments (babylon)", () => {
47 | const code =
48 | `
49 | // NativeModules.TTRNBridge = {log:()=>{}};NativeModules.TTRNDeviceInfo = { model: 'iPhone', appVersion: '6.3.0' };
50 | import { consoleKiller } from './src/utils';
51 | `.trim() + "\n";
52 |
53 | const result = sortImports(
54 | code,
55 | parserBabylon,
56 | ONE_BUCKET_NATURAL_NAMED_MEMBERS_STYLE,
57 | );
58 |
59 | assert.equal(code, result.code);
60 | });
61 |
62 | it("should not swallow parts of the leading comments (typescript)", () => {
63 | const code =
64 | `
65 | // NativeModules.TTRNBridge = {log:()=>{}};NativeModules.TTRNDeviceInfo = { model: 'iPhone', appVersion: '6.3.0' };
66 | import { consoleKiller } from './src/utils';
67 | `.trim() + "\n";
68 |
69 | const result = sortImports(
70 | code,
71 | parserTypescript,
72 | ONE_BUCKET_NATURAL_NAMED_MEMBERS_STYLE,
73 | );
74 |
75 | assert.equal(code, result.code);
76 | });
77 | });
78 |
79 | describe("Respect line ending (issue #37)", () => {
80 | it("CR+LF", () => {
81 | const code = `import b from "b"\r\nimport a from "a"\r\n`;
82 |
83 | const expected = `import a from "a"\r\nimport b from "b"\r\n`;
84 |
85 | const result = sortImports(
86 | code,
87 | parserBabylon,
88 | ONE_BUCKET_NATURAL_NAMED_MEMBERS_STYLE,
89 | );
90 |
91 | const actual = result.code;
92 | const changes = result.changes;
93 |
94 | assert.equal(actual, expected);
95 | assert.equal(applyChanges(code, changes), expected);
96 | });
97 |
98 | it("CR+LF, named members, babylon", () => {
99 | const code = `import {\r\nb,\r\na,\r\n} from "ab"\r\n`;
100 |
101 | const expected = `import {\r\na,\r\nb,\r\n} from "ab"\r\n`;
102 |
103 | const result = sortImports(
104 | code,
105 | parserBabylon,
106 | ONE_BUCKET_NATURAL_NAMED_MEMBERS_STYLE,
107 | );
108 |
109 | const actual = result.code;
110 | const changes = result.changes;
111 |
112 | assert.equal(actual, expected);
113 | assert.equal(applyChanges(code, changes), expected);
114 | });
115 |
116 | it("CR+LF, named members, typescript", () => {
117 | const code = `import {\r\nb,\r\na,\r\n} from "ab"\r\n`;
118 |
119 | const expected = `import {\r\na,\r\nb,\r\n} from "ab"\r\n`;
120 |
121 | const result = sortImports(
122 | code,
123 | parserTypescript,
124 | ONE_BUCKET_NATURAL_NAMED_MEMBERS_STYLE,
125 | );
126 |
127 | const actual = result.code;
128 | const changes = result.changes;
129 |
130 | assert.equal(actual, expected);
131 | assert.equal(applyChanges(code, changes), expected);
132 | });
133 |
134 | it("LF", () => {
135 | const code = `import b from "b"\nimport a from "a"\n`;
136 |
137 | const expected = `import a from "a"\nimport b from "b"\n`;
138 |
139 | const result = sortImports(
140 | code,
141 | parserBabylon,
142 | ONE_BUCKET_NATURAL_NAMED_MEMBERS_STYLE,
143 | );
144 |
145 | const actual = result.code;
146 | const changes = result.changes;
147 |
148 | assert.equal(actual, expected);
149 | assert.equal(applyChanges(code, changes), expected);
150 | });
151 |
152 | it("LF, named members, babylon", () => {
153 | const code = `import {\nb,\na,\n} from "ab"\n`;
154 |
155 | const expected = `import {\na,\nb,\n} from "ab"\n`;
156 |
157 | const result = sortImports(
158 | code,
159 | parserBabylon,
160 | ONE_BUCKET_NATURAL_NAMED_MEMBERS_STYLE,
161 | );
162 |
163 | const actual = result.code;
164 | const changes = result.changes;
165 |
166 | assert.equal(actual, expected);
167 | assert.equal(applyChanges(code, changes), expected);
168 | });
169 |
170 | it("LF, named members, typescript", () => {
171 | const code = `import {\nb,\na,\n} from "ab"\n`;
172 |
173 | const expected = `import {\na,\nb,\n} from "ab"\n`;
174 |
175 | const result = sortImports(
176 | code,
177 | parserTypescript,
178 | ONE_BUCKET_NATURAL_NAMED_MEMBERS_STYLE,
179 | );
180 |
181 | const actual = result.code;
182 | const changes = result.changes;
183 |
184 | assert.equal(actual, expected);
185 | assert.equal(applyChanges(code, changes), expected);
186 | });
187 | });
188 |
--------------------------------------------------------------------------------
/packages/import-sort/test/stubImport.ts:
--------------------------------------------------------------------------------
1 | import {IImport, NamedMember, ImportType} from "import-sort-parser";
2 |
3 | export default function stubImport(data: {
4 | type?: ImportType,
5 | moduleName?: string,
6 | defaultMember?: string,
7 | namespaceMember?: string,
8 | namedMembers?: Array,
9 | }): IImport {
10 | const imported = {
11 | start: 0,
12 | end: 0,
13 |
14 | type: data.type || "import",
15 | moduleName: data.moduleName || "foo",
16 |
17 | defaultMember: data.defaultMember,
18 | namespaceMember: data.namespaceMember,
19 | namedMembers: data.namedMembers || [],
20 | };
21 |
22 | return imported;
23 | }
24 |
--------------------------------------------------------------------------------
/packages/import-sort/test/style/StyleAPI.ts:
--------------------------------------------------------------------------------
1 | import "mocha";
2 | import {assert} from "chai";
3 |
4 | import {IPredicateFunction, IComparatorFunction} from "import-sort-style";
5 | import {IImport} from "import-sort-parser";
6 | import stubImport from "../stubImport";
7 |
8 | import StyleAPI from "../../src/style/StyleAPI";
9 |
10 | describe("StyleAPI", () => {
11 | it("member matcher", () => {
12 |
13 | const predicate: IPredicateFunction = member => {
14 | return member === "foo";
15 | };
16 |
17 | const imported: IImport = stubImport({defaultMember: "foo"});
18 |
19 | assert.isTrue(StyleAPI.member(predicate)(imported));
20 | });
21 |
22 | it("member sorter", () => {
23 | const comparator: IComparatorFunction = (firstMember, secondMember) => {
24 | return firstMember.localeCompare(secondMember);
25 | };
26 |
27 | const firstImport: IImport = stubImport({defaultMember: "a"});
28 | const secondImport: IImport = stubImport({defaultMember: "b"});
29 |
30 | assert.isBelow(StyleAPI.member(comparator)(firstImport, secondImport), 0);
31 | });
32 |
33 | it("should detect Node modules", () => {
34 | const imported: IImport = stubImport({moduleName: "fs"});
35 | assert.isTrue(StyleAPI.isNodeModule(imported));
36 | });
37 |
38 | it("should detect non-Node modules", () => {
39 | const imported: IImport = stubImport({moduleName: "foo"});
40 | assert.isFalse(StyleAPI.isNodeModule(imported));
41 | });
42 |
43 | it("should detect scoped modules", () => {
44 | const imported: IImport = stubImport({moduleName: "@foo/bar"});
45 | assert.isTrue(StyleAPI.isScopedModule(imported));
46 | });
47 |
48 | it("should detect non-scoped modules", () => {
49 | const imported: IImport = stubImport({moduleName: "foo"});
50 | assert.isFalse(StyleAPI.isScopedModule(imported));
51 | });
52 |
53 | it("should sort by dot segment count", () => {
54 | let firstImport: IImport = stubImport({moduleName: "./a"});
55 | let secondImport: IImport = stubImport({moduleName: "./b"});
56 |
57 | assert.equal(StyleAPI.dotSegmentCount(firstImport, secondImport), 0);
58 |
59 | firstImport = stubImport({moduleName: "../a"});
60 | secondImport = stubImport({moduleName: "./b"});
61 |
62 | assert.isBelow(StyleAPI.dotSegmentCount(firstImport, secondImport), 0);
63 |
64 | firstImport = stubImport({moduleName: "./a"});
65 | secondImport = stubImport({moduleName: "../b"});
66 |
67 | assert.isAbove(StyleAPI.dotSegmentCount(firstImport, secondImport), 0);
68 |
69 | firstImport = stubImport({moduleName: "../../a"});
70 | secondImport = stubImport({moduleName: "../b"});
71 |
72 | assert.isBelow(StyleAPI.dotSegmentCount(firstImport, secondImport), 0);
73 | });
74 |
75 | it("should match module names by prefix", () => {
76 | const imported: IImport = stubImport({moduleName: "foo/bar"});
77 | assert.isTrue(StyleAPI.moduleName(StyleAPI.startsWith("foo"))(imported));
78 | });
79 |
80 | it("should not match module names by prefix", () => {
81 | const imported: IImport = stubImport({moduleName: "foo/bar"});
82 | assert.isFalse(StyleAPI.moduleName(StyleAPI.startsWith("baz"))(imported));
83 | });
84 |
85 | it("should match installed modules", () => {
86 | const imported: IImport = stubImport({moduleName: "typescript"});
87 | assert.isTrue(StyleAPI.isInstalledModule(__filename)(imported))
88 | });
89 |
90 | it("should match relative modules as non-installed modules", () => {
91 | const imported: IImport = stubImport({moduleName: `./${__filename}`});
92 | assert.isFalse(StyleAPI.isInstalledModule(__filename)(imported))
93 | });
94 |
95 | it("should match relative non-resolvable modules as non-installed modules", () => {
96 | const imported: IImport = stubImport({moduleName: `./foo`});
97 | assert.isFalse(StyleAPI.isInstalledModule(__filename)(imported))
98 | });
99 |
100 | it("should match absolute non-resolvable modules as non-installed modules", () => {
101 | const imported: IImport = stubImport({moduleName: `foo`});
102 | assert.isFalse(StyleAPI.isInstalledModule(__filename)(imported))
103 | });
104 | });
105 |
--------------------------------------------------------------------------------
/packages/import-sort/test/style/issues.ts:
--------------------------------------------------------------------------------
1 | import "mocha";
2 | import {assert} from "chai";
3 | import {IImport} from "import-sort-parser";
4 | import stubImport from "../stubImport";
5 |
6 | import StyleAPI from "../../src/style/StyleAPI";
7 |
8 | describe("dotSegmentCount does not account for dots in filename (issue #36) ", () => {
9 | it("should not count file extension dots", () => {
10 | // Segment count 1
11 | let firstImport: IImport = stubImport({moduleName: "./a.a"});
12 |
13 | // Segment count 1
14 | let secondImport: IImport = stubImport({moduleName: "./b"});
15 |
16 | assert.equal(StyleAPI.dotSegmentCount(firstImport, secondImport), 0);
17 |
18 | // Segment count 3
19 | firstImport = stubImport({moduleName: "../aa.aa/./a.a"});
20 |
21 | // Segment count 2
22 | secondImport = stubImport({moduleName: "../b"});
23 |
24 | // firstImport should be before secondImport
25 | assert.equal(StyleAPI.dotSegmentCount(firstImport, secondImport), -1);
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/packages/import-sort/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.settings.json",
3 |
4 | "compilerOptions": {
5 | "outDir": "lib",
6 | "rootDir": "src"
7 | },
8 |
9 | "include": ["src"],
10 |
11 | "references": [
12 | {"path": "../import-sort-parser"},
13 | {"path": "../import-sort-style"}
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/packages/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": [],
3 | "references": [
4 | {"path": "import-sort"},
5 | {"path": "import-sort-cli"},
6 | {"path": "import-sort-config"},
7 | {"path": "import-sort-parser"},
8 | {"path": "import-sort-parser-babylon"},
9 | {"path": "import-sort-parser-typescript"},
10 | {"path": "import-sort-style"},
11 | {"path": "import-sort-style-eslint"},
12 | {"path": "import-sort-style-module"},
13 | {"path": "import-sort-style-renke"}
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/packages/tsconfig.settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 |
4 | "compilerOptions": {
5 | "target": "es6",
6 |
7 | "declaration": true,
8 | "declarationMap": true,
9 |
10 | "module": "commonjs",
11 | "moduleResolution": "node",
12 |
13 | "strictNullChecks": true,
14 |
15 | "baseUrl": "./packages",
16 | "composite": true,
17 |
18 | "types": ["node"],
19 |
20 | "lib": ["es2017"]
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/scripts/publish-atom-import-sort.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | /**
3 | *
4 | * Copied from https://github.com/traveloka/javascript/blob/master/scripts/publish-atom-linter
5 | *
6 | * HACK: APM can only be published via github repository. Because we use monorepo
7 | * approach, we can't run `apm publish` inside `atom-import-sort` directory.
8 | * The workaround for this problem is cloning placeholder repository for atom-import-sort,
9 | * copying all files inside atom-import-sort directory, commit those with version tag
10 | * specified in lerna.json, then force-push to repository then run `apm publish`
11 | */
12 | const path = require('path');
13 | const exec = require('child_process').execSync;
14 | const version = require('../lerna.json').version;
15 |
16 | const insidePlaceholderRepo = {
17 | cwd: path.join(process.cwd(), 'atom-import-sort-placeholder'),
18 | };
19 |
20 | exec('git clone git@github.com:renke/atom-import-sort.git atom-import-sort-placeholder');
21 | exec('rm -rf "atom-import-sort-placeholder/.*" 2> /dev/null');
22 | exec('cp -R packages/atom-import-sort/. atom-import-sort-placeholder/');
23 | exec('cd atom-import-sort-placeholder');
24 | exec('git add -A', insidePlaceholderRepo);
25 | exec(`git commit -m "v${version}"`, insidePlaceholderRepo);
26 | exec(`git tag "v${version}"`, insidePlaceholderRepo);
27 | exec('git push origin master --follow-tags --force', insidePlaceholderRepo);
28 | exec('git push origin master --tags --force', insidePlaceholderRepo);
29 | exec(`apm publish --tag "v${version}"`, insidePlaceholderRepo);
30 | exec('rm -rf atom-import-sort-placeholder');
31 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./packages/tsconfig.settings.json"
3 | }
4 |
--------------------------------------------------------------------------------