├── .babelrc ├── .circleci └── config.yml ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── LICENCE ├── README.md ├── index.js ├── package-lock.json ├── package.json └── test └── require-sort.test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/env", 5 | { 6 | "targets": [ 7 | "last 2 versions" 8 | ] 9 | } 10 | ] 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | working_directory: ~/eslint-plugin-require-sort 5 | docker: 6 | - image: circleci/node:latest 7 | steps: 8 | - checkout 9 | - restore_cache: 10 | key: npm-cache-v1-{{ checksum "package-lock.json" }} 11 | - run: 12 | name: Install Dependencies 13 | command: npm ci 14 | - save_cache: 15 | key: npm-cache-v1-{{ checksum "package-lock.json" }} 16 | paths: 17 | - /home/circleci/.npm 18 | - run: 19 | name: Run Tests 20 | command: npm run test 21 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | parser: 'babel-eslint', 5 | env: { es6: true }, 6 | parserOptions: { 7 | ecmaVersion: 2020 8 | }, 9 | plugins: ['eslint-plugin'], 10 | extends: [ 11 | '@extensionengine/eslint-config/base', 12 | 'plugin:eslint-plugin/recommended' 13 | ] 14 | }; 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode 3 | .DS_Store 4 | dist 5 | coverage 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": false, 3 | "semi": true, 4 | "singleQuote": true 5 | } 6 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Zdravko Ćurić 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eslint-plugin-require-sort 2 | 3 |

4 | 5 | 6 | 7 |

