├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ ├── publish.yml │ └── tests.yml ├── .gitignore ├── .huskyrc.js ├── .prettierignore ├── .prettierrc.js ├── .yarnrc ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── babel.config.js ├── docs └── rules │ ├── interface.md │ └── string-enum.md ├── jest.config.js ├── lint-staged.config.js ├── package.json ├── rollup.config.js ├── src ├── common │ └── options.ts ├── config │ └── recommended.ts ├── index.ts ├── rules │ ├── index.ts │ ├── interface.ts │ └── string-enum.ts └── utils │ ├── ast.ts │ ├── common.ts │ ├── compare.ts │ ├── plugin.ts │ └── rule.ts ├── tests ├── autofix.spec.ts ├── config.spec.ts ├── fixtures │ ├── autofix.input.ts │ ├── autofix.output.ts │ └── requiredFirst.output.ts ├── helpers │ ├── configs.ts │ ├── tsconfig.json │ └── util.ts ├── rules │ ├── .prettierrc.js │ ├── interface.spec.ts │ └── string-enum.spec.ts └── tsconfig.json ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | quote_type = single -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | tests/fixtures/** 2 | dist 3 | lib 4 | build 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | plugins: ['@typescript-eslint', 'eslint-plugin', 'import', 'jest', 'prettier'], 5 | env: { 6 | es6: true, 7 | node: true, 8 | }, 9 | extends: [ 10 | 'eslint:recommended', 11 | 'plugin:import/errors', 12 | 'plugin:import/warnings', 13 | 'plugin:eslint-plugin/all', 14 | 'plugin:prettier/recommended', 15 | ], 16 | parserOptions: { 17 | ecmaVersion: 10, 18 | sourceType: 'module', 19 | }, 20 | rules: { 21 | 'no-console': 'warn', 22 | }, 23 | overrides: [ 24 | { 25 | files: ['*.ts', '*.tsx'], 26 | extends: ['plugin:import/typescript', 'plugin:@typescript-eslint/recommended'], 27 | 28 | rules: { 29 | '@typescript-eslint/explicit-function-return-type': 'off', 30 | '@typescript-eslint/explicit-module-boundary-types': 'off', 31 | '@typescript-eslint/ban-ts-ignore': 'off', 32 | '@typescript-eslint/no-explicit-any': 'off', 33 | }, 34 | 35 | parserOptions: { 36 | project: ['./tsconfig.json', './tests/tsconfig.json'], 37 | }, 38 | overrides: [ 39 | { 40 | files: ['tests/**'], 41 | env: { 42 | jest: true, 43 | }, 44 | rules: { 45 | 'jest/no-disabled-tests': 'warn', 46 | 'jest/no-focused-tests': 'error', 47 | 'jest/no-alias-methods': 'error', 48 | 'jest/no-identical-title': 'error', 49 | 'jest/no-jasmine-globals': 'error', 50 | 'jest/no-test-prefixes': 'error', 51 | 'jest/no-test-return-statement': 'error', 52 | 'jest/prefer-to-have-length': 'warn', 53 | 'jest/prefer-spy-on': 'error', 54 | 'jest/valid-expect': 'error', 55 | 'jest/no-test-callback': 'off', 56 | }, 57 | }, 58 | ], 59 | }, 60 | ], 61 | settings: { 62 | 'import/resolver': { 63 | node: { 64 | moduleDirectory: ['node_modules', 'src'], 65 | }, 66 | }, 67 | }, 68 | } 69 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages 3 | 4 | name: Publish 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions/setup-node@v1 16 | with: 17 | node-version: 18 18 | - run: yarn 19 | - run: yarn verify 20 | 21 | publish-npm: 22 | needs: build 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v2 26 | - uses: actions/setup-node@v1 27 | with: 28 | node-version: 18 29 | registry-url: https://registry.npmjs.org/ 30 | - run: yarn 31 | - run: npm publish 32 | env: 33 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 34 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Tests 5 | 6 | on: 7 | push: 8 | branches: [master] 9 | pull_request: 10 | branches: [master] 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | matrix: 18 | node-version: [16.x, 18.x, 20.x] 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v1 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | - run: yarn 27 | - run: yarn verify 28 | env: 29 | CI: true 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (https://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # TypeScript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | .env.test 62 | 63 | # parcel-bundler cache (https://parceljs.org/) 64 | .cache 65 | 66 | # next.js build output 67 | .next 68 | 69 | # nuxt.js build output 70 | .nuxt 71 | 72 | # vuepress build output 73 | .vuepress/dist 74 | 75 | # Serverless directories 76 | .serverless/ 77 | 78 | # FuseBox cache 79 | .fusebox/ 80 | 81 | # DynamoDB Local files 82 | .dynamodb/ 83 | 84 | # IDE 85 | .vscode 86 | 87 | # Build artifacts 88 | dist 89 | build 90 | lib 91 | -------------------------------------------------------------------------------- /.huskyrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | hooks: { 3 | 'pre-commit': 'lint-staged', 4 | }, 5 | } 6 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | tests/fixtures/** 2 | build 3 | dist 4 | lib 5 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 90, 3 | trailingComma: 'all', 4 | arrowParens: 'avoid', 5 | semi: false, 6 | } 7 | -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | save-prefix "~" 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [3.3.0] - 2024-10-05 9 | 10 | ### Changed 11 | 12 | - Update @typescript-eslint/parser to >=6 13 | 14 | ## [3.2.0] - 2024-02-26 15 | 16 | ### Changed 17 | 18 | - Update @typescript-eslint/parser to v7 19 | 20 | ## [3.1.0] - 2023-10-15 21 | 22 | ### Changed 23 | 24 | - Fix esm export paths in config 25 | 26 | ## [3.0.0] - 2023-08-30 27 | 28 | ### Changed 29 | 30 | - [BREAKING] Minimal required node.js version is v16 31 | - [BREAKING] Update @typescript-eslint/parser to v6 🎉 32 | 33 | ## [2.3.0] - 2023-03-17 34 | 35 | ### Changed 36 | 37 | - Bump deps to fix security vulnerabilities 38 | 39 | ## [2.2.0] - 2023-03-17 40 | 41 | ### Changed 42 | 43 | - Bump peer dependencies 44 | 45 | ## [2.1.0] - 2021-11-23 46 | 47 | ### Changed 48 | 49 | - Bump deps to fix security vulnerabilities 50 | 51 | ## [2.0.0] - 2021-10-15 52 | 53 | ### Changed 54 | 55 | - [BREAKING] Drop support for node v10 56 | - [BREAKING] Update to eslint v8 🎉 57 | 58 | ## [1.8.0] - 2021-08-17 59 | 60 | ### Changed 61 | 62 | - Update super old `@typescript-eslint/experimental-utils` dependency 63 | - Update dev dependencies 64 | 65 | ## [1.7.0] - 2021-06-18 66 | 67 | ### Changed 68 | 69 | - Update dependencies with security issues 70 | - Update dev dependencies 71 | 72 | ## [1.6.0] - 2021-04-02 73 | 74 | ### Changed 75 | 76 | - Update dependencies with security issues 77 | - Update `package.json` `export` field 78 | 79 | ## [1.5.0] - 2020-09-22 80 | 81 | ### Changed 82 | 83 | - Use `^` to pin dependencies 84 | 85 | ## [1.4.0] - 2020-09-22 86 | 87 | ### Changed 88 | 89 | - Support typescript v4 and @typescript-eslint/parser v4 as peer deps 90 | 91 | ## [1.3.0] - 2020-07-16 92 | 93 | ### Changed 94 | 95 | - Bump @typescript-eslint/parser to v3.5.0 96 | 97 | ## [1.2.0] - 2020-05-24 98 | 99 | ### Added 100 | 101 | - Explicitly list supported node versions 102 | 103 | ### Fixed 104 | 105 | - Node conditional exports paths 106 | 107 | ## [1.1.0] - 2020-05-24 108 | 109 | ### Fixed 110 | 111 | - Add explicit dependency of json-schema for @typescript-eslint/experimental-utils 112 | 113 | ## [1.0.2] - 2020-05-22 114 | 115 | ### Changed 116 | 117 | - Update build artifacts 118 | 119 | ## [1.0.1] - 2020-05-22 120 | 121 | ### Fixed 122 | 123 | - Fix hanging Publish github action 124 | 125 | ## [1.0.0] - 2020-05-22 126 | 127 | ### Changed 128 | 129 | - Rewrite to typescript with strong types 130 | - Leverage helpers and types from @typescript-eslint/experimental-utils 131 | - Run autofix tests with ESLint Class rather than spawn a child process for eslint runner 132 | - Heavy refactoring and remove code paths that were never taken 133 | - Update ESLint config 134 | - Update to ESLint v7.0.0 135 | - Update dependencies 136 | 137 | ### Added 138 | 139 | - Follow semver 140 | - Rollup bundler 141 | 142 | ## [0.10.0] - 2020-05-21 143 | 144 | ### Added 145 | 146 | - Add ESLint 7 to peerDependencies 147 | 148 | ## [0.9.0] - 2020-05-19 149 | 150 | ### Added 151 | 152 | - New option to `interface` rule: `requiredFirst` - if `true`, enforce optional properties to come after required ones. 153 | 154 | ## [0.8.0] - 2020-04-02 155 | 156 | ### Added 157 | 158 | - License 159 | 160 | ### Fixed 161 | 162 | - Fix linter crash on accessing node key name 163 | 164 | [3.3.0]: https://github.com/infctr/eslint-plugin-typescript-sort-keys/compare/v3.2.0...v3.3.0 165 | [3.2.0]: https://github.com/infctr/eslint-plugin-typescript-sort-keys/compare/v3.1.0...v3.2.0 166 | [3.1.0]: https://github.com/infctr/eslint-plugin-typescript-sort-keys/compare/v3.0.0...v3.1.0 167 | [3.0.0]: https://github.com/infctr/eslint-plugin-typescript-sort-keys/compare/v2.3.0...v3.0.0 168 | [2.3.0]: https://github.com/infctr/eslint-plugin-typescript-sort-keys/compare/v2.2.0...v2.3.0 169 | [2.2.0]: https://github.com/infctr/eslint-plugin-typescript-sort-keys/compare/v2.1.0...v2.2.0 170 | [2.1.0]: https://github.com/infctr/eslint-plugin-typescript-sort-keys/compare/v2.0.0...v2.1.0 171 | [2.0.0]: https://github.com/infctr/eslint-plugin-typescript-sort-keys/compare/v1.8.0...v2.0.0 172 | [1.8.0]: https://github.com/infctr/eslint-plugin-typescript-sort-keys/compare/v1.7.0...v1.8.0 173 | [1.7.0]: https://github.com/infctr/eslint-plugin-typescript-sort-keys/compare/v1.6.0...v1.7.0 174 | [1.6.0]: https://github.com/infctr/eslint-plugin-typescript-sort-keys/compare/v1.5.0...v1.6.0 175 | [1.5.0]: https://github.com/infctr/eslint-plugin-typescript-sort-keys/compare/v1.4.0...v1.5.0 176 | [1.4.0]: https://github.com/infctr/eslint-plugin-typescript-sort-keys/compare/v1.3.0...v1.4.0 177 | [1.3.0]: https://github.com/infctr/eslint-plugin-typescript-sort-keys/compare/v1.2.0...v1.3.0 178 | [1.2.0]: https://github.com/infctr/eslint-plugin-typescript-sort-keys/compare/v1.1.0...v1.2.0 179 | [1.1.0]: https://github.com/infctr/eslint-plugin-typescript-sort-keys/compare/v1.0.2...v1.1.0 180 | [1.0.2]: https://github.com/infctr/eslint-plugin-typescript-sort-keys/compare/v1.0.1...v1.0.2 181 | [1.0.1]: https://github.com/infctr/eslint-plugin-typescript-sort-keys/compare/v1.0.0...v1.0.1 182 | [1.0.0]: https://github.com/infctr/eslint-plugin-typescript-sort-keys/compare/v0.10.0...v1.0.0 183 | [0.10.0]: https://github.com/infctr/eslint-plugin-typescript-sort-keys/compare/v0.9.0...v0.10.0 184 | [0.9.0]: https://github.com/infctr/eslint-plugin-typescript-sort-keys/compare/v0.8.0...v0.9.0 185 | [0.8.0]: https://github.com/infctr/eslint-plugin-typescript-sort-keys/compare/v0.7.0...v0.8.0 186 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019, infctr 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose 4 | with or without fee is hereby granted, provided that the above copyright notice 5 | and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 9 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 11 | OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 12 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 13 | THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Tests](https://github.com/infctr/eslint-plugin-typescript-sort-keys/workflows/Tests/badge.svg?branch=master) 2 | 3 | # eslint-plugin-typescript-sort-keys 4 | 5 | Sort interface and string enum keys 6 | 7 | Inspired by and sourced from [eslint/sort-keys]([https://github.com/eslint/eslint/blob/master/docs/rules/sort-keys.md](https://github.com/eslint/eslint/blob/main/docs/src/rules/sort-keys.md)) 8 | 9 | ## Installation 10 | 11 | You'll first need to install 12 | 13 | - [eslint](http://eslint.org) 14 | - [typescript](http://www.typescriptlang.org/) 15 | - [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/parser) 16 | 17 | ```sh 18 | yarn add -D eslint typescript @typescript-eslint/parser 19 | ``` 20 | 21 | Next, install `eslint-plugin-typescript-sort-keys`: 22 | 23 | ```sh 24 | yarn add -D eslint-plugin-typescript-sort-keys 25 | ``` 26 | 27 | **Note:** If you installed ESLint globally then you must also install `eslint-plugin-typescript-sort-keys` globally. 28 | 29 | ## Usage 30 | 31 | Specify the parser for typescript files in your `.eslintrc` configuration file: 32 | 33 | ```json 34 | { 35 | "parser": "@typescript-eslint/parser" 36 | } 37 | ``` 38 | 39 | Add `typescript-sort-keys` to the plugins section. You can omit the `eslint-plugin-` prefix: 40 | 41 | ```json 42 | { 43 | "plugins": ["typescript-sort-keys"] 44 | } 45 | ``` 46 | 47 | Then configure the rules you want to use under the rules section. 48 | 49 | ```json 50 | { 51 | "rules": { 52 | "typescript-sort-keys/interface": "error", 53 | "typescript-sort-keys/string-enum": "error" 54 | } 55 | } 56 | ``` 57 | 58 | Or enable all rules with defaults 59 | 60 | ```json 61 | { 62 | "extends": ["plugin:typescript-sort-keys/recommended"] 63 | } 64 | ``` 65 | 66 | ## Supported Rules 67 | 68 | 69 | 70 | **Key**: :heavy_check_mark: = recommended, :wrench: = fixable 71 | 72 | 73 | | Name | Description | :heavy_check_mark: | :wrench: | 74 | | ---- | ----------- | ------------------ | -------- | 75 | | [`typescript-sort-keys/interface`](./docs/rules/interface.md) | require interface keys to be sorted | :heavy_check_mark: | :wrench: | 76 | | [`typescript-sort-keys/string-enum`](./docs/rules/string-enum.md) | require string enum members to be sorted | :heavy_check_mark: | :wrench: | 77 | 78 | 79 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ['@babel/preset-env', { targets: { node: 'current' } }], 4 | '@babel/preset-typescript', 5 | ], 6 | } 7 | -------------------------------------------------------------------------------- /docs/rules/interface.md: -------------------------------------------------------------------------------- 1 | # require interface keys to be sorted (interface) 2 | 3 | When declaring multiple properties on an interface, some developers prefer to sort property names alphabetically to be able to find necessary property easier at the later time. Others feel that it adds complexity and becomes burden to maintain. 4 | 5 | ## Rule Details 6 | 7 | This rule checks all property definitions of an interface declaration and verifies that all keys are sorted alphabetically. 8 | 9 | Examples of **incorrect** code for this rule: 10 | 11 | ```ts 12 | /* eslint typescript-sort-keys/interface: "error" */ 13 | 14 | interface U { 15 | a: T 16 | c: T 17 | b: T 18 | } 19 | interface U { 20 | a: T 21 | c: T 22 | b: T 23 | } 24 | 25 | // Case-sensitive by default. 26 | interface U { 27 | a: T 28 | b: T 29 | C: T 30 | } 31 | 32 | // Non-natural order by default. 33 | interface U { 34 | 1: T 35 | 2: T 36 | 10: T 37 | } 38 | 39 | // Non-required first order by default. 40 | interface U { 41 | b?: T 42 | a: T 43 | c: T 44 | } 45 | 46 | interface U { 47 | a: T 48 | ['c']: T 49 | b: T 50 | } 51 | ``` 52 | 53 | Examples of **correct** code for this rule: 54 | 55 | ```ts 56 | /* eslint typescript-sort-keys/interface: "error" */ 57 | 58 | interface U { 59 | a: T 60 | b: T 61 | c: T 62 | } 63 | interface U { 64 | a: T 65 | b: T 66 | c: T 67 | } 68 | 69 | // Case-sensitive by default. 70 | interface U { 71 | C: T 72 | a: T 73 | b: T 74 | } 75 | 76 | // Non-natural order by default. 77 | interface U { 78 | 1: T 79 | 10: T 80 | 2: T 81 | } 82 | 83 | // Non-required first order by default. 84 | interface U { 85 | a: T 86 | b?: T 87 | c: T 88 | } 89 | 90 | // This rule checks computed properties which have a simple name as well. 91 | interface U { 92 | a: T 93 | ['b']: T 94 | c: T 95 | } 96 | ``` 97 | 98 | ## Options 99 | 100 | ```json 101 | { 102 | "typescript-sort-keys/interface": [ 103 | "error", 104 | "asc", 105 | { "caseSensitive": true, "natural": false, "requiredFirst": false } 106 | ] 107 | } 108 | ``` 109 | 110 | The 1st option is `"asc"` or `"desc"`. 111 | 112 | - `"asc"` (default) - enforce properties to be in ascending order. 113 | - `"desc"` - enforce properties to be in descending order. 114 | 115 | The 2nd option is an object which has 3 properties. 116 | 117 | - `caseSensitive` - if `true`, enforce properties to be in case-sensitive order. Default is `true`. 118 | - `natural` - if `true`, enforce properties to be in natural order. Default is `false`. Natural Order compares strings containing combination of letters and numbers in the way a human being would sort. It basically sorts numerically, instead of sorting alphabetically. So the number 10 comes after the number 3 in Natural Sorting. 119 | - `requiredFirst` - if `true`, enforce optional properties to come after required ones. 120 | 121 | Example for a list: 122 | 123 | With `natural` as true, the ordering would be 124 | 1 125 | 3 126 | 6 127 | 8 128 | 10 129 | 130 | With `natural` as false, the ordering would be 131 | 1 132 | 10 133 | 3 134 | 6 135 | 8 136 | 137 | ### desc 138 | 139 | Examples of **incorrect** code for the `"desc"` option: 140 | 141 | ```ts 142 | /* eslint typescript-sort-keys/interface: ["error", "desc"] */ 143 | 144 | interface U { 145 | b: T 146 | c: T 147 | a: T 148 | } 149 | interface U { 150 | b: T 151 | c: T 152 | a: T 153 | } 154 | 155 | // Case-sensitive by default. 156 | interface U { 157 | C: T 158 | b: T 159 | a: T 160 | } 161 | 162 | // Non-required first order by default. 163 | interface U { 164 | a: T 165 | b?: T 166 | c: T 167 | } 168 | 169 | // Non-natural order by default. 170 | interface U { 171 | 10: T 172 | 2: T 173 | 1: T 174 | } 175 | ``` 176 | 177 | Examples of **correct** code for the `"desc"` option: 178 | 179 | ```ts 180 | /* eslint typescript-sort-keys/interface: ["error", "desc"] */ 181 | 182 | interface U { 183 | c: T 184 | b: T 185 | a: T 186 | } 187 | interface U { 188 | c: T 189 | b: T 190 | a: T 191 | } 192 | 193 | // Case-sensitive by default. 194 | interface U { 195 | b: T 196 | a: T 197 | C: T 198 | } 199 | 200 | // Non-required first order by default. 201 | interface U { 202 | c: T 203 | b?: T 204 | a: T 205 | } 206 | 207 | // Non-natural order by default. 208 | interface U { 209 | 2: T 210 | 10: T 211 | 1: T 212 | } 213 | ``` 214 | 215 | ### insensitive 216 | 217 | Examples of **incorrect** code for the `{ caseSensitive: false }` option: 218 | 219 | ```ts 220 | /* eslint typescript-sort-keys/interface: ["error", "asc", { caseSensitive: false }] */ 221 | 222 | interface U { 223 | a: T 224 | c: T 225 | C: T 226 | b: T 227 | } 228 | interface U { 229 | a: T 230 | C: T 231 | c: T 232 | b: T 233 | } 234 | ``` 235 | 236 | Examples of **correct** code for the `{ caseSensitive: false }` option: 237 | 238 | ```ts 239 | /* eslint typescript-sort-keys/interface: ["error", "asc", { caseSensitive: false }] */ 240 | 241 | interface U { 242 | a: T 243 | b: T 244 | c: T 245 | C: T 246 | } 247 | interface U { 248 | a: T 249 | b: T 250 | C: T 251 | c: T 252 | } 253 | ``` 254 | 255 | ### natural 256 | 257 | Examples of **incorrect** code for the `{natural: true}` option: 258 | 259 | ```ts 260 | /* eslint typescript-sort-keys/interface: ["error", "asc", { natural: true }] */ 261 | 262 | interface U { 263 | 1: T 264 | 10: T 265 | 2: T 266 | } 267 | ``` 268 | 269 | Examples of **correct** code for the `{natural: true}` option: 270 | 271 | ```ts 272 | /* eslint typescript-sort-keys/interface: ["error", "asc", { natural: true }] */ 273 | 274 | interface U { 275 | 1: T 276 | 2: T 277 | 10: T 278 | } 279 | ``` 280 | 281 | ### required 282 | 283 | Examples of **incorrect** code for the `{ requiredFirst: true }` option: 284 | 285 | ```ts 286 | /* eslint typescript-sort-keys/interface: ["error", "asc", { requiredFirst: true }] */ 287 | 288 | interface U { 289 | d: T 290 | c?: T 291 | b?: T 292 | a: T 293 | } 294 | ``` 295 | 296 | Examples of **correct** code for the `{ requiredFirst: true }` option: 297 | 298 | ```ts 299 | /* eslint typescript-sort-keys/interface: ["error", "asc", { requiredFirst: true }] */ 300 | 301 | interface U { 302 | a: T 303 | d: T 304 | b?: T 305 | c?: T 306 | } 307 | ``` 308 | 309 | ## When Not To Use It 310 | 311 | If you don't want to notify about properties' order, then it's safe to disable this rule. 312 | -------------------------------------------------------------------------------- /docs/rules/string-enum.md: -------------------------------------------------------------------------------- 1 | # require string enum members to be sorted (string-enum) 2 | 3 | When declaring multiple members on an string enum, some developers prefer to sort enum member names alphabetically to be able to find necessary members easier at the later time. Others feel that it adds complexity and becomes burden to maintain. 4 | 5 | ## Rule Details 6 | 7 | This rule checks all members of a string enum declaration and verifies that all keys are sorted alphabetically. 8 | 9 | Examples of **incorrect** code for this rule: 10 | 11 | ```ts 12 | /* eslint typescript-sort-keys/string-enum: "error" */ 13 | 14 | enum U { 15 | a = 'T', 16 | c = 'T', 17 | b = 'T', 18 | } 19 | enum U { 20 | a = 'T', 21 | c = 'T', 22 | b = 'T', 23 | } 24 | 25 | // Case-sensitive by default. 26 | enum U { 27 | a = 'T', 28 | b = 'T', 29 | C = 'T', 30 | } 31 | 32 | enum U { 33 | a = 'T', 34 | 'c' = 'T', 35 | b = 'T', 36 | } 37 | ``` 38 | 39 | Examples of **correct** code for this rule: 40 | 41 | ```ts 42 | /* eslint typescript-sort-keys/string-enum: "error" */ 43 | 44 | enum U { 45 | a = 'T', 46 | b = 'T', 47 | c = 'T', 48 | } 49 | enum U { 50 | a = 'T', 51 | b = 'T', 52 | c = 'T', 53 | } 54 | 55 | // Case-sensitive by default. 56 | enum U { 57 | C = 'T', 58 | a = 'T', 59 | b = 'T', 60 | } 61 | 62 | // This rule checks computed properties which have a simple name as well. 63 | enum U { 64 | a = 'T', 65 | 'b' = 'T', 66 | c = 'T', 67 | } 68 | ``` 69 | 70 | ## Options 71 | 72 | ```json 73 | { 74 | "typescript-sort-keys/string-enum": ["error", "asc", { "caseSensitive": true }] 75 | } 76 | ``` 77 | 78 | The 1st option is `"asc"` or `"desc"`. 79 | 80 | - `"asc"` (default) - enforce enum members to be in ascending order. 81 | - `"desc"` - enforce enum members to be in descending order. 82 | 83 | The 2nd option is an object which has 2 properties. 84 | 85 | - `caseSensitive` - if `true`, enforce enum members to be in case-sensitive order. Default is `true`. 86 | - `natural` - if `true`, enforce enum members to be in natural order. Default is `false`. Natural Order compares strings containing combination of letters and numbers in the way a human being would sort. It basically sorts numerically, instead of sorting alphabetically. So the number 10 comes after the number 3 in Natural Sorting. 87 | 88 | ### desc 89 | 90 | Examples of **incorrect** code for the `"desc"` option: 91 | 92 | ```ts 93 | /* eslint typescript-sort-keys/string-enum: ["error", "desc"] */ 94 | 95 | enum U { 96 | b = 'T', 97 | c = 'T', 98 | a = 'T', 99 | } 100 | enum U { 101 | b = 'T', 102 | c = 'T', 103 | a = 'T', 104 | } 105 | 106 | // Case-sensitive by default. 107 | enum U { 108 | C = 'T', 109 | b = 'T', 110 | a = 'T', 111 | } 112 | ``` 113 | 114 | Examples of **correct** code for the `"desc"` option: 115 | 116 | ```ts 117 | /* eslint typescript-sort-keys/string-enum: ["error", "desc"] */ 118 | 119 | enum U { 120 | c = 'T', 121 | b = 'T', 122 | a = 'T', 123 | } 124 | enum U { 125 | c = 'T', 126 | b = 'T', 127 | a = 'T', 128 | } 129 | 130 | // Case-sensitive by default. 131 | enum U { 132 | b = 'T', 133 | a = 'T', 134 | C = 'T', 135 | } 136 | ``` 137 | 138 | ### insensitive 139 | 140 | Examples of **incorrect** code for the `{ caseSensitive: false }` option: 141 | 142 | ```ts 143 | /* eslint typescript-sort-keys/string-enum: ["error", "asc", { caseSensitive: false }] */ 144 | 145 | enum U { 146 | a = 'T', 147 | c = 'T', 148 | C = 'T', 149 | b = 'T', 150 | } 151 | enum U { 152 | a = 'T', 153 | C = 'T', 154 | c = 'T', 155 | b = 'T', 156 | } 157 | ``` 158 | 159 | Examples of **correct** code for the `{ caseSensitive: false }` option: 160 | 161 | ```ts 162 | /* eslint typescript-sort-keys/string-enum: ["error", "asc", { caseSensitive: false }] */ 163 | 164 | enum U { 165 | a = 'T', 166 | b = 'T', 167 | c = 'T', 168 | C = 'T', 169 | } 170 | enum U { 171 | a = 'T', 172 | b = 'T', 173 | C = 'T', 174 | c = 'T', 175 | } 176 | ``` 177 | 178 | ### natural 179 | 180 | Examples of **incorrect** code for the `{natural: true}` option: 181 | 182 | ```ts 183 | /* eslint typescript-sort-keys/string-enum: ["error", "asc", { natural: true }] */ 184 | 185 | enum U { 186 | a = 'T', 187 | _ = 'T', 188 | A = 'T', 189 | $ = 'T', 190 | } 191 | ``` 192 | 193 | Examples of **correct** code for the `{natural: true}` option: 194 | 195 | ```ts 196 | /* eslint typescript-sort-keys/string-enum: ["error", "asc", { natural: true }] */ 197 | 198 | enum U { 199 | a = 'T', 200 | A = 'T', 201 | _ = 'T', 202 | $ = 'T', 203 | } 204 | ``` 205 | 206 | ## When Not To Use It 207 | 208 | If you don't want to notify about enum members' order, then it's safe to disable this rule. 209 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testRegex: 'tests/.*\\.spec\\.(js|ts)$', 3 | moduleDirectories: ['node_modules', 'src'], 4 | } 5 | -------------------------------------------------------------------------------- /lint-staged.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '*.{js,ts}': ['eslint --fix --ext js,jsx,tsx,ts --max-warnings 0 --no-ignore'], 3 | '*.{md,yml,json}': ['prettier --write'], 4 | } 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-plugin-typescript-sort-keys", 3 | "version": "3.3.0", 4 | "description": "Sort interface and string enum keys", 5 | "keywords": [ 6 | "eslint", 7 | "eslintplugin", 8 | "eslint-plugin", 9 | "typescript" 10 | ], 11 | "author": "infctr ", 12 | "main": "./lib/index.cjs.js", 13 | "repository": "git@github.com:infctr/eslint-plugin-typescript-sort-keys.git", 14 | "url": "https://github.com/infctr/eslint-plugin-typescript-sort-keys", 15 | "files": [ 16 | "/lib", 17 | "package.json", 18 | "CHANGELOG.md", 19 | "LICENSE.md", 20 | "README.md" 21 | ], 22 | "exports": { 23 | ".": { 24 | "import": "./lib/index.mjs", 25 | "require": "./lib/index.cjs.js", 26 | "default": "./lib/index.cjs.js" 27 | }, 28 | "./package.json": "./package.json" 29 | }, 30 | "scripts": { 31 | "prepublishOnly": "yarn compile", 32 | "build": "yarn rimraf lib && yarn compile", 33 | "compile": "yarn rollup -c", 34 | "docs": "eslint-docs", 35 | "docs:check": "eslint-docs check", 36 | "lint": "eslint --ext .js,.ts src/ tests/", 37 | "format": "prettier --write src/**/*.{js,ts} tests/**/*.{js,ts}", 38 | "test": "yarn jest --watch", 39 | "coverage": "yarn test --coverage --watchAll=false", 40 | "coverage-preview": "http-server -o -p 5000 coverage/lcov-report", 41 | "typecheck": "tsc --noEmit --skipLibCheck", 42 | "verify": "yarn typecheck && yarn lint && yarn build && yarn coverage" 43 | }, 44 | "dependencies": { 45 | "@typescript-eslint/experimental-utils": "^5.0.0", 46 | "json-schema": "^0.4.0", 47 | "natural-compare-lite": "^1.4.0" 48 | }, 49 | "devDependencies": { 50 | "@babel/cli": "~7.14.8", 51 | "@babel/core": "~7.15.0", 52 | "@babel/preset-env": "~7.15.0", 53 | "@babel/preset-typescript": "~7.15.0", 54 | "@infctr/eslint-docs": "~0.4.0", 55 | "@rollup/plugin-commonjs": "~12.0.0", 56 | "@rollup/plugin-json": "~4.0.3", 57 | "@rollup/plugin-node-resolve": "~8.0.0", 58 | "@rollup/plugin-typescript": "~11.1.2", 59 | "@types/babel__core": "~7.1.7", 60 | "@types/babel__preset-env": "~7.9.0", 61 | "@types/eslint": "~7.28.1", 62 | "@types/jest": "~27.0.2", 63 | "@types/natural-compare-lite": "~1.4.0", 64 | "@types/rimraf": "~3.0.1", 65 | "@types/tmp": "~0.2.1", 66 | "@typescript-eslint/eslint-plugin": "~6.4.0", 67 | "@typescript-eslint/parser": "~6.4.0", 68 | "babel-jest": "~26.6.3", 69 | "babel-plugin-module-resolver": "~4.1.0", 70 | "eslint": "~8.0.1", 71 | "eslint-config-prettier": "~9.0.0", 72 | "eslint-plugin-eslint-plugin": "~5.1.1", 73 | "eslint-plugin-import": "~2.28.0", 74 | "eslint-plugin-jest": "~27.2.3", 75 | "eslint-plugin-prettier": "~5.0.0", 76 | "http-server": "~13.0.0", 77 | "husky": "~4.2.5", 78 | "jest": "~29.6.2", 79 | "lint-staged": "~10.5.4", 80 | "prettier": "3.0.1", 81 | "rimraf": "~3.0.2", 82 | "rollup": "~2.10.5", 83 | "tmp": "~0.2.1", 84 | "tsconfig": "~7.0.0", 85 | "typescript": "5.1.6" 86 | }, 87 | "peerDependencies": { 88 | "@typescript-eslint/parser": ">=6", 89 | "eslint": "^7 || ^8", 90 | "typescript": "^3 || ^4 || ^5" 91 | }, 92 | "engines": { 93 | "node": ">= 16" 94 | }, 95 | "license": "ISC" 96 | } 97 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonjs from '@rollup/plugin-commonjs' 2 | import resolve from '@rollup/plugin-node-resolve' 3 | import typescript from '@rollup/plugin-typescript' 4 | import json from '@rollup/plugin-json' 5 | import tsconfig from 'tsconfig' 6 | import fs from 'fs' 7 | import assert from 'assert' 8 | 9 | const filePath = tsconfig.resolveSync('.') 10 | 11 | assert(filePath) 12 | 13 | const config = tsconfig.readFileSync(filePath) 14 | const baseUrl = config.compilerOptions.baseUrl 15 | 16 | const moduleMappings = fs 17 | .readdirSync(baseUrl, { withFileTypes: true }) 18 | .filter(dir => dir.isDirectory()) 19 | .map(dir => [dir.name, '/'].join('')) 20 | 21 | const external = id => 22 | !id.startsWith('.') && 23 | !id.startsWith('/') && 24 | !id.startsWith('\0') && 25 | !moduleMappings.some(mapping => id.startsWith(mapping)) 26 | 27 | export default [ 28 | { 29 | input: './src/index.ts', 30 | external, 31 | output: [ 32 | { dir: 'lib', entryFileNames: '[name].cjs.js', format: 'cjs' }, 33 | { dir: 'lib', entryFileNames: '[name].mjs', format: 'es' }, 34 | ], 35 | plugins: [commonjs(), resolve(), typescript(), json()], 36 | }, 37 | ] 38 | -------------------------------------------------------------------------------- /src/common/options.ts: -------------------------------------------------------------------------------- 1 | import { JSONSchema4 } from 'json-schema' 2 | 3 | export enum SortingOrder { 4 | Ascending = 'asc', 5 | Descending = 'desc', 6 | } 7 | 8 | export const sortingOrderOptionSchema: JSONSchema4 = { 9 | enum: [SortingOrder.Ascending, SortingOrder.Descending], 10 | } 11 | 12 | export type SortingOrderOption = SortingOrder 13 | 14 | interface CaseSensitiveSortingOption { 15 | readonly caseSensitive: boolean 16 | } 17 | 18 | interface NaturalSortingOption { 19 | readonly natural: boolean 20 | } 21 | 22 | interface RequiredFirstSortingOption { 23 | readonly requiredFirst: boolean 24 | } 25 | 26 | export interface SortingParamsOptions { 27 | readonly caseSensitive: CaseSensitiveSortingOption 28 | readonly natural: NaturalSortingOption 29 | readonly requiredFirst: RequiredFirstSortingOption 30 | } 31 | 32 | export enum ErrorMessage { 33 | InterfaceInvalidOrder = `Expected interface keys to be in {{ requiredFirst }}{{ natural }}{{ insensitive }}{{ order }}ending order. '{{ thisName }}' should be before '{{ prevName }}'.`, 34 | StringEnumInvalidOrder = `Expected string enum members to be in {{ natural }}{{ insensitive }}{{ order }}ending order. '{{ thisName }}' should be before '{{ prevName }}'.`, 35 | } 36 | -------------------------------------------------------------------------------- /src/config/recommended.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: ['typescript-sort-keys'], 3 | rules: { 4 | 'typescript-sort-keys/interface': 'error' as const, 5 | 'typescript-sort-keys/string-enum': 'error' as const, 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import recommended from './config/recommended' 2 | import { rules } from './rules' 3 | 4 | const config = { 5 | rules, 6 | configs: { 7 | recommended, 8 | }, 9 | } 10 | 11 | export default config 12 | -------------------------------------------------------------------------------- /src/rules/index.ts: -------------------------------------------------------------------------------- 1 | import { name as interfaceName, rule as interfaceRule } from './interface' 2 | import { name as stringEnumName, rule as stringEnumRule } from './string-enum' 3 | 4 | export const rules = { 5 | [interfaceName]: interfaceRule, 6 | [stringEnumName]: stringEnumRule, 7 | } 8 | -------------------------------------------------------------------------------- /src/rules/interface.ts: -------------------------------------------------------------------------------- 1 | import { JSONSchema4 } from 'json-schema' 2 | 3 | import { getObjectBody } from 'utils/ast' 4 | import { createReporter } from 'utils/plugin' 5 | import { createRule, RuleMetaData } from 'utils/rule' 6 | import { 7 | sortingOrderOptionSchema, 8 | SortingOrder, 9 | ErrorMessage, 10 | SortingOrderOption, 11 | SortingParamsOptions, 12 | } from 'common/options' 13 | 14 | /** 15 | * The name of this rule. 16 | */ 17 | export const name = 'interface' as const 18 | 19 | type SortingParams = SortingParamsOptions['caseSensitive'] & 20 | SortingParamsOptions['natural'] & 21 | SortingParamsOptions['requiredFirst'] 22 | 23 | /** 24 | * The options this rule can take. 25 | */ 26 | export type Options = [SortingOrderOption] | [SortingOrderOption, Partial] 27 | 28 | const sortingParamsOptionSchema: JSONSchema4 = { 29 | type: 'object', 30 | properties: { 31 | caseSensitive: { 32 | type: 'boolean', 33 | }, 34 | natural: { 35 | type: 'boolean', 36 | }, 37 | requiredFirst: { 38 | type: 'boolean', 39 | }, 40 | }, 41 | additionalProperties: false, 42 | } 43 | 44 | /** 45 | * The schema for the rule options. 46 | */ 47 | const schema: JSONSchema4[] = [sortingOrderOptionSchema, sortingParamsOptionSchema] 48 | 49 | /** 50 | * The default options for the rule. 51 | */ 52 | const defaultOptions: Options = [ 53 | SortingOrder.Ascending, 54 | { caseSensitive: true, natural: false, requiredFirst: false }, 55 | ] 56 | 57 | /** 58 | * The possible error messages. 59 | */ 60 | const errorMessages = { 61 | invalidOrder: ErrorMessage.InterfaceInvalidOrder, 62 | } as const 63 | 64 | /** 65 | * The meta data for this rule. 66 | */ 67 | const meta: RuleMetaData = { 68 | type: 'suggestion', 69 | docs: { 70 | description: 'require interface keys to be sorted', 71 | recommended: 'warn', 72 | }, 73 | messages: errorMessages, 74 | fixable: 'code', 75 | schema, 76 | } 77 | 78 | /** 79 | * Create the rule. 80 | */ 81 | export const rule = createRule({ 82 | name, 83 | meta, 84 | defaultOptions, 85 | 86 | create(context) { 87 | const compareNodeListAndReport = createReporter(context, ({ loc }) => ({ 88 | loc, 89 | messageId: 'invalidOrder', 90 | })) 91 | 92 | return { 93 | TSInterfaceDeclaration(node) { 94 | const body = getObjectBody(node) 95 | 96 | return compareNodeListAndReport(body) 97 | }, 98 | 99 | TSTypeLiteral(node) { 100 | const body = getObjectBody(node) 101 | 102 | return compareNodeListAndReport(body) 103 | }, 104 | } 105 | }, 106 | }) 107 | -------------------------------------------------------------------------------- /src/rules/string-enum.ts: -------------------------------------------------------------------------------- 1 | import { JSONSchema4 } from 'json-schema' 2 | import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/experimental-utils' 3 | 4 | import { getObjectBody } from 'utils/ast' 5 | import { createReporter } from 'utils/plugin' 6 | import { createRule, RuleMetaData } from 'utils/rule' 7 | import { 8 | sortingOrderOptionSchema, 9 | SortingOrder, 10 | ErrorMessage, 11 | SortingOrderOption, 12 | SortingParamsOptions, 13 | } from 'common/options' 14 | 15 | /** 16 | * The name of this rule. 17 | */ 18 | export const name = 'string-enum' as const 19 | 20 | type SortingParams = SortingParamsOptions['caseSensitive'] & 21 | SortingParamsOptions['natural'] 22 | 23 | /** 24 | * The options this rule can take. 25 | */ 26 | export type Options = [SortingOrderOption] | [SortingOrderOption, Partial] 27 | 28 | const sortingParamsOptionSchema: JSONSchema4 = { 29 | type: 'object', 30 | properties: { 31 | caseSensitive: { 32 | type: 'boolean', 33 | }, 34 | natural: { 35 | type: 'boolean', 36 | }, 37 | }, 38 | additionalProperties: false, 39 | } 40 | 41 | /** 42 | * The schema for the rule options. 43 | */ 44 | const schema: JSONSchema4[] = [sortingOrderOptionSchema, sortingParamsOptionSchema] 45 | 46 | /** 47 | * The default options for the rule. 48 | */ 49 | const defaultOptions: Options = [ 50 | SortingOrder.Ascending, 51 | { caseSensitive: true, natural: false }, 52 | ] 53 | 54 | /** 55 | * The possible error messages. 56 | */ 57 | const errorMessages = { 58 | invalidOrder: ErrorMessage.StringEnumInvalidOrder, 59 | } as const 60 | 61 | /** 62 | * The meta data for this rule. 63 | */ 64 | const meta: RuleMetaData = { 65 | type: 'suggestion', 66 | docs: { 67 | description: 'require string enum members to be sorted', 68 | recommended: 'warn', 69 | }, 70 | messages: errorMessages, 71 | fixable: 'code', 72 | schema, 73 | } 74 | 75 | /** 76 | * Create the rule. 77 | */ 78 | export const rule = createRule({ 79 | name, 80 | meta, 81 | defaultOptions, 82 | 83 | create(context) { 84 | const compareNodeListAndReport = createReporter(context, ({ loc }) => ({ 85 | loc, 86 | messageId: 'invalidOrder', 87 | })) 88 | 89 | return { 90 | TSEnumDeclaration(node) { 91 | const body = getObjectBody(node) as TSESTree.TSEnumMember[] 92 | const isStringEnum = body.every( 93 | (member: TSESTree.TSEnumMember) => 94 | member.initializer && 95 | member.initializer.type === AST_NODE_TYPES.Literal && 96 | typeof member.initializer.value === 'string', 97 | ) 98 | 99 | if (isStringEnum) { 100 | compareNodeListAndReport(body) 101 | } 102 | }, 103 | } 104 | }, 105 | }) 106 | -------------------------------------------------------------------------------- /src/utils/ast.ts: -------------------------------------------------------------------------------- 1 | import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/experimental-utils' 2 | 3 | import { indexSignature } from './common' 4 | 5 | export function getObjectBody( 6 | node: 7 | | TSESTree.TSEnumDeclaration 8 | | TSESTree.TSInterfaceDeclaration 9 | | TSESTree.TSTypeLiteral, 10 | ) { 11 | switch (node.type) { 12 | case AST_NODE_TYPES.TSInterfaceDeclaration: 13 | return node.body.body 14 | case AST_NODE_TYPES.TSEnumDeclaration: 15 | case AST_NODE_TYPES.TSTypeLiteral: 16 | return node.members 17 | } 18 | } 19 | 20 | function getProperty(node: TSESTree.Node) { 21 | switch (node.type) { 22 | case AST_NODE_TYPES.TSIndexSignature: { 23 | const [identifier] = node.parameters 24 | 25 | return { 26 | ...identifier, 27 | // Override name for error message readability and weight calculation 28 | name: indexSignature.create( 29 | (identifier as TSESTree.Parameter & { name: string }).name, 30 | ), 31 | } 32 | } 33 | 34 | case AST_NODE_TYPES.TSPropertySignature: 35 | case AST_NODE_TYPES.TSMethodSignature: 36 | return node.key 37 | 38 | case AST_NODE_TYPES.TSEnumMember: 39 | return node.id 40 | 41 | default: 42 | return undefined 43 | } 44 | } 45 | 46 | /** 47 | * Gets the property name of the given `Property` node. 48 | * 49 | * - If the property's key is an `Identifier` node, this returns the key's name 50 | * whether it's a computed property or not. 51 | * - If the property has a static name, this returns the static name. 52 | * - Otherwise, this returns undefined. 53 | * 54 | * a.b // => "b" 55 | * a["b"] // => "b" 56 | * a['b'] // => "b" 57 | * a[`b`] // => "b" 58 | * a[100] // => "100" 59 | * a[b] // => undefined 60 | * a["a" + "b"] // => undefined 61 | * a[tag`b`] // => undefined 62 | * a[`${b}`] // => undefined 63 | * 64 | * let a = {b: 1} // => "b" 65 | * let a = {["b"]: 1} // => "b" 66 | * let a = {['b']: 1} // => "b" 67 | * let a = {[`b`]: 1} // => "b" 68 | * let a = {[100]: 1} // => "100" 69 | * let a = {[b]: 1} // => undefined 70 | * let a = {["a" + "b"]: 1} // => undefined 71 | * let a = {[tag`b`]: 1} // => undefined 72 | * let a = {[`${b}`]: 1} // => undefined 73 | */ 74 | export function getPropertyName(node: TSESTree.TypeElement | TSESTree.TSEnumMember) { 75 | const property = getProperty(node) 76 | 77 | if (!property) { 78 | return undefined 79 | } 80 | 81 | switch (property.type) { 82 | case AST_NODE_TYPES.Literal: 83 | return String(property.value) 84 | 85 | case AST_NODE_TYPES.Identifier: 86 | return property.name 87 | 88 | default: 89 | return undefined 90 | } 91 | } 92 | 93 | export function getPropertyIsOptional( 94 | node: TSESTree.TypeElement | TSESTree.TSEnumMember, 95 | ) { 96 | switch (node.type) { 97 | case AST_NODE_TYPES.TSMethodSignature: 98 | case AST_NODE_TYPES.TSPropertySignature: 99 | return Boolean(node.optional) 100 | } 101 | 102 | return false 103 | } 104 | -------------------------------------------------------------------------------- /src/utils/common.ts: -------------------------------------------------------------------------------- 1 | const nameToIndexSignature = (x: string) => `[index: ${x}]` 2 | const indexSignatureRegexp = new RegExp( 3 | `^${nameToIndexSignature('.+')}`.replace('[', '\\[').replace(']', '\\]'), 4 | ) 5 | 6 | export const indexSignature = { 7 | create: nameToIndexSignature, 8 | regex: indexSignatureRegexp, 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/compare.ts: -------------------------------------------------------------------------------- 1 | import naturalCompare from 'natural-compare-lite' 2 | 3 | import { indexSignature } from './common' 4 | 5 | function charCompare(a: string, b: string) { 6 | if (a < b) { 7 | return -1 8 | } 9 | 10 | if (b < a) { 11 | return 1 12 | } 13 | 14 | return 0 15 | } 16 | 17 | function getWeight(value: string) { 18 | switch (true) { 19 | // Custom name for index signature used here 20 | case indexSignature.regex.test(value): 21 | return 100 22 | default: 23 | return 0 24 | } 25 | } 26 | 27 | function weightedCompare( 28 | a: string, 29 | b: string, 30 | compareFn: (a: string, b: string) => number, 31 | ) { 32 | return compareFn(a, b) - getWeight(a) + getWeight(b) 33 | } 34 | 35 | const ascending = (a: string, b: string) => { 36 | return weightedCompare(a, b, charCompare) 37 | } 38 | 39 | const ascendingInsensitive = (a: string, b: string) => { 40 | return weightedCompare(a.toLowerCase(), b.toLowerCase(), charCompare) 41 | } 42 | 43 | const ascendingNatural = (a: string, b: string) => { 44 | return weightedCompare(a, b, naturalCompare) 45 | } 46 | 47 | const ascendingInsensitiveNatural = (a: string, b: string) => { 48 | return weightedCompare(a.toLowerCase(), b.toLowerCase(), naturalCompare) 49 | } 50 | 51 | /** 52 | * Functions which check that the given 2 names are in specific order. 53 | */ 54 | export const compareFn = 55 | (isAscending: boolean, isInsensitive: boolean, isNatural: boolean) => 56 | (...args: [string?, string?]) => { 57 | if (args.filter(Boolean).length !== 2) { 58 | return 0 59 | } 60 | 61 | const input = (isAscending ? args : args.reverse()) as [string, string] 62 | 63 | if (isInsensitive && isNatural) { 64 | return ascendingInsensitiveNatural(...input) 65 | } 66 | 67 | if (!isInsensitive && isNatural) { 68 | return ascendingNatural(...input) 69 | } 70 | 71 | if (isInsensitive && !isNatural) { 72 | return ascendingInsensitive(...input) 73 | } 74 | 75 | return ascending(...input) 76 | } 77 | -------------------------------------------------------------------------------- /src/utils/plugin.ts: -------------------------------------------------------------------------------- 1 | import { TSESTree, AST_TOKEN_TYPES } from '@typescript-eslint/experimental-utils' 2 | import { 3 | RuleContext as UtilRuleContext, 4 | RuleFixer, 5 | RuleFix, 6 | SourceCode, 7 | } from '@typescript-eslint/experimental-utils/dist/ts-eslint' 8 | import assert from 'assert' 9 | 10 | import { SortingOrder } from 'common/options' 11 | import { Options as InterfaceRuleOptions } from 'rules/interface' 12 | import { Options as StringEnumRuleOptions } from 'rules/string-enum' 13 | import { getPropertyName, getPropertyIsOptional } from './ast' 14 | import { compareFn } from './compare' 15 | 16 | type RuleOptions = InterfaceRuleOptions & StringEnumRuleOptions 17 | 18 | type TSType = TSESTree.TypeElement | TSESTree.TSEnumMember 19 | 20 | function createNodeSwapper(context: UtilRuleContext) { 21 | const sourceCode = context.getSourceCode() as SourceCode & { 22 | lineStartIndices: number[] 23 | } 24 | 25 | /** 26 | * Returns the indent range of a node if it's the first on its line. 27 | * Otherwise, returns a range starting immediately after the previous sibling. 28 | */ 29 | function getIndentRange(node: TSESTree.Node | TSESTree.Comment): TSESTree.Range { 30 | const prevSibling = sourceCode.getTokenBefore(node) 31 | const end = node.range[0] 32 | const start = 33 | prevSibling && prevSibling.loc.start.line === node.loc.start.line 34 | ? prevSibling.range[1] + 1 35 | : node.range[0] - node.loc.start.column 36 | 37 | return [start, end] 38 | } 39 | 40 | function getRangeWithIndent(node: TSESTree.Comment) { 41 | return [getIndentRange(node)[0], node.range[1]] 42 | } 43 | 44 | /** 45 | * Returns the range for the entire line, including EOL, if node is the only 46 | * token on its lines. Otherwise, returns the node range. 47 | */ 48 | function getLineRange(node: TSESTree.Comment): TSESTree.Range { 49 | const [start] = getRangeWithIndent(node) 50 | const index = sourceCode.lineStartIndices.findIndex(n => start === n) 51 | 52 | if (index < 0) { 53 | // Node is not at the beginning of the line 54 | return node.range 55 | } 56 | 57 | const lines = 1 + node.loc.end.line - node.loc.start.line 58 | 59 | return [ 60 | sourceCode.lineStartIndices[index], 61 | sourceCode.lineStartIndices[index + lines], 62 | ] 63 | } 64 | 65 | function getIndentText(node: TSESTree.Node) { 66 | return sourceCode.text.slice(...getIndentRange(node)) 67 | } 68 | 69 | function getNodePunctuator(node: TSESTree.Node) { 70 | const punctuator = sourceCode.getTokenAfter(node, { 71 | filter: n => n.type === AST_TOKEN_TYPES.Punctuator && n.value !== ':', 72 | includeComments: false, 73 | }) 74 | 75 | // Check the punctuator value outside of filter because we 76 | // want to stop traversal on any terminating punctuator 77 | return punctuator && /^[,;]$/.test(punctuator.value) ? punctuator : undefined 78 | } 79 | 80 | return ( 81 | fixer: RuleFixer, 82 | nodePositions: Map, 83 | currentNode: TSType, 84 | replaceNode: TSType, 85 | ) => 86 | [currentNode, replaceNode].reduce((acc, node) => { 87 | const otherNode = node === currentNode ? replaceNode : currentNode 88 | const comments = sourceCode.getCommentsBefore(node) 89 | const nextSibling = sourceCode.getTokenAfter(node) 90 | const isLastReplacingLast = 91 | nodePositions.get(node)?.final === nodePositions.size - 1 && 92 | nodePositions.get(node)?.final === nodePositions.get(otherNode)?.initial 93 | 94 | let text = [ 95 | comments.length ? getIndentText(node) : '', 96 | sourceCode.getText(node), 97 | ].join('') 98 | 99 | // If nextSibling is the node punctuator, remove it 100 | if (nextSibling === getNodePunctuator(node)) { 101 | acc.push(fixer.remove(nextSibling)) 102 | } 103 | 104 | if (!/[,;]$/.test(text)) { 105 | // Add a punctuator if the node doesn't already have one 106 | text += ',' 107 | } 108 | 109 | if (isLastReplacingLast) { 110 | // If we're moving the last node to its final destination, we can remove the punctuator 111 | text = text.replace(/,$/, '') 112 | } 113 | 114 | if (comments.length) { 115 | // Insert leading comments above the other node 116 | acc.push( 117 | fixer.insertTextBefore( 118 | otherNode, 119 | comments 120 | .map(comment => sourceCode.getText(comment as any)) 121 | .concat('') 122 | .join('\n'), 123 | ), 124 | ) 125 | } 126 | 127 | acc.push( 128 | // Insert the node before the other node 129 | fixer.insertTextBefore(otherNode, text), 130 | // Remove the original instance of node 131 | fixer.remove(node), 132 | // Remove the original instances of node comments 133 | ...comments.map(n => fixer.removeRange(getLineRange(n))), 134 | ) 135 | 136 | return acc 137 | }, []) 138 | } 139 | 140 | export function createReporter( 141 | context: UtilRuleContext, 142 | createReportObject: (node: TSESTree.Node) => { 143 | readonly loc: TSESTree.SourceLocation 144 | readonly messageId: MessageIds 145 | }, 146 | ) { 147 | // Parse options. 148 | const order = context.options[0] || SortingOrder.Ascending 149 | const options = context.options[1] 150 | const isAscending = order === SortingOrder.Ascending 151 | const isInsensitive = (options && options.caseSensitive) === false 152 | const isNatural = Boolean(options && options.natural) 153 | const isRequiredFirst = (options && options.requiredFirst) === true 154 | 155 | const compare = compareFn(isAscending, isInsensitive, isNatural) 156 | const swapNodes = createNodeSwapper(context) 157 | 158 | return (body: TSType[]) => { 159 | const sortedBody = isRequiredFirst 160 | ? [ 161 | ...body 162 | .slice(0) 163 | .filter(node => !getPropertyIsOptional(node)) 164 | .sort((a, b) => compare(getPropertyName(a), getPropertyName(b))), 165 | ...body 166 | .slice(0) 167 | .filter(node => getPropertyIsOptional(node)) 168 | .sort((a, b) => compare(getPropertyName(a), getPropertyName(b))), 169 | ] 170 | : body.slice(0).sort((a, b) => compare(getPropertyName(a), getPropertyName(b))) 171 | 172 | const nodePositions = new Map( 173 | body.map(n => [n, { initial: body.indexOf(n), final: sortedBody.indexOf(n) }]), 174 | ) 175 | 176 | for (let i = 1; i < body.length; i += 1) { 177 | const prevNode = body[i - 1] 178 | const currentNode = body[i] 179 | const prevNodeName = getPropertyName(prevNode) 180 | const currentNodeName = getPropertyName(currentNode) 181 | 182 | if ( 183 | (!isRequiredFirst && compare(prevNodeName, currentNodeName) > 0) || 184 | (isRequiredFirst && 185 | getPropertyIsOptional(prevNode) === getPropertyIsOptional(currentNode) && 186 | compare(prevNodeName, currentNodeName) > 0) || 187 | (isRequiredFirst && 188 | getPropertyIsOptional(prevNode) !== getPropertyIsOptional(currentNode) && 189 | getPropertyIsOptional(prevNode)) 190 | ) { 191 | const targetPosition = sortedBody.indexOf(currentNode) 192 | const replaceNode = body[targetPosition] 193 | const { loc, messageId } = createReportObject(currentNode) 194 | 195 | // Sanity check 196 | assert(loc, 'createReportObject return value must include a node location') 197 | assert( 198 | messageId, 199 | 'createReportObject return value must include a problem message', 200 | ) 201 | 202 | context.report({ 203 | loc, 204 | messageId, 205 | node: currentNode, 206 | data: { 207 | thisName: currentNodeName, 208 | prevName: prevNodeName, 209 | order, 210 | insensitive: isInsensitive ? 'insensitive ' : '', 211 | natural: isNatural ? 'natural ' : '', 212 | requiredFirst: isRequiredFirst ? 'required first ' : '', 213 | }, 214 | 215 | fix: fixer => { 216 | if (currentNode !== replaceNode) { 217 | return swapNodes(fixer, nodePositions, currentNode, replaceNode) 218 | } 219 | 220 | return null 221 | }, 222 | }) 223 | } 224 | } 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /src/utils/rule.ts: -------------------------------------------------------------------------------- 1 | import { ESLintUtils } from '@typescript-eslint/experimental-utils' 2 | import { 3 | ReportDescriptor, 4 | RuleContext as UtilRuleContext, 5 | RuleListener, 6 | RuleMetaData as UtilRuleMetaData, 7 | RuleMetaDataDocs as UtilRuleMetaDataDocs, 8 | RuleModule, 9 | } from '@typescript-eslint/experimental-utils/dist/ts-eslint' 10 | 11 | export type BaseOptions = readonly unknown[] 12 | 13 | // "url" will be set automatically. 14 | export type RuleMetaDataDocs = Omit 15 | 16 | // "docs.url" will be set automatically. 17 | export type RuleMetaData = { 18 | readonly docs: RuleMetaDataDocs 19 | } & Omit, 'docs'> 20 | 21 | export type RuleResult = { 22 | readonly context: UtilRuleContext 23 | readonly descriptors: readonly ReportDescriptor[] 24 | } 25 | 26 | type Mutable = { 27 | -readonly [P in keyof T]: T[P] 28 | } 29 | 30 | /** 31 | * Create a rule. 32 | */ 33 | export function createRule(data: { 34 | readonly name: string 35 | readonly meta: RuleMetaData 36 | readonly defaultOptions: Options 37 | readonly create: ( 38 | context: UtilRuleContext, 39 | optionsWithDefault: Mutable, 40 | ) => RuleListener 41 | }): RuleModule { 42 | return ESLintUtils.RuleCreator( 43 | name => 44 | `https://github.com/infctr/eslint-plugin-typescript-sort-keys/blob/master/docs/rules/${name}.md`, 45 | )(data) 46 | } 47 | -------------------------------------------------------------------------------- /tests/autofix.spec.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import fs from 'fs' 3 | import tmp from 'tmp' 4 | import { ESLint, Linter } from 'eslint' 5 | 6 | import plugin from '../src' 7 | import recommended from 'config/recommended' 8 | import { SortingOrder } from 'common/options' 9 | import { typescript } from './helpers/configs' 10 | 11 | describe('autofix', () => { 12 | beforeEach(() => { 13 | tmp.setGracefulCleanup() 14 | }) 15 | 16 | it.each([ 17 | [recommended, 'autofix.output.ts'], 18 | [ 19 | { 20 | plugins: recommended.plugins, 21 | rules: { 22 | ...recommended.rules, 23 | 'typescript-sort-keys/interface': [ 24 | 'error' as const, 25 | SortingOrder.Ascending, 26 | { caseSensitive: true, natural: true, requiredFirst: true }, 27 | ] as Linter.RuleEntry, 28 | }, 29 | }, 30 | 'requiredFirst.output.ts', 31 | ], 32 | ])( 33 | 'should autofix and properly format comments and indent level', 34 | async (config, fileName) => { 35 | const { name: tmpDir } = tmp.dirSync({ 36 | prefix: 'typescript-sort-keys-', 37 | unsafeCleanup: true, 38 | }) 39 | 40 | const testFilePath = path.join(tmpDir, 'autofix.ts') 41 | const input = fs.readFileSync('tests/fixtures/autofix.input.ts', 'utf8') 42 | const expected = fs.readFileSync(`tests/fixtures/${fileName}`, 'utf8') 43 | 44 | fs.writeFileSync(testFilePath, input) 45 | 46 | const eslint = new ESLint({ 47 | overrideConfig: { 48 | ...config, 49 | parser: typescript.parser, 50 | parserOptions: { sourceType: 'module' }, 51 | }, 52 | plugins: { 53 | 'typescript-sort-keys': plugin, 54 | }, 55 | useEslintrc: false, 56 | fix: true, 57 | }) 58 | 59 | const results = await eslint.lintFiles(testFilePath) 60 | const result = results[0] 61 | 62 | expect(result.messages).toHaveLength(0) 63 | expect(result.errorCount).toBe(0) 64 | expect(result.warningCount).toBe(0) 65 | expect(result.fixableErrorCount).toBe(0) 66 | expect(result.fixableWarningCount).toBe(0) 67 | 68 | await ESLint.outputFixes(results) 69 | 70 | const output = fs.readFileSync(testFilePath, 'utf8') 71 | 72 | expect(output).toStrictEqual(expected) 73 | }, 74 | ) 75 | }) 76 | -------------------------------------------------------------------------------- /tests/config.spec.ts: -------------------------------------------------------------------------------- 1 | import { readdirSync } from 'fs' 2 | 3 | import plugin from '../src' 4 | 5 | describe('recommended config', () => { 6 | const RULE_NAME_PREFIX = 'typescript-sort-keys/' 7 | const { 8 | rules, 9 | configs: { 10 | recommended: { rules: configRules }, 11 | }, 12 | } = plugin 13 | 14 | const entriesToObject = ( 15 | value: readonly [string, T][], 16 | ): Record => { 17 | return value.reduce>((memo, [k, v]) => { 18 | memo[k] = v 19 | return memo 20 | }, {}) 21 | } 22 | 23 | const ruleConfigs = Object.entries(rules) 24 | .filter(([, rule]) => rule.meta.docs && rule.meta.docs.recommended !== false) 25 | .map<[string, string]>(([name, rule]) => [ 26 | `${RULE_NAME_PREFIX}${name}`, 27 | rule.meta.docs && rule.meta.docs.recommended ? 'error' : 'off', 28 | ]) 29 | 30 | it('contains all recommended rules', () => { 31 | expect(entriesToObject(ruleConfigs)).toEqual(configRules) 32 | }) 33 | }) 34 | 35 | describe('plugin', () => { 36 | const ruleFiles: readonly string[] = readdirSync('./src/rules').filter( 37 | file => file !== 'index.ts' && file.endsWith('.ts'), 38 | ) 39 | 40 | const configFiles: readonly string[] = readdirSync('./src/config').filter( 41 | file => file !== 'index.ts' && file.endsWith('.ts'), 42 | ) 43 | 44 | it('should have all the rules', () => { 45 | expect(plugin).toHaveProperty('rules') 46 | expect(Object.keys(plugin.rules)).toHaveLength(ruleFiles.length) 47 | }) 48 | 49 | it('should have all the configs', () => { 50 | expect(plugin).toHaveProperty('configs') 51 | expect(Object.keys(plugin.configs)).toHaveLength(configFiles.length) 52 | }) 53 | }) 54 | -------------------------------------------------------------------------------- /tests/fixtures/autofix.input.ts: -------------------------------------------------------------------------------- 1 | // So typescript treats this as a module 2 | export {}; 3 | 4 | class GraphQLExtension {_: T} 5 | 6 | interface GraphQLResponse {} 7 | 8 | namespace Koa { 9 | export interface Context {} 10 | } 11 | 12 | const inlineArrow: (props: {foo: boolean; baz?: boolean; bar: boolean}) => null = ({...props}) => null; 13 | 14 | const inlineArrow2: (props: {foo: boolean; bar?: boolean; baz: boolean}) => null = ({...props}) => null; 15 | 16 | const inlineWeird: (props: {foo?: boolean;baz: boolean, 17 | bar: boolean}) => null = ({...props}) => null; 18 | 19 | function inlineGeneric({...props}: T | {foo: boolean; bar: boolean; baz?: boolean}) { 20 | return null 21 | } 22 | 23 | enum InlineEnum {e="T", c="T", d="T", b="T", a="T"} 24 | 25 | enum InlineEnum2 {Foo = 'FOO',Baz = 'BAZ', Bar = 'BAR' } 26 | 27 | enum InlineEnum3 {b_="T", c="T", C="T"} 28 | 29 | enum WeirdEnum { 30 | Foo = 'FOO',Baz = 'BAZ', Bar = 'BAR',} 31 | 32 | interface InlineInterface {e: "T"; c?:"T"; d:"T"; b:"T"; a?:"T"} 33 | 34 | class Class extends GraphQLExtension<{ 35 | graphqlResponse: GraphQLResponse; 36 | context?: Koa.Context; 37 | }> { 38 | public method(o: { 39 | graphqlResponse: GraphQLResponse; 40 | context?: Koa.Context; 41 | }): void | { graphqlResponse?: GraphQLResponse; context?: Koa.Context } { 42 | // 43 | } 44 | } 45 | 46 | interface Interface { 47 | /** 48 | * %foo 49 | */ 50 | foo: boolean; 51 | /* %baz */ 52 | baz?: boolean; 53 | // %bar 54 | bar: boolean; 55 | } 56 | 57 | type Type1 = Partial<{ 58 | // %foo 59 | foo?: boolean; 60 | /* %baz */ baz: boolean; 61 | 62 | /** 63 | * %bar 64 | */ 65 | bar: boolean; 66 | }> & {/* %foo */ 67 | foo?: boolean; 68 | 69 | // %baz 70 | baz: boolean; 71 | /** 72 | * %bar 73 | */ 74 | bar: boolean; 75 | } & { 76 | [K in keyof TKey]: boolean; 77 | }; 78 | 79 | enum StringEnum { 80 | /* %foo */ 81 | Foo = 'FOO', 82 | 83 | // %baz 84 | Baz = 'BAZ', 85 | 86 | /** 87 | * %bar 88 | */ 89 | Bar = 'BAR', 90 | } 91 | 92 | type Type2 = {/* %foo */ 93 | foo?: boolean; 94 | 95 | // %baz 96 | baz: boolean; 97 | /** 98 | * %bar 99 | */ 100 | bar: boolean; 101 | } 102 | 103 | interface ClockConstructor { 104 | new (hour: number, minute: number): ClockInterface; 105 | new (hour: number): ClockInterface; 106 | } 107 | 108 | interface ClockInterface { 109 | tick(): void; 110 | } 111 | 112 | interface Methods { 113 | /** 114 | * %foo 115 | */ 116 | quux(): any; 117 | qux?(); 118 | quuz?(): any; 119 | foo: boolean; 120 | /* %baz */ 121 | baz: boolean; 122 | // %bar 123 | bar(): boolean; 124 | ['grault']?(): void; 125 | ['corge']?(): void; 126 | ['garply'](); 127 | } 128 | -------------------------------------------------------------------------------- /tests/fixtures/autofix.output.ts: -------------------------------------------------------------------------------- 1 | // So typescript treats this as a module 2 | export {}; 3 | 4 | class GraphQLExtension {_: T} 5 | 6 | interface GraphQLResponse {} 7 | 8 | namespace Koa { 9 | export interface Context {} 10 | } 11 | 12 | const inlineArrow: (props: {bar: boolean, baz?: boolean; foo: boolean;}) => null = ({...props}) => null; 13 | 14 | const inlineArrow2: (props: {bar?: boolean; baz: boolean, foo: boolean;}) => null = ({...props}) => null; 15 | 16 | const inlineWeird: (props: {bar: boolean,baz: boolean, 17 | foo?: boolean;}) => null = ({...props}) => null; 18 | 19 | function inlineGeneric({...props}: T | {bar: boolean; baz?: boolean, foo: boolean;}) { 20 | return null 21 | } 22 | 23 | enum InlineEnum {a="T", b="T", c="T", d="T", e="T"} 24 | 25 | enum InlineEnum2 {Bar = 'BAR',Baz = 'BAZ', Foo = 'FOO' } 26 | 27 | enum InlineEnum3 {C="T", b_="T", c="T"} 28 | 29 | enum WeirdEnum { 30 | Bar = 'BAR',Baz = 'BAZ', Foo = 'FOO'} 31 | 32 | interface InlineInterface {a?:"T", b:"T"; c?:"T"; d:"T"; e: "T";} 33 | 34 | class Class extends GraphQLExtension<{ 35 | context?: Koa.Context; 36 | graphqlResponse: GraphQLResponse; 37 | }> { 38 | public method(o: { 39 | context?: Koa.Context; 40 | graphqlResponse: GraphQLResponse; 41 | }): void | { context?: Koa.Context, graphqlResponse?: GraphQLResponse; } { 42 | // 43 | } 44 | } 45 | 46 | interface Interface { 47 | // %bar 48 | bar: boolean; 49 | /* %baz */ 50 | baz?: boolean; 51 | /** 52 | * %foo 53 | */ 54 | foo: boolean; 55 | } 56 | 57 | type Type1 = Partial<{ 58 | /** 59 | * %bar 60 | */ 61 | bar: boolean; 62 | /* %baz */ baz: boolean; 63 | 64 | // %foo 65 | foo?: boolean; 66 | }> & { 67 | /** 68 | * %bar 69 | */ 70 | bar: boolean; 71 | 72 | // %baz 73 | baz: boolean; 74 | /* %foo */ 75 | foo?: boolean; 76 | } & { 77 | [K in keyof TKey]: boolean; 78 | }; 79 | 80 | enum StringEnum { 81 | /** 82 | * %bar 83 | */ 84 | Bar = 'BAR', 85 | 86 | // %baz 87 | Baz = 'BAZ', 88 | 89 | /* %foo */ 90 | Foo = 'FOO' 91 | } 92 | 93 | type Type2 = { 94 | /** 95 | * %bar 96 | */ 97 | bar: boolean; 98 | 99 | // %baz 100 | baz: boolean; 101 | /* %foo */ 102 | foo?: boolean; 103 | } 104 | 105 | interface ClockConstructor { 106 | new (hour: number, minute: number): ClockInterface; 107 | new (hour: number): ClockInterface; 108 | } 109 | 110 | interface ClockInterface { 111 | tick(): void; 112 | } 113 | 114 | interface Methods { 115 | // %bar 116 | bar(): boolean; 117 | /* %baz */ 118 | baz: boolean; 119 | ['corge']?(): void; 120 | foo: boolean; 121 | ['garply'](); 122 | ['grault']?(): void; 123 | /** 124 | * %foo 125 | */ 126 | quux(): any; 127 | quuz?(): any; 128 | qux?(); 129 | } 130 | -------------------------------------------------------------------------------- /tests/fixtures/requiredFirst.output.ts: -------------------------------------------------------------------------------- 1 | // So typescript treats this as a module 2 | export {}; 3 | 4 | class GraphQLExtension {_: T} 5 | 6 | interface GraphQLResponse {} 7 | 8 | namespace Koa { 9 | export interface Context {} 10 | } 11 | 12 | const inlineArrow: (props: {bar: boolean, foo: boolean; baz?: boolean;}) => null = ({...props}) => null; 13 | 14 | const inlineArrow2: (props: {baz: boolean, foo: boolean; bar?: boolean;}) => null = ({...props}) => null; 15 | 16 | const inlineWeird: (props: {bar: boolean,baz: boolean, 17 | foo?: boolean;}) => null = ({...props}) => null; 18 | 19 | function inlineGeneric({...props}: T | {bar: boolean; foo: boolean; baz?: boolean}) { 20 | return null 21 | } 22 | 23 | enum InlineEnum {a="T", b="T", c="T", d="T", e="T"} 24 | 25 | enum InlineEnum2 {Bar = 'BAR',Baz = 'BAZ', Foo = 'FOO' } 26 | 27 | enum InlineEnum3 {C="T", b_="T", c="T"} 28 | 29 | enum WeirdEnum { 30 | Bar = 'BAR',Baz = 'BAZ', Foo = 'FOO'} 31 | 32 | interface InlineInterface {b:"T"; d:"T"; e: "T"; a?:"T", c?:"T";} 33 | 34 | class Class extends GraphQLExtension<{ 35 | graphqlResponse: GraphQLResponse; 36 | context?: Koa.Context; 37 | }> { 38 | public method(o: { 39 | graphqlResponse: GraphQLResponse; 40 | context?: Koa.Context; 41 | }): void | { context?: Koa.Context, graphqlResponse?: GraphQLResponse; } { 42 | // 43 | } 44 | } 45 | 46 | interface Interface { 47 | // %bar 48 | bar: boolean; 49 | /** 50 | * %foo 51 | */ 52 | foo: boolean; 53 | /* %baz */ 54 | baz?: boolean; 55 | } 56 | 57 | type Type1 = Partial<{ 58 | /** 59 | * %bar 60 | */ 61 | bar: boolean; 62 | /* %baz */ baz: boolean; 63 | 64 | // %foo 65 | foo?: boolean; 66 | }> & { 67 | /** 68 | * %bar 69 | */ 70 | bar: boolean; 71 | 72 | // %baz 73 | baz: boolean; 74 | /* %foo */ 75 | foo?: boolean; 76 | } & { 77 | [K in keyof TKey]: boolean; 78 | }; 79 | 80 | enum StringEnum { 81 | /** 82 | * %bar 83 | */ 84 | Bar = 'BAR', 85 | 86 | // %baz 87 | Baz = 'BAZ', 88 | 89 | /* %foo */ 90 | Foo = 'FOO' 91 | } 92 | 93 | type Type2 = { 94 | /** 95 | * %bar 96 | */ 97 | bar: boolean; 98 | 99 | // %baz 100 | baz: boolean; 101 | /* %foo */ 102 | foo?: boolean; 103 | } 104 | 105 | interface ClockConstructor { 106 | new (hour: number, minute: number): ClockInterface; 107 | new (hour: number): ClockInterface; 108 | } 109 | 110 | interface ClockInterface { 111 | tick(): void; 112 | } 113 | 114 | interface Methods { 115 | // %bar 116 | bar(): boolean; 117 | /* %baz */ 118 | baz: boolean; 119 | foo: boolean; 120 | ['garply'](); 121 | /** 122 | * %foo 123 | */ 124 | quux(): any; 125 | ['corge']?(): void; 126 | ['grault']?(): void; 127 | quuz?(): any; 128 | qux?(); 129 | } 130 | -------------------------------------------------------------------------------- /tests/helpers/configs.ts: -------------------------------------------------------------------------------- 1 | import { Linter } from 'eslint' 2 | import * as path from 'path' 3 | 4 | export const filename = path.join(__dirname, 'file.ts') 5 | 6 | export const typescript: Linter.Config = { 7 | parser: require.resolve('@typescript-eslint/parser'), 8 | parserOptions: { 9 | sourceType: 'module', 10 | project: path.join(__dirname, './tsconfig.json'), 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /tests/helpers/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["es2017"], 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "skipLibCheck": true, 7 | "target": "es2017" 8 | }, 9 | "files": ["file.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /tests/helpers/util.ts: -------------------------------------------------------------------------------- 1 | import { RuleTester as ESLintRuleTester } from 'eslint' 2 | import { filename } from './configs' 3 | 4 | type OptionsSet = { 5 | /** 6 | * The set of options this test case should pass for. 7 | */ 8 | readonly optionsSet: readonly (Options | [])[] 9 | } 10 | 11 | export type ValidTestCase = Omit< 12 | ESLintRuleTester.ValidTestCase, 13 | 'options' 14 | > & 15 | OptionsSet 16 | 17 | export type InvalidTestCase = Omit< 18 | ESLintRuleTester.InvalidTestCase, 19 | 'options' 20 | > & 21 | OptionsSet 22 | 23 | /** 24 | * Convert our test cases into ones eslint test runner is expecting. 25 | */ 26 | export function processInvalidTestCase( 27 | testCases: readonly InvalidTestCase[], 28 | ): ESLintRuleTester.InvalidTestCase[] { 29 | return testCases.flatMap(testCase => 30 | testCase.optionsSet.map(options => { 31 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 32 | const { optionsSet, ...eslintTestCase } = testCase 33 | 34 | return { filename, ...eslintTestCase, options } 35 | }), 36 | ) 37 | } 38 | 39 | /** 40 | * Convert our test cases into ones eslint test runner is expecting. 41 | */ 42 | export function processValidTestCase( 43 | testCases: readonly ValidTestCase[], 44 | ): ESLintRuleTester.ValidTestCase[] { 45 | return processInvalidTestCase(testCases as any) 46 | } 47 | -------------------------------------------------------------------------------- /tests/rules/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 100, 3 | trailingComma: 'all', 4 | arrowParens: 'avoid', 5 | semi: false, 6 | } 7 | -------------------------------------------------------------------------------- /tests/rules/interface.spec.ts: -------------------------------------------------------------------------------- 1 | import { Rule, RuleTester } from 'eslint' 2 | 3 | import { rule, name, Options } from 'rules/interface' 4 | import { SortingOrder } from 'common/options' 5 | import { typescript } from '../helpers/configs' 6 | import { 7 | InvalidTestCase, 8 | processInvalidTestCase, 9 | processValidTestCase, 10 | ValidTestCase, 11 | } from '../helpers/util' 12 | 13 | const valid: readonly ValidTestCase[] = [ 14 | /** 15 | * default, asc, caseSensitive 16 | */ 17 | { 18 | code: 'interface U {_:T; a:T; b:T;}', 19 | optionsSet: [ 20 | [], 21 | [SortingOrder.Ascending], 22 | [SortingOrder.Ascending, { caseSensitive: true }], 23 | [SortingOrder.Ascending, { natural: false }], 24 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 25 | [SortingOrder.Ascending, { caseSensitive: true, natural: false, requiredFirst: false }], 26 | ], 27 | }, 28 | { 29 | code: 'interface U {a:T; b:T; c:T;}', 30 | optionsSet: [ 31 | [], 32 | [SortingOrder.Ascending], 33 | [SortingOrder.Ascending, { caseSensitive: true }], 34 | [SortingOrder.Ascending, { natural: false }], 35 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 36 | [SortingOrder.Ascending, { caseSensitive: true, natural: false, requiredFirst: false }], 37 | ], 38 | }, 39 | { 40 | code: 'interface U {a:T; b:T; b_:T;}', 41 | optionsSet: [ 42 | [], 43 | [SortingOrder.Ascending], 44 | [SortingOrder.Ascending, { caseSensitive: true }], 45 | [SortingOrder.Ascending, { natural: false }], 46 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 47 | [SortingOrder.Ascending, { caseSensitive: true, natural: false, requiredFirst: false }], 48 | ], 49 | }, 50 | { 51 | code: 'interface U {C:T; b_:T; c:T;}', 52 | optionsSet: [ 53 | [], 54 | [SortingOrder.Ascending], 55 | [SortingOrder.Ascending, { caseSensitive: true }], 56 | [SortingOrder.Ascending, { natural: false }], 57 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 58 | [SortingOrder.Ascending, { caseSensitive: true, natural: false, requiredFirst: false }], 59 | ], 60 | }, 61 | { 62 | code: 'interface U {$:T; A:T; _:T; a:T;}', 63 | optionsSet: [ 64 | [], 65 | [SortingOrder.Ascending], 66 | [SortingOrder.Ascending, { caseSensitive: true }], 67 | [SortingOrder.Ascending, { natural: false }], 68 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 69 | [SortingOrder.Ascending, { caseSensitive: true, natural: false, requiredFirst: false }], 70 | ], 71 | }, 72 | { 73 | code: "interface U {1:T; '11':T; 2:T; A:T;}", 74 | optionsSet: [ 75 | [], 76 | [SortingOrder.Ascending], 77 | [SortingOrder.Ascending, { caseSensitive: true }], 78 | [SortingOrder.Ascending, { natural: false }], 79 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 80 | [SortingOrder.Ascending, { caseSensitive: true, natural: false, requiredFirst: false }], 81 | ], 82 | }, 83 | { 84 | code: "interface U {'#':T; 'Z':T; À:T; è:T;}", 85 | optionsSet: [ 86 | [], 87 | [SortingOrder.Ascending], 88 | [SortingOrder.Ascending, { caseSensitive: true }], 89 | [SortingOrder.Ascending, { natural: false }], 90 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 91 | [SortingOrder.Ascending, { caseSensitive: true, natural: false, requiredFirst: false }], 92 | ], 93 | }, 94 | 95 | /** 96 | * computed 97 | */ 98 | { 99 | code: 'interface U {a:T; ["ab"]:T; b:T; c:T;}', 100 | optionsSet: [ 101 | [], 102 | [SortingOrder.Ascending], 103 | [SortingOrder.Ascending, { caseSensitive: true }], 104 | [SortingOrder.Ascending, { natural: false }], 105 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 106 | [SortingOrder.Ascending, { caseSensitive: true, natural: false, requiredFirst: false }], 107 | ], 108 | }, 109 | 110 | /** 111 | * nested 112 | */ 113 | { 114 | code: 'interface U {a:T; b:{x:T; y:T;}; c:T;}', 115 | optionsSet: [ 116 | [], 117 | [SortingOrder.Ascending], 118 | [SortingOrder.Ascending, { caseSensitive: true }], 119 | [SortingOrder.Ascending, { natural: false }], 120 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 121 | [SortingOrder.Ascending, { caseSensitive: true, natural: false, requiredFirst: false }], 122 | ], 123 | }, 124 | { 125 | code: 'interface U {a:T; b:{x:T; y:T; z:{i:T; j:T;};}; c:T;}', 126 | optionsSet: [ 127 | [], 128 | [SortingOrder.Ascending], 129 | [SortingOrder.Ascending, { caseSensitive: true }], 130 | [SortingOrder.Ascending, { natural: false }], 131 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 132 | [SortingOrder.Ascending, { caseSensitive: true, natural: false, requiredFirst: false }], 133 | ], 134 | }, 135 | { 136 | code: 'type U = {a:T; b:{x:T; y:T;}; c:T;}', 137 | optionsSet: [ 138 | [], 139 | [SortingOrder.Ascending], 140 | [SortingOrder.Ascending, { caseSensitive: true }], 141 | [SortingOrder.Ascending, { natural: false }], 142 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 143 | [SortingOrder.Ascending, { caseSensitive: true, natural: false, requiredFirst: false }], 144 | ], 145 | }, 146 | { 147 | code: 'type U = {a:T; b:{x:T; y:T; z:{i:T; j:T;};}; c:T;}', 148 | optionsSet: [ 149 | [], 150 | [SortingOrder.Ascending], 151 | [SortingOrder.Ascending, { caseSensitive: true }], 152 | [SortingOrder.Ascending, { natural: false }], 153 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 154 | [SortingOrder.Ascending, { caseSensitive: true, natural: false, requiredFirst: false }], 155 | ], 156 | }, 157 | 158 | /** 159 | * asc, insensitive 160 | */ 161 | { 162 | code: 'interface U {_:T; a:T; b:T;}', 163 | optionsSet: [[SortingOrder.Ascending, { caseSensitive: false }]], 164 | }, 165 | { 166 | code: 'interface U {a:T; b:T; c:T;}', 167 | optionsSet: [[SortingOrder.Ascending, { caseSensitive: false }]], 168 | }, 169 | { 170 | code: 'interface U {a:T; b:T; b_:T;}', 171 | optionsSet: [[SortingOrder.Ascending, { caseSensitive: false }]], 172 | }, 173 | { 174 | code: 'interface U {b_:T; C:T; c:T;}', 175 | optionsSet: [[SortingOrder.Ascending, { caseSensitive: false }]], 176 | }, 177 | { 178 | code: 'interface U {b_:T; c:T; C:T;}', 179 | optionsSet: [[SortingOrder.Ascending, { caseSensitive: false }]], 180 | }, 181 | { 182 | code: 'interface U {$:T; _:T; A:T; a:T;}', 183 | optionsSet: [[SortingOrder.Ascending, { caseSensitive: false }]], 184 | }, 185 | { 186 | code: "interface U {1:T; '11':T; 2:T; A:T;}", 187 | optionsSet: [[SortingOrder.Ascending, { caseSensitive: false }]], 188 | }, 189 | 190 | { 191 | code: "interface U {'#':T; 'Z':T; À:T; è:T;}", 192 | optionsSet: [[SortingOrder.Ascending, { natural: true }]], 193 | }, 194 | 195 | /** 196 | * asc, natural, insensitive 197 | */ 198 | { 199 | code: 'interface U {_:T; a:T; b:T;}', 200 | optionsSet: [[SortingOrder.Ascending, { natural: true, caseSensitive: false }]], 201 | }, 202 | { 203 | code: 'interface U {a:T; b:T; c:T;}', 204 | optionsSet: [[SortingOrder.Ascending, { natural: true, caseSensitive: false }]], 205 | }, 206 | { 207 | code: 'interface U {a:T; b:T; b_:T;}', 208 | optionsSet: [[SortingOrder.Ascending, { natural: true, caseSensitive: false }]], 209 | }, 210 | { 211 | code: 'interface U {b_:T; C:T; c:T;}', 212 | optionsSet: [[SortingOrder.Ascending, { natural: true, caseSensitive: false }]], 213 | }, 214 | { 215 | code: 'interface U {b_:T; c:T; C:T;}', 216 | optionsSet: [[SortingOrder.Ascending, { natural: true, caseSensitive: false }]], 217 | }, 218 | { 219 | code: 'interface U {$:T; _:T; A:T; a:T;}', 220 | optionsSet: [[SortingOrder.Ascending, { natural: true, caseSensitive: false }]], 221 | }, 222 | { 223 | code: "interface U {1:T; 2:T; '11':T; A:T;}", 224 | optionsSet: [[SortingOrder.Ascending, { natural: true, caseSensitive: false }]], 225 | }, 226 | { 227 | code: "interface U {'#':T; 'Z':T; À:T; è:T;}", 228 | optionsSet: [[SortingOrder.Ascending, { natural: true, caseSensitive: false }]], 229 | }, 230 | 231 | /** 232 | * asc, natural, insensitive, required 233 | */ 234 | { 235 | code: 'interface U {_:T; b:T; a?:T;}', 236 | optionsSet: [ 237 | [SortingOrder.Ascending, { natural: true, caseSensitive: false, requiredFirst: true }], 238 | ], 239 | }, 240 | { 241 | code: 'interface U {a:T; c:T; b?:T;}', 242 | optionsSet: [ 243 | [SortingOrder.Ascending, { natural: true, caseSensitive: false, requiredFirst: true }], 244 | ], 245 | }, 246 | { 247 | code: 'interface U {b:T; b_:T; a?:T;}', 248 | optionsSet: [ 249 | [SortingOrder.Ascending, { natural: true, caseSensitive: false, requiredFirst: true }], 250 | ], 251 | }, 252 | { 253 | code: 'interface U {C:T; c:T; b_?:T;}', 254 | optionsSet: [ 255 | [SortingOrder.Ascending, { natural: true, caseSensitive: false, requiredFirst: true }], 256 | ], 257 | }, 258 | { 259 | code: 'interface U {c:T; C:T; b_?:T;}', 260 | optionsSet: [ 261 | [SortingOrder.Ascending, { natural: true, caseSensitive: false, requiredFirst: true }], 262 | ], 263 | }, 264 | { 265 | code: 'interface U {$:T; _:T; A?:T; a?:T;}', 266 | optionsSet: [ 267 | [SortingOrder.Ascending, { natural: true, caseSensitive: false, requiredFirst: true }], 268 | ], 269 | }, 270 | { 271 | code: "interface U {1:T; '11':T; A:T; 2?:T;}", 272 | optionsSet: [ 273 | [SortingOrder.Ascending, { natural: true, caseSensitive: false, requiredFirst: true }], 274 | ], 275 | }, 276 | { 277 | code: "interface U {'Z':T; À:T; è:T; '#'?:T;}", 278 | optionsSet: [ 279 | [SortingOrder.Ascending, { natural: true, caseSensitive: false, requiredFirst: true }], 280 | ], 281 | }, 282 | 283 | /** 284 | * asc, required 285 | */ 286 | { 287 | code: 'interface U {_:T; b:T; a?:T;}', 288 | optionsSet: [[SortingOrder.Ascending, { requiredFirst: true }]], 289 | }, 290 | { 291 | code: 'interface U {a:T; c:T; b?:T;}', 292 | optionsSet: [[SortingOrder.Ascending, { requiredFirst: true }]], 293 | }, 294 | { 295 | code: 'interface U {b:T; b_:T; a?:T;}', 296 | optionsSet: [[SortingOrder.Ascending, { requiredFirst: true }]], 297 | }, 298 | { 299 | code: 'interface U {C:T; c:T; b_?:T;}', 300 | optionsSet: [[SortingOrder.Ascending, { requiredFirst: true }]], 301 | }, 302 | { 303 | code: 'interface U {1:T; 11:T; 9:T; 111?:T;}', 304 | optionsSet: [[SortingOrder.Ascending, { requiredFirst: true }]], 305 | }, 306 | { 307 | code: 'interface U {$:T; _:T; A?:T; a?:T;}', 308 | optionsSet: [[SortingOrder.Ascending, { requiredFirst: true }]], 309 | }, 310 | { 311 | code: "interface U {10:T; '11':T; 1?:T; 12?:T; 2?:T;}", 312 | optionsSet: [[SortingOrder.Ascending, { requiredFirst: true }]], 313 | }, 314 | { 315 | code: "interface U {'Z':T; À:T; è:T; '#'?:T;}", 316 | optionsSet: [[SortingOrder.Ascending, { requiredFirst: true }]], 317 | }, 318 | 319 | /** 320 | * asc, natural, insensitive, not-required 321 | */ 322 | { 323 | code: 'interface U {_:T; a?:T; b:T;}', 324 | optionsSet: [ 325 | [SortingOrder.Ascending, { natural: true, caseSensitive: false, requiredFirst: false }], 326 | ], 327 | }, 328 | { 329 | code: 'interface U {a:T; b?:T; c:T;}', 330 | optionsSet: [ 331 | [SortingOrder.Ascending, { natural: true, caseSensitive: false, requiredFirst: false }], 332 | ], 333 | }, 334 | { 335 | code: 'interface U {a?:T; b:T; b_:T;}', 336 | optionsSet: [ 337 | [SortingOrder.Ascending, { natural: true, caseSensitive: false, requiredFirst: false }], 338 | ], 339 | }, 340 | { 341 | code: 'interface U {b_?:T; C:T; c:T;}', 342 | optionsSet: [ 343 | [SortingOrder.Ascending, { natural: true, caseSensitive: false, requiredFirst: false }], 344 | ], 345 | }, 346 | { 347 | code: 'interface U {b_?:T; c:T; C:T;}', 348 | optionsSet: [ 349 | [SortingOrder.Ascending, { natural: true, caseSensitive: false, requiredFirst: false }], 350 | ], 351 | }, 352 | { 353 | code: 'interface U {$:T; _:T; A?:T; a?:T;}', 354 | optionsSet: [ 355 | [SortingOrder.Ascending, { natural: true, caseSensitive: false, requiredFirst: false }], 356 | ], 357 | }, 358 | { 359 | code: "interface U {1:T; 2?:T; '11':T; A:T;}", 360 | optionsSet: [ 361 | [SortingOrder.Ascending, { natural: true, caseSensitive: false, requiredFirst: false }], 362 | ], 363 | }, 364 | { 365 | code: "interface U {'#'?:T; 'Z':T; À:T; è:T;}", 366 | optionsSet: [ 367 | [SortingOrder.Ascending, { natural: true, caseSensitive: false, requiredFirst: false }], 368 | ], 369 | }, 370 | 371 | /** 372 | * desc 373 | */ 374 | { 375 | code: 'interface U {b:T; a:T; _:T;}', 376 | optionsSet: [ 377 | [SortingOrder.Descending], 378 | [SortingOrder.Descending, { caseSensitive: true }], 379 | [SortingOrder.Descending, { natural: false }], 380 | [SortingOrder.Descending, { caseSensitive: true, natural: false }], 381 | ], 382 | }, 383 | { 384 | code: 'interface U {c:T; b:T; a:T;}', 385 | optionsSet: [ 386 | [SortingOrder.Descending], 387 | [SortingOrder.Descending, { caseSensitive: true }], 388 | [SortingOrder.Descending, { natural: false }], 389 | [SortingOrder.Descending, { caseSensitive: true, natural: false }], 390 | ], 391 | }, 392 | { 393 | code: 'interface U {b_:T; b:T; a:T;}', 394 | optionsSet: [ 395 | [SortingOrder.Descending], 396 | [SortingOrder.Descending, { caseSensitive: true }], 397 | [SortingOrder.Descending, { natural: false }], 398 | [SortingOrder.Descending, { caseSensitive: true, natural: false }], 399 | ], 400 | }, 401 | { 402 | code: 'interface U {c:T; b_:T; C:T;}', 403 | optionsSet: [ 404 | [SortingOrder.Descending], 405 | [SortingOrder.Descending, { caseSensitive: true }], 406 | [SortingOrder.Descending, { natural: false }], 407 | [SortingOrder.Descending, { caseSensitive: true, natural: false }], 408 | ], 409 | }, 410 | { 411 | code: 'interface U {a:T; _:T; A:T; $:T;}', 412 | optionsSet: [ 413 | [SortingOrder.Descending], 414 | [SortingOrder.Descending, { caseSensitive: true }], 415 | [SortingOrder.Descending, { natural: false }], 416 | [SortingOrder.Descending, { caseSensitive: true, natural: false }], 417 | ], 418 | }, 419 | { 420 | code: "interface U {A:T; 2:T; '11':T; 1:T;}", 421 | optionsSet: [ 422 | [SortingOrder.Descending], 423 | [SortingOrder.Descending, { caseSensitive: true }], 424 | [SortingOrder.Descending, { natural: false }], 425 | [SortingOrder.Descending, { caseSensitive: true, natural: false }], 426 | ], 427 | }, 428 | { 429 | code: "interface U {è:T; À:T; 'Z':T; '#':T;}", 430 | optionsSet: [ 431 | [SortingOrder.Descending], 432 | [SortingOrder.Descending, { caseSensitive: true }], 433 | [SortingOrder.Descending, { natural: false }], 434 | [SortingOrder.Descending, { caseSensitive: true, natural: false }], 435 | ], 436 | }, 437 | 438 | /** 439 | * desc, insensitive 440 | */ 441 | { 442 | code: 'interface U {b:T; a:T; _:T;}', 443 | optionsSet: [ 444 | [SortingOrder.Descending, { caseSensitive: false }], 445 | [SortingOrder.Descending, { caseSensitive: false, natural: false }], 446 | ], 447 | }, 448 | { 449 | code: 'interface U {c:T; b:T; a:T;}', 450 | optionsSet: [ 451 | [SortingOrder.Descending, { caseSensitive: false }], 452 | [SortingOrder.Descending, { caseSensitive: false, natural: false }], 453 | ], 454 | }, 455 | { 456 | code: 'interface U {b_:T; b:T; a:T;}', 457 | optionsSet: [ 458 | [SortingOrder.Descending, { caseSensitive: false }], 459 | [SortingOrder.Descending, { caseSensitive: false, natural: false }], 460 | ], 461 | }, 462 | { 463 | code: 'interface U {c:T; C:T; b_:T;}', 464 | optionsSet: [ 465 | [SortingOrder.Descending, { caseSensitive: false }], 466 | [SortingOrder.Descending, { caseSensitive: false, natural: false }], 467 | ], 468 | }, 469 | { 470 | code: 'interface U {C:T; c:T; b_:T;}', 471 | optionsSet: [ 472 | [SortingOrder.Descending, { caseSensitive: false }], 473 | [SortingOrder.Descending, { caseSensitive: false, natural: false }], 474 | ], 475 | }, 476 | { 477 | code: 'interface U {a:T; A:T; _:T; $:T;}', 478 | optionsSet: [ 479 | [SortingOrder.Descending, { caseSensitive: false }], 480 | [SortingOrder.Descending, { caseSensitive: false, natural: false }], 481 | ], 482 | }, 483 | { 484 | code: "interface U {A:T; 2:T; '11':T; 1:T;}", 485 | optionsSet: [ 486 | [SortingOrder.Descending, { caseSensitive: false }], 487 | [SortingOrder.Descending, { caseSensitive: false, natural: false }], 488 | ], 489 | }, 490 | { 491 | code: "interface U {è:T; À:T; 'Z':T; '#':T;}", 492 | optionsSet: [ 493 | [SortingOrder.Descending, { caseSensitive: false }], 494 | [SortingOrder.Descending, { caseSensitive: false, natural: false }], 495 | ], 496 | }, 497 | 498 | /** 499 | * desc, natural 500 | */ 501 | { 502 | code: 'interface U {b:T; a:T; _:T;}', 503 | optionsSet: [ 504 | [SortingOrder.Descending, { natural: true }], 505 | [SortingOrder.Descending, { natural: true, caseSensitive: true }], 506 | ], 507 | }, 508 | { 509 | code: 'interface U {c:T; b:T; a:T;}', 510 | optionsSet: [ 511 | [SortingOrder.Descending, { natural: true }], 512 | [SortingOrder.Descending, { natural: true, caseSensitive: true }], 513 | ], 514 | }, 515 | { 516 | code: 'interface U {b_:T; b:T; a:T;}', 517 | optionsSet: [ 518 | [SortingOrder.Descending, { natural: true }], 519 | [SortingOrder.Descending, { natural: true, caseSensitive: true }], 520 | ], 521 | }, 522 | { 523 | code: 'interface U {c:T; b_:T; C:T;}', 524 | optionsSet: [ 525 | [SortingOrder.Descending, { natural: true }], 526 | [SortingOrder.Descending, { natural: true, caseSensitive: true }], 527 | ], 528 | }, 529 | { 530 | code: 'interface U {a:T; A:T; _:T; $:T;}', 531 | optionsSet: [ 532 | [SortingOrder.Descending, { natural: true }], 533 | [SortingOrder.Descending, { natural: true, caseSensitive: true }], 534 | ], 535 | }, 536 | { 537 | code: "interface U {A:T; '11':T; 2:T; 1:T;}", 538 | optionsSet: [ 539 | [SortingOrder.Descending, { natural: true }], 540 | [SortingOrder.Descending, { natural: true, caseSensitive: true }], 541 | ], 542 | }, 543 | { 544 | code: "interface U {è:T; À:T; 'Z':T; '#':T;}", 545 | optionsSet: [ 546 | [SortingOrder.Descending, { natural: true }], 547 | [SortingOrder.Descending, { natural: true, caseSensitive: true }], 548 | ], 549 | }, 550 | 551 | /** 552 | * desc, natural, insensitive 553 | */ 554 | { 555 | code: 'interface U {b:T; a:T; _:T;}', 556 | optionsSet: [[SortingOrder.Descending, { natural: true, caseSensitive: false }]], 557 | }, 558 | { 559 | code: 'interface U {c:T; b:T; a:T;}', 560 | optionsSet: [[SortingOrder.Descending, { natural: true, caseSensitive: false }]], 561 | }, 562 | { 563 | code: 'interface U {b_:T; b:T; a:T;}', 564 | optionsSet: [[SortingOrder.Descending, { natural: true, caseSensitive: false }]], 565 | }, 566 | { 567 | code: 'interface U {c:T; C:T; b_:T;}', 568 | optionsSet: [[SortingOrder.Descending, { natural: true, caseSensitive: false }]], 569 | }, 570 | { 571 | code: 'interface U {C:T; c:T; b_:T;}', 572 | optionsSet: [[SortingOrder.Descending, { natural: true, caseSensitive: false }]], 573 | }, 574 | { 575 | code: 'interface U {a:T; A:T; _:T; $:T;}', 576 | optionsSet: [[SortingOrder.Descending, { natural: true, caseSensitive: false }]], 577 | }, 578 | { 579 | code: "interface U {A:T; '11':T; 2:T; 1:T;}", 580 | optionsSet: [[SortingOrder.Descending, { natural: true, caseSensitive: false }]], 581 | }, 582 | { 583 | code: "interface U {è:T; À:T; 'Z':T; '#':T;}", 584 | optionsSet: [[SortingOrder.Descending, { natural: true, caseSensitive: false }]], 585 | }, 586 | 587 | /** 588 | * desc, natural, insensitive, required 589 | */ 590 | { 591 | code: 'interface U {b:T; _:T; a?:T;}', 592 | optionsSet: [ 593 | [SortingOrder.Descending, { natural: true, caseSensitive: false, requiredFirst: true }], 594 | ], 595 | }, 596 | { 597 | code: 'interface U {c:T; a:T; b?:T;}', 598 | optionsSet: [ 599 | [SortingOrder.Descending, { natural: true, caseSensitive: false, requiredFirst: true }], 600 | ], 601 | }, 602 | { 603 | code: 'interface U {b_:T; b:T; a?:T;}', 604 | optionsSet: [ 605 | [SortingOrder.Descending, { natural: true, caseSensitive: false, requiredFirst: true }], 606 | ], 607 | }, 608 | { 609 | code: 'interface U {c:T; C:T; b_?:T;}', 610 | optionsSet: [ 611 | [SortingOrder.Descending, { natural: true, caseSensitive: false, requiredFirst: true }], 612 | ], 613 | }, 614 | { 615 | code: 'interface U {C:T; c:T; b_?:T;}', 616 | optionsSet: [ 617 | [SortingOrder.Descending, { natural: true, caseSensitive: false, requiredFirst: true }], 618 | ], 619 | }, 620 | { 621 | code: 'interface U {_:T; $:T; a?:T; A?:T;}', 622 | optionsSet: [ 623 | [SortingOrder.Descending, { natural: true, caseSensitive: false, requiredFirst: true }], 624 | ], 625 | }, 626 | { 627 | code: "interface U { A:T; '11':T; 1:T; 2?:T;}", 628 | optionsSet: [ 629 | [SortingOrder.Descending, { natural: true, caseSensitive: false, requiredFirst: true }], 630 | ], 631 | }, 632 | { 633 | code: "interface U {è:T; 'Z':T; À?:T; '#'?:T;}", 634 | optionsSet: [ 635 | [SortingOrder.Descending, { natural: true, caseSensitive: false, requiredFirst: true }], 636 | ], 637 | }, 638 | 639 | /** 640 | * desc, required 641 | */ 642 | { 643 | code: 'interface U {b:T; _:T; a?:T;}', 644 | optionsSet: [[SortingOrder.Descending, { requiredFirst: true }]], 645 | }, 646 | { 647 | code: 'interface U {c:T; a:T; b?:T;}', 648 | optionsSet: [[SortingOrder.Descending, { requiredFirst: true }]], 649 | }, 650 | { 651 | code: 'interface U {b_:T; b:T; a?:T;}', 652 | optionsSet: [[SortingOrder.Descending, { requiredFirst: true }]], 653 | }, 654 | { 655 | code: 'interface U {c:T; C:T; b_?:T;}', 656 | optionsSet: [[SortingOrder.Descending, { requiredFirst: true }]], 657 | }, 658 | { 659 | code: 'interface U {9:T; 11:T; 1:T; 111?:T;}', 660 | optionsSet: [[SortingOrder.Descending, { requiredFirst: true }]], 661 | }, 662 | { 663 | code: 'interface U {_:T; $:T; a?:T; A?:T;}', 664 | optionsSet: [[SortingOrder.Descending, { requiredFirst: true }]], 665 | }, 666 | { 667 | code: "interface U {'11':T; 10:T; 2?:T; 12?:T; 1?:T;}", 668 | optionsSet: [[SortingOrder.Descending, { requiredFirst: true }]], 669 | }, 670 | { 671 | code: "interface U {è:T; À:T; 'Z':T; '#'?:T;}", 672 | optionsSet: [[SortingOrder.Descending, { requiredFirst: true }]], 673 | }, 674 | 675 | /** 676 | * desc, natural, insensitive, not-required 677 | */ 678 | { 679 | code: 'interface U {b:T; a?:T; _:T;}', 680 | optionsSet: [ 681 | [SortingOrder.Descending, { natural: true, caseSensitive: false, requiredFirst: false }], 682 | ], 683 | }, 684 | { 685 | code: 'interface U {c:T; b?:T; a:T;}', 686 | optionsSet: [ 687 | [SortingOrder.Descending, { natural: true, caseSensitive: false, requiredFirst: false }], 688 | ], 689 | }, 690 | { 691 | code: 'interface U {b_:T; b:T; a?:T;}', 692 | optionsSet: [ 693 | [SortingOrder.Descending, { natural: true, caseSensitive: false, requiredFirst: false }], 694 | ], 695 | }, 696 | { 697 | code: 'interface U {c:T; C:T; b_?:T;}', 698 | optionsSet: [ 699 | [SortingOrder.Descending, { natural: true, caseSensitive: false, requiredFirst: false }], 700 | ], 701 | }, 702 | { 703 | code: 'interface U {C:T; c:T; b_?:T;}', 704 | optionsSet: [ 705 | [SortingOrder.Descending, { natural: true, caseSensitive: false, requiredFirst: false }], 706 | ], 707 | }, 708 | { 709 | code: 'interface U {a?:T; A?:T; _:T; $:T;}', 710 | optionsSet: [ 711 | [SortingOrder.Descending, { natural: true, caseSensitive: false, requiredFirst: false }], 712 | ], 713 | }, 714 | { 715 | code: "interface U {A:T; '11':T; 2?:T; 1:T;}", 716 | optionsSet: [ 717 | [SortingOrder.Descending, { natural: true, caseSensitive: false, requiredFirst: false }], 718 | ], 719 | }, 720 | { 721 | code: "interface U {è:T; À:T; 'Z':T; '#'?:T;}", 722 | optionsSet: [ 723 | [SortingOrder.Descending, { natural: true, caseSensitive: false, requiredFirst: false }], 724 | ], 725 | }, 726 | 727 | /** 728 | * index signatures 729 | */ 730 | { 731 | code: `interface U { [nkey: number]: T; [skey: string]: T; $: T; A: T; _: T; a: T; }`, 732 | optionsSet: [[SortingOrder.Ascending]], 733 | }, 734 | { 735 | code: `interface U { a: T; _: T; A: T; $: T; [skey: string]: T; [nkey: number]: T; }`, 736 | optionsSet: [[SortingOrder.Descending]], 737 | }, 738 | ] 739 | 740 | const invalid: readonly InvalidTestCase[] = [ 741 | /** 742 | * default (asc) 743 | */ 744 | { 745 | code: 'interface U {a:T; _:T; b:T;}', 746 | output: 'interface U {_:T; a:T; b:T;}', 747 | errors: ["Expected interface keys to be in ascending order. '_' should be before 'a'."], 748 | optionsSet: [ 749 | [], 750 | [SortingOrder.Ascending], 751 | [SortingOrder.Ascending, { caseSensitive: true }], 752 | [SortingOrder.Ascending, { natural: false }], 753 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 754 | [SortingOrder.Ascending, { caseSensitive: true, natural: false, requiredFirst: false }], 755 | ], 756 | }, 757 | { 758 | code: 'interface U {a:T; c:T; b:T;}', 759 | output: 'interface U {a:T; b:T; c:T;}', 760 | errors: ["Expected interface keys to be in ascending order. 'b' should be before 'c'."], 761 | optionsSet: [ 762 | [], 763 | [SortingOrder.Ascending], 764 | [SortingOrder.Ascending, { caseSensitive: true }], 765 | [SortingOrder.Ascending, { natural: false }], 766 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 767 | [SortingOrder.Ascending, { caseSensitive: true, natural: false, requiredFirst: false }], 768 | ], 769 | }, 770 | { 771 | code: 'interface U {b_:T; a:T; b:T;}', 772 | output: 'interface U {a:T; b_:T; b:T;}', 773 | errors: ["Expected interface keys to be in ascending order. 'a' should be before 'b_'."], 774 | optionsSet: [ 775 | [], 776 | [SortingOrder.Ascending], 777 | [SortingOrder.Ascending, { caseSensitive: true }], 778 | [SortingOrder.Ascending, { natural: false }], 779 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 780 | [SortingOrder.Ascending, { caseSensitive: true, natural: false, requiredFirst: false }], 781 | ], 782 | }, 783 | { 784 | code: 'interface U {b_:T; c:T; C:T;}', 785 | output: 'interface U {C:T; c:T; b_:T;}', 786 | errors: ["Expected interface keys to be in ascending order. 'C' should be before 'c'."], 787 | optionsSet: [ 788 | [], 789 | [SortingOrder.Ascending], 790 | [SortingOrder.Ascending, { caseSensitive: true }], 791 | [SortingOrder.Ascending, { natural: false }], 792 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 793 | [SortingOrder.Ascending, { caseSensitive: true, natural: false, requiredFirst: false }], 794 | ], 795 | }, 796 | { 797 | code: 'interface U {$:T; _:T; A:T; a:T;}', 798 | output: 'interface U {$:T; A:T; _:T; a:T;}', 799 | errors: ["Expected interface keys to be in ascending order. 'A' should be before '_'."], 800 | optionsSet: [ 801 | [], 802 | [SortingOrder.Ascending], 803 | [SortingOrder.Ascending, { caseSensitive: true }], 804 | [SortingOrder.Ascending, { natural: false }], 805 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 806 | [SortingOrder.Ascending, { caseSensitive: true, natural: false, requiredFirst: false }], 807 | ], 808 | }, 809 | { 810 | code: "interface U {1:T; 2:T; A:T; '11':T;}", 811 | output: "interface U {1:T; '11':T; A:T; 2:T;}", 812 | errors: ["Expected interface keys to be in ascending order. '11' should be before 'A'."], 813 | optionsSet: [ 814 | [], 815 | [SortingOrder.Ascending], 816 | [SortingOrder.Ascending, { caseSensitive: true }], 817 | [SortingOrder.Ascending, { natural: false }], 818 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 819 | [SortingOrder.Ascending, { caseSensitive: true, natural: false, requiredFirst: false }], 820 | ], 821 | }, 822 | { 823 | code: "interface U {'#':T; À:T; 'Z':T; è:T;}", 824 | output: "interface U {'#':T; 'Z':T; À:T; è:T;}", 825 | errors: ["Expected interface keys to be in ascending order. 'Z' should be before 'À'."], 826 | optionsSet: [ 827 | [], 828 | [SortingOrder.Ascending], 829 | [SortingOrder.Ascending, { caseSensitive: true }], 830 | [SortingOrder.Ascending, { natural: false }], 831 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 832 | [SortingOrder.Ascending, { caseSensitive: true, natural: false, requiredFirst: false }], 833 | ], 834 | }, 835 | 836 | /** 837 | * methods 838 | */ 839 | { 840 | code: "interface U {1:T; 2:T; A():T; '11':T;}", 841 | output: "interface U {1:T; '11':T; A():T; 2:T;}", 842 | errors: ["Expected interface keys to be in ascending order. '11' should be before 'A'."], 843 | optionsSet: [ 844 | [], 845 | [SortingOrder.Ascending], 846 | [SortingOrder.Ascending, { caseSensitive: true }], 847 | [SortingOrder.Ascending, { natural: false }], 848 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 849 | [SortingOrder.Ascending, { caseSensitive: true, natural: false, requiredFirst: false }], 850 | ], 851 | }, 852 | { 853 | code: "interface U {'#'():T; À():T; 'Z':T; è:T;}", 854 | output: "interface U {'#'():T; 'Z':T; À():T; è:T;}", 855 | errors: ["Expected interface keys to be in ascending order. 'Z' should be before 'À'."], 856 | optionsSet: [ 857 | [], 858 | [SortingOrder.Ascending], 859 | [SortingOrder.Ascending, { caseSensitive: true }], 860 | [SortingOrder.Ascending, { natural: false }], 861 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 862 | [SortingOrder.Ascending, { caseSensitive: true, natural: false, requiredFirst: false }], 863 | ], 864 | }, 865 | 866 | /** 867 | * not ignore simple computed properties. 868 | */ 869 | { 870 | code: 'interface U {a:T; b:T; ["a"]:T; c:T;}', 871 | output: 'interface U {a:T; ["a"]:T; b:T; c:T;}', 872 | errors: ["Expected interface keys to be in ascending order. 'a' should be before 'b'."], 873 | optionsSet: [ 874 | [], 875 | [SortingOrder.Ascending], 876 | [SortingOrder.Ascending, { caseSensitive: true }], 877 | [SortingOrder.Ascending, { natural: false }], 878 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 879 | [SortingOrder.Ascending, { caseSensitive: true, natural: false, requiredFirst: false }], 880 | ], 881 | }, 882 | 883 | /** 884 | * nested 885 | */ 886 | { 887 | code: 'interface U {a:T; c:{y:T; x:T;}, b:T;}', 888 | output: 'interface U {a:T; b:T; c:{y:T; x:T;}}', 889 | errors: [ 890 | "Expected interface keys to be in ascending order. 'x' should be before 'y'.", 891 | "Expected interface keys to be in ascending order. 'b' should be before 'c'.", 892 | ], 893 | optionsSet: [ 894 | [], 895 | [SortingOrder.Ascending], 896 | [SortingOrder.Ascending, { caseSensitive: true }], 897 | [SortingOrder.Ascending, { natural: false }], 898 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 899 | [SortingOrder.Ascending, { caseSensitive: true, natural: false, requiredFirst: false }], 900 | ], 901 | }, 902 | { 903 | code: 'type U = {a:T; c:{y:T; x:T;}, b:T;}', 904 | output: 'type U = {a:T; b:T; c:{y:T; x:T;}}', 905 | errors: [ 906 | "Expected interface keys to be in ascending order. 'x' should be before 'y'.", 907 | "Expected interface keys to be in ascending order. 'b' should be before 'c'.", 908 | ], 909 | optionsSet: [ 910 | [], 911 | [SortingOrder.Ascending], 912 | [SortingOrder.Ascending, { caseSensitive: true }], 913 | [SortingOrder.Ascending, { natural: false }], 914 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 915 | [SortingOrder.Ascending, { caseSensitive: true, natural: false, requiredFirst: false }], 916 | ], 917 | }, 918 | 919 | /** 920 | * asc 921 | */ 922 | { 923 | code: 'interface U {a:T; _:T; b:T;}', 924 | output: 'interface U {_:T; a:T; b:T;}', 925 | errors: ["Expected interface keys to be in ascending order. '_' should be before 'a'."], 926 | optionsSet: [ 927 | [], 928 | [SortingOrder.Ascending], 929 | [SortingOrder.Ascending, { caseSensitive: true }], 930 | [SortingOrder.Ascending, { natural: false }], 931 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 932 | [SortingOrder.Ascending, { caseSensitive: true, natural: false, requiredFirst: false }], 933 | ], 934 | }, 935 | { 936 | code: 'interface U {a:T; c:T; b:T;}', 937 | output: 'interface U {a:T; b:T; c:T;}', 938 | errors: ["Expected interface keys to be in ascending order. 'b' should be before 'c'."], 939 | optionsSet: [ 940 | [], 941 | [SortingOrder.Ascending], 942 | [SortingOrder.Ascending, { caseSensitive: true }], 943 | [SortingOrder.Ascending, { natural: false }], 944 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 945 | [SortingOrder.Ascending, { caseSensitive: true, natural: false, requiredFirst: false }], 946 | ], 947 | }, 948 | { 949 | code: 'interface U {b_:T; a:T; b:T;}', 950 | output: 'interface U {a:T; b_:T; b:T;}', 951 | errors: ["Expected interface keys to be in ascending order. 'a' should be before 'b_'."], 952 | optionsSet: [ 953 | [], 954 | [SortingOrder.Ascending], 955 | [SortingOrder.Ascending, { caseSensitive: true }], 956 | [SortingOrder.Ascending, { natural: false }], 957 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 958 | [SortingOrder.Ascending, { caseSensitive: true, natural: false, requiredFirst: false }], 959 | ], 960 | }, 961 | { 962 | code: 'interface U {b_:T; c:T; C:T;}', 963 | output: 'interface U {C:T; c:T; b_:T;}', 964 | errors: ["Expected interface keys to be in ascending order. 'C' should be before 'c'."], 965 | optionsSet: [ 966 | [], 967 | [SortingOrder.Ascending], 968 | [SortingOrder.Ascending, { caseSensitive: true }], 969 | [SortingOrder.Ascending, { natural: false }], 970 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 971 | [SortingOrder.Ascending, { caseSensitive: true, natural: false, requiredFirst: false }], 972 | ], 973 | }, 974 | { 975 | code: 'interface U {$:T; _:T; A:T; a:T;}', 976 | output: 'interface U {$:T; A:T; _:T; a:T;}', 977 | errors: ["Expected interface keys to be in ascending order. 'A' should be before '_'."], 978 | optionsSet: [ 979 | [], 980 | [SortingOrder.Ascending], 981 | [SortingOrder.Ascending, { caseSensitive: true }], 982 | [SortingOrder.Ascending, { natural: false }], 983 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 984 | [SortingOrder.Ascending, { caseSensitive: true, natural: false, requiredFirst: false }], 985 | ], 986 | }, 987 | { 988 | code: "interface U {1:T; 2:T; A:T; '11':T;}", 989 | output: "interface U {1:T; '11':T; A:T; 2:T;}", 990 | errors: ["Expected interface keys to be in ascending order. '11' should be before 'A'."], 991 | optionsSet: [ 992 | [], 993 | [SortingOrder.Ascending], 994 | [SortingOrder.Ascending, { caseSensitive: true }], 995 | [SortingOrder.Ascending, { natural: false }], 996 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 997 | [SortingOrder.Ascending, { caseSensitive: true, natural: false, requiredFirst: false }], 998 | ], 999 | }, 1000 | { 1001 | code: "interface U {'#':T; À:T; 'Z':T; è:T;}", 1002 | output: "interface U {'#':T; 'Z':T; À:T; è:T;}", 1003 | errors: ["Expected interface keys to be in ascending order. 'Z' should be before 'À'."], 1004 | optionsSet: [ 1005 | [], 1006 | [SortingOrder.Ascending], 1007 | [SortingOrder.Ascending, { caseSensitive: true }], 1008 | [SortingOrder.Ascending, { natural: false }], 1009 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 1010 | ], 1011 | }, 1012 | 1013 | /** 1014 | * asc, insensitive 1015 | */ 1016 | { 1017 | code: 'interface U {a:T; _:T; b:T;}', 1018 | output: 'interface U {_:T; a:T; b:T;}', 1019 | errors: [ 1020 | "Expected interface keys to be in insensitive ascending order. '_' should be before 'a'.", 1021 | ], 1022 | optionsSet: [[SortingOrder.Ascending, { caseSensitive: false }]], 1023 | }, 1024 | { 1025 | code: 'interface U {a:T; c:T; b:T;}', 1026 | output: 'interface U {a:T; b:T; c:T;}', 1027 | errors: [ 1028 | "Expected interface keys to be in insensitive ascending order. 'b' should be before 'c'.", 1029 | ], 1030 | optionsSet: [[SortingOrder.Ascending, { caseSensitive: false }]], 1031 | }, 1032 | { 1033 | code: 'interface U {b_:T; a:T; b:T;}', 1034 | output: 'interface U {a:T; b_:T; b:T;}', 1035 | errors: [ 1036 | "Expected interface keys to be in insensitive ascending order. 'a' should be before 'b_'.", 1037 | ], 1038 | optionsSet: [[SortingOrder.Ascending, { caseSensitive: false }]], 1039 | }, 1040 | { 1041 | code: 'interface U {$:T; A:T; _:T; a:T;}', 1042 | output: 'interface U {$:T; _:T; A:T; a:T;}', 1043 | errors: [ 1044 | "Expected interface keys to be in insensitive ascending order. '_' should be before 'A'.", 1045 | ], 1046 | optionsSet: [[SortingOrder.Ascending, { caseSensitive: false }]], 1047 | }, 1048 | { 1049 | code: "interface U {1:T; 2:T; A:T; '11':T;}", 1050 | output: "interface U {1:T; '11':T; A:T; 2:T;}", 1051 | errors: [ 1052 | "Expected interface keys to be in insensitive ascending order. '11' should be before 'A'.", 1053 | ], 1054 | optionsSet: [[SortingOrder.Ascending, { caseSensitive: false }]], 1055 | }, 1056 | { 1057 | code: "interface U {'#':T; À:T; 'Z':T; è:T;}", 1058 | output: "interface U {'#':T; 'Z':T; À:T; è:T;}", 1059 | errors: [ 1060 | "Expected interface keys to be in insensitive ascending order. 'Z' should be before 'À'.", 1061 | ], 1062 | optionsSet: [[SortingOrder.Ascending, { caseSensitive: false }]], 1063 | }, 1064 | 1065 | /** 1066 | * asc, natural 1067 | */ 1068 | { 1069 | code: 'interface U {a:T; _:T; b:T;}', 1070 | output: 'interface U {_:T; a:T; b:T;}', 1071 | errors: ["Expected interface keys to be in natural ascending order. '_' should be before 'a'."], 1072 | optionsSet: [[SortingOrder.Ascending, { natural: true }]], 1073 | }, 1074 | { 1075 | code: 'interface U {a:T; c:T; b:T;}', 1076 | output: 'interface U {a:T; b:T; c:T;}', 1077 | errors: ["Expected interface keys to be in natural ascending order. 'b' should be before 'c'."], 1078 | optionsSet: [[SortingOrder.Ascending, { natural: true }]], 1079 | }, 1080 | { 1081 | code: 'interface U {b_:T; a:T; b:T;}', 1082 | output: 'interface U {a:T; b_:T; b:T;}', 1083 | errors: [ 1084 | "Expected interface keys to be in natural ascending order. 'a' should be before 'b_'.", 1085 | ], 1086 | optionsSet: [[SortingOrder.Ascending, { natural: true }]], 1087 | }, 1088 | { 1089 | code: 'interface U {b_:T; c:T; C:T;}', 1090 | output: 'interface U {C:T; c:T; b_:T;}', 1091 | errors: ["Expected interface keys to be in natural ascending order. 'C' should be before 'c'."], 1092 | optionsSet: [[SortingOrder.Ascending, { natural: true }]], 1093 | }, 1094 | { 1095 | code: 'interface U {$:T; A:T; _:T; a:T;}', 1096 | output: 'interface U {$:T; _:T; A:T; a:T;}', 1097 | errors: ["Expected interface keys to be in natural ascending order. '_' should be before 'A'."], 1098 | optionsSet: [[SortingOrder.Ascending, { natural: true }]], 1099 | }, 1100 | { 1101 | code: "interface U {1:T; 2:T; A:T; '11':T;}", 1102 | output: "interface U {1:T; 2:T; '11':T; A:T;}", 1103 | errors: [ 1104 | "Expected interface keys to be in natural ascending order. '11' should be before 'A'.", 1105 | ], 1106 | optionsSet: [[SortingOrder.Ascending, { natural: true }]], 1107 | }, 1108 | { 1109 | code: "interface U {'#':T; À:T; 'Z':T; è:T;}", 1110 | output: "interface U {'#':T; 'Z':T; À:T; è:T;}", 1111 | errors: ["Expected interface keys to be in natural ascending order. 'Z' should be before 'À'."], 1112 | optionsSet: [[SortingOrder.Ascending, { natural: true }]], 1113 | }, 1114 | 1115 | /** 1116 | * asc, natural, insensitive 1117 | */ 1118 | { 1119 | code: 'interface U {a:T; _:T; b:T;}', 1120 | output: 'interface U {_:T; a:T; b:T;}', 1121 | errors: [ 1122 | "Expected interface keys to be in natural insensitive ascending order. '_' should be before 'a'.", 1123 | ], 1124 | optionsSet: [[SortingOrder.Ascending, { natural: true, caseSensitive: false }]], 1125 | }, 1126 | { 1127 | code: 'interface U {a:T; c:T; b:T;}', 1128 | output: 'interface U {a:T; b:T; c:T;}', 1129 | errors: [ 1130 | "Expected interface keys to be in natural insensitive ascending order. 'b' should be before 'c'.", 1131 | ], 1132 | optionsSet: [[SortingOrder.Ascending, { natural: true, caseSensitive: false }]], 1133 | }, 1134 | { 1135 | code: 'interface U {b_:T; a:T; b:T;}', 1136 | output: 'interface U {a:T; b_:T; b:T;}', 1137 | errors: [ 1138 | "Expected interface keys to be in natural insensitive ascending order. 'a' should be before 'b_'.", 1139 | ], 1140 | optionsSet: [[SortingOrder.Ascending, { natural: true, caseSensitive: false }]], 1141 | }, 1142 | { 1143 | code: 'interface U {$:T; A:T; _:T; a:T;}', 1144 | output: 'interface U {$:T; _:T; A:T; a:T;}', 1145 | errors: [ 1146 | "Expected interface keys to be in natural insensitive ascending order. '_' should be before 'A'.", 1147 | ], 1148 | optionsSet: [[SortingOrder.Ascending, { natural: true, caseSensitive: false }]], 1149 | }, 1150 | { 1151 | code: "interface U {1:T; '11':T; 2:T; A:T;}", 1152 | output: "interface U {1:T; 2:T; '11':T; A:T;}", 1153 | errors: [ 1154 | "Expected interface keys to be in natural insensitive ascending order. '2' should be before '11'.", 1155 | ], 1156 | optionsSet: [[SortingOrder.Ascending, { natural: true, caseSensitive: false }]], 1157 | }, 1158 | { 1159 | code: "interface U {'#':T; À:T; 'Z':T; è:T;}", 1160 | output: "interface U {'#':T; 'Z':T; À:T; è:T;}", 1161 | errors: [ 1162 | "Expected interface keys to be in natural insensitive ascending order. 'Z' should be before 'À'.", 1163 | ], 1164 | optionsSet: [[SortingOrder.Ascending, { natural: true, caseSensitive: false }]], 1165 | }, 1166 | 1167 | /** 1168 | * asc, natural, insensitive, required 1169 | */ 1170 | { 1171 | code: 'interface U {_:T; a?:T; b:T;}', 1172 | output: 'interface U {_:T; b:T; a?:T;}', 1173 | errors: [ 1174 | "Expected interface keys to be in required first natural insensitive ascending order. 'b' should be before 'a'.", 1175 | ], 1176 | optionsSet: [ 1177 | [SortingOrder.Ascending, { natural: true, caseSensitive: false, requiredFirst: true }], 1178 | ], 1179 | }, 1180 | { 1181 | code: 'interface U {a:T; b?:T; c:T;}', 1182 | output: 'interface U {a:T; c:T; b?:T;}', 1183 | errors: [ 1184 | "Expected interface keys to be in required first natural insensitive ascending order. 'c' should be before 'b'.", 1185 | ], 1186 | optionsSet: [ 1187 | [SortingOrder.Ascending, { natural: true, caseSensitive: false, requiredFirst: true }], 1188 | ], 1189 | }, 1190 | { 1191 | code: 'interface U {b:T; a?:T; b_:T;}', 1192 | output: 'interface U {b:T; b_:T; a?:T;}', 1193 | errors: [ 1194 | "Expected interface keys to be in required first natural insensitive ascending order. 'b_' should be before 'a'.", 1195 | ], 1196 | optionsSet: [ 1197 | [SortingOrder.Ascending, { natural: true, caseSensitive: false, requiredFirst: true }], 1198 | ], 1199 | }, 1200 | { 1201 | code: 'interface U {C:T; b_?:T; c:T;}', 1202 | output: 'interface U {C:T; c:T; b_?:T;}', 1203 | errors: [ 1204 | "Expected interface keys to be in required first natural insensitive ascending order. 'c' should be before 'b_'.", 1205 | ], 1206 | optionsSet: [ 1207 | [SortingOrder.Ascending, { natural: true, caseSensitive: false, requiredFirst: true }], 1208 | ], 1209 | }, 1210 | { 1211 | code: 'interface U {C:T; b_?:T; c:T;}', 1212 | output: 'interface U {C:T; c:T; b_?:T;}', 1213 | errors: [ 1214 | "Expected interface keys to be in required first natural insensitive ascending order. 'c' should be before 'b_'.", 1215 | ], 1216 | optionsSet: [ 1217 | [SortingOrder.Ascending, { natural: true, caseSensitive: false, requiredFirst: true }], 1218 | ], 1219 | }, 1220 | { 1221 | code: 'interface U {$:T; A?:T; _:T; a?:T;}', 1222 | output: 'interface U {$:T; _:T; A?:T; a?:T;}', 1223 | errors: [ 1224 | "Expected interface keys to be in required first natural insensitive ascending order. '_' should be before 'A'.", 1225 | ], 1226 | optionsSet: [ 1227 | [SortingOrder.Ascending, { natural: true, caseSensitive: false, requiredFirst: true }], 1228 | ], 1229 | }, 1230 | { 1231 | code: "interface U {1:T; '11':T; 2?:T; A:T;}", 1232 | output: "interface U {1:T; '11':T; A:T; 2?:T;}", 1233 | errors: [ 1234 | "Expected interface keys to be in required first natural insensitive ascending order. 'A' should be before '2'.", 1235 | ], 1236 | optionsSet: [ 1237 | [SortingOrder.Ascending, { natural: true, caseSensitive: false, requiredFirst: true }], 1238 | ], 1239 | }, 1240 | { 1241 | code: "interface U {'Z':T; À:T; '#'?:T; è:T;}", 1242 | output: "interface U {'Z':T; À:T; è:T; '#'?:T;}", 1243 | errors: [ 1244 | "Expected interface keys to be in required first natural insensitive ascending order. 'è' should be before '#'.", 1245 | ], 1246 | optionsSet: [ 1247 | [SortingOrder.Ascending, { natural: true, caseSensitive: false, requiredFirst: true }], 1248 | ], 1249 | }, 1250 | 1251 | /** 1252 | * asc, natural, insensitive, not-required 1253 | */ 1254 | { 1255 | code: 'interface U {_:T; b:T; a?:T;}', 1256 | output: 'interface U {_:T; a?:T; b:T;}', 1257 | errors: [ 1258 | "Expected interface keys to be in natural insensitive ascending order. 'a' should be before 'b'.", 1259 | ], 1260 | optionsSet: [ 1261 | [SortingOrder.Ascending, { natural: true, caseSensitive: false, requiredFirst: false }], 1262 | ], 1263 | }, 1264 | { 1265 | code: 'interface U {b?:T; a:T; c:T;}', 1266 | output: 'interface U {a:T; b?:T; c:T;}', 1267 | errors: [ 1268 | "Expected interface keys to be in natural insensitive ascending order. 'a' should be before 'b'.", 1269 | ], 1270 | optionsSet: [ 1271 | [SortingOrder.Ascending, { natural: true, caseSensitive: false, requiredFirst: false }], 1272 | ], 1273 | }, 1274 | { 1275 | code: 'interface U {b:T; a?:T; b_:T;}', 1276 | output: 'interface U {a?:T; b:T; b_:T;}', 1277 | errors: [ 1278 | "Expected interface keys to be in natural insensitive ascending order. 'a' should be before 'b'.", 1279 | ], 1280 | optionsSet: [ 1281 | [SortingOrder.Ascending, { natural: true, caseSensitive: false, requiredFirst: false }], 1282 | ], 1283 | }, 1284 | { 1285 | code: 'interface U {C:T; c:T; b_?:T;}', 1286 | output: 'interface U {b_?:T; c:T; C:T;}', 1287 | errors: [ 1288 | "Expected interface keys to be in natural insensitive ascending order. 'b_' should be before 'c'.", 1289 | ], 1290 | optionsSet: [ 1291 | [SortingOrder.Ascending, { natural: true, caseSensitive: false, requiredFirst: false }], 1292 | ], 1293 | }, 1294 | { 1295 | code: 'interface U {C:T; b_?:T; c:T;}', 1296 | output: 'interface U {b_?:T; C:T; c:T;}', 1297 | errors: [ 1298 | "Expected interface keys to be in natural insensitive ascending order. 'b_' should be before 'C'.", 1299 | ], 1300 | optionsSet: [ 1301 | [SortingOrder.Ascending, { natural: true, caseSensitive: false, requiredFirst: false }], 1302 | ], 1303 | }, 1304 | { 1305 | code: 'interface U {$:T; A?:T; _:T; a?:T;}', 1306 | output: 'interface U {$:T; _:T; A?:T; a?:T;}', 1307 | errors: [ 1308 | "Expected interface keys to be in natural insensitive ascending order. '_' should be before 'A'.", 1309 | ], 1310 | optionsSet: [ 1311 | [SortingOrder.Ascending, { natural: true, caseSensitive: false, requiredFirst: false }], 1312 | ], 1313 | }, 1314 | { 1315 | code: "interface U {1:T; '11':T; 2?:T; A:T;}", 1316 | output: "interface U {1:T; 2?:T; '11':T; A:T;}", 1317 | errors: [ 1318 | "Expected interface keys to be in natural insensitive ascending order. '2' should be before '11'.", 1319 | ], 1320 | optionsSet: [ 1321 | [SortingOrder.Ascending, { natural: true, caseSensitive: false, requiredFirst: false }], 1322 | ], 1323 | }, 1324 | { 1325 | code: "interface U {'Z':T; À:T; '#'?:T; è:T;}", 1326 | output: "interface U {'#'?:T; À:T; 'Z':T; è:T;}", 1327 | errors: [ 1328 | "Expected interface keys to be in natural insensitive ascending order. '#' should be before 'À'.", 1329 | ], 1330 | optionsSet: [ 1331 | [SortingOrder.Ascending, { natural: true, caseSensitive: false, requiredFirst: false }], 1332 | ], 1333 | }, 1334 | 1335 | /** 1336 | * desc 1337 | */ 1338 | { 1339 | code: 'interface U {a:T; _:T; b:T;}', 1340 | output: 'interface U {b:T; _:T; a:T;}', 1341 | errors: ["Expected interface keys to be in descending order. 'b' should be before '_'."], 1342 | optionsSet: [[SortingOrder.Descending]], 1343 | }, 1344 | { 1345 | code: 'interface U {a:T; c:T; b:T;}', 1346 | output: 'interface U {c:T; a:T; b:T;}', 1347 | errors: ["Expected interface keys to be in descending order. 'c' should be before 'a'."], 1348 | optionsSet: [[SortingOrder.Descending]], 1349 | }, 1350 | { 1351 | code: 'interface U {b_:T; a:T; b:T;}', 1352 | output: 'interface U {b_:T; b:T; a:T;}', 1353 | errors: ["Expected interface keys to be in descending order. 'b' should be before 'a'."], 1354 | optionsSet: [[SortingOrder.Descending]], 1355 | }, 1356 | { 1357 | code: 'interface U {b_:T; c:T; C:T;}', 1358 | output: 'interface U {c:T; b_:T; C:T;}', 1359 | errors: ["Expected interface keys to be in descending order. 'c' should be before 'b_'."], 1360 | optionsSet: [[SortingOrder.Descending]], 1361 | }, 1362 | { 1363 | code: 'interface U {$:T; _:T; A:T; a:T;}', 1364 | output: 'interface U {a:T; _:T; A:T; $:T;}', 1365 | errors: [ 1366 | "Expected interface keys to be in descending order. '_' should be before '$'.", 1367 | "Expected interface keys to be in descending order. 'a' should be before 'A'.", 1368 | ], 1369 | optionsSet: [[SortingOrder.Descending]], 1370 | }, 1371 | { 1372 | code: "interface U {1:T; 2:T; A:T; '11':T;}", 1373 | output: "interface U {A:T; 2:T; 1:T; '11':T;}", 1374 | errors: [ 1375 | "Expected interface keys to be in descending order. '2' should be before '1'.", 1376 | "Expected interface keys to be in descending order. 'A' should be before '2'.", 1377 | ], 1378 | optionsSet: [[SortingOrder.Descending]], 1379 | }, 1380 | { 1381 | code: "interface U {'#':T; À:T; 'Z':T; è:T;}", 1382 | output: "interface U {è:T; À:T; 'Z':T; '#':T;}", 1383 | errors: [ 1384 | "Expected interface keys to be in descending order. 'À' should be before '#'.", 1385 | "Expected interface keys to be in descending order. 'è' should be before 'Z'.", 1386 | ], 1387 | optionsSet: [[SortingOrder.Descending]], 1388 | }, 1389 | 1390 | /** 1391 | * desc, insensitive 1392 | */ 1393 | { 1394 | code: 'interface U {a:T; _:T; b:T;}', 1395 | output: 'interface U {b:T; _:T; a:T;}', 1396 | errors: [ 1397 | "Expected interface keys to be in insensitive descending order. 'b' should be before '_'.", 1398 | ], 1399 | optionsSet: [[SortingOrder.Descending, { caseSensitive: false }]], 1400 | }, 1401 | { 1402 | code: 'interface U {a:T; c:T; b:T;}', 1403 | output: 'interface U {c:T; a:T; b:T;}', 1404 | errors: [ 1405 | "Expected interface keys to be in insensitive descending order. 'c' should be before 'a'.", 1406 | ], 1407 | optionsSet: [[SortingOrder.Descending, { caseSensitive: false }]], 1408 | }, 1409 | { 1410 | code: 'interface U {b_:T; a:T; b:T;}', 1411 | output: 'interface U {b_:T; b:T; a:T;}', 1412 | errors: [ 1413 | "Expected interface keys to be in insensitive descending order. 'b' should be before 'a'.", 1414 | ], 1415 | optionsSet: [[SortingOrder.Descending, { caseSensitive: false }]], 1416 | }, 1417 | { 1418 | code: 'interface U {b_:T; c:T; C:T;}', 1419 | output: 'interface U {c:T; b_:T; C:T;}', 1420 | errors: [ 1421 | "Expected interface keys to be in insensitive descending order. 'c' should be before 'b_'.", 1422 | ], 1423 | optionsSet: [[SortingOrder.Descending, { caseSensitive: false }]], 1424 | }, 1425 | { 1426 | code: 'interface U {$:T; _:T; A:T; a:T;}', 1427 | output: 'interface U {A:T; _:T; $:T; a:T;}', 1428 | errors: [ 1429 | "Expected interface keys to be in insensitive descending order. '_' should be before '$'.", 1430 | "Expected interface keys to be in insensitive descending order. 'A' should be before '_'.", 1431 | ], 1432 | optionsSet: [[SortingOrder.Descending, { caseSensitive: false }]], 1433 | }, 1434 | { 1435 | code: "interface U {1:T; 2:T; A:T; '11':T;}", 1436 | output: "interface U {A:T; 2:T; 1:T; '11':T;}", 1437 | errors: [ 1438 | "Expected interface keys to be in insensitive descending order. '2' should be before '1'.", 1439 | "Expected interface keys to be in insensitive descending order. 'A' should be before '2'.", 1440 | ], 1441 | optionsSet: [[SortingOrder.Descending, { caseSensitive: false }]], 1442 | }, 1443 | { 1444 | code: "interface U {'#':T; À:T; 'Z':T; è:T;}", 1445 | output: "interface U {è:T; À:T; 'Z':T; '#':T;}", 1446 | errors: [ 1447 | "Expected interface keys to be in insensitive descending order. 'À' should be before '#'.", 1448 | "Expected interface keys to be in insensitive descending order. 'è' should be before 'Z'.", 1449 | ], 1450 | optionsSet: [[SortingOrder.Descending, { caseSensitive: false }]], 1451 | }, 1452 | 1453 | /** 1454 | * desc, natural 1455 | */ 1456 | { 1457 | code: 'interface U {a:T; _:T; b:T;}', 1458 | output: 'interface U {b:T; _:T; a:T;}', 1459 | errors: [ 1460 | "Expected interface keys to be in natural descending order. 'b' should be before '_'.", 1461 | ], 1462 | optionsSet: [[SortingOrder.Descending, { natural: true }]], 1463 | }, 1464 | { 1465 | code: 'interface U {a:T; c:T; b:T;}', 1466 | output: 'interface U {c:T; a:T; b:T;}', 1467 | errors: [ 1468 | "Expected interface keys to be in natural descending order. 'c' should be before 'a'.", 1469 | ], 1470 | optionsSet: [[SortingOrder.Descending, { natural: true }]], 1471 | }, 1472 | { 1473 | code: 'interface U {b_:T; a:T; b:T;}', 1474 | output: 'interface U {b_:T; b:T; a:T;}', 1475 | errors: [ 1476 | "Expected interface keys to be in natural descending order. 'b' should be before 'a'.", 1477 | ], 1478 | optionsSet: [[SortingOrder.Descending, { natural: true }]], 1479 | }, 1480 | { 1481 | code: 'interface U {b_:T; c:T; C:T;}', 1482 | output: 'interface U {c:T; b_:T; C:T;}', 1483 | errors: [ 1484 | "Expected interface keys to be in natural descending order. 'c' should be before 'b_'.", 1485 | ], 1486 | optionsSet: [[SortingOrder.Descending, { natural: true }]], 1487 | }, 1488 | { 1489 | code: 'interface U {$:T; _:T; A:T; a:T;}', 1490 | output: 'interface U {a:T; _:T; A:T; $:T;}', 1491 | errors: [ 1492 | "Expected interface keys to be in natural descending order. '_' should be before '$'.", 1493 | "Expected interface keys to be in natural descending order. 'A' should be before '_'.", 1494 | "Expected interface keys to be in natural descending order. 'a' should be before 'A'.", 1495 | ], 1496 | optionsSet: [[SortingOrder.Descending, { natural: true }]], 1497 | }, 1498 | { 1499 | code: "interface U {1:T; 2:T; A:T; '11':T;}", 1500 | output: "interface U {A:T; 2:T; 1:T; '11':T;}", 1501 | errors: [ 1502 | "Expected interface keys to be in natural descending order. '2' should be before '1'.", 1503 | "Expected interface keys to be in natural descending order. 'A' should be before '2'.", 1504 | ], 1505 | optionsSet: [[SortingOrder.Descending, { natural: true }]], 1506 | }, 1507 | { 1508 | code: "interface U {'#':T; À:T; 'Z':T; è:T;}", 1509 | output: "interface U {è:T; À:T; 'Z':T; '#':T;}", 1510 | errors: [ 1511 | "Expected interface keys to be in natural descending order. 'À' should be before '#'.", 1512 | "Expected interface keys to be in natural descending order. 'è' should be before 'Z'.", 1513 | ], 1514 | optionsSet: [[SortingOrder.Descending, { natural: true }]], 1515 | }, 1516 | 1517 | /** 1518 | * desc, natural, insensitive 1519 | */ 1520 | { 1521 | code: 'interface U {a:T; _:T; b:T;}', 1522 | output: 'interface U {b:T; _:T; a:T;}', 1523 | errors: [ 1524 | "Expected interface keys to be in natural insensitive descending order. 'b' should be before '_'.", 1525 | ], 1526 | optionsSet: [[SortingOrder.Descending, { natural: true, caseSensitive: false }]], 1527 | }, 1528 | { 1529 | code: 'interface U {a:T; c:T; b:T;}', 1530 | output: 'interface U {c:T; a:T; b:T;}', 1531 | errors: [ 1532 | "Expected interface keys to be in natural insensitive descending order. 'c' should be before 'a'.", 1533 | ], 1534 | optionsSet: [[SortingOrder.Descending, { natural: true, caseSensitive: false }]], 1535 | }, 1536 | { 1537 | code: 'interface U {b_:T; a:T; b:T;}', 1538 | output: 'interface U {b_:T; b:T; a:T;}', 1539 | errors: [ 1540 | "Expected interface keys to be in natural insensitive descending order. 'b' should be before 'a'.", 1541 | ], 1542 | optionsSet: [[SortingOrder.Descending, { natural: true, caseSensitive: false }]], 1543 | }, 1544 | { 1545 | code: 'interface U {b_:T; c:T; C:T;}', 1546 | output: 'interface U {c:T; b_:T; C:T;}', 1547 | errors: [ 1548 | "Expected interface keys to be in natural insensitive descending order. 'c' should be before 'b_'.", 1549 | ], 1550 | optionsSet: [[SortingOrder.Descending, { natural: true, caseSensitive: false }]], 1551 | }, 1552 | { 1553 | code: 'interface U {$:T; _:T; A:T; a:T;}', 1554 | output: 'interface U {A:T; _:T; $:T; a:T;}', 1555 | errors: [ 1556 | "Expected interface keys to be in natural insensitive descending order. '_' should be before '$'.", 1557 | "Expected interface keys to be in natural insensitive descending order. 'A' should be before '_'.", 1558 | ], 1559 | optionsSet: [[SortingOrder.Descending, { natural: true, caseSensitive: false }]], 1560 | }, 1561 | { 1562 | code: "interface U {1:T; 2:T; '11':T; A:T;}", 1563 | output: "interface U {A:T; 2:T; '11':T; 1:T;}", 1564 | errors: [ 1565 | "Expected interface keys to be in natural insensitive descending order. '2' should be before '1'.", 1566 | "Expected interface keys to be in natural insensitive descending order. '11' should be before '2'.", 1567 | "Expected interface keys to be in natural insensitive descending order. 'A' should be before '11'.", 1568 | ], 1569 | optionsSet: [[SortingOrder.Descending, { natural: true, caseSensitive: false }]], 1570 | }, 1571 | { 1572 | code: "interface U {'#':T; À:T; 'Z':T; è:T;}", 1573 | output: "interface U {è:T; À:T; 'Z':T; '#':T;}", 1574 | errors: [ 1575 | "Expected interface keys to be in natural insensitive descending order. 'À' should be before '#'.", 1576 | "Expected interface keys to be in natural insensitive descending order. 'è' should be before 'Z'.", 1577 | ], 1578 | optionsSet: [[SortingOrder.Descending, { natural: true, caseSensitive: false }]], 1579 | }, 1580 | 1581 | /** 1582 | * desc, natural, insensitive, required 1583 | */ 1584 | { 1585 | code: 'interface U {_:T; a?:T; b:T;}', 1586 | output: 'interface U {b:T; a?:T; _:T;}', 1587 | errors: [ 1588 | "Expected interface keys to be in required first natural insensitive descending order. 'b' should be before 'a'.", 1589 | ], 1590 | optionsSet: [ 1591 | [SortingOrder.Descending, { natural: true, caseSensitive: false, requiredFirst: true }], 1592 | ], 1593 | }, 1594 | { 1595 | code: 'interface U {b:T; a?:T; _:T;}', 1596 | output: 'interface U {b:T; _:T; a?:T;}', 1597 | errors: [ 1598 | "Expected interface keys to be in required first natural insensitive descending order. '_' should be before 'a'.", 1599 | ], 1600 | optionsSet: [ 1601 | [SortingOrder.Descending, { natural: true, caseSensitive: false, requiredFirst: true }], 1602 | ], 1603 | }, 1604 | { 1605 | code: 'interface U {b:T; b_:T; a?:T;}', 1606 | output: 'interface U {b_:T; b:T; a?:T;}', 1607 | errors: [ 1608 | "Expected interface keys to be in required first natural insensitive descending order. 'b_' should be before 'b'.", 1609 | ], 1610 | optionsSet: [ 1611 | [SortingOrder.Descending, { natural: true, caseSensitive: false, requiredFirst: true }], 1612 | ], 1613 | }, 1614 | { 1615 | code: 'interface U {c:T; b_?:T; C:T;}', 1616 | output: 'interface U {c:T; C:T; b_?:T;}', 1617 | errors: [ 1618 | "Expected interface keys to be in required first natural insensitive descending order. 'C' should be before 'b_'.", 1619 | ], 1620 | optionsSet: [ 1621 | [SortingOrder.Descending, { natural: true, caseSensitive: false, requiredFirst: true }], 1622 | ], 1623 | }, 1624 | { 1625 | code: 'interface U {b_?:T; C:T; c:T;}', 1626 | output: 'interface U {C:T; b_?:T; c:T;}', 1627 | errors: [ 1628 | "Expected interface keys to be in required first natural insensitive descending order. 'C' should be before 'b_'.", 1629 | ], 1630 | optionsSet: [ 1631 | [SortingOrder.Descending, { natural: true, caseSensitive: false, requiredFirst: true }], 1632 | ], 1633 | }, 1634 | { 1635 | code: 'interface U {_:T; a?:T; $:T; A?:T;}', 1636 | output: 'interface U {_:T; $:T; a?:T; A?:T;}', 1637 | errors: [ 1638 | "Expected interface keys to be in required first natural insensitive descending order. '$' should be before 'a'.", 1639 | ], 1640 | optionsSet: [ 1641 | [SortingOrder.Descending, { natural: true, caseSensitive: false, requiredFirst: true }], 1642 | ], 1643 | }, 1644 | { 1645 | code: "interface U {2?:T; A:T; 1:T; '11':T;}", 1646 | output: "interface U {A:T; 2?:T; 1:T; '11':T;}", 1647 | errors: [ 1648 | "Expected interface keys to be in required first natural insensitive descending order. 'A' should be before '2'.", 1649 | "Expected interface keys to be in required first natural insensitive descending order. '11' should be before '1'.", 1650 | ], 1651 | optionsSet: [ 1652 | [SortingOrder.Descending, { natural: true, caseSensitive: false, requiredFirst: true }], 1653 | ], 1654 | }, 1655 | { 1656 | code: "interface U {è:T; 'Z':T; '#'?:T; À?:T;}", 1657 | output: "interface U {è:T; 'Z':T; À?:T; '#'?:T;}", 1658 | errors: [ 1659 | "Expected interface keys to be in required first natural insensitive descending order. 'À' should be before '#'.", 1660 | ], 1661 | optionsSet: [ 1662 | [SortingOrder.Descending, { natural: true, caseSensitive: false, requiredFirst: true }], 1663 | ], 1664 | }, 1665 | { 1666 | code: "interface U {À?:T; 'Z':T; '#'?:T; è:T;}", 1667 | output: "interface U {è:T; 'Z':T; '#'?:T; À?:T;}", 1668 | errors: [ 1669 | "Expected interface keys to be in required first natural insensitive descending order. 'Z' should be before 'À'.", 1670 | "Expected interface keys to be in required first natural insensitive descending order. 'è' should be before '#'.", 1671 | ], 1672 | optionsSet: [ 1673 | [SortingOrder.Descending, { natural: true, caseSensitive: false, requiredFirst: true }], 1674 | ], 1675 | }, 1676 | 1677 | /** 1678 | * desc, natural, insensitive, not-required 1679 | */ 1680 | { 1681 | code: 'interface U {_:T; a?:T; b:T;}', 1682 | output: 'interface U {b:T; a?:T; _:T;}', 1683 | errors: [ 1684 | "Expected interface keys to be in natural insensitive descending order. 'a' should be before '_'.", 1685 | "Expected interface keys to be in natural insensitive descending order. 'b' should be before 'a'.", 1686 | ], 1687 | optionsSet: [ 1688 | [SortingOrder.Descending, { natural: true, caseSensitive: false, requiredFirst: false }], 1689 | ], 1690 | }, 1691 | { 1692 | code: 'interface U {a?:T; b:T; _:T;}', 1693 | output: 'interface U {b:T; a?:T; _:T;}', 1694 | errors: [ 1695 | "Expected interface keys to be in natural insensitive descending order. 'b' should be before 'a'.", 1696 | ], 1697 | optionsSet: [ 1698 | [SortingOrder.Descending, { natural: true, caseSensitive: false, requiredFirst: false }], 1699 | ], 1700 | }, 1701 | { 1702 | code: 'interface U {b:T; b_:T; a?:T;}', 1703 | output: 'interface U {b_:T; b:T; a?:T;}', 1704 | errors: [ 1705 | "Expected interface keys to be in natural insensitive descending order. 'b_' should be before 'b'.", 1706 | ], 1707 | optionsSet: [ 1708 | [SortingOrder.Descending, { natural: true, caseSensitive: false, requiredFirst: false }], 1709 | ], 1710 | }, 1711 | { 1712 | code: 'interface U {c:T; b_?:T; C:T;}', 1713 | output: 'interface U {c:T; C:T; b_?:T;}', 1714 | errors: [ 1715 | "Expected interface keys to be in natural insensitive descending order. 'C' should be before 'b_'.", 1716 | ], 1717 | optionsSet: [ 1718 | [SortingOrder.Descending, { natural: true, caseSensitive: false, requiredFirst: false }], 1719 | ], 1720 | }, 1721 | { 1722 | code: 'interface U {b_?:T; C:T; c:T;}', 1723 | output: 'interface U {C:T; b_?:T; c:T;}', 1724 | errors: [ 1725 | "Expected interface keys to be in natural insensitive descending order. 'C' should be before 'b_'.", 1726 | ], 1727 | optionsSet: [ 1728 | [SortingOrder.Descending, { natural: true, caseSensitive: false, requiredFirst: false }], 1729 | ], 1730 | }, 1731 | { 1732 | code: 'interface U {_:T; a?:T; $:T; A?:T;}', 1733 | output: 'interface U {a?:T; _:T; $:T; A?:T;}', 1734 | errors: [ 1735 | "Expected interface keys to be in natural insensitive descending order. 'a' should be before '_'.", 1736 | "Expected interface keys to be in natural insensitive descending order. 'A' should be before '$'.", 1737 | ], 1738 | optionsSet: [ 1739 | [SortingOrder.Descending, { natural: true, caseSensitive: false, requiredFirst: false }], 1740 | ], 1741 | }, 1742 | { 1743 | code: "interface U {2?:T; A:T; 1:T; '11':T;}", 1744 | output: "interface U {A:T; 2?:T; 1:T; '11':T;}", 1745 | errors: [ 1746 | "Expected interface keys to be in natural insensitive descending order. 'A' should be before '2'.", 1747 | "Expected interface keys to be in natural insensitive descending order. '11' should be before '1'.", 1748 | ], 1749 | optionsSet: [ 1750 | [SortingOrder.Descending, { natural: true, caseSensitive: false, requiredFirst: false }], 1751 | ], 1752 | }, 1753 | { 1754 | code: "interface U {è:T; 'Z':T; '#'?:T; À?:T;}", 1755 | output: "interface U {è:T; À?:T; '#'?:T; 'Z':T;}", 1756 | errors: [ 1757 | "Expected interface keys to be in natural insensitive descending order. 'À' should be before '#'.", 1758 | ], 1759 | optionsSet: [ 1760 | [SortingOrder.Descending, { natural: true, caseSensitive: false, requiredFirst: false }], 1761 | ], 1762 | }, 1763 | { 1764 | code: "interface U {À?:T; 'Z':T; '#'?:T; è:T;}", 1765 | output: "interface U {è:T; 'Z':T; '#'?:T; À?:T;}", 1766 | errors: [ 1767 | "Expected interface keys to be in natural insensitive descending order. 'è' should be before '#'.", 1768 | ], 1769 | optionsSet: [ 1770 | [SortingOrder.Descending, { natural: true, caseSensitive: false, requiredFirst: false }], 1771 | ], 1772 | }, 1773 | 1774 | /** 1775 | * index signatures 1776 | */ 1777 | { 1778 | code: 'interface U { A: T; [skey: string]: T; _: T; }', 1779 | output: 'interface U { [skey: string]: T; A: T; _: T; }', 1780 | errors: [ 1781 | "Expected interface keys to be in ascending order. '[index: skey]' should be before 'A'.", 1782 | ], 1783 | optionsSet: [[SortingOrder.Ascending]], 1784 | }, 1785 | { 1786 | code: 'interface U { _: T; [skey: string]: T; A: T; }', 1787 | output: 'interface U { _: T; A: T; [skey: string]: T; }', 1788 | errors: [ 1789 | "Expected interface keys to be in descending order. 'A' should be before '[index: skey]'.", 1790 | ], 1791 | optionsSet: [[SortingOrder.Descending]], 1792 | }, 1793 | ] 1794 | 1795 | describe('TypeScript', () => { 1796 | const ruleTester = new RuleTester(typescript) 1797 | 1798 | ruleTester.run(name, rule as unknown as Rule.RuleModule, { 1799 | valid: processValidTestCase(valid), 1800 | invalid: processInvalidTestCase(invalid), 1801 | }) 1802 | }) 1803 | -------------------------------------------------------------------------------- /tests/rules/string-enum.spec.ts: -------------------------------------------------------------------------------- 1 | import { Rule, RuleTester } from 'eslint' 2 | 3 | import { rule, name, Options } from 'rules/string-enum' 4 | import { SortingOrder } from 'common/options' 5 | import { typescript } from '../helpers/configs' 6 | import { 7 | InvalidTestCase, 8 | processInvalidTestCase, 9 | processValidTestCase, 10 | ValidTestCase, 11 | } from '../helpers/util' 12 | 13 | const valid: readonly ValidTestCase[] = [ 14 | /** 15 | * ignores 16 | */ 17 | { code: 'enum U {c, b, a}', optionsSet: [[]] }, 18 | { code: 'enum U {c=a(), b, a}', optionsSet: [[]] }, 19 | { code: 'enum U {c=0, b, a}', optionsSet: [[]] }, 20 | { code: 'enum U {c=3, b, a}', optionsSet: [[]] }, 21 | { code: 'enum U {c=1<<1, b, a}', optionsSet: [[]] }, 22 | { code: 'enum U {c=M|N, b, a}', optionsSet: [[]] }, 23 | { code: 'enum U {c="123".length, b, a}', optionsSet: [[]] }, 24 | { code: 'enum U {c=0, b="b", a}', optionsSet: [[]] }, 25 | { code: 'const enum U {A=1, B=A*2}', optionsSet: [[]] }, 26 | 27 | /** 28 | * default (asc) 29 | */ 30 | { 31 | code: 'enum U {_="a", a="b", b="c"}', 32 | optionsSet: [ 33 | [], 34 | [SortingOrder.Ascending], 35 | [SortingOrder.Ascending, { caseSensitive: true }], 36 | [SortingOrder.Ascending, { natural: false }], 37 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 38 | ], 39 | }, 40 | { 41 | code: 'enum U {a="a", b="b", c="c"}', 42 | optionsSet: [ 43 | [], 44 | [SortingOrder.Ascending], 45 | [SortingOrder.Ascending, { caseSensitive: true }], 46 | [SortingOrder.Ascending, { natural: false }], 47 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 48 | ], 49 | }, 50 | { 51 | code: 'enum U {a="a", b="b", b_="c"}', 52 | optionsSet: [ 53 | [], 54 | [SortingOrder.Ascending], 55 | [SortingOrder.Ascending, { caseSensitive: true }], 56 | [SortingOrder.Ascending, { natural: false }], 57 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 58 | ], 59 | }, 60 | { 61 | code: 'enum U {C="a", b_="b", c="c"}', 62 | optionsSet: [ 63 | [], 64 | [SortingOrder.Ascending], 65 | [SortingOrder.Ascending, { caseSensitive: true }], 66 | [SortingOrder.Ascending, { natural: false }], 67 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 68 | ], 69 | }, 70 | { 71 | code: 'enum U {$="a", A="b", _="c", a="d"}', 72 | optionsSet: [ 73 | [], 74 | [SortingOrder.Ascending], 75 | [SortingOrder.Ascending, { caseSensitive: true }], 76 | [SortingOrder.Ascending, { natural: false }], 77 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 78 | ], 79 | }, 80 | { 81 | code: "enum U {'#'='a', 'Z'='b', À='c', è='d'}", 82 | optionsSet: [ 83 | [], 84 | [SortingOrder.Ascending], 85 | [SortingOrder.Ascending, { caseSensitive: true }], 86 | [SortingOrder.Ascending, { natural: false }], 87 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 88 | ], 89 | }, 90 | 91 | { 92 | code: 'enum U {_="T", a="T", b="T"}', 93 | optionsSet: [ 94 | [], 95 | [SortingOrder.Ascending], 96 | [SortingOrder.Ascending, { caseSensitive: true }], 97 | [SortingOrder.Ascending, { natural: false }], 98 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 99 | ], 100 | }, 101 | { 102 | code: 'enum U {a="T", b="T", c="T"}', 103 | optionsSet: [ 104 | [], 105 | [SortingOrder.Ascending], 106 | [SortingOrder.Ascending, { caseSensitive: true }], 107 | [SortingOrder.Ascending, { natural: false }], 108 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 109 | ], 110 | }, 111 | { 112 | code: 'enum U {a="T", b="T", b_="T"}', 113 | optionsSet: [ 114 | [], 115 | [SortingOrder.Ascending], 116 | [SortingOrder.Ascending, { caseSensitive: true }], 117 | [SortingOrder.Ascending, { natural: false }], 118 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 119 | ], 120 | }, 121 | { 122 | code: 'enum U {C="T", b_="T", c="T"}', 123 | optionsSet: [ 124 | [], 125 | [SortingOrder.Ascending], 126 | [SortingOrder.Ascending, { caseSensitive: true }], 127 | [SortingOrder.Ascending, { natural: false }], 128 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 129 | ], 130 | }, 131 | { 132 | code: 'enum U {$="T", A="T", _="T", a="T"}', 133 | optionsSet: [ 134 | [], 135 | [SortingOrder.Ascending], 136 | [SortingOrder.Ascending, { caseSensitive: true }], 137 | [SortingOrder.Ascending, { natural: false }], 138 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 139 | ], 140 | }, 141 | { 142 | code: "enum U {'#'='T', 'Z'='T', À='T', è='T'}", 143 | optionsSet: [ 144 | [], 145 | [SortingOrder.Ascending], 146 | [SortingOrder.Ascending, { caseSensitive: true }], 147 | [SortingOrder.Ascending, { natural: false }], 148 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 149 | ], 150 | }, 151 | 152 | /** 153 | * computed 154 | */ 155 | { 156 | code: '{a="T", ["aa"]="T", b="T", c="T"}', 157 | optionsSet: [ 158 | [], 159 | [SortingOrder.Ascending], 160 | [SortingOrder.Ascending, { caseSensitive: true }], 161 | [SortingOrder.Ascending, { natural: false }], 162 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 163 | ], 164 | }, 165 | 166 | /** 167 | * asc, insensitive 168 | */ 169 | { 170 | code: 'enum U {_="T", a="T", b="T"}', 171 | optionsSet: [[SortingOrder.Ascending, { caseSensitive: false }]], 172 | }, 173 | { 174 | code: 'enum U {a="T", b="T", c="T"}', 175 | optionsSet: [[SortingOrder.Ascending, { caseSensitive: false }]], 176 | }, 177 | { 178 | code: 'enum U {a="T", b="T", b_="T"}', 179 | optionsSet: [[SortingOrder.Ascending, { caseSensitive: false }]], 180 | }, 181 | { 182 | code: 'enum U {b_="T", C="T", c="T"}', 183 | optionsSet: [[SortingOrder.Ascending, { caseSensitive: false }]], 184 | }, 185 | { 186 | code: 'enum U {b_="T", c="T", C="T"}', 187 | optionsSet: [[SortingOrder.Ascending, { caseSensitive: false }]], 188 | }, 189 | { 190 | code: 'enum U {$="T", _="T", A="T", a="T"}', 191 | optionsSet: [[SortingOrder.Ascending, { caseSensitive: false }]], 192 | }, 193 | { 194 | code: "enum U {'#'='T', 'Z'='T', À='T', è='T'}", 195 | optionsSet: [[SortingOrder.Ascending, { natural: true }]], 196 | }, 197 | 198 | /** 199 | * asc, natural, insensitive 200 | */ 201 | { 202 | code: 'enum U {_="T", a="T", b="T"}', 203 | optionsSet: [[SortingOrder.Ascending, { natural: true, caseSensitive: false }]], 204 | }, 205 | { 206 | code: 'enum U {a="T", b="T", c="T"}', 207 | optionsSet: [[SortingOrder.Ascending, { natural: true, caseSensitive: false }]], 208 | }, 209 | { 210 | code: 'enum U {a="T", b="T", b_="T"}', 211 | optionsSet: [[SortingOrder.Ascending, { natural: true, caseSensitive: false }]], 212 | }, 213 | { 214 | code: 'enum U {b_="T", C="T", c="T"}', 215 | optionsSet: [[SortingOrder.Ascending, { natural: true, caseSensitive: false }]], 216 | }, 217 | { 218 | code: 'enum U {b_="T", c="T", C="T"}', 219 | optionsSet: [[SortingOrder.Ascending, { natural: true, caseSensitive: false }]], 220 | }, 221 | { 222 | code: 'enum U {$="T", _="T", A="T", a="T"}', 223 | optionsSet: [[SortingOrder.Ascending, { natural: true, caseSensitive: false }]], 224 | }, 225 | { 226 | code: "enum U {'#'='T', 'Z'='T', À='T', è='T'}", 227 | optionsSet: [[SortingOrder.Ascending, { natural: true, caseSensitive: false }]], 228 | }, 229 | 230 | /** 231 | * desc 232 | */ 233 | { code: 'enum U {b="T", a="T", _="T"}', optionsSet: [[SortingOrder.Descending]] }, 234 | { code: 'enum U {c="T", b="T", a="T"}', optionsSet: [[SortingOrder.Descending]] }, 235 | { code: 'enum U {b_="T", b="T", a="T"}', optionsSet: [[SortingOrder.Descending]] }, 236 | { code: 'enum U {c="T", b_="T", C="T"}', optionsSet: [[SortingOrder.Descending]] }, 237 | { code: 'enum U {a="T", _="T", A="T", $="T"}', optionsSet: [[SortingOrder.Descending]] }, 238 | { code: "enum U {è='T', À='T', 'Z'='T', '#'='T'}", optionsSet: [[SortingOrder.Descending]] }, 239 | 240 | /** 241 | * desc, insensitive 242 | */ 243 | { 244 | code: 'enum U {b="T", a="T", _="T"}', 245 | optionsSet: [[SortingOrder.Descending, { caseSensitive: false }]], 246 | }, 247 | { 248 | code: 'enum U {c="T", b="T", a="T"}', 249 | optionsSet: [[SortingOrder.Descending, { caseSensitive: false }]], 250 | }, 251 | { 252 | code: 'enum U {b_="T", b="T", a="T"}', 253 | optionsSet: [[SortingOrder.Descending, { caseSensitive: false }]], 254 | }, 255 | { 256 | code: 'enum U {c="T", C="T", b_="T"}', 257 | optionsSet: [[SortingOrder.Descending, { caseSensitive: false }]], 258 | }, 259 | { 260 | code: 'enum U {C="T", c="T", b_="T"}', 261 | optionsSet: [[SortingOrder.Descending, { caseSensitive: false }]], 262 | }, 263 | { 264 | code: 'enum U {a="T", A="T", _="T", $="T"}', 265 | optionsSet: [[SortingOrder.Descending, { caseSensitive: false }]], 266 | }, 267 | { 268 | code: "enum U {è='T', À='T', 'Z'='T', '#'='T'}", 269 | optionsSet: [[SortingOrder.Descending, { caseSensitive: false }]], 270 | }, 271 | 272 | /** 273 | * desc, natural 274 | */ 275 | { 276 | code: 'enum U {b="T", a="T", _="T"}', 277 | optionsSet: [[SortingOrder.Descending, { natural: true }]], 278 | }, 279 | { 280 | code: 'enum U {c="T", b="T", a="T"}', 281 | optionsSet: [[SortingOrder.Descending, { natural: true }]], 282 | }, 283 | { 284 | code: 'enum U {b_="T", b="T", a="T"}', 285 | optionsSet: [[SortingOrder.Descending, { natural: true }]], 286 | }, 287 | { 288 | code: 'enum U {c="T", b_="T", C="T"}', 289 | optionsSet: [[SortingOrder.Descending, { natural: true }]], 290 | }, 291 | { 292 | code: 'enum U {a="T", A="T", _="T", $="T"}', 293 | optionsSet: [[SortingOrder.Descending, { natural: true }]], 294 | }, 295 | { 296 | code: "enum U {è='T', À='T', 'Z'='T', '#'='T'}", 297 | optionsSet: [[SortingOrder.Descending, { natural: true }]], 298 | }, 299 | 300 | /** 301 | * desc, natural, insensitive 302 | */ 303 | { 304 | code: 'enum U {b="T", a="T", _="T"}', 305 | optionsSet: [[SortingOrder.Descending, { natural: true, caseSensitive: false }]], 306 | }, 307 | { 308 | code: 'enum U {c="T", b="T", a="T"}', 309 | optionsSet: [[SortingOrder.Descending, { natural: true, caseSensitive: false }]], 310 | }, 311 | { 312 | code: 'enum U {b_="T", b="T", a="T"}', 313 | optionsSet: [[SortingOrder.Descending, { natural: true, caseSensitive: false }]], 314 | }, 315 | { 316 | code: 'enum U {c="T", C="T", b_="T"}', 317 | optionsSet: [[SortingOrder.Descending, { natural: true, caseSensitive: false }]], 318 | }, 319 | { 320 | code: 'enum U {C="T", c="T", b_="T"}', 321 | optionsSet: [[SortingOrder.Descending, { natural: true, caseSensitive: false }]], 322 | }, 323 | { 324 | code: 'enum U {a="T", A="T", _="T", $="T"}', 325 | optionsSet: [[SortingOrder.Descending, { natural: true, caseSensitive: false }]], 326 | }, 327 | { 328 | code: "enum U {è='T', À='T', 'Z'='T', '#'='T'}", 329 | optionsSet: [[SortingOrder.Descending, { natural: true, caseSensitive: false }]], 330 | }, 331 | ] 332 | 333 | const invalid: readonly InvalidTestCase[] = [ 334 | /** 335 | * default (asc) 336 | */ 337 | { 338 | code: 'enum U {a="a", _="b", b="c"}', 339 | output: 'enum U {_="b", a="a", b="c"}', 340 | errors: ["Expected string enum members to be in ascending order. '_' should be before 'a'."], 341 | optionsSet: [ 342 | [], 343 | [SortingOrder.Ascending], 344 | [SortingOrder.Ascending, { caseSensitive: true }], 345 | [SortingOrder.Ascending, { natural: false }], 346 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 347 | ], 348 | }, 349 | { 350 | code: 'enum U {a="T", c="T", b="T"}', 351 | output: 'enum U {a="T", b="T", c="T"}', 352 | errors: ["Expected string enum members to be in ascending order. 'b' should be before 'c'."], 353 | optionsSet: [ 354 | [], 355 | [SortingOrder.Ascending], 356 | [SortingOrder.Ascending, { caseSensitive: true }], 357 | [SortingOrder.Ascending, { natural: false }], 358 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 359 | ], 360 | }, 361 | { 362 | code: 'enum U {b_="T", a="T", b="T"}', 363 | output: 'enum U {a="T", b_="T", b="T"}', 364 | errors: ["Expected string enum members to be in ascending order. 'a' should be before 'b_'."], 365 | optionsSet: [ 366 | [], 367 | [SortingOrder.Ascending], 368 | [SortingOrder.Ascending, { caseSensitive: true }], 369 | [SortingOrder.Ascending, { natural: false }], 370 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 371 | ], 372 | }, 373 | { 374 | code: 'enum U {b_="T", c="T", C="T"}', 375 | output: 'enum U {C="T", c="T", b_="T",}', 376 | errors: ["Expected string enum members to be in ascending order. 'C' should be before 'c'."], 377 | optionsSet: [ 378 | [], 379 | [SortingOrder.Ascending], 380 | [SortingOrder.Ascending, { caseSensitive: true }], 381 | [SortingOrder.Ascending, { natural: false }], 382 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 383 | ], 384 | }, 385 | { 386 | code: 'enum U {$="T", _="T", A="T", a="T"}', 387 | output: 'enum U {$="T", A="T", _="T", a="T"}', 388 | errors: ["Expected string enum members to be in ascending order. 'A' should be before '_'."], 389 | optionsSet: [ 390 | [], 391 | [SortingOrder.Ascending], 392 | [SortingOrder.Ascending, { caseSensitive: true }], 393 | [SortingOrder.Ascending, { natural: false }], 394 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 395 | ], 396 | }, 397 | { 398 | code: "enum U {'#'='T', À='T', 'Z'='T', è='T'}", 399 | output: "enum U {'#'='T', 'Z'='T', À='T', è='T'}", 400 | errors: ["Expected string enum members to be in ascending order. 'Z' should be before 'À'."], 401 | optionsSet: [ 402 | [], 403 | [SortingOrder.Ascending], 404 | [SortingOrder.Ascending, { caseSensitive: true }], 405 | [SortingOrder.Ascending, { natural: false }], 406 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 407 | ], 408 | }, 409 | 410 | /** 411 | * not ignore simple computed properties. 412 | */ 413 | { 414 | code: 'enum U {a="T", b="T", ["aa"]="T", c="T"}', 415 | output: 'enum U {a="T", ["aa"]="T", b="T", c="T"}', 416 | errors: ["Expected string enum members to be in ascending order. 'aa' should be before 'b'."], 417 | optionsSet: [ 418 | [], 419 | [SortingOrder.Ascending], 420 | [SortingOrder.Ascending, { caseSensitive: true }], 421 | [SortingOrder.Ascending, { natural: false }], 422 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 423 | ], 424 | }, 425 | 426 | /** 427 | * asc 428 | */ 429 | { 430 | code: 'enum U {a="T", _="T", b="T"}', 431 | output: 'enum U {_="T", a="T", b="T"}', 432 | errors: ["Expected string enum members to be in ascending order. '_' should be before 'a'."], 433 | optionsSet: [ 434 | [], 435 | [SortingOrder.Ascending], 436 | [SortingOrder.Ascending, { caseSensitive: true }], 437 | [SortingOrder.Ascending, { natural: false }], 438 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 439 | ], 440 | }, 441 | { 442 | code: 'enum U {a="T", c="T", b="T"}', 443 | output: 'enum U {a="T", b="T", c="T"}', 444 | errors: ["Expected string enum members to be in ascending order. 'b' should be before 'c'."], 445 | optionsSet: [ 446 | [], 447 | [SortingOrder.Ascending], 448 | [SortingOrder.Ascending, { caseSensitive: true }], 449 | [SortingOrder.Ascending, { natural: false }], 450 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 451 | ], 452 | }, 453 | { 454 | code: 'enum U {b_="T", a="T", b="T"}', 455 | output: 'enum U {a="T", b_="T", b="T"}', 456 | errors: ["Expected string enum members to be in ascending order. 'a' should be before 'b_'."], 457 | optionsSet: [ 458 | [], 459 | [SortingOrder.Ascending], 460 | [SortingOrder.Ascending, { caseSensitive: true }], 461 | [SortingOrder.Ascending, { natural: false }], 462 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 463 | ], 464 | }, 465 | { 466 | code: 'enum U {b_="T", c="T", C="T"}', 467 | output: 'enum U {C="T", c="T", b_="T",}', 468 | errors: ["Expected string enum members to be in ascending order. 'C' should be before 'c'."], 469 | optionsSet: [ 470 | [], 471 | [SortingOrder.Ascending], 472 | [SortingOrder.Ascending, { caseSensitive: true }], 473 | [SortingOrder.Ascending, { natural: false }], 474 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 475 | ], 476 | }, 477 | { 478 | code: 'enum U {$="T", _="T", A="T", a="T"}', 479 | output: 'enum U {$="T", A="T", _="T", a="T"}', 480 | errors: ["Expected string enum members to be in ascending order. 'A' should be before '_'."], 481 | optionsSet: [ 482 | [], 483 | [SortingOrder.Ascending], 484 | [SortingOrder.Ascending, { caseSensitive: true }], 485 | [SortingOrder.Ascending, { natural: false }], 486 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 487 | ], 488 | }, 489 | { 490 | code: "enum U {'#'='T', À='T', 'Z'='T', è='T'}", 491 | output: "enum U {'#'='T', 'Z'='T', À='T', è='T'}", 492 | errors: ["Expected string enum members to be in ascending order. 'Z' should be before 'À'."], 493 | optionsSet: [ 494 | [], 495 | [SortingOrder.Ascending], 496 | [SortingOrder.Ascending, { caseSensitive: true }], 497 | [SortingOrder.Ascending, { natural: false }], 498 | [SortingOrder.Ascending, { caseSensitive: true, natural: false }], 499 | ], 500 | }, 501 | 502 | /** 503 | * asc, insensitive 504 | */ 505 | { 506 | code: 'enum U {a="T", _="T", b="T"}', 507 | output: 'enum U {_="T", a="T", b="T"}', 508 | errors: [ 509 | "Expected string enum members to be in insensitive ascending order. '_' should be before 'a'.", 510 | ], 511 | optionsSet: [[SortingOrder.Ascending, { caseSensitive: false }]], 512 | }, 513 | { 514 | code: 'enum U {a="T", c="T", b="T"}', 515 | output: 'enum U {a="T", b="T", c="T"}', 516 | errors: [ 517 | "Expected string enum members to be in insensitive ascending order. 'b' should be before 'c'.", 518 | ], 519 | optionsSet: [[SortingOrder.Ascending, { caseSensitive: false }]], 520 | }, 521 | { 522 | code: 'enum U {b_="T", a="T", b="T"}', 523 | output: 'enum U {a="T", b_="T", b="T"}', 524 | errors: [ 525 | "Expected string enum members to be in insensitive ascending order. 'a' should be before 'b_'.", 526 | ], 527 | optionsSet: [[SortingOrder.Ascending, { caseSensitive: false }]], 528 | }, 529 | { 530 | code: 'enum U {$="T", A="T", _="T", a="T"}', 531 | output: 'enum U {$="T", _="T", A="T", a="T"}', 532 | errors: [ 533 | "Expected string enum members to be in insensitive ascending order. '_' should be before 'A'.", 534 | ], 535 | optionsSet: [[SortingOrder.Ascending, { caseSensitive: false }]], 536 | }, 537 | { 538 | code: "enum U {'#'='T', À='T', 'Z'='T', è='T'}", 539 | output: "enum U {'#'='T', 'Z'='T', À='T', è='T'}", 540 | errors: [ 541 | "Expected string enum members to be in insensitive ascending order. 'Z' should be before 'À'.", 542 | ], 543 | optionsSet: [[SortingOrder.Ascending, { caseSensitive: false }]], 544 | }, 545 | 546 | /** 547 | * asc, natural 548 | */ 549 | { 550 | code: 'enum U {a="T", _="T", b="T"}', 551 | output: 'enum U {_="T", a="T", b="T"}', 552 | errors: [ 553 | "Expected string enum members to be in natural ascending order. '_' should be before 'a'.", 554 | ], 555 | optionsSet: [[SortingOrder.Ascending, { natural: true }]], 556 | }, 557 | { 558 | code: 'enum U {a="T", c="T", b="T"}', 559 | output: 'enum U {a="T", b="T", c="T"}', 560 | errors: [ 561 | "Expected string enum members to be in natural ascending order. 'b' should be before 'c'.", 562 | ], 563 | optionsSet: [[SortingOrder.Ascending, { natural: true }]], 564 | }, 565 | { 566 | code: 'enum U {b_="T", a="T", b="T"}', 567 | output: 'enum U {a="T", b_="T", b="T"}', 568 | errors: [ 569 | "Expected string enum members to be in natural ascending order. 'a' should be before 'b_'.", 570 | ], 571 | optionsSet: [[SortingOrder.Ascending, { natural: true }]], 572 | }, 573 | { 574 | code: 'enum U {b_="T", c="T", C="T"}', 575 | output: 'enum U {C="T", c="T", b_="T",}', 576 | errors: [ 577 | "Expected string enum members to be in natural ascending order. 'C' should be before 'c'.", 578 | ], 579 | optionsSet: [[SortingOrder.Ascending, { natural: true }]], 580 | }, 581 | { 582 | code: 'enum U {$="T", A="T", _="T", a="T"}', 583 | output: 'enum U {$="T", _="T", A="T", a="T"}', 584 | errors: [ 585 | "Expected string enum members to be in natural ascending order. '_' should be before 'A'.", 586 | ], 587 | optionsSet: [[SortingOrder.Ascending, { natural: true }]], 588 | }, 589 | { 590 | code: "enum U {'#'='T', À='T', 'Z'='T', è='T'}", 591 | output: "enum U {'#'='T', 'Z'='T', À='T', è='T'}", 592 | errors: [ 593 | "Expected string enum members to be in natural ascending order. 'Z' should be before 'À'.", 594 | ], 595 | optionsSet: [[SortingOrder.Ascending, { natural: true }]], 596 | }, 597 | 598 | /** 599 | * asc, natural, insensitive 600 | */ 601 | { 602 | code: 'enum U {a="T", _="T", b="T"}', 603 | output: 'enum U {_="T", a="T", b="T"}', 604 | errors: [ 605 | "Expected string enum members to be in natural insensitive ascending order. '_' should be before 'a'.", 606 | ], 607 | optionsSet: [[SortingOrder.Ascending, { natural: true, caseSensitive: false }]], 608 | }, 609 | { 610 | code: 'enum U {a="T", c="T", b="T"}', 611 | output: 'enum U {a="T", b="T", c="T"}', 612 | errors: [ 613 | "Expected string enum members to be in natural insensitive ascending order. 'b' should be before 'c'.", 614 | ], 615 | optionsSet: [[SortingOrder.Ascending, { natural: true, caseSensitive: false }]], 616 | }, 617 | { 618 | code: 'enum U {b_="T", a="T", b="T"}', 619 | output: 'enum U {a="T", b_="T", b="T"}', 620 | errors: [ 621 | "Expected string enum members to be in natural insensitive ascending order. 'a' should be before 'b_'.", 622 | ], 623 | optionsSet: [[SortingOrder.Ascending, { natural: true, caseSensitive: false }]], 624 | }, 625 | { 626 | code: 'enum U {$="T", A="T", _="T", a="T"}', 627 | output: 'enum U {$="T", _="T", A="T", a="T"}', 628 | errors: [ 629 | "Expected string enum members to be in natural insensitive ascending order. '_' should be before 'A'.", 630 | ], 631 | optionsSet: [[SortingOrder.Ascending, { natural: true, caseSensitive: false }]], 632 | }, 633 | { 634 | code: "enum U {'#'='T', À='T', 'Z'='T', è='T'}", 635 | output: "enum U {'#'='T', 'Z'='T', À='T', è='T'}", 636 | errors: [ 637 | "Expected string enum members to be in natural insensitive ascending order. 'Z' should be before 'À'.", 638 | ], 639 | optionsSet: [[SortingOrder.Ascending, { natural: true, caseSensitive: false }]], 640 | }, 641 | 642 | /** 643 | * desc 644 | */ 645 | { 646 | code: 'enum U {a="T", _="T", b="T"}', 647 | output: 'enum U {b="T", _="T", a="T",}', 648 | errors: ["Expected string enum members to be in descending order. 'b' should be before '_'."], 649 | optionsSet: [[SortingOrder.Descending]], 650 | }, 651 | { 652 | code: 'enum U {a="T", c="T", b="T"}', 653 | output: 'enum U {c="T", a="T", b="T"}', 654 | errors: ["Expected string enum members to be in descending order. 'c' should be before 'a'."], 655 | optionsSet: [[SortingOrder.Descending]], 656 | }, 657 | { 658 | code: 'enum U {b_="T", a="T", b="T"}', 659 | output: 'enum U {b_="T", b="T", a="T"}', 660 | errors: ["Expected string enum members to be in descending order. 'b' should be before 'a'."], 661 | optionsSet: [[SortingOrder.Descending]], 662 | }, 663 | { 664 | code: 'enum U {b_="T", c="T", C="T"}', 665 | output: 'enum U {c="T", b_="T", C="T"}', 666 | errors: ["Expected string enum members to be in descending order. 'c' should be before 'b_'."], 667 | optionsSet: [[SortingOrder.Descending]], 668 | }, 669 | { 670 | code: 'enum U {$="T", _="T", A="T", a="T"}', 671 | output: 'enum U {a="T", _="T", A="T", $="T"}', 672 | errors: [ 673 | "Expected string enum members to be in descending order. '_' should be before '$'.", 674 | "Expected string enum members to be in descending order. 'a' should be before 'A'.", 675 | ], 676 | optionsSet: [[SortingOrder.Descending]], 677 | }, 678 | { 679 | code: "enum U {'#'='T', À='T', 'Z'='T', è='T'}", 680 | output: "enum U {è='T', À='T', 'Z'='T', '#'='T'}", 681 | errors: [ 682 | "Expected string enum members to be in descending order. 'À' should be before '#'.", 683 | "Expected string enum members to be in descending order. 'è' should be before 'Z'.", 684 | ], 685 | optionsSet: [[SortingOrder.Descending]], 686 | }, 687 | 688 | /** 689 | * desc, insensitive 690 | */ 691 | { 692 | code: 'enum U {a="T", _="T", b="T"}', 693 | output: 'enum U {b="T", _="T", a="T",}', 694 | errors: [ 695 | "Expected string enum members to be in insensitive descending order. 'b' should be before '_'.", 696 | ], 697 | optionsSet: [[SortingOrder.Descending, { caseSensitive: false }]], 698 | }, 699 | { 700 | code: 'enum U {a="T", c="T", b="T"}', 701 | output: 'enum U {c="T", a="T", b="T"}', 702 | errors: [ 703 | "Expected string enum members to be in insensitive descending order. 'c' should be before 'a'.", 704 | ], 705 | optionsSet: [[SortingOrder.Descending, { caseSensitive: false }]], 706 | }, 707 | { 708 | code: 'enum U {b_="T", a="T", b="T"}', 709 | output: 'enum U {b_="T", b="T", a="T"}', 710 | errors: [ 711 | "Expected string enum members to be in insensitive descending order. 'b' should be before 'a'.", 712 | ], 713 | optionsSet: [[SortingOrder.Descending, { caseSensitive: false }]], 714 | }, 715 | { 716 | code: 'enum U {b_="T", c="T", C="T"}', 717 | output: 'enum U {c="T", b_="T", C="T"}', 718 | errors: [ 719 | "Expected string enum members to be in insensitive descending order. 'c' should be before 'b_'.", 720 | ], 721 | optionsSet: [[SortingOrder.Descending, { caseSensitive: false }]], 722 | }, 723 | { 724 | code: 'enum U {$="T", _="T", A="T", a="T"}', 725 | output: 'enum U {A="T", _="T", $="T", a="T"}', 726 | errors: [ 727 | "Expected string enum members to be in insensitive descending order. '_' should be before '$'.", 728 | "Expected string enum members to be in insensitive descending order. 'A' should be before '_'.", 729 | ], 730 | optionsSet: [[SortingOrder.Descending, { caseSensitive: false }]], 731 | }, 732 | { 733 | code: "enum U {'#'='T', À='T', 'Z'='T', è='T'}", 734 | output: "enum U {è='T', À='T', 'Z'='T', '#'='T'}", 735 | errors: [ 736 | "Expected string enum members to be in insensitive descending order. 'À' should be before '#'.", 737 | "Expected string enum members to be in insensitive descending order. 'è' should be before 'Z'.", 738 | ], 739 | optionsSet: [[SortingOrder.Descending, { caseSensitive: false }]], 740 | }, 741 | 742 | /** 743 | * desc, natural 744 | */ 745 | { 746 | code: 'enum U {a="T", _="T", b="T"}', 747 | output: 'enum U {b="T", _="T", a="T",}', 748 | errors: [ 749 | "Expected string enum members to be in natural descending order. 'b' should be before '_'.", 750 | ], 751 | optionsSet: [[SortingOrder.Descending, { natural: true }]], 752 | }, 753 | { 754 | code: 'enum U {a="T", c="T", b="T"}', 755 | output: 'enum U {c="T", a="T", b="T"}', 756 | errors: [ 757 | "Expected string enum members to be in natural descending order. 'c' should be before 'a'.", 758 | ], 759 | optionsSet: [[SortingOrder.Descending, { natural: true }]], 760 | }, 761 | { 762 | code: 'enum U {b_="T", a="T", b="T"}', 763 | output: 'enum U {b_="T", b="T", a="T"}', 764 | errors: [ 765 | "Expected string enum members to be in natural descending order. 'b' should be before 'a'.", 766 | ], 767 | optionsSet: [[SortingOrder.Descending, { natural: true }]], 768 | }, 769 | { 770 | code: 'enum U {b_="T", c="T", C="T"}', 771 | output: 'enum U {c="T", b_="T", C="T"}', 772 | errors: [ 773 | "Expected string enum members to be in natural descending order. 'c' should be before 'b_'.", 774 | ], 775 | optionsSet: [[SortingOrder.Descending, { natural: true }]], 776 | }, 777 | { 778 | code: 'enum U {$="T", _="T", A="T", a="T"}', 779 | output: 'enum U {a="T", _="T", A="T", $="T"}', 780 | errors: [ 781 | "Expected string enum members to be in natural descending order. '_' should be before '$'.", 782 | "Expected string enum members to be in natural descending order. 'A' should be before '_'.", 783 | "Expected string enum members to be in natural descending order. 'a' should be before 'A'.", 784 | ], 785 | optionsSet: [[SortingOrder.Descending, { natural: true }]], 786 | }, 787 | { 788 | code: "enum U {'#'='T', À='T', 'Z'='T', è='T'}", 789 | output: "enum U {è='T', À='T', 'Z'='T', '#'='T'}", 790 | errors: [ 791 | "Expected string enum members to be in natural descending order. 'À' should be before '#'.", 792 | "Expected string enum members to be in natural descending order. 'è' should be before 'Z'.", 793 | ], 794 | optionsSet: [[SortingOrder.Descending, { natural: true }]], 795 | }, 796 | 797 | /** 798 | * desc, natural, insensitive 799 | */ 800 | { 801 | code: 'enum U {a="T", _="T", b="T"}', 802 | output: 'enum U {b="T", _="T", a="T",}', 803 | errors: [ 804 | "Expected string enum members to be in natural insensitive descending order. 'b' should be before '_'.", 805 | ], 806 | optionsSet: [[SortingOrder.Descending, { natural: true, caseSensitive: false }]], 807 | }, 808 | { 809 | code: 'enum U {a="T", c="T", b="T"}', 810 | output: 'enum U {c="T", a="T", b="T"}', 811 | errors: [ 812 | "Expected string enum members to be in natural insensitive descending order. 'c' should be before 'a'.", 813 | ], 814 | optionsSet: [[SortingOrder.Descending, { natural: true, caseSensitive: false }]], 815 | }, 816 | { 817 | code: 'enum U {b_="T", a="T", b="T"}', 818 | output: 'enum U {b_="T", b="T", a="T"}', 819 | errors: [ 820 | "Expected string enum members to be in natural insensitive descending order. 'b' should be before 'a'.", 821 | ], 822 | optionsSet: [[SortingOrder.Descending, { natural: true, caseSensitive: false }]], 823 | }, 824 | { 825 | code: 'enum U {b_="T", c="T", C="T"}', 826 | output: 'enum U {c="T", b_="T", C="T"}', 827 | errors: [ 828 | "Expected string enum members to be in natural insensitive descending order. 'c' should be before 'b_'.", 829 | ], 830 | optionsSet: [[SortingOrder.Descending, { natural: true, caseSensitive: false }]], 831 | }, 832 | { 833 | code: 'enum U {$="T", _="T", A="T", a="T"}', 834 | output: 'enum U {A="T", _="T", $="T", a="T"}', 835 | errors: [ 836 | "Expected string enum members to be in natural insensitive descending order. '_' should be before '$'.", 837 | "Expected string enum members to be in natural insensitive descending order. 'A' should be before '_'.", 838 | ], 839 | optionsSet: [[SortingOrder.Descending, { natural: true, caseSensitive: false }]], 840 | }, 841 | { 842 | code: "enum U {'#'='T', À='T', 'Z'='T', è='T'}", 843 | output: "enum U {è='T', À='T', 'Z'='T', '#'='T'}", 844 | errors: [ 845 | "Expected string enum members to be in natural insensitive descending order. 'À' should be before '#'.", 846 | "Expected string enum members to be in natural insensitive descending order. 'è' should be before 'Z'.", 847 | ], 848 | optionsSet: [[SortingOrder.Descending, { natural: true, caseSensitive: false }]], 849 | }, 850 | ] 851 | 852 | describe('TypeScript', () => { 853 | const ruleTester = new RuleTester(typescript) 854 | 855 | ruleTester.run(name, rule as unknown as Rule.RuleModule, { 856 | valid: processValidTestCase(valid), 857 | invalid: processInvalidTestCase(invalid), 858 | }) 859 | }) 860 | -------------------------------------------------------------------------------- /tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "outDir": "../build", 6 | // Turn off checks to make debugging nicer. 7 | "noFallthroughCasesInSwitch": false, 8 | "noImplicitAny": false, 9 | "noImplicitReturns": false, 10 | "noImplicitThis": false, 11 | "noUnusedLocals": false, 12 | "noUnusedParameters": false, 13 | "strictNullChecks": false 14 | }, 15 | "include": ["**/*.ts"], 16 | "exclude": ["fixtures"] 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "ES6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */, 6 | "module": "esnext" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, 7 | "lib": ["ESNext"] /* Specify library files to be included in the compilation. */, 8 | "allowJs": true /* Allow javascript files to be compiled. */, 9 | "checkJs": false /* Report errors in .js files. */, 10 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | // "declaration": true /* Generates corresponding '.d.ts' file. */, 12 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 13 | "sourceMap": false /* Generates corresponding '.map' file. */, 14 | // "outFile": "./", /* Concatenate and emit output to single file. */ 15 | "outDir": "lib" /* Redirect output structure to the directory. */, 16 | // "rootDir": "src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, 17 | // "composite": true, /* Enable project compilation */ 18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | 25 | /* Strict Type-Checking Options */ 26 | "strict": true /* Enable all strict type-checking options. */, 27 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 28 | // "strictNullChecks": true, /* Enable strict null checks. */ 29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 30 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 31 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 34 | 35 | /* Additional Checks */ 36 | "noUnusedLocals": true /* Report errors on unused locals. */, 37 | "noUnusedParameters": true /* Report errors on unused parameters. */, 38 | "noImplicitReturns": true /* Report error when not all code paths in function return a value. */, 39 | "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */, 40 | 41 | /* Module Resolution Options */ 42 | "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, 43 | "baseUrl": "src" /* Base directory to resolve non-absolute module names. */, 44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 46 | // "typeRoots": [], /* List of folders to include type definitions from. */ 47 | // "types": [], /* Type declaration files to be included in compilation. */ 48 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 49 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 51 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 52 | 53 | /* Source Map Options */ 54 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 55 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 56 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 57 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 58 | 59 | /* Experimental Options */ 60 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 61 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 62 | 63 | /* Advanced Options */ 64 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 65 | }, 66 | "exclude": ["./node_modules", "tests/fixtures", "dist", "lib"] 67 | } 68 | --------------------------------------------------------------------------------