├── .eslintignore ├── .eslintrc ├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .prettierrc ├── CHANGELOG.md ├── README.md ├── jest-e2e.config.js ├── jest-e2e.setup.js ├── jest-unit.config.js ├── jest.config.js ├── package.json ├── src ├── dependencies │ └── dependencies.ts ├── e2e │ ├── generate │ │ ├── __snapshots__ │ │ │ ├── eslint.test.ts.snap │ │ │ ├── prettier.test.ts.snap │ │ │ ├── react.test.ts.snap │ │ │ ├── typescript.test.ts.snap │ │ │ └── vue.test.ts.snap │ │ ├── eslint.test.ts │ │ ├── prettier.test.ts │ │ ├── react.test.ts │ │ ├── typescript.test.ts │ │ └── vue.test.ts │ ├── helpers.ts │ └── upgrade │ │ ├── __snapshots__ │ │ ├── eslint.test.ts.snap │ │ ├── prettier.test.ts.snap │ │ └── typescript.test.ts.snap │ │ ├── eslint.test.ts │ │ ├── prettier.test.ts │ │ └── typescript.test.ts ├── generator │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ ├── generate.test.ts.snap │ │ │ └── upgrade.test.ts.snap │ │ ├── generate.test.ts │ │ └── upgrade.test.ts │ ├── base-configs │ │ ├── envESLintConfig.ts │ │ ├── eslintBaseConfig.ts │ │ ├── index.ts │ │ ├── prettierConfig.ts │ │ ├── prettierESLintConfig.ts │ │ ├── reactESLintConfig.ts │ │ ├── typescriptEslintConfig.ts │ │ └── vueESLintConfig.ts │ ├── env.ts │ ├── front-framework.ts │ ├── generate.ts │ ├── index.ts │ ├── override.ts │ ├── prettier.ts │ ├── test-framework.ts │ ├── types.ts │ └── typescript.ts ├── index.ts ├── logger │ └── clinter-settings.ts ├── migration │ ├── eslint.ts │ ├── index.ts │ ├── insertIgnoreLinesScript.ts │ └── types.ts ├── parser │ ├── __tests__ │ │ └── typescript-config.test.ts │ ├── clinter-mode.ts │ ├── clinter-settings-input-parser.ts │ ├── clinter-settings.ts │ ├── linter-config-parser │ │ ├── config-container.ts │ │ ├── eslint.ts │ │ ├── index.ts │ │ ├── linter-config-parser.ts │ │ └── types.ts │ ├── package-tool.ts │ ├── project-dependencies.ts │ ├── project-info-inferer │ │ ├── index.ts │ │ ├── project-info-inferer.ts │ │ └── types.ts │ ├── project-info.ts │ ├── typescript-config.ts │ └── user-questions │ │ ├── clinter-mode-questions.ts │ │ ├── generator-questions.ts │ │ ├── index.ts │ │ ├── migration-mode-questions.ts │ │ └── project-info-questions.ts ├── types.ts ├── utils │ └── utility.ts └── writer │ └── linter-config │ └── fileWriter.ts ├── tsconfig.json └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | ./tests -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "browser": true, 5 | "node": true, 6 | "es6": true, 7 | "jest": true 8 | }, 9 | "extends": [ 10 | "eslint:recommended", 11 | "plugin:@typescript-eslint/recommended", 12 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 13 | "plugin:prettier/recommended" 14 | ], 15 | "parser": "@typescript-eslint/parser", 16 | "parserOptions": { 17 | "project": "tsconfig.json" 18 | }, 19 | "plugins": ["import"], 20 | "rules": { 21 | "@typescript-eslint/prefer-optional-chain": "error", 22 | "@typescript-eslint/prefer-nullish-coalescing": "error", 23 | "@typescript-eslint/strict-boolean-expressions": "error", 24 | "curly": ["error", "all"], 25 | "eqeqeq": ["error", "smart"], 26 | "max-lines": ["error", 200], 27 | "max-params": ["error", 4], 28 | "import/no-extraneous-dependencies": [ 29 | "error", 30 | { 31 | "devDependencies": true, 32 | "optionalDependencies": false, 33 | "peerDependencies": false 34 | } 35 | ], 36 | "no-shadow": "off", 37 | "prefer-const": "error", 38 | "import/order": [ 39 | "error", 40 | { 41 | "groups": [["external", "builtin"], "internal", ["parent", "sibling", "index"]] 42 | } 43 | ], 44 | "sort-imports": [ 45 | "error", 46 | { 47 | "ignoreCase": true, 48 | "ignoreDeclarationSort": true, 49 | "ignoreMemberSort": false, 50 | "memberSyntaxSortOrder": ["none", "all", "multiple", "single"] 51 | } 52 | ] 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test-and-build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | 11 | - name: Install packages 12 | run: yarn install --frozen-lockfile 13 | 14 | - name: Unit tests 15 | run: yarn test:unit 16 | 17 | - name: Compile package 18 | run: yarn build 19 | 20 | - name: E2E tests 21 | run: yarn test:e2e:ci -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: [workflow_dispatch] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | 11 | - name: Install packages 12 | run: yarn install --frozen-lockfile 13 | 14 | - name: Unit tests 15 | run: yarn test:unit 16 | 17 | - name: E2E tests 18 | run: yarn test:e2e 19 | 20 | release: 21 | runs-on: ubuntu-latest 22 | needs: test 23 | steps: 24 | 25 | - uses: actions/checkout@v2 26 | 27 | - name: Setup Node.js 28 | uses: actions/setup-node@v1 29 | with: 30 | node-version: 12.x 31 | 32 | - name: Install packages 33 | run: yarn install --frozen-lockfile 34 | 35 | - name: Release 36 | run: npx semantic-release 37 | env: 38 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} 39 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "semi": true, 4 | "tabWidth": 2 5 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [1.17.0](https://github.com/theodo/clinter/compare/v1.16.0...v1.17.0) (2023-04-03) 2 | 3 | 4 | ### Bug Fixes 5 | 6 | * bump ttypescript for a successful build ([c6e1b4e](https://github.com/theodo/clinter/commit/c6e1b4e496c268202f7e64c5532e855a2131e294)) 7 | 8 | 9 | ### Features 10 | 11 | * add support for pnpm ([fe4c19c](https://github.com/theodo/clinter/commit/fe4c19c821fe101be5551caf4e048e2202618f56)) 12 | 13 | # [1.16.0](https://github.com/theodo/clinter/compare/v1.15.0...v1.16.0) (2022-07-18) 14 | 15 | 16 | ### Bug Fixes 17 | 18 | * typos ([5035d49](https://github.com/theodo/clinter/commit/5035d497ef5266cd2bc5d7629fc235a0407fc5fe)) 19 | 20 | 21 | ### Features 22 | 23 | * add support for yarn berry ([d9ec37d](https://github.com/theodo/clinter/commit/d9ec37dad89fad757835cace561785474256f60a)) 24 | * **lint:** add restrict-template-expressions ([fd7c9ce](https://github.com/theodo/clinter/commit/fd7c9cebc8155e7fd5f9771e5a783fd2d51d608b)) 25 | * **typescript-config:** add real restriction for boolean expressions ([356872e](https://github.com/theodo/clinter/commit/356872e9494d5bc1c0d92c21907bc93d2fa62924)) 26 | 27 | # [1.15.0](https://github.com/theodo/clinter/compare/v1.14.1...v1.15.0) (2021-07-19) 28 | 29 | 30 | ### Bug Fixes 31 | 32 | * **paths:** fix ts path mapping ([e5597e5](https://github.com/theodo/clinter/commit/e5597e5f8037855c1078f4270ca3f59b467daadd)) 33 | 34 | 35 | ### Features 36 | 37 | * **deps:** allow skip deps upgrade ([161bc89](https://github.com/theodo/clinter/commit/161bc8940808d5f0972941361fb270e5257a9a8f)) 38 | 39 | ## [1.14.1](https://github.com/theodo/clinter/compare/v1.14.0...v1.14.1) (2021-07-18) 40 | 41 | 42 | ### Bug Fixes 43 | 44 | * **deps:** fix audit deps ([caf7c59](https://github.com/theodo/clinter/commit/caf7c5912e2ee0b0bb150ec4b5869fa3bb675f60)) 45 | * **ts:** fix ts strict checks ([57be5df](https://github.com/theodo/clinter/commit/57be5dfce4af03cf480951aa69b90c33b786ee76)) 46 | * **types:** fix ts types ([cd37d37](https://github.com/theodo/clinter/commit/cd37d3790328956b321da02f8a85a48b51b3c32c)) 47 | 48 | # [1.14.0](https://github.com/theodo/clinter/compare/v1.13.1...v1.14.0) (2021-02-23) 49 | 50 | 51 | ### Features 52 | 53 | * add tsconfig checker ([487974d](https://github.com/theodo/clinter/commit/487974dc2cb570a484d2d50d3a7cbbe7f000e432)) 54 | * check strict null checks ([5e58812](https://github.com/theodo/clinter/commit/5e5881263a3255a50b42a6b237dc366fc00988bc)) 55 | * clean prettier configs ([3c249ac](https://github.com/theodo/clinter/commit/3c249ac4ebb322f8834758c8581c27b42426570b)) 56 | * **prettier:** adapt prettier conf for eslint-plugin-prettier ([e263c6d](https://github.com/theodo/clinter/commit/e263c6dac0725bcaf38839d93ea8f1c78d2ea8c3)) 57 | 58 | ## [1.13.1](https://github.com/theodo/clinter/compare/v1.13.0...v1.13.1) (2020-12-29) 59 | 60 | 61 | ### Bug Fixes 62 | 63 | * ignore workspaces warning with yarn ([85f6394](https://github.com/theodo/clinter/commit/85f6394015f8530147afa98c4c7787f9abcbbdbe)) 64 | 65 | # [1.13.0](https://github.com/theodo/clinter/compare/v1.12.0...v1.13.0) (2020-12-19) 66 | 67 | 68 | ### Features 69 | 70 | * move TS prettier to override ([8738837](https://github.com/theodo/clinter/commit/8738837223b1895251f641054ae6dd155fb85c39)) 71 | * remove complexity rule ([57f3aa1](https://github.com/theodo/clinter/commit/57f3aa15c86d691f49fbc2c5be16a0ba9a21d7c8)) 72 | 73 | # [1.12.0](https://github.com/theodo/clinter/compare/v1.11.0...v1.12.0) (2020-11-17) 74 | 75 | 76 | ### Bug Fixes 77 | 78 | * remove clinter from CI ([2ade34f](https://github.com/theodo/clinter/commit/2ade34f02a2efccff4134850990219b9ccc19c17)) 79 | 80 | 81 | ### Features 82 | 83 | * improve readme to account for migration ([f0e134a](https://github.com/theodo/clinter/commit/f0e134a362eb6e62e654e61ef599eaf95d31773d)) 84 | 85 | # [1.11.0](https://github.com/theodo/clinter/compare/v1.10.0...v1.11.0) (2020-11-11) 86 | 87 | 88 | ### Features 89 | 90 | * add auto mode for cli ([1c9289d](https://github.com/theodo/clinter/commit/1c9289dc8e0405dc2bd3d21c76501606c158fd20)) 91 | * add TS rules ([16538e7](https://github.com/theodo/clinter/commit/16538e7e7ae5b6b13a0e171e44ee2e2d245e56c5)) 92 | 93 | # [1.10.0](https://github.com/theodo/clinter/compare/v1.9.1...v1.10.0) (2020-11-06) 94 | 95 | 96 | ### Bug Fixes 97 | 98 | * add early return in disable only mode ([0a19e5c](https://github.com/theodo/clinter/commit/0a19e5c159ee3066338d827f64c5998a285d960c)) 99 | * replace help by description ([0e946a3](https://github.com/theodo/clinter/commit/0e946a382152844abac7b2e68f6404af243a31b6)) 100 | * **ts:** fix no-shadow error for enums with TS4 ([f4440e7](https://github.com/theodo/clinter/commit/f4440e79d213b440ad85189a5f1900ced0fbbbfd)) 101 | 102 | 103 | ### Features 104 | 105 | * add disableError arg input ([0ad5efb](https://github.com/theodo/clinter/commit/0ad5efb32ab8ca8844dfe2a9facb9929e4552228)) 106 | * upgrade performance migration ([a8d8171](https://github.com/theodo/clinter/commit/a8d8171442810540f72995932340a39da10feaef)) 107 | * **eslint:** force linebreak before return ([#26](https://github.com/theodo/clinter/issues/26)) ([b1539d9](https://github.com/theodo/clinter/commit/b1539d9957b07fb41d181fe805ffef9b2d2f74ea)) 108 | 109 | ## [1.9.1](https://github.com/theodo/clinter/compare/v1.9.0...v1.9.1) (2020-10-26) 110 | 111 | 112 | ### Bug Fixes 113 | 114 | * fix deps ([a933d2f](https://github.com/theodo/clinter/commit/a933d2f567f58f4eda8b91c8dd8274b7416d0b15)) 115 | 116 | # [1.9.0](https://github.com/theodo/clinter/compare/v1.8.1...v1.9.0) (2020-10-26) 117 | 118 | 119 | ### Features 120 | 121 | * clean generated eslint config ([8d1dff1](https://github.com/theodo/clinter/commit/8d1dff1144736195fbc672565883542fc162bd65)) 122 | * **eslint:** add package json config support ([bb38337](https://github.com/theodo/clinter/commit/bb38337b93a3a3a036ce3ce301acc5620ab00805)) 123 | * **ts:** add ts clean config ([c4c20b2](https://github.com/theodo/clinter/commit/c4c20b232b21f619530d66d754e32e44f451c267)) 124 | * **ts:** clean typescript config ([f0de9d8](https://github.com/theodo/clinter/commit/f0de9d8dc8c54e2517c8a4ec2934a1e33bfd82fe)) 125 | * **ts:** move ts specific rules to overrides category ([304deae](https://github.com/theodo/clinter/commit/304deae4498ccb4f2bfb5aa6d3667529db93472e)) 126 | 127 | ## [1.8.1](https://github.com/theodo/clinter/compare/v1.8.0...v1.8.1) (2020-10-11) 128 | 129 | 130 | ### Bug Fixes 131 | 132 | * **deps:** add eslint dep ([2351f8c](https://github.com/theodo/clinter/commit/2351f8cb436f40433eb313e68b27bae30558f135)) 133 | 134 | # [1.8.0](https://github.com/theodo/clinter/compare/v1.7.0...v1.8.0) (2020-10-11) 135 | 136 | 137 | ### Features 138 | 139 | * **migration:** add migration results ([a16afa9](https://github.com/theodo/clinter/commit/a16afa9401c1179284194a8cfdefd85a0e05a412)) 140 | * **migration:** add option to insert disable comments ([716b106](https://github.com/theodo/clinter/commit/716b10604e8f1c8d374a7cc96bbdef3914741fd9)) 141 | * add error hen using wrong node version ([5b93da2](https://github.com/theodo/clinter/commit/5b93da2910628076120447ae9a327e5cfdec87c2)) 142 | * **react:** add accessibility rules ([807592e](https://github.com/theodo/clinter/commit/807592e480d7a6d19cfad5c4a2c9e23bfb192a8e)) 143 | 144 | # [1.7.0](https://github.com/theodo/clinter/compare/v1.6.0...v1.7.0) (2020-09-03) 145 | 146 | 147 | ### Features 148 | 149 | * **log:** improve logger ([2586d5d](https://github.com/theodo/clinter/commit/2586d5d447006a890db5d362d1af2fd138bb69ac)) 150 | 151 | # [1.6.0](https://github.com/theodo/clinter/compare/v1.5.1...v1.6.0) (2020-09-02) 152 | 153 | 154 | ### Features 155 | 156 | * add auto option ([cd53a2a](https://github.com/theodo/clinter/commit/cd53a2a45be06a86ef45a238e6315af103ea31a7)) 157 | * add project config inference ([f784039](https://github.com/theodo/clinter/commit/f7840392cfbc74078e9fbccf09c5450c2f62a82b)) 158 | 159 | ## [1.5.1](https://github.com/theodo/clinter/compare/v1.5.0...v1.5.1) (2020-09-01) 160 | 161 | 162 | ### Bug Fixes 163 | 164 | * **deps:** add latest tag to deps ([0e56494](https://github.com/theodo/clinter/commit/0e56494c68e4c512f1399e1b1a22a1705ecc0f9b)) 165 | 166 | # [1.5.0](https://github.com/theodo/clinter/compare/v1.4.0...v1.5.0) (2020-08-11) 167 | 168 | 169 | ### Bug Fixes 170 | 171 | * fix release ([ab3a24e](https://github.com/theodo/clinter/commit/ab3a24ebb541ca0106ce8c1d84e34f600c3757df)) 172 | * fix release process ([15df27a](https://github.com/theodo/clinter/commit/15df27ad3527f9bebf7838eb217d8209a42ccc16)) 173 | * **eslint-base:** add default objects ([8551c82](https://github.com/theodo/clinter/commit/8551c8210e022c2ae8faff8e623e61f14a8fcd23)) 174 | * **js-config:** fix write support for js configs ([#7](https://github.com/theodo/clinter/issues/7)) ([03a1a95](https://github.com/theodo/clinter/commit/03a1a950fee7d123b9fa95ece813c24d656ffd43)) 175 | * typo in README.md ([077da64](https://github.com/theodo/clinter/commit/077da648b76ca7abc24fc0cb149f025e382f7c33)) 176 | 177 | 178 | ### Features 179 | 180 | * **test:** add e2e test support ([f614e5b](https://github.com/theodo/clinter/commit/f614e5bc5b6f202fe0796248636c1b603eca88f9)) 181 | 182 | # [1.4.0](https://github.com/theodo/clinter/compare/v1.3.0...v1.4.0) (2020-07-04) 183 | 184 | 185 | ### Bug Fixes 186 | 187 | * **react:** fix react config by using react app ([d7fa825](https://github.com/theodo/clinter/commit/d7fa825649b225b5a71696bb1b8860f28f09b874)) 188 | 189 | 190 | ### Features 191 | 192 | * **jest:** add jest eslint dependencies ([8866371](https://github.com/theodo/clinter/commit/886637114001b33485413ae3d7eed700e0b5549f)) 193 | 194 | # [1.3.0](https://github.com/theodo/clinter/compare/v1.2.0...v1.3.0) (2020-07-04) 195 | 196 | 197 | ### Features 198 | 199 | * **git:** link git repo to package ([63ef4a3](https://github.com/theodo/clinter/commit/63ef4a3dc33ac8ff76633c8dfa50ba6e0354ce0c)) 200 | 201 | # [1.2.0](https://github.com/theodo/eslint-config-generator/compare/v1.1.0...v1.2.0) (2020-07-04) 202 | 203 | 204 | ### Features 205 | 206 | * **deps:** install required deps when generating config ([ae0ff00](https://github.com/theodo/eslint-config-generator/commit/ae0ff00ce20c65aeb4477e0f6e51cd0e30377dfc)) 207 | 208 | # [1.1.0](https://github.com/theodo/eslint-config-generator/compare/v1.0.1...v1.1.0) (2020-07-02) 209 | 210 | 211 | ### Features 212 | 213 | * **test:** add jest ([18c3cc5](https://github.com/theodo/eslint-config-generator/commit/18c3cc576b9034a6aa5265af69adaaadaa919d36)) 214 | 215 | ## [1.0.1](https://github.com/theodo/eslint-config-generator/compare/v1.0.0...v1.0.1) (2020-07-02) 216 | 217 | 218 | ### Bug Fixes 219 | 220 | * **release:** fix npm release ([ddc5fbf](https://github.com/theodo/eslint-config-generator/commit/ddc5fbf5a8e38ccb4a643e816b3600872ff826c2)) 221 | 222 | # 1.0.0 (2020-07-02) 223 | 224 | 225 | ### Features 226 | 227 | * **name:** name tool clinter ([26cc204](https://github.com/theodo/eslint-config-generator/commit/26cc204e66c9081eac32cd88ebb51301c4d1b161)) 228 | * **release:** add release config ([52c4946](https://github.com/theodo/eslint-config-generator/commit/52c494650861b8e1fdc5eebc570cec9ff13387d3)) 229 | * **upgrader:** add upgrade support ([ffd52d4](https://github.com/theodo/eslint-config-generator/commit/ffd52d49f3dc1e212a46b7138a6a5149d62c4606)) 230 | * **vue:** add vue eslint support ([4393cce](https://github.com/theodo/eslint-config-generator/commit/4393ccef90e7cbf9b0da65cb028c4f5b0295f2ef)) 231 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clinter 2 | 3 | CLI tool to generate or upgrade linter configurations. 4 | 5 | # Motivation 6 | 7 | Every time we start a new project or join an existing one, it is always struggling to create the perfect linter configuration for the project or upgrade the existing one to suit our needs. Another major problem we encounter is how to share configurations between different projects while still acknowledging the differences in tools and frameworks used. This tool attempts to solve these issues by providing an easily extendible and shareable source of truth for all projects. 8 | 9 | # Install 10 | 11 | ## Prequesites 12 | 13 | - NodeJS v12.0 or later 14 | 15 | ## Installation Process 16 | 17 | ```bash 18 | # Using Yarn 19 | yarn global add clinter 20 | 21 | cd my_project/ 22 | yarn clinter 23 | 24 | # Using npm 25 | npm i clinter -g 26 | 27 | cd my_project/ 28 | clinter 29 | 30 | # Using Pnpm 31 | cd my_project/ 32 | pnpx clinter 33 | ``` 34 | 35 | # Upgrade or generate the latest recommended ESLint configuration for your project 36 | 37 | ## Upgrading or generating a ESLint configuration within an existing project 38 | 39 | - Navigate inside your project directory where your ESLint configuration lives or where it needs to be (if you have or want multiple ESLint configurations in subdirectories, clinter will need to be run in each of the directories) 40 | - Run Clinter by running `clinter` in the terminal 41 | - Choose the `automatic` option and hit `enter`. This option tells Clinter to infer what frameworks and languages the project is using and generate the best matching ESLint configuration. Clinter will also infer whether an ESLint configuration already exists and use it as a base to add the recommended rules. 42 | - The next option checks whether Clinter needs to insert ignore comments in your code to silence the ESLint errors. This can be done later. Leave this option to its default value `No` and hit `enter`. 43 | 44 | Clinter will then update the existing eslint configuration where it was previously defined or generate one if none was found. It will also install and update all required dependencies for the newly updated or generated configuration. 45 | 46 | - After Clinter is run, it is recommended to fix all ESLint fixable errors by using ESLint's fix option on the whole project: `eslint --fix` 47 | - Check the number of errors reported by ESLint. If the work needed to fix these errors seems too great, you may use Clinter's `disable-errors` option to temporarily disable these errors line by line. This gives the option to progressively fix the ESLint errors while still having the latest recommended ESLint configuration on the project. Check the Migration section for more information. 48 | 49 | ## Generating an ESLint for a new project manually 50 | 51 | Some use cases require to have the user to manually select which type of project he will be working on. 52 | 53 | - Navigate inside your project directory where your ESLint configuration should live (if you need multiple ESLint configurations in different subdirectories, clinter will need to be run in each of the directories) 54 | - Run Clinter by running `clinter` in the terminal 55 | - Choose the `manual` option and hit `enter`. You will now have a series of questions to answer to inform Clinter of what type of project you are working on. 56 | 57 | Clinter will then generate a ESLint configuration matching the provided answers. It will also install and update all required dependencies for the newly updated or generated configuration. 58 | 59 | # Migration 60 | 61 | ## Migrating a codebase to a new ESLint configuration 62 | 63 | Adding ESLint to a project can sometimes be painful as it oftentimes requires developers to make a compromise between the ESLint rules they want to add and their cost in terms of code needed to align the codebase to the new standards. This is also true for projects that already have an ESLint configuration and that wish adding a new set of rules to their project. 64 | 65 | Clinter aims to provide a clear strategy on how to easily and progressively migrate an existing codebase to the new rules. 66 | 67 | - Add the new ESLint rules to your project configuration (using Clinter or manually by selecting the rules and adding them to the project's ESLint configuration) 68 | - Run a ESLint pass on your project. You should see a number of errors because of the newly added rules or configuration. 69 | - Run Clinter with the `disable-errors` option in the project: `clinter --disable-errors`. This command will tell Clinter to add `// eslint-disable-next-line ` lines inside your codebase where errors are detected. 70 | - Run a ESLint pass again to check if all errors are silenced. 71 | 72 | It is now up to the project's developers to progressively align the codebase to the new ESLint rules. This can be done by continuously removing the ignore comments and fixing the underlying issues when coming across them. 73 | 74 | ## Monitoring the status of the migration 75 | 76 | The migration of the project depends on the number of remaining ESLint ignore comments within the code. To easily monitor these, this CLI utility parses the entire project to find and report them: [https://github.com/CorentinDoue/eslint-disabled-stats](https://github.com/CorentinDoue/eslint-disabled-stats). 77 | 78 | # Details 79 | 80 | ## Options 81 | 82 | ### --path 83 | 84 | Boolean: Path of the directory where clinter should be run. 85 | 86 | Defaults to the path where the Clinter is run 87 | 88 | ### --auto 89 | 90 | Boolean: Option to let Clinter automatically infer the ESLint's configuration generator settings. If false, Clinter will ask the option from the user. 91 | 92 | Defaults to false. 93 | 94 | ### --disable-errors 95 | 96 | Boolean: If true, run Clinter in disable errors mode. Clinter will run ESLint on all files of the project and add `// eslint-disable-next-line ` line comments where ESLint reports an error. 97 | 98 | Defaults to false. 99 | 100 | ## Clinter Mode 101 | 102 | Clinter supports generating an ESLint configuration from scratch or upgrading an existing configuration. 103 | 104 | - Generator Mode: Generate ESLint config file from scratch 105 | - Upgrade Mode: Parse the eslint configuration file that sits within the directory and override it using the new desired configuration. 106 | 107 | Upgrade mode will try and find a valid eslint configuration within the current directory. Supported configurations are: 108 | 109 | - Eslint configuration in `.eslintrc` file 110 | - Eslint configuration in `.eslintrc.json` file 111 | - ESLint configuration in `package.json` file 112 | 113 | Once a new configuration is generated, Clinter will override your existing configuration with the new one using a custom merge system. We recommend saving your existing configuration before using clinter in upgrade mode. 114 | 115 | # Supported configurations 116 | 117 | For now, clinter only supports generating and upgrading ESLint configuration files. 118 | 119 | ## Supported languages and frameworks: 120 | 121 | - JavaScript & TypeScript 122 | - React & Vue 123 | - Browser and/or Node based projects 124 | - Jest Testing Framework 125 | - Prettier 126 | -------------------------------------------------------------------------------- /jest-e2e.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "e2e", 3 | displayName: "E2E Tests", 4 | roots: ["/src/e2e/"], 5 | preset: "ts-jest", 6 | testEnvironment: "node", 7 | setupFilesAfterEnv: ["./jest-e2e.setup.js"], 8 | moduleNameMapper: { 9 | "^generator/(.*)$": "/src/generator/$1", 10 | "^e2e/(.*)$": "/src/e2e/$1", 11 | "^dependencies/(.*)$": "/src/dependencies/$1", 12 | "^writer/(.*)$": "/src/writer/$1", 13 | "^parser/(.*)$": "/src/parser/$1", 14 | "^logger/(.*)$": "/src/logger/$1", 15 | "^utils/(.*)$": "/src/utils/$1", 16 | "^types": "/src/types.ts", 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /jest-e2e.setup.js: -------------------------------------------------------------------------------- 1 | jest.setTimeout(60000); 2 | -------------------------------------------------------------------------------- /jest-unit.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: "unit", 3 | displayName: "Unit Tests", 4 | roots: ["/src"], 5 | modulePathIgnorePatterns: ["/src/e2e/"], 6 | preset: "ts-jest", 7 | testEnvironment: "node", 8 | moduleNameMapper: { 9 | "^generator/(.*)$": "/src/generator/$1", 10 | "^e2e/(.*)$": "/src/e2e/$1", 11 | "^dependencies/(.*)$": "/src/dependencies/$1", 12 | "^writer/(.*)$": "/src/writer/$1", 13 | "^parser/(.*)$": "/src/parser/$1", 14 | "^logger/(.*)$": "/src/logger/$1", 15 | "^utils/(.*)$": "/src/utils/$1", 16 | "^types": "/src/types.ts", 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | projects: ["/jest-unit.config.js", "/jest-e2e.config.js"], 3 | }; 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clinter", 3 | "version": "1.17.0", 4 | "description": "A cli tool to quickly generate eslint configurations", 5 | "main": "build/index.js", 6 | "scripts": { 7 | "lint": "eslint ./src", 8 | "test": "jest", 9 | "test:unit": "jest --projects jest-unit.config.js", 10 | "test:e2e": "yarn build && jest --projects jest-e2e.config.js", 11 | "test:e2e:ci": "jest --projects jest-e2e.config.js", 12 | "build": "rm -rf ./build && ttsc", 13 | "release": "yarn build && semantic-release" 14 | }, 15 | "files": [ 16 | "build", 17 | "CHANGELOG.md", 18 | "package.json", 19 | "yarn.lock" 20 | ], 21 | "bin": { 22 | "clinter": "./build/index.js" 23 | }, 24 | "keywords": [ 25 | "eslint", 26 | "prettier", 27 | "linter", 28 | "formatter" 29 | ], 30 | "author": "Francois Hendriks", 31 | "license": "ISC", 32 | "devDependencies": { 33 | "@commitlint/cli": "^12.1.4", 34 | "@commitlint/config-conventional": "^12.1.4", 35 | "@schemastore/package": "^0.0.6", 36 | "@semantic-release/changelog": "^5.0.1", 37 | "@semantic-release/git": "^9.0.0", 38 | "@semantic-release/release-notes-generator": "^9.0.3", 39 | "@types/child-process-promise": "^2.2.2", 40 | "@types/eslint": "^7.28.0", 41 | "@types/jest": "^26.0.24", 42 | "@types/prettier": "^2.3.2", 43 | "@types/signale": "^1.4.2", 44 | "@types/inquirer": "^7.3.3", 45 | "@types/uuid": "^8.3.1", 46 | "@types/workerpool": "^6.0.1", 47 | "@types/yargs": "^17.0.2", 48 | "@typescript-eslint/eslint-plugin": "^4.28.3", 49 | "@typescript-eslint/parser": "^4.28.3", 50 | "eslint-config-prettier": "^8.3.0", 51 | "eslint-plugin-import": "^2.23.4", 52 | "eslint-plugin-prettier": "^3.4.0", 53 | "jest": "^27.0.6", 54 | "prettier": "^2.3.2", 55 | "semantic-release": "^17.4.4", 56 | "ts-jest": "^27.0.3", 57 | "ttypescript": "^1.5.15", 58 | "typescript": "^4.3.5", 59 | "typescript-transform-paths": "^3.1.0", 60 | "uuid": "^8.3.2" 61 | }, 62 | "dependencies": { 63 | "boxen": "^5.0.1", 64 | "child-process-promise": "^2.2.1", 65 | "comment-json": "^4.1.0", 66 | "eslint": "^7.31.0", 67 | "inquirer": "^8.1.2", 68 | "signale": "^1.4.0", 69 | "workerpool": "^6.1.5", 70 | "yargs": "^17.0.1" 71 | }, 72 | "release": { 73 | "plugins": [ 74 | "@semantic-release/commit-analyzer", 75 | "@semantic-release/release-notes-generator", 76 | [ 77 | "@semantic-release/changelog", 78 | { 79 | "changelogFile": "CHANGELOG.md" 80 | } 81 | ], 82 | "@semantic-release/npm", 83 | "@semantic-release/git" 84 | ], 85 | "ci": false 86 | }, 87 | "repository": { 88 | "type": "git", 89 | "url": "ssh://git@github.com/theodo/clinter.git" 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/dependencies/dependencies.ts: -------------------------------------------------------------------------------- 1 | import { getPackageTool, PackageTool } from "parser/package-tool"; 2 | import { exec } from "child-process-promise"; 3 | 4 | function formatDependencies(dependencies: string[]): string { 5 | return dependencies.reduce((dependencyString, dependency) => `${dependencyString} ${dependency}@latest`, ""); 6 | } 7 | 8 | function installDependenciesYarn(dependencies: string[], dirpath: string): Promise { 9 | return exec(`yarn add --cwd ${dirpath} ${formatDependencies(dependencies)} -D -W`).then(() => Promise.resolve()); 10 | } 11 | 12 | function installDependenciesYarnBerry(dependencies: string[], dirpath: string): Promise { 13 | return exec(`yarn add --cwd ${dirpath} ${formatDependencies(dependencies)} -D`).then(() => Promise.resolve()); 14 | } 15 | 16 | function installDependenciesNpm(dependencies: string[], dirpath: string): Promise { 17 | return exec(`npm install --prefix ${dirpath} ${formatDependencies(dependencies)} --save-dev`).then(() => 18 | Promise.resolve() 19 | ); 20 | } 21 | 22 | function installDependenciesPnpm(dependencies: string[], dirpath: string): Promise { 23 | return exec(`pnpm add -Dw --dir ${dirpath} ${formatDependencies(dependencies)}`).then(() => Promise.resolve()); 24 | } 25 | 26 | export function installDevDependencies(dependencies: string[], dirPath: string): Promise { 27 | switch (getPackageTool(dirPath)) { 28 | case PackageTool.NPM: 29 | return installDependenciesNpm(dependencies, dirPath); 30 | 31 | case PackageTool.YARN: 32 | return installDependenciesYarn(dependencies, dirPath); 33 | 34 | case PackageTool.YARN_BERRY: 35 | return installDependenciesYarnBerry(dependencies, dirPath); 36 | 37 | case PackageTool.PNPM: 38 | return installDependenciesPnpm(dependencies, dirPath); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/e2e/generate/__snapshots__/eslint.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`ESLint Base Configuration Generator Mode should return a standard eslint configuration with no extensions: eslint-simple-config 1`] = ` 4 | Object { 5 | "extends": Array [ 6 | "eslint:recommended", 7 | ], 8 | "plugins": Array [ 9 | "import", 10 | ], 11 | "root": true, 12 | "rules": Object { 13 | "curly": Array [ 14 | "error", 15 | "all", 16 | ], 17 | "eqeqeq": Array [ 18 | "error", 19 | "smart", 20 | ], 21 | "import/no-extraneous-dependencies": Array [ 22 | "error", 23 | Object { 24 | "devDependencies": true, 25 | "optionalDependencies": false, 26 | "peerDependencies": false, 27 | }, 28 | ], 29 | "import/order": Array [ 30 | "error", 31 | Object { 32 | "groups": Array [ 33 | Array [ 34 | "external", 35 | "builtin", 36 | ], 37 | "internal", 38 | Array [ 39 | "parent", 40 | "sibling", 41 | "index", 42 | ], 43 | ], 44 | }, 45 | ], 46 | "no-shadow": Array [ 47 | "error", 48 | Object { 49 | "hoist": "all", 50 | }, 51 | ], 52 | "padding-line-between-statements": Array [ 53 | "error", 54 | Object { 55 | "blankLine": "always", 56 | "next": "return", 57 | "prev": "*", 58 | }, 59 | ], 60 | "prefer-const": "error", 61 | "sort-imports": Array [ 62 | "error", 63 | Object { 64 | "ignoreCase": true, 65 | "ignoreDeclarationSort": true, 66 | "ignoreMemberSort": false, 67 | "memberSyntaxSortOrder": Array [ 68 | "none", 69 | "all", 70 | "multiple", 71 | "single", 72 | ], 73 | }, 74 | ], 75 | }, 76 | } 77 | `; 78 | -------------------------------------------------------------------------------- /src/e2e/generate/__snapshots__/prettier.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Prettier eslint configuration Generator Mode should return a prettier eslint configuration: eslint-prettier-config 1`] = ` 4 | Object { 5 | "extends": Array [ 6 | "eslint:recommended", 7 | "plugin:prettier/recommended", 8 | ], 9 | "plugins": Array [ 10 | "import", 11 | ], 12 | "root": true, 13 | "rules": Object { 14 | "curly": Array [ 15 | "error", 16 | "all", 17 | ], 18 | "eqeqeq": Array [ 19 | "error", 20 | "smart", 21 | ], 22 | "import/no-extraneous-dependencies": Array [ 23 | "error", 24 | Object { 25 | "devDependencies": true, 26 | "optionalDependencies": false, 27 | "peerDependencies": false, 28 | }, 29 | ], 30 | "import/order": Array [ 31 | "error", 32 | Object { 33 | "groups": Array [ 34 | Array [ 35 | "external", 36 | "builtin", 37 | ], 38 | "internal", 39 | Array [ 40 | "parent", 41 | "sibling", 42 | "index", 43 | ], 44 | ], 45 | }, 46 | ], 47 | "no-shadow": Array [ 48 | "error", 49 | Object { 50 | "hoist": "all", 51 | }, 52 | ], 53 | "padding-line-between-statements": Array [ 54 | "error", 55 | Object { 56 | "blankLine": "always", 57 | "next": "return", 58 | "prev": "*", 59 | }, 60 | ], 61 | "prefer-const": "error", 62 | "sort-imports": Array [ 63 | "error", 64 | Object { 65 | "ignoreCase": true, 66 | "ignoreDeclarationSort": true, 67 | "ignoreMemberSort": false, 68 | "memberSyntaxSortOrder": Array [ 69 | "none", 70 | "all", 71 | "multiple", 72 | "single", 73 | ], 74 | }, 75 | ], 76 | }, 77 | } 78 | `; 79 | 80 | exports[`Prettier eslint configuration Generator Mode should return a prettier react eslint configuration with type checking: eslint-prettier-react-config 1`] = ` 81 | Object { 82 | "extends": Array [ 83 | "eslint:recommended", 84 | "react-app", 85 | "plugin:jsx-a11y/recommended", 86 | "plugin:prettier/recommended", 87 | ], 88 | "plugins": Array [ 89 | "import", 90 | "jsx-a11y", 91 | ], 92 | "root": true, 93 | "rules": Object { 94 | "curly": Array [ 95 | "error", 96 | "all", 97 | ], 98 | "eqeqeq": Array [ 99 | "error", 100 | "smart", 101 | ], 102 | "import/no-extraneous-dependencies": Array [ 103 | "error", 104 | Object { 105 | "devDependencies": true, 106 | "optionalDependencies": false, 107 | "peerDependencies": false, 108 | }, 109 | ], 110 | "import/order": Array [ 111 | "error", 112 | Object { 113 | "groups": Array [ 114 | Array [ 115 | "external", 116 | "builtin", 117 | ], 118 | "internal", 119 | Array [ 120 | "parent", 121 | "sibling", 122 | "index", 123 | ], 124 | ], 125 | }, 126 | ], 127 | "no-shadow": Array [ 128 | "error", 129 | Object { 130 | "hoist": "all", 131 | }, 132 | ], 133 | "padding-line-between-statements": Array [ 134 | "error", 135 | Object { 136 | "blankLine": "always", 137 | "next": "return", 138 | "prev": "*", 139 | }, 140 | ], 141 | "prefer-const": "error", 142 | "react-hooks/exhaustive-deps": "warn", 143 | "react-hooks/rules-of-hooks": "error", 144 | "react/no-string-refs": "warn", 145 | "sort-imports": Array [ 146 | "error", 147 | Object { 148 | "ignoreCase": true, 149 | "ignoreDeclarationSort": true, 150 | "ignoreMemberSort": false, 151 | "memberSyntaxSortOrder": Array [ 152 | "none", 153 | "all", 154 | "multiple", 155 | "single", 156 | ], 157 | }, 158 | ], 159 | }, 160 | "settings": Object { 161 | "react": Object { 162 | "version": "detect", 163 | }, 164 | }, 165 | } 166 | `; 167 | 168 | exports[`Prettier eslint configuration Generator Mode should return a prettier react typescript eslint configuration with type checking: eslint-prettier-ts-react-config 1`] = ` 169 | Object { 170 | "extends": Array [ 171 | "eslint:recommended", 172 | "react-app", 173 | "plugin:jsx-a11y/recommended", 174 | "plugin:prettier/recommended", 175 | ], 176 | "overrides": Array [ 177 | Object { 178 | "extends": Array [ 179 | "plugin:@typescript-eslint/recommended", 180 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 181 | "plugin:prettier/recommended", 182 | ], 183 | "files": Array [ 184 | "**/*.ts?(x)", 185 | ], 186 | "parser": "@typescript-eslint/parser", 187 | "parserOptions": Object { 188 | "project": "tsconfig.json", 189 | }, 190 | "rules": Object { 191 | "@typescript-eslint/no-shadow": "error", 192 | "@typescript-eslint/no-unnecessary-boolean-literal-compare": "error", 193 | "@typescript-eslint/no-unnecessary-condition": "error", 194 | "@typescript-eslint/no-unnecessary-type-arguments": "error", 195 | "@typescript-eslint/prefer-nullish-coalescing": "error", 196 | "@typescript-eslint/prefer-optional-chain": "error", 197 | "@typescript-eslint/prefer-string-starts-ends-with": "error", 198 | "@typescript-eslint/restrict-template-expressions": Array [ 199 | "error", 200 | Object { 201 | "allowBoolean": true, 202 | "allowNumber": true, 203 | }, 204 | ], 205 | "@typescript-eslint/strict-boolean-expressions": Array [ 206 | "error", 207 | Object { 208 | "allowNullableObject": true, 209 | "allowNumber": false, 210 | "allowString": false, 211 | }, 212 | ], 213 | "@typescript-eslint/switch-exhaustiveness-check": "error", 214 | "no-shadow": "off", 215 | }, 216 | }, 217 | ], 218 | "plugins": Array [ 219 | "import", 220 | "jsx-a11y", 221 | ], 222 | "root": true, 223 | "rules": Object { 224 | "@typescript-eslint/explicit-function-return-type": "off", 225 | "curly": Array [ 226 | "error", 227 | "all", 228 | ], 229 | "eqeqeq": Array [ 230 | "error", 231 | "smart", 232 | ], 233 | "import/no-extraneous-dependencies": Array [ 234 | "error", 235 | Object { 236 | "devDependencies": true, 237 | "optionalDependencies": false, 238 | "peerDependencies": false, 239 | }, 240 | ], 241 | "import/order": Array [ 242 | "error", 243 | Object { 244 | "groups": Array [ 245 | Array [ 246 | "external", 247 | "builtin", 248 | ], 249 | "internal", 250 | Array [ 251 | "parent", 252 | "sibling", 253 | "index", 254 | ], 255 | ], 256 | }, 257 | ], 258 | "no-shadow": Array [ 259 | "error", 260 | Object { 261 | "hoist": "all", 262 | }, 263 | ], 264 | "padding-line-between-statements": Array [ 265 | "error", 266 | Object { 267 | "blankLine": "always", 268 | "next": "return", 269 | "prev": "*", 270 | }, 271 | ], 272 | "prefer-const": "error", 273 | "react-hooks/exhaustive-deps": "warn", 274 | "react-hooks/rules-of-hooks": "error", 275 | "react/no-string-refs": "warn", 276 | "sort-imports": Array [ 277 | "error", 278 | Object { 279 | "ignoreCase": true, 280 | "ignoreDeclarationSort": true, 281 | "ignoreMemberSort": false, 282 | "memberSyntaxSortOrder": Array [ 283 | "none", 284 | "all", 285 | "multiple", 286 | "single", 287 | ], 288 | }, 289 | ], 290 | }, 291 | "settings": Object { 292 | "react": Object { 293 | "version": "detect", 294 | }, 295 | }, 296 | } 297 | `; 298 | 299 | exports[`Prettier eslint configuration Generator Mode should return a prettier typescript eslint configuration with type checking: eslint-prettier-ts-config 1`] = ` 300 | Object { 301 | "extends": Array [ 302 | "eslint:recommended", 303 | "plugin:prettier/recommended", 304 | ], 305 | "overrides": Array [ 306 | Object { 307 | "extends": Array [ 308 | "plugin:@typescript-eslint/recommended", 309 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 310 | "plugin:prettier/recommended", 311 | ], 312 | "files": Array [ 313 | "**/*.ts?(x)", 314 | ], 315 | "parser": "@typescript-eslint/parser", 316 | "parserOptions": Object { 317 | "project": "tsconfig.json", 318 | }, 319 | "rules": Object { 320 | "@typescript-eslint/no-shadow": "error", 321 | "@typescript-eslint/no-unnecessary-boolean-literal-compare": "error", 322 | "@typescript-eslint/no-unnecessary-condition": "error", 323 | "@typescript-eslint/no-unnecessary-type-arguments": "error", 324 | "@typescript-eslint/prefer-nullish-coalescing": "error", 325 | "@typescript-eslint/prefer-optional-chain": "error", 326 | "@typescript-eslint/prefer-string-starts-ends-with": "error", 327 | "@typescript-eslint/restrict-template-expressions": Array [ 328 | "error", 329 | Object { 330 | "allowBoolean": true, 331 | "allowNumber": true, 332 | }, 333 | ], 334 | "@typescript-eslint/strict-boolean-expressions": Array [ 335 | "error", 336 | Object { 337 | "allowNullableObject": true, 338 | "allowNumber": false, 339 | "allowString": false, 340 | }, 341 | ], 342 | "@typescript-eslint/switch-exhaustiveness-check": "error", 343 | "no-shadow": "off", 344 | }, 345 | }, 346 | ], 347 | "plugins": Array [ 348 | "import", 349 | ], 350 | "root": true, 351 | "rules": Object { 352 | "curly": Array [ 353 | "error", 354 | "all", 355 | ], 356 | "eqeqeq": Array [ 357 | "error", 358 | "smart", 359 | ], 360 | "import/no-extraneous-dependencies": Array [ 361 | "error", 362 | Object { 363 | "devDependencies": true, 364 | "optionalDependencies": false, 365 | "peerDependencies": false, 366 | }, 367 | ], 368 | "import/order": Array [ 369 | "error", 370 | Object { 371 | "groups": Array [ 372 | Array [ 373 | "external", 374 | "builtin", 375 | ], 376 | "internal", 377 | Array [ 378 | "parent", 379 | "sibling", 380 | "index", 381 | ], 382 | ], 383 | }, 384 | ], 385 | "no-shadow": Array [ 386 | "error", 387 | Object { 388 | "hoist": "all", 389 | }, 390 | ], 391 | "padding-line-between-statements": Array [ 392 | "error", 393 | Object { 394 | "blankLine": "always", 395 | "next": "return", 396 | "prev": "*", 397 | }, 398 | ], 399 | "prefer-const": "error", 400 | "sort-imports": Array [ 401 | "error", 402 | Object { 403 | "ignoreCase": true, 404 | "ignoreDeclarationSort": true, 405 | "ignoreMemberSort": false, 406 | "memberSyntaxSortOrder": Array [ 407 | "none", 408 | "all", 409 | "multiple", 410 | "single", 411 | ], 412 | }, 413 | ], 414 | }, 415 | } 416 | `; 417 | 418 | exports[`Prettier eslint configuration Generator Mode should return a prettier vue eslint configuration with type checking: eslint-prettier-vue-config 1`] = ` 419 | Object { 420 | "extends": Array [ 421 | "eslint:recommended", 422 | "plugin:vue/essential", 423 | "plugin:prettier/recommended", 424 | ], 425 | "parserOptions": Object { 426 | "parser": "babel-eslint", 427 | }, 428 | "plugins": Array [ 429 | "import", 430 | ], 431 | "root": true, 432 | "rules": Object { 433 | "curly": Array [ 434 | "error", 435 | "all", 436 | ], 437 | "eqeqeq": Array [ 438 | "error", 439 | "smart", 440 | ], 441 | "import/no-extraneous-dependencies": Array [ 442 | "error", 443 | Object { 444 | "devDependencies": true, 445 | "optionalDependencies": false, 446 | "peerDependencies": false, 447 | }, 448 | ], 449 | "import/order": Array [ 450 | "error", 451 | Object { 452 | "groups": Array [ 453 | Array [ 454 | "external", 455 | "builtin", 456 | ], 457 | "internal", 458 | Array [ 459 | "parent", 460 | "sibling", 461 | "index", 462 | ], 463 | ], 464 | }, 465 | ], 466 | "no-console": "off", 467 | "no-debugger": "off", 468 | "no-shadow": Array [ 469 | "error", 470 | Object { 471 | "hoist": "all", 472 | }, 473 | ], 474 | "padding-line-between-statements": Array [ 475 | "error", 476 | Object { 477 | "blankLine": "always", 478 | "next": "return", 479 | "prev": "*", 480 | }, 481 | ], 482 | "prefer-const": "error", 483 | "sort-imports": Array [ 484 | "error", 485 | Object { 486 | "ignoreCase": true, 487 | "ignoreDeclarationSort": true, 488 | "ignoreMemberSort": false, 489 | "memberSyntaxSortOrder": Array [ 490 | "none", 491 | "all", 492 | "multiple", 493 | "single", 494 | ], 495 | }, 496 | ], 497 | }, 498 | } 499 | `; 500 | 501 | exports[`Prettier eslint configuration Generator Mode should return a prettier vue typescript eslint configuration with type checking: eslint-prettier-ts-vue-config 1`] = ` 502 | Object { 503 | "extends": Array [ 504 | "eslint:recommended", 505 | "plugin:vue/essential", 506 | "plugin:prettier/recommended", 507 | ], 508 | "overrides": Array [ 509 | Object { 510 | "extends": Array [ 511 | "plugin:@typescript-eslint/recommended", 512 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 513 | "plugin:prettier/recommended", 514 | ], 515 | "files": Array [ 516 | "**/*.ts?(x)", 517 | ], 518 | "parser": "@typescript-eslint/parser", 519 | "parserOptions": Object { 520 | "project": "tsconfig.json", 521 | }, 522 | "rules": Object { 523 | "@typescript-eslint/no-shadow": "error", 524 | "@typescript-eslint/no-unnecessary-boolean-literal-compare": "error", 525 | "@typescript-eslint/no-unnecessary-condition": "error", 526 | "@typescript-eslint/no-unnecessary-type-arguments": "error", 527 | "@typescript-eslint/prefer-nullish-coalescing": "error", 528 | "@typescript-eslint/prefer-optional-chain": "error", 529 | "@typescript-eslint/prefer-string-starts-ends-with": "error", 530 | "@typescript-eslint/restrict-template-expressions": Array [ 531 | "error", 532 | Object { 533 | "allowBoolean": true, 534 | "allowNumber": true, 535 | }, 536 | ], 537 | "@typescript-eslint/strict-boolean-expressions": Array [ 538 | "error", 539 | Object { 540 | "allowNullableObject": true, 541 | "allowNumber": false, 542 | "allowString": false, 543 | }, 544 | ], 545 | "@typescript-eslint/switch-exhaustiveness-check": "error", 546 | "no-shadow": "off", 547 | }, 548 | }, 549 | ], 550 | "parserOptions": Object { 551 | "parser": "babel-eslint", 552 | }, 553 | "plugins": Array [ 554 | "import", 555 | ], 556 | "root": true, 557 | "rules": Object { 558 | "curly": Array [ 559 | "error", 560 | "all", 561 | ], 562 | "eqeqeq": Array [ 563 | "error", 564 | "smart", 565 | ], 566 | "import/no-extraneous-dependencies": Array [ 567 | "error", 568 | Object { 569 | "devDependencies": true, 570 | "optionalDependencies": false, 571 | "peerDependencies": false, 572 | }, 573 | ], 574 | "import/order": Array [ 575 | "error", 576 | Object { 577 | "groups": Array [ 578 | Array [ 579 | "external", 580 | "builtin", 581 | ], 582 | "internal", 583 | Array [ 584 | "parent", 585 | "sibling", 586 | "index", 587 | ], 588 | ], 589 | }, 590 | ], 591 | "no-console": "off", 592 | "no-debugger": "off", 593 | "no-shadow": Array [ 594 | "error", 595 | Object { 596 | "hoist": "all", 597 | }, 598 | ], 599 | "padding-line-between-statements": Array [ 600 | "error", 601 | Object { 602 | "blankLine": "always", 603 | "next": "return", 604 | "prev": "*", 605 | }, 606 | ], 607 | "prefer-const": "error", 608 | "sort-imports": Array [ 609 | "error", 610 | Object { 611 | "ignoreCase": true, 612 | "ignoreDeclarationSort": true, 613 | "ignoreMemberSort": false, 614 | "memberSyntaxSortOrder": Array [ 615 | "none", 616 | "all", 617 | "multiple", 618 | "single", 619 | ], 620 | }, 621 | ], 622 | }, 623 | } 624 | `; 625 | -------------------------------------------------------------------------------- /src/e2e/generate/__snapshots__/react.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`React configuration Generator Mode should return a react eslint configuration: eslint-react-config 1`] = ` 4 | Object { 5 | "extends": Array [ 6 | "eslint:recommended", 7 | "react-app", 8 | "plugin:jsx-a11y/recommended", 9 | ], 10 | "plugins": Array [ 11 | "import", 12 | "jsx-a11y", 13 | ], 14 | "root": true, 15 | "rules": Object { 16 | "curly": Array [ 17 | "error", 18 | "all", 19 | ], 20 | "eqeqeq": Array [ 21 | "error", 22 | "smart", 23 | ], 24 | "import/no-extraneous-dependencies": Array [ 25 | "error", 26 | Object { 27 | "devDependencies": true, 28 | "optionalDependencies": false, 29 | "peerDependencies": false, 30 | }, 31 | ], 32 | "import/order": Array [ 33 | "error", 34 | Object { 35 | "groups": Array [ 36 | Array [ 37 | "external", 38 | "builtin", 39 | ], 40 | "internal", 41 | Array [ 42 | "parent", 43 | "sibling", 44 | "index", 45 | ], 46 | ], 47 | }, 48 | ], 49 | "no-shadow": Array [ 50 | "error", 51 | Object { 52 | "hoist": "all", 53 | }, 54 | ], 55 | "padding-line-between-statements": Array [ 56 | "error", 57 | Object { 58 | "blankLine": "always", 59 | "next": "return", 60 | "prev": "*", 61 | }, 62 | ], 63 | "prefer-const": "error", 64 | "react-hooks/exhaustive-deps": "warn", 65 | "react-hooks/rules-of-hooks": "error", 66 | "react/no-string-refs": "warn", 67 | "sort-imports": Array [ 68 | "error", 69 | Object { 70 | "ignoreCase": true, 71 | "ignoreDeclarationSort": true, 72 | "ignoreMemberSort": false, 73 | "memberSyntaxSortOrder": Array [ 74 | "none", 75 | "all", 76 | "multiple", 77 | "single", 78 | ], 79 | }, 80 | ], 81 | }, 82 | "settings": Object { 83 | "react": Object { 84 | "version": "detect", 85 | }, 86 | }, 87 | } 88 | `; 89 | 90 | exports[`React configuration Generator Mode should return a react typescript eslint configuration with type checking: eslint-react-ts-config 1`] = ` 91 | Object { 92 | "extends": Array [ 93 | "eslint:recommended", 94 | "react-app", 95 | "plugin:jsx-a11y/recommended", 96 | ], 97 | "overrides": Array [ 98 | Object { 99 | "extends": Array [ 100 | "plugin:@typescript-eslint/recommended", 101 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 102 | ], 103 | "files": Array [ 104 | "**/*.ts?(x)", 105 | ], 106 | "parser": "@typescript-eslint/parser", 107 | "parserOptions": Object { 108 | "project": "tsconfig.json", 109 | }, 110 | "rules": Object { 111 | "@typescript-eslint/no-shadow": "error", 112 | "@typescript-eslint/no-unnecessary-boolean-literal-compare": "error", 113 | "@typescript-eslint/no-unnecessary-condition": "error", 114 | "@typescript-eslint/no-unnecessary-type-arguments": "error", 115 | "@typescript-eslint/prefer-nullish-coalescing": "error", 116 | "@typescript-eslint/prefer-optional-chain": "error", 117 | "@typescript-eslint/prefer-string-starts-ends-with": "error", 118 | "@typescript-eslint/restrict-template-expressions": Array [ 119 | "error", 120 | Object { 121 | "allowBoolean": true, 122 | "allowNumber": true, 123 | }, 124 | ], 125 | "@typescript-eslint/strict-boolean-expressions": Array [ 126 | "error", 127 | Object { 128 | "allowNullableObject": true, 129 | "allowNumber": false, 130 | "allowString": false, 131 | }, 132 | ], 133 | "@typescript-eslint/switch-exhaustiveness-check": "error", 134 | "no-shadow": "off", 135 | }, 136 | }, 137 | ], 138 | "plugins": Array [ 139 | "import", 140 | "jsx-a11y", 141 | ], 142 | "root": true, 143 | "rules": Object { 144 | "@typescript-eslint/explicit-function-return-type": "off", 145 | "curly": Array [ 146 | "error", 147 | "all", 148 | ], 149 | "eqeqeq": Array [ 150 | "error", 151 | "smart", 152 | ], 153 | "import/no-extraneous-dependencies": Array [ 154 | "error", 155 | Object { 156 | "devDependencies": true, 157 | "optionalDependencies": false, 158 | "peerDependencies": false, 159 | }, 160 | ], 161 | "import/order": Array [ 162 | "error", 163 | Object { 164 | "groups": Array [ 165 | Array [ 166 | "external", 167 | "builtin", 168 | ], 169 | "internal", 170 | Array [ 171 | "parent", 172 | "sibling", 173 | "index", 174 | ], 175 | ], 176 | }, 177 | ], 178 | "no-shadow": Array [ 179 | "error", 180 | Object { 181 | "hoist": "all", 182 | }, 183 | ], 184 | "padding-line-between-statements": Array [ 185 | "error", 186 | Object { 187 | "blankLine": "always", 188 | "next": "return", 189 | "prev": "*", 190 | }, 191 | ], 192 | "prefer-const": "error", 193 | "react-hooks/exhaustive-deps": "warn", 194 | "react-hooks/rules-of-hooks": "error", 195 | "react/no-string-refs": "warn", 196 | "sort-imports": Array [ 197 | "error", 198 | Object { 199 | "ignoreCase": true, 200 | "ignoreDeclarationSort": true, 201 | "ignoreMemberSort": false, 202 | "memberSyntaxSortOrder": Array [ 203 | "none", 204 | "all", 205 | "multiple", 206 | "single", 207 | ], 208 | }, 209 | ], 210 | }, 211 | "settings": Object { 212 | "react": Object { 213 | "version": "detect", 214 | }, 215 | }, 216 | } 217 | `; 218 | -------------------------------------------------------------------------------- /src/e2e/generate/__snapshots__/typescript.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`TypeScript configuration Generator Mode should return a typescript eslint configuration with no type checking: eslint-ts-no-type-checking-config 1`] = ` 4 | Object { 5 | "extends": Array [ 6 | "eslint:recommended", 7 | ], 8 | "overrides": Array [ 9 | Object { 10 | "extends": Array [ 11 | "plugin:@typescript-eslint/recommended", 12 | ], 13 | "files": Array [ 14 | "**/*.ts?(x)", 15 | ], 16 | "parser": "@typescript-eslint/parser", 17 | "parserOptions": Object { 18 | "project": "tsconfig.json", 19 | }, 20 | "rules": Object { 21 | "@typescript-eslint/no-shadow": "error", 22 | "@typescript-eslint/prefer-optional-chain": "error", 23 | "no-shadow": "off", 24 | }, 25 | }, 26 | ], 27 | "plugins": Array [ 28 | "import", 29 | ], 30 | "root": true, 31 | "rules": Object { 32 | "curly": Array [ 33 | "error", 34 | "all", 35 | ], 36 | "eqeqeq": Array [ 37 | "error", 38 | "smart", 39 | ], 40 | "import/no-extraneous-dependencies": Array [ 41 | "error", 42 | Object { 43 | "devDependencies": true, 44 | "optionalDependencies": false, 45 | "peerDependencies": false, 46 | }, 47 | ], 48 | "import/order": Array [ 49 | "error", 50 | Object { 51 | "groups": Array [ 52 | Array [ 53 | "external", 54 | "builtin", 55 | ], 56 | "internal", 57 | Array [ 58 | "parent", 59 | "sibling", 60 | "index", 61 | ], 62 | ], 63 | }, 64 | ], 65 | "no-shadow": Array [ 66 | "error", 67 | Object { 68 | "hoist": "all", 69 | }, 70 | ], 71 | "padding-line-between-statements": Array [ 72 | "error", 73 | Object { 74 | "blankLine": "always", 75 | "next": "return", 76 | "prev": "*", 77 | }, 78 | ], 79 | "prefer-const": "error", 80 | "sort-imports": Array [ 81 | "error", 82 | Object { 83 | "ignoreCase": true, 84 | "ignoreDeclarationSort": true, 85 | "ignoreMemberSort": false, 86 | "memberSyntaxSortOrder": Array [ 87 | "none", 88 | "all", 89 | "multiple", 90 | "single", 91 | ], 92 | }, 93 | ], 94 | }, 95 | } 96 | `; 97 | 98 | exports[`TypeScript configuration Generator Mode should return a typescript eslint configuration with type checking: eslint-ts-type-checking-config 1`] = ` 99 | Object { 100 | "extends": Array [ 101 | "eslint:recommended", 102 | ], 103 | "overrides": Array [ 104 | Object { 105 | "extends": Array [ 106 | "plugin:@typescript-eslint/recommended", 107 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 108 | ], 109 | "files": Array [ 110 | "**/*.ts?(x)", 111 | ], 112 | "parser": "@typescript-eslint/parser", 113 | "parserOptions": Object { 114 | "project": "tsconfig.json", 115 | }, 116 | "rules": Object { 117 | "@typescript-eslint/no-shadow": "error", 118 | "@typescript-eslint/no-unnecessary-boolean-literal-compare": "error", 119 | "@typescript-eslint/no-unnecessary-condition": "error", 120 | "@typescript-eslint/no-unnecessary-type-arguments": "error", 121 | "@typescript-eslint/prefer-nullish-coalescing": "error", 122 | "@typescript-eslint/prefer-optional-chain": "error", 123 | "@typescript-eslint/prefer-string-starts-ends-with": "error", 124 | "@typescript-eslint/restrict-template-expressions": Array [ 125 | "error", 126 | Object { 127 | "allowBoolean": true, 128 | "allowNumber": true, 129 | }, 130 | ], 131 | "@typescript-eslint/strict-boolean-expressions": Array [ 132 | "error", 133 | Object { 134 | "allowNullableObject": true, 135 | "allowNumber": false, 136 | "allowString": false, 137 | }, 138 | ], 139 | "@typescript-eslint/switch-exhaustiveness-check": "error", 140 | "no-shadow": "off", 141 | }, 142 | }, 143 | ], 144 | "plugins": Array [ 145 | "import", 146 | ], 147 | "root": true, 148 | "rules": Object { 149 | "curly": Array [ 150 | "error", 151 | "all", 152 | ], 153 | "eqeqeq": Array [ 154 | "error", 155 | "smart", 156 | ], 157 | "import/no-extraneous-dependencies": Array [ 158 | "error", 159 | Object { 160 | "devDependencies": true, 161 | "optionalDependencies": false, 162 | "peerDependencies": false, 163 | }, 164 | ], 165 | "import/order": Array [ 166 | "error", 167 | Object { 168 | "groups": Array [ 169 | Array [ 170 | "external", 171 | "builtin", 172 | ], 173 | "internal", 174 | Array [ 175 | "parent", 176 | "sibling", 177 | "index", 178 | ], 179 | ], 180 | }, 181 | ], 182 | "no-shadow": Array [ 183 | "error", 184 | Object { 185 | "hoist": "all", 186 | }, 187 | ], 188 | "padding-line-between-statements": Array [ 189 | "error", 190 | Object { 191 | "blankLine": "always", 192 | "next": "return", 193 | "prev": "*", 194 | }, 195 | ], 196 | "prefer-const": "error", 197 | "sort-imports": Array [ 198 | "error", 199 | Object { 200 | "ignoreCase": true, 201 | "ignoreDeclarationSort": true, 202 | "ignoreMemberSort": false, 203 | "memberSyntaxSortOrder": Array [ 204 | "none", 205 | "all", 206 | "multiple", 207 | "single", 208 | ], 209 | }, 210 | ], 211 | }, 212 | } 213 | `; 214 | -------------------------------------------------------------------------------- /src/e2e/generate/__snapshots__/vue.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Vue configuration Generator Mode should return a vue eslint configuration: eslint-vue-config 1`] = ` 4 | Object { 5 | "extends": Array [ 6 | "eslint:recommended", 7 | "plugin:vue/essential", 8 | ], 9 | "parserOptions": Object { 10 | "parser": "babel-eslint", 11 | }, 12 | "plugins": Array [ 13 | "import", 14 | ], 15 | "root": true, 16 | "rules": Object { 17 | "curly": Array [ 18 | "error", 19 | "all", 20 | ], 21 | "eqeqeq": Array [ 22 | "error", 23 | "smart", 24 | ], 25 | "import/no-extraneous-dependencies": Array [ 26 | "error", 27 | Object { 28 | "devDependencies": true, 29 | "optionalDependencies": false, 30 | "peerDependencies": false, 31 | }, 32 | ], 33 | "import/order": Array [ 34 | "error", 35 | Object { 36 | "groups": Array [ 37 | Array [ 38 | "external", 39 | "builtin", 40 | ], 41 | "internal", 42 | Array [ 43 | "parent", 44 | "sibling", 45 | "index", 46 | ], 47 | ], 48 | }, 49 | ], 50 | "no-console": "off", 51 | "no-debugger": "off", 52 | "no-shadow": Array [ 53 | "error", 54 | Object { 55 | "hoist": "all", 56 | }, 57 | ], 58 | "padding-line-between-statements": Array [ 59 | "error", 60 | Object { 61 | "blankLine": "always", 62 | "next": "return", 63 | "prev": "*", 64 | }, 65 | ], 66 | "prefer-const": "error", 67 | "sort-imports": Array [ 68 | "error", 69 | Object { 70 | "ignoreCase": true, 71 | "ignoreDeclarationSort": true, 72 | "ignoreMemberSort": false, 73 | "memberSyntaxSortOrder": Array [ 74 | "none", 75 | "all", 76 | "multiple", 77 | "single", 78 | ], 79 | }, 80 | ], 81 | }, 82 | } 83 | `; 84 | 85 | exports[`Vue configuration Generator Mode should return a vue typescript eslint configuration with type checking: eslint-vue-ts-config 1`] = ` 86 | Object { 87 | "extends": Array [ 88 | "eslint:recommended", 89 | "plugin:vue/essential", 90 | ], 91 | "overrides": Array [ 92 | Object { 93 | "extends": Array [ 94 | "plugin:@typescript-eslint/recommended", 95 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 96 | ], 97 | "files": Array [ 98 | "**/*.ts?(x)", 99 | ], 100 | "parser": "@typescript-eslint/parser", 101 | "parserOptions": Object { 102 | "project": "tsconfig.json", 103 | }, 104 | "rules": Object { 105 | "@typescript-eslint/no-shadow": "error", 106 | "@typescript-eslint/no-unnecessary-boolean-literal-compare": "error", 107 | "@typescript-eslint/no-unnecessary-condition": "error", 108 | "@typescript-eslint/no-unnecessary-type-arguments": "error", 109 | "@typescript-eslint/prefer-nullish-coalescing": "error", 110 | "@typescript-eslint/prefer-optional-chain": "error", 111 | "@typescript-eslint/prefer-string-starts-ends-with": "error", 112 | "@typescript-eslint/restrict-template-expressions": Array [ 113 | "error", 114 | Object { 115 | "allowBoolean": true, 116 | "allowNumber": true, 117 | }, 118 | ], 119 | "@typescript-eslint/strict-boolean-expressions": Array [ 120 | "error", 121 | Object { 122 | "allowNullableObject": true, 123 | "allowNumber": false, 124 | "allowString": false, 125 | }, 126 | ], 127 | "@typescript-eslint/switch-exhaustiveness-check": "error", 128 | "no-shadow": "off", 129 | }, 130 | }, 131 | ], 132 | "parserOptions": Object { 133 | "parser": "babel-eslint", 134 | }, 135 | "plugins": Array [ 136 | "import", 137 | ], 138 | "root": true, 139 | "rules": Object { 140 | "curly": Array [ 141 | "error", 142 | "all", 143 | ], 144 | "eqeqeq": Array [ 145 | "error", 146 | "smart", 147 | ], 148 | "import/no-extraneous-dependencies": Array [ 149 | "error", 150 | Object { 151 | "devDependencies": true, 152 | "optionalDependencies": false, 153 | "peerDependencies": false, 154 | }, 155 | ], 156 | "import/order": Array [ 157 | "error", 158 | Object { 159 | "groups": Array [ 160 | Array [ 161 | "external", 162 | "builtin", 163 | ], 164 | "internal", 165 | Array [ 166 | "parent", 167 | "sibling", 168 | "index", 169 | ], 170 | ], 171 | }, 172 | ], 173 | "no-console": "off", 174 | "no-debugger": "off", 175 | "no-shadow": Array [ 176 | "error", 177 | Object { 178 | "hoist": "all", 179 | }, 180 | ], 181 | "padding-line-between-statements": Array [ 182 | "error", 183 | Object { 184 | "blankLine": "always", 185 | "next": "return", 186 | "prev": "*", 187 | }, 188 | ], 189 | "prefer-const": "error", 190 | "sort-imports": Array [ 191 | "error", 192 | Object { 193 | "ignoreCase": true, 194 | "ignoreDeclarationSort": true, 195 | "ignoreMemberSort": false, 196 | "memberSyntaxSortOrder": Array [ 197 | "none", 198 | "all", 199 | "multiple", 200 | "single", 201 | ], 202 | }, 203 | ], 204 | }, 205 | } 206 | `; 207 | -------------------------------------------------------------------------------- /src/e2e/generate/eslint.test.ts: -------------------------------------------------------------------------------- 1 | import { defaultInputConfig, makeTestService } from "e2e/helpers"; 2 | 3 | describe("ESLint Base Configuration Generator Mode", () => { 4 | it("should return a standard eslint configuration with no extensions", async () => { 5 | const testService = makeTestService(); 6 | 7 | testService.loadInputConfig(defaultInputConfig); 8 | await testService.runClinter(); 9 | const outputConfig = testService.getParsedOutputConfig(); 10 | 11 | expect(outputConfig).toMatchSnapshot("eslint-simple-config"); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/e2e/generate/prettier.test.ts: -------------------------------------------------------------------------------- 1 | import { defaultInputConfig, makeTestService } from "e2e/helpers"; 2 | import { ClinterSettings, FormatterInfo, FrontFrameworkInfo, TypescriptInfo } from "types"; 3 | 4 | describe("Prettier eslint configuration Generator Mode", () => { 5 | const testService = makeTestService(); 6 | 7 | it("should return a prettier eslint configuration", async () => { 8 | const inputConfig: ClinterSettings = { 9 | ...defaultInputConfig, 10 | generatorConfig: { 11 | ...defaultInputConfig.generatorConfig, 12 | formatter: FormatterInfo.Prettier, 13 | }, 14 | }; 15 | 16 | testService.loadInputConfig(inputConfig); 17 | await testService.runClinter(); 18 | const outputConfig = testService.getParsedOutputConfig(); 19 | 20 | expect(outputConfig).toMatchSnapshot("eslint-prettier-config"); 21 | }); 22 | 23 | it("should return a prettier typescript eslint configuration with type checking", async () => { 24 | const inputConfig: ClinterSettings = { 25 | ...defaultInputConfig, 26 | generatorConfig: { 27 | ...defaultInputConfig.generatorConfig, 28 | typescript: TypescriptInfo.WithTypeChecking, 29 | formatter: FormatterInfo.Prettier, 30 | }, 31 | }; 32 | 33 | const tsConfig = { 34 | compilerOptions: { 35 | strict: true, 36 | }, 37 | }; 38 | 39 | testService.loadInputConfig(inputConfig); 40 | testService.loadInitialTSConfig(tsConfig); 41 | await testService.runClinter(); 42 | const outputConfig = testService.getParsedOutputConfig(); 43 | 44 | expect(outputConfig).toMatchSnapshot("eslint-prettier-ts-config"); 45 | }); 46 | 47 | it("should return a prettier react eslint configuration with type checking", async () => { 48 | const inputConfig: ClinterSettings = { 49 | ...defaultInputConfig, 50 | generatorConfig: { 51 | ...defaultInputConfig.generatorConfig, 52 | formatter: FormatterInfo.Prettier, 53 | frontFramework: FrontFrameworkInfo.React, 54 | }, 55 | }; 56 | 57 | testService.loadInputConfig(inputConfig); 58 | await testService.runClinter(); 59 | const outputConfig = testService.getParsedOutputConfig(); 60 | 61 | expect(outputConfig).toMatchSnapshot("eslint-prettier-react-config"); 62 | }); 63 | 64 | it("should return a prettier react typescript eslint configuration with type checking", async () => { 65 | const inputConfig: ClinterSettings = { 66 | ...defaultInputConfig, 67 | generatorConfig: { 68 | ...defaultInputConfig.generatorConfig, 69 | typescript: TypescriptInfo.WithTypeChecking, 70 | formatter: FormatterInfo.Prettier, 71 | frontFramework: FrontFrameworkInfo.React, 72 | }, 73 | }; 74 | 75 | const tsConfig = { 76 | compilerOptions: { 77 | strict: true, 78 | }, 79 | }; 80 | 81 | testService.loadInputConfig(inputConfig); 82 | testService.loadInitialTSConfig(tsConfig); 83 | 84 | await testService.runClinter(); 85 | const outputConfig = testService.getParsedOutputConfig(); 86 | 87 | expect(outputConfig).toMatchSnapshot("eslint-prettier-ts-react-config"); 88 | }); 89 | 90 | it("should return a prettier vue eslint configuration with type checking", async () => { 91 | const inputConfig: ClinterSettings = { 92 | ...defaultInputConfig, 93 | generatorConfig: { 94 | ...defaultInputConfig.generatorConfig, 95 | formatter: FormatterInfo.Prettier, 96 | frontFramework: FrontFrameworkInfo.Vue, 97 | }, 98 | }; 99 | 100 | testService.loadInputConfig(inputConfig); 101 | await testService.runClinter(); 102 | const outputConfig = testService.getParsedOutputConfig(); 103 | 104 | expect(outputConfig).toMatchSnapshot("eslint-prettier-vue-config"); 105 | }); 106 | 107 | it("should return a prettier vue typescript eslint configuration with type checking", async () => { 108 | const inputConfig: ClinterSettings = { 109 | ...defaultInputConfig, 110 | generatorConfig: { 111 | ...defaultInputConfig.generatorConfig, 112 | typescript: TypescriptInfo.WithTypeChecking, 113 | formatter: FormatterInfo.Prettier, 114 | frontFramework: FrontFrameworkInfo.Vue, 115 | }, 116 | }; 117 | 118 | const tsConfig = { 119 | compilerOptions: { 120 | strict: true, 121 | }, 122 | }; 123 | 124 | testService.loadInputConfig(inputConfig); 125 | testService.loadInitialTSConfig(tsConfig); 126 | 127 | await testService.runClinter(); 128 | const outputConfig = testService.getParsedOutputConfig(); 129 | 130 | expect(outputConfig).toMatchSnapshot("eslint-prettier-ts-vue-config"); 131 | }); 132 | }); 133 | -------------------------------------------------------------------------------- /src/e2e/generate/react.test.ts: -------------------------------------------------------------------------------- 1 | import { defaultInputConfig, makeTestService } from "e2e/helpers"; 2 | import { ClinterSettings, FrontFrameworkInfo, TypescriptInfo } from "types"; 3 | 4 | describe("React configuration Generator Mode", () => { 5 | const testService = makeTestService(); 6 | 7 | it("should return a react eslint configuration", async () => { 8 | const inputConfig: ClinterSettings = { 9 | ...defaultInputConfig, 10 | generatorConfig: { 11 | ...defaultInputConfig.generatorConfig, 12 | frontFramework: FrontFrameworkInfo.React, 13 | }, 14 | }; 15 | 16 | testService.loadInputConfig(inputConfig); 17 | await testService.runClinter(); 18 | const outputConfig = testService.getParsedOutputConfig(); 19 | 20 | expect(outputConfig).toMatchSnapshot("eslint-react-config"); 21 | }); 22 | 23 | it("should return a react typescript eslint configuration with type checking", async () => { 24 | const inputConfig: ClinterSettings = { 25 | ...defaultInputConfig, 26 | generatorConfig: { 27 | ...defaultInputConfig.generatorConfig, 28 | typescript: TypescriptInfo.WithTypeChecking, 29 | frontFramework: FrontFrameworkInfo.React, 30 | }, 31 | }; 32 | 33 | const tsConfig = { 34 | compilerOptions: { 35 | strict: true, 36 | }, 37 | }; 38 | 39 | testService.loadInputConfig(inputConfig); 40 | testService.loadInitialTSConfig(tsConfig); 41 | 42 | await testService.runClinter(); 43 | const outputConfig = testService.getParsedOutputConfig(); 44 | 45 | expect(outputConfig).toMatchSnapshot("eslint-react-ts-config"); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /src/e2e/generate/typescript.test.ts: -------------------------------------------------------------------------------- 1 | import { defaultInputConfig, makeTestService } from "e2e/helpers"; 2 | import { ClinterSettings, TypescriptInfo } from "types"; 3 | 4 | describe("TypeScript configuration Generator Mode", () => { 5 | const testService = makeTestService(); 6 | 7 | it("should return a typescript eslint configuration with no type checking", async () => { 8 | const inputConfig: ClinterSettings = { 9 | ...defaultInputConfig, 10 | generatorConfig: { 11 | ...defaultInputConfig.generatorConfig, 12 | typescript: TypescriptInfo.NoTypeChecking, 13 | }, 14 | }; 15 | 16 | const tsConfig = { 17 | compilerOptions: { 18 | strict: true, 19 | }, 20 | }; 21 | 22 | testService.loadInputConfig(inputConfig); 23 | testService.loadInitialTSConfig(tsConfig); 24 | 25 | await testService.runClinter(); 26 | const outputConfig = testService.getParsedOutputConfig(); 27 | 28 | expect(outputConfig).toMatchSnapshot("eslint-ts-no-type-checking-config"); 29 | }); 30 | 31 | it("should return a typescript eslint configuration with type checking", async () => { 32 | const inputConfig: ClinterSettings = { 33 | ...defaultInputConfig, 34 | generatorConfig: { 35 | ...defaultInputConfig.generatorConfig, 36 | typescript: TypescriptInfo.WithTypeChecking, 37 | }, 38 | }; 39 | 40 | const tsConfig = { 41 | compilerOptions: { 42 | strict: true, 43 | }, 44 | }; 45 | 46 | testService.loadInputConfig(inputConfig); 47 | testService.loadInitialTSConfig(tsConfig); 48 | 49 | await testService.runClinter(); 50 | const outputConfig = testService.getParsedOutputConfig(); 51 | 52 | expect(outputConfig).toMatchSnapshot("eslint-ts-type-checking-config"); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /src/e2e/generate/vue.test.ts: -------------------------------------------------------------------------------- 1 | import { defaultInputConfig, makeTestService } from "e2e/helpers"; 2 | import { ClinterSettings, FrontFrameworkInfo, TypescriptInfo } from "types"; 3 | 4 | describe("Vue configuration Generator Mode", () => { 5 | const testService = makeTestService(); 6 | 7 | it("should return a vue eslint configuration", async () => { 8 | const inputConfig: ClinterSettings = { 9 | ...defaultInputConfig, 10 | generatorConfig: { 11 | ...defaultInputConfig.generatorConfig, 12 | frontFramework: FrontFrameworkInfo.Vue, 13 | }, 14 | }; 15 | 16 | testService.loadInputConfig(inputConfig); 17 | await testService.runClinter(); 18 | const outputConfig = testService.getParsedOutputConfig(); 19 | 20 | expect(outputConfig).toMatchSnapshot("eslint-vue-config"); 21 | }); 22 | 23 | it("should return a vue typescript eslint configuration with type checking", async () => { 24 | const inputConfig: ClinterSettings = { 25 | ...defaultInputConfig, 26 | generatorConfig: { 27 | ...defaultInputConfig.generatorConfig, 28 | typescript: TypescriptInfo.WithTypeChecking, 29 | frontFramework: FrontFrameworkInfo.Vue, 30 | }, 31 | }; 32 | 33 | const tsConfig = { 34 | compilerOptions: { 35 | strict: true, 36 | }, 37 | }; 38 | 39 | testService.loadInputConfig(inputConfig); 40 | testService.loadInitialTSConfig(tsConfig); 41 | await testService.runClinter(); 42 | const outputConfig = testService.getParsedOutputConfig(); 43 | 44 | expect(outputConfig).toMatchSnapshot("eslint-vue-ts-config"); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /src/e2e/helpers.ts: -------------------------------------------------------------------------------- 1 | import { v4 as uuidv4 } from "uuid"; 2 | import path from "path"; 3 | import fs from "fs"; 4 | import { exec } from "child-process-promise"; 5 | 6 | import { LinterConfigs } from "parser/linter-config-parser/types"; 7 | import { 8 | ClinterModeInfo, 9 | ClinterSettings, 10 | FormatterInfo, 11 | FrontFrameworkInfo, 12 | TestFrameworkInfo, 13 | TypescriptInfo, 14 | } from "types"; 15 | 16 | export interface TestService { 17 | loadInputConfig: (config: ClinterSettings) => void; 18 | loadInitialLinterConfig: (config: string) => void; 19 | loadInitialParsedLinterConfig: (config: LinterConfigs) => void; 20 | installPackages: (packages: string[]) => Promise; 21 | runClinter: () => Promise; 22 | getParsedOutputConfig: () => LinterConfigs; 23 | getOutputConfig: () => string; 24 | getInstalledPackages: () => string[]; 25 | loadInitialTSConfig: (config: Record) => void; 26 | } 27 | 28 | export function makeTestService(outputFileName = ".eslintrc.json"): TestService { 29 | const id = uuidv4(); 30 | const dirPath = `/tmp/${id}/`; 31 | const inputConfigPath = path.join(dirPath, "inputConfig.json"); 32 | const tsconfigPath = path.join(dirPath, "tsconfig.json"); 33 | const packagePath = path.join(dirPath, "package.json"); 34 | const outputConfigPath = path.join(dirPath, outputFileName); 35 | 36 | fs.mkdirSync(dirPath); 37 | 38 | const loadInputConfig = (config: ClinterSettings) => { 39 | fs.writeFileSync(inputConfigPath, JSON.stringify(config, null, 2)); 40 | }; 41 | 42 | const loadInitialLinterConfig = (config: string) => { 43 | fs.writeFileSync(outputConfigPath, config); 44 | }; 45 | 46 | const loadInitialParsedLinterConfig = (config: LinterConfigs) => { 47 | fs.writeFileSync(outputConfigPath, JSON.stringify(config, null, 2)); 48 | }; 49 | 50 | const loadInitialTSConfig = (config: Record) => { 51 | fs.writeFileSync(tsconfigPath, JSON.stringify(config, null, 2)); 52 | }; 53 | 54 | const installPackages = (packages: string[]) => { 55 | return Promise.resolve(); 56 | }; 57 | 58 | const runClinter = () => { 59 | return exec(`node ${path.join(process.cwd(), "build/index.js")} --path ${dirPath} --inputFile ${inputConfigPath}`) 60 | .then(() => Promise.resolve()) 61 | .catch((error) => { 62 | throw new Error(error); 63 | }); 64 | }; 65 | 66 | const getOutputConfig = (): string => { 67 | return fs.readFileSync(outputConfigPath, "utf-8"); 68 | }; 69 | 70 | const getParsedOutputConfig = (): LinterConfigs => { 71 | return JSON.parse(fs.readFileSync(outputConfigPath, "utf-8")) as LinterConfigs; 72 | }; 73 | 74 | const getInstalledPackages = () => { 75 | return []; 76 | }; 77 | 78 | return { 79 | getInstalledPackages, 80 | getParsedOutputConfig, 81 | getOutputConfig, 82 | installPackages, 83 | loadInitialLinterConfig, 84 | loadInitialParsedLinterConfig, 85 | loadInputConfig, 86 | runClinter, 87 | loadInitialTSConfig, 88 | }; 89 | } 90 | 91 | export const defaultInputConfig: ClinterSettings = { 92 | modeConfig: { 93 | mode: ClinterModeInfo.Generator, 94 | }, 95 | generatorConfig: { 96 | typescript: TypescriptInfo.None, 97 | env: [], 98 | formatter: FormatterInfo.None, 99 | frontFramework: FrontFrameworkInfo.None, 100 | test: TestFrameworkInfo.None, 101 | }, 102 | migrationModeConfig: { 103 | migration: false, 104 | }, 105 | dependenciesConfig: { 106 | upgradeDependencies: false, 107 | }, 108 | }; 109 | -------------------------------------------------------------------------------- /src/e2e/upgrade/__snapshots__/eslint.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`ESLint Base Configuration Upgrade Mode should return a standard eslint configuration with no extensions appended to an empty config in .eslintrc.js: eslint-simple-config-.eslintrc.js 1`] = ` 4 | "module.exports = { 5 | \\"root\\": true, 6 | \\"extends\\": [ 7 | \\"eslint:recommended\\" 8 | ], 9 | \\"plugins\\": [ 10 | \\"import\\" 11 | ], 12 | \\"rules\\": { 13 | \\"curly\\": [ 14 | \\"error\\", 15 | \\"all\\" 16 | ], 17 | \\"eqeqeq\\": [ 18 | \\"error\\", 19 | \\"smart\\" 20 | ], 21 | \\"import/no-extraneous-dependencies\\": [ 22 | \\"error\\", 23 | { 24 | \\"devDependencies\\": true, 25 | \\"optionalDependencies\\": false, 26 | \\"peerDependencies\\": false 27 | } 28 | ], 29 | \\"no-shadow\\": [ 30 | \\"error\\", 31 | { 32 | \\"hoist\\": \\"all\\" 33 | } 34 | ], 35 | \\"prefer-const\\": \\"error\\", 36 | \\"import/order\\": [ 37 | \\"error\\", 38 | { 39 | \\"groups\\": [ 40 | [ 41 | \\"external\\", 42 | \\"builtin\\" 43 | ], 44 | \\"internal\\", 45 | [ 46 | \\"parent\\", 47 | \\"sibling\\", 48 | \\"index\\" 49 | ] 50 | ] 51 | } 52 | ], 53 | \\"sort-imports\\": [ 54 | \\"error\\", 55 | { 56 | \\"ignoreCase\\": true, 57 | \\"ignoreDeclarationSort\\": true, 58 | \\"ignoreMemberSort\\": false, 59 | \\"memberSyntaxSortOrder\\": [ 60 | \\"none\\", 61 | \\"all\\", 62 | \\"multiple\\", 63 | \\"single\\" 64 | ] 65 | } 66 | ], 67 | \\"padding-line-between-statements\\": [ 68 | \\"error\\", 69 | { 70 | \\"blankLine\\": \\"always\\", 71 | \\"prev\\": \\"*\\", 72 | \\"next\\": \\"return\\" 73 | } 74 | ] 75 | } 76 | }" 77 | `; 78 | 79 | exports[`ESLint Base Configuration Upgrade Mode should return a standard eslint configuration with no extensions appended to an empty config in .eslintrc.json: eslint-simple-config-.eslintrc.json 1`] = ` 80 | "{ 81 | \\"root\\": true, 82 | \\"extends\\": [ 83 | \\"eslint:recommended\\" 84 | ], 85 | \\"plugins\\": [ 86 | \\"import\\" 87 | ], 88 | \\"rules\\": { 89 | \\"curly\\": [ 90 | \\"error\\", 91 | \\"all\\" 92 | ], 93 | \\"eqeqeq\\": [ 94 | \\"error\\", 95 | \\"smart\\" 96 | ], 97 | \\"import/no-extraneous-dependencies\\": [ 98 | \\"error\\", 99 | { 100 | \\"devDependencies\\": true, 101 | \\"optionalDependencies\\": false, 102 | \\"peerDependencies\\": false 103 | } 104 | ], 105 | \\"no-shadow\\": [ 106 | \\"error\\", 107 | { 108 | \\"hoist\\": \\"all\\" 109 | } 110 | ], 111 | \\"prefer-const\\": \\"error\\", 112 | \\"import/order\\": [ 113 | \\"error\\", 114 | { 115 | \\"groups\\": [ 116 | [ 117 | \\"external\\", 118 | \\"builtin\\" 119 | ], 120 | \\"internal\\", 121 | [ 122 | \\"parent\\", 123 | \\"sibling\\", 124 | \\"index\\" 125 | ] 126 | ] 127 | } 128 | ], 129 | \\"sort-imports\\": [ 130 | \\"error\\", 131 | { 132 | \\"ignoreCase\\": true, 133 | \\"ignoreDeclarationSort\\": true, 134 | \\"ignoreMemberSort\\": false, 135 | \\"memberSyntaxSortOrder\\": [ 136 | \\"none\\", 137 | \\"all\\", 138 | \\"multiple\\", 139 | \\"single\\" 140 | ] 141 | } 142 | ], 143 | \\"padding-line-between-statements\\": [ 144 | \\"error\\", 145 | { 146 | \\"blankLine\\": \\"always\\", 147 | \\"prev\\": \\"*\\", 148 | \\"next\\": \\"return\\" 149 | } 150 | ] 151 | } 152 | }" 153 | `; 154 | 155 | exports[`ESLint Base Configuration Upgrade Mode should return a standard eslint configuration with no extensions appended to an empty config in .eslintrc: eslint-simple-config-.eslintrc 1`] = ` 156 | "{ 157 | \\"root\\": true, 158 | \\"extends\\": [ 159 | \\"eslint:recommended\\" 160 | ], 161 | \\"plugins\\": [ 162 | \\"import\\" 163 | ], 164 | \\"rules\\": { 165 | \\"curly\\": [ 166 | \\"error\\", 167 | \\"all\\" 168 | ], 169 | \\"eqeqeq\\": [ 170 | \\"error\\", 171 | \\"smart\\" 172 | ], 173 | \\"import/no-extraneous-dependencies\\": [ 174 | \\"error\\", 175 | { 176 | \\"devDependencies\\": true, 177 | \\"optionalDependencies\\": false, 178 | \\"peerDependencies\\": false 179 | } 180 | ], 181 | \\"no-shadow\\": [ 182 | \\"error\\", 183 | { 184 | \\"hoist\\": \\"all\\" 185 | } 186 | ], 187 | \\"prefer-const\\": \\"error\\", 188 | \\"import/order\\": [ 189 | \\"error\\", 190 | { 191 | \\"groups\\": [ 192 | [ 193 | \\"external\\", 194 | \\"builtin\\" 195 | ], 196 | \\"internal\\", 197 | [ 198 | \\"parent\\", 199 | \\"sibling\\", 200 | \\"index\\" 201 | ] 202 | ] 203 | } 204 | ], 205 | \\"sort-imports\\": [ 206 | \\"error\\", 207 | { 208 | \\"ignoreCase\\": true, 209 | \\"ignoreDeclarationSort\\": true, 210 | \\"ignoreMemberSort\\": false, 211 | \\"memberSyntaxSortOrder\\": [ 212 | \\"none\\", 213 | \\"all\\", 214 | \\"multiple\\", 215 | \\"single\\" 216 | ] 217 | } 218 | ], 219 | \\"padding-line-between-statements\\": [ 220 | \\"error\\", 221 | { 222 | \\"blankLine\\": \\"always\\", 223 | \\"prev\\": \\"*\\", 224 | \\"next\\": \\"return\\" 225 | } 226 | ] 227 | } 228 | }" 229 | `; 230 | -------------------------------------------------------------------------------- /src/e2e/upgrade/__snapshots__/prettier.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`ESLint Prettier Base Configuration Upgrade Mode should return a react eslint configuration with no type checking with a TSConfig without strict null checks: eslint-prettier-react-upgrade 1`] = ` 4 | "{ 5 | \\"extends\\": [ 6 | \\"eslint:recommended\\", 7 | \\"react-app\\", 8 | \\"plugin:jsx-a11y/recommended\\", 9 | \\"plugin:prettier/recommended\\" 10 | ], 11 | \\"rules\\": { 12 | \\"@typescript-eslint/prefer-optional-chain\\": \\"error\\", 13 | \\"curly\\": [ 14 | \\"error\\", 15 | \\"all\\" 16 | ], 17 | \\"eqeqeq\\": [ 18 | \\"error\\", 19 | \\"smart\\" 20 | ], 21 | \\"import/no-extraneous-dependencies\\": [ 22 | \\"error\\", 23 | { 24 | \\"devDependencies\\": true, 25 | \\"optionalDependencies\\": false, 26 | \\"peerDependencies\\": false 27 | } 28 | ], 29 | \\"no-shadow\\": [ 30 | \\"error\\", 31 | { 32 | \\"hoist\\": \\"all\\" 33 | } 34 | ], 35 | \\"prefer-const\\": \\"error\\", 36 | \\"import/order\\": [ 37 | \\"error\\", 38 | { 39 | \\"groups\\": [ 40 | [ 41 | \\"external\\", 42 | \\"builtin\\" 43 | ], 44 | \\"internal\\", 45 | [ 46 | \\"parent\\", 47 | \\"sibling\\", 48 | \\"index\\" 49 | ] 50 | ] 51 | } 52 | ], 53 | \\"sort-imports\\": [ 54 | \\"error\\", 55 | { 56 | \\"ignoreCase\\": true, 57 | \\"ignoreDeclarationSort\\": true, 58 | \\"ignoreMemberSort\\": false, 59 | \\"memberSyntaxSortOrder\\": [ 60 | \\"none\\", 61 | \\"all\\", 62 | \\"multiple\\", 63 | \\"single\\" 64 | ] 65 | } 66 | ], 67 | \\"padding-line-between-statements\\": [ 68 | \\"error\\", 69 | { 70 | \\"blankLine\\": \\"always\\", 71 | \\"prev\\": \\"*\\", 72 | \\"next\\": \\"return\\" 73 | } 74 | ], 75 | \\"react/no-string-refs\\": \\"warn\\", 76 | \\"react-hooks/rules-of-hooks\\": \\"error\\", 77 | \\"react-hooks/exhaustive-deps\\": \\"warn\\" 78 | }, 79 | \\"root\\": true, 80 | \\"plugins\\": [ 81 | \\"import\\", 82 | \\"jsx-a11y\\" 83 | ], 84 | \\"settings\\": { 85 | \\"react\\": { 86 | \\"version\\": \\"detect\\" 87 | } 88 | } 89 | }" 90 | `; 91 | 92 | exports[`ESLint Prettier Base Configuration Upgrade Mode should return a typescript eslint configuration with a cleaned prettier config without prettier/@typescript-eslint: eslint-prettier-typescript-upgrade 1`] = ` 93 | "{ 94 | \\"extends\\": [ 95 | \\"eslint:recommended\\", 96 | \\"plugin:prettier/recommended\\" 97 | ], 98 | \\"rules\\": { 99 | \\"curly\\": [ 100 | \\"error\\", 101 | \\"all\\" 102 | ], 103 | \\"eqeqeq\\": [ 104 | \\"error\\", 105 | \\"smart\\" 106 | ], 107 | \\"import/no-extraneous-dependencies\\": [ 108 | \\"error\\", 109 | { 110 | \\"devDependencies\\": true, 111 | \\"optionalDependencies\\": false, 112 | \\"peerDependencies\\": false 113 | } 114 | ], 115 | \\"no-shadow\\": [ 116 | \\"error\\", 117 | { 118 | \\"hoist\\": \\"all\\" 119 | } 120 | ], 121 | \\"prefer-const\\": \\"error\\", 122 | \\"import/order\\": [ 123 | \\"error\\", 124 | { 125 | \\"groups\\": [ 126 | [ 127 | \\"external\\", 128 | \\"builtin\\" 129 | ], 130 | \\"internal\\", 131 | [ 132 | \\"parent\\", 133 | \\"sibling\\", 134 | \\"index\\" 135 | ] 136 | ] 137 | } 138 | ], 139 | \\"sort-imports\\": [ 140 | \\"error\\", 141 | { 142 | \\"ignoreCase\\": true, 143 | \\"ignoreDeclarationSort\\": true, 144 | \\"ignoreMemberSort\\": false, 145 | \\"memberSyntaxSortOrder\\": [ 146 | \\"none\\", 147 | \\"all\\", 148 | \\"multiple\\", 149 | \\"single\\" 150 | ] 151 | } 152 | ], 153 | \\"padding-line-between-statements\\": [ 154 | \\"error\\", 155 | { 156 | \\"blankLine\\": \\"always\\", 157 | \\"prev\\": \\"*\\", 158 | \\"next\\": \\"return\\" 159 | } 160 | ] 161 | }, 162 | \\"root\\": true, 163 | \\"plugins\\": [ 164 | \\"import\\" 165 | ], 166 | \\"overrides\\": [ 167 | { 168 | \\"files\\": [ 169 | \\"**/*.ts?(x)\\" 170 | ], 171 | \\"extends\\": [ 172 | \\"plugin:@typescript-eslint/recommended\\", 173 | \\"plugin:@typescript-eslint/recommended-requiring-type-checking\\", 174 | \\"plugin:prettier/recommended\\" 175 | ], 176 | \\"parser\\": \\"@typescript-eslint/parser\\", 177 | \\"parserOptions\\": { 178 | \\"project\\": \\"tsconfig.json\\" 179 | }, 180 | \\"rules\\": { 181 | \\"@typescript-eslint/prefer-optional-chain\\": \\"error\\", 182 | \\"no-shadow\\": \\"off\\", 183 | \\"@typescript-eslint/no-shadow\\": \\"error\\", 184 | \\"@typescript-eslint/prefer-nullish-coalescing\\": \\"error\\", 185 | \\"@typescript-eslint/strict-boolean-expressions\\": [ 186 | \\"error\\", 187 | { 188 | \\"allowString\\": false, 189 | \\"allowNumber\\": false, 190 | \\"allowNullableObject\\": true 191 | } 192 | ], 193 | \\"@typescript-eslint/no-unnecessary-boolean-literal-compare\\": \\"error\\", 194 | \\"@typescript-eslint/no-unnecessary-condition\\": \\"error\\", 195 | \\"@typescript-eslint/no-unnecessary-type-arguments\\": \\"error\\", 196 | \\"@typescript-eslint/prefer-string-starts-ends-with\\": \\"error\\", 197 | \\"@typescript-eslint/switch-exhaustiveness-check\\": \\"error\\", 198 | \\"@typescript-eslint/restrict-template-expressions\\": [ 199 | \\"error\\", 200 | { 201 | \\"allowNumber\\": true, 202 | \\"allowBoolean\\": true 203 | } 204 | ] 205 | } 206 | } 207 | ] 208 | }" 209 | `; 210 | -------------------------------------------------------------------------------- /src/e2e/upgrade/__snapshots__/typescript.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`ESLint Base Configuration Upgrade Mode should return a typescript eslint configuration with an override and no typescript-eslint in base rules: eslint-typescript-upgrade 1`] = ` 4 | "{ 5 | \\"extends\\": [ 6 | \\"eslint:recommended\\" 7 | ], 8 | \\"rules\\": { 9 | \\"curly\\": [ 10 | \\"error\\", 11 | \\"all\\" 12 | ], 13 | \\"eqeqeq\\": [ 14 | \\"error\\", 15 | \\"smart\\" 16 | ], 17 | \\"import/no-extraneous-dependencies\\": [ 18 | \\"error\\", 19 | { 20 | \\"devDependencies\\": true, 21 | \\"optionalDependencies\\": false, 22 | \\"peerDependencies\\": false 23 | } 24 | ], 25 | \\"no-shadow\\": [ 26 | \\"error\\", 27 | { 28 | \\"hoist\\": \\"all\\" 29 | } 30 | ], 31 | \\"prefer-const\\": \\"error\\", 32 | \\"import/order\\": [ 33 | \\"error\\", 34 | { 35 | \\"groups\\": [ 36 | [ 37 | \\"external\\", 38 | \\"builtin\\" 39 | ], 40 | \\"internal\\", 41 | [ 42 | \\"parent\\", 43 | \\"sibling\\", 44 | \\"index\\" 45 | ] 46 | ] 47 | } 48 | ], 49 | \\"sort-imports\\": [ 50 | \\"error\\", 51 | { 52 | \\"ignoreCase\\": true, 53 | \\"ignoreDeclarationSort\\": true, 54 | \\"ignoreMemberSort\\": false, 55 | \\"memberSyntaxSortOrder\\": [ 56 | \\"none\\", 57 | \\"all\\", 58 | \\"multiple\\", 59 | \\"single\\" 60 | ] 61 | } 62 | ], 63 | \\"padding-line-between-statements\\": [ 64 | \\"error\\", 65 | { 66 | \\"blankLine\\": \\"always\\", 67 | \\"prev\\": \\"*\\", 68 | \\"next\\": \\"return\\" 69 | } 70 | ] 71 | }, 72 | \\"root\\": true, 73 | \\"plugins\\": [ 74 | \\"import\\" 75 | ], 76 | \\"overrides\\": [ 77 | { 78 | \\"files\\": [ 79 | \\"**/*.ts?(x)\\" 80 | ], 81 | \\"extends\\": [ 82 | \\"plugin:@typescript-eslint/recommended\\", 83 | \\"plugin:@typescript-eslint/recommended-requiring-type-checking\\" 84 | ], 85 | \\"parser\\": \\"@typescript-eslint/parser\\", 86 | \\"parserOptions\\": { 87 | \\"project\\": \\"tsconfig.json\\" 88 | }, 89 | \\"rules\\": { 90 | \\"@typescript-eslint/prefer-optional-chain\\": \\"error\\", 91 | \\"no-shadow\\": \\"off\\", 92 | \\"@typescript-eslint/no-shadow\\": \\"error\\", 93 | \\"@typescript-eslint/prefer-nullish-coalescing\\": \\"error\\", 94 | \\"@typescript-eslint/strict-boolean-expressions\\": [ 95 | \\"error\\", 96 | { 97 | \\"allowString\\": false, 98 | \\"allowNumber\\": false, 99 | \\"allowNullableObject\\": true 100 | } 101 | ], 102 | \\"@typescript-eslint/no-unnecessary-boolean-literal-compare\\": \\"error\\", 103 | \\"@typescript-eslint/no-unnecessary-condition\\": \\"error\\", 104 | \\"@typescript-eslint/no-unnecessary-type-arguments\\": \\"error\\", 105 | \\"@typescript-eslint/prefer-string-starts-ends-with\\": \\"error\\", 106 | \\"@typescript-eslint/switch-exhaustiveness-check\\": \\"error\\", 107 | \\"@typescript-eslint/restrict-template-expressions\\": [ 108 | \\"error\\", 109 | { 110 | \\"allowNumber\\": true, 111 | \\"allowBoolean\\": true 112 | } 113 | ] 114 | } 115 | } 116 | ] 117 | }" 118 | `; 119 | 120 | exports[`ESLint Base Configuration Upgrade Mode should return a typescript eslint configuration with no type checking with a TSConfig without strict null checks: eslint-typescript-upgrade-no-type-checking 1`] = ` 121 | "{ 122 | \\"extends\\": [ 123 | \\"eslint:recommended\\" 124 | ], 125 | \\"rules\\": { 126 | \\"curly\\": [ 127 | \\"error\\", 128 | \\"all\\" 129 | ], 130 | \\"eqeqeq\\": [ 131 | \\"error\\", 132 | \\"smart\\" 133 | ], 134 | \\"import/no-extraneous-dependencies\\": [ 135 | \\"error\\", 136 | { 137 | \\"devDependencies\\": true, 138 | \\"optionalDependencies\\": false, 139 | \\"peerDependencies\\": false 140 | } 141 | ], 142 | \\"no-shadow\\": [ 143 | \\"error\\", 144 | { 145 | \\"hoist\\": \\"all\\" 146 | } 147 | ], 148 | \\"prefer-const\\": \\"error\\", 149 | \\"import/order\\": [ 150 | \\"error\\", 151 | { 152 | \\"groups\\": [ 153 | [ 154 | \\"external\\", 155 | \\"builtin\\" 156 | ], 157 | \\"internal\\", 158 | [ 159 | \\"parent\\", 160 | \\"sibling\\", 161 | \\"index\\" 162 | ] 163 | ] 164 | } 165 | ], 166 | \\"sort-imports\\": [ 167 | \\"error\\", 168 | { 169 | \\"ignoreCase\\": true, 170 | \\"ignoreDeclarationSort\\": true, 171 | \\"ignoreMemberSort\\": false, 172 | \\"memberSyntaxSortOrder\\": [ 173 | \\"none\\", 174 | \\"all\\", 175 | \\"multiple\\", 176 | \\"single\\" 177 | ] 178 | } 179 | ], 180 | \\"padding-line-between-statements\\": [ 181 | \\"error\\", 182 | { 183 | \\"blankLine\\": \\"always\\", 184 | \\"prev\\": \\"*\\", 185 | \\"next\\": \\"return\\" 186 | } 187 | ] 188 | }, 189 | \\"root\\": true, 190 | \\"plugins\\": [ 191 | \\"import\\" 192 | ], 193 | \\"overrides\\": [ 194 | { 195 | \\"files\\": [ 196 | \\"**/*.ts?(x)\\" 197 | ], 198 | \\"extends\\": [ 199 | \\"plugin:@typescript-eslint/recommended\\" 200 | ], 201 | \\"parser\\": \\"@typescript-eslint/parser\\", 202 | \\"parserOptions\\": { 203 | \\"project\\": \\"tsconfig.json\\" 204 | }, 205 | \\"rules\\": { 206 | \\"@typescript-eslint/prefer-optional-chain\\": \\"error\\", 207 | \\"no-shadow\\": \\"off\\", 208 | \\"@typescript-eslint/no-shadow\\": \\"error\\" 209 | } 210 | } 211 | ] 212 | }" 213 | `; 214 | 215 | exports[`ESLint Base Configuration Upgrade Mode should return a typescript eslint configuration with no type checking with an override and no typescript-eslint in base rules: eslint-typescript-upgrade-no-type-checking 1`] = ` 216 | "{ 217 | \\"extends\\": [ 218 | \\"eslint:recommended\\" 219 | ], 220 | \\"rules\\": { 221 | \\"curly\\": [ 222 | \\"error\\", 223 | \\"all\\" 224 | ], 225 | \\"eqeqeq\\": [ 226 | \\"error\\", 227 | \\"smart\\" 228 | ], 229 | \\"import/no-extraneous-dependencies\\": [ 230 | \\"error\\", 231 | { 232 | \\"devDependencies\\": true, 233 | \\"optionalDependencies\\": false, 234 | \\"peerDependencies\\": false 235 | } 236 | ], 237 | \\"no-shadow\\": [ 238 | \\"error\\", 239 | { 240 | \\"hoist\\": \\"all\\" 241 | } 242 | ], 243 | \\"prefer-const\\": \\"error\\", 244 | \\"import/order\\": [ 245 | \\"error\\", 246 | { 247 | \\"groups\\": [ 248 | [ 249 | \\"external\\", 250 | \\"builtin\\" 251 | ], 252 | \\"internal\\", 253 | [ 254 | \\"parent\\", 255 | \\"sibling\\", 256 | \\"index\\" 257 | ] 258 | ] 259 | } 260 | ], 261 | \\"sort-imports\\": [ 262 | \\"error\\", 263 | { 264 | \\"ignoreCase\\": true, 265 | \\"ignoreDeclarationSort\\": true, 266 | \\"ignoreMemberSort\\": false, 267 | \\"memberSyntaxSortOrder\\": [ 268 | \\"none\\", 269 | \\"all\\", 270 | \\"multiple\\", 271 | \\"single\\" 272 | ] 273 | } 274 | ], 275 | \\"padding-line-between-statements\\": [ 276 | \\"error\\", 277 | { 278 | \\"blankLine\\": \\"always\\", 279 | \\"prev\\": \\"*\\", 280 | \\"next\\": \\"return\\" 281 | } 282 | ] 283 | }, 284 | \\"root\\": true, 285 | \\"plugins\\": [ 286 | \\"import\\" 287 | ], 288 | \\"overrides\\": [ 289 | { 290 | \\"files\\": [ 291 | \\"**/*.ts?(x)\\" 292 | ], 293 | \\"extends\\": [ 294 | \\"plugin:@typescript-eslint/recommended\\" 295 | ], 296 | \\"parser\\": \\"@typescript-eslint/parser\\", 297 | \\"parserOptions\\": { 298 | \\"project\\": \\"tsconfig.json\\" 299 | }, 300 | \\"rules\\": { 301 | \\"@typescript-eslint/prefer-optional-chain\\": \\"error\\", 302 | \\"no-shadow\\": \\"off\\", 303 | \\"@typescript-eslint/no-shadow\\": \\"error\\" 304 | } 305 | } 306 | ] 307 | }" 308 | `; 309 | -------------------------------------------------------------------------------- /src/e2e/upgrade/eslint.test.ts: -------------------------------------------------------------------------------- 1 | import { defaultInputConfig, makeTestService } from "e2e/helpers"; 2 | import { ClinterModeInfo } from "types"; 3 | 4 | describe("ESLint Base Configuration Upgrade Mode", () => { 5 | it.each` 6 | filename | initialConfig 7 | ${".eslintrc"} | ${"{}"} 8 | ${".eslintrc.json"} | ${"{}"} 9 | ${".eslintrc.js"} | ${"module.exports = {}"} 10 | `( 11 | "should return a standard eslint configuration with no extensions appended to an empty config in $filename", 12 | async ({ filename, initialConfig }: { filename: string; initialConfig: string }) => { 13 | const testService = makeTestService(filename); 14 | 15 | testService.loadInputConfig({ ...defaultInputConfig, modeConfig: { mode: ClinterModeInfo.Upgrade } }); 16 | testService.loadInitialLinterConfig(initialConfig); 17 | await testService.runClinter(); 18 | const outputConfig = testService.getOutputConfig(); 19 | 20 | expect(outputConfig).toMatchSnapshot(`eslint-simple-config-${filename}`); 21 | } 22 | ); 23 | }); 24 | -------------------------------------------------------------------------------- /src/e2e/upgrade/prettier.test.ts: -------------------------------------------------------------------------------- 1 | import { Linter } from "eslint"; 2 | 3 | import { defaultInputConfig, makeTestService } from "e2e/helpers"; 4 | import { ClinterModeInfo, FormatterInfo, FrontFrameworkInfo, TypescriptInfo } from "types"; 5 | 6 | describe("ESLint Prettier Base Configuration Upgrade Mode", () => { 7 | it("should return a typescript eslint configuration with a cleaned prettier config without prettier/@typescript-eslint", async () => { 8 | const testService = makeTestService(); 9 | 10 | const initialConfig: Linter.Config = { 11 | extends: [ 12 | "plugin:@typescript-eslint/recommended", 13 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 14 | "plugin:prettier/recommended", 15 | "prettier/@typescript-eslint", 16 | ], 17 | rules: { 18 | "@typescript-eslint/prefer-optional-chain": "error", 19 | "@typescript-eslint/prefer-nullish-coalescing": "error", 20 | "@typescript-eslint/strict-boolean-expressions": "error", 21 | }, 22 | }; 23 | 24 | const tsConfig = { 25 | compilerOptions: { 26 | strict: true, 27 | }, 28 | }; 29 | 30 | testService.loadInputConfig({ 31 | ...defaultInputConfig, 32 | modeConfig: { mode: ClinterModeInfo.Upgrade }, 33 | generatorConfig: { 34 | ...defaultInputConfig.generatorConfig, 35 | typescript: TypescriptInfo.WithTypeChecking, 36 | formatter: FormatterInfo.Prettier, 37 | }, 38 | }); 39 | testService.loadInitialLinterConfig(JSON.stringify(initialConfig)); 40 | 41 | testService.loadInitialTSConfig(tsConfig); 42 | 43 | await testService.runClinter(); 44 | const outputConfig = testService.getOutputConfig(); 45 | 46 | expect(outputConfig).toMatchSnapshot(`eslint-prettier-typescript-upgrade`); 47 | }); 48 | 49 | it("should return a react eslint configuration with no type checking with a TSConfig without strict null checks", async () => { 50 | const testService = makeTestService(); 51 | 52 | const initialConfig: Linter.Config = { 53 | extends: ["plugin:prettier/recommended", "prettier/@typescript-eslint"], 54 | rules: { 55 | "@typescript-eslint/prefer-optional-chain": "error", 56 | }, 57 | }; 58 | 59 | testService.loadInputConfig({ 60 | ...defaultInputConfig, 61 | modeConfig: { mode: ClinterModeInfo.Upgrade }, 62 | generatorConfig: { 63 | ...defaultInputConfig.generatorConfig, 64 | frontFramework: FrontFrameworkInfo.React, 65 | formatter: FormatterInfo.Prettier, 66 | }, 67 | }); 68 | testService.loadInitialLinterConfig(JSON.stringify(initialConfig)); 69 | await testService.runClinter(); 70 | const outputConfig = testService.getOutputConfig(); 71 | 72 | expect(outputConfig).toMatchSnapshot(`eslint-prettier-react-upgrade`); 73 | }); 74 | }); 75 | -------------------------------------------------------------------------------- /src/e2e/upgrade/typescript.test.ts: -------------------------------------------------------------------------------- 1 | import { Linter } from "eslint"; 2 | 3 | import { defaultInputConfig, makeTestService } from "e2e/helpers"; 4 | import { ClinterModeInfo, TypescriptInfo } from "types"; 5 | 6 | describe("ESLint Base Configuration Upgrade Mode", () => { 7 | it("should return a typescript eslint configuration with an override and no typescript-eslint in base rules", async () => { 8 | const testService = makeTestService(); 9 | 10 | const initialConfig: Linter.Config = { 11 | extends: [ 12 | "plugin:@typescript-eslint/recommended", 13 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 14 | ], 15 | rules: { 16 | "@typescript-eslint/prefer-optional-chain": "error", 17 | "@typescript-eslint/prefer-nullish-coalescing": "error", 18 | "@typescript-eslint/strict-boolean-expressions": "error", 19 | }, 20 | }; 21 | 22 | const tsConfig = { 23 | compilerOptions: { 24 | strict: true, 25 | }, 26 | }; 27 | 28 | testService.loadInputConfig({ 29 | ...defaultInputConfig, 30 | modeConfig: { mode: ClinterModeInfo.Upgrade }, 31 | generatorConfig: { ...defaultInputConfig.generatorConfig, typescript: TypescriptInfo.WithTypeChecking }, 32 | }); 33 | testService.loadInitialLinterConfig(JSON.stringify(initialConfig)); 34 | 35 | testService.loadInitialTSConfig(tsConfig); 36 | 37 | await testService.runClinter(); 38 | const outputConfig = testService.getOutputConfig(); 39 | 40 | expect(outputConfig).toMatchSnapshot(`eslint-typescript-upgrade`); 41 | }); 42 | 43 | it("should return a typescript eslint configuration with no type checking with an override and no typescript-eslint in base rules", async () => { 44 | const testService = makeTestService(); 45 | 46 | const initialConfig: Linter.Config = { 47 | extends: [ 48 | "plugin:@typescript-eslint/recommended", 49 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 50 | ], 51 | rules: { 52 | "@typescript-eslint/prefer-optional-chain": "error", 53 | }, 54 | }; 55 | 56 | const tsConfig = { 57 | compilerOptions: { 58 | strict: true, 59 | }, 60 | }; 61 | 62 | testService.loadInputConfig({ 63 | ...defaultInputConfig, 64 | modeConfig: { mode: ClinterModeInfo.Upgrade }, 65 | generatorConfig: { ...defaultInputConfig.generatorConfig, typescript: TypescriptInfo.NoTypeChecking }, 66 | }); 67 | testService.loadInitialLinterConfig(JSON.stringify(initialConfig)); 68 | testService.loadInitialTSConfig(tsConfig); 69 | await testService.runClinter(); 70 | const outputConfig = testService.getOutputConfig(); 71 | 72 | expect(outputConfig).toMatchSnapshot(`eslint-typescript-upgrade-no-type-checking`); 73 | }); 74 | 75 | it("should return a typescript eslint configuration with no type checking with a TSConfig without strict null checks", async () => { 76 | const testService = makeTestService(); 77 | 78 | const initialConfig: Linter.Config = { 79 | extends: [ 80 | "plugin:@typescript-eslint/recommended", 81 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 82 | ], 83 | rules: { 84 | "@typescript-eslint/prefer-optional-chain": "error", 85 | }, 86 | }; 87 | 88 | const tsConfig = { 89 | compilerOptions: { 90 | strict: true, 91 | }, 92 | }; 93 | 94 | testService.loadInputConfig({ 95 | ...defaultInputConfig, 96 | modeConfig: { mode: ClinterModeInfo.Upgrade }, 97 | generatorConfig: { ...defaultInputConfig.generatorConfig, typescript: TypescriptInfo.NoTypeChecking }, 98 | }); 99 | testService.loadInitialLinterConfig(JSON.stringify(initialConfig)); 100 | testService.loadInitialTSConfig(tsConfig); 101 | await testService.runClinter(); 102 | const outputConfig = testService.getOutputConfig(); 103 | 104 | expect(outputConfig).toMatchSnapshot(`eslint-typescript-upgrade-no-type-checking`); 105 | }); 106 | }); 107 | -------------------------------------------------------------------------------- /src/generator/__tests__/__snapshots__/generate.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`generateEslintConfig Generator Mode React should return a Vue configuration 1`] = ` 4 | Object { 5 | "extends": Array [ 6 | "eslint:recommended", 7 | "plugin:vue/essential", 8 | ], 9 | "parserOptions": Object { 10 | "parser": "babel-eslint", 11 | }, 12 | "plugins": Array [ 13 | "import", 14 | ], 15 | "root": true, 16 | "rules": Object { 17 | "curly": Array [ 18 | "error", 19 | "all", 20 | ], 21 | "eqeqeq": Array [ 22 | "error", 23 | "smart", 24 | ], 25 | "import/no-extraneous-dependencies": Array [ 26 | "error", 27 | Object { 28 | "devDependencies": true, 29 | "optionalDependencies": false, 30 | "peerDependencies": false, 31 | }, 32 | ], 33 | "import/order": Array [ 34 | "error", 35 | Object { 36 | "groups": Array [ 37 | Array [ 38 | "external", 39 | "builtin", 40 | ], 41 | "internal", 42 | Array [ 43 | "parent", 44 | "sibling", 45 | "index", 46 | ], 47 | ], 48 | }, 49 | ], 50 | "no-console": "off", 51 | "no-debugger": "off", 52 | "no-shadow": Array [ 53 | "error", 54 | Object { 55 | "hoist": "all", 56 | }, 57 | ], 58 | "padding-line-between-statements": Array [ 59 | "error", 60 | Object { 61 | "blankLine": "always", 62 | "next": "return", 63 | "prev": "*", 64 | }, 65 | ], 66 | "prefer-const": "error", 67 | "sort-imports": Array [ 68 | "error", 69 | Object { 70 | "ignoreCase": true, 71 | "ignoreDeclarationSort": true, 72 | "ignoreMemberSort": false, 73 | "memberSyntaxSortOrder": Array [ 74 | "none", 75 | "all", 76 | "multiple", 77 | "single", 78 | ], 79 | }, 80 | ], 81 | }, 82 | } 83 | `; 84 | 85 | exports[`generateEslintConfig Generator Mode React should return a react configuration 1`] = ` 86 | Object { 87 | "extends": Array [ 88 | "eslint:recommended", 89 | "react-app", 90 | "plugin:jsx-a11y/recommended", 91 | ], 92 | "plugins": Array [ 93 | "import", 94 | "jsx-a11y", 95 | ], 96 | "root": true, 97 | "rules": Object { 98 | "curly": Array [ 99 | "error", 100 | "all", 101 | ], 102 | "eqeqeq": Array [ 103 | "error", 104 | "smart", 105 | ], 106 | "import/no-extraneous-dependencies": Array [ 107 | "error", 108 | Object { 109 | "devDependencies": true, 110 | "optionalDependencies": false, 111 | "peerDependencies": false, 112 | }, 113 | ], 114 | "import/order": Array [ 115 | "error", 116 | Object { 117 | "groups": Array [ 118 | Array [ 119 | "external", 120 | "builtin", 121 | ], 122 | "internal", 123 | Array [ 124 | "parent", 125 | "sibling", 126 | "index", 127 | ], 128 | ], 129 | }, 130 | ], 131 | "no-shadow": Array [ 132 | "error", 133 | Object { 134 | "hoist": "all", 135 | }, 136 | ], 137 | "padding-line-between-statements": Array [ 138 | "error", 139 | Object { 140 | "blankLine": "always", 141 | "next": "return", 142 | "prev": "*", 143 | }, 144 | ], 145 | "prefer-const": "error", 146 | "react-hooks/exhaustive-deps": "warn", 147 | "react-hooks/rules-of-hooks": "error", 148 | "react/no-string-refs": "warn", 149 | "sort-imports": Array [ 150 | "error", 151 | Object { 152 | "ignoreCase": true, 153 | "ignoreDeclarationSort": true, 154 | "ignoreMemberSort": false, 155 | "memberSyntaxSortOrder": Array [ 156 | "none", 157 | "all", 158 | "multiple", 159 | "single", 160 | ], 161 | }, 162 | ], 163 | }, 164 | "settings": Object { 165 | "react": Object { 166 | "version": "detect", 167 | }, 168 | }, 169 | } 170 | `; 171 | 172 | exports[`generateEslintConfig Generator Mode TypeScript should return a TS configuration with type checking 1`] = ` 173 | Object { 174 | "extends": Array [ 175 | "eslint:recommended", 176 | ], 177 | "overrides": Array [ 178 | Object { 179 | "extends": Array [ 180 | "plugin:@typescript-eslint/recommended", 181 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 182 | ], 183 | "files": Array [ 184 | "**/*.ts?(x)", 185 | ], 186 | "parser": "@typescript-eslint/parser", 187 | "parserOptions": Object { 188 | "project": "tsconfig.json", 189 | }, 190 | "rules": Object { 191 | "@typescript-eslint/no-shadow": "error", 192 | "@typescript-eslint/no-unnecessary-boolean-literal-compare": "error", 193 | "@typescript-eslint/no-unnecessary-condition": "error", 194 | "@typescript-eslint/no-unnecessary-type-arguments": "error", 195 | "@typescript-eslint/prefer-nullish-coalescing": "error", 196 | "@typescript-eslint/prefer-optional-chain": "error", 197 | "@typescript-eslint/prefer-string-starts-ends-with": "error", 198 | "@typescript-eslint/restrict-template-expressions": Array [ 199 | "error", 200 | Object { 201 | "allowBoolean": true, 202 | "allowNumber": true, 203 | }, 204 | ], 205 | "@typescript-eslint/strict-boolean-expressions": Array [ 206 | "error", 207 | Object { 208 | "allowNullableObject": true, 209 | "allowNumber": false, 210 | "allowString": false, 211 | }, 212 | ], 213 | "@typescript-eslint/switch-exhaustiveness-check": "error", 214 | "no-shadow": "off", 215 | }, 216 | }, 217 | ], 218 | "plugins": Array [ 219 | "import", 220 | ], 221 | "root": true, 222 | "rules": Object { 223 | "curly": Array [ 224 | "error", 225 | "all", 226 | ], 227 | "eqeqeq": Array [ 228 | "error", 229 | "smart", 230 | ], 231 | "import/no-extraneous-dependencies": Array [ 232 | "error", 233 | Object { 234 | "devDependencies": true, 235 | "optionalDependencies": false, 236 | "peerDependencies": false, 237 | }, 238 | ], 239 | "import/order": Array [ 240 | "error", 241 | Object { 242 | "groups": Array [ 243 | Array [ 244 | "external", 245 | "builtin", 246 | ], 247 | "internal", 248 | Array [ 249 | "parent", 250 | "sibling", 251 | "index", 252 | ], 253 | ], 254 | }, 255 | ], 256 | "no-shadow": Array [ 257 | "error", 258 | Object { 259 | "hoist": "all", 260 | }, 261 | ], 262 | "padding-line-between-statements": Array [ 263 | "error", 264 | Object { 265 | "blankLine": "always", 266 | "next": "return", 267 | "prev": "*", 268 | }, 269 | ], 270 | "prefer-const": "error", 271 | "sort-imports": Array [ 272 | "error", 273 | Object { 274 | "ignoreCase": true, 275 | "ignoreDeclarationSort": true, 276 | "ignoreMemberSort": false, 277 | "memberSyntaxSortOrder": Array [ 278 | "none", 279 | "all", 280 | "multiple", 281 | "single", 282 | ], 283 | }, 284 | ], 285 | }, 286 | } 287 | `; 288 | 289 | exports[`generateEslintConfig Generator Mode TypeScript should return a TS configuration without type checking 1`] = ` 290 | Object { 291 | "extends": Array [ 292 | "eslint:recommended", 293 | ], 294 | "overrides": Array [ 295 | Object { 296 | "extends": Array [ 297 | "plugin:@typescript-eslint/recommended", 298 | ], 299 | "files": Array [ 300 | "**/*.ts?(x)", 301 | ], 302 | "parser": "@typescript-eslint/parser", 303 | "parserOptions": Object { 304 | "project": "tsconfig.json", 305 | }, 306 | "rules": Object { 307 | "@typescript-eslint/no-shadow": "error", 308 | "@typescript-eslint/prefer-optional-chain": "error", 309 | "no-shadow": "off", 310 | }, 311 | }, 312 | ], 313 | "plugins": Array [ 314 | "import", 315 | ], 316 | "root": true, 317 | "rules": Object { 318 | "curly": Array [ 319 | "error", 320 | "all", 321 | ], 322 | "eqeqeq": Array [ 323 | "error", 324 | "smart", 325 | ], 326 | "import/no-extraneous-dependencies": Array [ 327 | "error", 328 | Object { 329 | "devDependencies": true, 330 | "optionalDependencies": false, 331 | "peerDependencies": false, 332 | }, 333 | ], 334 | "import/order": Array [ 335 | "error", 336 | Object { 337 | "groups": Array [ 338 | Array [ 339 | "external", 340 | "builtin", 341 | ], 342 | "internal", 343 | Array [ 344 | "parent", 345 | "sibling", 346 | "index", 347 | ], 348 | ], 349 | }, 350 | ], 351 | "no-shadow": Array [ 352 | "error", 353 | Object { 354 | "hoist": "all", 355 | }, 356 | ], 357 | "padding-line-between-statements": Array [ 358 | "error", 359 | Object { 360 | "blankLine": "always", 361 | "next": "return", 362 | "prev": "*", 363 | }, 364 | ], 365 | "prefer-const": "error", 366 | "sort-imports": Array [ 367 | "error", 368 | Object { 369 | "ignoreCase": true, 370 | "ignoreDeclarationSort": true, 371 | "ignoreMemberSort": false, 372 | "memberSyntaxSortOrder": Array [ 373 | "none", 374 | "all", 375 | "multiple", 376 | "single", 377 | ], 378 | }, 379 | ], 380 | }, 381 | } 382 | `; 383 | -------------------------------------------------------------------------------- /src/generator/__tests__/__snapshots__/upgrade.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`generateEslintConfig Upgrade Mode Simple eslint config should return a ESLint config without changing the order of the original config fields 1`] = ` 4 | Object { 5 | "env": Object { 6 | "browser": true, 7 | "node": true, 8 | }, 9 | "extends": Array [ 10 | "CUSTOM-ESLINT-CONFIG", 11 | "eslint:recommended", 12 | ], 13 | "plugins": Array [ 14 | "import", 15 | ], 16 | "root": true, 17 | "rules": Object { 18 | "curly": Array [ 19 | "error", 20 | "all", 21 | ], 22 | "custom-rule-name": "error", 23 | "eqeqeq": Array [ 24 | "error", 25 | "smart", 26 | ], 27 | "import/no-extraneous-dependencies": Array [ 28 | "error", 29 | Object { 30 | "devDependencies": true, 31 | "optionalDependencies": false, 32 | "peerDependencies": false, 33 | }, 34 | ], 35 | "import/order": Array [ 36 | "error", 37 | Object { 38 | "groups": Array [ 39 | Array [ 40 | "external", 41 | "builtin", 42 | ], 43 | "internal", 44 | Array [ 45 | "parent", 46 | "sibling", 47 | "index", 48 | ], 49 | ], 50 | }, 51 | ], 52 | "no-shadow": Array [ 53 | "error", 54 | Object { 55 | "hoist": "all", 56 | }, 57 | ], 58 | "padding-line-between-statements": Array [ 59 | "error", 60 | Object { 61 | "blankLine": "always", 62 | "next": "return", 63 | "prev": "*", 64 | }, 65 | ], 66 | "prefer-const": "error", 67 | "sort-imports": Array [ 68 | "error", 69 | Object { 70 | "ignoreCase": true, 71 | "ignoreDeclarationSort": true, 72 | "ignoreMemberSort": false, 73 | "memberSyntaxSortOrder": Array [ 74 | "none", 75 | "all", 76 | "multiple", 77 | "single", 78 | ], 79 | }, 80 | ], 81 | }, 82 | } 83 | `; 84 | -------------------------------------------------------------------------------- /src/generator/__tests__/generate.test.ts: -------------------------------------------------------------------------------- 1 | import { generateEslintConfig } from "generator/generate"; 2 | import { FormatterInfo, FrontFrameworkInfo, TestFrameworkInfo, TypescriptInfo } from "types"; 3 | import { eslintBaseConfig } from "generator/base-configs"; 4 | 5 | describe("generateEslintConfig", () => { 6 | describe("Generator Mode", () => { 7 | describe("Simple eslint config", () => { 8 | it("should return a simple eslint configuration when no special answers are provided", () => { 9 | expect( 10 | generateEslintConfig({ 11 | typescript: TypescriptInfo.None, 12 | env: [], 13 | formatter: FormatterInfo.None, 14 | frontFramework: FrontFrameworkInfo.None, 15 | test: TestFrameworkInfo.None, 16 | }) 17 | ).toStrictEqual(eslintBaseConfig); 18 | }); 19 | }); 20 | 21 | describe("TypeScript", () => { 22 | it("should return a TS configuration with type checking", () => { 23 | const config = generateEslintConfig({ 24 | typescript: TypescriptInfo.WithTypeChecking, 25 | env: [], 26 | formatter: FormatterInfo.None, 27 | frontFramework: FrontFrameworkInfo.None, 28 | test: TestFrameworkInfo.None, 29 | }); 30 | 31 | expect(config).toMatchSnapshot(); 32 | expect(config.overrides?.[0].extends).toContain("plugin:@typescript-eslint/recommended"); 33 | expect(config.overrides?.[0].extends).toContain( 34 | "plugin:@typescript-eslint/recommended-requiring-type-checking" 35 | ); 36 | }); 37 | 38 | it("should return a TS configuration without type checking", () => { 39 | const config = generateEslintConfig({ 40 | typescript: TypescriptInfo.NoTypeChecking, 41 | env: [], 42 | formatter: FormatterInfo.None, 43 | frontFramework: FrontFrameworkInfo.None, 44 | test: TestFrameworkInfo.None, 45 | }); 46 | 47 | expect(config).toMatchSnapshot(); 48 | expect(config.overrides?.[0].extends).toContain("plugin:@typescript-eslint/recommended"); 49 | }); 50 | }); 51 | 52 | describe("React", () => { 53 | it("should return a react configuration", () => { 54 | const config = generateEslintConfig({ 55 | typescript: TypescriptInfo.None, 56 | env: [], 57 | formatter: FormatterInfo.None, 58 | frontFramework: FrontFrameworkInfo.React, 59 | test: TestFrameworkInfo.None, 60 | }); 61 | 62 | expect(config).toMatchSnapshot(); 63 | expect(config.extends).toContain("react-app"); 64 | }); 65 | }); 66 | 67 | describe("React", () => { 68 | it("should return a Vue configuration", () => { 69 | const config = generateEslintConfig({ 70 | typescript: TypescriptInfo.None, 71 | env: [], 72 | formatter: FormatterInfo.None, 73 | frontFramework: FrontFrameworkInfo.Vue, 74 | test: TestFrameworkInfo.None, 75 | }); 76 | 77 | expect(config).toMatchSnapshot(); 78 | expect(config.extends).toContain("plugin:vue/essential"); 79 | }); 80 | }); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /src/generator/__tests__/upgrade.test.ts: -------------------------------------------------------------------------------- 1 | import { generateEslintConfig } from "generator/generate"; 2 | import { FormatterInfo, FrontFrameworkInfo, TestFrameworkInfo, TypescriptInfo } from "types"; 3 | import { eslintBaseConfig } from "generator/base-configs"; 4 | import { Linter } from "eslint"; 5 | 6 | describe("generateEslintConfig", () => { 7 | describe("Upgrade Mode", () => { 8 | describe("Simple eslint config", () => { 9 | it("should return the base eslint config when no start config is given", () => { 10 | expect( 11 | generateEslintConfig( 12 | { 13 | typescript: TypescriptInfo.None, 14 | env: [], 15 | formatter: FormatterInfo.None, 16 | frontFramework: FrontFrameworkInfo.None, 17 | test: TestFrameworkInfo.None, 18 | }, 19 | {} 20 | ) 21 | ).toStrictEqual(eslintBaseConfig); 22 | }); 23 | 24 | it("should return a ESLint config with empty fields removed", () => { 25 | expect( 26 | generateEslintConfig( 27 | { 28 | typescript: TypescriptInfo.None, 29 | env: [], 30 | formatter: FormatterInfo.None, 31 | frontFramework: FrontFrameworkInfo.None, 32 | test: TestFrameworkInfo.None, 33 | }, 34 | { 35 | env: {}, 36 | } 37 | ) 38 | ).toStrictEqual(eslintBaseConfig); 39 | }); 40 | 41 | it("should return a ESLint config without changing the order of the original config fields", () => { 42 | const customESLintConfig: Linter.Config = { 43 | env: { 44 | node: true, 45 | browser: true, 46 | }, 47 | extends: ["CUSTOM-ESLINT-CONFIG"], 48 | root: true, 49 | rules: { 50 | "custom-rule-name": "error", 51 | }, 52 | }; 53 | expect( 54 | generateEslintConfig( 55 | { 56 | typescript: TypescriptInfo.None, 57 | env: [], 58 | formatter: FormatterInfo.None, 59 | frontFramework: FrontFrameworkInfo.None, 60 | test: TestFrameworkInfo.None, 61 | }, 62 | customESLintConfig 63 | ) 64 | ).toMatchSnapshot(); 65 | }); 66 | }); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /src/generator/base-configs/envESLintConfig.ts: -------------------------------------------------------------------------------- 1 | import { Linter } from "eslint"; 2 | 3 | export const browserESLintEnvConfig: Linter.Config = { 4 | env: { 5 | browser: true, 6 | es6: true, 7 | }, 8 | }; 9 | 10 | export const nodeESLintEnvConfig: Linter.Config = { 11 | env: { 12 | node: true, 13 | es6: true, 14 | }, 15 | }; 16 | 17 | export const jestESLintEnvConfig: Linter.Config = { 18 | env: { 19 | jest: true, 20 | }, 21 | }; 22 | 23 | export const jestESLintDependencies = ["eslint-plugin-jest"]; 24 | -------------------------------------------------------------------------------- /src/generator/base-configs/eslintBaseConfig.ts: -------------------------------------------------------------------------------- 1 | import { Linter } from "eslint"; 2 | 3 | export const eslintBaseConfig: Linter.Config = { 4 | root: true, 5 | extends: ["eslint:recommended"], 6 | plugins: ["import"], 7 | rules: { 8 | curly: ["error", "all"], 9 | eqeqeq: ["error", "smart"], 10 | "import/no-extraneous-dependencies": [ 11 | "error", 12 | { devDependencies: true, optionalDependencies: false, peerDependencies: false }, 13 | ], 14 | "no-shadow": [ 15 | "error", 16 | { 17 | hoist: "all", 18 | }, 19 | ], 20 | "prefer-const": "error", 21 | "import/order": [ 22 | "error", 23 | { 24 | groups: [["external", "builtin"], "internal", ["parent", "sibling", "index"]], 25 | }, 26 | ], 27 | "sort-imports": [ 28 | "error", 29 | { 30 | ignoreCase: true, 31 | ignoreDeclarationSort: true, 32 | ignoreMemberSort: false, 33 | memberSyntaxSortOrder: ["none", "all", "multiple", "single"], 34 | }, 35 | ], 36 | "padding-line-between-statements": ["error", { blankLine: "always", prev: "*", next: "return" }], 37 | }, 38 | }; 39 | 40 | export const eslintBaseDependencies = ["eslint", "eslint-plugin-import"]; 41 | -------------------------------------------------------------------------------- /src/generator/base-configs/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./envESLintConfig"; 2 | export * from "./eslintBaseConfig"; 3 | export * from "./prettierConfig"; 4 | export * from "./prettierESLintConfig"; 5 | export * from "./reactESLintConfig"; 6 | export * from "./typescriptEslintConfig"; 7 | export * from "./vueESLintConfig"; 8 | -------------------------------------------------------------------------------- /src/generator/base-configs/prettierConfig.ts: -------------------------------------------------------------------------------- 1 | import { Options } from "prettier"; 2 | 3 | export const prettierConfig: Options = { 4 | printWidth: 120, 5 | semi: true, 6 | tabWidth: 2, 7 | }; 8 | -------------------------------------------------------------------------------- /src/generator/base-configs/prettierESLintConfig.ts: -------------------------------------------------------------------------------- 1 | import { Linter } from "eslint"; 2 | 3 | export const prettierESLintConfig: Linter.Config = { 4 | extends: ["plugin:prettier/recommended"], 5 | }; 6 | 7 | export const prettierESLintDependencies = ["prettier", "eslint-config-prettier", "eslint-plugin-prettier"]; 8 | -------------------------------------------------------------------------------- /src/generator/base-configs/reactESLintConfig.ts: -------------------------------------------------------------------------------- 1 | import { Linter } from "eslint"; 2 | 3 | export const reactESLintConfig: Linter.Config = { 4 | settings: { 5 | react: { 6 | version: "detect", 7 | }, 8 | }, 9 | extends: ["react-app", "plugin:jsx-a11y/recommended"], 10 | rules: { 11 | "react/no-string-refs": "warn", 12 | "react-hooks/rules-of-hooks": "error", 13 | "react-hooks/exhaustive-deps": "warn", 14 | }, 15 | plugins: ["jsx-a11y"], 16 | }; 17 | 18 | export const reactTypescriptESLintConfig: Linter.Config = { 19 | rules: { 20 | "@typescript-eslint/explicit-function-return-type": "off", 21 | }, 22 | }; 23 | 24 | export const reactESLintDependencies = ["eslint-plugin-react-app", "eslint-config-react-app", "eslint-plugin-jsx-a11y"]; 25 | -------------------------------------------------------------------------------- /src/generator/base-configs/typescriptEslintConfig.ts: -------------------------------------------------------------------------------- 1 | import { Linter } from "eslint"; 2 | import { ESLintOverrider } from "generator/types"; 3 | 4 | export const typescriptBaseEslintConfig: Linter.Config = { 5 | extends: ["plugin:@typescript-eslint/recommended"], 6 | parser: "@typescript-eslint/parser", 7 | parserOptions: { 8 | project: "tsconfig.json", 9 | }, 10 | rules: { 11 | // Prefer using concise optional chain expressions instead of chained logical ands 12 | "@typescript-eslint/prefer-optional-chain": "error", 13 | // Disable JS no-shadow rule 14 | "no-shadow": "off", 15 | // Disallow variable declarations from shadowing variables declared in the outer scope 16 | "@typescript-eslint/no-shadow": "error", 17 | }, 18 | }; 19 | 20 | export const typescriptTypeEslintConfig: Linter.Config = { 21 | extends: ["plugin:@typescript-eslint/recommended-requiring-type-checking"], 22 | rules: { 23 | // Enforce the usage of the nullish coalescing operator instead of logical chaining 24 | "@typescript-eslint/prefer-nullish-coalescing": "error", 25 | // Restricts the types allowed in boolean expressions 26 | "@typescript-eslint/strict-boolean-expressions": [ 27 | "error", 28 | { 29 | allowString: false, 30 | allowNumber: false, 31 | allowNullableObject: true, 32 | }, 33 | ], 34 | // Flags unnecessary equality comparisons against boolean literals 35 | "@typescript-eslint/no-unnecessary-boolean-literal-compare": "error", 36 | // Prevents conditionals where the type is always truthy or always falsy 37 | "@typescript-eslint/no-unnecessary-condition": "error", 38 | // Enforces that type arguments will not be used if not required 39 | "@typescript-eslint/no-unnecessary-type-arguments": "error", 40 | // Enforce the use of String#startsWith and String#endsWith instead of other equivalent methods of checking substrings 41 | "@typescript-eslint/prefer-string-starts-ends-with": "error", 42 | // Exhaustiveness checking in switch with union type 43 | "@typescript-eslint/switch-exhaustiveness-check": "error", 44 | // Prevents the text 'undefined' to appear in a template string 45 | "@typescript-eslint/restrict-template-expressions": [ 46 | "error", 47 | { 48 | allowNumber: true, 49 | allowBoolean: true, 50 | }, 51 | ], 52 | }, 53 | }; 54 | 55 | export const tsOverrider: ESLintOverrider = { 56 | files: ["**/*.ts?(x)"], 57 | }; 58 | 59 | export const typescriptESLintDependencies = ["@typescript-eslint/parser", "@typescript-eslint/eslint-plugin"]; 60 | -------------------------------------------------------------------------------- /src/generator/base-configs/vueESLintConfig.ts: -------------------------------------------------------------------------------- 1 | import { Linter } from "eslint"; 2 | 3 | export const vueESLintConfig: Linter.Config = { 4 | extends: ["plugin:vue/essential"], 5 | parserOptions: { 6 | parser: "babel-eslint", 7 | }, 8 | rules: { 9 | "no-console": process.env.NODE_ENV === "production" ? "warn" : "off", 10 | "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off", 11 | }, 12 | }; 13 | 14 | export const vueESLintDependencies = ["eslint-plugin-vue"]; 15 | -------------------------------------------------------------------------------- /src/generator/env.ts: -------------------------------------------------------------------------------- 1 | import { ESLintGenerator } from "generator/types"; 2 | import { identity, pipe } from "utils/utility"; 3 | import { browserESLintEnvConfig, nodeESLintEnvConfig } from "generator/base-configs"; 4 | import { concatConfig } from "generator/generate"; 5 | import { EnvInfo } from "types"; 6 | 7 | const generateBrowserEnvESLintConfig: ESLintGenerator = (userAnswers) => { 8 | return userAnswers.env.includes(EnvInfo.Browser) ? concatConfig(browserESLintEnvConfig) : identity; 9 | }; 10 | 11 | const generateNodeEnvESLintConfig: ESLintGenerator = (userAnswers) => { 12 | return userAnswers.env.includes(EnvInfo.Node) ? concatConfig(nodeESLintEnvConfig) : identity; 13 | }; 14 | 15 | export const generateEnvESLintConfig: ESLintGenerator = (userAnswers) => { 16 | return pipe(generateBrowserEnvESLintConfig(userAnswers), generateNodeEnvESLintConfig(userAnswers)); 17 | }; 18 | -------------------------------------------------------------------------------- /src/generator/front-framework.ts: -------------------------------------------------------------------------------- 1 | import { ESLintDependencyGenerator, ESLintGenerator } from "generator/types"; 2 | import { identity, pipe } from "utils/utility"; 3 | import { concatConfig, concatDependencies } from "generator/generate"; 4 | import { FrontFrameworkInfo, TypescriptInfo } from "types"; 5 | import { 6 | reactESLintConfig, 7 | reactESLintDependencies, 8 | reactTypescriptESLintConfig, 9 | vueESLintConfig, 10 | vueESLintDependencies, 11 | } from "generator/base-configs"; 12 | 13 | export const generateFrontFrameworkESLintConfig: ESLintGenerator = (userAnswers) => { 14 | switch (userAnswers.frontFramework) { 15 | case FrontFrameworkInfo.None: 16 | return identity; 17 | 18 | case FrontFrameworkInfo.Vue: 19 | return concatConfig(vueESLintConfig); 20 | 21 | case FrontFrameworkInfo.React: 22 | return pipe( 23 | concatConfig(reactESLintConfig), 24 | userAnswers.typescript !== TypescriptInfo.None ? concatConfig(reactTypescriptESLintConfig) : identity 25 | ); 26 | } 27 | }; 28 | 29 | export const getFrontFrameworkESLintDependencies: ESLintDependencyGenerator = (userAnswers) => { 30 | switch (userAnswers.frontFramework) { 31 | case FrontFrameworkInfo.None: 32 | return identity; 33 | 34 | case FrontFrameworkInfo.Vue: 35 | return concatDependencies(vueESLintDependencies); 36 | 37 | case FrontFrameworkInfo.React: 38 | return concatDependencies(reactESLintDependencies); 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /src/generator/generate.ts: -------------------------------------------------------------------------------- 1 | import { Linter } from "eslint"; 2 | import { generateTestESLintConfig, getTestESLintDependencies } from "generator/test-framework"; 3 | import { generateTypescriptESLintConfig, getTypescriptESLintDependencies } from "generator/typescript"; 4 | import { generateEnvESLintConfig } from "generator/env"; 5 | import { generateFrontFrameworkESLintConfig, getFrontFrameworkESLintDependencies } from "generator/front-framework"; 6 | import { cleanPrettierConfig, generatePrettierESlintConfig, getPrettierESLintDependencies } from "generator/prettier"; 7 | import { eslintBaseConfig, eslintBaseDependencies } from "generator/base-configs/eslintBaseConfig"; 8 | import { ESLintDependencyGenerator, ESLintGenerator } from "generator/types"; 9 | import { areArraysEqual, mergeArrays, pipe } from "utils/utility"; 10 | import { ProjectInfoObject } from "types"; 11 | 12 | function addEslintOverride( 13 | overrides: Linter.ConfigOverride[], 14 | newOverride: Linter.ConfigOverride 15 | ): Linter.ConfigOverride[] { 16 | if (overrides.length === 0) { 17 | return [newOverride]; 18 | } 19 | 20 | const [currentOverride, ...rest] = overrides; 21 | 22 | // Configuration overrides are entirely defined by their files property 23 | const areOverrideFilesEqual = areArraysEqual( 24 | typeof currentOverride.files === "string" ? [currentOverride.files] : currentOverride.files, 25 | typeof newOverride.files === "string" ? [newOverride.files] : newOverride.files 26 | ); 27 | 28 | if (currentOverride.parser === newOverride.parser || areOverrideFilesEqual) { 29 | return [concatConfig(newOverride)(currentOverride), ...rest]; 30 | } 31 | 32 | return [currentOverride, ...addEslintOverride(rest, newOverride)]; 33 | } 34 | 35 | function concatEslintOverrides( 36 | prevOverrides: Linter.ConfigOverride[], 37 | nextOverrides: Linter.ConfigOverride[] 38 | ): Linter.ConfigOverride[] { 39 | return nextOverrides.reduce(addEslintOverride, prevOverrides); 40 | } 41 | 42 | function concatESlintObjects>(prevObject: T, nextObject: T): T { 43 | return { ...prevObject, ...nextObject }; 44 | } 45 | 46 | function shouldKeepConfigEntry(configEntry: [string, unknown]): boolean { 47 | const [_, value] = configEntry; 48 | 49 | if (typeof value === "object" && value !== null && Object.keys(value).length === 0) { 50 | return false; 51 | } 52 | 53 | return true; 54 | } 55 | 56 | function cleanObjectConfig(config: T): T { 57 | const entries = Object.entries(config); 58 | 59 | const cleanEntries = entries.filter(shouldKeepConfigEntry); 60 | 61 | return Object.fromEntries(cleanEntries) as T; 62 | } 63 | 64 | function cleanExtendsValue( 65 | extendsValue: string | string[], 66 | filterPredicate: (item: string) => boolean 67 | ): string | string[] { 68 | if (typeof extendsValue === "string") { 69 | return filterPredicate(extendsValue) ? extendsValue : []; 70 | } else { 71 | return extendsValue.filter(filterPredicate); 72 | } 73 | } 74 | 75 | function cleanExtendsField(config: T, filterPredicate: (item: string) => boolean): T { 76 | const { extends: extendsValue } = config; 77 | if (extendsValue === undefined) { 78 | return config; 79 | } 80 | return { 81 | ...config, 82 | extends: cleanExtendsValue(extendsValue, filterPredicate), 83 | }; 84 | } 85 | 86 | export function cleanESLintExtendsField( 87 | config: Linter.Config, 88 | filterPredicate: (item: string) => boolean 89 | ): Linter.Config { 90 | const { overrides, ...rest } = cleanExtendsField(config, filterPredicate); 91 | 92 | const cleanOverrides = overrides?.map((overrideConfig) => cleanExtendsField(overrideConfig, filterPredicate)); 93 | 94 | if (overrides) { 95 | return { ...rest, overrides: cleanOverrides }; 96 | } 97 | 98 | return { ...rest }; 99 | } 100 | 101 | function clearEmptyFields(config: Linter.Config) { 102 | const { overrides, ...rest } = cleanObjectConfig(config); 103 | 104 | const cleanOverrides = overrides?.map(cleanObjectConfig); 105 | 106 | if (overrides) { 107 | return { ...rest, overrides: cleanOverrides }; 108 | } 109 | 110 | return { ...rest }; 111 | } 112 | 113 | function cleanESLintConfig(config: Linter.Config): Linter.Config { 114 | return pipe(cleanPrettierConfig, clearEmptyFields)(config); 115 | } 116 | 117 | function concatESlintArrays(prev: Array | string, next: Array | string): Array { 118 | const prevArray = typeof prev === "string" ? [prev] : prev; 119 | const nextArray = typeof next === "string" ? [next] : next; 120 | 121 | return mergeArrays(prevArray, nextArray); 122 | } 123 | 124 | export function concatConfig(config: T): (prevConfig: T) => T { 125 | return (prevConfig) => ({ 126 | ...prevConfig, 127 | ...config, 128 | extends: concatESlintArrays(prevConfig.extends ?? [], config.extends ?? []), 129 | rules: concatESlintObjects(prevConfig.rules ?? {}, config.rules ?? {}), 130 | plugins: concatESlintArrays(prevConfig.plugins ?? [], config.plugins ?? []), 131 | parserOptions: concatESlintObjects(prevConfig.parserOptions ?? {}, config.parserOptions ?? {}), 132 | env: concatESlintObjects(prevConfig.env ?? {}, config.env ?? {}), 133 | overrides: concatEslintOverrides(prevConfig.overrides ?? [], config.overrides ?? []), 134 | }); 135 | } 136 | 137 | export function concatDependencies(deps2: string[]): (deps: string[]) => string[] { 138 | return (deps1) => mergeArrays(deps1, deps2); 139 | } 140 | 141 | const generateBaseESLintConfig: ESLintGenerator = (_userAnswers) => { 142 | return concatConfig(eslintBaseConfig); 143 | }; 144 | 145 | const getBaseESLintDependencies: ESLintDependencyGenerator = (_userAnswers) => 146 | concatDependencies(eslintBaseDependencies); 147 | 148 | export function generateEslintConfig(userAnswers: ProjectInfoObject, startConfig: Linter.Config = {}): Linter.Config { 149 | return pipe( 150 | generateBaseESLintConfig(userAnswers), 151 | generateTypescriptESLintConfig(userAnswers), 152 | generateEnvESLintConfig(userAnswers), 153 | generateTestESLintConfig(userAnswers), 154 | generateFrontFrameworkESLintConfig(userAnswers), 155 | // Prettier must be at the end of the list to avoid potential conflicts 156 | generatePrettierESlintConfig(userAnswers), 157 | cleanESLintConfig 158 | )(startConfig); 159 | } 160 | 161 | export function getConfigDependencies(userAnswers: ProjectInfoObject): string[] { 162 | return pipe( 163 | getBaseESLintDependencies(userAnswers), 164 | getTypescriptESLintDependencies(userAnswers), 165 | getTestESLintDependencies(userAnswers), 166 | getFrontFrameworkESLintDependencies(userAnswers), 167 | getPrettierESLintDependencies(userAnswers) 168 | )([]); 169 | } 170 | -------------------------------------------------------------------------------- /src/generator/index.ts: -------------------------------------------------------------------------------- 1 | export { generateEslintConfig, getConfigDependencies } from "./generate"; 2 | -------------------------------------------------------------------------------- /src/generator/override.ts: -------------------------------------------------------------------------------- 1 | import { Linter } from "eslint"; 2 | import { ESLintOverrider } from "generator/types"; 3 | 4 | /** 5 | * Transform a config into an override config 6 | * @param overrider the eslint attributes to be added to transform the config to an override config 7 | */ 8 | export function wrapConfigInOverride(overrider: ESLintOverrider): (config: Linter.Config) => Linter.Config { 9 | return (config) => ({ 10 | overrides: [ 11 | { 12 | ...overrider, 13 | ...config, 14 | }, 15 | ], 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /src/generator/prettier.ts: -------------------------------------------------------------------------------- 1 | import { Linter } from "eslint"; 2 | 3 | import { ESLintDependencyGenerator, ESLintGenerator } from "generator/types"; 4 | import { identity, pipe } from "utils/utility"; 5 | import { concatConfig, concatDependencies } from "generator/generate"; 6 | import { FormatterInfo, TypescriptInfo } from "types"; 7 | import { prettierESLintConfig, prettierESLintDependencies, tsOverrider } from "generator/base-configs"; 8 | import { wrapConfigInOverride } from "generator/override"; 9 | import { cleanESLintExtendsField } from "generator/generate"; 10 | 11 | const generatePrettierTypescriptESLintConfig: ESLintGenerator = (userAnswers) => { 12 | switch (userAnswers.typescript) { 13 | case TypescriptInfo.None: 14 | return identity; 15 | 16 | case TypescriptInfo.NoTypeChecking: 17 | case TypescriptInfo.WithTypeChecking: 18 | return concatConfig(wrapConfigInOverride(tsOverrider)(prettierESLintConfig)); 19 | } 20 | }; 21 | 22 | export const generatePrettierESlintConfig: ESLintGenerator = (userAnswers) => { 23 | switch (userAnswers.formatter) { 24 | case FormatterInfo.None: 25 | return identity; 26 | 27 | case FormatterInfo.Prettier: 28 | return pipe(concatConfig(prettierESLintConfig), generatePrettierTypescriptESLintConfig(userAnswers)); 29 | } 30 | }; 31 | 32 | export const getPrettierESLintDependencies: ESLintDependencyGenerator = (userAnswers) => { 33 | switch (userAnswers.formatter) { 34 | case FormatterInfo.None: 35 | return identity; 36 | 37 | case FormatterInfo.Prettier: 38 | return concatDependencies(prettierESLintDependencies); 39 | } 40 | }; 41 | 42 | export const cleanPrettierConfig = (config: Linter.Config): Linter.Config => { 43 | return cleanESLintExtendsField(config, (item: string) => !item.startsWith("prettier/")); 44 | }; 45 | -------------------------------------------------------------------------------- /src/generator/test-framework.ts: -------------------------------------------------------------------------------- 1 | import { ESLintDependencyGenerator, ESLintGenerator } from "generator/types"; 2 | import { identity } from "utils/utility"; 3 | import { concatConfig, concatDependencies } from "generator/generate"; 4 | import { TestFrameworkInfo } from "types"; 5 | import { jestESLintDependencies, jestESLintEnvConfig } from "generator/base-configs"; 6 | 7 | export const generateTestESLintConfig: ESLintGenerator = (userAnswers) => { 8 | switch (userAnswers.test) { 9 | case TestFrameworkInfo.None: 10 | return identity; 11 | 12 | case TestFrameworkInfo.Jest: 13 | return concatConfig(jestESLintEnvConfig); 14 | } 15 | }; 16 | 17 | export const getTestESLintDependencies: ESLintDependencyGenerator = (userAnswers) => { 18 | switch (userAnswers.test) { 19 | case TestFrameworkInfo.None: 20 | return identity; 21 | 22 | case TestFrameworkInfo.Jest: 23 | return concatDependencies(jestESLintDependencies); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /src/generator/types.ts: -------------------------------------------------------------------------------- 1 | import { Linter } from "eslint"; 2 | import { ProjectInfoObject } from "types"; 3 | 4 | export type ESLintGenerator = (userAnswers: ProjectInfoObject) => (config: Linter.Config) => Linter.Config; 5 | 6 | export type ESLintDependencyGenerator = (userAnswers: ProjectInfoObject) => (deps: string[]) => string[]; 7 | 8 | export interface ESLintOverrider { 9 | files: string[]; 10 | } 11 | -------------------------------------------------------------------------------- /src/generator/typescript.ts: -------------------------------------------------------------------------------- 1 | import { Linter } from "eslint"; 2 | 3 | import { TypescriptInfo } from "types"; 4 | import { identity, pipe } from "utils/utility"; 5 | import { ESLintDependencyGenerator, ESLintGenerator } from "generator/types"; 6 | import { concatConfig, concatDependencies } from "generator/generate"; 7 | import { 8 | tsOverrider, 9 | typescriptBaseEslintConfig, 10 | typescriptESLintDependencies, 11 | typescriptTypeEslintConfig, 12 | } from "generator/base-configs/typescriptEslintConfig"; 13 | import { wrapConfigInOverride } from "generator/override"; 14 | 15 | const cleanConfigRules = (config: Linter.Config): Linter.Config => { 16 | const { rules, ...rest } = config; 17 | 18 | if (!rules) { 19 | return { ...rest }; 20 | } 21 | 22 | const newRules = Object.entries(rules) 23 | .filter(([key]) => !key.includes("@typescript-eslint")) 24 | .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}); 25 | 26 | return { rules: newRules, ...rest }; 27 | }; 28 | 29 | const cleanConfigExtendsArray = (config: Linter.Config): Linter.Config => { 30 | const { extends: extendsProperty, ...rest } = config; 31 | 32 | const extendsArray = typeof extendsProperty === "string" ? [extendsProperty] : extendsProperty; 33 | 34 | if (!extendsArray) { 35 | return { ...rest }; 36 | } 37 | 38 | const newExtendsArray = extendsArray.filter((item) => !item.includes("@typescript-eslint")); 39 | 40 | return { extends: newExtendsArray, ...rest }; 41 | }; 42 | 43 | const cleanConfigParser = (config: Linter.Config): Linter.Config => { 44 | const { parser, ...rest } = config; 45 | 46 | if (parser === "@typescript-eslint/parser") { 47 | return { ...rest }; 48 | } 49 | 50 | return { ...config }; 51 | }; 52 | 53 | const cleanConfigParserOptions = (config: Linter.Config): Linter.Config => { 54 | const { parserOptions, ...rest } = config; 55 | 56 | if (parserOptions !== undefined) { 57 | const { project, ...restParserOptions } = parserOptions; 58 | return { ...rest, parserOptions: { ...restParserOptions } }; 59 | } 60 | 61 | return { ...config }; 62 | }; 63 | 64 | const cleanConfigWithTSSetup = pipe( 65 | cleanConfigRules, 66 | cleanConfigExtendsArray, 67 | cleanConfigParser, 68 | cleanConfigParserOptions 69 | ); 70 | 71 | export const generateTypescriptESLintConfig: ESLintGenerator = (userAnswers) => { 72 | const baseConfig = wrapConfigInOverride(tsOverrider)(typescriptBaseEslintConfig); 73 | const typeConfig = wrapConfigInOverride(tsOverrider)( 74 | concatConfig(typescriptTypeEslintConfig)(typescriptBaseEslintConfig) 75 | ); 76 | 77 | switch (userAnswers.typescript) { 78 | case TypescriptInfo.None: 79 | return identity; 80 | 81 | case TypescriptInfo.WithTypeChecking: 82 | return pipe(concatConfig(typeConfig), cleanConfigWithTSSetup); 83 | 84 | case TypescriptInfo.NoTypeChecking: 85 | return pipe(concatConfig(baseConfig), cleanConfigWithTSSetup); 86 | } 87 | }; 88 | 89 | export const getTypescriptESLintDependencies: ESLintDependencyGenerator = (userAnswers) => { 90 | switch (userAnswers.typescript) { 91 | case TypescriptInfo.None: 92 | return identity; 93 | 94 | case TypescriptInfo.WithTypeChecking: 95 | case TypescriptInfo.NoTypeChecking: 96 | return concatDependencies(typescriptESLintDependencies); 97 | } 98 | }; 99 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import signale from "signale"; 4 | import boxen from "boxen"; 5 | import yargs from "yargs/yargs"; 6 | 7 | import { generateEslintConfig, getConfigDependencies } from "generator"; 8 | import { writeEslintConfig } from "writer/linter-config/fileWriter"; 9 | import { createDefaultConfigContainer, findESLintConfigurationFiles } from "parser/linter-config-parser"; 10 | import { ClinterModeInfo, DependenciesConfigObject, ProjectInfoObject, TypescriptInfo } from "types"; 11 | import { installDevDependencies } from "dependencies/dependencies"; 12 | import { assertUnreachable } from "utils/utility"; 13 | import { getClinterSettings } from "parser/clinter-settings"; 14 | import { logClinterSettings } from "logger/clinter-settings"; 15 | import { migrateProjectESLint } from "migration"; 16 | import { hasTSStrictNullChecks } from "parser/typescript-config"; 17 | 18 | async function adaptUserAnswersTSConfig(userAnswers: ProjectInfoObject, dirPath: string): Promise { 19 | if (userAnswers.typescript === TypescriptInfo.None || userAnswers.typescript === TypescriptInfo.NoTypeChecking) { 20 | return userAnswers; 21 | } 22 | 23 | signale.info("Checking TS configuration pour strict null checks"); 24 | 25 | try { 26 | const hasStrictNullChecks = await hasTSStrictNullChecks(dirPath); 27 | if (!hasStrictNullChecks) { 28 | signale.warn( 29 | "Clinter detected you are not using the strictNullChecks rule in your TS configuration ! The linter configuration quality will be impacted as a result. It is recommended to migrate your project to include strict null checks before running Clinter." 30 | ); 31 | 32 | return { ...userAnswers, typescript: TypescriptInfo.NoTypeChecking }; 33 | } 34 | } catch (error) { 35 | signale.error( 36 | "Failed to parse your project's TS configuration file. Defaulting to a strict TS configuration by default with strict null checks" 37 | ); 38 | } 39 | 40 | signale.info("TS configuration successfully checked"); 41 | 42 | return userAnswers; 43 | } 44 | 45 | async function runGeneratorMode( 46 | userAnswers: ProjectInfoObject, 47 | dirPath: string, 48 | dependenciesConfig: DependenciesConfigObject 49 | ) { 50 | signale.info("Generating ESLint configuration ..."); 51 | const eslintConfig = generateEslintConfig(userAnswers); 52 | signale.success("ESLint config generated"); 53 | 54 | if (dependenciesConfig.upgradeDependencies) { 55 | signale.info("Installing required dependencies ..."); 56 | await installDevDependencies(getConfigDependencies(userAnswers), dirPath); 57 | signale.success("All dependencies successfully Installed"); 58 | } else { 59 | signale.info("Skipping dependencies upgrade"); 60 | } 61 | 62 | writeEslintConfig(createDefaultConfigContainer(dirPath, eslintConfig)); 63 | signale.success("ESLint config written to eslintrc.json file"); 64 | } 65 | 66 | async function runUpgradeMode( 67 | userAnswers: ProjectInfoObject, 68 | dirPath: string, 69 | dependenciesConfig: DependenciesConfigObject 70 | ) { 71 | signale.info("Adapting existing ESLint configuration ..."); 72 | const existingConfigContainer = findESLintConfigurationFiles(dirPath)[0]; 73 | const eslintConfig = generateEslintConfig(userAnswers, existingConfigContainer.config); 74 | signale.success("ESLint config generated from previous configuration"); 75 | 76 | if (dependenciesConfig.upgradeDependencies) { 77 | signale.info("Installing required dependencies ..."); 78 | await installDevDependencies(getConfigDependencies(userAnswers), dirPath); 79 | signale.success("All dependencies successfully installed"); 80 | } else { 81 | signale.info("Skipping dependencies upgrade"); 82 | } 83 | 84 | writeEslintConfig({ ...existingConfigContainer, config: eslintConfig }); 85 | signale.success(`ESLint config written to ${existingConfigContainer.file.name}`); 86 | } 87 | 88 | async function main() { 89 | // Check node version 90 | const nodeVersion = parseFloat(process.versions.node); 91 | if (nodeVersion < 12) { 92 | signale.error( 93 | `Node version is not compatible with clinter. Required version: > 12. Installed version: ${nodeVersion}` 94 | ); 95 | process.exit(0); 96 | } 97 | 98 | const { dirPath, inputFile, auto, disableErrors } = yargs(process.argv.slice(2)) 99 | .options({ 100 | dirPath: { 101 | type: "string", 102 | default: process.cwd(), 103 | alias: "path", 104 | description: "The path of the directory where a configuration should be generated or upgraded", 105 | }, 106 | inputFile: { type: "string", description: "Path to a file that contains the clinter input settings" }, 107 | auto: { type: "boolean", description: "Tell clinter to run in automatic or manual mode" }, 108 | disableErrors: { 109 | type: "boolean", 110 | argv: "disable-errors", 111 | description: "Run clinter's automatic fix of all eslint issues by disabling them in the code", 112 | }, 113 | }) 114 | .parseSync(); 115 | 116 | signale.log( 117 | boxen("Welcome to the Clinter ! \n A simple linter config generator and upgrader", { 118 | padding: 1, 119 | borderColor: "green", 120 | align: "center", 121 | float: "center", 122 | margin: 3, 123 | }) 124 | ); 125 | 126 | if (disableErrors === true) { 127 | signale.info("Inserting ignore comments to ease project migration ..."); 128 | const { errorCount, fixableErrorCount } = await migrateProjectESLint(dirPath); 129 | signale.success(`Ignore comments sucessfully inserted for ${errorCount - fixableErrorCount} eslint errors !`); 130 | signale.info(`${fixableErrorCount} fixable errors detected. Run eslint with the --fix option to fix them`); 131 | return; 132 | } 133 | 134 | signale.info("Retrieveing project info and settings ..."); 135 | const { 136 | generatorConfig: clinterSettings, 137 | modeConfig, 138 | migrationModeConfig, 139 | dependenciesConfig, 140 | } = await getClinterSettings(inputFile, auto, dirPath); 141 | signale.success("Project settings successfully retrieved !"); 142 | logClinterSettings({ generatorConfig: clinterSettings, modeConfig, migrationModeConfig, dependenciesConfig }); 143 | 144 | /** 145 | * Check and adapt user answers based on user TS configuration 146 | */ 147 | 148 | const generatorConfig = await adaptUserAnswersTSConfig(clinterSettings, dirPath); 149 | 150 | /** 151 | * Generate new ESLint configuration or adapt from existing one 152 | */ 153 | 154 | switch (modeConfig.mode) { 155 | case ClinterModeInfo.Generator: 156 | await runGeneratorMode(generatorConfig, dirPath, dependenciesConfig); 157 | break; 158 | 159 | case ClinterModeInfo.Upgrade: 160 | await runUpgradeMode(generatorConfig, dirPath, dependenciesConfig); 161 | break; 162 | 163 | default: 164 | assertUnreachable(modeConfig.mode); 165 | } 166 | /** 167 | * Migrate the project by ignoring all errors 168 | */ 169 | if (migrationModeConfig.migration) { 170 | signale.info("Inserting ignore comments to ease project migration ..."); 171 | const { errorCount, fixableErrorCount } = await migrateProjectESLint(dirPath); 172 | signale.success(`Ignore comments sucessfully inserted for ${errorCount - fixableErrorCount} eslint errors !`); 173 | signale.info(`${fixableErrorCount} fixable errors detected. Run eslint with the --fix option to fix them`); 174 | } 175 | } 176 | 177 | void main(); 178 | -------------------------------------------------------------------------------- /src/logger/clinter-settings.ts: -------------------------------------------------------------------------------- 1 | import signale from "signale"; 2 | import { ClinterSettings } from "types"; 3 | 4 | export const logClinterSettings = (settings: ClinterSettings): void => { 5 | console.log(""); 6 | signale.info("Project settings:"); 7 | signale.note("Mode", settings.modeConfig.mode); 8 | signale.note("Typescript", settings.generatorConfig.typescript); 9 | signale.note("Formatter", settings.generatorConfig.formatter); 10 | signale.note("Front Framework", settings.generatorConfig.frontFramework); 11 | signale.note("Environment", settings.generatorConfig.env); 12 | console.log(""); 13 | }; 14 | -------------------------------------------------------------------------------- /src/migration/eslint.ts: -------------------------------------------------------------------------------- 1 | import { ESLint } from "eslint"; 2 | import { pool } from "workerpool"; 3 | 4 | import { MigrationResults } from "migration/types"; 5 | 6 | const computeErrors = async (dirPath: string): Promise => { 7 | const eslint = new ESLint({ 8 | cwd: dirPath, 9 | }); 10 | const results = await eslint.lintFiles("**/*.{js,ts,jsx,tsx}"); 11 | 12 | return results; 13 | }; 14 | 15 | export const migrateProject = async (dirPath: string): Promise => { 16 | const errors = await computeErrors(dirPath); 17 | const workerPool = pool(__dirname + "/insertIgnoreLinesScript.js"); 18 | 19 | const promises = errors.map((error) => 20 | workerPool.exec("insertIgnoreLinesInFile", [error.filePath, error.messages]).catch(console.log) 21 | ); 22 | 23 | await Promise.all(promises); 24 | await workerPool.terminate(); 25 | 26 | return errors.reduce( 27 | (results: MigrationResults, error) => ({ 28 | errorCount: results.errorCount + error.errorCount, 29 | fixableErrorCount: results.fixableErrorCount + error.fixableErrorCount, 30 | }), 31 | { 32 | errorCount: 0, 33 | fixableErrorCount: 0, 34 | } 35 | ); 36 | }; 37 | -------------------------------------------------------------------------------- /src/migration/index.ts: -------------------------------------------------------------------------------- 1 | export { migrateProject as migrateProjectESLint } from "./eslint"; 2 | -------------------------------------------------------------------------------- /src/migration/insertIgnoreLinesScript.ts: -------------------------------------------------------------------------------- 1 | import { Linter } from "eslint"; 2 | import { readFileSync, writeFileSync } from "fs"; 3 | import { worker } from "workerpool"; 4 | 5 | const groupErrorsByLine = (errors: Linter.LintMessage[]): Record => { 6 | return errors.reduce((result: Record, error) => { 7 | const lineNumber = error.line; 8 | if (result[lineNumber] === undefined) { 9 | result[lineNumber] = []; 10 | } 11 | 12 | result[lineNumber].push(error); 13 | 14 | return result; 15 | }, {}); 16 | }; 17 | 18 | const insertErrorIgnoreLines = (sourceLines: string[], errorsByLine: Record): string[] => 19 | (Object.entries(errorsByLine) as unknown as [number, Linter.LintMessage[]][]) 20 | .sort(([lineA], [lineB]) => lineB - lineA) 21 | .reduce((source, [lineNumber, errors]) => { 22 | const mappedErrors = errors 23 | .filter((error) => error.severity === 2 && error.fix === undefined) 24 | .map((error) => error.ruleId); 25 | 26 | if (mappedErrors.length === 0) { 27 | return source; 28 | } 29 | 30 | const errorRulesString = mappedErrors.join(", "); 31 | 32 | const ignoreString = `// eslint-disable-next-line ${errorRulesString}`; 33 | 34 | const indentation = /^\s*/.exec(source[lineNumber - 1])?.[0] ?? ""; 35 | 36 | source.splice(lineNumber - 1, 0, indentation + ignoreString); 37 | 38 | return source; 39 | }, sourceLines); 40 | 41 | const insertIgnoreLinesInFile = (filePath: string, fileErrors: Linter.LintMessage[]) => { 42 | const fileSource = readFileSync(filePath).toString().split("\n"); 43 | 44 | const errorsByLine = groupErrorsByLine(fileErrors); 45 | 46 | const newSource = insertErrorIgnoreLines(fileSource, errorsByLine).join("\n"); 47 | 48 | writeFileSync(filePath, newSource); 49 | }; 50 | 51 | // create a worker and register public functions 52 | worker({ 53 | insertIgnoreLinesInFile, 54 | }); 55 | -------------------------------------------------------------------------------- /src/migration/types.ts: -------------------------------------------------------------------------------- 1 | export interface MigrationResults { 2 | fixableErrorCount: number; 3 | errorCount: number; 4 | } 5 | -------------------------------------------------------------------------------- /src/parser/__tests__/typescript-config.test.ts: -------------------------------------------------------------------------------- 1 | import { getStrictNullChecksOption } from "../typescript-config"; 2 | 3 | describe("TSConfig parser", () => { 4 | it("should return true if strict is true and no strictNullChecks present", () => { 5 | const tsconfig = { 6 | compilerOptions: { 7 | strict: true, 8 | }, 9 | }; 10 | 11 | expect(getStrictNullChecksOption(tsconfig)).toBe(true); 12 | }); 13 | 14 | it("should return true if strict is false and strictNullChecks is true", () => { 15 | const tsconfig = { 16 | compilerOptions: { 17 | strict: false, 18 | strictNullChecks: true, 19 | }, 20 | }; 21 | 22 | expect(getStrictNullChecksOption(tsconfig)).toBe(true); 23 | }); 24 | 25 | it("should return true if strict is true and strictNullChecks is true", () => { 26 | const tsconfig = { 27 | compilerOptions: { 28 | strict: true, 29 | strictNullChecks: true, 30 | }, 31 | }; 32 | 33 | expect(getStrictNullChecksOption(tsconfig)).toBe(true); 34 | }); 35 | 36 | it("should return false by default", () => { 37 | const tsconfig = { 38 | compilerOptions: {}, 39 | }; 40 | 41 | expect(getStrictNullChecksOption(tsconfig)).toBe(false); 42 | }); 43 | 44 | it("should return false if strict is false", () => { 45 | const tsconfig = { 46 | compilerOptions: { 47 | strict: false, 48 | }, 49 | }; 50 | 51 | expect(getStrictNullChecksOption(tsconfig)).toBe(false); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /src/parser/clinter-mode.ts: -------------------------------------------------------------------------------- 1 | import { findESLintConfigurationFiles } from "parser/linter-config-parser"; 2 | import { ClinterModeInfo, ClinterModeInfoObject } from "types"; 3 | 4 | const isEslintConfigSetup = (dirPath: string) => findESLintConfigurationFiles(dirPath).length > 0; 5 | 6 | const makeUpgradeClinterMode = (): ClinterModeInfoObject => ({ 7 | mode: ClinterModeInfo.Upgrade, 8 | }); 9 | 10 | const makeGeneratorClinterMode = (): ClinterModeInfoObject => ({ 11 | mode: ClinterModeInfo.Generator, 12 | }); 13 | 14 | export const inferClinterMode = (dirPath: string): ClinterModeInfoObject => { 15 | return isEslintConfigSetup(dirPath) ? makeUpgradeClinterMode() : makeGeneratorClinterMode(); 16 | }; 17 | -------------------------------------------------------------------------------- /src/parser/clinter-settings-input-parser.ts: -------------------------------------------------------------------------------- 1 | import signale from "signale"; 2 | import { existsSync, readFileSync } from "fs"; 3 | import { ClinterSettings } from "types"; 4 | 5 | export function parseInputConfigFile(inputFileName: string): ClinterSettings { 6 | if (!existsSync(inputFileName)) { 7 | signale.error(`Cannot find InputFile ${inputFileName}`); 8 | process.exit(1); 9 | } 10 | 11 | try { 12 | return JSON.parse(readFileSync(inputFileName, "utf-8")) as ClinterSettings; 13 | } catch (error) { 14 | signale.error("Error parsing InputFile configuration"); 15 | process.exit(1); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/parser/clinter-settings.ts: -------------------------------------------------------------------------------- 1 | import { inferClinterMode } from "parser/clinter-mode"; 2 | import { parseInputConfigFile } from "parser/clinter-settings-input-parser"; 3 | import { parseProjectDependencies } from "parser/project-dependencies"; 4 | import { inferProjectInfo } from "parser/project-info-inferer"; 5 | import { ProjectInfoRetrievalMode } from "parser/project-info-inferer/types"; 6 | import { 7 | promptGeneratorUserQuestions, 8 | promptMigrationModeQuestions, 9 | promptModeUserQuestions, 10 | promptProjectInfoRetrievalModeQuestions, 11 | } from "parser/user-questions"; 12 | import { ClinterSettings } from "types"; 13 | import { assertUnreachable } from "utils/utility"; 14 | 15 | export const getClinterSettings = async ( 16 | inputFile: string | undefined, 17 | auto: boolean | undefined, 18 | dirPath: string 19 | ): Promise => { 20 | if (inputFile !== undefined) { 21 | return parseInputConfigFile(inputFile); 22 | } 23 | 24 | const projectDependencies = parseProjectDependencies(dirPath); 25 | 26 | if (auto === true) { 27 | return { 28 | modeConfig: inferClinterMode(dirPath), 29 | generatorConfig: inferProjectInfo({ dirPath, projectDependencies }), 30 | migrationModeConfig: { 31 | migration: false, 32 | }, 33 | dependenciesConfig: { 34 | upgradeDependencies: true, 35 | }, 36 | }; 37 | } 38 | 39 | const configRetrievalModeInfo = await promptProjectInfoRetrievalModeQuestions(); 40 | 41 | switch (configRetrievalModeInfo.mode) { 42 | case ProjectInfoRetrievalMode.Automatic: 43 | return { 44 | modeConfig: inferClinterMode(dirPath), 45 | generatorConfig: inferProjectInfo({ dirPath, projectDependencies }), 46 | migrationModeConfig: await promptMigrationModeQuestions(), 47 | dependenciesConfig: { 48 | upgradeDependencies: true, 49 | }, 50 | }; 51 | 52 | case ProjectInfoRetrievalMode.Manual: { 53 | const modeConfig = await promptModeUserQuestions(); 54 | const generatorConfig = await promptGeneratorUserQuestions(); 55 | 56 | return { 57 | generatorConfig, 58 | modeConfig, 59 | migrationModeConfig: await promptMigrationModeQuestions(), 60 | dependenciesConfig: { 61 | upgradeDependencies: true, 62 | }, 63 | }; 64 | } 65 | 66 | default: 67 | return assertUnreachable(configRetrievalModeInfo.mode); 68 | } 69 | }; 70 | -------------------------------------------------------------------------------- /src/parser/linter-config-parser/config-container.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | 3 | import { ConfigContainer, FileExtension } from "parser/linter-config-parser/types"; 4 | 5 | export function createDefaultConfigContainer(dirPath: string, config: T): ConfigContainer { 6 | return { 7 | config, 8 | file: { 9 | extension: FileExtension.NONE, 10 | name: path.join(dirPath, ".eslintrc.json"), 11 | }, 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /src/parser/linter-config-parser/eslint.ts: -------------------------------------------------------------------------------- 1 | import { Linter } from "eslint"; 2 | import { ConfigContainer, ConfigFileObject, FileExtension } from "parser/linter-config-parser/types"; 3 | import { parseLinterConfigFiles } from "parser/linter-config-parser/linter-config-parser"; 4 | 5 | const possibleESLintFiles: ConfigFileObject[] = [ 6 | { name: ".eslintrc", extension: FileExtension.NONE }, 7 | { name: ".eslintrc.js", extension: FileExtension.JS }, 8 | { name: ".eslintrc.json", extension: FileExtension.JSON }, 9 | { name: ".eslintrc.yaml", extension: FileExtension.YAML }, 10 | { name: "package.json", extension: FileExtension.JSON, attribute: "eslintConfig" }, 11 | ]; 12 | 13 | export function findESLintConfigurationFiles(dirPath: string): ConfigContainer[] { 14 | return parseLinterConfigFiles(possibleESLintFiles, dirPath); 15 | } 16 | -------------------------------------------------------------------------------- /src/parser/linter-config-parser/index.ts: -------------------------------------------------------------------------------- 1 | export { parseLinterConfigFiles } from "./linter-config-parser"; 2 | export { findESLintConfigurationFiles } from "./eslint"; 3 | export { createDefaultConfigContainer } from "./config-container"; 4 | -------------------------------------------------------------------------------- /src/parser/linter-config-parser/linter-config-parser.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | import { existsSync, readFileSync } from "fs"; 3 | import { parse } from "comment-json"; 4 | import path from "path"; 5 | import { ConfigContainer, ConfigFileObject, FileExtension, LinterConfigs } from "./types"; 6 | 7 | /** 8 | * Maps the FileObject name by joining its name with the dirname 9 | */ 10 | export function mapPath(dirPath: string): (filePath: ConfigFileObject) => ConfigFileObject { 11 | return (filePath: ConfigFileObject) => ({ ...filePath, name: path.join(dirPath, filePath.name) }); 12 | } 13 | 14 | /** 15 | * Check if the filePath exists within the directory 16 | */ 17 | export function checkFilePath(filePath: ConfigFileObject): boolean { 18 | return existsSync(filePath.name); 19 | } 20 | 21 | /** 22 | * Check if the configjuration exists within file object 23 | */ 24 | export function checkIfConfigurationExists(container: ConfigContainer): boolean { 25 | return Boolean(container.config); 26 | } 27 | 28 | function parseJSONFile(configFile: ConfigFileObject): ConfigContainer { 29 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment 30 | const configObject: any = parse(readFileSync(configFile.name, "utf-8"), undefined, true); 31 | return { 32 | // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access 33 | config: (configFile.attribute !== undefined ? configObject[configFile.attribute] : configObject) as T, 34 | file: configFile, 35 | }; 36 | } 37 | 38 | function parseJSFile(configFile: ConfigFileObject): ConfigContainer { 39 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment 40 | const configObject: any = require(configFile.name); 41 | return { 42 | // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access 43 | config: (configFile.attribute !== undefined ? configObject[configFile.attribute] : configObject) as T, 44 | file: configFile, 45 | }; 46 | } 47 | 48 | /** 49 | * Parse files according to their extensions and return their configs 50 | */ 51 | export function parseLinterConfigFiles( 52 | configFiles: ConfigFileObject[], 53 | dirPath = "" 54 | ): ConfigContainer[] { 55 | return configFiles 56 | .map(mapPath(dirPath)) 57 | .filter(checkFilePath) 58 | .map>((configFile) => { 59 | switch (configFile.extension) { 60 | case FileExtension.JSON: 61 | case FileExtension.NONE: 62 | return parseJSONFile(configFile); 63 | 64 | case FileExtension.YAML: 65 | throw "NO PARSER FOR YAML FILES DEFINED"; 66 | 67 | case FileExtension.JS: 68 | return parseJSFile(configFile); 69 | } 70 | }) 71 | .filter(checkIfConfigurationExists); 72 | } 73 | -------------------------------------------------------------------------------- /src/parser/linter-config-parser/types.ts: -------------------------------------------------------------------------------- 1 | import { Options } from "prettier"; 2 | import { Linter } from "eslint"; 3 | 4 | export enum FileExtension { 5 | JSON = "json", 6 | YAML = "yml", 7 | JS = "js", 8 | NONE = "none", 9 | } 10 | 11 | export interface ConfigFileObject { 12 | name: string; 13 | extension: FileExtension; 14 | attribute?: string; 15 | } 16 | 17 | export type LinterConfigs = Options | Linter.Config; 18 | 19 | export interface ConfigContainer { 20 | config: T; 21 | file: ConfigFileObject; 22 | } 23 | -------------------------------------------------------------------------------- /src/parser/package-tool.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | 4 | export enum PackageTool { 5 | NPM = "NPM", 6 | YARN = "YARN", 7 | YARN_BERRY = "YARN_BERRY", 8 | PNPM = "PNPM", 9 | } 10 | 11 | export function getPackageTool(currentDirPath: string = process.cwd()): PackageTool { 12 | if (fs.existsSync(path.resolve(currentDirPath, "package-lock.json"))) { 13 | return PackageTool.NPM; 14 | } 15 | 16 | if (fs.existsSync(path.resolve(currentDirPath, ".yarnrc.yml"))) { 17 | return PackageTool.YARN_BERRY; 18 | } 19 | 20 | if (fs.existsSync(path.resolve(currentDirPath, "yarn.lock"))) { 21 | return PackageTool.YARN; 22 | } 23 | 24 | if (fs.existsSync(path.resolve(currentDirPath, "pnpm-lock.yaml"))) { 25 | return PackageTool.PNPM; 26 | } 27 | 28 | throw new Error("Unable to determine package manager"); 29 | } 30 | -------------------------------------------------------------------------------- /src/parser/project-dependencies.ts: -------------------------------------------------------------------------------- 1 | import { CoreProperties } from "@schemastore/package"; 2 | import path from "path"; 3 | import { readFileSync } from "fs"; 4 | 5 | const PACKAGE_JSON_FILE_NAME = "package.json"; 6 | 7 | export const parseProjectDependencies = (dirPath: string): string[] => { 8 | const packageFile = JSON.parse(readFileSync(path.join(dirPath, PACKAGE_JSON_FILE_NAME), "utf-8")) as CoreProperties; 9 | return packageFile.dependencies ? Object.keys(packageFile.dependencies) : []; 10 | }; 11 | -------------------------------------------------------------------------------- /src/parser/project-info-inferer/index.ts: -------------------------------------------------------------------------------- 1 | export { inferProjectInfo } from "./project-info-inferer"; 2 | -------------------------------------------------------------------------------- /src/parser/project-info-inferer/project-info-inferer.ts: -------------------------------------------------------------------------------- 1 | import { ProjectInfoInferer } from "parser/project-info-inferer/types"; 2 | import path from "path"; 3 | import { existsSync } from "fs"; 4 | import { 5 | EnvInfo, 6 | FormatterInfo, 7 | FrontFrameworkInfo, 8 | ProjectInfoObject, 9 | TestFrameworkInfo, 10 | TypescriptInfo, 11 | } from "types"; 12 | import { pipe } from "utils/utility"; 13 | 14 | const TYPESCRIPT_CONFIG_FILE = "tsconfig.json"; 15 | const JEST_DEPENDENCY = "jest"; 16 | const VUE_DEPENDENCY = "vue"; 17 | const REACT_DEPENDENCY = "react"; 18 | 19 | const makeDefaultProjectInfo = (): ProjectInfoObject => ({ 20 | env: [], 21 | formatter: FormatterInfo.None, 22 | frontFramework: FrontFrameworkInfo.None, 23 | test: TestFrameworkInfo.None, 24 | typescript: TypescriptInfo.None, 25 | }); 26 | 27 | const inferTypescriptInfo = 28 | (dirPath: string): ProjectInfoInferer => 29 | (projectInfo) => { 30 | const isProjectUsingTypescript = existsSync(path.join(dirPath, TYPESCRIPT_CONFIG_FILE)); 31 | return { 32 | ...projectInfo, 33 | typescript: isProjectUsingTypescript ? TypescriptInfo.WithTypeChecking : TypescriptInfo.None, 34 | }; 35 | }; 36 | 37 | const inferTestEnvInfo = 38 | (projectDependencies: string[]): ProjectInfoInferer => 39 | (projectInfo) => { 40 | const isProjectUsingJest = projectDependencies.includes(JEST_DEPENDENCY); 41 | return { 42 | ...projectInfo, 43 | test: isProjectUsingJest ? TestFrameworkInfo.Jest : TestFrameworkInfo.None, 44 | }; 45 | }; 46 | 47 | const inferFrontFrameworkInfo = 48 | (projectDependencies: string[]): ProjectInfoInferer => 49 | (projectInfo) => { 50 | const isProjectUsingVue = projectDependencies.includes(VUE_DEPENDENCY); 51 | const isProjectUsingReact = projectDependencies.includes(REACT_DEPENDENCY); 52 | 53 | return { 54 | ...projectInfo, 55 | frontFramework: isProjectUsingVue 56 | ? FrontFrameworkInfo.Vue 57 | : isProjectUsingReact 58 | ? FrontFrameworkInfo.React 59 | : FrontFrameworkInfo.None, 60 | }; 61 | }; 62 | 63 | const inferFormatterInfo = 64 | (projectDependencies: string[]): ProjectInfoInferer => 65 | (projectInfo) => { 66 | return { 67 | ...projectInfo, 68 | formatter: FormatterInfo.Prettier, 69 | }; 70 | }; 71 | 72 | const inferEnvInfo = (): ProjectInfoInferer => (projectInfo) => { 73 | return { 74 | ...projectInfo, 75 | env: [EnvInfo.Browser, EnvInfo.Node], 76 | }; 77 | }; 78 | 79 | export interface InferProjectInfoParams { 80 | dirPath: string; 81 | projectDependencies: string[]; 82 | } 83 | 84 | export const inferProjectInfo = ({ dirPath, projectDependencies }: InferProjectInfoParams): ProjectInfoObject => 85 | pipe( 86 | inferTypescriptInfo(dirPath), 87 | inferTestEnvInfo(projectDependencies), 88 | inferFrontFrameworkInfo(projectDependencies), 89 | inferFormatterInfo(projectDependencies), 90 | inferEnvInfo() 91 | )(makeDefaultProjectInfo()); 92 | -------------------------------------------------------------------------------- /src/parser/project-info-inferer/types.ts: -------------------------------------------------------------------------------- 1 | import { ProjectInfoObject } from "types"; 2 | 3 | export enum ProjectInfoRetrievalMode { 4 | Automatic = "automatic", 5 | Manual = "manual", 6 | } 7 | 8 | export interface ProjectInfoRetrievalModeObject { 9 | mode: ProjectInfoRetrievalMode; 10 | } 11 | 12 | export type ProjectInfoInferer = (projectInfo: ProjectInfoObject) => ProjectInfoObject; 13 | -------------------------------------------------------------------------------- /src/parser/project-info.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/theodo/clinter/05b0096a6cbf51d767e9648547b343babccd04e4/src/parser/project-info.ts -------------------------------------------------------------------------------- /src/parser/typescript-config.ts: -------------------------------------------------------------------------------- 1 | import { exec } from "child-process-promise"; 2 | import { getPackageTool, PackageTool } from "parser/package-tool"; 3 | 4 | const getTSConfigShellString = (dirPath: string, packageTool: PackageTool): string => { 5 | switch (packageTool) { 6 | case PackageTool.NPM: 7 | return `npx tsc --showConfig -p ${dirPath}`; 8 | 9 | case PackageTool.YARN: 10 | return `yarn --silent tsc --showConfig -p ${dirPath}`; 11 | 12 | case PackageTool.YARN_BERRY: 13 | return `yarn tsc --showConfig -p ${dirPath}`; 14 | 15 | case PackageTool.PNPM: 16 | return `pnpm tsc --showConfig -p ${dirPath}`; 17 | 18 | default: 19 | throw new Error(`Unsupported package tool: !`); 20 | } 21 | }; 22 | 23 | const loadTSConfig = async (dirPath: string): Promise> => { 24 | return new Promise((resolve, reject) => { 25 | exec(getTSConfigShellString(dirPath, getPackageTool(dirPath))) 26 | .then((result) => { 27 | resolve(JSON.parse(result.stdout)); 28 | }) 29 | .catch((error) => { 30 | console.log(error); 31 | reject(error); 32 | }); 33 | }); 34 | }; 35 | 36 | export const getStrictNullChecksOption = (tsconfig: Record): boolean => { 37 | // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return 38 | return tsconfig.compilerOptions?.strictNullChecks ?? tsconfig.compilerOptions?.strict ?? false; 39 | }; 40 | 41 | export const hasTSStrictNullChecks = async (dirPath: string): Promise => { 42 | const tsconfig = await loadTSConfig(dirPath); 43 | return getStrictNullChecksOption(tsconfig); 44 | }; 45 | -------------------------------------------------------------------------------- /src/parser/user-questions/clinter-mode-questions.ts: -------------------------------------------------------------------------------- 1 | import inquirer from "inquirer"; 2 | import { ClinterModeInfo, ClinterModeInfoObject } from "types"; 3 | 4 | const ModeQuestion: inquirer.ListQuestion = { 5 | name: "mode", 6 | default: ClinterModeInfo.Generator, 7 | message: "This tool can either generate a new configuration or upgrade an already existing one. Choose your mode.", 8 | type: "list", 9 | choices: Object.values(ClinterModeInfo), 10 | }; 11 | 12 | export async function promptModeUserQuestions(): Promise { 13 | return inquirer.prompt(ModeQuestion); 14 | } 15 | -------------------------------------------------------------------------------- /src/parser/user-questions/generator-questions.ts: -------------------------------------------------------------------------------- 1 | import inquirer, { QuestionCollection } from "inquirer"; 2 | import { 3 | EnvInfo, 4 | EnvInfoObject, 5 | FormatterInfo, 6 | FormatterInfoObject, 7 | FrontFrameworkInfo, 8 | FrontFrameworkInfoObject, 9 | ProjectInfoObject, 10 | TestFrameworkInfo, 11 | TestFrameworkInfoObject, 12 | TypescriptInfo, 13 | TypescriptInfoObject, 14 | } from "types"; 15 | 16 | const FrontFrameworkQuestion: inquirer.ListQuestion = { 17 | name: "frontFramework", 18 | default: FrontFrameworkInfo.None, 19 | message: "Front Framework", 20 | type: "list", 21 | choices: Object.values(FrontFrameworkInfo), 22 | }; 23 | 24 | const FormatterQuestion: inquirer.ListQuestion = { 25 | name: "formatter", 26 | default: FormatterInfo.Prettier, 27 | message: "Are you using a formatter?", 28 | type: "list", 29 | choices: Object.values(FormatterInfo), 30 | }; 31 | 32 | const TypescriptQuestion: inquirer.ListQuestion = { 33 | name: "typescript", 34 | default: TypescriptInfo.WithTypeChecking, 35 | message: "Are you using typescript? What kind of eslint configuration would you like?", 36 | type: "list", 37 | choices: Object.values(TypescriptInfo), 38 | }; 39 | 40 | const EnvQuestion: inquirer.CheckboxQuestion = { 41 | name: "env", 42 | default: [EnvInfo.Browser, EnvInfo.Node], 43 | message: "For which environment are you developing?", 44 | type: "checkbox", 45 | choices: Object.values(EnvInfo), 46 | }; 47 | 48 | const TestFrameworkQuestion: inquirer.ListQuestion = { 49 | name: "test", 50 | default: TestFrameworkInfo.None, 51 | message: "Will you be writing tests?", 52 | type: "list", 53 | choices: Object.values(TestFrameworkInfo), 54 | }; 55 | 56 | const questions: QuestionCollection = [ 57 | TypescriptQuestion, 58 | FrontFrameworkQuestion, 59 | FormatterQuestion, 60 | EnvQuestion, 61 | TestFrameworkQuestion, 62 | ]; 63 | 64 | export async function promptGeneratorUserQuestions(): Promise { 65 | return inquirer.prompt(questions); 66 | } 67 | -------------------------------------------------------------------------------- /src/parser/user-questions/index.ts: -------------------------------------------------------------------------------- 1 | export { promptGeneratorUserQuestions } from "./generator-questions"; 2 | export { promptModeUserQuestions } from "./clinter-mode-questions"; 3 | export { promptProjectInfoRetrievalModeQuestions } from "./project-info-questions"; 4 | export { promptMigrationModeQuestions } from "./migration-mode-questions"; 5 | -------------------------------------------------------------------------------- /src/parser/user-questions/migration-mode-questions.ts: -------------------------------------------------------------------------------- 1 | import inquirer from "inquirer"; 2 | import { MigrationModeInfoObject } from "types"; 3 | 4 | const MigrationModeQuestion: inquirer.ConfirmQuestion = { 5 | name: "migration", 6 | default: false, 7 | message: "Should clinter migrate your project by inserting error ignores in the code ?", 8 | type: "confirm", 9 | }; 10 | 11 | export async function promptMigrationModeQuestions(): Promise { 12 | return inquirer.prompt(MigrationModeQuestion); 13 | } 14 | -------------------------------------------------------------------------------- /src/parser/user-questions/project-info-questions.ts: -------------------------------------------------------------------------------- 1 | import inquirer from "inquirer"; 2 | import { ProjectInfoRetrievalMode, ProjectInfoRetrievalModeObject } from "parser/project-info-inferer/types"; 3 | 4 | const ModeQuestion: inquirer.ListQuestion = { 5 | name: "mode", 6 | default: ProjectInfoRetrievalMode.Automatic, 7 | message: "Choose how clinter should retrieve project settings", 8 | type: "list", 9 | choices: Object.values(ProjectInfoRetrievalMode), 10 | }; 11 | 12 | export async function promptProjectInfoRetrievalModeQuestions(): Promise { 13 | return inquirer.prompt(ModeQuestion); 14 | } 15 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export enum FormatterInfo { 2 | None = "None", 3 | Prettier = "Prettier", 4 | } 5 | 6 | export interface FormatterInfoObject { 7 | formatter: FormatterInfo; 8 | } 9 | 10 | export enum FrontFrameworkInfo { 11 | None = "None", 12 | Vue = "Vue", 13 | React = "React", 14 | } 15 | 16 | export interface FrontFrameworkInfoObject { 17 | frontFramework: FrontFrameworkInfo; 18 | } 19 | 20 | export enum TypescriptInfo { 21 | None = "None", 22 | WithTypeChecking = "With type checking", 23 | NoTypeChecking = "Without type checking", 24 | } 25 | 26 | export interface TypescriptInfoObject { 27 | typescript: TypescriptInfo; 28 | } 29 | 30 | export enum EnvInfo { 31 | Browser = "Browser", 32 | Node = "Node", 33 | } 34 | 35 | export interface EnvInfoObject { 36 | env: EnvInfo[]; 37 | } 38 | 39 | export enum TestFrameworkInfo { 40 | None = "None", 41 | Jest = "Jest", 42 | } 43 | 44 | export interface TestFrameworkInfoObject { 45 | test: TestFrameworkInfo; 46 | } 47 | 48 | export type ProjectInfoObject = FormatterInfoObject & 49 | FrontFrameworkInfoObject & 50 | TypescriptInfoObject & 51 | EnvInfoObject & 52 | TestFrameworkInfoObject; 53 | 54 | export enum ClinterModeInfo { 55 | Generator = "Generator Mode", 56 | Upgrade = "Upgrade Mode", 57 | } 58 | 59 | export interface ClinterModeInfoObject { 60 | mode: ClinterModeInfo; 61 | } 62 | 63 | export interface MigrationModeInfoObject { 64 | migration: boolean; 65 | } 66 | 67 | export interface DependenciesConfigObject { 68 | upgradeDependencies: boolean; 69 | } 70 | 71 | export interface ClinterSettings { 72 | generatorConfig: ProjectInfoObject; 73 | modeConfig: ClinterModeInfoObject; 74 | migrationModeConfig: MigrationModeInfoObject; 75 | dependenciesConfig: DependenciesConfigObject; 76 | } 77 | -------------------------------------------------------------------------------- /src/utils/utility.ts: -------------------------------------------------------------------------------- 1 | export const pipe = (fn1: (a: R) => R, ...fns: Array<(a: R) => R>): ((a: R) => R) => 2 | fns.reduce((prevFn, nextFn) => (value) => nextFn(prevFn(value)), fn1); 3 | 4 | export const identity = (arg: T): T => arg; 5 | 6 | export const mergeArrays = (array1: Array, array2: Array): Array => { 7 | const jointArray = [...array1, ...array2].reverse(); 8 | 9 | const uniqueArray = jointArray.filter((item, index) => jointArray.indexOf(item) === index); 10 | return uniqueArray.reverse(); 11 | }; 12 | 13 | export const assertUnreachable = (reason: string): never => { 14 | throw new Error(`Unreachable code path: ${reason}`); 15 | }; 16 | 17 | export const areArraysEqual = (array1: T[], array2: T[]): boolean => { 18 | if (array1.length !== array2.length) { 19 | return false; 20 | } 21 | 22 | const length = array1.length; 23 | 24 | for (let index = 0; index < length; index++) { 25 | if (array1[index] !== array2[index]) { 26 | return false; 27 | } 28 | } 29 | return true; 30 | }; 31 | -------------------------------------------------------------------------------- /src/writer/linter-config/fileWriter.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import { Linter } from "eslint"; 3 | import { Options } from "prettier"; 4 | import { ConfigContainer, FileExtension } from "parser/linter-config-parser/types"; 5 | import { assertUnreachable } from "utils/utility"; 6 | 7 | function writeJSConfig(path: string, config: unknown) { 8 | const prependString = "module.exports = "; 9 | const configString = prependString + JSON.stringify(config, null, 2); 10 | 11 | fs.writeFileSync(path, configString); 12 | } 13 | 14 | function writeJSONConfig(path: string, config: unknown) { 15 | fs.writeFileSync(path, JSON.stringify(config, null, 2)); 16 | } 17 | 18 | function writePackageJSONConfig(path: string, attribute: string, config: unknown) { 19 | const fileContent = JSON.parse(fs.readFileSync(path, "utf-8")) as Record; 20 | 21 | const newFileContent = { 22 | ...fileContent, 23 | [attribute]: config, 24 | }; 25 | 26 | fs.writeFileSync(path, JSON.stringify(newFileContent, null, 2)); 27 | } 28 | 29 | export function writeEslintConfig(configContainer: ConfigContainer): void { 30 | switch (configContainer.file.extension) { 31 | case FileExtension.JS: 32 | return writeJSConfig(configContainer.file.name, configContainer.config); 33 | 34 | case FileExtension.NONE: 35 | case FileExtension.JSON: 36 | if (configContainer.file.attribute !== undefined) { 37 | return writePackageJSONConfig( 38 | configContainer.file.name, 39 | configContainer.file.attribute, 40 | configContainer.config 41 | ); 42 | } 43 | return writeJSONConfig(configContainer.file.name, configContainer.config); 44 | 45 | case FileExtension.YAML: 46 | throw new Error("YAML files are not supported for config writing"); 47 | 48 | default: 49 | assertUnreachable(configContainer.file.extension); 50 | } 51 | } 52 | 53 | export function writePrettierConfig(config: Options): void { 54 | fs.writeFileSync("./prettier.json", JSON.stringify(config, null, 2)); 55 | } 56 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, 8 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, 9 | // "lib": [], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 13 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 15 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | "outDir": "./build" /* Redirect output structure to the directory. */, 18 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "composite": true, /* Enable project compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true /* Enable all strict type-checking options. */, 29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 30 | // "strictNullChecks": true, /* Enable strict null checks. */ 31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | 43 | /* Module Resolution Options */ 44 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 45 | "baseUrl": "./src" /* Base directory to resolve non-absolute module names. */, 46 | "paths": { 47 | "generator": ["generator"], 48 | "generator/*": ["generator/*"], 49 | "migration/*": ["migration/*"], 50 | "migration": ["migration"], 51 | "e2e/*": ["e2e/*"], 52 | "e2e": ["e2e"], 53 | "dependencies/*": ["dependencies/*"], 54 | "dependencies": ["dependencies"], 55 | "writer/*": ["writer/*"], 56 | "writer": ["writer"], 57 | "parser/*": ["parser/*"], 58 | "parser": ["parser"], 59 | "logger/*": ["logger/*"], 60 | "logger": ["logger"], 61 | "utils/*": ["utils/*"], 62 | "utils": ["utils"], 63 | "types": ["types.ts"], 64 | }, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 65 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 66 | // "typeRoots": [], /* List of folders to include type definitions from. */ 67 | // "types": [], /* Type declaration files to be included in compilation. */ 68 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 69 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 70 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 71 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 72 | 73 | /* Source Map Options */ 74 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 75 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 76 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 77 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 78 | 79 | /* Experimental Options */ 80 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 81 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 82 | 83 | /* Advanced Options */ 84 | "skipLibCheck": true /* Skip type checking of declaration files. */, 85 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */, 86 | "plugins": [{ "transform": "typescript-transform-paths" }] 87 | } 88 | } 89 | --------------------------------------------------------------------------------