8 | 9 | - [Introduction](#introduction) 10 | - [Installation](#installation) 11 | - [Usage](#usage) 12 | - [Rule Details](#rule-details) 13 | - [Options](#options) 14 | - [Examples](#examples) 15 | - [Default settings](#default-settings) 16 | - [`ignoreCase`](#ignorecase) 17 | - [`ignoreDeclarationSort`](#ignoredeclarationsort) 18 | - [`ignorePropertySort`](#ignorepropertysort) 19 | - [`propertySyntaxSortOrder`](#propertysyntaxsortorder) 20 | - [Credits](#credits) 21 | - [Contributions](#contributions) 22 | - [License](#license) 23 | 24 | ## Introduction 25 | What is `require`? From latest [Node documentation](https://nodejs.org/api/modules.html) require is a `function`. As Node follows CommonJS module system, `require` function is the easiest way to include modules that exist in separate files - more on that [here](https://nodejs.org/en/knowledge/getting-started/what-is-require/). 26 | 27 | Difference between CommonJS and ES2015 modules is very nicely explained [in this short talk](https://www.youtube.com/watch?v=8O_H2JgV7EQ). 28 | 29 | [From AST point of view](https://astexplorer.net/#/gist/577afe7c245364a40a495051b8289508/9dc2d43629dd548417fb26b4c2aa9ae68578b7d1) `const foo = require('bar')` is a `VariableDeclaration` and `require` is `CallExpression`. And in this eslint plugin is treated as such. 30 | `foo` is in this case an `Identifier`. 31 | In case of destructuring `const { foo, bar } = require('baz')`, `foo` and `bar` are `properties` in `ObjectPattern`. 32 | 33 | This is important for nomenclature in the plugin, because it's not straight forward as in [`sort-imports`](https://eslint.org/docs/rules/sort-imports) rule provided by `eslint`. 34 | 35 | In this plugin all `Identifiers` are called `properties` to simplify things. The goal is to follow the AST as closely as possible. What `member` is to `sort-imports` rule, `properties` are to this plugin. 36 | 37 | Hopefully this all makes sense. 38 | 39 | ## Installation 40 | 41 | Install [ESLint](http://eslint.org): 42 | 43 | ``` 44 | $ npm install eslint --save-dev 45 | ``` 46 | 47 | Install `eslint-plugin-require-sort`: 48 | 49 | ``` 50 | $ npm install eslint-plugin-require-sort --save-dev 51 | ``` 52 | 53 | **Note:** If you installed ESLint globally (using the `-g` flag) then you must 54 | also install `eslint-plugin-require-sort` globally. 55 | 56 | ## Usage 57 | 58 | Add `require-sort` to the plugins and rules section of your `.eslintrc(.js|.json|.yaml)` configuration 59 | file: 60 | 61 | ```json 62 | { 63 | "plugins": ["require-sort"], 64 | "rules": { 65 | "require-sort/require-sort": "error" 66 | } 67 | } 68 | ``` 69 | 70 | ## Rule Details 71 | 72 | This rule checks all declarations and verifies that all are first sorted by the used property syntax and then alphabetically by the first property or alias name. 73 | 74 | The `--fix` option on the command line automatically fixes some problems reported by this rule: multiple properties on a single line are automatically sorted (e.g. `const { b, a } = require('foo')` is corrected to `const { a, b } = require('foo')`), but multiple lines are not reordered. 75 | 76 | Rule ignores `require` functions inside functions, `if` statements, etc. For example, 77 | ```js 78 | function foo() { 79 | const bar = require('baz'); 80 | } 81 | ``` 82 | will be ignored. Only top level variable declarations with `require` are considered. 83 | 84 | ## Options 85 | 86 | This rule accepts an object with its properties as 87 | 88 | * `ignoreCase` (default: `false`) 89 | * `ignoreDeclarationOrder` (default: `false`) 90 | * `ignorePropertySort` (default: `false`) 91 | * `propertySyntaxSortOrder` (default: `["none", "multiple", "single"]`); all items must be present in the array, but you can change the order. 92 | 93 | Default option settings are: 94 | 95 | ```json 96 | { 97 | "require-sort/require-sort": ["error", { 98 | "ignoreCase": false, 99 | "ignoreDeclarationSort": false, 100 | "ignorePropertySort": false, 101 | "propertySyntaxSortOrder": ["none", "multiple", "single"] 102 | }] 103 | } 104 | ``` 105 | 106 | ## Examples 107 | 108 | ### Default settings 109 | 110 | Examples of **correct** code for this rule when using default options: 111 | 112 | ```js 113 | /*eslint require-sort: "error"*/ 114 | const { alpha, beta } = require('alpha'); 115 | const { delta, gamma } = require('delta'); 116 | const a = require('baz'); 117 | const b = require('qux'); 118 | 119 | /*eslint require-sort: "error"*/ 120 | const a = require('foo'); 121 | const b = require('bar'); 122 | const c = require('baz'); 123 | 124 | /*eslint require-sort: "error"*/ 125 | const { a, b } = require('baz'); 126 | const c = require('qux'); 127 | 128 | /*eslint require-sort: "error"*/ 129 | const { a, b, c } = require('foo)' 130 | ``` 131 | 132 | Examples of **incorrect** code for this rule when using default options: 133 | 134 | ```js 135 | /*eslint require-sort: "error"*/ 136 | const b = require('foo'); 137 | const a = require('bar'); 138 | 139 | /*eslint require-sort: "error"*/ 140 | const a = require('foo'); 141 | const A = require('bar'); 142 | 143 | /*eslint require-sort: "error"*/ 144 | const { b, c } = require('foo'); 145 | const { a, b } = require('bar'); 146 | 147 | /*eslint require-sort: "error"*/ 148 | const a = require('foo'); 149 | const { b, c } = require('bar'); 150 | 151 | /*eslint require-sort: "error"*/ 152 | const a = require('foo'); 153 | 154 | /*eslint require-sort: "error"*/ 155 | const { b, a, c } = require('foo'); 156 | ``` 157 | 158 | ### `ignoreCase` 159 | 160 | When `true` the rule ignores the case-sensitivity of the declaration 161 | 162 | Examples of **incorrect** code for this rule with the `{ "ignoreCase": true }` option: 163 | 164 | ```js 165 | /*eslint require-sort: ["error", { "ignoreCase": true }]*/ 166 | 167 | const b = require('foo'); 168 | const a = require('bar'); 169 | ``` 170 | 171 | Examples of **correct** code for this rule with the `{ "ignoreCase": true }` option: 172 | 173 | ```js 174 | /*eslint require-sort: ["error", { "ignoreCase": true }]*/ 175 | 176 | const a = require('foo'); 177 | const B = require('bar'); 178 | const c = require('baz'); 179 | ``` 180 | 181 | Default is `false`. 182 | 183 | ### `ignoreDeclarationSort` 184 | 185 | Ignores the sorting of variable declarations with `require`. 186 | 187 | Examples of **incorrect** code for this rule with the default `{ "ignoreDeclarationSort": false }` option: 188 | 189 | ```js 190 | /*eslint require-sort: ["error", { "ignoreDeclarationSort": false }]*/ 191 | const b = require('foo'); 192 | const a = require('bar'); 193 | ``` 194 | 195 | Examples of **correct** code for this rule with the `{ "ignoreDeclarationSort": true }` option: 196 | 197 | ```js 198 | /*eslint require-sort: ["error", { "ignoreDeclarationSort": true }]*/ 199 | const a = require('foo'); 200 | const b = require('bar'); 201 | ``` 202 | 203 | ```js 204 | /*eslint require-sort: ["error", { "ignoreDeclarationSort": true }]*/ 205 | const b = require('foo'); 206 | const a = require('bar'); 207 | ``` 208 | 209 | Default is `false`. 210 | 211 | ### `ignorePropertySort` 212 | 213 | Ignores the property sorting within a `multiple` property in declaration. 214 | 215 | Examples of **incorrect** code for this rule with the default `{ "ignorePropertySort": false }` option: 216 | 217 | ```js 218 | /*eslint require-sort: ["error", { "ignorePropertySort": false }]*/ 219 | const { b, a, c } = require('foo'); 220 | ``` 221 | 222 | Examples of **correct** code for this rule with the `{ "ignorePropertySort": true }` option: 223 | 224 | ```js 225 | /*eslint require-sort: ["error", { "ignorePropertySort": true }]*/ 226 | const { b, a, c } = require('foo'); 227 | ``` 228 | 229 | Default is `false`. 230 | 231 | ### `propertySyntaxSortOrder` 232 | 233 | There are three different styles and the default property syntax sort order is: 234 | s. 235 | * `none` - just a require expression 236 | * `multiple` - require multiple properties. 237 | * `single` - require single property. 238 | 239 | Both options must be specified in the array, but you can customize their order. 240 | 241 | Examples of **correct** code for this rule with the `{ "propertySyntaxSortOrder": ["none", "single", "multiple"] }` option: 242 | 243 | ```js 244 | /*eslint require-sort: ["error", { "propertySyntaxSortOrder": ["none", "single", "multiple"] }]*/ 245 | require('bar'); 246 | const z = require('zoo'); 247 | const { a, b } = require('foo'); 248 | ``` 249 | 250 | Default is `["none", "multiple", "single"]`. 251 | 252 | ## Credits 253 | - This plugin is [inspired by `sort-imports` rule](https://github.com/eslint/eslint/blob/master/lib/rules/sort-imports.js). Credits to authors an maintainers of that rule. 254 | - [@vladimyr](https://github.com/vladimyr) who pointed me to AST explorer. 255 | - [@kronicker](https://github.com/kronicker) who said it that this won't work in some cases :stuck_out_tongue_winking_eye: 256 | 257 | ## Contributions 258 | - All contributions, suggestions are welcome. 259 | 260 | ## License 261 | 262 | MIT @ Zdravko Ćurić [(zcuric)](https://github.com/zcuric) 263 | 264 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | rules: { 5 | 'require-sort': { 6 | meta: { 7 | type: 'suggestion', 8 | docs: { 9 | description: 'enforce sorted require declarations within modules', 10 | category: 'ECMAScript 6', 11 | recommended: false, 12 | url: 'https://github.com/zcuric/eslint-plugin-require-sort' 13 | }, 14 | schema: [ 15 | { 16 | type: 'object', 17 | properties: { 18 | ignoreCase: { 19 | type: 'boolean', 20 | default: false 21 | }, 22 | propertySyntaxSortOrder: { 23 | type: 'array', 24 | items: { 25 | enum: ['none', 'multiple', 'single'] 26 | }, 27 | uniqueItems: true, 28 | minItems: 3, 29 | maxItems: 3 30 | }, 31 | ignoreDeclarationSort: { 32 | type: 'boolean', 33 | default: false 34 | }, 35 | ignorePropertySort: { 36 | type: 'boolean', 37 | default: false 38 | } 39 | }, 40 | additionalProperties: false 41 | } 42 | ], 43 | fixable: 'code' 44 | }, 45 | create(context) { 46 | const configuration = context.options[0] || {}; 47 | const { 48 | ignoreCase = false, 49 | ignoreDeclarationSort = false, 50 | ignorePropertySort = false, 51 | propertySyntaxSortOrder = ['none', 'multiple', 'single'] 52 | } = configuration; 53 | const sourceCode = context.getSourceCode(); 54 | const nodes = []; 55 | let previousNode = null; 56 | 57 | const handleDeclarationSort = node => { 58 | if (previousNode) { 59 | const currentIndex = getPropertySyntaxIndex(node); 60 | const previousIndex = getPropertySyntaxIndex(previousNode); 61 | /* 62 | * When the current declaration uses a different property syntax, 63 | * then check if the ordering is correct. 64 | * Otherwise, make a default string compare (like rule sort-vars to be consistent) 65 | * of the first used property name. 66 | */ 67 | if (currentIndex === previousIndex) { 68 | reportOnAlphabeticalSort(node, previousNode); 69 | } 70 | if (currentIndex < previousIndex) { 71 | reportOnExpectedSyntax(node, currentIndex, previousIndex); 72 | } 73 | } 74 | 75 | previousNode = node; 76 | }; 77 | 78 | const handlePropertySort = node => { 79 | if (isStaticRequire(node)) return; 80 | if (!node.declarations[0].id.properties) return; 81 | const properties = node.declarations[0].id.properties; 82 | const mergeText = (sourceText, property, index) => { 83 | let textAfterProperty = ''; 84 | if (index !== properties.length - 1) { 85 | textAfterProperty = sourceCode 86 | .getText() 87 | .slice( 88 | properties[index].range[1], 89 | properties[index + 1].range[0] 90 | ); 91 | } 92 | return ( 93 | sourceText + sourceCode.getText(property) + textAfterProperty 94 | ); 95 | }; 96 | const firstUnsortedIndex = properties 97 | .map(getSortableName) 98 | .findIndex((name, index, array) => array[index - 1] > name); 99 | 100 | const fix = ({ replaceTextRange }) => { 101 | // If there are comments in the property list, don't rearrange the properties. 102 | if (hasComments(properties)) return null; 103 | const range = [ 104 | properties[0].range[0], 105 | properties[properties.length - 1].range[1] 106 | ]; 107 | const text = [...properties].sort(sortByName).reduce(mergeText, ''); 108 | return replaceTextRange(range, text); 109 | }; 110 | 111 | if (firstUnsortedIndex === -1) return; 112 | const { value } = properties[firstUnsortedIndex]; 113 | const propertyName = isAssignmentPattern(value) 114 | ? value.left.name 115 | : value.name; 116 | 117 | context.report({ 118 | node: properties[firstUnsortedIndex], 119 | message: 120 | "Property '{{propertyName}}' of the require declaration should be sorted alphabetically.", 121 | data: { propertyName }, 122 | fix 123 | }); 124 | }; 125 | 126 | const isTopLevel = ({ parent }) => parent.type === 'Program'; 127 | const isStaticRequire = node => { 128 | if (node.type !== 'CallExpression') return false; 129 | return ( 130 | node.callee?.type === 'Identifier' && 131 | node.callee?.name === 'require' && 132 | node.arguments?.length === 1 133 | ); 134 | }; 135 | const isRequire = node => 136 | node.declarations[0]?.init?.callee?.name === 'require'; 137 | const isAssignmentPattern = node => node?.type === 'AssignmentPattern'; 138 | const hasObjectPattern = node => 139 | node.declarations[0]?.id?.type === 'ObjectPattern'; 140 | const hasMultipleProperties = node => 141 | node.declarations[0]?.id?.properties.length > 1; 142 | 143 | const hasComments = properties => 144 | properties.some(property => { 145 | const commentsBefore = sourceCode.getCommentsBefore(property); 146 | const commentsAfter = sourceCode.getCommentsAfter(property); 147 | return commentsBefore.length || commentsAfter.length; 148 | }); 149 | 150 | const getSortableName = ({ value }) => { 151 | const name = isAssignmentPattern(value) 152 | ? value.left.name 153 | : value.name; 154 | if (name) return ignoreCase ? name.toLowerCase() : name; 155 | return null; 156 | }; 157 | 158 | const sortByName = (propertyA, propertyB) => { 159 | const aName = getSortableName(propertyA); 160 | const bName = getSortableName(propertyB); 161 | return aName > bName ? 1 : -1; 162 | }; 163 | 164 | const getPropertySyntax = node => { 165 | if (isStaticRequire(node)) return 'none'; 166 | if (!hasObjectPattern(node) || !hasMultipleProperties(node)) { 167 | return 'single'; 168 | } 169 | return 'multiple'; 170 | }; 171 | 172 | const getPropertySyntaxIndex = node => 173 | propertySyntaxSortOrder.indexOf(getPropertySyntax(node)); 174 | 175 | const getDeclarationName = node => { 176 | if (isStaticRequire(node)) return node.arguments[0].value; 177 | if (!hasObjectPattern(node)) return node.declarations[0].id.name; 178 | const value = node.declarations[0].id.properties[0].value; 179 | return isAssignmentPattern(value) ? value.left.name : value.name; 180 | }; 181 | 182 | const reportOnAlphabeticalSort = (node, previousNode) => { 183 | let firstName = getDeclarationName(node); 184 | let previousName = getDeclarationName(previousNode); 185 | if (ignoreCase) { 186 | previousName = previousName && previousName.toLowerCase(); 187 | firstName = firstName && firstName.toLowerCase(); 188 | } 189 | if (previousName && firstName && firstName < previousName) { 190 | context.report({ 191 | node, 192 | message: 'Requires should be sorted alphabetically.' 193 | }); 194 | } 195 | }; 196 | 197 | const reportOnExpectedSyntax = (node, currentIndex, previousIndex) => { 198 | context.report({ 199 | node, 200 | message: 201 | "Expected '{{syntaxA}}' syntax before '{{syntaxB}}' syntax.", 202 | data: { 203 | syntaxA: propertySyntaxSortOrder[currentIndex], 204 | syntaxB: propertySyntaxSortOrder[previousIndex] 205 | } 206 | }); 207 | }; 208 | 209 | return { 210 | ExpressionStatement(node) { 211 | if (!isTopLevel(node)) return; 212 | if (!isStaticRequire(node.expression)) return; 213 | nodes.push(node.expression); 214 | }, 215 | VariableDeclaration(node) { 216 | if (!isTopLevel(node)) return; 217 | if (!isRequire(node)) return; 218 | nodes.push(node); 219 | }, 220 | 'Program:exit'() { 221 | if (!ignoreDeclarationSort) nodes.forEach(handleDeclarationSort); 222 | if (!ignorePropertySort) nodes.forEach(handlePropertySort); 223 | } 224 | }; 225 | } 226 | } 227 | } 228 | }; 229 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "publishConfig": { 3 | "access": "public" 4 | }, 5 | "name": "eslint-plugin-require-sort", 6 | "version": "1.3.0", 7 | "description": "ESlint plugin for sorting requires (CommonJS modules) alphabetically", 8 | "main": "dist/index.js", 9 | "scripts": { 10 | "test": "jest", 11 | "lint": "eslint .", 12 | "prebuild": "rimraf dist", 13 | "prepare": "npm run build", 14 | "build": "babel index.js -d dist", 15 | "release": "npx np" 16 | }, 17 | "files": [ 18 | "dist" 19 | ], 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/zcuric/eslint-plugin-require-sort" 23 | }, 24 | "author": "Zdravko Ćurić ", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/zcuric/eslint-plugin-require-sort/issues" 28 | }, 29 | "homepage": "https://github.com/zcuric/eslint-plugin-require-sort", 30 | "keywords": [ 31 | "commonjs", 32 | "eslint", 33 | "eslint-plugin", 34 | "eslint-plugin-sort-imports", 35 | "eslint-plugin-require-sort", 36 | "node", 37 | "require" 38 | ], 39 | "devDependencies": { 40 | "@babel/cli": "^7.10.5", 41 | "@babel/core": "^7.19.0", 42 | "@babel/preset-env": "^7.19.0", 43 | "@extensionengine/eslint-config": "^3.0.1", 44 | "babel-eslint": "^10.1.0", 45 | "babel-jest": "^29.0.3", 46 | "eslint": "^8.23.1", 47 | "eslint-config-semistandard": "^17.0.0", 48 | "eslint-config-standard": "^17.0.0", 49 | "eslint-plugin-eslint-plugin": "^5.0.6", 50 | "eslint-plugin-import": "^2.26.0", 51 | "eslint-plugin-node": "^11.1.0", 52 | "eslint-plugin-promise": "^6.0.1", 53 | "eslint-plugin-standard": "^5.0.0", 54 | "eslint-plugin-vue": "^9.4.0", 55 | "jest": "^29.0.3", 56 | "prettier": "^2.7.1", 57 | "rimraf": "^3.0.2" 58 | }, 59 | "jest": { 60 | "testPathIgnorePatterns": [ 61 | "dist", 62 | "/node_modules/" 63 | ] 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /test/require-sort.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const rule = require('../index.js').rules['require-sort']; 4 | const { RuleTester } = require('eslint'); 5 | 6 | const ruleTester = new RuleTester({ 7 | parserOptions: { ecmaVersion: 2020, sourceType: 'script' } 8 | }); 9 | 10 | const expectedError = { 11 | message: 'Requires should be sorted alphabetically.', 12 | type: 'VariableDeclaration' 13 | }; 14 | const ignoreCaseArgs = [{ ignoreCase: true }]; 15 | 16 | const test = ({ only = false, ...settings }) => { 17 | return { 18 | settings, 19 | only 20 | }; 21 | }; 22 | 23 | Object.defineProperty(test, 'only', { 24 | value: settings => test({ ...settings, only: true }) 25 | }); 26 | 27 | const getTests = (testCases = [], allSuites = true) => { 28 | if (!allSuites) return []; 29 | if (testCases.some(({ only }) => only === true)) { 30 | return testCases.reduce((acc, test) => { 31 | if (test.only === true) acc.push(test.settings); 32 | return acc; 33 | }, []); 34 | } 35 | return testCases.map(({ settings }) => ({ ...settings })); 36 | }; 37 | 38 | ruleTester.run('require-sort', rule, { 39 | valid: getTests([ 40 | // Sorts only top level 41 | test({ 42 | code: ` 43 | const a = require('bar'); 44 | const b = require('foo'); 45 | if (bar) { 46 | const c = require('baz'); 47 | } 48 | ` 49 | }), 50 | test({ 51 | code: ` 52 | require('baz'); 53 | const a = require('bar'); 54 | const { b: c = {} } = require('foo'); 55 | const d = 'foobar'; 56 | ` 57 | }), 58 | test({ 59 | code: ` 60 | const a = require('bar'); 61 | const b = require('foo'); 62 | 63 | if (bar) { 64 | const c = require('baz'); 65 | } 66 | ` 67 | }), 68 | // ignoreCase 69 | test({ 70 | code: ` 71 | const A = require('bar'); 72 | const a = require('foo'); 73 | ` 74 | }), 75 | test({ 76 | code: ` 77 | const { a } = require('foo'); 78 | const A = require('bar'); 79 | `, 80 | options: ignoreCaseArgs 81 | }), 82 | // propertySyntaxSortOrder 83 | test({ 84 | code: ` 85 | require('foo'); 86 | const { b, c } = require('baz'); 87 | const A = require('bar'); 88 | ` 89 | }), 90 | test({ 91 | code: ` 92 | require('foo'); 93 | const A = require('bar'); 94 | const { b, c } = require('baz'); 95 | require('foo', 'bar'); 96 | if (true) { 97 | require('baz'); 98 | } 99 | `, 100 | options: [ 101 | { 102 | propertySyntaxSortOrder: ['none', 'single', 'multiple'] 103 | } 104 | ] 105 | }), 106 | test({ 107 | code: ` 108 | const A = require('bar'); 109 | const { b, c } = require('baz'); 110 | require('foo'); 111 | `, 112 | options: [ 113 | { 114 | propertySyntaxSortOrder: ['single', 'multiple', 'none'] 115 | } 116 | ] 117 | }), 118 | test({ 119 | code: ` 120 | const { b, c } = require('baz'); 121 | require('foo'); 122 | const A = require('bar'); 123 | `, 124 | options: [ 125 | { 126 | propertySyntaxSortOrder: ['multiple', 'none', 'single'] 127 | } 128 | ] 129 | }), 130 | // ignoreDeclarationSort 131 | test({ 132 | code: ` 133 | const A = require('bar'); 134 | const a = require('foo'); 135 | ` 136 | }), 137 | test({ 138 | code: ` 139 | const z = require('bar'); 140 | const a = require('foo'); 141 | `, 142 | options: [ 143 | { 144 | ignoreDeclarationSort: true 145 | } 146 | ] 147 | }), 148 | // ignorePropertySort 149 | test({ 150 | code: ` 151 | const { a, b, c } = require('bar'); 152 | ` 153 | }), 154 | test({ 155 | code: ` 156 | const { a, b: { d: e }, c } = require('bar'); 157 | ` 158 | }), 159 | test({ 160 | code: ` 161 | const { b, c, a } = require('bar'); 162 | `, 163 | options: [ 164 | { 165 | ignorePropertySort: true 166 | } 167 | ] 168 | }) 169 | ]), 170 | invalid: getTests([ 171 | // ignoreCase 172 | test({ 173 | code: ` 174 | require('baz'); 175 | const a = require('foo'); 176 | const A = require('bar'); 177 | `, 178 | output: null, 179 | errors: [expectedError] 180 | }), 181 | test({ 182 | code: ` 183 | require('foo'); 184 | require('bar'); 185 | const a = require('foo'); 186 | const A = require('bar'); 187 | `, 188 | output: null, 189 | errors: [{ ...expectedError, type: 'CallExpression' }, expectedError] 190 | }), 191 | // propertySyntaxSortOrder 192 | test({ 193 | code: ` 194 | require('foo'); 195 | const A = require('bar'); 196 | const { Ba } = require('bar'); 197 | const { b, c } = require('baz'); 198 | `, 199 | options: [ 200 | { 201 | propertySyntaxSortOrder: ['none', 'multiple', 'single'], 202 | ignoreCase: true 203 | } 204 | ], 205 | errors: [ 206 | { 207 | message: "Expected 'multiple' syntax before 'single' syntax.", 208 | type: 'VariableDeclaration' 209 | } 210 | ] 211 | }), 212 | test({ 213 | code: ` 214 | const A = require('bar'); 215 | require('foo'); 216 | const { b, c } = require('baz'); 217 | `, 218 | options: [ 219 | { 220 | propertySyntaxSortOrder: ['none', 'multiple', 'single'] 221 | } 222 | ], 223 | errors: [ 224 | { 225 | message: "Expected 'none' syntax before 'single' syntax.", 226 | type: 'CallExpression' 227 | } 228 | ] 229 | }), 230 | // property order 231 | test({ 232 | code: ` 233 | const { b, a, d, c } = require('foo'); 234 | `, 235 | output: ` 236 | const { a, b, c, d } = require('foo'); 237 | `, 238 | errors: [ 239 | { 240 | message: 241 | "Property 'a' of the require declaration should be sorted alphabetically.", 242 | type: 'Property' 243 | } 244 | ] 245 | }), 246 | // property order with assignment 247 | test({ 248 | code: ` 249 | const { b, a = {}, d, c } = require('foo'); 250 | `, 251 | output: ` 252 | const { a = {}, b, c, d } = require('foo'); 253 | `, 254 | errors: [ 255 | { 256 | message: 257 | "Property 'a' of the require declaration should be sorted alphabetically.", 258 | type: 'Property' 259 | } 260 | ] 261 | }), 262 | // property order with aliases 263 | test({ 264 | code: ` 265 | const { b: z, c: g, d: e } = require('foo'); 266 | `, 267 | output: ` 268 | const { d: e, c: g, b: z } = require('foo'); 269 | `, 270 | errors: [ 271 | { 272 | message: 273 | "Property 'g' of the require declaration should be sorted alphabetically.", 274 | type: 'Property' 275 | } 276 | ] 277 | }), 278 | test({ 279 | code: ` 280 | const {zzzzz, /* comment */ aaaaa} = require('foo'); 281 | `, 282 | output: null, // not fixed due to comment 283 | errors: [ 284 | { 285 | message: 286 | "Property 'aaaaa' of the require declaration should be sorted alphabetically.", 287 | type: 'Property' 288 | } 289 | ] 290 | }) 291 | ]) 292 | }); 293 | --------------------------------------------------------------------------------