├── .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 | ![atom-import-sort in action](https://raw.githubusercontent.com/renke/import-sort/master/packages/atom-import-sort/doc/atom-import-sort.gif) 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 | --------------------------------------------------------------------------------