├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── .vscode └── launch.json ├── README.md ├── __tests__ └── lib │ └── rules │ └── sort-keys-fix.js ├── lib ├── index.js ├── rules │ └── sort-keys-fix.js └── util │ └── ast-utils.js ├── package.json └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['standard', 'plugin:prettier/recommended'], 4 | rules: { 5 | 'no-console': [process.env.NODE_ENV === 'production' ? 'error' : 0, { allow: ["warn", "error"] }], 6 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 0, 7 | 'no-var': 'warn', 8 | 'prefer-const': 'warn', 9 | 'no-else-return': 'error', 10 | 'vue/order-in-components': 0, 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .thumbs.db 3 | node_modules 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | package-lock.json 8 | 9 | # Editor directories and files 10 | .vs 11 | .idea 12 | *.suo 13 | *.ntvs* 14 | *.njsproj 15 | *.sln -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": false, 3 | "printWidth": 120, 4 | "tabWidth": 2, 5 | "singleQuote": true, 6 | "trailingComma": "all", 7 | "jsxBracketSameLine": false, 8 | "semi": false 9 | } 10 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "attach", 7 | "name": "Attach to tests started by 'test-debug' command", 8 | "skipFiles": [ 9 | "/**" 10 | ], 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # eslint-plugin-sort-keys-fix 2 | 3 | Fork of eslint rule that sorts keys in objects (https://eslint.org/docs/rules/sort-keys) with autofix enabled 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-keys-fix`: 14 | 15 | ``` 16 | $ npm install eslint-plugin-sort-keys-fix --save-dev 17 | ``` 18 | 19 | **Note:** If you installed ESLint globally (using the `-g` flag) then you must also install `eslint-plugin-sort-keys-fix` globally. 20 | 21 | ## Usage 22 | 23 | Add `sort-keys-fix` to the plugins section of your `.eslintrc` configuration file. You can omit the `eslint-plugin-` prefix: 24 | 25 | ```json 26 | { 27 | "plugins": [ 28 | "sort-keys-fix" 29 | ] 30 | } 31 | ``` 32 | 33 | 34 | Then add sort-keys-fix rule under the rules section. 35 | 36 | ```json 37 | { 38 | "rules": { 39 | "sort-keys-fix/sort-keys-fix": "warn" 40 | } 41 | } 42 | ``` 43 | 44 | Often it makes sense to enable `sort-keys-fix` only for certain files/directories. For cases like that, use override key of eslint config: 45 | 46 | ```jsonc 47 | { 48 | "rules": { 49 | // ... 50 | }, 51 | "overrides": [ 52 | { 53 | "files": ["src/alphabetical.js", "bin/*.js", "lib/*.js"], 54 | "rules": { 55 | "sort-keys-fix/sort-keys-fix": "warn" 56 | } 57 | } 58 | ] 59 | } 60 | ``` 61 | 62 | ## Rule configuration 63 | 64 | For available config options, see [official sort-keys reference](https://eslint.org/docs/rules/sort-keys#require-object-keys-to-be-sorted-sort-keys). All options supported by `sort-keys`, besides `minKeys`, are supported by `sort-keys-fix`. 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /__tests__/lib/rules/sort-keys-fix.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Tests for sort-keys-fix rule. 3 | * @author Toru Nagashima 4 | */ 5 | 6 | 'use strict' 7 | 8 | // ------------------------------------------------------------------------------ 9 | // Requirements 10 | // ------------------------------------------------------------------------------ 11 | 12 | const path = require('path') 13 | const rule = require('../../../lib/rules/sort-keys-fix') 14 | const RuleTester = require('eslint').RuleTester 15 | 16 | // ------------------------------------------------------------------------------ 17 | // Tests 18 | // ------------------------------------------------------------------------------ 19 | 20 | const test = { 21 | valid: [ 22 | // default (asc) 23 | { code: 'var obj = {_:2, a:1, b:3} // default', options: [] }, 24 | { code: 'var obj = {a:1, b:3, c:2}', options: [] }, 25 | { code: 'var obj = {a:2, b:3, b_:1}', options: [] }, 26 | { code: 'var obj = {C:3, b_:1, c:2}', options: [] }, 27 | { code: 'var obj = {$:1, A:3, _:2, a:4}', options: [] }, 28 | { code: "var obj = {1:1, '11':2, 2:4, A:3}", options: [] }, 29 | { code: "var obj = {'#':1, 'Z':2, À:3, è:4}", options: [] }, 30 | 31 | // ignore non-simple computed properties. 32 | { code: 'var obj = {a:1, b:3, [a + b]: -1, c:2}', options: [], parserOptions: { ecmaVersion: 6 } }, 33 | { code: "var obj = {'':1, [f()]:2, a:3}", options: [], parserOptions: { ecmaVersion: 6 } }, 34 | { code: "var obj = {a:1, [b++]:2, '':3}", options: ['desc'], parserOptions: { ecmaVersion: 6 } }, 35 | 36 | // ignore properties separated by spread properties 37 | { code: 'var obj = {a:1, ...z, b:1}', options: [], parserOptions: { ecmaVersion: 2018 } }, 38 | { code: 'var obj = {b:1, ...z, a:1}', options: [], parserOptions: { ecmaVersion: 2018 } }, 39 | { code: 'var obj = {...a, b:1, ...c, d:1}', options: [], parserOptions: { ecmaVersion: 2018 } }, 40 | { code: 'var obj = {...a, b:1, ...d, ...c, e:2, z:5}', options: [], parserOptions: { ecmaVersion: 2018 } }, 41 | { code: 'var obj = {b:1, ...c, ...d, e:2}', options: [], parserOptions: { ecmaVersion: 2018 } }, 42 | { code: "var obj = {a:1, ...z, '':2}", options: [], parserOptions: { ecmaVersion: 2018 } }, 43 | { code: "var obj = {'':1, ...z, 'a':2}", options: ['desc'], parserOptions: { ecmaVersion: 2018 } }, 44 | 45 | // not ignore properties not separated by spread properties 46 | { code: 'var obj = {...z, a:1, b:1}', options: [], parserOptions: { ecmaVersion: 2018 } }, 47 | { code: 'var obj = {...z, ...c, a:1, b:1}', options: [], parserOptions: { ecmaVersion: 2018 } }, 48 | { code: 'var obj = {a:1, b:1, ...z}', options: [], parserOptions: { ecmaVersion: 2018 } }, 49 | { 50 | code: 'var obj = {...z, ...x, a:1, ...c, ...d, f:5, e:4}', 51 | options: ['desc'], 52 | parserOptions: { ecmaVersion: 2018 }, 53 | }, 54 | 55 | // works when spread occurs somewhere other than an object literal 56 | { code: 'function fn(...args) { return [...args].length; }', options: [], parserOptions: { ecmaVersion: 2018 } }, 57 | { 58 | code: 'function g() {}; function f(...args) { return g(...args); }', 59 | options: [], 60 | parserOptions: { ecmaVersion: 2018 }, 61 | }, 62 | 63 | // ignore destructuring patterns. 64 | { code: 'let {a, b} = {}', options: [], parserOptions: { ecmaVersion: 6 } }, 65 | 66 | // nested 67 | { code: 'var obj = {a:1, b:{x:1, y:1}, c:1}', options: [] }, 68 | 69 | // asc 70 | { code: 'var obj = {_:2, a:1, b:3} // asc', options: ['asc'] }, 71 | { code: 'var obj = {a:1, b:3, c:2}', options: ['asc'] }, 72 | { code: 'var obj = {a:2, b:3, b_:1}', options: ['asc'] }, 73 | { code: 'var obj = {C:3, b_:1, c:2}', options: ['asc'] }, 74 | { code: 'var obj = {$:1, A:3, _:2, a:4}', options: ['asc'] }, 75 | { code: "var obj = {1:1, '11':2, 2:4, A:3}", options: ['asc'] }, 76 | { code: "var obj = {'#':1, 'Z':2, À:3, è:4}", options: ['asc'] }, 77 | 78 | // asc, minKeys should ignore unsorted keys when number of keys is less than minKeys 79 | // { code: "var obj = {a:1, c:2, b:3}", options: ["asc", { minKeys: 4 }] }, 80 | 81 | // asc, insensitive 82 | { code: 'var obj = {_:2, a:1, b:3} // asc, insensitive', options: ['asc', { caseSensitive: false }] }, 83 | { code: 'var obj = {a:1, b:3, c:2}', options: ['asc', { caseSensitive: false }] }, 84 | { code: 'var obj = {a:2, b:3, b_:1}', options: ['asc', { caseSensitive: false }] }, 85 | { code: 'var obj = {b_:1, C:3, c:2}', options: ['asc', { caseSensitive: false }] }, 86 | { code: 'var obj = {b_:1, c:3, C:2}', options: ['asc', { caseSensitive: false }] }, 87 | { code: 'var obj = {$:1, _:2, A:3, a:4}', options: ['asc', { caseSensitive: false }] }, 88 | { code: "var obj = {1:1, '11':2, 2:4, A:3}", options: ['asc', { caseSensitive: false }] }, 89 | { code: "var obj = {'#':1, 'Z':2, À:3, è:4}", options: ['asc', { caseSensitive: false }] }, 90 | 91 | // asc, insensitive, minKeys should ignore unsorted keys when number of keys is less than minKeys 92 | // { code: "var obj = {$:1, A:3, _:2, a:4}", options: ["asc", { caseSensitive: false, minKeys: 5 }] }, 93 | 94 | // asc, natural 95 | { code: 'var obj = {_:2, a:1, b:3} // asc, natural', options: ['asc', { natural: true }] }, 96 | { code: 'var obj = {a:1, b:3, c:2}', options: ['asc', { natural: true }] }, 97 | { code: 'var obj = {a:2, b:3, b_:1}', options: ['asc', { natural: true }] }, 98 | { code: 'var obj = {C:3, b_:1, c:2}', options: ['asc', { natural: true }] }, 99 | { code: 'var obj = {$:1, _:2, A:3, a:4}', options: ['asc', { natural: true }] }, 100 | { code: "var obj = {1:1, 2:4, '11':2, A:3}", options: ['asc', { natural: true }] }, 101 | { code: "var obj = {'#':1, 'Z':2, À:3, è:4}", options: ['asc', { natural: true }] }, 102 | 103 | // asc, natural, minKeys should ignore unsorted keys when number of keys is less than minKeys 104 | // { code: "var obj = {b_:1, a:2, b:3}", options: ["asc", { natural: true, minKeys: 4 }] }, 105 | 106 | // asc, natural, insensitive 107 | { 108 | code: 'var obj = {_:2, a:1, b:3} // asc, natural, insensitive', 109 | options: ['asc', { natural: true, caseSensitive: false }], 110 | }, 111 | { code: 'var obj = {a:1, b:3, c:2}', options: ['asc', { natural: true, caseSensitive: false }] }, 112 | { code: 'var obj = {a:2, b:3, b_:1}', options: ['asc', { natural: true, caseSensitive: false }] }, 113 | { code: 'var obj = {b_:1, C:3, c:2}', options: ['asc', { natural: true, caseSensitive: false }] }, 114 | { code: 'var obj = {b_:1, c:3, C:2}', options: ['asc', { natural: true, caseSensitive: false }] }, 115 | { code: 'var obj = {$:1, _:2, A:3, a:4}', options: ['asc', { natural: true, caseSensitive: false }] }, 116 | { code: "var obj = {1:1, 2:4, '11':2, A:3}", options: ['asc', { natural: true, caseSensitive: false }] }, 117 | { code: "var obj = {'#':1, 'Z':2, À:3, è:4}", options: ['asc', { natural: true, caseSensitive: false }] }, 118 | 119 | // asc, natural, insensitive, minKeys should ignore unsorted keys when number of keys is less than minKeys 120 | // { code: "var obj = {a:1, _:2, b:3}", options: ["asc", { natural: true, caseSensitive: false, minKeys: 4 }] }, 121 | 122 | // desc 123 | { code: 'var obj = {b:3, a:1, _:2} // desc', options: ['desc'] }, 124 | { code: 'var obj = {c:2, b:3, a:1}', options: ['desc'] }, 125 | { code: 'var obj = {b_:1, b:3, a:2}', options: ['desc'] }, 126 | { code: 'var obj = {c:2, b_:1, C:3}', options: ['desc'] }, 127 | { code: 'var obj = {a:4, _:2, A:3, $:1}', options: ['desc'] }, 128 | { code: "var obj = {A:3, 2:4, '11':2, 1:1}", options: ['desc'] }, 129 | { code: "var obj = {è:4, À:3, 'Z':2, '#':1}", options: ['desc'] }, 130 | 131 | // desc, minKeys should ignore unsorted keys when number of keys is less than minKeys 132 | // { code: "var obj = {a:1, c:2, b:3}", options: ["desc", { minKeys: 4 }] }, 133 | 134 | // desc, insensitive 135 | { code: 'var obj = {b:3, a:1, _:2} // desc, insensitive', options: ['desc', { caseSensitive: false }] }, 136 | { code: 'var obj = {c:2, b:3, a:1}', options: ['desc', { caseSensitive: false }] }, 137 | { code: 'var obj = {b_:1, b:3, a:2}', options: ['desc', { caseSensitive: false }] }, 138 | { code: 'var obj = {c:2, C:3, b_:1}', options: ['desc', { caseSensitive: false }] }, 139 | { code: 'var obj = {C:2, c:3, b_:1}', options: ['desc', { caseSensitive: false }] }, 140 | { code: 'var obj = {a:4, A:3, _:2, $:1}', options: ['desc', { caseSensitive: false }] }, 141 | { code: "var obj = {A:3, 2:4, '11':2, 1:1}", options: ['desc', { caseSensitive: false }] }, 142 | { code: "var obj = {è:4, À:3, 'Z':2, '#':1}", options: ['desc', { caseSensitive: false }] }, 143 | 144 | // desc, insensitive, minKeys should ignore unsorted keys when number of keys is less than minKeys 145 | // { code: "var obj = {$:1, _:2, A:3, a:4}", options: ["desc", { caseSensitive: false, minKeys: 5 }] }, 146 | 147 | // desc, natural 148 | { code: 'var obj = {b:3, a:1, _:2} // desc, natural', options: ['desc', { natural: true }] }, 149 | { code: 'var obj = {c:2, b:3, a:1}', options: ['desc', { natural: true }] }, 150 | { code: 'var obj = {b_:1, b:3, a:2}', options: ['desc', { natural: true }] }, 151 | { code: 'var obj = {c:2, b_:1, C:3}', options: ['desc', { natural: true }] }, 152 | { code: 'var obj = {a:4, A:3, _:2, $:1}', options: ['desc', { natural: true }] }, 153 | { code: "var obj = {A:3, '11':2, 2:4, 1:1}", options: ['desc', { natural: true }] }, 154 | { code: "var obj = {è:4, À:3, 'Z':2, '#':1}", options: ['desc', { natural: true }] }, 155 | 156 | // desc, natural, minKeys should ignore unsorted keys when number of keys is less than minKeys 157 | // { code: "var obj = {b_:1, a:2, b:3}", options: ["desc", { natural: true, minKeys: 4 }] }, 158 | 159 | // desc, natural, insensitive 160 | { 161 | code: 'var obj = {b:3, a:1, _:2} // desc, natural, insensitive', 162 | options: ['desc', { natural: true, caseSensitive: false }], 163 | }, 164 | { code: 'var obj = {c:2, b:3, a:1}', options: ['desc', { natural: true, caseSensitive: false }] }, 165 | { code: 'var obj = {b_:1, b:3, a:2}', options: ['desc', { natural: true, caseSensitive: false }] }, 166 | { code: 'var obj = {c:2, C:3, b_:1}', options: ['desc', { natural: true, caseSensitive: false }] }, 167 | { code: 'var obj = {C:2, c:3, b_:1}', options: ['desc', { natural: true, caseSensitive: false }] }, 168 | { code: 'var obj = {a:4, A:3, _:2, $:1}', options: ['desc', { natural: true, caseSensitive: false }] }, 169 | { code: "var obj = {A:3, '11':2, 2:4, 1:1}", options: ['desc', { natural: true, caseSensitive: false }] }, 170 | { code: "var obj = {è:4, À:3, 'Z':2, '#':1}", options: ['desc', { natural: true, caseSensitive: false }] }, 171 | 172 | // desc, natural, insensitive, minKeys should ignore unsorted keys when number of keys is less than minKeys 173 | // { code: "var obj = {a:1, _:2, b:3}", options: ["desc", { natural: true, caseSensitive: false, minKeys: 4 }] } 174 | ], 175 | invalid: [ 176 | // move comments on the same line as property together with property 177 | // not implemented yet 178 | // { 179 | // code: 'var obj = {\na:1,\n _:2, // comment\n b:3\n}', 180 | // errors: ["Expected object keys to be in ascending order. '_' should be before 'a'."], 181 | // output: 'var obj = {\n_:2, // comment\n a:1,\n b:3\n}', 182 | // }, 183 | 184 | // move inline comments on the line above property together with property 185 | { 186 | code: 'var obj = {\n// comment\n// comment 2\na:1,\n_:2,\nb:3\n}', 187 | errors: ["Expected object keys to be in ascending order. '_' should be before 'a'."], 188 | output: 'var obj = {\n\n\n_:2,\n// comment\n// comment 2\na:1,\nb:3\n}', 189 | }, 190 | 191 | // move multiline comments on the line above property together with property 192 | { 193 | code: 'var obj = {\n/* comment\n comment 2 */\na:1,\n_:2,\nb:3\n}', 194 | errors: ["Expected object keys to be in ascending order. '_' should be before 'a'."], 195 | output: 'var obj = {\n\n_:2,\n/* comment\n comment 2 */\na:1,\nb:3\n}', 196 | }, 197 | 198 | // default (asc) 199 | { 200 | code: 'var obj = {a:1, _:2, b:3} // default', 201 | errors: ["Expected object keys to be in ascending order. '_' should be before 'a'."], 202 | output: 'var obj = {_:2, a:1, b:3} // default', 203 | }, 204 | { 205 | code: 'var obj = {a:1, c:2, b:3}', 206 | errors: ["Expected object keys to be in ascending order. 'b' should be before 'c'."], 207 | output: 'var obj = {a:1, b:3, c:2}', 208 | }, 209 | { 210 | code: 'var obj = {b_:1, a:2, b:3}', 211 | errors: ["Expected object keys to be in ascending order. 'a' should be before 'b_'."], 212 | output: 'var obj = {a:2, b_:1, b:3}', 213 | }, 214 | { 215 | code: 'var obj = {b_:1, c:2, C:3}', 216 | errors: ["Expected object keys to be in ascending order. 'C' should be before 'c'."], 217 | output: 'var obj = {b_:1, C:3, c:2}', 218 | }, 219 | { 220 | code: 'var obj = {$:1, _:2, A:3, a:4}', 221 | errors: ["Expected object keys to be in ascending order. 'A' should be before '_'."], 222 | output: 'var obj = {$:1, A:3, _:2, a:4}', 223 | }, 224 | { 225 | code: "var obj = {1:1, 2:4, A:3, '11':2}", 226 | errors: ["Expected object keys to be in ascending order. '11' should be before 'A'."], 227 | output: "var obj = {1:1, 2:4, '11':2, A:3}", 228 | }, 229 | { 230 | code: "var obj = {'#':1, À:3, 'Z':2, è:4}", 231 | errors: ["Expected object keys to be in ascending order. 'Z' should be before 'À'."], 232 | output: "var obj = {'#':1, 'Z':2, À:3, è:4}", 233 | }, 234 | 235 | // not ignore properties not separated by spread properties 236 | { 237 | code: 'var obj = {...z, c:1, b:1}', 238 | parserOptions: { ecmaVersion: 2018 }, 239 | errors: ["Expected object keys to be in ascending order. 'b' should be before 'c'."], 240 | output: 'var obj = {...z, b:1, c:1}', 241 | }, 242 | { 243 | code: 'var obj = {...z, ...c, d:4, b:1, ...y, ...f, e:2, a:1}', 244 | parserOptions: { ecmaVersion: 2018 }, 245 | errors: [ 246 | "Expected object keys to be in ascending order. 'b' should be before 'd'.", 247 | "Expected object keys to be in ascending order. 'a' should be before 'e'.", 248 | ], 249 | output: 'var obj = {...z, ...c, b:1, d:4, ...y, ...f, a:1, e:2}', 250 | }, 251 | { 252 | code: 'var obj = {c:1, b:1, ...a}', 253 | parserOptions: { ecmaVersion: 2018 }, 254 | errors: ["Expected object keys to be in ascending order. 'b' should be before 'c'."], 255 | output: 'var obj = {b:1, c:1, ...a}', 256 | }, 257 | { 258 | code: 'var obj = {...z, ...a, c:1, b:1}', 259 | parserOptions: { ecmaVersion: 2018 }, 260 | errors: ["Expected object keys to be in ascending order. 'b' should be before 'c'."], 261 | output: 'var obj = {...z, ...a, b:1, c:1}', 262 | }, 263 | { 264 | code: 'var obj = {...z, b:1, a:1, ...d, ...c}', 265 | parserOptions: { ecmaVersion: 2018 }, 266 | errors: ["Expected object keys to be in ascending order. 'a' should be before 'b'."], 267 | output: 'var obj = {...z, a:1, b:1, ...d, ...c}', 268 | }, 269 | { 270 | code: 'var obj = {...z, a:2, b:0, ...x, ...c}', 271 | options: ['desc'], 272 | parserOptions: { ecmaVersion: 2018 }, 273 | errors: ["Expected object keys to be in descending order. 'b' should be before 'a'."], 274 | output: 'var obj = {...z, b:0, a:2, ...x, ...c}', 275 | }, 276 | { 277 | code: 'var obj = {...z, a:2, b:0, ...x}', 278 | options: ['desc'], 279 | parserOptions: { ecmaVersion: 2018 }, 280 | errors: ["Expected object keys to be in descending order. 'b' should be before 'a'."], 281 | output: 'var obj = {...z, b:0, a:2, ...x}', 282 | }, 283 | { 284 | code: "var obj = {...z, '':1, a:2}", 285 | options: ['desc'], 286 | parserOptions: { ecmaVersion: 2018 }, 287 | errors: ["Expected object keys to be in descending order. 'a' should be before ''."], 288 | output: `var obj = {...z, a:2, '':1}`, 289 | }, 290 | 291 | // ignore non-simple computed properties, but their position shouldn't affect other comparisons. 292 | { 293 | code: "var obj = {a:1, [b+c]:2, '':3}", 294 | parserOptions: { ecmaVersion: 6 }, 295 | errors: ["Expected object keys to be in ascending order. '' should be before 'a'."], 296 | output: "var obj = {'':3, [b+c]:2, a:1}", 297 | }, 298 | { 299 | code: "var obj = {'':1, [b+c]:2, a:3}", 300 | options: ['desc'], 301 | parserOptions: { ecmaVersion: 6 }, 302 | errors: ["Expected object keys to be in descending order. 'a' should be before ''."], 303 | output: "var obj = {a:3, [b+c]:2, '':1}", 304 | }, 305 | { 306 | code: "var obj = {b:1, [f()]:2, '':3, a:4}", 307 | options: ['desc'], 308 | parserOptions: { ecmaVersion: 6 }, 309 | errors: ["Expected object keys to be in descending order. 'a' should be before ''."], 310 | output: `var obj = {b:1, [f()]:2, a:4, '':3}`, 311 | }, 312 | 313 | // not ignore simple computed properties. 314 | { 315 | code: 'var obj = {a:1, b:3, [a]: -1, c:2}', 316 | parserOptions: { ecmaVersion: 6 }, 317 | errors: ["Expected object keys to be in ascending order. 'a' should be before 'b'."], 318 | output: 'var obj = {a:1, [a]: -1, b:3, c:2}', 319 | }, 320 | 321 | // nested 322 | { 323 | code: 'var obj = {a:1, c:{y:1, x:1}, b:1}', 324 | errors: [ 325 | "Expected object keys to be in ascending order. 'x' should be before 'y'.", 326 | "Expected object keys to be in ascending order. 'b' should be before 'c'.", 327 | ], 328 | output: 'var obj = {a:1, b:1, c:{y:1, x:1}}', 329 | }, 330 | 331 | // asc 332 | { 333 | code: 'var obj = {a:1, _:2, b:3} // asc', 334 | options: ['asc'], 335 | errors: ["Expected object keys to be in ascending order. '_' should be before 'a'."], 336 | output: 'var obj = {_:2, a:1, b:3} // asc', 337 | }, 338 | { 339 | code: 'var obj = {a:1, c:2, b:3}', 340 | options: ['asc'], 341 | errors: ["Expected object keys to be in ascending order. 'b' should be before 'c'."], 342 | output: 'var obj = {a:1, b:3, c:2}', 343 | }, 344 | { 345 | code: 'var obj = {b_:1, a:2, b:3}', 346 | options: ['asc'], 347 | errors: ["Expected object keys to be in ascending order. 'a' should be before 'b_'."], 348 | output: 'var obj = {a:2, b_:1, b:3}', 349 | }, 350 | { 351 | code: 'var obj = {b_:1, c:2, C:3}', 352 | options: ['asc'], 353 | errors: ["Expected object keys to be in ascending order. 'C' should be before 'c'."], 354 | output: 'var obj = {b_:1, C:3, c:2}', 355 | }, 356 | { 357 | code: 'var obj = {$:1, _:2, A:3, a:4}', 358 | options: ['asc'], 359 | errors: ["Expected object keys to be in ascending order. 'A' should be before '_'."], 360 | output: 'var obj = {$:1, A:3, _:2, a:4}', 361 | }, 362 | { 363 | code: "var obj = {1:1, 2:4, A:3, '11':2}", 364 | options: ['asc'], 365 | errors: ["Expected object keys to be in ascending order. '11' should be before 'A'."], 366 | output: "var obj = {1:1, 2:4, '11':2, A:3}", 367 | }, 368 | { 369 | code: "var obj = {'#':1, À:3, 'Z':2, è:4}", 370 | options: ['asc'], 371 | errors: ["Expected object keys to be in ascending order. 'Z' should be before 'À'."], 372 | output: "var obj = {'#':1, 'Z':2, À:3, è:4}", 373 | }, 374 | 375 | // asc, minKeys should error when number of keys is greater than or equal to minKeys 376 | // { 377 | // code: 'var obj = {a:1, _:2, b:3}', 378 | // options: ['asc', { minKeys: 3 }], 379 | // errors: ["Expected object keys to be in ascending order. '_' should be before 'a'."], 380 | // }, 381 | 382 | // asc, insensitive 383 | { 384 | code: 'var obj = {a:1, _:2, b:3} // asc, insensitive', 385 | options: ['asc', { caseSensitive: false }], 386 | errors: ["Expected object keys to be in insensitive ascending order. '_' should be before 'a'."], 387 | output: 'var obj = {_:2, a:1, b:3} // asc, insensitive', 388 | }, 389 | { 390 | code: 'var obj = {a:1, c:2, b:3}', 391 | options: ['asc', { caseSensitive: false }], 392 | errors: ["Expected object keys to be in insensitive ascending order. 'b' should be before 'c'."], 393 | output: 'var obj = {a:1, b:3, c:2}', 394 | }, 395 | { 396 | code: 'var obj = {b_:1, a:2, b:3}', 397 | options: ['asc', { caseSensitive: false }], 398 | errors: ["Expected object keys to be in insensitive ascending order. 'a' should be before 'b_'."], 399 | output: 'var obj = {a:2, b_:1, b:3}', 400 | }, 401 | { 402 | code: 'var obj = {$:1, A:3, _:2, a:4}', 403 | options: ['asc', { caseSensitive: false }], 404 | errors: ["Expected object keys to be in insensitive ascending order. '_' should be before 'A'."], 405 | output: 'var obj = {$:1, _:2, A:3, a:4}', 406 | }, 407 | { 408 | code: "var obj = {1:1, 2:4, A:3, '11':2}", 409 | options: ['asc', { caseSensitive: false }], 410 | errors: ["Expected object keys to be in insensitive ascending order. '11' should be before 'A'."], 411 | output: "var obj = {1:1, 2:4, '11':2, A:3}", 412 | }, 413 | { 414 | code: "var obj = {'#':1, À:3, 'Z':2, è:4}", 415 | options: ['asc', { caseSensitive: false }], 416 | errors: ["Expected object keys to be in insensitive ascending order. 'Z' should be before 'À'."], 417 | output: "var obj = {'#':1, 'Z':2, À:3, è:4}", 418 | }, 419 | 420 | // asc, insensitive, minKeys should error when number of keys is greater than or equal to minKeys 421 | // { 422 | // code: 'var obj = {a:1, _:2, b:3}', 423 | // options: ['asc', { caseSensitive: false, minKeys: 3 }], 424 | // errors: ["Expected object keys to be in insensitive ascending order. '_' should be before 'a'."], 425 | // }, 426 | 427 | // asc, natural 428 | { 429 | code: 'var obj = {a:1, _:2, b:3} // asc, natural', 430 | options: ['asc', { natural: true }], 431 | errors: ["Expected object keys to be in natural ascending order. '_' should be before 'a'."], 432 | output: 'var obj = {_:2, a:1, b:3} // asc, natural', 433 | }, 434 | { 435 | code: 'var obj = {a:1, c:2, b:3}', 436 | options: ['asc', { natural: true }], 437 | errors: ["Expected object keys to be in natural ascending order. 'b' should be before 'c'."], 438 | output: 'var obj = {a:1, b:3, c:2}', 439 | }, 440 | { 441 | code: 'var obj = {b_:1, a:2, b:3}', 442 | options: ['asc', { natural: true }], 443 | errors: ["Expected object keys to be in natural ascending order. 'a' should be before 'b_'."], 444 | output: 'var obj = {a:2, b_:1, b:3}', 445 | }, 446 | { 447 | code: 'var obj = {b_:1, c:2, C:3}', 448 | options: ['asc', { natural: true }], 449 | errors: ["Expected object keys to be in natural ascending order. 'C' should be before 'c'."], 450 | output: 'var obj = {b_:1, C:3, c:2}', 451 | }, 452 | { 453 | code: 'var obj = {$:1, A:3, _:2, a:4}', 454 | options: ['asc', { natural: true }], 455 | errors: ["Expected object keys to be in natural ascending order. '_' should be before 'A'."], 456 | output: 'var obj = {$:1, _:2, A:3, a:4}', 457 | }, 458 | { 459 | code: "var obj = {1:1, 2:4, A:3, '11':2}", 460 | options: ['asc', { natural: true }], 461 | errors: ["Expected object keys to be in natural ascending order. '11' should be before 'A'."], 462 | output: "var obj = {1:1, 2:4, '11':2, A:3}", 463 | }, 464 | { 465 | code: "var obj = {'#':1, À:3, 'Z':2, è:4}", 466 | options: ['asc', { natural: true }], 467 | errors: ["Expected object keys to be in natural ascending order. 'Z' should be before 'À'."], 468 | output: "var obj = {'#':1, 'Z':2, À:3, è:4}", 469 | }, 470 | 471 | // asc, natural, minKeys should error when number of keys is greater than or equal to minKeys 472 | // { 473 | // code: 'var obj = {a:1, _:2, b:3}', 474 | // options: ['asc', { natural: true, minKeys: 2 }], 475 | // errors: ["Expected object keys to be in natural ascending order. '_' should be before 'a'."], 476 | // }, 477 | 478 | // asc, natural, insensitive 479 | { 480 | code: 'var obj = {a:1, _:2, b:3} // asc, natural, insensitive', 481 | options: ['asc', { natural: true, caseSensitive: false }], 482 | errors: ["Expected object keys to be in natural insensitive ascending order. '_' should be before 'a'."], 483 | output: 'var obj = {_:2, a:1, b:3} // asc, natural, insensitive', 484 | }, 485 | { 486 | code: 'var obj = {a:1, c:2, b:3}', 487 | options: ['asc', { natural: true, caseSensitive: false }], 488 | errors: ["Expected object keys to be in natural insensitive ascending order. 'b' should be before 'c'."], 489 | output: 'var obj = {a:1, b:3, c:2}', 490 | }, 491 | { 492 | code: 'var obj = {b_:1, a:2, b:3}', 493 | options: ['asc', { natural: true, caseSensitive: false }], 494 | errors: ["Expected object keys to be in natural insensitive ascending order. 'a' should be before 'b_'."], 495 | output: 'var obj = {a:2, b_:1, b:3}', 496 | }, 497 | { 498 | code: 'var obj = {$:1, A:3, _:2, a:4}', 499 | options: ['asc', { natural: true, caseSensitive: false }], 500 | errors: ["Expected object keys to be in natural insensitive ascending order. '_' should be before 'A'."], 501 | output: 'var obj = {$:1, _:2, A:3, a:4}', 502 | }, 503 | { 504 | code: "var obj = {1:1, '11':2, 2:4, A:3}", 505 | options: ['asc', { natural: true, caseSensitive: false }], 506 | errors: ["Expected object keys to be in natural insensitive ascending order. '2' should be before '11'."], 507 | output: "var obj = {1:1, 2:4, '11':2, A:3}", 508 | }, 509 | { 510 | code: "var obj = {'#':1, À:3, 'Z':2, è:4}", 511 | options: ['asc', { natural: true, caseSensitive: false }], 512 | errors: ["Expected object keys to be in natural insensitive ascending order. 'Z' should be before 'À'."], 513 | output: "var obj = {'#':1, 'Z':2, À:3, è:4}", 514 | }, 515 | 516 | // asc, natural, insensitive, minKeys should error when number of keys is greater than or equal to minKeys 517 | // { 518 | // code: 'var obj = {a:1, _:2, b:3}', 519 | // options: ['asc', { natural: true, caseSensitive: false, minKeys: 3 }], 520 | // errors: ["Expected object keys to be in natural insensitive ascending order. '_' should be before 'a'."], 521 | // }, 522 | 523 | // desc 524 | { 525 | code: 'var obj = {a:1, _:2, b:3} // desc', 526 | options: ['desc'], 527 | errors: ["Expected object keys to be in descending order. 'b' should be before '_'."], 528 | output: 'var obj = {a:1, b:3, _:2} // desc', 529 | }, 530 | { 531 | code: 'var obj = {a:1, c:2, b:3}', 532 | options: ['desc'], 533 | errors: ["Expected object keys to be in descending order. 'c' should be before 'a'."], 534 | output: 'var obj = {c:2, a:1, b:3}', 535 | }, 536 | { 537 | code: 'var obj = {b_:1, a:2, b:3}', 538 | options: ['desc'], 539 | errors: ["Expected object keys to be in descending order. 'b' should be before 'a'."], 540 | output: 'var obj = {b_:1, b:3, a:2}', 541 | }, 542 | { 543 | code: 'var obj = {b_:1, c:2, C:3}', 544 | options: ['desc'], 545 | errors: ["Expected object keys to be in descending order. 'c' should be before 'b_'."], 546 | output: 'var obj = {c:2, b_:1, C:3}', 547 | }, 548 | { 549 | code: 'var obj = {$:1, _:2, A:3, a:4}', 550 | options: ['desc'], 551 | errors: [ 552 | "Expected object keys to be in descending order. '_' should be before '$'.", 553 | "Expected object keys to be in descending order. 'a' should be before 'A'.", 554 | ], 555 | output: 'var obj = {_:2, $:1, a:4, A:3}', 556 | }, 557 | { 558 | code: "var obj = {1:1, 2:4, A:3, '11':2}", 559 | options: ['desc'], 560 | errors: [ 561 | "Expected object keys to be in descending order. '2' should be before '1'.", 562 | "Expected object keys to be in descending order. 'A' should be before '2'.", 563 | ], 564 | output: "var obj = {2:4, 1:1, A:3, '11':2}", 565 | }, 566 | { 567 | code: "var obj = {'#':1, À:3, 'Z':2, è:4}", 568 | options: ['desc'], 569 | errors: [ 570 | "Expected object keys to be in descending order. 'À' should be before '#'.", 571 | "Expected object keys to be in descending order. 'è' should be before 'Z'.", 572 | ], 573 | output: "var obj = {À:3, '#':1, è:4, 'Z':2}", 574 | }, 575 | 576 | // desc, minKeys should error when number of keys is greater than or equal to minKeys 577 | // { 578 | // code: 'var obj = {a:1, _:2, b:3}', 579 | // options: ['desc', { minKeys: 3 }], 580 | // errors: ["Expected object keys to be in descending order. 'b' should be before '_'."], 581 | // }, 582 | 583 | // desc, insensitive 584 | { 585 | code: 'var obj = {a:1, _:2, b:3} // desc, insensitive', 586 | options: ['desc', { caseSensitive: false }], 587 | errors: ["Expected object keys to be in insensitive descending order. 'b' should be before '_'."], 588 | output: 'var obj = {a:1, b:3, _:2} // desc, insensitive', 589 | }, 590 | { 591 | code: 'var obj = {a:1, c:2, b:3}', 592 | options: ['desc', { caseSensitive: false }], 593 | errors: ["Expected object keys to be in insensitive descending order. 'c' should be before 'a'."], 594 | output: 'var obj = {c:2, a:1, b:3}', 595 | }, 596 | { 597 | code: 'var obj = {b_:1, a:2, b:3}', 598 | options: ['desc', { caseSensitive: false }], 599 | errors: ["Expected object keys to be in insensitive descending order. 'b' should be before 'a'."], 600 | output: 'var obj = {b_:1, b:3, a:2}', 601 | }, 602 | { 603 | code: 'var obj = {b_:1, c:2, C:3}', 604 | options: ['desc', { caseSensitive: false }], 605 | errors: ["Expected object keys to be in insensitive descending order. 'c' should be before 'b_'."], 606 | output: 'var obj = {c:2, b_:1, C:3}', 607 | }, 608 | { 609 | code: 'var obj = {$:1, _:2, A:3, a:4}', 610 | options: ['desc', { caseSensitive: false }], 611 | errors: [ 612 | "Expected object keys to be in insensitive descending order. '_' should be before '$'.", 613 | "Expected object keys to be in insensitive descending order. 'A' should be before '_'.", 614 | ], 615 | output: 'var obj = {_:2, $:1, A:3, a:4}', 616 | }, 617 | { 618 | code: "var obj = {1:1, 2:4, A:3, '11':2}", 619 | options: ['desc', { caseSensitive: false }], 620 | errors: [ 621 | "Expected object keys to be in insensitive descending order. '2' should be before '1'.", 622 | "Expected object keys to be in insensitive descending order. 'A' should be before '2'.", 623 | ], 624 | output: "var obj = {2:4, 1:1, A:3, '11':2}", 625 | }, 626 | { 627 | code: "var obj = {'#':1, À:3, 'Z':2, è:4}", 628 | options: ['desc', { caseSensitive: false }], 629 | errors: [ 630 | "Expected object keys to be in insensitive descending order. 'À' should be before '#'.", 631 | "Expected object keys to be in insensitive descending order. 'è' should be before 'Z'.", 632 | ], 633 | output: "var obj = {À:3, '#':1, è:4, 'Z':2}", 634 | }, 635 | 636 | // desc, insensitive should error when number of keys is greater than or equal to minKeys 637 | // { 638 | // code: 'var obj = {a:1, _:2, b:3}', 639 | // options: ['desc', { caseSensitive: false, minKeys: 2 }], 640 | // errors: ["Expected object keys to be in insensitive descending order. 'b' should be before '_'."], 641 | // }, 642 | 643 | // desc, natural 644 | { 645 | code: 'var obj = {a:1, _:2, b:3} // desc, natural', 646 | options: ['desc', { natural: true }], 647 | errors: ["Expected object keys to be in natural descending order. 'b' should be before '_'."], 648 | output: 'var obj = {a:1, b:3, _:2} // desc, natural', 649 | }, 650 | { 651 | code: 'var obj = {a:1, c:2, b:3}', 652 | options: ['desc', { natural: true }], 653 | errors: ["Expected object keys to be in natural descending order. 'c' should be before 'a'."], 654 | output: 'var obj = {c:2, a:1, b:3}', 655 | }, 656 | { 657 | code: 'var obj = {b_:1, a:2, b:3}', 658 | options: ['desc', { natural: true }], 659 | errors: ["Expected object keys to be in natural descending order. 'b' should be before 'a'."], 660 | output: 'var obj = {b_:1, b:3, a:2}', 661 | }, 662 | { 663 | code: 'var obj = {b_:1, c:2, C:3}', 664 | options: ['desc', { natural: true }], 665 | errors: ["Expected object keys to be in natural descending order. 'c' should be before 'b_'."], 666 | output: 'var obj = {c:2, b_:1, C:3}', 667 | }, 668 | { 669 | code: 'var obj = {$:1, _:2, A:3, a:4}', 670 | options: ['desc', { natural: true }], 671 | errors: [ 672 | "Expected object keys to be in natural descending order. '_' should be before '$'.", 673 | "Expected object keys to be in natural descending order. 'A' should be before '_'.", 674 | "Expected object keys to be in natural descending order. 'a' should be before 'A'.", 675 | ], 676 | output: 'var obj = {_:2, $:1, a:4, A:3}', 677 | }, 678 | { 679 | code: "var obj = {1:1, 2:4, A:3, '11':2}", 680 | options: ['desc', { natural: true }], 681 | errors: [ 682 | "Expected object keys to be in natural descending order. '2' should be before '1'.", 683 | "Expected object keys to be in natural descending order. 'A' should be before '2'.", 684 | ], 685 | output: "var obj = {2:4, 1:1, A:3, '11':2}", 686 | }, 687 | { 688 | code: "var obj = {'#':1, À:3, 'Z':2, è:4}", 689 | options: ['desc', { natural: true }], 690 | errors: [ 691 | "Expected object keys to be in natural descending order. 'À' should be before '#'.", 692 | "Expected object keys to be in natural descending order. 'è' should be before 'Z'.", 693 | ], 694 | output: "var obj = {À:3, '#':1, è:4, 'Z':2}", 695 | }, 696 | 697 | // desc, natural should error when number of keys is greater than or equal to minKeys 698 | // { 699 | // code: 'var obj = {a:1, _:2, b:3}', 700 | // options: ['desc', { natural: true, minKeys: 3 }], 701 | // errors: ["Expected object keys to be in natural descending order. 'b' should be before '_'."], 702 | // }, 703 | 704 | // desc, natural, insensitive 705 | { 706 | code: 'var obj = {a:1, _:2, b:3} // desc, natural, insensitive', 707 | options: ['desc', { natural: true, caseSensitive: false }], 708 | errors: ["Expected object keys to be in natural insensitive descending order. 'b' should be before '_'."], 709 | output: 'var obj = {a:1, b:3, _:2} // desc, natural, insensitive', 710 | }, 711 | { 712 | code: 'var obj = {a:1, c:2, b:3}', 713 | options: ['desc', { natural: true, caseSensitive: false }], 714 | errors: ["Expected object keys to be in natural insensitive descending order. 'c' should be before 'a'."], 715 | output: 'var obj = {c:2, a:1, b:3}', 716 | }, 717 | { 718 | code: 'var obj = {b_:1, a:2, b:3}', 719 | options: ['desc', { natural: true, caseSensitive: false }], 720 | errors: ["Expected object keys to be in natural insensitive descending order. 'b' should be before 'a'."], 721 | output: 'var obj = {b_:1, b:3, a:2}', 722 | }, 723 | { 724 | code: 'var obj = {b_:1, c:2, C:3}', 725 | options: ['desc', { natural: true, caseSensitive: false }], 726 | errors: ["Expected object keys to be in natural insensitive descending order. 'c' should be before 'b_'."], 727 | output: 'var obj = {c:2, b_:1, C:3}', 728 | }, 729 | { 730 | code: 'var obj = {$:1, _:2, A:3, a:4}', 731 | options: ['desc', { natural: true, caseSensitive: false }], 732 | errors: [ 733 | "Expected object keys to be in natural insensitive descending order. '_' should be before '$'.", 734 | "Expected object keys to be in natural insensitive descending order. 'A' should be before '_'.", 735 | ], 736 | output: 'var obj = {_:2, $:1, A:3, a:4}', 737 | }, 738 | { 739 | code: "var obj = {1:1, 2:4, '11':2, A:3}", 740 | options: ['desc', { natural: true, caseSensitive: false }], 741 | errors: [ 742 | "Expected object keys to be in natural insensitive descending order. '2' should be before '1'.", 743 | "Expected object keys to be in natural insensitive descending order. '11' should be before '2'.", 744 | "Expected object keys to be in natural insensitive descending order. 'A' should be before '11'.", 745 | ], 746 | output: "var obj = {2:4, 1:1, A:3, '11':2}", 747 | }, 748 | { 749 | code: "var obj = {'#':1, À:3, 'Z':2, è:4}", 750 | options: ['desc', { natural: true, caseSensitive: false }], 751 | errors: [ 752 | "Expected object keys to be in natural insensitive descending order. 'À' should be before '#'.", 753 | "Expected object keys to be in natural insensitive descending order. 'è' should be before 'Z'.", 754 | ], 755 | output: "var obj = {À:3, '#':1, è:4, 'Z':2}", 756 | }, 757 | 758 | // desc, natural, insensitive should error when number of keys is greater than or equal to minKeys 759 | // { 760 | // code: 'var obj = {a:1, _:2, b:3}', 761 | // options: ['desc', { natural: true, caseSensitive: false, minKeys: 2 }], 762 | // errors: ["Expected object keys to be in natural insensitive descending order. 'b' should be before '_'."], 763 | // }, 764 | ], 765 | } 766 | 767 | const ruleTester = new RuleTester() 768 | 769 | ruleTester.run('sort-keys-fix', rule, test) 770 | 771 | const babelRuleTester = new RuleTester({ 772 | parser: path.resolve('node_modules/babel-eslint/lib/index.js'), 773 | }) 774 | 775 | babelRuleTester.run('babel-eslint/sort-keys-fix', rule, test) 776 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Fork of eslint rule that sorts keys in objects (https://eslint.org/docs/rules/sort-keys) with autofix enabled 3 | * @author Leonid Buneev 4 | */ 5 | 'use strict' 6 | const path = require('path') 7 | // ------------------------------------------------------------------------------ 8 | // Requirements 9 | // ------------------------------------------------------------------------------ 10 | 11 | const requireIndex = require('requireindex') 12 | 13 | // ------------------------------------------------------------------------------ 14 | // Plugin Definition 15 | // ------------------------------------------------------------------------------ 16 | 17 | // import all rules in lib/rules 18 | module.exports.rules = requireIndex(path.join(__dirname, 'rules')) 19 | -------------------------------------------------------------------------------- /lib/rules/sort-keys-fix.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Rule to require object keys to be sorted 3 | * @author Toru Nagashima 4 | */ 5 | 6 | 'use strict' 7 | 8 | // ------------------------------------------------------------------------------ 9 | // Requirements 10 | // ------------------------------------------------------------------------------ 11 | 12 | const astUtils = require('../util/ast-utils') 13 | 14 | const naturalCompare = require('natural-compare') 15 | 16 | // ------------------------------------------------------------------------------ 17 | // Helpers 18 | // ------------------------------------------------------------------------------ 19 | 20 | /** 21 | * Gets the property name of the given `Property` node. 22 | * 23 | * - If the property's key is an `Identifier` node, this returns the key's name 24 | * whether it's a computed property or not. 25 | * - If the property has a static name, this returns the static name. 26 | * - Otherwise, this returns null. 27 | * 28 | * @param {ASTNode} node - The `Property` node to get. 29 | * @returns {string|null} The property name or null. 30 | * @private 31 | */ 32 | function getPropertyName(node) { 33 | const staticName = astUtils.getStaticPropertyName(node) 34 | 35 | if (staticName !== null) { 36 | return staticName 37 | } 38 | 39 | return node.key.name || null 40 | } 41 | 42 | /** 43 | * Functions which check that the given 2 names are in specific order. 44 | * 45 | * Postfix `I` is meant insensitive. 46 | * Postfix `N` is meant natual. 47 | * 48 | * @private 49 | */ 50 | const isValidOrders = { 51 | asc(a, b) { 52 | return a <= b 53 | }, 54 | ascI(a, b) { 55 | return a.toLowerCase() <= b.toLowerCase() 56 | }, 57 | ascN(a, b) { 58 | return naturalCompare(a, b) <= 0 59 | }, 60 | ascIN(a, b) { 61 | return naturalCompare(a.toLowerCase(), b.toLowerCase()) <= 0 62 | }, 63 | desc(a, b) { 64 | return isValidOrders.asc(b, a) 65 | }, 66 | descI(a, b) { 67 | return isValidOrders.ascI(b, a) 68 | }, 69 | descN(a, b) { 70 | return isValidOrders.ascN(b, a) 71 | }, 72 | descIN(a, b) { 73 | return isValidOrders.ascIN(b, a) 74 | }, 75 | } 76 | 77 | // ------------------------------------------------------------------------------ 78 | // Rule Definition 79 | // ------------------------------------------------------------------------------ 80 | 81 | module.exports = { 82 | meta: { 83 | type: 'suggestion', 84 | fixable: 'code', 85 | docs: { 86 | description: 'require object keys to be sorted', 87 | category: 'Stylistic Issues', 88 | recommended: false, 89 | url: 'https://github.com/leo-buneev/eslint-plugin-sort-keys-fix', 90 | }, 91 | 92 | schema: [ 93 | { 94 | enum: ['asc', 'desc'], 95 | }, 96 | { 97 | type: 'object', 98 | properties: { 99 | caseSensitive: { 100 | type: 'boolean', 101 | }, 102 | natural: { 103 | type: 'boolean', 104 | }, 105 | }, 106 | additionalProperties: false, 107 | }, 108 | ], 109 | }, 110 | 111 | create(context) { 112 | // Parse options. 113 | const order = context.options[0] || 'asc' 114 | const options = context.options[1] 115 | const insensitive = (options && options.caseSensitive) === false 116 | const natual = Boolean(options && options.natural) 117 | const isValidOrder = isValidOrders[order + (insensitive ? 'I' : '') + (natual ? 'N' : '')] 118 | 119 | // The stack to save the previous property's name for each object literals. 120 | let stack = null 121 | 122 | const SpreadElement = node => { 123 | if (node.parent.type === 'ObjectExpression') { 124 | stack.prevName = null 125 | } 126 | } 127 | 128 | return { 129 | ExperimentalSpreadProperty: SpreadElement, 130 | 131 | ObjectExpression() { 132 | stack = { 133 | upper: stack, 134 | prevName: null, 135 | prevNode: null, 136 | } 137 | }, 138 | 139 | 'ObjectExpression:exit'() { 140 | stack = stack.upper 141 | }, 142 | 143 | SpreadElement, 144 | 145 | Property(node) { 146 | if (node.parent.type === 'ObjectPattern') { 147 | return 148 | } 149 | 150 | const prevName = stack.prevName 151 | const prevNode = stack.prevNode 152 | const thisName = getPropertyName(node) 153 | 154 | if (thisName !== null) { 155 | stack.prevName = thisName 156 | stack.prevNode = node || prevNode 157 | } 158 | 159 | if (prevName === null || thisName === null) { 160 | return 161 | } 162 | 163 | if (!isValidOrder(prevName, thisName)) { 164 | context.report({ 165 | node, 166 | loc: node.key.loc, 167 | message: 168 | "Expected object keys to be in {{natual}}{{insensitive}}{{order}}ending order. '{{thisName}}' should be before '{{prevName}}'.", 169 | data: { 170 | thisName, 171 | prevName, 172 | order, 173 | insensitive: insensitive ? 'insensitive ' : '', 174 | natual: natual ? 'natural ' : '', 175 | }, 176 | fix(fixer) { 177 | const fixes = [] 178 | const sourceCode = context.getSourceCode() 179 | const moveProperty = (fromNode, toNode) => { 180 | const prevText = sourceCode.getText(fromNode) 181 | const thisComments = sourceCode.getCommentsBefore(fromNode) 182 | for (const thisComment of thisComments) { 183 | fixes.push(fixer.insertTextBefore(toNode, sourceCode.getText(thisComment) + '\n')) 184 | fixes.push(fixer.remove(thisComment)) 185 | } 186 | fixes.push(fixer.replaceText(toNode, prevText)) 187 | } 188 | moveProperty(node, prevNode) 189 | moveProperty(prevNode, node) 190 | return fixes 191 | }, 192 | }) 193 | } 194 | }, 195 | } 196 | }, 197 | } 198 | -------------------------------------------------------------------------------- /lib/util/ast-utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Common utils for AST. 3 | * @author Gyandeep Singh 4 | */ 5 | 6 | 'use strict' 7 | 8 | // ------------------------------------------------------------------------------ 9 | // Requirements 10 | // ------------------------------------------------------------------------------ 11 | 12 | const esutils = require('esutils') 13 | const espree = require('espree') 14 | 15 | // ------------------------------------------------------------------------------ 16 | // Helpers 17 | // ------------------------------------------------------------------------------ 18 | 19 | const anyFunctionPattern = /^(?:Function(?:Declaration|Expression)|ArrowFunctionExpression)$/ 20 | const anyLoopPattern = /^(?:DoWhile|For|ForIn|ForOf|While)Statement$/ 21 | const arrayOrTypedArrayPattern = /Array$/ 22 | const arrayMethodPattern = /^(?:every|filter|find|findIndex|forEach|map|some)$/ 23 | const bindOrCallOrApplyPattern = /^(?:bind|call|apply)$/ 24 | const breakableTypePattern = /^(?:(?:Do)?While|For(?:In|Of)?|Switch)Statement$/ 25 | const thisTagPattern = /^[\s*]*@this/m 26 | 27 | const COMMENTS_IGNORE_PATTERN = /^\s*(?:eslint|jshint\s+|jslint\s+|istanbul\s+|globals?\s+|exported\s+|jscs)/ 28 | const LINEBREAKS = new Set(['\r\n', '\r', '\n', '\u2028', '\u2029']) 29 | const LINEBREAK_MATCHER = /\r\n|[\r\n\u2028\u2029]/ 30 | const SHEBANG_MATCHER = /^#!([^\r\n]+)/ 31 | 32 | // A set of node types that can contain a list of statements 33 | const STATEMENT_LIST_PARENTS = new Set(['Program', 'BlockStatement', 'SwitchCase']) 34 | 35 | /** 36 | * Checks reference if is non initializer and writable. 37 | * @param {Reference} reference - A reference to check. 38 | * @param {int} index - The index of the reference in the references. 39 | * @param {Reference[]} references - The array that the reference belongs to. 40 | * @returns {boolean} Success/Failure 41 | * @private 42 | */ 43 | function isModifyingReference(reference, index, references) { 44 | const identifier = reference.identifier 45 | 46 | /* 47 | * Destructuring assignments can have multiple default value, so 48 | * possibly there are multiple writeable references for the same 49 | * identifier. 50 | */ 51 | const modifyingDifferentIdentifier = index === 0 || references[index - 1].identifier !== identifier 52 | 53 | return identifier && reference.init === false && reference.isWrite() && modifyingDifferentIdentifier 54 | } 55 | 56 | /** 57 | * Checks whether the given string starts with uppercase or not. 58 | * 59 | * @param {string} s - The string to check. 60 | * @returns {boolean} `true` if the string starts with uppercase. 61 | */ 62 | function startsWithUpperCase(s) { 63 | return s[0] !== s[0].toLocaleLowerCase() 64 | } 65 | 66 | /** 67 | * Checks whether or not a node is a constructor. 68 | * @param {ASTNode} node - A function node to check. 69 | * @returns {boolean} Wehether or not a node is a constructor. 70 | */ 71 | function isES5Constructor(node) { 72 | return node.id && startsWithUpperCase(node.id.name) 73 | } 74 | 75 | /** 76 | * Finds a function node from ancestors of a node. 77 | * @param {ASTNode} node - A start node to find. 78 | * @returns {Node|null} A found function node. 79 | */ 80 | function getUpperFunction(node) { 81 | for (let currentNode = node; currentNode; currentNode = currentNode.parent) { 82 | if (anyFunctionPattern.test(currentNode.type)) { 83 | return currentNode 84 | } 85 | } 86 | return null 87 | } 88 | 89 | /** 90 | * Checks whether a given node is a function node or not. 91 | * The following types are function nodes: 92 | * 93 | * - ArrowFunctionExpression 94 | * - FunctionDeclaration 95 | * - FunctionExpression 96 | * 97 | * @param {ASTNode|null} node - A node to check. 98 | * @returns {boolean} `true` if the node is a function node. 99 | */ 100 | function isFunction(node) { 101 | return Boolean(node && anyFunctionPattern.test(node.type)) 102 | } 103 | 104 | /** 105 | * Checks whether a given node is a loop node or not. 106 | * The following types are loop nodes: 107 | * 108 | * - DoWhileStatement 109 | * - ForInStatement 110 | * - ForOfStatement 111 | * - ForStatement 112 | * - WhileStatement 113 | * 114 | * @param {ASTNode|null} node - A node to check. 115 | * @returns {boolean} `true` if the node is a loop node. 116 | */ 117 | function isLoop(node) { 118 | return Boolean(node && anyLoopPattern.test(node.type)) 119 | } 120 | 121 | /** 122 | * Checks whether the given node is in a loop or not. 123 | * 124 | * @param {ASTNode} node - The node to check. 125 | * @returns {boolean} `true` if the node is in a loop. 126 | */ 127 | function isInLoop(node) { 128 | for (let currentNode = node; currentNode && !isFunction(currentNode); currentNode = currentNode.parent) { 129 | if (isLoop(currentNode)) { 130 | return true 131 | } 132 | } 133 | 134 | return false 135 | } 136 | 137 | /** 138 | * Checks whether or not a node is `null` or `undefined`. 139 | * @param {ASTNode} node - A node to check. 140 | * @returns {boolean} Whether or not the node is a `null` or `undefined`. 141 | * @public 142 | */ 143 | function isNullOrUndefined(node) { 144 | return ( 145 | module.exports.isNullLiteral(node) || 146 | (node.type === 'Identifier' && node.name === 'undefined') || 147 | (node.type === 'UnaryExpression' && node.operator === 'void') 148 | ) 149 | } 150 | 151 | /** 152 | * Checks whether or not a node is callee. 153 | * @param {ASTNode} node - A node to check. 154 | * @returns {boolean} Whether or not the node is callee. 155 | */ 156 | function isCallee(node) { 157 | return node.parent.type === 'CallExpression' && node.parent.callee === node 158 | } 159 | 160 | /** 161 | * Checks whether or not a node is `Reflect.apply`. 162 | * @param {ASTNode} node - A node to check. 163 | * @returns {boolean} Whether or not the node is a `Reflect.apply`. 164 | */ 165 | function isReflectApply(node) { 166 | return ( 167 | node.type === 'MemberExpression' && 168 | node.object.type === 'Identifier' && 169 | node.object.name === 'Reflect' && 170 | node.property.type === 'Identifier' && 171 | node.property.name === 'apply' && 172 | node.computed === false 173 | ) 174 | } 175 | 176 | /** 177 | * Checks whether or not a node is `Array.from`. 178 | * @param {ASTNode} node - A node to check. 179 | * @returns {boolean} Whether or not the node is a `Array.from`. 180 | */ 181 | function isArrayFromMethod(node) { 182 | return ( 183 | node.type === 'MemberExpression' && 184 | node.object.type === 'Identifier' && 185 | arrayOrTypedArrayPattern.test(node.object.name) && 186 | node.property.type === 'Identifier' && 187 | node.property.name === 'from' && 188 | node.computed === false 189 | ) 190 | } 191 | 192 | /** 193 | * Checks whether or not a node is a method which has `thisArg`. 194 | * @param {ASTNode} node - A node to check. 195 | * @returns {boolean} Whether or not the node is a method which has `thisArg`. 196 | */ 197 | function isMethodWhichHasThisArg(node) { 198 | for ( 199 | let currentNode = node; 200 | currentNode.type === 'MemberExpression' && !currentNode.computed; 201 | currentNode = currentNode.property 202 | ) { 203 | if (currentNode.property.type === 'Identifier') { 204 | return arrayMethodPattern.test(currentNode.property.name) 205 | } 206 | } 207 | 208 | return false 209 | } 210 | 211 | /** 212 | * Creates the negate function of the given function. 213 | * @param {Function} f - The function to negate. 214 | * @returns {Function} Negated function. 215 | */ 216 | function negate(f) { 217 | return token => !f(token) 218 | } 219 | 220 | /** 221 | * Checks whether or not a node has a `@this` tag in its comments. 222 | * @param {ASTNode} node - A node to check. 223 | * @param {SourceCode} sourceCode - A SourceCode instance to get comments. 224 | * @returns {boolean} Whether or not the node has a `@this` tag in its comments. 225 | */ 226 | function hasJSDocThisTag(node, sourceCode) { 227 | const jsdocComment = sourceCode.getJSDocComment(node) 228 | 229 | if (jsdocComment && thisTagPattern.test(jsdocComment.value)) { 230 | return true 231 | } 232 | 233 | // Checks `@this` in its leading comments for callbacks, 234 | // because callbacks don't have its JSDoc comment. 235 | // e.g. 236 | // sinon.test(/* @this sinon.Sandbox */function() { this.spy(); }); 237 | return sourceCode.getCommentsBefore(node).some(comment => thisTagPattern.test(comment.value)) 238 | } 239 | 240 | /** 241 | * Determines if a node is surrounded by parentheses. 242 | * @param {SourceCode} sourceCode The ESLint source code object 243 | * @param {ASTNode} node The node to be checked. 244 | * @returns {boolean} True if the node is parenthesised. 245 | * @private 246 | */ 247 | function isParenthesised(sourceCode, node) { 248 | const previousToken = sourceCode.getTokenBefore(node) 249 | 250 | const nextToken = sourceCode.getTokenAfter(node) 251 | 252 | return ( 253 | Boolean(previousToken && nextToken) && 254 | previousToken.value === '(' && 255 | previousToken.range[1] <= node.range[0] && 256 | nextToken.value === ')' && 257 | nextToken.range[0] >= node.range[1] 258 | ) 259 | } 260 | 261 | /** 262 | * Checks if the given token is an arrow token or not. 263 | * 264 | * @param {Token} token - The token to check. 265 | * @returns {boolean} `true` if the token is an arrow token. 266 | */ 267 | function isArrowToken(token) { 268 | return token.value === '=>' && token.type === 'Punctuator' 269 | } 270 | 271 | /** 272 | * Checks if the given token is a comma token or not. 273 | * 274 | * @param {Token} token - The token to check. 275 | * @returns {boolean} `true` if the token is a comma token. 276 | */ 277 | function isCommaToken(token) { 278 | return token.value === ',' && token.type === 'Punctuator' 279 | } 280 | 281 | /** 282 | * Checks if the given token is a semicolon token or not. 283 | * 284 | * @param {Token} token - The token to check. 285 | * @returns {boolean} `true` if the token is a semicolon token. 286 | */ 287 | function isSemicolonToken(token) { 288 | return token.value === ';' && token.type === 'Punctuator' 289 | } 290 | 291 | /** 292 | * Checks if the given token is a colon token or not. 293 | * 294 | * @param {Token} token - The token to check. 295 | * @returns {boolean} `true` if the token is a colon token. 296 | */ 297 | function isColonToken(token) { 298 | return token.value === ':' && token.type === 'Punctuator' 299 | } 300 | 301 | /** 302 | * Checks if the given token is an opening parenthesis token or not. 303 | * 304 | * @param {Token} token - The token to check. 305 | * @returns {boolean} `true` if the token is an opening parenthesis token. 306 | */ 307 | function isOpeningParenToken(token) { 308 | return token.value === '(' && token.type === 'Punctuator' 309 | } 310 | 311 | /** 312 | * Checks if the given token is a closing parenthesis token or not. 313 | * 314 | * @param {Token} token - The token to check. 315 | * @returns {boolean} `true` if the token is a closing parenthesis token. 316 | */ 317 | function isClosingParenToken(token) { 318 | return token.value === ')' && token.type === 'Punctuator' 319 | } 320 | 321 | /** 322 | * Checks if the given token is an opening square bracket token or not. 323 | * 324 | * @param {Token} token - The token to check. 325 | * @returns {boolean} `true` if the token is an opening square bracket token. 326 | */ 327 | function isOpeningBracketToken(token) { 328 | return token.value === '[' && token.type === 'Punctuator' 329 | } 330 | 331 | /** 332 | * Checks if the given token is a closing square bracket token or not. 333 | * 334 | * @param {Token} token - The token to check. 335 | * @returns {boolean} `true` if the token is a closing square bracket token. 336 | */ 337 | function isClosingBracketToken(token) { 338 | return token.value === ']' && token.type === 'Punctuator' 339 | } 340 | 341 | /** 342 | * Checks if the given token is an opening brace token or not. 343 | * 344 | * @param {Token} token - The token to check. 345 | * @returns {boolean} `true` if the token is an opening brace token. 346 | */ 347 | function isOpeningBraceToken(token) { 348 | return token.value === '{' && token.type === 'Punctuator' 349 | } 350 | 351 | /** 352 | * Checks if the given token is a closing brace token or not. 353 | * 354 | * @param {Token} token - The token to check. 355 | * @returns {boolean} `true` if the token is a closing brace token. 356 | */ 357 | function isClosingBraceToken(token) { 358 | return token.value === '}' && token.type === 'Punctuator' 359 | } 360 | 361 | /** 362 | * Checks if the given token is a comment token or not. 363 | * 364 | * @param {Token} token - The token to check. 365 | * @returns {boolean} `true` if the token is a comment token. 366 | */ 367 | function isCommentToken(token) { 368 | return token.type === 'Line' || token.type === 'Block' || token.type === 'Shebang' 369 | } 370 | 371 | /** 372 | * Checks if the given token is a keyword token or not. 373 | * 374 | * @param {Token} token - The token to check. 375 | * @returns {boolean} `true` if the token is a keyword token. 376 | */ 377 | function isKeywordToken(token) { 378 | return token.type === 'Keyword' 379 | } 380 | 381 | /** 382 | * Gets the `(` token of the given function node. 383 | * 384 | * @param {ASTNode} node - The function node to get. 385 | * @param {SourceCode} sourceCode - The source code object to get tokens. 386 | * @returns {Token} `(` token. 387 | */ 388 | function getOpeningParenOfParams(node, sourceCode) { 389 | return node.id 390 | ? sourceCode.getTokenAfter(node.id, isOpeningParenToken) 391 | : sourceCode.getFirstToken(node, isOpeningParenToken) 392 | } 393 | 394 | /** 395 | * Creates a version of the LINEBREAK_MATCHER regex with the global flag. 396 | * Global regexes are mutable, so this needs to be a function instead of a constant. 397 | * @returns {RegExp} A global regular expression that matches line terminators 398 | */ 399 | function createGlobalLinebreakMatcher() { 400 | return new RegExp(LINEBREAK_MATCHER.source, 'g') 401 | } 402 | 403 | /** 404 | * Checks whether or not the tokens of two given nodes are same. 405 | * @param {ASTNode} left - A node 1 to compare. 406 | * @param {ASTNode} right - A node 2 to compare. 407 | * @param {SourceCode} sourceCode - The ESLint source code object. 408 | * @returns {boolean} the source code for the given node. 409 | */ 410 | function equalTokens(left, right, sourceCode) { 411 | const tokensL = sourceCode.getTokens(left) 412 | const tokensR = sourceCode.getTokens(right) 413 | 414 | if (tokensL.length !== tokensR.length) { 415 | return false 416 | } 417 | for (let i = 0; i < tokensL.length; ++i) { 418 | if (tokensL[i].type !== tokensR[i].type || tokensL[i].value !== tokensR[i].value) { 419 | return false 420 | } 421 | } 422 | 423 | return true 424 | } 425 | 426 | // ------------------------------------------------------------------------------ 427 | // Public Interface 428 | // ------------------------------------------------------------------------------ 429 | 430 | module.exports = { 431 | COMMENTS_IGNORE_PATTERN, 432 | LINEBREAKS, 433 | LINEBREAK_MATCHER, 434 | SHEBANG_MATCHER, 435 | STATEMENT_LIST_PARENTS, 436 | 437 | /** 438 | * Determines whether two adjacent tokens are on the same line. 439 | * @param {Object} left - The left token object. 440 | * @param {Object} right - The right token object. 441 | * @returns {boolean} Whether or not the tokens are on the same line. 442 | * @public 443 | */ 444 | isTokenOnSameLine(left, right) { 445 | return left.loc.end.line === right.loc.start.line 446 | }, 447 | 448 | isNullOrUndefined, 449 | isCallee, 450 | isES5Constructor, 451 | getUpperFunction, 452 | isFunction, 453 | isLoop, 454 | isInLoop, 455 | isArrayFromMethod, 456 | isParenthesised, 457 | createGlobalLinebreakMatcher, 458 | equalTokens, 459 | 460 | isArrowToken, 461 | isClosingBraceToken, 462 | isClosingBracketToken, 463 | isClosingParenToken, 464 | isColonToken, 465 | isCommaToken, 466 | isCommentToken, 467 | isKeywordToken, 468 | isNotClosingBraceToken: negate(isClosingBraceToken), 469 | isNotClosingBracketToken: negate(isClosingBracketToken), 470 | isNotClosingParenToken: negate(isClosingParenToken), 471 | isNotColonToken: negate(isColonToken), 472 | isNotCommaToken: negate(isCommaToken), 473 | isNotOpeningBraceToken: negate(isOpeningBraceToken), 474 | isNotOpeningBracketToken: negate(isOpeningBracketToken), 475 | isNotOpeningParenToken: negate(isOpeningParenToken), 476 | isNotSemicolonToken: negate(isSemicolonToken), 477 | isOpeningBraceToken, 478 | isOpeningBracketToken, 479 | isOpeningParenToken, 480 | isSemicolonToken, 481 | 482 | /** 483 | * Checks whether or not a given node is a string literal. 484 | * @param {ASTNode} node - A node to check. 485 | * @returns {boolean} `true` if the node is a string literal. 486 | */ 487 | isStringLiteral(node) { 488 | return (node.type === 'Literal' && typeof node.value === 'string') || node.type === 'TemplateLiteral' 489 | }, 490 | 491 | /** 492 | * Checks whether a given node is a breakable statement or not. 493 | * The node is breakable if the node is one of the following type: 494 | * 495 | * - DoWhileStatement 496 | * - ForInStatement 497 | * - ForOfStatement 498 | * - ForStatement 499 | * - SwitchStatement 500 | * - WhileStatement 501 | * 502 | * @param {ASTNode} node - A node to check. 503 | * @returns {boolean} `true` if the node is breakable. 504 | */ 505 | isBreakableStatement(node) { 506 | return breakableTypePattern.test(node.type) 507 | }, 508 | 509 | /** 510 | * Gets the label if the parent node of a given node is a LabeledStatement. 511 | * 512 | * @param {ASTNode} node - A node to get. 513 | * @returns {string|null} The label or `null`. 514 | */ 515 | getLabel(node) { 516 | if (node.parent.type === 'LabeledStatement') { 517 | return node.parent.label.name 518 | } 519 | return null 520 | }, 521 | 522 | /** 523 | * Gets references which are non initializer and writable. 524 | * @param {Reference[]} references - An array of references. 525 | * @returns {Reference[]} An array of only references which are non initializer and writable. 526 | * @public 527 | */ 528 | getModifyingReferences(references) { 529 | return references.filter(isModifyingReference) 530 | }, 531 | 532 | /** 533 | * Validate that a string passed in is surrounded by the specified character 534 | * @param {string} val The text to check. 535 | * @param {string} character The character to see if it's surrounded by. 536 | * @returns {boolean} True if the text is surrounded by the character, false if not. 537 | * @private 538 | */ 539 | isSurroundedBy(val, character) { 540 | return val[0] === character && val[val.length - 1] === character 541 | }, 542 | 543 | /** 544 | * Returns whether the provided node is an ESLint directive comment or not 545 | * @param {Line|Block} node The comment token to be checked 546 | * @returns {boolean} `true` if the node is an ESLint directive comment 547 | */ 548 | isDirectiveComment(node) { 549 | const comment = node.value.trim() 550 | 551 | return ( 552 | (node.type === 'Line' && comment.indexOf('eslint-') === 0) || 553 | (node.type === 'Block' && 554 | (comment.indexOf('global ') === 0 || comment.indexOf('eslint ') === 0 || comment.indexOf('eslint-') === 0)) 555 | ) 556 | }, 557 | 558 | /** 559 | * Gets the trailing statement of a given node. 560 | * 561 | * if (code) 562 | * consequent; 563 | * 564 | * When taking this `IfStatement`, returns `consequent;` statement. 565 | * 566 | * @param {ASTNode} A node to get. 567 | * @returns {ASTNode|null} The trailing statement's node. 568 | */ 569 | getTrailingStatement: esutils.ast.trailingStatement, 570 | 571 | /** 572 | * Finds the variable by a given name in a given scope and its upper scopes. 573 | * 574 | * @param {eslint-scope.Scope} initScope - A scope to start find. 575 | * @param {string} name - A variable name to find. 576 | * @returns {eslint-scope.Variable|null} A found variable or `null`. 577 | */ 578 | getVariableByName(initScope, name) { 579 | let scope = initScope 580 | 581 | while (scope) { 582 | const variable = scope.set.get(name) 583 | 584 | if (variable) { 585 | return variable 586 | } 587 | 588 | scope = scope.upper 589 | } 590 | 591 | return null 592 | }, 593 | 594 | /** 595 | * Checks whether or not a given function node is the default `this` binding. 596 | * 597 | * First, this checks the node: 598 | * 599 | * - The function name does not start with uppercase (it's a constructor). 600 | * - The function does not have a JSDoc comment that has a @this tag. 601 | * 602 | * Next, this checks the location of the node. 603 | * If the location is below, this judges `this` is valid. 604 | * 605 | * - The location is not on an object literal. 606 | * - The location is not assigned to a variable which starts with an uppercase letter. 607 | * - The location is not on an ES2015 class. 608 | * - Its `bind`/`call`/`apply` method is not called directly. 609 | * - The function is not a callback of array methods (such as `.forEach()`) if `thisArg` is given. 610 | * 611 | * @param {ASTNode} node - A function node to check. 612 | * @param {SourceCode} sourceCode - A SourceCode instance to get comments. 613 | * @returns {boolean} The function node is the default `this` binding. 614 | */ 615 | isDefaultThisBinding(node, sourceCode) { 616 | if (isES5Constructor(node) || hasJSDocThisTag(node, sourceCode)) { 617 | return false 618 | } 619 | const isAnonymous = node.id === null 620 | let currentNode = node 621 | 622 | while (currentNode) { 623 | const parent = currentNode.parent 624 | 625 | switch (parent.type) { 626 | /* 627 | * Looks up the destination. 628 | * e.g., obj.foo = nativeFoo || function foo() { ... }; 629 | */ 630 | case 'LogicalExpression': 631 | case 'ConditionalExpression': 632 | currentNode = parent 633 | break 634 | 635 | /* 636 | * If the upper function is IIFE, checks the destination of the return value. 637 | * e.g. 638 | * obj.foo = (function() { 639 | * // setup... 640 | * return function foo() { ... }; 641 | * })(); 642 | * obj.foo = (() => 643 | * function foo() { ... } 644 | * )(); 645 | */ 646 | case 'ReturnStatement': { 647 | const func = getUpperFunction(parent) 648 | 649 | if (func === null || !isCallee(func)) { 650 | return true 651 | } 652 | currentNode = func.parent 653 | break 654 | } 655 | case 'ArrowFunctionExpression': 656 | if (currentNode !== parent.body || !isCallee(parent)) { 657 | return true 658 | } 659 | currentNode = parent.parent 660 | break 661 | 662 | /* 663 | * e.g. 664 | * var obj = { foo() { ... } }; 665 | * var obj = { foo: function() { ... } }; 666 | * class A { constructor() { ... } } 667 | * class A { foo() { ... } } 668 | * class A { get foo() { ... } } 669 | * class A { set foo() { ... } } 670 | * class A { static foo() { ... } } 671 | */ 672 | case 'Property': 673 | case 'MethodDefinition': 674 | return parent.value !== currentNode 675 | 676 | /* 677 | * e.g. 678 | * obj.foo = function foo() { ... }; 679 | * Foo = function() { ... }; 680 | * [obj.foo = function foo() { ... }] = a; 681 | * [Foo = function() { ... }] = a; 682 | */ 683 | case 'AssignmentExpression': 684 | case 'AssignmentPattern': 685 | if (parent.left.type === 'MemberExpression') { 686 | return false 687 | } 688 | if (isAnonymous && parent.left.type === 'Identifier' && startsWithUpperCase(parent.left.name)) { 689 | return false 690 | } 691 | return true 692 | 693 | /* 694 | * e.g. 695 | * var Foo = function() { ... }; 696 | */ 697 | case 'VariableDeclarator': 698 | return !( 699 | isAnonymous && 700 | parent.init === currentNode && 701 | parent.id.type === 'Identifier' && 702 | startsWithUpperCase(parent.id.name) 703 | ) 704 | 705 | /* 706 | * e.g. 707 | * var foo = function foo() { ... }.bind(obj); 708 | * (function foo() { ... }).call(obj); 709 | * (function foo() { ... }).apply(obj, []); 710 | */ 711 | case 'MemberExpression': 712 | return ( 713 | parent.object !== currentNode || 714 | parent.property.type !== 'Identifier' || 715 | !bindOrCallOrApplyPattern.test(parent.property.name) || 716 | !isCallee(parent) || 717 | parent.parent.arguments.length === 0 || 718 | isNullOrUndefined(parent.parent.arguments[0]) 719 | ) 720 | 721 | /* 722 | * e.g. 723 | * Reflect.apply(function() {}, obj, []); 724 | * Array.from([], function() {}, obj); 725 | * list.forEach(function() {}, obj); 726 | */ 727 | case 'CallExpression': 728 | if (isReflectApply(parent.callee)) { 729 | return ( 730 | parent.arguments.length !== 3 || 731 | parent.arguments[0] !== currentNode || 732 | isNullOrUndefined(parent.arguments[1]) 733 | ) 734 | } 735 | if (isArrayFromMethod(parent.callee)) { 736 | return ( 737 | parent.arguments.length !== 3 || 738 | parent.arguments[1] !== currentNode || 739 | isNullOrUndefined(parent.arguments[2]) 740 | ) 741 | } 742 | if (isMethodWhichHasThisArg(parent.callee)) { 743 | return ( 744 | parent.arguments.length !== 2 || 745 | parent.arguments[0] !== currentNode || 746 | isNullOrUndefined(parent.arguments[1]) 747 | ) 748 | } 749 | return true 750 | 751 | // Otherwise `this` is default. 752 | default: 753 | return true 754 | } 755 | } 756 | 757 | /* istanbul ignore next */ 758 | return true 759 | }, 760 | 761 | /** 762 | * Get the precedence level based on the node type 763 | * @param {ASTNode} node node to evaluate 764 | * @returns {int} precedence level 765 | * @private 766 | */ 767 | getPrecedence(node) { 768 | switch (node.type) { 769 | case 'SequenceExpression': 770 | return 0 771 | 772 | case 'AssignmentExpression': 773 | case 'ArrowFunctionExpression': 774 | case 'YieldExpression': 775 | return 1 776 | 777 | case 'ConditionalExpression': 778 | return 3 779 | 780 | case 'LogicalExpression': 781 | switch (node.operator) { 782 | case '||': 783 | return 4 784 | case '&&': 785 | return 5 786 | 787 | // no default 788 | } 789 | 790 | /* falls through */ 791 | 792 | case 'BinaryExpression': 793 | switch (node.operator) { 794 | case '|': 795 | return 6 796 | case '^': 797 | return 7 798 | case '&': 799 | return 8 800 | case '==': 801 | case '!=': 802 | case '===': 803 | case '!==': 804 | return 9 805 | case '<': 806 | case '<=': 807 | case '>': 808 | case '>=': 809 | case 'in': 810 | case 'instanceof': 811 | return 10 812 | case '<<': 813 | case '>>': 814 | case '>>>': 815 | return 11 816 | case '+': 817 | case '-': 818 | return 12 819 | case '*': 820 | case '/': 821 | case '%': 822 | return 13 823 | case '**': 824 | return 15 825 | 826 | // no default 827 | } 828 | 829 | /* falls through */ 830 | 831 | case 'UnaryExpression': 832 | case 'AwaitExpression': 833 | return 16 834 | 835 | case 'UpdateExpression': 836 | return 17 837 | 838 | case 'CallExpression': 839 | return 18 840 | 841 | case 'NewExpression': 842 | return 19 843 | 844 | default: 845 | return 20 846 | } 847 | }, 848 | 849 | /** 850 | * Checks whether the given node is an empty block node or not. 851 | * 852 | * @param {ASTNode|null} node - The node to check. 853 | * @returns {boolean} `true` if the node is an empty block. 854 | */ 855 | isEmptyBlock(node) { 856 | return Boolean(node && node.type === 'BlockStatement' && node.body.length === 0) 857 | }, 858 | 859 | /** 860 | * Checks whether the given node is an empty function node or not. 861 | * 862 | * @param {ASTNode|null} node - The node to check. 863 | * @returns {boolean} `true` if the node is an empty function. 864 | */ 865 | isEmptyFunction(node) { 866 | return isFunction(node) && module.exports.isEmptyBlock(node.body) 867 | }, 868 | 869 | /** 870 | * Gets the property name of a given node. 871 | * The node can be a MemberExpression, a Property, or a MethodDefinition. 872 | * 873 | * If the name is dynamic, this returns `null`. 874 | * 875 | * For examples: 876 | * 877 | * a.b // => "b" 878 | * a["b"] // => "b" 879 | * a['b'] // => "b" 880 | * a[`b`] // => "b" 881 | * a[100] // => "100" 882 | * a[b] // => null 883 | * a["a" + "b"] // => null 884 | * a[tag`b`] // => null 885 | * a[`${b}`] // => null 886 | * 887 | * let a = {b: 1} // => "b" 888 | * let a = {["b"]: 1} // => "b" 889 | * let a = {['b']: 1} // => "b" 890 | * let a = {[`b`]: 1} // => "b" 891 | * let a = {[100]: 1} // => "100" 892 | * let a = {[b]: 1} // => null 893 | * let a = {["a" + "b"]: 1} // => null 894 | * let a = {[tag`b`]: 1} // => null 895 | * let a = {[`${b}`]: 1} // => null 896 | * 897 | * @param {ASTNode} node - The node to get. 898 | * @returns {string|null} The property name if static. Otherwise, null. 899 | */ 900 | getStaticPropertyName(node) { 901 | let prop 902 | 903 | switch (node && node.type) { 904 | case 'Property': 905 | case 'MethodDefinition': 906 | prop = node.key 907 | break 908 | 909 | case 'MemberExpression': 910 | prop = node.property 911 | break 912 | 913 | // no default 914 | } 915 | 916 | switch (prop && prop.type) { 917 | case 'Literal': 918 | return String(prop.value) 919 | 920 | case 'TemplateLiteral': 921 | if (prop.expressions.length === 0 && prop.quasis.length === 1) { 922 | return prop.quasis[0].value.cooked 923 | } 924 | break 925 | 926 | case 'Identifier': 927 | if (!node.computed) { 928 | return prop.name 929 | } 930 | break 931 | 932 | // no default 933 | } 934 | 935 | return null 936 | }, 937 | 938 | /** 939 | * Get directives from directive prologue of a Program or Function node. 940 | * @param {ASTNode} node - The node to check. 941 | * @returns {ASTNode[]} The directives found in the directive prologue. 942 | */ 943 | getDirectivePrologue(node) { 944 | const directives = [] 945 | 946 | // Directive prologues only occur at the top of files or functions. 947 | if ( 948 | node.type === 'Program' || 949 | node.type === 'FunctionDeclaration' || 950 | node.type === 'FunctionExpression' || 951 | /* 952 | * Do not check arrow functions with implicit return. 953 | * `() => "use strict";` returns the string `"use strict"`. 954 | */ 955 | (node.type === 'ArrowFunctionExpression' && node.body.type === 'BlockStatement') 956 | ) { 957 | const statements = node.type === 'Program' ? node.body : node.body.body 958 | 959 | for (const statement of statements) { 960 | if (statement.type === 'ExpressionStatement' && statement.expression.type === 'Literal') { 961 | directives.push(statement) 962 | } else { 963 | break 964 | } 965 | } 966 | } 967 | 968 | return directives 969 | }, 970 | 971 | /** 972 | * Determines whether this node is a decimal integer literal. If a node is a decimal integer literal, a dot added 973 | * after the node will be parsed as a decimal point, rather than a property-access dot. 974 | * @param {ASTNode} node - The node to check. 975 | * @returns {boolean} `true` if this node is a decimal integer. 976 | * @example 977 | * 978 | * 5 // true 979 | * 5. // false 980 | * 5.0 // false 981 | * 05 // false 982 | * 0x5 // false 983 | * 0b101 // false 984 | * 0o5 // false 985 | * 5e0 // false 986 | * '5' // false 987 | */ 988 | isDecimalInteger(node) { 989 | return node.type === 'Literal' && typeof node.value === 'number' && /^(0|[1-9]\d*)$/.test(node.raw) 990 | }, 991 | 992 | /** 993 | * Gets the name and kind of the given function node. 994 | * 995 | * - `function foo() {}` .................... `function 'foo'` 996 | * - `(function foo() {})` .................. `function 'foo'` 997 | * - `(function() {})` ...................... `function` 998 | * - `function* foo() {}` ................... `generator function 'foo'` 999 | * - `(function* foo() {})` ................. `generator function 'foo'` 1000 | * - `(function*() {})` ..................... `generator function` 1001 | * - `() => {}` ............................. `arrow function` 1002 | * - `async () => {}` ....................... `async arrow function` 1003 | * - `({ foo: function foo() {} })` ......... `method 'foo'` 1004 | * - `({ foo: function() {} })` ............. `method 'foo'` 1005 | * - `({ ['foo']: function() {} })` ......... `method 'foo'` 1006 | * - `({ [foo]: function() {} })` ........... `method` 1007 | * - `({ foo() {} })` ....................... `method 'foo'` 1008 | * - `({ foo: function* foo() {} })` ........ `generator method 'foo'` 1009 | * - `({ foo: function*() {} })` ............ `generator method 'foo'` 1010 | * - `({ ['foo']: function*() {} })` ........ `generator method 'foo'` 1011 | * - `({ [foo]: function*() {} })` .......... `generator method` 1012 | * - `({ *foo() {} })` ...................... `generator method 'foo'` 1013 | * - `({ foo: async function foo() {} })` ... `async method 'foo'` 1014 | * - `({ foo: async function() {} })` ....... `async method 'foo'` 1015 | * - `({ ['foo']: async function() {} })` ... `async method 'foo'` 1016 | * - `({ [foo]: async function() {} })` ..... `async method` 1017 | * - `({ async foo() {} })` ................. `async method 'foo'` 1018 | * - `({ get foo() {} })` ................... `getter 'foo'` 1019 | * - `({ set foo(a) {} })` .................. `setter 'foo'` 1020 | * - `class A { constructor() {} }` ......... `constructor` 1021 | * - `class A { foo() {} }` ................. `method 'foo'` 1022 | * - `class A { *foo() {} }` ................ `generator method 'foo'` 1023 | * - `class A { async foo() {} }` ........... `async method 'foo'` 1024 | * - `class A { ['foo']() {} }` ............. `method 'foo'` 1025 | * - `class A { *['foo']() {} }` ............ `generator method 'foo'` 1026 | * - `class A { async ['foo']() {} }` ....... `async method 'foo'` 1027 | * - `class A { [foo]() {} }` ............... `method` 1028 | * - `class A { *[foo]() {} }` .............. `generator method` 1029 | * - `class A { async [foo]() {} }` ......... `async method` 1030 | * - `class A { get foo() {} }` ............. `getter 'foo'` 1031 | * - `class A { set foo(a) {} }` ............ `setter 'foo'` 1032 | * - `class A { static foo() {} }` .......... `static method 'foo'` 1033 | * - `class A { static *foo() {} }` ......... `static generator method 'foo'` 1034 | * - `class A { static async foo() {} }` .... `static async method 'foo'` 1035 | * - `class A { static get foo() {} }` ...... `static getter 'foo'` 1036 | * - `class A { static set foo(a) {} }` ..... `static setter 'foo'` 1037 | * 1038 | * @param {ASTNode} node - The function node to get. 1039 | * @returns {string} The name and kind of the function node. 1040 | */ 1041 | getFunctionNameWithKind(node) { 1042 | const parent = node.parent 1043 | const tokens = [] 1044 | 1045 | if (parent.type === 'MethodDefinition' && parent.static) { 1046 | tokens.push('static') 1047 | } 1048 | if (node.async) { 1049 | tokens.push('async') 1050 | } 1051 | if (node.generator) { 1052 | tokens.push('generator') 1053 | } 1054 | 1055 | if (node.type === 'ArrowFunctionExpression') { 1056 | tokens.push('arrow', 'function') 1057 | } else if (parent.type === 'Property' || parent.type === 'MethodDefinition') { 1058 | if (parent.kind === 'constructor') { 1059 | return 'constructor' 1060 | } 1061 | if (parent.kind === 'get') { 1062 | tokens.push('getter') 1063 | } else if (parent.kind === 'set') { 1064 | tokens.push('setter') 1065 | } else { 1066 | tokens.push('method') 1067 | } 1068 | } else { 1069 | tokens.push('function') 1070 | } 1071 | 1072 | if (node.id) { 1073 | tokens.push(`'${node.id.name}'`) 1074 | } else { 1075 | const name = module.exports.getStaticPropertyName(parent) 1076 | 1077 | if (name) { 1078 | tokens.push(`'${name}'`) 1079 | } 1080 | } 1081 | 1082 | return tokens.join(' ') 1083 | }, 1084 | 1085 | /** 1086 | * Gets the location of the given function node for reporting. 1087 | * 1088 | * - `function foo() {}` 1089 | * ^^^^^^^^^^^^ 1090 | * - `(function foo() {})` 1091 | * ^^^^^^^^^^^^ 1092 | * - `(function() {})` 1093 | * ^^^^^^^^ 1094 | * - `function* foo() {}` 1095 | * ^^^^^^^^^^^^^ 1096 | * - `(function* foo() {})` 1097 | * ^^^^^^^^^^^^^ 1098 | * - `(function*() {})` 1099 | * ^^^^^^^^^ 1100 | * - `() => {}` 1101 | * ^^ 1102 | * - `async () => {}` 1103 | * ^^ 1104 | * - `({ foo: function foo() {} })` 1105 | * ^^^^^^^^^^^^^^^^^ 1106 | * - `({ foo: function() {} })` 1107 | * ^^^^^^^^^^^^^ 1108 | * - `({ ['foo']: function() {} })` 1109 | * ^^^^^^^^^^^^^^^^^ 1110 | * - `({ [foo]: function() {} })` 1111 | * ^^^^^^^^^^^^^^^ 1112 | * - `({ foo() {} })` 1113 | * ^^^ 1114 | * - `({ foo: function* foo() {} })` 1115 | * ^^^^^^^^^^^^^^^^^^ 1116 | * - `({ foo: function*() {} })` 1117 | * ^^^^^^^^^^^^^^ 1118 | * - `({ ['foo']: function*() {} })` 1119 | * ^^^^^^^^^^^^^^^^^^ 1120 | * - `({ [foo]: function*() {} })` 1121 | * ^^^^^^^^^^^^^^^^ 1122 | * - `({ *foo() {} })` 1123 | * ^^^^ 1124 | * - `({ foo: async function foo() {} })` 1125 | * ^^^^^^^^^^^^^^^^^^^^^^^ 1126 | * - `({ foo: async function() {} })` 1127 | * ^^^^^^^^^^^^^^^^^^^ 1128 | * - `({ ['foo']: async function() {} })` 1129 | * ^^^^^^^^^^^^^^^^^^^^^^^ 1130 | * - `({ [foo]: async function() {} })` 1131 | * ^^^^^^^^^^^^^^^^^^^^^ 1132 | * - `({ async foo() {} })` 1133 | * ^^^^^^^^^ 1134 | * - `({ get foo() {} })` 1135 | * ^^^^^^^ 1136 | * - `({ set foo(a) {} })` 1137 | * ^^^^^^^ 1138 | * - `class A { constructor() {} }` 1139 | * ^^^^^^^^^^^ 1140 | * - `class A { foo() {} }` 1141 | * ^^^ 1142 | * - `class A { *foo() {} }` 1143 | * ^^^^ 1144 | * - `class A { async foo() {} }` 1145 | * ^^^^^^^^^ 1146 | * - `class A { ['foo']() {} }` 1147 | * ^^^^^^^ 1148 | * - `class A { *['foo']() {} }` 1149 | * ^^^^^^^^ 1150 | * - `class A { async ['foo']() {} }` 1151 | * ^^^^^^^^^^^^^ 1152 | * - `class A { [foo]() {} }` 1153 | * ^^^^^ 1154 | * - `class A { *[foo]() {} }` 1155 | * ^^^^^^ 1156 | * - `class A { async [foo]() {} }` 1157 | * ^^^^^^^^^^^ 1158 | * - `class A { get foo() {} }` 1159 | * ^^^^^^^ 1160 | * - `class A { set foo(a) {} }` 1161 | * ^^^^^^^ 1162 | * - `class A { static foo() {} }` 1163 | * ^^^^^^^^^^ 1164 | * - `class A { static *foo() {} }` 1165 | * ^^^^^^^^^^^ 1166 | * - `class A { static async foo() {} }` 1167 | * ^^^^^^^^^^^^^^^^ 1168 | * - `class A { static get foo() {} }` 1169 | * ^^^^^^^^^^^^^^ 1170 | * - `class A { static set foo(a) {} }` 1171 | * ^^^^^^^^^^^^^^ 1172 | * 1173 | * @param {ASTNode} node - The function node to get. 1174 | * @param {SourceCode} sourceCode - The source code object to get tokens. 1175 | * @returns {string} The location of the function node for reporting. 1176 | */ 1177 | getFunctionHeadLoc(node, sourceCode) { 1178 | const parent = node.parent 1179 | let start = null 1180 | let end = null 1181 | 1182 | if (node.type === 'ArrowFunctionExpression') { 1183 | const arrowToken = sourceCode.getTokenBefore(node.body, isArrowToken) 1184 | 1185 | start = arrowToken.loc.start 1186 | end = arrowToken.loc.end 1187 | } else if (parent.type === 'Property' || parent.type === 'MethodDefinition') { 1188 | start = parent.loc.start 1189 | end = getOpeningParenOfParams(node, sourceCode).loc.start 1190 | } else { 1191 | start = node.loc.start 1192 | end = getOpeningParenOfParams(node, sourceCode).loc.start 1193 | } 1194 | 1195 | return { 1196 | start: Object.assign({}, start), 1197 | end: Object.assign({}, end), 1198 | } 1199 | }, 1200 | 1201 | /** 1202 | * Gets the parenthesized text of a node. This is similar to sourceCode.getText(node), but it also includes any parentheses 1203 | * surrounding the node. 1204 | * @param {SourceCode} sourceCode The source code object 1205 | * @param {ASTNode} node An expression node 1206 | * @returns {string} The text representing the node, with all surrounding parentheses included 1207 | */ 1208 | getParenthesisedText(sourceCode, node) { 1209 | let leftToken = sourceCode.getFirstToken(node) 1210 | let rightToken = sourceCode.getLastToken(node) 1211 | 1212 | while ( 1213 | sourceCode.getTokenBefore(leftToken) && 1214 | sourceCode.getTokenBefore(leftToken).type === 'Punctuator' && 1215 | sourceCode.getTokenBefore(leftToken).value === '(' && 1216 | sourceCode.getTokenAfter(rightToken) && 1217 | sourceCode.getTokenAfter(rightToken).type === 'Punctuator' && 1218 | sourceCode.getTokenAfter(rightToken).value === ')' 1219 | ) { 1220 | leftToken = sourceCode.getTokenBefore(leftToken) 1221 | rightToken = sourceCode.getTokenAfter(rightToken) 1222 | } 1223 | 1224 | return sourceCode.getText().slice(leftToken.range[0], rightToken.range[1]) 1225 | }, 1226 | 1227 | /* 1228 | * Determine if a node has a possiblity to be an Error object 1229 | * @param {ASTNode} node ASTNode to check 1230 | * @returns {boolean} True if there is a chance it contains an Error obj 1231 | */ 1232 | couldBeError(node) { 1233 | switch (node.type) { 1234 | case 'Identifier': 1235 | case 'CallExpression': 1236 | case 'NewExpression': 1237 | case 'MemberExpression': 1238 | case 'TaggedTemplateExpression': 1239 | case 'YieldExpression': 1240 | case 'AwaitExpression': 1241 | return true // possibly an error object. 1242 | 1243 | case 'AssignmentExpression': 1244 | return module.exports.couldBeError(node.right) 1245 | 1246 | case 'SequenceExpression': { 1247 | const exprs = node.expressions 1248 | 1249 | return exprs.length !== 0 && module.exports.couldBeError(exprs[exprs.length - 1]) 1250 | } 1251 | 1252 | case 'LogicalExpression': 1253 | return module.exports.couldBeError(node.left) || module.exports.couldBeError(node.right) 1254 | 1255 | case 'ConditionalExpression': 1256 | return module.exports.couldBeError(node.consequent) || module.exports.couldBeError(node.alternate) 1257 | 1258 | default: 1259 | return false 1260 | } 1261 | }, 1262 | 1263 | /** 1264 | * Determines whether the given node is a `null` literal. 1265 | * @param {ASTNode} node The node to check 1266 | * @returns {boolean} `true` if the node is a `null` literal 1267 | */ 1268 | isNullLiteral(node) { 1269 | /* 1270 | * Checking `node.value === null` does not guarantee that a literal is a null literal. 1271 | * When parsing values that cannot be represented in the current environment (e.g. unicode 1272 | * regexes in Node 4), `node.value` is set to `null` because it wouldn't be possible to 1273 | * set `node.value` to a unicode regex. To make sure a literal is actually `null`, check 1274 | * `node.regex` instead. Also see: https://github.com/eslint/eslint/issues/8020 1275 | */ 1276 | return node.type === 'Literal' && node.value === null && !node.regex 1277 | }, 1278 | 1279 | /** 1280 | * Determines whether two tokens can safely be placed next to each other without merging into a single token 1281 | * @param {Token|string} leftValue The left token. If this is a string, it will be tokenized and the last token will be used. 1282 | * @param {Token|string} rightValue The right token. If this is a string, it will be tokenized and the first token will be used. 1283 | * @returns {boolean} If the tokens cannot be safely placed next to each other, returns `false`. If the tokens can be placed 1284 | * next to each other, behavior is undefined (although it should return `true` in most cases). 1285 | */ 1286 | canTokensBeAdjacent(leftValue, rightValue) { 1287 | let leftToken 1288 | 1289 | if (typeof leftValue === 'string') { 1290 | const leftTokens = espree.tokenize(leftValue, { ecmaVersion: 2015 }) 1291 | 1292 | leftToken = leftTokens[leftTokens.length - 1] 1293 | } else { 1294 | leftToken = leftValue 1295 | } 1296 | 1297 | const rightToken = 1298 | typeof rightValue === 'string' ? espree.tokenize(rightValue, { ecmaVersion: 2015 })[0] : rightValue 1299 | 1300 | if (leftToken.type === 'Punctuator' || rightToken.type === 'Punctuator') { 1301 | if (leftToken.type === 'Punctuator' && rightToken.type === 'Punctuator') { 1302 | const PLUS_TOKENS = new Set(['+', '++']) 1303 | const MINUS_TOKENS = new Set(['-', '--']) 1304 | 1305 | return !( 1306 | (PLUS_TOKENS.has(leftToken.value) && PLUS_TOKENS.has(rightToken.value)) || 1307 | (MINUS_TOKENS.has(leftToken.value) && MINUS_TOKENS.has(rightToken.value)) 1308 | ) 1309 | } 1310 | return true 1311 | } 1312 | 1313 | if ( 1314 | leftToken.type === 'String' || 1315 | rightToken.type === 'String' || 1316 | leftToken.type === 'Template' || 1317 | rightToken.type === 'Template' 1318 | ) { 1319 | return true 1320 | } 1321 | 1322 | if (leftToken.type !== 'Numeric' && rightToken.type === 'Numeric' && rightToken.value.startsWith('.')) { 1323 | return true 1324 | } 1325 | 1326 | return false 1327 | }, 1328 | } 1329 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-plugin-sort-keys-fix", 3 | "version": "1.1.2", 4 | "description": "Fork of eslint rule that sorts keys in objects (https://eslint.org/docs/rules/sort-keys) with autofix enabled", 5 | "repository": { 6 | "url": "https://github.com/leo-buneev/eslint-plugin-sort-keys-fix", 7 | "type": "git" 8 | }, 9 | "keywords": [ 10 | "eslint", 11 | "eslintplugin", 12 | "eslint-plugin" 13 | ], 14 | "author": "Leonid Buneev ", 15 | "main": "lib/index.js", 16 | "scripts": { 17 | "lint": "eslint ./lib", 18 | "lint-fix": "eslint ./lib --fix", 19 | "test": "jest", 20 | "test-debug": "node --inspect node_modules/.bin/jest --watch --runInBand" 21 | }, 22 | "dependencies": { 23 | "espree": "^6.1.2", 24 | "esutils": "^2.0.2", 25 | "natural-compare": "^1.4.0", 26 | "requireindex": "~1.2.0" 27 | }, 28 | "devDependencies": { 29 | "babel-eslint": "^10.1.0", 30 | "eslint": "~6.6.0", 31 | "eslint-config-prettier": "^6.5.0", 32 | "eslint-config-standard": "^14.1.0", 33 | "eslint-plugin-import": "^2.18.2", 34 | "eslint-plugin-node": "^10.0.0", 35 | "eslint-plugin-prettier": "^3.1.1", 36 | "eslint-plugin-promise": "^4.2.1", 37 | "eslint-plugin-standard": "^4.0.1", 38 | "eslint-plugin-tyrecheck": "https://tycgitlab.tyrecheck.com/leonid.buneev/eslint-plugin-tyrecheck.git", 39 | "jest": "^25.4.0", 40 | "prettier": "^1.19.1" 41 | }, 42 | "engines": { 43 | "node": ">=0.10.0" 44 | }, 45 | "license": "ISC" 46 | } 47 | --------------------------------------------------------------------------------