├── .prettierrc ├── .gitignore ├── lib ├── index.js └── rules │ └── sort-destructure-keys.js ├── .github ├── dependabot.yml └── workflows │ └── main.yml ├── docs └── rules │ └── sort-destructure-keys.md ├── LICENSE ├── package.json ├── README.md └── tests └── lib └── rules └── sort-destructure-keys.js /.prettierrc: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | module.exports.rules = { 2 | "sort-destructure-keys": require("./rules/sort-destructure-keys"), 3 | }; 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "13:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /docs/rules/sort-destructure-keys.md: -------------------------------------------------------------------------------- 1 | # require object destructure keys to be sorted (sort-destructure-keys) 2 | 3 | Keys in an object pattern should be sorted in alphabetical order. The exception 4 | being when any of those keys have a default value equal to previously 5 | destructured key. 6 | 7 | ## Rule Details 8 | 9 | Examples of **incorrect** code for this rule: 10 | 11 | ```js 12 | const {b, a} = someObj; 13 | ``` 14 | 15 | Examples of **correct** code for this rule: 16 | 17 | ```js 18 | const {a, b} = someObj; 19 | 20 | const {b, a = b} = someObj; 21 | ``` 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2018, Michael Hadley 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: push 3 | jobs: 4 | test: 5 | runs-on: ubuntu-latest 6 | strategy: 7 | matrix: 8 | include: 9 | - node: 20 10 | eslint: 9 11 | - node: 18 12 | eslint: 8 13 | - node: 12 14 | eslint: 5 15 | name: node@${{ matrix.node }} and eslint@${{ matrix.eslint }} 16 | steps: 17 | - uses: actions/checkout@v3 18 | - name: Setup node 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: ${{ matrix.node }} 22 | - run: npm ci 23 | - run: npm install eslint@${{ matrix.eslint }} 24 | - run: npm test 25 | 26 | format: 27 | runs-on: ubuntu-latest 28 | name: Check Formatting 29 | steps: 30 | - uses: actions/checkout@v3 31 | - name: Setup node 32 | uses: actions/setup-node@v3 33 | with: 34 | node-version: 18 35 | - run: npm ci 36 | - run: npm run prettier 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-plugin-sort-destructure-keys", 3 | "version": "2.0.0", 4 | "description": "require object destructure key to be sorted", 5 | "repository": "https://github.com/mthadley/eslint-plugin-sort-destructure-keys", 6 | "keywords": [ 7 | "eslint", 8 | "eslintplugin", 9 | "eslint-plugin" 10 | ], 11 | "author": "Michael Hadley", 12 | "main": "lib/index.js", 13 | "scripts": { 14 | "test": "mocha tests --recursive", 15 | "prettier": "prettier --check \"{lib,tests}/**/*.js\"", 16 | "format": "prettier --write \"{lib,tests}/**/*.js\"" 17 | }, 18 | "peerDependencies": { 19 | "eslint": "5 - 9" 20 | }, 21 | "dependencies": { 22 | "natural-compare-lite": "^1.4.0" 23 | }, 24 | "devDependencies": { 25 | "@babel/core": "^7.15.8", 26 | "@babel/eslint-parser": "^7.15.8", 27 | "eslint": "^8.37.0", 28 | "mocha": "^10.0.0", 29 | "prettier": "^3.2.5" 30 | }, 31 | "engines": { 32 | "node": ">=12" 33 | }, 34 | "license": "ISC", 35 | "files": [ 36 | "lib", 37 | "README.md", 38 | "LICENSE", 39 | "docs" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eslint-plugin-sort-destructure-keys 2 | 3 | require object destructure key to be sorted 4 | 5 | ## Installation 6 | 7 | You'll first need to install [ESLint](http://eslint.org): 8 | 9 | ``` 10 | $ npm i eslint --save-dev 11 | ``` 12 | 13 | Next, install `eslint-plugin-sort-destructure-keys`: 14 | 15 | ``` 16 | $ npm install eslint-plugin-sort-destructure-keys --save-dev 17 | ``` 18 | 19 | **Note:** If you installed ESLint globally (using the `-g` flag) then you must also install `eslint-plugin-sort-destructure-keys` globally. 20 | 21 | ## Usage 22 | 23 | Add `sort-destructure-keys` to the plugins section of your `.eslintrc` configuration file. You can omit the `eslint-plugin-` prefix: 24 | 25 | ```json 26 | { 27 | "plugins": ["sort-destructure-keys"] 28 | } 29 | ``` 30 | 31 | Then configure the rule under the rules section. 32 | 33 | ```json 34 | { 35 | "rules": { 36 | "sort-destructure-keys/sort-destructure-keys": 2 37 | } 38 | } 39 | ``` 40 | 41 | ## Rule Options 42 | 43 | ```json 44 | { 45 | "sort-destructure-keys/sort-destructure-keys": [2, { "caseSensitive": false }] 46 | } 47 | ``` 48 | 49 | ### `caseSensitive` 50 | 51 | When `true` the rule will enforce properties to be in case-sensitive order. Default is `true`. 52 | 53 | Example of **incorrect** code for the `{"caseSensitive": false}` option: 54 | 55 | ```js 56 | let { B, a, c } = obj; 57 | ``` 58 | 59 | Example of **correct** code for the `{"caseSensitive": false}` option: 60 | 61 | ```js 62 | let { a, B, c } = obj; 63 | ``` 64 | 65 | Example of **incorrect** code for the `{"caseSensitive": true}` option: 66 | 67 | ```js 68 | let { a, B, c } = obj; 69 | ``` 70 | 71 | Example of **correct** code for the `{"caseSensitive": true}` option: 72 | 73 | ```js 74 | let { B, a, c } = obj; 75 | ``` 76 | 77 | ## Changelog 78 | 79 | ### `2.0.0` 80 | 81 | - Drop support for ESLint versions older than 5. ([#269]) 82 | 83 | [#269]: https://github.com/mthadley/eslint-plugin-sort-destructure-keys/pull/269 84 | 85 | ### `1.6.0` 86 | 87 | - Add compatibility for ESLint 9. ([#267], by [ptb]) 88 | 89 | [#267]: https://github.com/mthadley/eslint-plugin-sort-destructure-keys/pull/267 90 | [ptb]: https://github.com/ptb 91 | 92 | ### `1.5.0` 93 | 94 | - Allow sorting properties with defaults when the default doesn't reference 95 | other properties ([#215], by [ianobermiller]). 96 | 97 | [#215]: https://github.com/mthadley/eslint-plugin-sort-destructure-keys/pull/215 98 | [ianobermiller]: https://github.com/ianobermiller 99 | 100 | ### `1.4.0` 101 | 102 | - Add compatibility with eslint 8. ([#123]) 103 | 104 | [#123]: https://github.com/mthadley/eslint-plugin-sort-destructure-keys/pull/123 105 | 106 | ### `1.3.5` 107 | 108 | - Add `^7.0.0` to eslint peer dependency. ([#53], by [dsernst]) 109 | 110 | [#53]: https://github.com/mthadley/eslint-plugin-sort-destructure-keys/pull/53 111 | [dsernst]: https://github.com/dsernst 112 | 113 | ### `1.3.4` 114 | 115 | - Fixes TypeError issue with multiple property expressions. ([#20]) 116 | 117 | [#20]: https://github.com/mthadley/eslint-plugin-sort-destructure-keys/issues/20 118 | 119 | ### `1.3.3` 120 | 121 | - Add `6.0.0` to eslint peer dependency. ([#21], by [@7rulnik]) 122 | 123 | [#21]: https://github.com/mthadley/eslint-plugin-sort-destructure-keys/pull/21 124 | [@7rulnik]: https://github.com/7rulnik 125 | 126 | ### `1.3.2` 127 | 128 | - Fix bug where computed properties were causing the rule to throw errors. ([#15], thanks [@TSMMark]!) 129 | 130 | [#15]: https://github.com/mthadley/eslint-plugin-sort-destructure-keys/issues/15 131 | [@tsmmark]: https://github.com/TSMMark 132 | 133 | ### `1.3.1` 134 | 135 | - Fix bug with rest properties being sorted incorrectly. ([#11], [#12], thanks [@briandastous] and [@njdancer]!) 136 | 137 | [#11]: https://github.com/mthadley/eslint-plugin-sort-destructure-keys/issues/11 138 | [@briandastous]: https://github.com/briandastous 139 | [#12]: https://github.com/mthadley/eslint-plugin-sort-destructure-keys/pull/12 140 | [@njdancer]: https://github.com/njdancer 141 | 142 | ### `1.3.0` 143 | 144 | - Add support for `--fix` eslint cli flag 145 | 146 | ### `1.2.0` 147 | 148 | - Add peer dependency support for eslint `^5.0.0` 149 | 150 | ### `1.1.0` 151 | 152 | - Add `caseSensitive` option ([#1] by [@bsonntag]) 153 | 154 | [#1]: https://github.com/mthadley/eslint-plugin-sort-destructure-keys/pull/1 155 | [@bsonntag]: https://github.com/bsonntag 156 | -------------------------------------------------------------------------------- /lib/rules/sort-destructure-keys.js: -------------------------------------------------------------------------------- 1 | const naturalCompare = require("natural-compare-lite"); 2 | 3 | /** 4 | * Get's the "name" of the node, which could be an Identifier, 5 | * StringLiteral, or NumberLiteral, etc. 6 | * 7 | * This returned string is used to sort nodes of the same type. 8 | */ 9 | function getNodeName(node) { 10 | switch (node.type) { 11 | case "Property": 12 | return getNodeName(node.key); 13 | case "Identifier": 14 | return node.name; 15 | case "Literal": 16 | return node.value.toString(); 17 | case "RestElement": 18 | case "RestProperty": 19 | case "ExperimentalRestProperty": 20 | return node.argument.name; 21 | default: 22 | return node.type; 23 | } 24 | } 25 | 26 | /** 27 | * Sort priority for node types. 28 | */ 29 | const NODE_TYPE_SORT_ORDER = { 30 | /* ObjectPattern children */ 31 | Property: 1, 32 | RestElement: 99, 33 | RestProperty: 99, 34 | ExperimentalRestProperty: 99, 35 | 36 | /* Property keys */ 37 | Identifier: 1, 38 | Literal: 1, 39 | TemplateLiteral: 2, 40 | ComputedProperty: 2, 41 | }; 42 | 43 | const SORT_ORDER_DEFAULT = 98; 44 | 45 | function getSortOrder(node) { 46 | return isComputedProperty(node) 47 | ? NODE_TYPE_SORT_ORDER.ComputedProperty 48 | : NODE_TYPE_SORT_ORDER[node.type] || SORT_ORDER_DEFAULT; 49 | } 50 | 51 | /** 52 | * Returns true if the node is a "real", computed property. We don't consider 53 | * computed properties with a literal value to be a "real" computed property. This 54 | * is useful since that means they can be sorted. 55 | */ 56 | function isComputedProperty(node) { 57 | return node.computed && node.key.type !== "Literal"; 58 | } 59 | 60 | /** 61 | * Returns true if any references to this ID are within the objectPatternNode 62 | */ 63 | function isReferencedByOtherProperties(scope, objectPatternNode, id) { 64 | for (const variable of scope.variables) { 65 | if (variable.name !== id.name) { 66 | continue; 67 | } 68 | 69 | for (const reference of variable.references) { 70 | if (reference.identifier === id) { 71 | continue; 72 | } 73 | 74 | let current = reference.identifier; 75 | while (current) { 76 | if (current === objectPatternNode) { 77 | return true; 78 | } 79 | 80 | current = current.parent; 81 | } 82 | } 83 | 84 | return false; 85 | } 86 | } 87 | 88 | /** 89 | * Returns whether or not a node is safe to be sorted. 90 | */ 91 | function shouldCheck(scope, objectPatternNode, node) { 92 | if (node.type !== "Property") { 93 | return true; 94 | } 95 | 96 | switch (node.value.type) { 97 | case "ObjectPattern": 98 | return node.value.properties.every((propertyNode) => 99 | shouldCheck(scope, objectPatternNode, propertyNode), 100 | ); 101 | case "ArrayPattern": 102 | // Fake the element as a property for simplicity 103 | return node.value.elements.every( 104 | (element) => 105 | !element || 106 | shouldCheck(scope, objectPatternNode, { 107 | type: "Property", 108 | value: element, 109 | }), 110 | ); 111 | case "AssignmentPattern": 112 | if (node.value.left.type === "Identifier") { 113 | return !isReferencedByOtherProperties( 114 | scope, 115 | objectPatternNode, 116 | node.value.left, 117 | ); 118 | } 119 | return true; 120 | case "Identifier": 121 | return !isReferencedByOtherProperties( 122 | scope, 123 | objectPatternNode, 124 | node.value, 125 | ); 126 | default: 127 | return true; 128 | } 129 | } 130 | 131 | /** 132 | * Returns a function that will sort two nodes found in an `ObjectPattern`. 133 | * 134 | * TODO: Maybe it makes sense to do a topological sort here based on identifiers? 135 | * Ideally we wouldn't need to arbitrarily skip sorting nodes because we are worried 136 | * about breaking the code. 137 | */ 138 | function createSorter(caseSensitive) { 139 | const sortName = (a) => (caseSensitive ? a : a.toLowerCase()); 140 | 141 | return (a, b) => { 142 | // When we have different node "types" 143 | const nodeResult = getSortOrder(a) - getSortOrder(b); 144 | if (nodeResult !== 0) return nodeResult; 145 | 146 | // When the keys have different "types" 147 | const keyResult = getSortOrder(a.key) - getSortOrder(b.key); 148 | if (keyResult !== 0) return keyResult; 149 | 150 | return naturalCompare(sortName(getNodeName(a)), sortName(getNodeName(b))); 151 | }; 152 | } 153 | 154 | /** 155 | * Creates a "fixer" function to be used by `--fix`. 156 | */ 157 | function createFix({ context, fixer, node, sorter }) { 158 | const sourceCode = context.getSourceCode(); 159 | const sourceText = sourceCode.getText(); 160 | 161 | const sorted = node.properties.concat().sort(sorter); 162 | 163 | const newText = sorted 164 | .map((child, i) => { 165 | const textAfter = 166 | i === sorted.length - 1 167 | ? // If it's the last item, there's no text after to append. 168 | "" 169 | : // Otherwise, we need to grab the text after the original node. 170 | sourceText.slice( 171 | node.properties[i].range[1], // End index of the current node . 172 | node.properties[i + 1].range[0], // Start index of the next node. 173 | ); 174 | 175 | return sourceCode.getText(child) + textAfter; 176 | }) 177 | .join(""); 178 | 179 | return fixer.replaceTextRange( 180 | [ 181 | node.properties[0].range[0], // Start index of the first node. 182 | node.properties[node.properties.length - 1].range[1], // End index of the last node. 183 | ], 184 | newText, 185 | ); 186 | } 187 | 188 | module.exports = { 189 | meta: { 190 | docs: { 191 | description: "require object destructure keys to be sorted", 192 | category: "Stylistic Issues", 193 | recommended: false, 194 | }, 195 | fixable: "code", 196 | messages: { 197 | sort: `Expected object keys to be in sorted order. Expected {{first}} to be before {{second}}.`, 198 | }, 199 | schema: [ 200 | { 201 | type: "object", 202 | properties: { 203 | caseSensitive: { 204 | type: "boolean", 205 | }, 206 | }, 207 | additionalProperties: false, 208 | }, 209 | ], 210 | }, 211 | 212 | create(context) { 213 | const { sourceCode } = context; 214 | const options = context.options[0] || {}; 215 | const { caseSensitive = true } = options; 216 | const sorter = createSorter(caseSensitive); 217 | 218 | return { 219 | ObjectPattern(objectPatternNode) { 220 | const scope = 221 | sourceCode && sourceCode.getScope 222 | ? sourceCode.getScope(objectPatternNode) 223 | : context.getScope(); 224 | 225 | /* 226 | * If the node is more complex than just basic destructuring 227 | * with literal defaults, we just skip it. If some values use 228 | * previous values as defaults, then we cannot simply sort them. 229 | */ 230 | if ( 231 | !objectPatternNode.properties.every((node) => 232 | shouldCheck(scope, objectPatternNode, node), 233 | ) 234 | ) { 235 | return; 236 | } 237 | 238 | let prevNode = null; 239 | 240 | for (const nextNode of objectPatternNode.properties) { 241 | if (prevNode && sorter(prevNode, nextNode) > 0) { 242 | context.report({ 243 | node: nextNode, 244 | messageId: "sort", 245 | data: { 246 | first: getNodeName(nextNode), 247 | second: getNodeName(prevNode), 248 | }, 249 | fix: (fixer) => 250 | createFix({ 251 | context, 252 | caseSensitive, 253 | fixer, 254 | node: objectPatternNode, 255 | sorter, 256 | }), 257 | }); 258 | 259 | break; 260 | } 261 | 262 | prevNode = nextNode; 263 | } 264 | }, 265 | }; 266 | }, 267 | }; 268 | -------------------------------------------------------------------------------- /tests/lib/rules/sort-destructure-keys.js: -------------------------------------------------------------------------------- 1 | const assert = require("assert"); 2 | const rule = require("../../../lib/rules/sort-destructure-keys"); 3 | const eslint = require("eslint"); 4 | 5 | function eslintMajorVersion() { 6 | const version = eslint.CLIEngine 7 | ? eslint.CLIEngine.version 8 | : eslint.ESLint.version; 9 | const match = version.match(/^(?\d+)\.\d+\.\d+$/); 10 | assert.ok(match, "ESLint `version` must have a major version number."); 11 | 12 | return Number(match.groups.major); 13 | } 14 | 15 | function msg(second, first) { 16 | return { 17 | messageId: "sort", 18 | data: { first, second }, 19 | }; 20 | } 21 | 22 | function just(...args) { 23 | return [msg(...args)]; 24 | } 25 | 26 | function testsWithParser(parser, parserOptions = {}) { 27 | describe(`with parser: ${parser}`, () => { 28 | parserOptions = { 29 | ...parserOptions, 30 | ecmaVersion: 2018, 31 | }; 32 | 33 | const ruleTester = new eslint.RuleTester( 34 | eslintMajorVersion() >= 9 35 | ? { 36 | languageOptions: { 37 | parser: require(parser), 38 | parserOptions, 39 | }, 40 | } 41 | : { 42 | parser: require.resolve(parser), 43 | parserOptions, 44 | }, 45 | ); 46 | 47 | const testsFor = (name, ...args) => ruleTester.run(name, rule, ...args); 48 | 49 | testsFor("basic sorting", { 50 | valid: [ 51 | ` 52 | const { 53 | a, 54 | b 55 | } = someObj; 56 | `, 57 | "const {a, b} = someObj;", 58 | "const {a, b: c} = someObj;", 59 | "const {aBc, abd} = someObj;", 60 | "const {a: foo, b} = someObj;", 61 | "const func = ({a, b}) => a + b;", 62 | ], 63 | invalid: [ 64 | { 65 | code: "const {b, a} = someObj;", 66 | errors: just("b", "a"), 67 | output: "const {a, b} = someObj;", 68 | }, 69 | { 70 | code: "const {a, c, b} = someObj;", 71 | errors: just("c", "b"), 72 | output: "const {a, b, c} = someObj;", 73 | }, 74 | { 75 | code: ` 76 | const { 77 | b, 78 | a = 3, 79 | c 80 | } = someObj; 81 | `, 82 | errors: just("b", "a"), 83 | output: ` 84 | const { 85 | a = 3, 86 | b, 87 | c 88 | } = someObj; 89 | `, 90 | }, 91 | { 92 | code: "const func = ({b, a}) => a + b;", 93 | errors: just("b", "a"), 94 | output: "const func = ({a, b}) => a + b;", 95 | }, 96 | ], 97 | }); 98 | 99 | testsFor("rest properties", { 100 | valid: [ 101 | "const {a, ...other} = someObj;", 102 | "const {a, b, ...other} = someObj;", 103 | "const {...other} = someObj;", 104 | ], 105 | invalid: [ 106 | { 107 | code: "const {b, a, ...rest} = someObj;", 108 | errors: just("b", "a"), 109 | output: "const {a, b, ...rest} = someObj;", 110 | }, 111 | { 112 | code: ` 113 | function foo({ 114 | b = false, 115 | a, 116 | ...rest 117 | }) {} 118 | `, 119 | errors: just("b", "a"), 120 | output: ` 121 | function foo({ 122 | a, 123 | b = false, 124 | ...rest 125 | }) {} 126 | `, 127 | }, 128 | ], 129 | }); 130 | 131 | testsFor("key literals", { 132 | valid: [ 133 | "const {1: a, 2: b} = someObj;", 134 | "const {'a': a, 'b': b} = someObj;", 135 | ], 136 | invalid: [ 137 | { 138 | code: "const {2: b, 1: a} = someObj;", 139 | errors: just("2", "1"), 140 | output: "const {1: a, 2: b} = someObj;", 141 | }, 142 | { 143 | code: "const {'b': b, 'a': a} = someObj;", 144 | errors: just("b", "a"), 145 | output: "const {'a': a, 'b': b} = someObj;", 146 | }, 147 | ], 148 | }); 149 | 150 | testsFor("default literals", { 151 | valid: [ 152 | "const {a = {}, b = {}} = someObj;", 153 | "const {a = 1, b = '2'} = someObj;", 154 | ], 155 | invalid: [ 156 | { 157 | code: "const {a, c, b = 2} = someObj;", 158 | errors: just("c", "b"), 159 | output: "const {a, b = 2, c} = someObj;", 160 | }, 161 | ], 162 | }); 163 | 164 | testsFor("default identifiers", { 165 | valid: [ 166 | "const {b, a = b} = someObj;", 167 | "const {b, a = foo(b)} = someObj;", 168 | "const {b, ['a']: {x = b}} = someObj;", 169 | "const {a, c: {e, d = e}, b} = someObj;", 170 | `const {c, b, d = 'foo', a = d} = someObj;`, 171 | `const {c, b, d: { e }, a = e} = someObj;`, 172 | `const {c, b, d: [e], a = e} = someObj;`, 173 | `const {c, b, d: [, e], a = e} = someObj;`, 174 | `const {c, b, d: e, a = e} = someObj;`, 175 | ], 176 | invalid: [ 177 | { 178 | code: "const {a, c: {e, d}, b = a} = someObj;", 179 | errors: just("e", "d"), 180 | output: "const {a, c: {d, e}, b = a} = someObj;", 181 | }, 182 | { 183 | code: "const {a, c, b = 3} = someObj;", 184 | errors: just("c", "b"), 185 | output: "const {a, b = 3, c} = someObj;", 186 | }, 187 | // Allow identifiers or expressions, as long as they aren't the other properties 188 | { 189 | code: "const {a, c, b = outOfScope} = someObj;", 190 | errors: just("c", "b"), 191 | output: "const {a, b = outOfScope, c} = someObj;", 192 | }, 193 | ], 194 | }); 195 | 196 | testsFor("nested object patterns", { 197 | valid: ["const {a: {b, c}, d: {e, f: {g}}} = someObj;"], 198 | invalid: [ 199 | { 200 | code: "const {a, b, c: {e, d}} = someObj;", 201 | errors: just("e", "d"), 202 | output: "const {a, b, c: {d, e}} = someObj;", 203 | }, 204 | { 205 | code: "const {a, c: {e, d}, b} = someObj;", 206 | errors: [msg("e", "d"), msg("c", "b")], 207 | output: "const {a, b, c: {e, d}} = someObj;", 208 | }, 209 | { 210 | code: ` 211 | const { 212 | a, 213 | b: { 214 | e, 215 | d 216 | }, 217 | c 218 | } = someObj; 219 | `, 220 | errors: just("e", "d"), 221 | output: ` 222 | const { 223 | a, 224 | b: { 225 | d, 226 | e 227 | }, 228 | c 229 | } = someObj; 230 | `, 231 | }, 232 | ], 233 | }); 234 | 235 | testsFor("computed property literals", { 236 | valid: [ 237 | "const {a, ['b']: x} = someObj;", 238 | "const {['a']: {c: d}, b} = someObj;", 239 | ], 240 | invalid: [ 241 | { 242 | code: "const {b, ['a']: x, z} = someObj;", 243 | errors: just("b", "a"), 244 | output: "const {['a']: x, b, z} = someObj;", 245 | }, 246 | ], 247 | }); 248 | 249 | testsFor("computed properties", { 250 | valid: [ 251 | "const {[foo]: bar, [one]: two} = someObj;", 252 | "const {a, [`b${foo}`]: x} = someObj;", 253 | ` 254 | const { 255 | a, 256 | b, 257 | ['c']: x, 258 | ['d']: y, 259 | [\`\${e}foo\`]: z 260 | } = someObj; 261 | `, 262 | ` 263 | const { 264 | a, 265 | [\`\${e}foo\`]: y, 266 | [\`\${d}foo\`]: z 267 | } = someObj; 268 | `, 269 | ` 270 | const { 271 | a, 272 | [\`\${d}foo\`]: z, 273 | [\`\${e}foo\`]: y 274 | } = someObj; 275 | `, 276 | ` 277 | const { 278 | [a]: z, 279 | [b]: y 280 | } = someObj; 281 | `, 282 | ` 283 | const { 284 | [a.foo]: z, 285 | [b.bar]: y 286 | } = someObj; 287 | `, 288 | ` 289 | const { 290 | [b.foo]: z, 291 | [a.bar]: y 292 | } = someObj; 293 | `, 294 | ], 295 | invalid: [ 296 | { 297 | code: "const {[b]: c, a} = someObj;", 298 | errors: just("b", "a"), 299 | output: "const {a, [b]: c} = someObj;", 300 | }, 301 | { 302 | code: ` 303 | const { 304 | a, 305 | b, 306 | ['d']: y, 307 | ['c']: x, 308 | [\`\${e}foo\`]: z 309 | } = someObj; 310 | `, 311 | errors: just("d", "c"), 312 | output: ` 313 | const { 314 | a, 315 | b, 316 | ['c']: x, 317 | ['d']: y, 318 | [\`\${e}foo\`]: z 319 | } = someObj; 320 | `, 321 | }, 322 | ], 323 | }); 324 | 325 | describe("options", () => { 326 | testsFor("caseSensitive", { 327 | valid: [ 328 | { 329 | code: "const {a, b} = someObj;", 330 | options: [{ caseSensitive: true }], 331 | }, 332 | { 333 | code: "const {B, a} = someObj;", 334 | options: [{ caseSensitive: true }], 335 | }, 336 | { 337 | code: "const {aCc, abb} = someObj;", 338 | options: [{ caseSensitive: true }], 339 | }, 340 | ], 341 | invalid: [ 342 | { 343 | code: "const {b, a} = someObj;", 344 | errors: just("b", "a"), 345 | output: "const {a, b} = someObj;", 346 | options: [{ caseSensitive: true }], 347 | }, 348 | { 349 | code: "const {a, B} = someObj;", 350 | errors: just("a", "B"), 351 | output: "const {B, a} = someObj;", 352 | options: [{ caseSensitive: true }], 353 | }, 354 | { 355 | code: "const {abc, aBd} = someObj;", 356 | errors: just("abc", "aBd"), 357 | output: "const {aBd, abc} = someObj;", 358 | options: [{ caseSensitive: true }], 359 | }, 360 | ], 361 | }); 362 | }); 363 | }); 364 | } 365 | 366 | describe("sort-destructure-keys", () => { 367 | testsWithParser("espree"); 368 | testsWithParser("@babel/eslint-parser", { requireConfigFile: false }); 369 | }); 370 | --------------------------------------------------------------------------------